updates

ober

2eff0a36659af4d976f785d91c0758fcf1d0d88d

diff --git a/AGENTS.md b/AGENTS.md
index 702fc5e..410cc2c 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -425,6 +425,7 @@ Jerboa is a niche Scheme dialect with limited training data. **Never guess — a
 - **`jerboa_howto_add`**: Save new patterns to cookbook (MANDATORY when you discover something non-trivial)
 - **`jerboa_howto_run`** / **`jerboa_howto_verify`**: Validate recipes still work
 - **`jerboa_error_fix_add`**: Save error→fix mappings for common mistakes
+- **`jerboa_anti_pattern_lookup`**: Search reusable local-model mistakes and failed strategies
 
 **The knowledge base is `data/*.sexp` in THIS repo**, embedded into `jmcp` at build time. The write tools above edit it live — the server reads `data/` from disk first, with the embedded copy as fallback (`JERBOA_MCP_REPO` points every client at this repo). When you add a stdlib/language feature, also update `data/` (a cookbook recipe + `api-signatures.sexp` + `changelog.sexp`) and **commit it**. Run `make jmcp` (or `make jmcp-portable`) only to refresh the embedded copy shipped in portable binaries.
 
@@ -451,6 +452,25 @@ Jerboa is niche — every non-trivial pattern you discover prevents future sessi
 
 **Recipe format**: `id` (kebab-case), `tags` (4-6 search keywords incl. module name), `imports` (all required), `code` (complete working example), `notes` (gotchas/alternatives).
 
+### Save anti-patterns (`data/anti-patterns.sexp`) whenever you:
+- See a plausible local-model strategy that failed verification
+- Find a weak verifier pattern that allowed false success
+- See a repeated repair loop, such as broad-reading after a concrete error
+- Find a generic runtime mistake, such as missing lower-bound checks before vector access
+
+**Before saving**: check `jerboa_anti_pattern_lookup` to avoid duplicates. If none exists, call `jerboa_anti_pattern_add`; only edit `data/anti-patterns.sexp` directly if the writer tool is unavailable. Save the normalized reusable mistake, not the whole trace or benchmark name.
+
+**Anti-pattern format**: `id`, `title`, `kinds`, `severity`, `tags`, `pattern`, `avoid`, `advice`, `tools`.
+
+### Save error fixes (`jerboa_error_fix_add`) whenever you:
+- See exact compiler/runtime/verifier text with a repeatable repair
+- Hit an error that `jerboa_failure_advisor` should classify better next time
+- Debug a local-model generated-code failure where a short diagnosis prevents another failed iteration
+
+**Before saving**: check `jerboa_error_fix_lookup` with the exact error text. **Do NOT save**: one-off project business-logic mistakes.
+
+**Error-fix format**: `id`, `pattern`, `fix`; optional `type`, `explanation`, `code_example`.
+
 ### Suggest tooling improvements (`jerboa_suggest_feature`) whenever you:
 - Make multiple sequential tool calls that could be one tool
 - Fall back to bash because an MCP tool is missing or insufficient
@@ -458,7 +478,7 @@ Jerboa is niche — every non-trivial pattern you discover prevents future sessi
 **Before suggesting**: check `jerboa_list_features`; vote with `jerboa_vote_feature` if it already exists.
 
 ### Save Discoveries Mechanisms
-- **`/save-discoveries` skill**: invoke anytime to review session and save patterns + suggestions
+- **`/save-discoveries` skill**: invoke anytime to review session and save recipes, anti-patterns, error fixes, feature suggestions, and security patterns
 - **PreCompact hook**: add `PreCompact` hook with `type: "prompt"` in `.claude/settings.json` to auto-save before context compaction
 
 ---
diff --git a/data/anti-patterns.sexp b/data/anti-patterns.sexp
index 2fa969a..01df451 100644
--- a/data/anti-patterns.sexp
+++ b/data/anti-patterns.sexp
@@ -52,6 +52,19 @@
    ("tools" "jerboa_verify_plan" "jerboa_verify" "jerboa_run_tests"))
  (("advice"
     .
+    "Check both lower and upper bounds before computing a vector index or calling `vector-ref`/`vector-set!`. Prefer an `in-bounds?` helper such as `(and (>= x 0) (< x width) (>= y 0) (< y height))`.")
+   ("avoid"
+    .
+    "Do not check only `(< x width)` and `(< y height)` before vector-backed grid access; negative coordinates still pass those checks.")
+   ("id" . "upper-bound-only-grid-check")
+   ("kinds" "script" "module" "debug-error" "test")
+   ("pattern" . "vector-ref|vector-set!|not a valid index|grid|neighbor")
+   ("severity" . "high")
+   ("tags" "vector" "grid" "bounds" "index" "runtime")
+   ("title" . "Checking only upper bounds before vector grid access")
+   ("tools" "jerboa_failure_advisor" "jerboa_error_fix_lookup" "jerboa_howto" "jerboa_verify"))
+ (("advice"
+    .
     "Run `jerboa_check_balance`, read the enclosing top-level form, and replace the whole broken span.")
    ("avoid"
     .
diff --git a/data/error-fixes.sexp b/data/error-fixes.sexp
index 0b84dcc..dbae6b9 100644
--- a/data/error-fixes.sexp
+++ b/data/error-fixes.sexp
@@ -2023,4 +2023,16 @@
      "Run `jerboa_check_balance` on the generated file and inspect the reported line. For AI-copied code blocks, compare the local form with the verified reference shape and prefer replacing the whole malformed top-level form or file instead of making repeated tiny edits. In `jcode verified`, continue with `read`, `edit`, and `verify`; do not switch to unavailable shell/run tools.")
    ("id" . "unexpected-close-paren-copy-error")
    ("pattern" . "unexpected close parenthesis")
-   ("type" . "syntax")))
+   ("type" . "syntax"))
+ (("code_example"
+    .
+    ";; Bad: upper bounds only; negative x/y still pass.\n(when (and (< x width) (< y height))\n  (vector-ref grid (idx x y)))\n\n;; Good: lower and upper bounds before idx/vector-ref/vector-set!.\n(def (in-bounds? x y width height)\n  (and (>= x 0) (< x width)\n       (>= y 0) (< y height)))\n\n(when (in-bounds? x y width height)\n  (vector-ref grid (idx x y)))")
+   ("explanation"
+     .
+     "Vector-backed grids often compute indexes from coordinates. Checking only the upper bounds allows negative coordinates through, producing negative row-major indexes such as -6.")
+   ("fix"
+     .
+     "Add both lower and upper coordinate bounds before computing the index or calling vector-ref/vector-set!: `(and (>= x 0) (< x width) (>= y 0) (< y height))`. Prefer a reusable `in-bounds?` helper and use it in neighbor loops and grid accessors.")
+   ("id" . "vector-ref-negative-grid-index")
+   ("pattern" . "vector-ref: -?[0-9]+ is not a valid index|not a valid index for")
+   ("type" . "runtime-vector-bounds")))
diff --git a/docs/mcp-advisor-plan.md b/docs/mcp-advisor-plan.md
index f1743f8..dc52914 100644
--- a/docs/mcp-advisor-plan.md
+++ b/docs/mcp-advisor-plan.md
@@ -260,24 +260,28 @@ Current landed slice:
 - `data/anti-patterns.sexp` stores reusable local-model mistakes with avoid/do
   guidance and follow-up tools.
 - `jerboa_anti_pattern_lookup` exposes the anti-pattern data directly.
+- `jerboa_anti_pattern_add` adds or updates reusable anti-pattern entries.
+- `/save-discoveries` now treats anti-patterns and error fixes as first-class
+  outputs: use `jerboa_error_fix_add` for reusable exact error repairs and
+  `jerboa_anti_pattern_add` for reusable local-model mistakes.
 - Recipe selection now expands task queries by inferred kind so tasks can find
   useful script/module/docs/debug recipes even when the exact user wording is
   not in the cookbook.
-- Protocol tests cover tool visibility, advisor output, failure diagnosis, and
-  the upgraded cookbook bundle.
+- Protocol tests cover tool visibility, advisor output, failure diagnosis, the
+  upgraded cookbook bundle, and anti-pattern add/lookup round-trips.
 - `jcode verified` now calls a registered MCP `jerboa_request_advisor` before
-  Jerboa-looking runs and appends `jerboa_failure_advisor` output to failed
-  verifier details when that MCP tool is available.
+  Jerboa-looking runs and appends source targets, `jerboa_failure_advisor`,
+  `jerboa_error_fix_lookup`, and `jerboa_anti_pattern_lookup` output to failed
+  verifier details when those MCP tools are available.
 
 Remaining work:
 
-- Add writer tooling for anti-patterns only if the read path proves useful
-  enough to need live updates.
+- Convert advisor outputs to optional structured JSON blocks so `jcode` can
+  enforce repair actions without parsing prose.
 
 ## Open Questions
 
 - Should advisor outputs be pure text, pure JSON, or text plus a JSON block?
-- Should `jmcp` own a data-backed anti-pattern registry from the start?
 - Should jcode call the failure advisor automatically, or only inject the
   request advisor up front?
 - How much cookbook code is safe to include by default for very small context
diff --git a/mcp/server.ss b/mcp/server.ss
index b8b392d..e7a6206 100644
--- a/mcp/server.ss
+++ b/mcp/server.ss
@@ -45,6 +45,7 @@
     "jerboa_verify_plan"
     "jerboa_failure_advisor"
     "jerboa_anti_pattern_lookup"
+    "jerboa_anti_pattern_add"
     "jerboa_task_workflow_advisor"
     "jerboa_compact_tool_manifest"
     "jerboa_script_scaffold_verify"
@@ -1069,6 +1070,7 @@
     ("verify_plan" "verify command plan oracle jcode verified write scope")
     ("failure_advisor" "failure advisor repair error verify output compiler diagnosis next action")
     ("anti_pattern_lookup" "anti pattern lookup local model mistakes avoid guidance failure")
+    ("anti_pattern_add" "add anti pattern antipattern local model mistake avoid guidance failure writer")
     ("task_workflow_advisor" "workflow plan advisor local model agent mcp next tools")
     ("compact_tool_manifest" "manifest guide local model small context tool order")
     ("script_scaffold_verify" "script command scaffold cli args verify write file")))
@@ -5381,6 +5383,41 @@
               (bullet-lines (fallback-anti-patterns kind))
               (bullet-lines (map anti-pattern-line matches))))))))
 
+(def (missing-required-string? value)
+  (or (not (string? value))
+      (= (string-length (string-trim value)) 0)))
+
+(def (tool-anti-pattern-add args)
+  (let* ([path (hash-get* args "anti_patterns_path" (data-path "anti-patterns.sexp"))]
+         [id (hash-get* args "id" #f)]
+         [title (hash-get* args "title" #f)]
+         [avoid (hash-get* args "avoid" #f)]
+         [advice (hash-get* args "advice" #f)]
+         [kinds (hash-get* args "kinds" (list "all"))]
+         [severity (hash-get* args "severity" "medium")]
+         [tags (hash-get* args "tags" '())]
+         [pattern (hash-get* args "pattern" "")]
+         [tools (hash-get* args "tools" '())]
+         [entries (load-anti-patterns path)])
+    (if (or (missing-required-string? id)
+            (missing-required-string? title)
+            (missing-required-string? avoid)
+            (missing-required-string? advice))
+        (text-result "id, title, avoid, and advice are required." #t)
+        (let ([entry (jhash "id" id
+                            "title" title
+                            "kinds" kinds
+                            "severity" severity
+                            "tags" tags
+                            "pattern" pattern
+                            "avoid" avoid
+                            "advice" advice
+                            "tools" tools)])
+          (let ([updated (upsert-by-id entries entry)])
+            (write-json-file path updated)
+            (text-result
+             (string-append "Added/updated anti-pattern \"" id "\" in " path ".")))))))
+
 (def (tool-verify-plan args)
   (let* ([task-raw (task-text-raw args)]
          [task (string-downcase task-raw)]
@@ -5577,6 +5614,7 @@
     "jerboa_verify_plan"
     "jerboa_failure_advisor"
     "jerboa_anti_pattern_lookup"
+    "jerboa_anti_pattern_add"
     "jerboa_task_workflow_advisor"
     "jerboa_compact_tool_manifest"
     "jerboa_cookbook_task_bundle"
@@ -5607,6 +5645,7 @@
     [(string=? name "jerboa_verify_plan") "recommend verifier command and write scope"]
     [(string=? name "jerboa_failure_advisor") "diagnose verifier failure and next repair move"]
     [(string=? name "jerboa_anti_pattern_lookup") "task-specific mistakes to avoid"]
+    [(string=? name "jerboa_anti_pattern_add") "save reusable local-model mistakes"]
     [(string=? name "jerboa_task_workflow_advisor") "first call for a task-specific plan"]
     [(string=? name "jerboa_compact_tool_manifest") "small-context catalog and call order"]
     [(string=? name "jerboa_cookbook_task_bundle") "package relevant cookbook recipes for any task"]
@@ -7790,6 +7829,21 @@
                          '())
                  tool-anti-pattern-lookup #t
                  '("anti_pattern_lookup" "anti_patterns" "antipatterns"))
+  (register-tool "jerboa_anti_pattern_add" "Anti-Pattern Add"
+                 "Add or update a reusable local-model anti-pattern."
+                 (schema (list (list "anti_patterns_path" (property "string" "Anti-pattern data path"))
+                               (list "id" (property "string" "Anti-pattern id"))
+                               (list "title" (property "string" "Title"))
+                               (list "kinds" (property "array" "Applicable task kinds"))
+                               (list "severity" (property "string" "Severity"))
+                               (list "tags" (property "array" "Tags"))
+                               (list "pattern" (property "string" "Search or regex pattern"))
+                               (list "avoid" (property "string" "Mistake to avoid"))
+                               (list "advice" (property "string" "Repair guidance"))
+                               (list "tools" (property "array" "Recommended follow-up tools")))
+                         '("id" "title" "avoid" "advice"))
+                 tool-anti-pattern-add #t
+                 '("anti_pattern_add" "anti_patterns_add" "antipattern_add"))
   (register-tool "jerboa_security_scan" "Security Scanner"
                  "Static security scanner for Jerboa, C, and Rust code."
                  (schema (list (list "file_path" (property "string" "Single file"))
@@ -8177,7 +8231,7 @@
                                (list "search_all" (property "boolean" "Return all matches"))
                                (list "error_fixes_path" (property "string" "Error fixes path")))
                          '("error_message"))
-                 tool-error-fix-lookup #f '())
+                 tool-error-fix-lookup #t '())
   (register-tool "jerboa_error_fix_add" "Error Fix Add"
                  "Add or update an error-fix mapping."
                  (schema (list (list "error_fixes_path" (property "string" "Error fixes path"))
diff --git a/mcp/test/protocol-test.ss b/mcp/test/protocol-test.ss
index 227c9d4..fe947c6 100644
--- a/mcp/test/protocol-test.ss
+++ b/mcp/test/protocol-test.ss
@@ -101,6 +101,7 @@
 (def cookbook-file (path-join "/tmp" "jerboa-mcp2-cookbook.sexp"))
 (def features-file (path-join "/tmp" "jerboa-mcp2-features.sexp"))
 (def errors-file (path-join "/tmp" "jerboa-mcp2-errors.sexp"))
+(def anti-patterns-file (path-join "/tmp" "jerboa-mcp2-anti-patterns.sexp"))
 (def doc-file (path-join "/tmp" "jerboa-mcp2-doc.md"))
 (def security-rules-file (path-join "/tmp" "jerboa-mcp2-rules.sexp"))
 (def ffi-file (path-join "/tmp" "jerboa-mcp2-ffi.ss"))
@@ -117,6 +118,7 @@
 (def auto-src-dir (path-join auto-project "src" "tmp" "auto"))
 (def auto-lib-file (path-join auto-src-dir "lib.sls"))
 (def auto-use-file (path-join auto-src-dir "use.ss"))
+(guard (e [else #f]) (delete-file anti-patterns-file))
 (write-file-string doc-file "Done mcp/server.ss\n")
 (write-file-string ffi-file "(load-shared-object \"libx\")\n(def c-read (foreign-procedure \"read\" () int))\n(set! x 1)\n(system \"ls\")\n#\\escape\n")
 (write-file-string variadic-file "(import (jerboa prelude))\n(def c-fcntl (foreign-procedure \"fcntl\" (int int int) int))\n")
@@ -622,9 +624,27 @@
                (alist->hash-table
                 (list (cons "task" "write a command line script in Jerboa")
                       (cons "file_path" "tool.ss")
+                      (cons "max_results" 3))))
+    (call-tool 124 "jerboa_anti_pattern_add"
+               (alist->hash-table
+                (list (cons "anti_patterns_path" anti-patterns-file)
+                      (cons "id" "tmp-anti-pattern")
+                      (cons "title" "Temporary anti-pattern")
+                      (cons "kinds" (list "debug-error"))
+                      (cons "severity" "medium")
+                      (cons "tags" (list "tmp" "bounds"))
+                      (cons "pattern" "tmp-vector-error")
+                      (cons "avoid" "Do not repeat the temporary failing edit.")
+                      (cons "advice" "tmp anti-pattern advice")
+                      (cons "tools" (list "jerboa_failure_advisor")))))
+    (call-tool 125 "jerboa_anti_pattern_lookup"
+               (alist->hash-table
+                (list (cons "anti_patterns_path" anti-patterns-file)
+                      (cons "task" "tmp-vector-error while debugging")
+                      (cons "kind" "debug-error")
                       (cons "max_results" 3)))))))
 
-(check "expected 123 responses" (= (length responses) 123))
+(check "expected 125 responses" (= (length responses) 125))
 
 (def init-result (result (car responses)))
 (check "initialize server name"
@@ -646,6 +666,7 @@
 (check "tools/list includes verify plan" (has-tool? tools "jerboa_verify_plan"))
 (check "tools/list includes failure advisor" (has-tool? tools "jerboa_failure_advisor"))
 (check "tools/list includes anti-pattern lookup" (has-tool? tools "jerboa_anti_pattern_lookup"))
+(check "tools/list includes anti-pattern add" (has-tool? tools "jerboa_anti_pattern_add"))
 
 (check "eval returns 3"
        (string-contains (content-text (result (list-ref responses 2))) "3"))
@@ -1031,5 +1052,12 @@
        (and (string-contains (content-text (result (list-ref responses 122))) "Jerboa anti-pattern lookup")
             (string-contains (content-text (result (list-ref responses 122))) "script-written-as-library")))
 
+(check "anti-pattern add writes temp mapping"
+       (string-contains (content-text (result (list-ref responses 123))) "tmp-anti-pattern"))
+
+(check "anti-pattern lookup finds temp mapping"
+       (and (string-contains (content-text (result (list-ref responses 124))) "tmp-anti-pattern")
+            (string-contains (content-text (result (list-ref responses 124))) "tmp anti-pattern advice")))
+
 (display "protocol-test: PASS")
 (newline)