git:20260813.f252be3 to git:20260829.efc032a

3 added, 1 removed. Audit A to A.

# DOX — packages/kb-extension/src
Files in this directory. One row per source file.
| File | Purpose |
|------|---------|
- | `extension.ts` | Extension entry. Registers kb_search/kb_neighbors/kb_get native tools. tool_result hook: Job 1 md write→debounced hash-gated reindex (AGENTS.md edit also `acknowledgeRows`); Job 2 opt-in doxEnforcement nudge (default OFF, KB_DOX_ENFORCEMENT=1 forces on). tool_call hook (opt-in `dirAgentsPush`, config directoryLevelAgents.enabled+mode=="push"): write/edit steers nearest AGENTS.md body as kb-agents-push. Isolated standalone extension, not in bridge.ts. Re-exports `closeKbForCwd`. See change: sweep-worktree-residual-on-remove. `kb_neighbors`/`kb_get` now `await ensurePopulated` (guarded try/catch, fall back to existing index) before serving — cold KB self-populates instead of returning empty. Re-exports `ensurePopulated`. See change: fix-kb-neighbors-get-cold-start. `kb_search` output: condensed text default (rank-led, positional, `(+N dup)`, `⤷ parent`, one-line snippet) via shared `renderHits`; opt-in `format` param (free string, in-body allowlist — unknown→condensed, never throws) selects compact `json` (retains `score`, adds `rank`); empty-query guard after format parse emits `(no query)`/`[]`; description rewritten. See change: slim-kb-search-output. `kb_search` now passes `sourceDedup`/`laneQuota`/`coverageRerank`/`queryExpansion`/`prf` from `cfg.ranking` + `cfg.queryExpansion`; condensed entries carry the LEAF heading + `(+N more sections)`, `limit` bounds DISTINCT SOURCES (BREAKING), json retains full `headingPath` + `suppressedSections`; description states that shape and drops the "prefer 2–5 terms" advice the ranking does not reward. `kb_get` appends `(+N more sections in this file — pass \`section\`)` on a path-only fetch of a multi-chunk file and reports `details.suppressedSections` — never a silent 1-of-N slice. See change: fix-kb-search-retrieval-quality. |
+ | `extension.ts` | Extension entry. Registers kb_search/kb_neighbors/kb_get native tools. tool_result hook: Job 1 md write→debounced hash-gated reindex (AGENTS.md edit also `acknowledgeRows`); Job 2 opt-in doxEnforcement nudge (default OFF, KB_DOX_ENFORCEMENT=1 forces on). tool_call hook (opt-in `dirAgentsPush`, config directoryLevelAgents.enabled+mode=="push"): write/edit steers nearest AGENTS.md body as kb-agents-push. Isolated standalone extension, not in bridge.ts. Re-exports `closeKbForCwd`. See change: sweep-worktree-residual-on-remove. `kb_neighbors`/`kb_get` now `await ensurePopulated` (guarded try/catch, fall back to existing index) before serving — cold KB self-populates instead of returning empty. Re-exports `ensurePopulated`. See change: fix-kb-neighbors-get-cold-start. `kb_search` output: condensed text default (rank-led, positional, `(+N dup)`, `⤷ parent`, one-line snippet) via shared `renderHits`; opt-in `format` param (free string, in-body allowlist — unknown→condensed, never throws) selects compact `json` (retains `score`, adds `rank`); empty-query guard after format parse emits `(no query)`/`[]`; description rewritten. See change: slim-kb-search-output. `kb_search` now passes `sourceDedup`/`laneQuota`/`coverageRerank`/`queryExpansion`/`prf` from `cfg.ranking` + `cfg.queryExpansion`; condensed entries carry the LEAF heading + `(+N more sections)`, `limit` bounds DISTINCT SOURCES (BREAKING), json retains full `headingPath` + `suppressedSections`; description states that shape and drops the "prefer 2–5 terms" advice the ranking does not reward. `kb_get` appends `(+N more sections in this file — pass \`section\`)` on a path-only fetch of a multi-chunk file and reports `details.suppressedSections` — never a silent 1-of-N slice. See change: fix-kb-search-retrieval-quality. `kb_search` runs `enrichHits` post-search (trust verdicts, agents hits, bodies from disk) and states the verdict is a trust label, never ranking. Guard wiring (arm B): OWN `tool_call` hook feeds `guardNoteSafe` once per invocation (reset-before-count inside guard.ts); advisory firings ride to `tool_result` keyed by toolCallId and PREPEND to the result; block verdicts return `{block, reason}` from `tool_call`; `turn_start` ticks the pause clock; `kb_guard_pause` tool = agent self-service suspension; mode from `resolveGuardMode(cfg.readDiscipline.guard.mode, KB_GUARD_MODE)`. See change: add-kb-trust-verdicts-and-search-guard. |
+ | `guard.ts` | Search-guard core (arm B). Pure state machine, NO pi imports. `createGuard({mode})` → `note(toolName, input)` (null \| string \| `{block, reason}`), `suspend` (clamp 1–20, junk no-op, max-wins), `tickTurn` (expiry = clean slate). Fires at 3 consecutive searches (CHAIN_THRESHOLD); reset set = kb_search/kb_neighbors/kb_get + bash leading `kb`, processed BEFORE counting, clears chain AND firings; edits never reset; empty kb_search query resets. Bash segment-parsed on `\| \|\| && ;` newline, segment counts iff it LEADS with a search binary; `timeout 60 rg` / `FOO=1 rg` evade — accepted gap. Ladder warn→escalate→block; block only in `block` mode; `off` inert. `resolveGuardMode(config, env)`: KB_GUARD_MODE selects off/warn only — can weaken, never enable block (D14). `guardNoteSafe` = fault-isolated feed (X1: guard failure leaves results untouched). See change: add-kb-trust-verdicts-and-search-guard. |
| `index.ts` | Barrel. Re-exports extension default + reindex. |
+ | `__tests__/guard.test.ts` | Vitest for `guard.ts`. Chain threshold/reset-set/edit-doesn't-reset, bash segment table (incl. the asserted `timeout 60 rg` gap), ladder × mode, suspension clamp/expiry, mode resolution (env weakens never strengthens), off inert, X1 silent degradation. |
| `reindex.ts` | Pure reindex + DOX-nudge logic. No pi imports. Testable without running pi. `reindexNow` is ASYNC now (awaits `indexSource`); debounce timer fire-and-forgets via `.catch`, `extension.ts` `kb_search` freshness `await`s it. See change: fix-kb-index-feedback. `acknowledgeRows`/`decideNudge` resolve DOX rows relative to their AGENTS.md dir (+ repo-root fallback via local `resolveRowPath` mirror), key staleness cwd-relative. Fixes always-"missing" nudge on basename rows. See change: fix-dox-lint-false-positives. `getKb`/`reindexNow` self-heal on cwd removal: if `cwd` gone, `getKb` throws (refuses fresh `new SqliteFtsStore` whose `mkdirSync` would recreate `<cwd>/.pi/dashboard/kb` = husk), `reindexNow` evicts + no-ops `{changed:0,chunks:0}`. Adds `closeKbForCwd(state,cwd)`: close+evict one cwd's store (WAL checkpoint drops `-wal`/`-shm`), cancel its timer. See change: sweep-worktree-residual-on-remove. `ensurePopulated(state,cwd)`: cold-start guard — opens store via `getKb`, runs `reindexNow` ONLY when `store.counts().chunks===0` (warm index = COUNT-only no-op); removed cwd = safe no-op; throws propagate to caller. Wired into `kb_neighbors`/`kb_get`. See change: fix-kb-neighbors-get-cold-start. |