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.
- package/README.md +45 -1
- package/dist/assets/core/core-assets/agents/auditor.md +1 -1
- package/dist/assets/core/core-assets/agents/commit-pr-pilot.md +30 -26
- package/dist/assets/core/core-assets/agents/explorer.md +1 -1
- package/dist/assets/core/core-assets/agents/implementer.md +2 -2
- package/dist/assets/core/core-assets/agents/leader.md +10 -7
- package/dist/assets/core/core-assets/agents/researcher.md +1 -1
- package/dist/assets/core/core-assets/agents/reviewer.md +9 -0
- package/dist/assets/core/core-assets/hooks/_partials/audit-arm.sh +2 -2
- package/dist/assets/core/core-assets/hooks/_partials/audit-log.sh +100 -8
- package/dist/assets/core/core-assets/hooks/_partials/audit-repo.sh +17 -0
- package/dist/assets/core/core-assets/hooks/_partials/classify-source.sh +52 -0
- package/dist/assets/core/core-assets/hooks/_partials/extract-cmd.sh +6 -6
- package/dist/assets/core/core-assets/hooks/_partials/gate-trigger.sh +31 -12
- package/dist/assets/core/core-assets/hooks/_partials/scan-scope.sh +113 -0
- package/dist/assets/core/core-assets/hooks/audit-mode-close.sh +3 -1
- package/dist/assets/core/core-assets/hooks/audit-mode-trigger.sh +27 -1
- package/dist/assets/core/core-assets/hooks/guard-destructive.sh +1 -0
- package/dist/assets/core/core-assets/hooks/managed-drift-watch.sh +50 -2
- package/dist/assets/core/core-assets/hooks/pr-pilot-confirm.sh +138 -0
- package/dist/assets/core/core-assets/hooks/precompact-session-summary.sh +1 -0
- package/dist/assets/core/core-assets/hooks/quality-gate-pre-commit.sh +5 -0
- package/dist/assets/core/core-assets/hooks/routing-watch.sh +191 -18
- package/dist/assets/core/core-assets/hooks/session-start-context.sh +1 -0
- package/dist/assets/core/core-assets/hooks/stop-verify-reminder.sh +1 -0
- package/dist/assets/core/core-assets/hooks/subagent-stop-handoff.sh +1 -0
- package/dist/assets/core/core-assets/hooks/worktree-reclaim.sh +1 -0
- package/dist/assets/core/core-assets/lib-skills/zod-validation.md +5 -4
- package/dist/assets/core/core-assets/managed/cierre-sesion.md +3 -3
- package/dist/assets/core/core-assets/managed/formato-respuesta.md +1 -1
- package/dist/assets/core/core-assets/managed/operaciones-seguras.md +1 -1
- package/dist/assets/core/core-assets/managed/orquestacion.md +45 -23
- package/dist/assets/core/core-assets/settings/settings-base.json +1 -3
- package/dist/assets/core/core-assets/skills/debug-error.md +15 -10
- package/dist/assets/core/core-assets/skills/review-diff.md +4 -0
- package/dist/assets/core/core-assets/skills/solution-design.md +2 -2
- package/dist/assets/core/core-assets/skills/structural-search.md +7 -1
- package/dist/assets/core/core-assets/skills/ticket-intake.md +6 -2
- package/dist/assets/core/core-assets/skills/verify-before-done.md +1 -1
- package/dist/assets/plugins/codegraph/managed/codegraph-protocol.md +2 -2
- package/dist/assets/plugins/engram/managed/engram-protocol.md +2 -2
- package/dist/assets/plugins/engram/skills/engram-leader.md +2 -2
- package/dist/assets/plugins/jscpd/plugin.json +8 -1
- package/dist/assets/plugins/jscpd/scripts/check-jscpd.sh +28 -30
- package/dist/assets/plugins/semgrep/plugin.json +8 -1
- package/dist/assets/plugins/semgrep/scripts/check-semgrep.sh +28 -42
- package/dist/assets/plugins/tgrep/managed/tgrep-protocol.md +1 -1
- package/dist/assets/plugins/tgrep/scripts/guard-search-routing.sh +201 -7
- package/dist/assets/plugins/tgrep/scripts/tgrep-search.sh +93 -0
- package/dist/assets/plugins/tgrep/scripts/tgrep-session.sh +48 -1
- package/dist/assets/plugins/tgrep/skills/tgrep-rung.md +2 -2
- package/dist/index.js +382 -341
- package/package.json +2 -2
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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;
|
|
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
|
|
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
|
|
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
|
|
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
|
-
<!--
|
|
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
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
- **
|
|
133
|
-
- **
|
|
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
|
|
143
|
-
-
|
|
144
|
-
-
|
|
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**:
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
|
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
|
|
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 →
|
|
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
|
|
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
|
|
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
|
|
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=$(
|
|
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
|
-
|
|
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
|
-
|
|
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=$(
|
|
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" ]
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
+
}
|