@abarcenas/ai-workflow-template 1.41.0 → 1.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -227,3 +227,110 @@
227
227
  - **researcher**: CLI-search diagnostics are the fastest way to compare thresholds — running the SAME query through `knowledgebase-cli.js` at `--threshold 0.0` vs `--threshold 0.6` reproduced the bug exactly and quantified the real similarity range (0.01–0.41). When an MCP tool returns empty but the data exists, use the CLI to isolate whether the problem is the tool's defaults vs. the data itself.
228
228
  - **coder**: When fixing a default-value bug (`||` → `??`), scan sibling call sites for the same falsy-coercion pattern — the same class of bug can hide in `args.limit || 5` (also in this handler) and any other `||` default.
229
229
  - **tracker**: Record MCP-server restart requirements as a universal gotcha — live MCP tools run long-lived processes that do NOT hot-reload code changes; document the restart step in verification notes.
230
+
231
+ ## Session: 2026-08-02 — PG Knowledgebase Write Bug Fix (registerProject upstream)
232
+
233
+ **Pipeline:** implementer (bootstrap) → implementer (planning) → coder (Batch A: T1+T2 combined, T5) → coder (Batch B: T3) → unit-tester (T4) → reviewer → tracker
234
+ **Coverage:** 176/176 tests passing across 10 test files, 0 failures; `mcp-knowledgebase-server.js` at 79.66% stmts / 81.03% lines (up from 0% test coverage — the MCP server previously had zero tests)
235
+ **TDD Iterations:** 0 (bug-fix pipeline, not TDD)
236
+
237
+ **New knowledge:**
238
+ - **FK-violation bug class: writing to a child table before ensuring the parent row exists** — `knowledge_chunks.project_id` has a FOREIGN KEY to `projects(id)`. The MCP `knowledgebase_index` handler called `upsertChunks()` without ever calling `registerProject()`, so inserts for non-bootstrap-registered projects failed with PG error `23503` (foreign_key_violation). **The failure was invisible**: `upsertChunks()`'s generic catch-all swallowed the error and counted the chunk as `skipped`, producing a misleading "indexed 0, updated 0, skipped N" result. **Universal pattern:** when a handler writes rows that FK-reference a parent table, ensure the parent row exists first (idempotent upsert), and audit catch-all error handlers that can silently convert real failures into "skipped" counts.
239
+ - **The registerProject fix pattern for the MCP server mirrors the CLI** — `scripts/knowledgebase-cli.js:122` already called `registerProject(projectId, projectId)` before `upsertChunks()`. The MCP server was the only caller missing it. **Universal pattern:** when one entry point in a family has the correct call sequence, diff all sibling entry points (CLI vs MCP vs setup) for the same missing call — the "one fixed, sibling missed" pattern has now hit this project repeatedly (dotenv imports, threshold defaults, registerProject).
240
+ - **Call ordering convention: guard first, then register, then upsert** — the `registerProject()` call goes AFTER the `chunks.length === 0` guard (avoids a DB call for empty input) and BEFORE `upsertChunks()`. `registerProject()` is idempotent (`INSERT … ON CONFLICT DO UPDATE`) so re-indexing existing projects is a no-op. Placing it after the guard matches the CLI convention and avoids creating parent rows for invalid/empty content.
241
+ - **`(projectId, projectId)` argument convention** — `registerProject(projectId, name)` is called with `projectId` for both args because project ID == display name in this system. The CLI and MCP server both use this convention.
242
+ - **Testability seam for MCP stdio servers: export the handler + guard the bootstrap** — to unit-test an MCP server that auto-starts a stdio transport on import, (1) extract the `CallToolRequestSchema` handler into an exported `handleToolCall(request)` (body unchanged) and (2) guard the `main()`/SIGTERM bootstrap with an `isDirectRun` check (`process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)`). Importing the module in tests then opens no transport and exits cleanly; direct execution (`node scripts/mcp-knowledgebase-server.js`) is unchanged. This seam enabled the MCP server's first-ever test suite (16 tests).
243
+ - **Mutation testing proves a regression guard is non-vacuous** — after writing the tests, the unit-tester and reviewer independently removed the `registerProject` call in a temp copy and re-ran the suite: TEST-01 (`toHaveBeenCalledTimes(1)` got 0) and TEST-16 failed. A passing suite alone does not prove a test catches the regression — only a mutation check does. This is now the established validation for any "regression guard" claim.
244
+ - **Order assertions with `mock.invocationCallOrder`** — to assert `registerProject` resolves BEFORE `upsertChunks` starts, compare `kb.registerProject.mock.invocationCallOrder[0]` vs `kb.upsertChunks.mock.invocationCallOrder[0]` rather than only checking call counts. Call-count assertions catch "called or not", but not "called in the right order".
245
+ - **Coverage gap on the direct-run bootstrap is acceptable for unit tests** — remaining uncovered lines (`ListToolsRequestSchema` tools builder, `main()`, `isDirectRun`, SIGTERM) require either production seams or a manual live-spawn smoke test (plan TEST-12). The 79.66% file coverage is a huge improvement over the previous 0%; the global 40.93% below the 90% gate is pre-existing and out of scope.
246
+
247
+ **Agent tuning notes:**
248
+ - **implementer (planning)**: When two tasks edit the SAME file, merge them into one task — the plan's T1+T2 both touched `scripts/mcp-knowledgebase-server.js`, and the orchestrator combined them into a single coder invocation to avoid read-modify-write clobbering. Also, when planning a fix for a missing call, verify the correct pattern exists elsewhere in the codebase (the CLI was the reference) and make the plan's snippet match the ACTUAL file style — the plan claimed "semicolon-free convention" but the server file uses semicolons, a minor doc/code mismatch the reviewer flagged.
249
+ - **coder**: When adding an imported function for the first time, verify it is actually exported from the source module before writing the import (this plan confirmed `registerProject` is exported at `knowledgebase-index.js:782`). Keep the fix minimal and sibling-call-site consistent — one file, two lines, plus the testability seam.
250
+ - **unit-tester**: For MCP servers, mock the core engine (`vi.mock('./knowledgebase-index.js')`) and `node:fs`'s `readFileSync` to test handler logic hermetically — no DB, no stdio. Assert call ORDER with `invocationCallOrder` in addition to call counts. Run a mutation test on the fix line to prove the guard catches regression.
251
+ - **reviewer**: Re-run the mutation test independently (the reviewer's run failed 2 tests vs the unit-tester's 1 — both prove non-vacuous). Distinguish pre-existing nits (schema default 0.6 mismatch, raw `err.message`, whitespace projectId, plan wording) from fix-introduced issues. The `isDirectRun` symlink concern is theoretical and non-blocking.
252
+ - **tracker**: Record FK-parent-insertion and "one-entry-point-fixed, sibling-missed" as universal patterns — both are recurring bug classes in this codebase. The MCP-server testability seam (export handler + `isDirectRun` guard) is a reusable pattern for any stdio MCP server needing unit tests.
253
+
254
+ ## Session: 2026-08-02 — Knowledgebase registerProject Follow-up (nits, TEST-12 live smoke, fail-closed redaction)
255
+
256
+ **Pipeline:** coder (nits) → unit-tester (TEST-12 live-spawn) → reviewer (⚠️ CHANGES REQUESTED) → coder (redaction hardening) → reviewer (✅ APPROVED) → tracker
257
+ **Coverage:** 181/181 tests passing across 10 test files, 0 failures; MCP server test file 21/21
258
+ **TDD Iterations:** 0 (bug-fix follow-up pipeline, not TDD)
259
+
260
+ **New knowledge:**
261
+ - **`new URL()` throws on unix-socket-style connection-string authorities — URL-parse-based redaction FAILS OPEN** — `postgres://user:secret@/var/run/postgresql` (and `postgresql://user:secret@/tmp?host=/tmp`) make `new URL()` throw "Invalid URL". A helper with `try { new URL(match[1]) } catch { return message }` returns the message UNCHANGED, leaking `user:secret` into MCP error responses. The engine helper (`knowledgebase-index.js:62`) fails CLOSED (`'***'`) for the same input — a local helper must never be WEAKER than the engine it mirrors. **Universal pattern:** for credential redaction in error messages, prefer a pure regex that strips `userinfo@` (`/(postgres(?:ql)?:\/\/)([^/\s]+)@/gi → '$1***@'`) over URL parsing — there is no parse to fail, so unparseable tokens are redacted by construction (fail-closed).
262
+ - **`[^/\s]+` beats `[^@\s]+` for credential redaction** — the reviewer suggested `[^@\s]+`, but that stops at the first `@`, leaking a password containing `@` (`postgres://user:p@ss@host` → would leave `ss@host`). `[^/\s]+` consumes the entire userinfo up to the authority delimiter, fully redacting such passwords. Socket-only (`postgres:///var/run/postgresql`) and host-only (`postgres://10.0.0.5:5432/db`) forms have no `@` → regex matches nothing → message unchanged (no over-matching).
263
+ - **Global-flag regex redacts ALL tokens in one pass** — `message.replace(/.../gi, ...)` with the `g` flag rewrites every `postgres://` token in a message, satisfying "redact all tokens" without a loop. A parse-based helper that does `replace(match[1], redacted)` and returns only redacts the FIRST token — and would leak a second unparseable token when the first parses.
264
+ - **Live-spawn smoke tests are the proof for direct-run bootstrap paths** — the MCP server's `main()`/`isDirectRun`/SIGTERM path is not unit-testable without production seams. A real stdio spawn against the live DB (`DATABASE_URL` in `.env`, absent from process env) closed that last coverage gap: `initialize` handshake OK, `tools/call knowledgebase_index` with a fresh projectId returned **"Indexed 1 chunks, updated 0, skipped 0"** (NOT "skipped 1") — direct evidence `registerProject()` ran before `upsertChunks()`. Cleanup (DELETE test rows) + post-cleanup `SELECT count(*)` verified 0 residue; SIGTERM handled cleanly.
265
+ - **Whitespace-validation strengthening made the regression guard STRICTLY stronger** — after adding `!projectId?.trim()` + trim-flow, the mutation test (remove `registerProject(pid, pid)`) now fails **3 tests** (TEST-01 `toHaveBeenCalledTimes(1)`, TEST-16 error path, TEST-06c `toHaveBeenCalledWith('test-project','test-project')`) instead of 2. A trim-downstream assertion also fails when the call is missing — validation fixes can accidentally weaken guards, but here the new trim test added a third independent detector.
266
+ - **Non-vacuous redaction tests via replay** — the new redaction tests (TEST-17/18/19) exercise the real outer-catch path by rejecting `upsertChunks` with URL-bearing messages. Non-vacuousness was proven by replaying the OLD helper (from git diff): all 3 new tests fail against it (TEST-17 leaks `user:secret`, TEST-18 output mismatch + leak, TEST-19 leaks the second token). Replaying the pre-fix implementation is a cheap, decisive way to prove a security test matters.
267
+ - **Helper kept private; tests hit the real production path** — the redaction helper is intentionally NOT exported. Tests reject `upsertChunks` with error messages containing URLs, so they verify the actual outer-catch behavior (`Error: ${redactConnectionString(err.message)}`) rather than the helper in isolation — zero production surface change.
268
+
269
+ **Agent tuning notes:**
270
+ - **reviewer**: When a redaction/security helper is introduced, probe it with unparseable input classes (unix-socket authorities, invalid percent-encoding, `?host=` variants) — a passing happy-path test does not prove fail-closed behavior. Empirically copy the exact helper code into node and run it against adversarial inputs before trusting it. Also re-run the full suite independently (reviewer's run must match the unit-tester's count) and replay the old implementation to confirm new tests are non-vacuous.
271
+ - **coder**: For fail-closed security fixes, prefer regex-only construction ("nothing to fail") over catch-fallback layers ("a second failure mode can still leak"). When choosing a regex character class for userinfo redaction, prefer `[^/\s]+` (stops at the authority delimiter) over `[^@\s]+` (stops at the first `@`). Always add a JSDoc rationale explaining WHY the construction is fail-closed so the next reader doesn't "simplify" it back into a parse-based helper.
272
+ - **unit-tester**: A live-spawn smoke test needs disciplined cleanup + verification — delete test rows via direct SQL, then `SELECT count(*)` to prove 0 residue, and terminate via SIGTERM to exercise the shutdown path. When validating a mutation guard after unrelated validation changes, re-run the mutation test: the expected failure count can legitimately grow (3 tests now) — that is strictly stronger, not a problem.
273
+ - **tracker**: Record regex-redaction and live-spawn-smoke-test as reusable patterns; they complement the existing MCP testability-seam pattern and apply to any stdio MCP server that surfaces raw error messages.
274
+
275
+ ## Session: 2026-08-02 — Knowledgebase Final Minor-Hygiene Pass (search trim, limit `??`, query-string redaction)
276
+
277
+ **Pipeline:** coder (3 minors) → unit-tester (independent validation) → reviewer (final sign-off) → tracker
278
+ **Coverage:** 185/185 tests passing across 10 test files, 0 failures; MCP server test file 25/25
279
+ **TDD Iterations:** 0 (bug-fix hygiene follow-up pipeline, not TDD)
280
+
281
+ **New knowledge:**
282
+ - **Search-filter vs write-target asymmetry is a deliberate design pattern** — for an OPTIONAL, read-only filter (`knowledgebase_search.projectId`), an invalid/whitespace-only value DEGRADES to match-all (`undefined`) instead of erroring; for a REQUIRED write-target (`knowledgebase_index.projectId`), a whitespace-only value hard-errors. The asymmetry is intentional (read-only filters may degrade; write targets must not silently target the wrong scope) and MUST be documented in a code comment at the call site so future readers don't "fix" it into a surprising inconsistency. Trim happens at the handler boundary (`args.projectId?.trim() || undefined`) and the engine only applies the `AND project_id = $n` clause when the filter is truthy.
283
+ - **`?? 5` vs `|| 5` — explicit-zero honoring (same falsy-coercion class as `threshold`)** — `args.limit || 5` silently coerces an explicit `limit: 0` to the default 5 because `0` is falsy. `args.limit ?? 5` honors `limit: 0` (`LIMIT 0`), which is legitimate caller intent since the engine passes `limit` straight to SQL. This is the SAME bug class as the earlier `args.threshold || 0.6` → `?? 0.1` fix — **Universal pattern:** after fixing one `||`-default to `??`, scan ALL sibling `|| default` expressions in the same handler/file; a `limit`/`topK` default is typically the next one hiding the same bug.
284
+ - **Query-string/fragment redaction extends the regex pattern without losing fail-closed** — the pure-regex redaction helper was extended from `/(postgres(?:ql)?:\/\/)([^/\s]+)@/gi → '$1***@'` to `/(postgres(?:ql)?:\/\/)([^/\s]+)@([^?#\s]*)([?#][^\s]*)?/gi` with a callback that replaces the query or fragment with `?***`/`#***` while preserving host/path (`postgres://user:secret@host:5432/kb?password=hunter2` → `postgres://***@host:5432/kb?***`). Because it is still a pure regex with no `new URL()`, the fail-closed-by-construction property is preserved — unparseable tokens remain redacted, `@`-in-password still fully redacted, non-URL messages unchanged. **Universal pattern:** a fail-closed regex can be EXTENDED (add capture groups + callback) without reopening the failure mode that URL parsing introduced.
285
+ - **Real temp mutations are the decisive non-vacuity proof for hygiene fixes** — the unit-tester created a temp copy of the server + tests + real engine and reverted each fix one at a time: `?? 5`→`|| 5` fails TEST-21; trim removed fails TEST-20+TEST-20b; old redaction regex fails TEST-18+TEST-22; removing `registerProject` fails TEST-01+06c+16. Four independent guards, four matching mutations — each new test genuinely catches its fix's regression. This is the established validation standard (mutation testing) applied to one-line hygiene fixes, not just the original bug fix.
286
+ - **Informational schema descriptions can go stale when defaults honor explicit zero** — after `?? 5`, the search schema `limit` description "(1–50)" no longer describes actual behavior (0 is now honored). When a default's semantics change, audit the accompanying inputSchema description for the same staleness.
287
+ - **CLI/MCP parity is a separate, constraint-scoped concern** — `knowledgebase-cli.js` still uses `topK || 5` and an untrimmed `--project`; it was deliberately out of scope for the MCP-focused hygiene pass. Parity fixes across sibling entry points are best tracked explicitly as a future item rather than silently expanded mid-pipeline.
288
+
289
+ **Agent tuning notes:**
290
+ - **coder**: When applying a `||`→`??` default fix, grep the same file/handler for other `|| default` expressions (this pass found `args.limit || 5` one line away from the already-fixed `threshold`). When extending a fail-closed regex, preserve the "no URL parse → nothing to fail" property and update the JSDoc rationale so the next reader doesn't regress it to a parse-based helper.
291
+ - **unit-tester**: For one-line hygiene fixes, still run real temp mutations (not reasoning) to prove each new test is non-vacuous — this pass's 4-guard mutation matrix is the reference pattern. Confirm the exact count with the coder's report (25/25 and 185/185 matched).
292
+ - **reviewer**: Run an empirical regex probe (12 cases: unix-socket, `?host=`, `?password=`, `#fragment`, multi-token, `@`-in-password, non-URL, socket/host-only, uppercase scheme) against the EXACT helper code before approving, and verify git diff scope discipline (only the intended files changed). Distinguish informational doc nits (schema description stale) from code issues.
293
+ - **tracker**: Record the search-filter vs write-target asymmetry and the extended-regex-preserves-fail-closed pattern as reusable design knowledge; note CLI/MCP parity gaps as explicit follow-ups rather than letting them fall through the cracks.
294
+
295
+ ## Session: 2026-08-03 — README Trim + Instructions-on-Top (docs accuracy audit)
296
+
297
+ **Pipeline:** coder (README rewrite) → reviewer (✅ APPROVED) → coder (1-line graphify nit) → tracker
298
+ **Coverage:** N/A (documentation-only pipeline — no code or tests changed; 185/185 suite unaffected)
299
+ **TDD Iterations:** 0 (docs pipeline, not TDD)
300
+
301
+ **New knowledge:**
302
+ - **README/doc accuracy-audit pattern — verify every factual claim against the filesystem and CLI before writing** — the rewrite's four corrections came from an explicit verification pass: skill counts via `ls .agents/skills/*/SKILL.md | wc -l` (claimed 27, actually 29), file paths via `ls` (orchestrator agent at `.opencode/agents/orchestrator/orchestrator.agent.md`), CLI flags via `node bin/setup.js --help` (added the real `--knowledgebase`), and existence checks for referenced directories (`.agents/compress/` and `.agents/agents/` do NOT exist and were removed). **Universal pattern:** any documentation that enumerates filesystem entities (skill/agent counts, paths, dirs) or CLI surface (flags, commands) MUST be verified against the actual filesystem and `--help` output before writing — a docs edit that "looks right" but contains stale facts is worse than a long README.
303
+ - **"Instructions on the very top" means the first screen (~30 lines), not "somewhere near the top"** — the accepted structure is: title → one-line description → **Install** → **Git Hook Setup** (first screen), then a condensed capability list, then reference sections. For a developer-tool README, a new user must be able to install and configure without scrolling. Instructions confirmed at lines 1–51; Install at line 5, Git Hook Setup at line 13.
304
+ - **Fluff-cut criterion: drop only what is non-actionable; preserve every command/option/path/table/link** — removed: emoji decoration, salesy intro prose, a verbose run-flow list (condensed to 2 bullets), a YAML frontmatter example (condensed to 1 sentence), stale structure-tree entries. Survived (verified via diff audit + grep): every install/setup/sync command, all setup options (incl. `--knowledgebase`), orchestrator usage examples, all 11 agent-table rows + paths, TDD table, memory-bank MCP tools, local dev commands (cross-checked against package.json), publish flow + NPM_TOKEN steps, external links. 469 → 289 lines (−38%) with zero actionable-content loss.
305
+ - **Global skills vs package skills is a real documentation trap** — `graphify` lives at `~/.config/opencode/skills/` (installed globally), NOT in `.agents/skills/` (package-shipped). Only `graphify-framework-aware` is shipped with the package. The reviewer caught the README's Featured Skills list presenting `graphify` as a package skill; the fix marks it `(global skill)` and adds the actually-shipped sibling. **Universal pattern:** when documenting skills/tools, verify WHERE each one lives (global config vs repo) before claiming it is part of the distributed package.
306
+ - **markdownlint as a doc-quality gate — separate structural errors from stylistic noise** — `npx markdownlint-cli2 README.md` reported **0 structural errors** after the rewrite; the only remaining finding is MD013 (line-length), a stylistic default with no repo config (39 findings vs 42 in the old README). The old README failed structural rules MD001/032/040/058/060/031 — all now clean. Run markdownlint with MD013 disabled (or interpret it separately) so structural quality stays visible despite default line-length noise.
307
+ - **The reviewer's independent re-verification is the trust anchor for docs changes** — the reviewer re-confirmed all 4 coder accuracy claims (skills 29, orchestrator path exists, `--knowledgebase` real, removed dirs don't exist) against the filesystem/CLI rather than trusting the coder's report, and diff-audited the old vs new README to prove no actionable content was lost. Even a small docs change warrants the verify-before-trust loop.
308
+
309
+ **Agent tuning notes:**
310
+ - **coder**: For README/doc rewrites, run the fact-verification pass FIRST (counts, paths, flags) and keep a checklist of claims you changed; verify every command against `package.json` scripts and every flag against `--help` before writing. After the edit, run markdownlint (0 structural errors) and grep to confirm no command/table/option was dropped.
311
+ - **reviewer**: For docs pipelines, perform independent accuracy spot-checks (skill counts via filesystem, paths via `ls`, flags via `--help`, existence of removed/kept references) and diff-audit against the old version to confirm only non-actionable content was removed — distinguish "dropped" (fluff) from "lost" (actionable). A single informational nit (global vs package skill) is exactly what an accuracy-focused review catches.
312
+ - **tracker**: Record the doc-accuracy verification pattern as universal — the "skill count wrong / path wrong / flag missing / removed dirs still referenced" cluster is the recurring failure mode in README maintenance, and the fix pattern (verify against filesystem + CLI `--help` before writing, then reviewer re-verification) applies to any documentation that enumerates filesystem entities or CLI surface.
313
+
314
+ ## Session: 2026-08-03 — Vocab-Sync: Pre-commit Auto-Update of Memory-Bank Vocabulary JSON
315
+
316
+ **Pipeline:** implementer (bootstrap) → implementer (planning) → coder (Batch A: 7 parallel) → coder (Batch B: 3 parallel) → unit-tester → reviewer (⚠️ CHANGES REQUESTED) → coder (review fixes) → reviewer (✅ APPROVED) → tracker
317
+ **Coverage:** 259/259 tests passing across 12 files, 0 failures; setup suite 104/104 across 6 files; vocab-sync.js 98.91% stmts / 92.15% branch / 100% lines; validate-memory-schema.js 92.7% stmts / 93.1% branch / 92.47% lines (both ≥ 90% gate)
318
+ **TDD Iterations:** 0 (feature pipeline, not TDD)
319
+
320
+ **New knowledge:**
321
+ - **Staged-files-only pre-commit auto-sync is the right scope for vocabulary maintenance** — `scripts/vocab-sync.js` scans ONLY staged memory-bank files (`git diff --cached --name-only --diff-filter=ACM`, `memory-bank/*.md`, excluding `/.index/`), builds a known-tag set from ALL `tags.*` group values PLUS `entity_patterns` keys, normalizes candidates (lowercase → kebab-case, drop empty, dedupe), appends genuinely-new tags to the `topic` catch-all group (sorted, idempotent), and runs `git add memory-bank/.vocabulary.json` inside try/catch so the vocab commits with the same change. Warning-only contract: exits 0 in every graceful path (no staged files → silent; missing/malformed vocab → warn + skip); only unhandled errors exit 1. Scanning ALL memory-bank files instead would produce noisy writes from unrelated files — the staged diff is the only thing that matters in a pre-commit context.
322
+ - **Sync-before-validate ordering eliminates the chicken-and-egg false positive** — `vocab-sync.js` must run BEFORE `validate-memory-schema.js` in the pre-commit hook (both this repo's `.husky/pre-commit` and the setup tool's `TEMPLATE_HOOKS['pre-commit'].content`), so first-time tags are appended to `.vocabulary.json` before the validator's unknown-tag check. Verified empirically in a scratch temp git repo: staged `brand-new-tag` → vocab-sync appended it → validator ran with NO unknown-tag warning, exit 0 → second vocab-sync run was a silent no-op (idempotent). **Universal pattern:** when a validator warns about data that a companion tool can auto-heal, order the healer FIRST in the same hook.
323
+ - **`entity_patterns` keys are known tags — a validator can produce false-positive warnings by ignoring them** — the original trigger was pre-commit warnings `[float32array, learned-knowledge, npm, package-structure, readme]`; the validator only checked `tags.*` groups, but entity/tool names (`npm`, `vitest`, `playwright`, …) live as `entity_patterns` keys, not tags. Fix: `allTags = [...Object.values(vocab.tags).flat(), ...Object.keys(vocab.entity_patterns || {})]` with a `|| {}` guard so a vocab lacking the key doesn't crash. **Universal pattern:** when auditing "unknown X" warnings, enumerate ALL places valid values can live, not just the obvious collection.
324
+ - **Normalization PARITY between producer and consumer is a hidden correctness requirement (reviewer M1)** — vocab-sync normalizes tags (lowercase/kebab) but the validator originally compared raw frontmatter tags exactly, so a mixed-case `My Tag` still warned on every commit after vocab-sync appended `my-tag` — the chicken-and-egg fix was incomplete for non-normalized tags, and no test covered it (all 15 validator tests used lowercase). Fix: the validator applies the SAME `normalizeTag` to both frontmatter tags and vocab `allTags` before the unknown-tag filter (+3 tests: `My Tag` no longer flagged once `my-tag` in vocab; controls still warn). **Universal pattern:** when two tools normalize the same input, they must normalize IDENTICALLY — duplicate the normalization on both sides or the "known" set drifts.
325
+ - **Strict JSON forbids comment syntax in docs instructions (reviewer M2)** — an instruction telling agents to "add a one-line comment in the JSON" is impossible in strict JSON; following it literally corrupts `.vocabulary.json`. The correct instruction: record rationale in the commit message/PR/review report or in existing string fields like `categories` descriptions, with an explicit "strict JSON — no comment syntax" warning. **Universal pattern:** before writing documentation that instructs editing a data file, verify the instruction is valid for that file's format — reviewers should grep doc instructions for format-illegal suggestions.
326
+ - **Consumer-controlled malformed data needs array guards (reviewer minor 1)** — `buildKnownTags` iterates `Object.values(vocab.tags)`; if a consumer's `vocab.tags` is an array (or a group is a plain string), iterating a group directly walks CHARACTERS instead of tags. The `if (!Array.isArray(group)) continue;` guard before iteration is the fix (+2 malformed-shape tests). **Universal pattern:** any code parsing a consumer/maintainer-editable JSON file must defend against shape surprises, not just missing keys.
327
+ - **Test-fixture parity with the real sync array is what catches distribution regressions (reviewer minor 3)** — `scripts/sync.test.js` had a hand-maintained `scriptsToSync` fixture that omitted `vocab-sync.js`, so a future removal from the real array would go uncaught. Fix: fixture now byte-matches the real 7-entry array (vocab-sync adjacent to validator). **Universal pattern:** when a distribution list (sync array, files list, manifest) has a test fixture, keep them byte-identical — fixture drift silently disables the regression guard.
328
+ - **Consumer distribution of a hook script is layered and must be wired in EVERY channel** — the new script reaches consumers via: (1) npm tarball (`files` includes `scripts/`), (2) `sync.js` `scriptsToSync` (copies to consumer's `scripts/`), (3) the hook CONTENT via setup `TEMPLATE_HOOKS['pre-commit'].content` (written by `npx ai-workflow-setup`). All three must be updated or a consumer gets a hook line pointing at a missing script. The setup hook tests needed ZERO edits because `expectedContent()` auto-derives from `TEMPLATE_HOOKS` — the marker-first idempotency detection (`discover.js` compares only the first line) survives mid-content additions.
329
+ - **`isDirectRun` testability seam is the established convention for direct-run scripts** — `vocab-sync.js` and `validate-memory-schema.js` both use `process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)` + `export { … }`, matching `mcp-knowledgebase-server.js`. Importing in tests triggers no side effects; direct execution is unchanged. The validator's first-ever test suite (15 tests + 3 M1 tests) was enabled by this seam.
330
+
331
+ **Agent tuning notes:**
332
+ - **implementer (planning)**: Map the distribution mechanism BEFORE writing tasks — this plan's key insight was that a hook script needs FOUR channels (npm files, scriptsToSync, TEMPLATE_HOOKS content, and the repo's own hook file) and each channel is a distinct task. The parallel-batch table (A: 7 files, all disjoint; B: 3 tests) again prevented collisions — but note T10 required an ADDITIONAL seam change to `validate-memory-schema.js` that T3 also touched; batch planning should flag when a Batch B test task requires modifying a Batch A production file (the seam was additive, so no conflict occurred).
333
+ - **coder (parallel batches)**: When one batch task adds a testability seam to a file another task modifies, keep the seam purely additive (new import + guard + exports, no behavior change) to stay collision-safe. Preserve file metadata on hook edits — the `.husky/pre-commit` executable bit (100755) survived the one-line insertion. For hook template changes, update the hook's `description` string too, not just the content array.
334
+ - **unit-tester**: Prove non-vacuity with real temp mutations even for small features — removing the `entity_patterns` merge fails validator TEST-01/04; disabling the new-tag append fails 14 vocab-sync tests. Scratch-repo smoke tests (git init → stage file with new tag → run hook scripts in order → assert no warning → re-run assert idempotent) are the definitive end-to-end proof for pre-commit features. M1 re-verification must use a mixed-case tag (`My Tag`), not just lowercase — lowercase-only fixtures hid the parity gap.
335
+ - **reviewer**: For auto-heal/validator pairs, check NORMALIZATION PARITY (does the consumer compare against the same normalized set the producer writes?) — this was the M1 catch no test covered. Grep doc instructions for format-illegal suggestions (JSON comments) before approving. Probe consumer-controlled data shapes (tags-as-array, string group) in addition to missing keys. Verify test fixtures against the real production array for distribution features.
336
+ - **tracker**: Record sync-before-validate ordering, normalization parity, and "verify doc instructions are format-legal" as universal patterns — all three are reusable beyond vocab-sync (any auto-generated data file with a validator, any producer/consumer pair with normalization, any JSON-format documentation).
@@ -64,6 +64,28 @@ When `scripts/normalize-memory.js` runs (postinstall or manual), it:
64
64
  - Does NOT overwrite existing frontmatter fields
65
65
  - Logs all auto-applied suggestions
66
66
 
67
+ ## Maintaining the Vocabulary
68
+
69
+ When adding a new tag to a memory-bank file's frontmatter:
70
+
71
+ 1. **Check if it already exists** in `memory-bank/.vocabulary.json`:
72
+ - Search `tags.*` groups (all values across `agent_roles`, `workflow`, `memory_ops`, `topic`)
73
+ - Search `entity_patterns` keys (component/tool names like `npm`, `vitest`, `playwright`)
74
+ 2. **If it's a tool, framework, or component:** add it to `entity_patterns` as a new key with at least one regex pattern.
75
+ 3. **If it's a concept or topic:** add it to the appropriate `tags.*` group:
76
+ - `topic` — general concepts, technologies, domains (default catch-all)
77
+ - `workflow` — processes, pipelines, ceremonies
78
+ - `memory_ops` — memory-bank operations
79
+ - `agent_roles` — agent role identifiers
80
+ 4. **Tag format rules:**
81
+ - Lowercase only
82
+ - Kebab-case (hyphens for spaces, no underscores)
83
+ - Maximum 5 tags per file
84
+ - No whitespace-only or empty tags
85
+ 5. **Rationale:** Record why the tag exists in the commit message or PR description, or in the existing `categories` description strings. Strict JSON supports no comments — do NOT add comment syntax to `.vocabulary.json` (it would corrupt the file).
86
+
87
+ The pre-commit hook (`vocab-sync.js`) auto-appends genuinely new tags to the `topic` group as a safety net, but manual categorization is preferred for accuracy.
88
+
67
89
  ## Pre-Commit Validation
68
90
 
69
91
  The husky pre-commit hook (`scripts/validate-memory-schema.js`) rejects commits where:
package/.husky/pre-commit CHANGED
@@ -1,4 +1,5 @@
1
1
  # Managed by @abarcenas/ai-workflow-template setup
2
2
 
3
3
  node scripts/bump-version.js
4
+ node scripts/vocab-sync.js
4
5
  node scripts/validate-memory-schema.js
@@ -38,6 +38,7 @@ You are a code review specialist. You never write code — you read, analyze, an
38
38
  - Check for: error handling, input validation, state management, concurrency, type safety
39
39
  - Do NOT make any edits or write any code
40
40
  - Return structured feedback sorted by severity
41
+ - **Memory-bank hygiene**: In any diff touching `memory-bank/**/*.md`, verify frontmatter tags are known to `memory-bank/.vocabulary.json` (check BOTH `tags.*` groups and `entity_patterns` keys). If unknown tags exist, update the vocabulary file — add to the appropriate `tags.*` group (default: `topic`) and record the rationale in the review report or commit message (strict JSON — no comment syntax; do NOT add comments to the file), or add to `entity_patterns` if the tag is a tool/component name — and flag the change in the review report. Report any unaddressed unknown tags as 🔵 minor findings.
41
42
 
42
43
  ## Output Expectations
43
44