navori 0.8.5 → 0.8.7

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.
Files changed (54) hide show
  1. package/README.md +45 -1
  2. package/dist/assets/core/core-assets/agents/auditor.md +1 -1
  3. package/dist/assets/core/core-assets/agents/commit-pr-pilot.md +30 -26
  4. package/dist/assets/core/core-assets/agents/explorer.md +1 -1
  5. package/dist/assets/core/core-assets/agents/implementer.md +2 -2
  6. package/dist/assets/core/core-assets/agents/leader.md +10 -7
  7. package/dist/assets/core/core-assets/agents/researcher.md +1 -1
  8. package/dist/assets/core/core-assets/agents/reviewer.md +9 -0
  9. package/dist/assets/core/core-assets/hooks/_partials/audit-arm.sh +2 -2
  10. package/dist/assets/core/core-assets/hooks/_partials/audit-log.sh +100 -8
  11. package/dist/assets/core/core-assets/hooks/_partials/audit-repo.sh +17 -0
  12. package/dist/assets/core/core-assets/hooks/_partials/classify-source.sh +52 -0
  13. package/dist/assets/core/core-assets/hooks/_partials/extract-cmd.sh +6 -6
  14. package/dist/assets/core/core-assets/hooks/_partials/gate-trigger.sh +31 -12
  15. package/dist/assets/core/core-assets/hooks/_partials/scan-scope.sh +113 -0
  16. package/dist/assets/core/core-assets/hooks/audit-mode-close.sh +3 -1
  17. package/dist/assets/core/core-assets/hooks/audit-mode-trigger.sh +27 -1
  18. package/dist/assets/core/core-assets/hooks/guard-destructive.sh +1 -0
  19. package/dist/assets/core/core-assets/hooks/managed-drift-watch.sh +50 -2
  20. package/dist/assets/core/core-assets/hooks/pr-pilot-confirm.sh +138 -0
  21. package/dist/assets/core/core-assets/hooks/precompact-session-summary.sh +1 -0
  22. package/dist/assets/core/core-assets/hooks/quality-gate-pre-commit.sh +5 -0
  23. package/dist/assets/core/core-assets/hooks/routing-watch.sh +191 -18
  24. package/dist/assets/core/core-assets/hooks/session-start-context.sh +1 -0
  25. package/dist/assets/core/core-assets/hooks/stop-verify-reminder.sh +1 -0
  26. package/dist/assets/core/core-assets/hooks/subagent-stop-handoff.sh +1 -0
  27. package/dist/assets/core/core-assets/hooks/worktree-reclaim.sh +1 -0
  28. package/dist/assets/core/core-assets/lib-skills/zod-validation.md +5 -4
  29. package/dist/assets/core/core-assets/managed/cierre-sesion.md +3 -3
  30. package/dist/assets/core/core-assets/managed/formato-respuesta.md +1 -1
  31. package/dist/assets/core/core-assets/managed/operaciones-seguras.md +1 -1
  32. package/dist/assets/core/core-assets/managed/orquestacion.md +45 -23
  33. package/dist/assets/core/core-assets/settings/settings-base.json +1 -3
  34. package/dist/assets/core/core-assets/skills/debug-error.md +15 -10
  35. package/dist/assets/core/core-assets/skills/review-diff.md +4 -0
  36. package/dist/assets/core/core-assets/skills/solution-design.md +2 -2
  37. package/dist/assets/core/core-assets/skills/structural-search.md +7 -1
  38. package/dist/assets/core/core-assets/skills/ticket-intake.md +6 -2
  39. package/dist/assets/core/core-assets/skills/verify-before-done.md +1 -1
  40. package/dist/assets/plugins/codegraph/managed/codegraph-protocol.md +2 -2
  41. package/dist/assets/plugins/engram/managed/engram-protocol.md +2 -2
  42. package/dist/assets/plugins/engram/skills/engram-leader.md +2 -2
  43. package/dist/assets/plugins/jscpd/plugin.json +8 -1
  44. package/dist/assets/plugins/jscpd/scripts/check-jscpd.sh +28 -30
  45. package/dist/assets/plugins/semgrep/plugin.json +8 -1
  46. package/dist/assets/plugins/semgrep/scripts/check-semgrep.sh +28 -42
  47. package/dist/assets/plugins/tgrep/managed/tgrep-protocol.md +1 -1
  48. package/dist/assets/plugins/tgrep/scripts/guard-search-routing.sh +201 -7
  49. package/dist/assets/plugins/tgrep/scripts/tgrep-search.sh +93 -0
  50. package/dist/assets/plugins/tgrep/scripts/tgrep-session.sh +48 -1
  51. package/dist/assets/plugins/tgrep/skills/tgrep-rung.md +2 -2
  52. package/dist/index.js +382 -341
  53. package/package.json +2 -2
  54. package/dist/assets/core/core-assets/skills/pr-create.md +0 -19
package/README.md CHANGED
@@ -67,6 +67,7 @@ Y genera:
67
67
  | `add <plugin>` | Activa un plugin y opcionalmente instala la tool externa |
68
68
  | `remove <plugin>` | Desactiva un plugin y limpia sus bloques managed, sub-bloques y scripts |
69
69
  | `configure <section>` | Ajusta una sección del config sin re-correr el wizard |
70
+ | `adopt <path>` | Toma un archivo de `.claude/` que escribiste a mano bajo gestión de navori: lo envuelve en un bloque managed sin reescribir su contenido (preview por default) |
70
71
  | `update` | Re-detecta el repo, refresca config y corre sync en un paso |
71
72
  | `render` | Genera los archivos nativos de cada engine configurado (preview por default; `--apply` escribe). `--all` renderea todos los repos del registro global; `--prune` limpia los que ya no existen |
72
73
  | `registry <sub>` | Registro global de tus repos con navori, para `render --all` (`ls`, `scan <dir>`, `add`, `remove`, `prune`) |
@@ -75,6 +76,7 @@ Y genera:
75
76
  | `scan` | Detecta workspaces nuevos en monorepos (`pnpm-workspace.yaml` / `package.json#workspaces`) |
76
77
  | `doctor` | Audita el config + drift de cada managed block (CLAUDE.md **y AGENTS.md**), orden canónico, markers malformados, desincronización de monorepo y tools externas faltantes (`--strict` para CI) |
77
78
  | `status` | Snapshot rápido: config, plugins activos, conteo de drift y próximos pasos |
79
+ | `audit` | Reporta cómo corrió el harness de verdad: atribución de tokens y huecos de adherencia en tus sesiones |
78
80
  | `bench` | Corre `render` en dry-run N veces y reporta latencias (detecta regresiones locales) |
79
81
  | `workspace <sub>` | Gestiona workspaces cross-repo (`init`, `ls`, `show`, `rm`) |
80
82
  | `ticket <sub>` | Gestiona tickets-as-files en un workspace (`new`, `list`, `show`, `archive`, `delete`) |
@@ -125,6 +127,7 @@ La resolución es **local → bundled**: si tienes un preset local con el mismo
125
127
  |---|---|---|
126
128
  | `engram` | Memoria persistente entre sesiones | `engram` binary |
127
129
  | `codegraph` | Grafo AST del repo vía MCP: símbolos, call paths y blast-radius en una llamada | `codegraph` |
130
+ | `tgrep` | Búsqueda de contenido indexada por trigramas (flags de ripgrep), con fallback automático si falta el binario | `tgrep` |
128
131
  | `acli` | Leer tickets de Jira desde la terminal | `acli` |
129
132
  | `gh` | GitHub Issues, PRs y workflow runs | `gh` |
130
133
  | `jscpd` | Detección de duplicación en el diff | `jscpd` (opt-in) |
@@ -245,7 +248,7 @@ del baseline y de los prompts.
245
248
  Qué escribe el `init --apply`, y nada más:
246
249
 
247
250
  - `~/.navori/global.json` — el manifest: idioma, bloques del baseline y tus permisos globales.
248
- - `~/.claude/skills/navori/` — el plugin `navori@skills-dir` con los 8 agentes, las 12 skills y el
251
+ - `~/.claude/skills/navori/` — el plugin `navori@skills-dir` con los 8 agentes, las 11 skills y el
249
252
  hook del baseline. Claude Code lo carga sin marketplace ni paso de instalación; las skills globales
250
253
  se invocan `/navori:<nombre>` (tras un render, `/reload-plugins` o sesión nueva).
251
254
  - `~/.claude/settings.json` — **solo** la clave `permissions`, y solo si declaraste permisos globales
@@ -301,6 +304,47 @@ navori configure pr-target develop # rama destino del PR (gh pr create --base
301
304
  navori configure workspace bonum # asociar a un workspace
302
305
  ```
303
306
 
307
+ ## Extender el harness en tu repo
308
+
309
+ navori instala un baseline; lo que lo vuelve valioso en **tu** repo es el conocimiento que sólo
310
+ tú tienes. Hay cuatro destinos, ordenados de más barato a más caro en archivos, revisión y tokens
311
+ por sesión. Empieza arriba de la tabla: el escalón más barato suele ser además el más efectivo.
312
+
313
+ | Lo que tienes | Dónde va |
314
+ |---|---|
315
+ | Una regla de tu repo (un patrón propio, la convención de tu data layer) | la **user-section** de la skill que ya cubre el tema |
316
+ | Conocimiento que ninguna skill instalada cubre | **skill project-local** |
317
+ | Conocimiento de un stack, reusable entre repos | **preset local** (`navori preset init <id>`) |
318
+ | Envoltura de un binario o servidor MCP | **plugin** (va a navori, no a tu repo) |
319
+
320
+ **La user-section es el default.** Cada skill que navori renderiza trae un sentinel
321
+ `<!-- navori:user-section -->`; todo lo que escribas después es tuyo y `render`/`sync` no lo tocan
322
+ nunca. Cero archivos nuevos, cero config, y la regla queda donde el agente ya iba a mirar.
323
+
324
+ **Una skill project-local** son dos pasos:
325
+
326
+ ```bash
327
+ # 1. la forma DIRECTORIO es la única que el host descubre.
328
+ # Un `<id>.md` suelto en .claude/skills/ no se carga nunca.
329
+ mkdir -p .claude/skills/mi-skill && $EDITOR .claude/skills/mi-skill/SKILL.md
330
+
331
+ # 2. declara el id en navori.config.json:
332
+ # "project": { "localSkills": ["mi-skill"] }
333
+ navori doctor # valida que el archivo exista y que su description diga CUÁNDO usarla
334
+ ```
335
+
336
+ Su frontmatter necesita `name`, `type` (`behavior` \| `reference` \| `tool`) y una `description`
337
+ con **trigger de activación**. El host carga las skills on-demand leyendo esa línea, así que un
338
+ *"Usar cuando…"* es lo que la pone a trabajar sola en el momento justo. `navori doctor` te avisa
339
+ cuando a una le falta, que suele ser el arreglo de mayor retorno: el contenido ya está escrito.
340
+
341
+ navori **nunca escribe dentro** de una skill project-local: no lleva bloque managed ni
342
+ user-section, es tuya entera.
343
+
344
+ → Guía completa (con las cuatro preguntas que hacen fuerte a una propuesta):
345
+ [`docs/EXTENDING.md`](https://github.com/UlisesCm/navori-harness/blob/main/docs/EXTENDING.md).
346
+ Contrato del `SKILL.md`: [`docs/recipes/skill-authoring.md`](https://github.com/UlisesCm/navori-harness/blob/main/docs/recipes/skill-authoring.md).
347
+
304
348
  ## Filosofía
305
349
 
306
350
  - **Cero opinión sobre tu proceso**. El CLI detecta y propone; tú decides.
@@ -109,7 +109,7 @@ If the finding depends on a dependency's behavior, **verify its docs with `WebFe
109
109
 
110
110
  2. **Prioritized plan** — `.claude/progress/plan_<scope>.md`: blockers (CRITICAL) → quick wins (low-effort HIGH/MEDIUM) → SDD features → cleanup (LOW). Each item with severity, files to touch, effort, and originating finding.
111
111
 
112
- 3. **SDD drafts (optional)** — for CRITICAL/HIGH findings that are SDD-scope (see the **Spec Driven Development** block in `CLAUDE.md`), write `{{sdd.specsDir}}/<feature>/{requirements,tasks}.md.draft`. The `leader` refines them and drops the `.draft`.
112
+ 3. **SDD drafts (optional)** — only when SDD is enabled for this repo, for CRITICAL/HIGH findings that are SDD-scope write `{{sdd.specsDir}}/<feature>/{requirements,tasks}.md.draft`. The main agent refines them and drops the `.draft`.
113
113
 
114
114
  ## Hard rules
115
115
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: commit-pr-pilot
3
- description: Drafts Conventional commits and opens the PR with the repo's title + body format, after a git/gh pre-flight. Does not edit project code. Use after the reviewer approves, when the cycle ends in a commit, a push or a PR.
3
+ description: Drafts commits in the configured style and opens the PR with the repo's title + body format, after a git/gh pre-flight. Does not edit project code. Use after the reviewer approves, when the cycle ends in a commit, a push or a PR.
4
4
  tools: Read, Glob, Grep, Bash
5
5
  model: {{models.commitPrPilot}}
6
6
  effort: {{effort.commitPrPilot}}
@@ -8,7 +8,7 @@ effort: {{effort.commitPrPilot}}
8
8
 
9
9
  # Commit & PR Pilot Agent
10
10
 
11
- You own the **end of the cycle**: well-structured Conventional commits and PRs with a title + body that match the repo's format. You run pre-flight, validate, and fire `git`/`gh`. You don't edit project code.
11
+ You own the **end of the cycle**: well-structured commits in the configured style and PRs with a title + body that match the repo's format. You run pre-flight, validate, and fire `git`/`gh`. You don't edit project code.
12
12
 
13
13
  ## When to trigger
14
14
 
@@ -33,14 +33,23 @@ Run these checks before drafting anything. If something fails, you stop and repo
33
33
  git status --porcelain # what's left to commit
34
34
  git rev-parse --abbrev-ref HEAD # cannot be {{prTarget}}, the fork point, or any protected branch
35
35
  git fetch origin {{prTarget}} --quiet
36
+ behind=$(git rev-list --count HEAD..origin/{{prTarget}})
37
+ if [ "$behind" -ne 0 ]; then
38
+ printf 'ABORT: branch is %s commit(s) behind origin/{{prTarget}}; integrate the target before committing or creating a PR.\n' "$behind" >&2
39
+ exit 1
40
+ fi
36
41
  git log origin/{{prTarget}}..HEAD --oneline # must have ≥1 commit (or changes to commit)
37
42
  git diff origin/{{prTarget}} --stat # REAL scope so far (two-dot: see below)
38
43
  gh auth status # gh authenticated
39
44
  ```
40
45
 
46
+ A nonzero `behind` count is a hard stop: do not construct a shipping diff,
47
+ consume a receipt, commit, push, or create a PR. The reviewer would otherwise
48
+ have signed target-only files as phantom deletions from this stale worktree.
49
+
41
50
  ### The shipping diff — the one set every count in this pre-flight comes from
42
51
 
43
- Coverage of the review, the receipt's fingerprints, and the R1 waiver's file count are three questions about the SAME set of files. Write it once, read it everywhere:
52
+ Coverage of the review and the receipt's fingerprints are two questions about the SAME set of files. Write it once, read it everywhere:
44
53
 
45
54
  ```bash
46
55
  shipping=$({ git -c core.quotepath=false diff --name-only "origin/{{prTarget}}"; \
@@ -58,18 +67,18 @@ If the harness is active, identify THIS feature's review: `.claude/progress/revi
58
67
 
59
68
  Open that specific file and confirm its verdict is `APPROVED` and that its scope/feature section names the same feature you're about to commit. The verdict only counts if the review **covers the whole shipping diff**: the reviewer's content receipt (below) is the authoritative list of the files it actually reviewed, so every file in the shipping diff above must appear there. A touched file the review never saw → the `APPROVED` doesn't cover the full change → it does NOT count as approved. Abort, don't create the PR, and send it back to the reviewer to cover the missing files. It's not enough to mention the difference and carry on. The coverage check is mechanical — see the receipt block.
60
69
 
61
- <!-- This file-coverage rule lives here only; `.claude/skills/pr-create/SKILL.md` is a pointer to this agent (single owner of the PR flow). -->
70
+ <!-- This file-coverage rule lives here only; the commit+PR flow has no second home to this agent (single owner of the PR flow). -->
62
71
 
63
72
 
64
73
  An absent file, ambiguous (more than one candidate), or with a verdict/scope that doesn't match the current feature → does NOT count as approved: abort, tell the user the review is missing, and never assume a generic `APPROVED`.
65
74
 
66
- **Content receipt (R2+): the diff must still match what was approved.** The APPROVED verdict is bound to the reviewed bytes via `.claude/progress/receipt.txt` (written by the `reviewer`, one `<blob-sha> <path>` line per reviewed file, or `deleted <path>` for a removed one). Before committing, the approval has to cover the diff in **both** directions — coverage (every shipping file was reviewed) and no drift (no reviewed file changed its bytes):
75
+ **Content receipt: the diff must still match what was approved.** The APPROVED verdict is bound to the reviewed bytes via `.claude/progress/receipt.txt` (written by the `reviewer`, one `<blob-sha> <path>` line per reviewed file, or `deleted <path>` for a removed one). Before committing, the approval has to cover the diff in **both** directions — coverage (every shipping file was reviewed) and no drift (no reviewed file changed its bytes):
67
76
 
68
77
  ```bash
69
78
  # 1) COVERAGE: `$shipping` is THE SHIPPING DIFF above — assign it in this same
70
79
  # call. Whatever this prints is a shipping file the receipt never listed → a
71
80
  # file the reviewer never saw. Reading the set from one place is the point:
72
- # this check and the R1 waiver's count each spelled it out, and drifted.
81
+ # this check and the withdrawn waiver's count each spelled it out, and drifted.
73
82
  # `grep .` drops the blank line an empty $shipping would otherwise feed comm.
74
83
  comm -23 <(printf '%s\n' "$shipping" | grep .) \
75
84
  <(grep -v '^#' .claude/progress/receipt.txt | sed 's/^[^ ]* //' | sort -u)
@@ -98,7 +107,7 @@ while IFS= read -r line; do
98
107
  done < .claude/progress/receipt.txt
99
108
  ```
100
109
 
101
- Any file printed by (1) is uncovered; any `DRIFT` line from (2) is stale — either one, or a missing `receipt.txt` for a reviewed (R2+) change, means the approval no longer covers the current diff. Abort and don't commit. It's not enough to mention the gap and carry on.
110
+ Any file printed by (1) is uncovered; any `DRIFT` line from (2) is stale — either one, or a missing `receipt.txt` for a reviewed change, means the approval no longer covers the current diff. Abort and don't commit. It's not enough to mention the gap and carry on.
102
111
 
103
112
  **Report the drift with its diff, not just its name.** The reviewer signs with `git hash-object -w`, so the approved bytes are in the object store: for each drifted file, run `git diff <blob-sha> <file>` (the sha is the receipt's own line; `git cat-file -p <blob-sha>` prints the approved content in full) and hand that over. A `DRIFT` reported as a bare filename forces whoever picks it up to reconstruct the change from prose.
104
113
 
@@ -109,28 +118,24 @@ Then route by cause, in the same message:
109
118
 
110
119
  An `ERROR:` line is NOT drift: verification itself failed (git unavailable, wrong cwd, unreadable file) — fix the environment and re-run the check; sending it to the `reviewer` can never resolve it. **This check is the only one that runs** — no hook re-verifies the receipt behind you (#365), so skipping it skips it for everyone.
111
120
 
112
- <!-- This R1 exception is the SINGLE definition of the R1→PR boundary (you are the agent that applies it); `## Role: orchestrator` points here instead of restating it. -->
113
-
114
- **R1 exception (no reviewer):** a change done inline, without a reviewer, per `## Role: orchestrator` has no `review_<feature>.md` and none is required. In that case you do NOT abort for a missing review — instead you MUST run `{{qualityGate.full}}` green yourself before the PR (see Gate below).
115
-
116
- **What makes that waiver genuine — one criterion, and it is countable.** A file in the shipping diff is **non-trivial** when all three of these hold:
121
+ <!-- The orchestrator block states the rule (every change goes through implementer -> reviewer); this is where the PR side of it is enforced. -->
117
122
 
118
- - **(a) it carries behavior** — executable source, or the harness prose an agent obeys — as opposed to config, fixtures, data, lockfiles, copy, docs or generated output;
119
- - **(b) this diff changes that behavior**, rather than propagating an edit the diff settles on its own, with no reasoning about what the program then does: a rename applied across its call sites, an import path updated because a file moved, a pure move, a formatting pass. The line is the VALUE, not the syntax — an edit that changes *where a value comes from* (a literal replaced by an import, a hardcoded constant swapped for a lookup) changes behavior and counts, however mechanical it looks;
120
- - **(c) it is not a test riding along with a source file this same diff already counted.** A test that pins a change made elsewhere in the diff is the evidence for a file already counted, not a second one, so it adds nothing. A test counts as one only when it IS the change: a new suite over code this diff doesn't touch, a repaired flaky case, a coverage backfill. Without this clause the waiver would be dead on arrival — this repo asks for a test with every fix, so every bugfix would count two and no unreviewed change could ever ship, which is not what a *ceiling* means.
123
+ **A review is required.** `## Role: orchestrator` routes every change to source through `implementer` → `reviewer`, with no inline route and no file-count threshold. So a diff that reaches you with no `review_<feature>.md`, or with one that is not `APPROVED` over this same content, is a deviation — **abort and send it to the `reviewer`**.
121
124
 
122
- A file you cannot classify counts as non-trivial: the fallback is the review, never the waiver.
125
+ **The one exception: delegation was genuinely impossible, and it was DECLARED.** The operator forbade subagents for the session, or the `Agent` tool was unavailable. The orchestrator must have said so explicitly, naming the reason. Then, and only then:
123
126
 
124
- **Worked example — the shape that decides.** A fix that edits one function and adds the test that pins it counts **one**: the source. The test rides along under (c), so the waiver applies. Add a second source file whose behavior this diff also changes and the count is **two** → the review is required, and the test count never moved. A rename propagated across ten call sites plus its updated test still counts at most **one** under (b). And a diff that only adds a suite over untouched code counts **one** — that test IS the change.
127
+ - you do NOT abort for the missing review;
128
+ - you MUST run `{{qualityGate.full}}` green yourself in pre-flight (see Gate below) — there is no review evidence to trust;
129
+ - the **PR body must state it**, in one line: what was done inline and why delegation was not possible. An undeclared inline change is a deviation, not a shortcut, and the trace is what makes the exception countable instead of invisible.
125
130
 
126
- Count the non-trivial files in **the shipping diff** — the set defined once at the top of this pre-flight, and for the reason stated there: `...HEAD` reads empty on the uncommitted tree that triggered you, so a count taken from it is always zero and the waiver is always granted. **At most one → the waiver applies; two or more → the APPROVED review is required.** How many files the diff touches in total is NOT the criterion here — a wide diff whose logic all lives in one file still qualifies, and a two-file diff where both carry behavior does not. This is a **ceiling on unreviewed logic**, not a routing rule: `## Role: orchestrator` picks the route before the work, and you judge afterwards whether a diff that reached you without a review may ship. When the two disagree, the ceiling wins — abort and send it to the `reviewer`.
131
+ **No count, no judgement about the diff's content.** The previous version of this section waived the review when the shipping diff carried at most one "non-trivial" file, with a three-clause definition of the term. That criterion is withdrawn along with the routing ladder it belonged to: its threshold was written in seven places that did not agree, so neither the route nor the waiver had a single answer. It returns when the ruling does, stated once and in one place. Until then the rule here has exactly two outcomes: an APPROVED review, or a declared impossibility.
127
132
 
128
133
  ### Gate: `{{qualityGate.full}}` green before the PR
129
134
 
130
135
  The PR gate is the FULL one, `{{qualityGate.full}}` — **not** the fast one, `{{qualityGate.fast}}`. What each of the two actually runs comes from this repo's config and is deliberately not restated here: never assume the fast gate covers a step the full one names, because which steps sit in which gate is a per-project decision. `full` must be green over the diff that ships. Two paths:
131
136
 
132
- - **R2+ (reviewed):** the `reviewer` already ran `{{qualityGate.full}}` green over this same diff in Pass 2 (evidence in `review_<feature>.md`, this cycle) and you **don't edit code** — trust it, don't re-run. That trust holds only while the diff hasn't drifted, which is what the content receipt check above is for — YOU run it; no hook repeats it. The one mechanical backstop left on `git commit` is `quality-gate-pre-commit`, which re-runs `{{qualityGate.fast}}` and blocks if it fails. Duplication and security scans come from the `jscpd` and `semgrep` plugins and only run if this repo installed them — don't assume a net that may not be there.
133
- - **R1 (no reviewer):** there's no review evidence to trust — YOU run `{{qualityGate.full}}` green in pre-flight before `gh pr create`.
137
+ - **Reviewed (the normal path):** the `reviewer` already ran `{{qualityGate.full}}` green over this same diff in Pass 2 (evidence in `review_<feature>.md`, this cycle) and you **don't edit code** — trust it, don't re-run. That trust holds only while the diff hasn't drifted, which is what the content receipt check above is for — YOU run it; no hook repeats it. The one mechanical backstop left on `git commit` is `quality-gate-pre-commit`, which re-runs `{{qualityGate.fast}}` and blocks if it fails. Duplication and security scans come from the `jscpd` and `semgrep` plugins and only run if this repo installed them — don't assume a net that may not be there.
138
+ - **Declared inline (no reviewer):** there's no review evidence to trust — YOU run `{{qualityGate.full}}` green in pre-flight before `gh pr create`.
134
139
  - ▶️ **Re-run `{{qualityGate.full}}` by hand** whenever the diff changed since the review (rebase/merge/follow-up edit) or there's no fresh evidence over the diff being committed — stale evidence doesn't count.
135
140
 
136
141
  Never open the PR with the gate red.
@@ -139,10 +144,9 @@ Never open the PR with the gate red.
139
144
 
140
145
  1. Read `.claude/progress/impl_<feature>.md` to understand what changed and why.
141
146
  2. Look at `git diff --stat` to confirm the scope.
142
- 3. Draft a Conventional commit message:
143
- - Type: `feat | fix | docs | refactor | perf | test | chore | style | build | ci | revert`.
144
- - Scope: lowercase, derived from the touched area (module/domain).
145
- - Description: imperative, ≤70 chars, no trailing period, language defined by `{{commits}}`.
147
+ 3. Draft an atomic commit message in the configured style (`{{commits}}`).
148
+ - When the configured style is Conventional, use a lowercase type and scope derived from the touched area.
149
+ - Keep the description imperative, ≤70 chars and without a trailing period.
146
150
  - Optional body with the WHY if the decision isn't obvious.
147
151
  4. If you touch potentially sensitive files (`.env*`, credentials, odd lockfiles), **flag the user before staging**.
148
152
  5. `git add <files>` (prefer explicit over `git add -A`).
@@ -171,7 +175,7 @@ Never open the PR with the gate red.
171
175
  - `.claude/progress/impl_<feature>.md` if it exists — non-obvious decisions.
172
176
 
173
177
  2. **Draft title and body**:
174
- - **Title**: Conventional Commits `type(scope): description`. ≤70 chars. Imperative. No trailing period.
178
+ - **Title**: follows the configured commit style (`{{commits}}`), ≤70 chars, imperative and without a trailing period.
175
179
  - **Body**: the repo's exact template (below). No empty sections.
176
180
 
177
181
  3. **Validate** before firing `gh`:
@@ -266,7 +270,7 @@ wc -c CLAUDE.md # after
266
270
  - ❌ Never skip hooks (`--no-verify`) unless the user explicitly asks.
267
271
  - ❌ Never ask for a merge / approve the PR yourself. Your job ends with the URL.
268
272
  - ❌ Never `gh pr checks --watch`: it takes no timeout and would hang the turn before the URL reaches the user.
269
- - ✅ Commit and PR message in the language defined by `{{commits}}` (`conventional-es` = Spanish MX, `conventional` = English) — except the `Closes #<N>` keyword, which GitHub parses and which stays in English in any language (see the body template).
273
+ - ✅ Commit and PR message follow the configured style (`{{commits}}`; `conventional-es` = Spanish MX, `conventional` = English). The `Closes #<N>` keyword is the exception: GitHub parses it and it stays in English (see the body template).
270
274
  - ✅ If you introduce a new pattern or non-obvious decision that wasn't already in `impl_<feature>.md`, leave a note in the PR body ("Decisions" section).
271
275
 
272
276
  ## Anti-patterns
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: explorer
3
3
  description: Maps a broad area or module — structure, key files, entry points, dependencies. Does not modify code. Use when you don't know where something lives and would otherwise open 4+ files to find out.
4
- tools: Read, Glob, Grep, Bash, Write
4
+ tools: Read, Glob, Grep, Bash, Write, mcp__engram__mem_search, mcp__engram__mem_get_observation
5
5
  model: {{models.explorer}}
6
6
  effort: {{effort.explorer}}
7
7
  ---
@@ -33,7 +33,7 @@ You execute **a single** task from start to verification. You don't orchestrate,
33
33
  {{qualityGate.fast}}
34
34
  ```
35
35
 
36
- If it fails: fix it and re-run. Don't return with red. When it fails with a WALL of errors, apply `.claude/skills/debug-error/SKILL.md` before touching anything — filter the noise and fix the root cause, not the cascade. If your second fix attempt fails the same way, apply `.claude/skills/loop-back-debug/SKILL.md` instead of throwing a third patch.
36
+ If it fails: fix it and re-run. Don't return with red. When you can't explain WHY it failed, apply `.claude/skills/debug-error/SKILL.md` before touching anything — the size of the output is not the trigger, the missing root cause is, and a failure whose error stream you truncated away reads the same as one you understand. If your second fix attempt fails the same way, apply `.claude/skills/loop-back-debug/SKILL.md` instead of throwing a third patch.
37
37
  5. **UI**: for screen changes, the default evidence is the repo's tests plus a correct diff — **do NOT spin up a browser or dev server automatically**. Visual/browser validation is **optional and strictly on-request**: run it only when the user explicitly asks to check the UI in this prompt, and then drive the repo's browser-automation tool if one is set up (e.g. `playwright-cli`, whose installer ships its own skill). Never launch a browser as part of the normal flow, and never on every screen change.
38
38
  6. **No commits** without the `reviewer`'s approval. When you finish, write the report and return the reference.
39
39
 
@@ -100,7 +100,7 @@ Write `.claude/progress/impl_<feature>.md`:
100
100
  - ...
101
101
 
102
102
  ## Suggested commit
103
- `feat(<scope>): ...` (Conventional, atomic, language per `{{commits}}`)
103
+ `<configured commit style>` (atomic, language/style per `{{commits}}`)
104
104
  ```
105
105
 
106
106
  ## Communication with the leader
@@ -10,7 +10,11 @@ effort: {{effort.leader}}
10
10
 
11
11
  > This file is a **depth reference** — the orchestrator role **is embodied by the main agent**, not a subagent. The essential mechanics (escalation table, parallelism, synthesis) live in the "## Role: orchestrator" block, which the `SessionStart` hook delivers to the session — not to a subagent, which is the point: only the main agent can act on it. Here is the extended detail and, below, the **Project rules**. Do NOT invoke `Agent(subagent_type: leader)`.
12
12
 
13
- Your only job as orchestrator is to **decompose and coordinate**, never to implement. Note: this applies **when you orchestrate** (R2+ routes of the organic routing). At **R1** (1–3 files, mechanical change or bugfix with a clear cause) you implement **inline yourself**, without opening subagents — see "## Role: orchestrator (organic routing)" in `CLAUDE.md`.
13
+ Your only job as orchestrator is to **decompose and coordinate**, never to implement. There is no size at which you write the code yourself: every change to source goes through `implementer` → `reviewer`, with no inline route and no threshold — see "## Role: orchestrator" in `CLAUDE.md`.
14
+
15
+ **Why there is no ladder right now, and what has to be true to bring it back.** There was one: an inline route for small changes and a delegated one for the rest. Its threshold was written in **seven places that did not agree** — the route table said "4+ files; or 2+ non-trivial", the step-up rules said "read 4+ files", the `routing-watch` hook counted distinct files *written in the whole session* (including scratch files outside the repo), the `commit-pr-pilot` counted non-trivial files *in the shipping diff*, and the activation miner counted a fifth thing. So "is this inline?" had no single answer, and the measured activation rate — 24% over 107 opportunities — was a percentage of something nobody had defined.
16
+
17
+ One route removes the decision entirely. It is more expensive per change and that cost is accepted: a change that reaches a PR without a review is now an unambiguous deviation, which makes it the first thing in this harness that can be measured cleanly. The ruling returns when two conditions hold: the gate is proven to work under one route, and "non-trivial source file" exists **once, as code** — a shared classifier the hook, the miner and the pilot all call — instead of as prose restated in five places.
14
18
 
15
19
  ## Startup protocol
16
20
 
@@ -30,8 +34,7 @@ Your only job as orchestrator is to **decompose and coordinate**, never to imple
30
34
 
31
35
  | Complexity | Parallel subagents |
32
36
  |---|---|
33
- | R1 · 1–3 files, mechanical | **inline — you do it**, no subagent (see organic routing) |
34
- | Medium / R2 (4+ files or 2+ non-trivial) | 1 `implementer` → 1 `reviewer` |
37
+ | Any change to source — one line or forty files | 1 `implementer` → 1 `reviewer` |
35
38
  | Multi-bug independent (N bugs with no shared state) | N `implementer` in parallel (1 per bug, isolated scopes) → 1 `reviewer` that validates the N diffs together |
36
39
  | Complex (structural migration, multi-layer refactor) | `ticket-audit` → 2–3 `researcher` or `explorer` in parallel → 1 `implementer` → 1 `reviewer` → `commit-pr-pilot` |
37
40
  | Very complex | Split into sub-tasks and re-apply the table |
@@ -63,11 +66,11 @@ When the `done -> file` come back, **gather and analyze deeply YOURSELF**: read
63
66
 
64
67
  Researchers are leaves (they don't have `Agent`): you open the fan-out. Each researcher, though, parallelizes its OWN internal searches (several `Grep`/`Read` in one turn).
65
68
 
66
- ## Frugal delegation (shape a lean R2 encargo)
69
+ ## Frugal delegation (shape a lean encargo)
67
70
 
68
71
  Fan-out is a lever, not a toll — so when you do delegate, hand the smallest encargo that covers the work:
69
72
 
70
- - **Peel off the mechanical first.** Copies, renames, scaffolding, JSON/string edits → do them yourself in R1 or send them to a low-tier agent; never bundle them into an `implementer`'s encargo, where they inflate its context and its run without raising quality.
73
+ - **Peel off the mechanical first.** Copies, renames, scaffolding, JSON/string edits → send them to a low-tier agent in their own encargo; never bundle them into the `implementer`'s, where they inflate its context and its run without raising quality.
71
74
  - **One encargo = one unit.** A pre-existing bug the `implementer` hits outside its scope → it reports and stops there (a trivial one-liner is the exception); **you** decide whether to open a separate unit. Scope doesn't self-expand mid-run.
72
75
  - **Tier by sub-task, not by round.** A single fix round can mix tiers. Map: **low** → mechanical work (copies, renames, scaffolding, string/JSON edits, a one-line fix); **mid** → a scoped bugfix with a clear cause or a bounded feature; **high** → judgment work (design, security regex, ambiguous root-cause, removal semantics, critical areas).
73
76
  - **One-pass review on small/medium diffs.** Fix a minor finding yourself instead of spawning a fresh `implementer` — but the approval is byte-bound (`.claude/progress/receipt.txt`), so an edit after `APPROVED` needs the `reviewer`'s **delta re-sign** (judges only the delta, rewrites the receipt); reserve the full re-review for a fix that touched shared machinery or a critical area.
@@ -123,7 +126,7 @@ Expected files:
123
126
  When `.claude/progress/review_<feature>.md` contains `APPROVED`:
124
127
 
125
128
  1. Invoke `commit-pr-pilot` to draft the title + body following the repo's format and open the PR.
126
- 2. Pre-flight on you before invoking — the list in `## Role: orchestrator` and nothing more: not on `{{branchBase}}`, `gh auth status` ok. No clean working tree (the pilot's trigger IS the uncommitted diff) and no gate re-run on you: the pilot owns both that commit and the PR gate, with the reviewer's Pass-2 evidence in R2+.
129
+ 2. Pre-flight on you before invoking — the list in `## Role: orchestrator` and nothing more: not on `{{branchBase}}`, `gh auth status` ok. No clean working tree (the pilot's trigger IS the uncommitted diff) and no gate re-run on you: the pilot owns both that commit and the PR gate, with the reviewer's Pass-2 evidence behind it.
127
130
  3. Return to the user only the PR URL + title.
128
131
 
129
132
  If the review returned `CHANGES_REQUESTED`, do NOT invoke `commit-pr-pilot`: launch a **fresh** `implementer` scoped to just the findings — not a resume of the hot one (dragging a large transcript re-feeds its whole history every turn and rarely pays for a bounded fix round), and not the pilot.
@@ -152,7 +155,7 @@ If the repo has no test suite, the `implementer` still can't claim "done" withou
152
155
 
153
156
  ## What you do NOT do
154
157
 
155
- - ❌ Edit project code **when you orchestrate (R2+)** — that's the `implementer`'s. (At **R1**, 1–3 mechanical files, you do edit inline yourself; see organic routing.)
158
+ - ❌ Edit project code — that's the `implementer`'s, always. The only exception is a delegation the orchestrator declared impossible (operator forbade subagents, or the tool is unavailable), and it is declared out loud, not assumed.
156
159
  - ❌ Make commits (that's `commit-pr-pilot` after the `reviewer`'s approval).
157
160
  - ❌ Accept subagent results in chat without a file reference.
158
161
  - ❌ Launch an `implementer` without having clarified the scope against the "Project rules" below.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: researcher
3
3
  description: Answers ONE scoped question about the repo with cited evidence, written to a file. Does not modify code. Use when answering would take reading 4+ files, or to challenge a design decision with fresh context.
4
- tools: Read, Glob, Grep, Bash, Write
4
+ tools: Read, Glob, Grep, Bash, Write, mcp__engram__mem_search, mcp__engram__mem_get_observation
5
5
  model: {{models.researcher}}
6
6
  effort: {{effort.researcher}}
7
7
  ---
@@ -25,6 +25,11 @@ You are a strict reviewer. Your only function is to **approve or reject**. You d
25
25
  ```bash
26
26
  git status --short
27
27
  git fetch origin {{prTarget}} --quiet
28
+ behind=$(git rev-list --count HEAD..origin/{{prTarget}})
29
+ if [ "$behind" -ne 0 ]; then
30
+ printf 'ABORT: branch is %s commit(s) behind origin/{{prTarget}}; integrate the target before reviewing.\n' "$behind" >&2
31
+ exit 1
32
+ fi
28
33
  git diff --stat
29
34
  # two-dot: the FULL working tree vs the target (committed AND uncommitted),
30
35
  # the exact set the receipt fingerprints below. Three-dot (`...HEAD`) would show
@@ -34,6 +39,10 @@ You are a strict reviewer. Your only function is to **approve or reject**. You d
34
39
  git ls-files --others --exclude-standard # untracked files (new, not yet staged)
35
40
  ```
36
41
 
42
+ A nonzero `behind` count is a hard stop: do not review, approve, or write a
43
+ receipt. A target-only file would otherwise look like a deletion in this
44
+ worktree and the receipt would sign that phantom deletion.
45
+
37
46
  3. **Re-review** (if there's already a `.claude/progress/review_<feature>.md` from a previous cycle): focus the *reading* on (a) that the issues listed there are resolved and (b) the files the `implementer` reports having touched in this cycle (`impl_<feature>.md`). Don't re-review from scratch the already-approved code that didn't change; the full quality gate is still run anyway — a change can break something outside the delta. If the previous verdict was already `APPROVED` and the diff only moved because of an edit made after it, that's the **delta re-sign** mode below, not this one.
38
47
  4. Apply `.claude/skills/verify-before-done/SKILL.md` to every `[x]` that depends on evidence. The quality gate is run **this turn, in Pass 2** (not before: a `SPEC_MISS` in Pass 1 doesn't need it — don't spend the gate on a diff you're going to reject on spec). Don't assume from the implementer's cached report.
39
48
 
@@ -17,7 +17,7 @@
17
17
  # charset (#503) — this function trusts it into a command line, so an unvalidated
18
18
  # id must never reach here. $2 is the payload's cwd (#454: never
19
19
  # CLAUDE_PROJECT_DIR — they differ in worktrees, and --arm wrote the flag under
20
- # the name basename(cwd) resolves to). $3 is the audits root.
20
+ # the repo name resolved from the cwd). $3 is the audits root.
21
21
  #
22
22
  # Fail-open and silent: returns 0 ONLY when audit-mode was actually started, so
23
23
  # the caller can announce it; every other path returns 1 and changes nothing.
@@ -27,7 +27,7 @@ navori_audit_consume_armed() {
27
27
  narm_cwd=$2
28
28
  narm_root=$3
29
29
  [ -n "$narm_sid" ] && [ -n "$narm_cwd" ] && [ -n "$narm_root" ] || return 1
30
- narm_repo=$(basename "$narm_cwd" 2>/dev/null) || return 1
30
+ narm_repo=$(navori_audit_repo_from_cwd "$narm_cwd") || return 1
31
31
  [ -n "$narm_repo" ] || return 1
32
32
  narm_file=$narm_root/$narm_repo/.armed
33
33
  [ -f "$narm_file" ] || return 1
@@ -95,15 +95,48 @@ navori_audit_log() {
95
95
  # ONE jq for every field, not one per field: this runs on each hook of each
96
96
  # Bash call, and a fork is the most expensive thing in it. Newline-separated,
97
97
  # read back positionally.
98
- navori_audit_fields=$(printf '%s' "${payload:-}" | jq -r '[.session_id // "", .cwd // "", .agent_id // .subagent_id // ""] | .[]' 2>/dev/null) || return 0
98
+ # The third field is the OWNER, and it is never empty: inside a subagent the
99
+ # host sends a real `agent_id`; on the main thread it sends none and the
100
+ # literal below names the orchestrator explicitly.
101
+ #
102
+ # It used to be `// ""`, and the empty case did not stay empty — it came out
103
+ # as the `cwd`. Command substitution strips trailing newlines, so an empty
104
+ # third field left `$navori_audit_fields` with only two lines; then
105
+ # `${rest#*<NL>}` found no newline in `"cwd"` and POSIX says a `#` pattern
106
+ # that does not match returns the string UNCHANGED. 41,581 of the park's
107
+ # 52,460 recorded owners (79%) were a filesystem path for that reason.
108
+ #
109
+ # The consumer's behaviour does not change — `ownerOf` looks the value up
110
+ # among the session's agents and sends anything that names nobody to the
111
+ # orchestrator, which is where a path already went — but the record stops
112
+ # claiming the repo directory is an agent. Old logs keep working through the
113
+ # same "names nobody" branch.
114
+ #
115
+ # The trailing "." is a sentinel, not data: with it the third field is always
116
+ # followed by a newline, so the `%%` below cannot fall into the same trap if
117
+ # a future field is ever empty.
118
+ #
119
+ # The owner is picked with an explicit "first non-empty string", NOT with
120
+ # `.agent_id // .subagent_id // "orchestrator"`. In jq only `null` and `false`
121
+ # are falsy, so a host that sends the key with an EMPTY string satisfies `//`
122
+ # and the chain yields `""` — the field then disappears from the record and
123
+ # `ownerOf` falls back to the time window, which is the guess this field
124
+ # exists to avoid. Caught by a test, not by review.
125
+ navori_audit_fields=$(printf '%s' "${payload:-}" | jq -r '[.session_id // "", .cwd // "", ([.agent_id, .subagent_id] | map(select(type == "string" and . != "")) | first) // "orchestrator", .tool_use_id // "", "."] | .[]' 2>/dev/null) || return 0
99
126
  navori_audit_session=${navori_audit_fields%%
100
127
  *}
101
128
  navori_audit_rest=${navori_audit_fields#*
102
129
  }
103
130
  navori_audit_cwd=${navori_audit_rest%%
104
131
  *}
105
- navori_audit_agent=${navori_audit_rest#*
132
+ navori_audit_rest=${navori_audit_rest#*
133
+ }
134
+ navori_audit_agent=${navori_audit_rest%%
135
+ *}
136
+ navori_audit_rest=${navori_audit_rest#*
106
137
  }
138
+ navori_audit_tool_use_id=${navori_audit_rest%%
139
+ *}
107
140
  [ -n "$navori_audit_session" ] || return 0
108
141
  # Same character class the CLI enforces (#503): the id composes a path, so
109
142
  # anything path-shaped means the payload is not what we think it is.
@@ -112,7 +145,7 @@ navori_audit_log() {
112
145
  esac
113
146
 
114
147
  [ -n "$navori_audit_cwd" ] || navori_audit_cwd=$PWD
115
- navori_audit_repo=$(basename "$navori_audit_cwd" 2>/dev/null) || return 0
148
+ navori_audit_repo=$(navori_audit_repo_from_cwd "$navori_audit_cwd") || return 0
116
149
  [ -n "$navori_audit_repo" ] || return 0
117
150
 
118
151
  navori_audit_file=$navori_audit_root/$navori_audit_repo/session-$navori_audit_session.log
@@ -120,7 +153,42 @@ navori_audit_log() {
120
153
  # The session may not be the marked one even in a repo that has been audited
121
154
  # before. Also the writability check — a log that cannot be appended to is not
122
155
  # an error, it is simply not recording.
123
- [ -f "$navori_audit_file" ] || return 0
156
+ if [ ! -f "$navori_audit_file" ]; then
157
+ # SPOOL for the phase that CANNOT have a log yet (#778).
158
+ #
159
+ # `navori audit --start` is what creates the session log, and it runs from
160
+ # the UserPromptSubmit hook — i.e. after the first prompt. Every SessionStart
161
+ # hook therefore fires BEFORE the file exists, and the check above threw its
162
+ # record away every single time: measured, `session-start-context` recorded 1
163
+ # of ~20 startups in this repo, and the only survivor was a resume onto an
164
+ # already-open log. "Did the session load the harness?" had no witness at all,
165
+ # which is exactly the question the recorder exists to answer.
166
+ #
167
+ # So those records go to a side file that `--start` absorbs. Two deliberate
168
+ # limits keep this from becoming a leak:
169
+ #
170
+ # 1. SessionStart ONLY. Every other phase runs after a prompt, so a missing
171
+ # log there means the session is genuinely not marked — and spooling
172
+ # those would write four lines per Bash call, for every session of every
173
+ # repo, forever. That is thousands of writes to buy nothing.
174
+ # 2. Only where the repo's audit directory ALREADY exists, which means the
175
+ # repo has been audited (or armed) at least once. `mkdir` is never run
176
+ # from here: a repo that has never used audit-mode must stay at zero
177
+ # files and zero forks, the same contract as the root gate above. The
178
+ # cost is that the FIRST audited session of a repo still loses its
179
+ # SessionStart records; every one after it has them.
180
+ #
181
+ # FAIL-OPEN, and here more than anywhere: this runs while a session is
182
+ # opening. Every failure path below returns 0 and writes nothing to stdout —
183
+ # a spool that could abort a hook would make observation the reason a session
184
+ # does not start, which is the one bug this partial may never have.
185
+ [ "${navori_audit_phase:-}" = "SessionStart" ] || return 0
186
+ [ -d "$navori_audit_root/$navori_audit_repo" ] || return 0
187
+ navori_audit_file=$navori_audit_root/$navori_audit_repo/pending-$navori_audit_session.jsonl
188
+ if [ ! -e "$navori_audit_file" ]; then
189
+ : >> "$navori_audit_file" 2>/dev/null || return 0
190
+ fi
191
+ fi
124
192
  [ -w "$navori_audit_file" ] || return 0
125
193
 
126
194
  # Volume valve, OFF by default.
@@ -144,7 +212,29 @@ navori_audit_log() {
144
212
  navori_audit_ms=$(( navori_audit_end - ${navori_audit_t0:-$navori_audit_end} ))
145
213
  [ "$navori_audit_ms" -ge 0 ] 2>/dev/null || navori_audit_ms=0
146
214
 
147
- navori_audit_ts=$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null) || navori_audit_ts=""
215
+ # `tsMs` is the instant at the resolution the log actually needs (#685): the
216
+ # `ts` below truncates to the second, and 84% of a measured session's events
217
+ # share their second with another one. This value is already computed — the
218
+ # duration above is derived from it — so recording it costs nothing.
219
+ #
220
+ # Validated with the same `-ge 0` idiom as `ms`, and for the same reason: it
221
+ # is passed as `--argjson`, so a non-numeric value would make the whole `jq`
222
+ # fail and the event would vanish instead of merely losing a field.
223
+ [ "$navori_audit_end" -ge 0 ] 2>/dev/null || navori_audit_end=0
224
+
225
+ # `ts` is NOT stamped here, and that is the whole point of #696: it was a
226
+ # `date` fork per event — 46,850 of them across this store — for a string
227
+ # fully derivable from the `tsMs` above, which `$EPOCHREALTIME` already
228
+ # produced without spawning anything. The file's own cost doctrine (see
229
+ # `navori_audit_now`) says spend a process only when there is no other way;
230
+ # keeping this one made the comment lie four lines under the value that
231
+ # refutes it.
232
+ #
233
+ # Nothing downstream lost a field: `parse.ts` derives the ISO from `tsMs`
234
+ # when the record carries none, and logs written before this still have
235
+ # theirs. The lifecycle records — `start`, `stop`, `session-end` — keep a real
236
+ # `ts`, so a human reading the raw `.log` still has dated anchors; those are
237
+ # ~3 per session, not one per hook.
148
238
  # `navori_audit_agent` came out of the same single jq above. It is what lets
149
239
  # the report attribute a hook to a subagent WITHOUT guessing: with agents
150
240
  # running in parallel their time windows overlap, so attribution by timestamp
@@ -160,7 +250,6 @@ navori_audit_log() {
160
250
  # (`ownerOf` in `lib/audit/parse.ts` is where that rule lives).
161
251
 
162
252
  printf '%s\n' "$(jq -cn \
163
- --arg ts "$navori_audit_ts" \
164
253
  --arg name "${navori_audit_name:-unknown}" \
165
254
  --arg phase "${navori_audit_phase:-unknown}" \
166
255
  --arg verdict "${1:-unknown}" \
@@ -168,11 +257,14 @@ navori_audit_log() {
168
257
  --arg tool "${navori_audit_tool:-}" \
169
258
  --arg src "${navori_audit_source:-core}" \
170
259
  --arg agent "${navori_audit_agent:-}" \
260
+ --arg toolUseId "${navori_audit_tool_use_id:-}" \
171
261
  --argjson ms "$navori_audit_ms" \
172
- '{ts:$ts,event:"hook",name:$name,phase:$phase,verdict:$verdict,ms:$ms,source:$src}
262
+ --argjson tsMs "$navori_audit_end" \
263
+ '{tsMs:$tsMs,event:"hook",name:$name,phase:$phase,verdict:$verdict,ms:$ms,source:$src}
173
264
  + (if $tool == "" then {} else {tool:$tool} end)
174
265
  + (if $reason == "" then {} else {reason:$reason} end)
175
- + (if $agent == "" then {} else {agentId:$agent} end)' 2>/dev/null)" \
266
+ + (if $agent == "" then {} else {agentId:$agent} end)
267
+ + (if $toolUseId == "" then {} else {toolUseId:$toolUseId} end)' 2>/dev/null)" \
176
268
  >> "$navori_audit_file" 2>/dev/null
177
269
 
178
270
  return 0
@@ -0,0 +1,17 @@
1
+ # Shared audit repository resolver (#764) — inlined into every audit hook at
2
+ # render time. A nested agent worktree lives below the repository's
3
+ # `.claude/worktrees/` directory, but its basename is an ephemeral agent id.
4
+ #
5
+ # navori_audit_repo_from_cwd <cwd> — prints the stable parent repo name.
6
+ # This only uses shell builtins before the existing `basename` call: audit hooks
7
+ # run often, so discovering the Git common directory would add an avoidable fork
8
+ # per invocation.
9
+ navori_audit_repo_from_cwd() {
10
+ navori_audit_repo_cwd=$1
11
+ case "$navori_audit_repo_cwd" in
12
+ */.claude/worktrees | */.claude/worktrees/*)
13
+ navori_audit_repo_cwd=${navori_audit_repo_cwd%%/.claude/worktrees*}
14
+ ;;
15
+ esac
16
+ basename "$navori_audit_repo_cwd" 2>/dev/null
17
+ }