navori 0.6.1 → 0.6.3
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/dist/assets/core/core-assets/agents/auditor.md +5 -3
- package/dist/assets/core/core-assets/agents/commit-pr-pilot.md +99 -27
- package/dist/assets/core/core-assets/agents/explorer.md +2 -0
- package/dist/assets/core/core-assets/agents/implementer.md +2 -0
- package/dist/assets/core/core-assets/agents/leader.md +4 -2
- package/dist/assets/core/core-assets/agents/researcher.md +2 -0
- package/dist/assets/core/core-assets/agents/reviewer.md +6 -4
- package/dist/assets/core/core-assets/agents/ticket-audit.md +2 -0
- package/dist/assets/core/core-assets/hooks/audit-mode-close.sh +40 -0
- package/dist/assets/core/core-assets/hooks/audit-mode-trigger.sh +132 -0
- package/dist/assets/core/core-assets/hooks/guard-destructive.sh +259 -36
- package/dist/assets/core/core-assets/hooks/quality-gate-pre-commit.sh +17 -1
- package/dist/assets/core/core-assets/hooks/session-start-context.sh +29 -4
- package/dist/assets/core/core-assets/managed/operaciones-seguras.md +1 -1
- package/dist/assets/core/core-assets/managed/orquestacion.md +5 -3
- package/dist/assets/core/core-assets/presets/astro.json +1 -1
- package/dist/assets/core/core-assets/presets/background-worker.json +1 -1
- package/dist/assets/core/core-assets/presets/bun-keystone.json +1 -1
- package/dist/assets/core/core-assets/presets/express-mongoose.json +1 -1
- package/dist/assets/core/core-assets/presets/express.json +1 -1
- package/dist/assets/core/core-assets/presets/medusa.json +1 -1
- package/dist/assets/core/core-assets/presets/monorepo-turbopnpm.json +1 -1
- package/dist/assets/core/core-assets/presets/nestjs.json +1 -1
- package/dist/assets/core/core-assets/presets/nextjs.json +1 -1
- package/dist/assets/core/core-assets/presets/react-native-expo.json +1 -1
- package/dist/assets/core/core-assets/presets/vite-react-ts-mantine.json +1 -1
- package/dist/assets/core/core-assets/presets/vite-react-ts.json +1 -1
- package/dist/assets/core/core-assets/prompts.json +1 -1
- package/dist/assets/core/core-assets/settings/settings-base.json +158 -4
- package/dist/assets/core/core-assets/skills/structural-search.md +6 -4
- package/dist/assets/core/core-assets/skills/ticket-intake.md +3 -3
- package/dist/assets/plugins/codegraph/managed/codegraph-protocol.md +3 -13
- package/dist/assets/plugins/codegraph/skills/codegraph-rung.md +9 -6
- package/dist/assets/plugins/engram/managed/engram-protocol.md +3 -1
- package/dist/assets/plugins/engram/plugin.json +7 -0
- package/dist/assets/plugins/jscpd/scripts/check-jscpd.sh +51 -2
- package/dist/assets/plugins/semgrep/scripts/check-semgrep.sh +53 -7
- package/dist/index.js +354 -309
- package/package.json +7 -2
|
@@ -35,7 +35,7 @@ If there's a recent audit of the same scope and the code hasn't changed, read it
|
|
|
35
35
|
## Protocol
|
|
36
36
|
|
|
37
37
|
### 1. Startup
|
|
38
|
-
`CLAUDE.md` (project rules + the orchestrator block) is already in your context when your host injects it — read it from disk ONLY if your host did not inject it. Read the `user-section` below. Set the scope: **targeted** (1 file/feature/module) or **full** (
|
|
38
|
+
`CLAUDE.md` (project rules + the orchestrator block) is already in your context when your host injects it — read it from disk ONLY if your host did not inject it. Read the `user-section` below. Set the scope: **targeted** (1 file/feature/module) or **full** (every source directory the repo has — derive them from its layout, a monorepo has one per package; never assume a single root `src/`).
|
|
39
39
|
|
|
40
40
|
### 2. Context gathering
|
|
41
41
|
Explore **yourself** — you are a subagent and cannot launch others (`Agent` does not nest). For broad scope: `Glob` the structure, `Grep` the risk patterns, and read in full only the candidate files. Don't read generated/lock artifacts or library `ui`.
|
|
@@ -51,7 +51,7 @@ Every finding carries **root cause + `file:line` + suggested fix**.
|
|
|
51
51
|
|
|
52
52
|
### 3-bis. Mandatory axes — Security and Performance
|
|
53
53
|
|
|
54
|
-
Even if the user asks to focus "only on X", you **always** run both checklists over the scope. If the focus wasn't security/performance, their findings go in as a **NOTE** (root cause + 1 line); if they are **CRITICAL**, they escalate to the CRITICAL section anyway. The report **always** includes the
|
|
54
|
+
Even if the user asks to focus "only on X", you **always** run both checklists over the scope. If the focus wasn't security/performance, their findings go in as a **NOTE** (root cause + 1 line); if they are **CRITICAL**, they escalate to the CRITICAL section anyway. The report **always** includes the Security and Performance sub-sections (see the skeleton below), even if they say "no findings in this scope".
|
|
55
55
|
|
|
56
56
|
**SECURITY axis (generic — adapt to the stack in the user-section):**
|
|
57
57
|
- Hardcoded secrets or secrets in logs: grep `Bearer`, `sk_`, `api_key`, `secret`, `password=`, a committed `.env`.
|
|
@@ -125,9 +125,11 @@ If the finding depends on a dependency's behavior, **verify its docs with `WebFe
|
|
|
125
125
|
One line:
|
|
126
126
|
|
|
127
127
|
```
|
|
128
|
-
done -> .claude/progress/audit_deep_<scope>.md (+ plan_<scope>.md)
|
|
128
|
+
done -> .claude/progress/audit_deep_<scope>.md (+ .claude/progress/plan_<scope>.md)
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
+
Both are **input to the next step of the pipeline**, not chat summaries: the leader decomposes from the plan and hands the report to an `implementer` as its mandatory reference. Write them at those literal paths even where a host rule discourages writing report files — that rule exempts files written as input to another tool, and these are.
|
|
132
|
+
|
|
131
133
|
The leader (or the human) reads the report and the plan from disk and executes from there.
|
|
132
134
|
|
|
133
135
|
<!-- navori:user-section -->
|
|
@@ -19,11 +19,11 @@ You own the **end of the cycle**: well-structured Conventional commits and PRs w
|
|
|
19
19
|
## When NOT to trigger
|
|
20
20
|
|
|
21
21
|
- Working tree with uncommitted changes when the user only asked to "open the PR" → first commit or ask for permission.
|
|
22
|
-
- You are on `{{
|
|
23
|
-
- Harness active and
|
|
22
|
+
- You are on `{{prTarget}}`, on the branch this one was forked from, or another protected branch → abort + ask for a branch.
|
|
23
|
+
- Harness active and THIS feature's review — `.claude/progress/review_<feature>.md`, the single file the pre-flight below identifies by name — contains `CHANGES_REQUESTED` → no PR is created. Never scan the directory for it: a `CHANGES_REQUESTED` belonging to someone else's closed cycle must not abort your PR, exactly as another feature's `APPROVED` never unblocks it.
|
|
24
24
|
- Quality gate red this turn.
|
|
25
25
|
|
|
26
|
-
> **Two branches,
|
|
26
|
+
> **Two branches, one that decides:** `{{prTarget}}` is the PR's target branch — the one `gh pr create --base` receives and the one every diff below is computed against. The fork point (the branch this one was branched from) is a separate setting the repo declares on its own; in most repos the two name the same branch and the distinction costs you nothing. Where they differ, the fork-point diff is NOT the PR's, so the target always wins and you never have to work out which of the two a given name refers to.
|
|
27
27
|
|
|
28
28
|
## Mandatory pre-flight
|
|
29
29
|
|
|
@@ -31,18 +31,34 @@ Run these checks before drafting anything. If something fails, you stop and repo
|
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
33
|
git status --porcelain # what's left to commit
|
|
34
|
-
git rev-parse --abbrev-ref HEAD # cannot be {{
|
|
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
36
|
git log origin/{{prTarget}}..HEAD --oneline # must have ≥1 commit (or changes to commit)
|
|
37
|
-
git diff origin/{{prTarget}}
|
|
37
|
+
git diff origin/{{prTarget}} --stat # REAL scope so far (two-dot: see below)
|
|
38
38
|
gh auth status # gh authenticated
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
### The shipping diff — the one set every count in this pre-flight comes from
|
|
42
|
+
|
|
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:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
shipping=$({ git -c core.quotepath=false diff --name-only "origin/{{prTarget}}"; \
|
|
47
|
+
git -c core.quotepath=false ls-files --others --exclude-standard; } \
|
|
48
|
+
| sort -u | grep -vE '^(\.claude/progress/|progress/)')
|
|
49
|
+
printf '%s\n' "$shipping" # read it: this is what ships
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- **`$shipping` does not survive the call.** Each Bash call starts a fresh shell — no variable or function crosses over — so re-run the assignment in the same call as whatever reads it. That is a copy of four lines, not a second definition of the set.
|
|
53
|
+
- **Two dots, plus the untracked files — NEVER `...HEAD`.** Three-dot lists only what is already *committed*, and your trigger is by construction an **uncommitted** tree: there is no clean-working-tree check in this pre-flight because the commit is yours to make, further down. Run against an uncommitted tree, a three-dot listing comes back EMPTY — the coverage check then finds nothing missing and the waiver's count reads zero, so both are granted on every diff. It fails silently, in the unsafe direction. Two-dot plus `ls-files --others` is the exact set the `reviewer` captured and signed.
|
|
54
|
+
- **`progress/` is dropped**, the same grep the receipt applies, so the two sets line up 1:1 and a git-persisted session-state update never looks like an unreviewed file. Deletions DO stay in the set (the receipt records them as `deleted <path>`), so a removed file can't ship unreviewed.
|
|
55
|
+
- **`quotepath=false` on both listings**, exactly as the reviewer signed them: git C-quotes a non-ASCII path by default, and a quoted path never matches the receipt's line — the file would read as uncovered, or slip by unverified.
|
|
56
|
+
|
|
41
57
|
If the harness is active, identify THIS feature's review: `.claude/progress/review_<feature>.md`, with `<feature>` the id you received in your brief. A broad glob (`review_*.md`) over all reviews is not valid — it's not enough that some review with `APPROVED` exists in the directory, it has to be this feature's.
|
|
42
58
|
|
|
43
|
-
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
|
|
59
|
+
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.
|
|
44
60
|
|
|
45
|
-
<!-- This file-coverage rule lives here only;
|
|
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). -->
|
|
46
62
|
|
|
47
63
|
|
|
48
64
|
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`.
|
|
@@ -50,19 +66,12 @@ An absent file, ambiguous (more than one candidate), or with a verdict/scope tha
|
|
|
50
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):
|
|
51
67
|
|
|
52
68
|
```bash
|
|
53
|
-
# 1) COVERAGE: shipping
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
|
|
59
|
-
# quotepath=false on both listings, exactly as the reviewer signed them: git
|
|
60
|
-
# C-quotes a non-ASCII path by default and a quoted path never matches the
|
|
61
|
-
# receipt's line, so the file would look uncovered (or slip by unverified).
|
|
62
|
-
comm -23 \
|
|
63
|
-
<({ git -c core.quotepath=false diff --name-only "origin/{{prTarget}}"; \
|
|
64
|
-
git -c core.quotepath=false ls-files --others --exclude-standard; } \
|
|
65
|
-
| sort -u | grep -vE '^(\.claude/progress/|progress/)') \
|
|
69
|
+
# 1) COVERAGE: `$shipping` is THE SHIPPING DIFF above — assign it in this same
|
|
70
|
+
# call. Whatever this prints is a shipping file the receipt never listed → a
|
|
71
|
+
# 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.
|
|
73
|
+
# `grep .` drops the blank line an empty $shipping would otherwise feed comm.
|
|
74
|
+
comm -23 <(printf '%s\n' "$shipping" | grep .) \
|
|
66
75
|
<(grep -v '^#' .claude/progress/receipt.txt | sed 's/^[^ ]* //' | sort -u)
|
|
67
76
|
|
|
68
77
|
# 2) DRIFT: a reviewed file whose bytes changed since the review. A `deleted`
|
|
@@ -102,11 +111,23 @@ An `ERROR:` line is NOT drift: verification itself failed (git unavailable, wron
|
|
|
102
111
|
|
|
103
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. -->
|
|
104
113
|
|
|
105
|
-
**R1 exception (no reviewer):** a
|
|
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:
|
|
117
|
+
|
|
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.
|
|
121
|
+
|
|
122
|
+
A file you cannot classify counts as non-trivial: the fallback is the review, never the waiver.
|
|
123
|
+
|
|
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.
|
|
125
|
+
|
|
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`.
|
|
106
127
|
|
|
107
128
|
### Gate: `{{qualityGate.full}}` green before the PR
|
|
108
129
|
|
|
109
|
-
The PR gate is `{{qualityGate.full}}`
|
|
130
|
+
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:
|
|
110
131
|
|
|
111
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.
|
|
112
133
|
- **R1 (no reviewer):** there's no review evidence to trust — YOU run `{{qualityGate.full}}` green in pre-flight before `gh pr create`.
|
|
@@ -135,7 +156,17 @@ Never open the PR with the gate red.
|
|
|
135
156
|
- `git log origin/{{prTarget}}..HEAD --oneline` — commits included.
|
|
136
157
|
- `git diff origin/{{prTarget}}...HEAD --stat` — always.
|
|
137
158
|
- `git diff origin/{{prTarget}}...HEAD` — only if the diff < 500 lines. If larger, use only the stat + file list + the hunks of the 2–3 most relevant files.
|
|
138
|
-
- **Commit drag**
|
|
159
|
+
- **Commit drag** — only when the fork point and the target are different branches. Don't assert that they differ: let the shell settle it, so the ordinary case (both names resolve to the same branch, nothing can drag) simply doesn't run instead of producing a comparison of a branch with itself.
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
base={{branchBase}} # the fork point, as the repo declares it
|
|
163
|
+
if [ "$base" != "{{prTarget}}" ]; then
|
|
164
|
+
git fetch origin "$base" --quiet
|
|
165
|
+
git rev-list --count "origin/{{prTarget}}..origin/$base"
|
|
166
|
+
fi
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
A count > 0 means the fork point is ahead of `{{prTarget}}` and your PR drags those foreign commits: warn the user and suggest rebasing onto `{{prTarget}}` before opening.
|
|
139
170
|
- Ticket if applicable: branch name (e.g. `BT-1234-fix-x` → `BT-1234`) or a reference in the first commit.
|
|
140
171
|
- `.claude/progress/impl_<feature>.md` if it exists — non-obvious decisions.
|
|
141
172
|
|
|
@@ -148,7 +179,15 @@ Never open the PR with the gate red.
|
|
|
148
179
|
- If you mention a file that is NOT in `--stat`, remove it.
|
|
149
180
|
- No emojis. No `Co-Authored-By` unless the repo explicitly allows it in CLAUDE.md.
|
|
150
181
|
|
|
151
|
-
4. **
|
|
182
|
+
4. **Publish the branch** — the step between validating and firing `gh`, and the one that is easiest to assume someone else did. A PR shows what the REMOTE has, so on a branch with no upstream `gh pr create` drops into an interactive prompt asking where to push it: a prompt you cannot answer, so the turn hangs and no URL ever reaches the user.
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
git push -u origin HEAD
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Push AFTER the last commit and BEFORE `gh pr create` — a commit made later is not in the PR. `-u origin HEAD` works whether or not the branch already exists on the remote and never force-pushes; if the remote rejects it as non-fast-forward, stop and report, because resolving that is not yours (see Hard rules).
|
|
189
|
+
|
|
190
|
+
5. **Create the PR**:
|
|
152
191
|
|
|
153
192
|
```bash
|
|
154
193
|
gh pr create \
|
|
@@ -162,9 +201,9 @@ Never open the PR with the gate red.
|
|
|
162
201
|
|
|
163
202
|
Always pass `--base {{prTarget}}` explicitly — don't let `gh` use the repo's default branch. If the target changed, adjust it with `navori configure pr-target`.
|
|
164
203
|
|
|
165
|
-
|
|
204
|
+
6. **Output to the user**: only the PR URL + 1 line with the title. Nothing else.
|
|
166
205
|
|
|
167
|
-
|
|
206
|
+
7. **Checks — read them ONCE, never wait**: `gh pr checks <N> --json name,bucket,state,link,workflow`. `bucket: pending` (the normal case right after creating the PR) → say so in **one extra line** and stop, no retry. `bucket: fail` → name the check in that line and point to `babysit-prs` for the diagnosis. Informative only: you never hold or revert a PR over a red check.
|
|
168
207
|
|
|
169
208
|
## Body template (generic default)
|
|
170
209
|
|
|
@@ -215,9 +254,42 @@ wc -c CLAUDE.md # after
|
|
|
215
254
|
- ❌ Skipping pre-flight to "go faster" — the recurring bug is creating PRs with failing tests.
|
|
216
255
|
- ❌ Using `gh pr create --web` — you lose the controlled format.
|
|
217
256
|
|
|
257
|
+
## Worktree left behind (report it, never remove it)
|
|
258
|
+
|
|
259
|
+
Once the PR is open the worktree you ran in has done its job, and nobody
|
|
260
|
+
reclaims it: agent worktrees accumulate a full checkout each (they have reached
|
|
261
|
+
tens of GB in a single repo). But removing it is NOT yours to do — you are
|
|
262
|
+
standing inside it, and the call belongs to the human, so **report and stop
|
|
263
|
+
there**.
|
|
264
|
+
|
|
265
|
+
After the PR URL, check whether this run happened in a worktree and whether its
|
|
266
|
+
work is safely on the remote. Run it there and nowhere earlier: the verdict is
|
|
267
|
+
only informative once PR flow step 4 has pushed. Before that push `[ahead N]` is
|
|
268
|
+
true by construction — you just committed — so the check would report `NOT safe`
|
|
269
|
+
on every single cycle and mean nothing.
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
# A linked worktree has its own git dir; the main checkout has them equal.
|
|
273
|
+
[ "$(git rev-parse --git-dir)" = "$(git rev-parse --git-common-dir)" ] && echo "main-checkout" || echo "worktree"
|
|
274
|
+
git status --porcelain # must be empty
|
|
275
|
+
git status -sb | head -1 # must NOT say [ahead N] — step 4 pushed;
|
|
276
|
+
# [ahead N] here means a commit landed after it
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Then close your report with exactly one of:
|
|
280
|
+
|
|
281
|
+
- `worktree: none` — this ran in the main checkout, nothing to clean up.
|
|
282
|
+
- `worktree: <abs-path> — safe to remove (clean, pushed)` — the branch is on the
|
|
283
|
+
remote and nothing is uncommitted, so the PR holds every byte of the work.
|
|
284
|
+
- `worktree: <abs-path> — NOT safe (uncommitted changes | not pushed)` — say
|
|
285
|
+
which of the two, so the leader can decide instead of guessing.
|
|
286
|
+
|
|
287
|
+
Never run `git worktree remove` yourself, and never treat "the PR is open" as
|
|
288
|
+
proof the work is safe: what makes it recoverable is the branch being pushed.
|
|
289
|
+
|
|
218
290
|
## Communication with the leader
|
|
219
291
|
|
|
220
|
-
- If all OK: one line with the PR URL and the title.
|
|
292
|
+
- If all OK: one line with the PR URL and the title, plus the `worktree:` line.
|
|
221
293
|
- If pre-flight failed: one line explaining the check that failed, without invoking `gh`.
|
|
222
294
|
|
|
223
295
|
|
|
@@ -79,6 +79,8 @@ One line:
|
|
|
79
79
|
done -> .claude/progress/explore_<area>.md
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
+
`explore_<area>.md` is **input to the next step of the pipeline**, not a chat summary: the leader cross-reads it with the other explorers' files, and the `implementer` opens it as prior context. Write it at that literal path even where a host rule discourages writing report files — that rule exempts files written as input to another tool, and this is one.
|
|
83
|
+
|
|
82
84
|
<!-- navori:user-section -->
|
|
83
85
|
## Project rules
|
|
84
86
|
|
|
@@ -119,6 +119,8 @@ blocked -> .claude/progress/impl_<feature>.md
|
|
|
119
119
|
|
|
120
120
|
(In both cases the file is the same: your report with `Status: DONE | BLOCKED`. The leader consolidates blockers and session state in `progress/current.md`; you don't touch that file.)
|
|
121
121
|
|
|
122
|
+
`impl_<feature>.md` is **input to another tool**, not a chat summary: the `reviewer` opens it to judge your diff, and a `SubagentStop` hook flags it when it lands empty or without its `Status:` line — that hook never sees one that didn't land at all, so nothing else catches a handoff you skip. Write it at that literal path even where a host rule discourages writing report files — that rule exempts files written as input to another tool, and this is one.
|
|
123
|
+
|
|
122
124
|
Never return the diff in chat. The leader reads it from disk if it needs it.
|
|
123
125
|
|
|
124
126
|
<!-- navori:user-section -->
|
|
@@ -15,7 +15,7 @@ Your only job as orchestrator is to **decompose and coordinate**, never to imple
|
|
|
15
15
|
## Startup protocol
|
|
16
16
|
|
|
17
17
|
1. `CLAUDE.md` (stack, conventions, quality gate) is already in your context when your host injects it; read it from disk ONLY if your host did not inject it (e.g. an engine without automatic injection).
|
|
18
|
-
2. The catalog of subagents and skills is in `CLAUDE.md`
|
|
18
|
+
2. The catalog of subagents and skills is in `CLAUDE.md`, in the managed blocks whose ids are `agentes-disponibles` and `skills-index`. Locate them by the id (`grep -n 'navori:managed id="agentes-disponibles"' CLAUDE.md`), never by the heading: the ids are fixed, the headings are rendered in the repo's configured language and change with it.
|
|
19
19
|
3. Read `progress/current.md` (repo root) if it exists — the previous session's state.
|
|
20
20
|
4. Identify the task's scope against the "Project rules" below (legacy paths, critical areas, repo conventions).
|
|
21
21
|
5. **Did text from a ticket (Jira/Linear/GitHub/Slack) arrive?** If it matches your `ticket-audit` agent's triggers (bug in a critical feature, structural migration, feature that crosses >3 layers), invoke that agent first — it produces `.claude/progress/audit_ticket_<ID>.md` that guides all later decomposition. For trivial tickets (typo, copy, color), skip the audit.
|
|
@@ -83,12 +83,14 @@ Without "shall I proceed?" between each node.
|
|
|
83
83
|
|
|
84
84
|
## Anti-broken-telephone rule
|
|
85
85
|
|
|
86
|
-
When you launch subagents,
|
|
86
|
+
When you launch subagents, the **literal path** of the file each one must write is a fixed field of the encargo, not a recommendation. "Write a report" is prose and gets summarized on the way out; `.claude/progress/impl_auth.md` does not. You receive only:
|
|
87
87
|
|
|
88
88
|
```
|
|
89
89
|
done -> .claude/progress/<file>.md
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
+
Those files are **input to the next step of the pipeline**, not chat summaries for a reader: the `reviewer` opens the `implementer`'s, the `commit-pr-pilot` opens the `reviewer`'s and its `receipt.txt`, and a `SubagentStop` hook flags one that lands empty or without its `Status:`/verdict line (that hook never sees one that didn't land at all — that check is yours). A host rule against writing report files does not reach them — it exempts files written as input to another tool, and these are exactly that. Say so in the encargo if a subagent hesitates.
|
|
93
|
+
|
|
92
94
|
Expected files:
|
|
93
95
|
|
|
94
96
|
- `.claude/progress/audit_ticket_<TICKET-ID>.md` — deep analysis of one ticket (`ticket-audit`)
|
|
@@ -84,6 +84,8 @@ or
|
|
|
84
84
|
blocked -> <brief reason>
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
`research_<slug>.md` is **input to the next step of the pipeline**, not a chat summary: the leader cross-reads it against the other researchers' files to decide the decomposition. Write it at that literal path even where a host rule discourages writing report files — that rule exempts files written as input to another tool, and this is one.
|
|
88
|
+
|
|
87
89
|
Never return the report's content in chat. The leader reads it from disk.
|
|
88
90
|
|
|
89
91
|
<!-- navori:user-section -->
|
|
@@ -17,10 +17,10 @@ You are a strict reviewer. Your only function is to **approve or reject**. You d
|
|
|
17
17
|
1. Ground yourself in `CLAUDE.md` — already in your context when your host injects it; read it from disk ONLY if your host did not inject it. Then read `.claude/progress/impl_<feature>.md`, `.claude/progress/audit_ticket_<ID>.md` and `.claude/progress/solution_<scope>.md` (whichever exist). When there IS a solution artifact, the diff is judged against the approach it records — an implementation that quietly took a different path is a `SPEC_MISS`, even if the code is good. You do NOT re-open the design itself: whether that approach was the right one was settled in its own phase; your question is whether the code did what was agreed.
|
|
18
18
|
2. Identify modified files. Diff against `{{prTarget}}` (the PR's target
|
|
19
19
|
branch), **not** against the fork point: it's the EXACT diff GitHub will show and
|
|
20
|
-
the one commit-pr-pilot reviews.
|
|
21
|
-
the
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
the one commit-pr-pilot reviews. In most repos the branch you forked from and
|
|
21
|
+
the branch the PR targets are the same, and the distinction costs you nothing;
|
|
22
|
+
where they differ, the fork-point diff is NOT the PR's — so the target always
|
|
23
|
+
wins, and you never have to work out which of the two a given name refers to.
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
26
|
git status --short
|
|
@@ -189,6 +189,8 @@ or
|
|
|
189
189
|
CHANGES_REQUESTED -> .claude/progress/review_<feature>.md
|
|
190
190
|
```
|
|
191
191
|
|
|
192
|
+
`review_<feature>.md` and `receipt.txt` are **input to another tool**, not chat summaries: the `commit-pr-pilot` reads the verdict and re-hashes the receipt before it commits, and a `SubagentStop` hook flags a `review_*.md` that lands empty or without a verdict (that hook never sees one that didn't land at all, and never looks at `receipt.txt`). Write them at those literal paths even where a host rule discourages writing report files — that rule exempts files written as input to another tool, and these are.
|
|
193
|
+
|
|
192
194
|
## Hard rules
|
|
193
195
|
|
|
194
196
|
- ❌ Never skip Pass 1 (spec compliance). If the code is pretty but doesn't do what was asked, it's `CHANGES_REQUESTED`.
|
|
@@ -147,6 +147,8 @@ done -> .claude/progress/audit_ticket_<ID>.md
|
|
|
147
147
|
|
|
148
148
|
The leader reads the audit from disk and decomposes from there.
|
|
149
149
|
|
|
150
|
+
`audit_ticket_<ID>.md` is **input to the next step of the pipeline**, not a chat summary: every later phase reads it, and the `implementer` gets its path as a mandatory reference. Write it at that literal path even where a host rule discourages writing report files — that rule exempts files written as input to another tool, and this is one.
|
|
151
|
+
|
|
150
152
|
<!-- navori:user-section -->
|
|
151
153
|
## Project rules
|
|
152
154
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# navori — audit-mode close (SessionEnd)
|
|
3
|
+
#
|
|
4
|
+
# Seals the session's append-only log when the session ends. It does NOT ask
|
|
5
|
+
# and does NOT generate the report: SessionEnd has no one to ask — the human is
|
|
6
|
+
# already gone — and the log is immutable, so `navori audit` can build the
|
|
7
|
+
# report later, tomorrow or in two weeks, from the same intact file.
|
|
8
|
+
#
|
|
9
|
+
# FAIL-OPEN ABSOLUTE: exit 0 on anything unexpected.
|
|
10
|
+
|
|
11
|
+
set +e
|
|
12
|
+
|
|
13
|
+
payload=$(cat 2>/dev/null) || exit 0
|
|
14
|
+
[ -n "$payload" ] || exit 0
|
|
15
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
16
|
+
|
|
17
|
+
session_id=$(printf '%s' "$payload" | jq -r '.session_id // ""' 2>/dev/null) || exit 0
|
|
18
|
+
cwd=$(printf '%s' "$payload" | jq -r '.cwd // ""' 2>/dev/null) || exit 0
|
|
19
|
+
reason=$(printf '%s' "$payload" | jq -r '.reason // .matcher // "other"' 2>/dev/null) || reason="other"
|
|
20
|
+
[ -n "$session_id" ] || exit 0
|
|
21
|
+
[ -n "$cwd" ] || cwd=$PWD
|
|
22
|
+
|
|
23
|
+
repo=$(basename "$cwd" 2>/dev/null) || exit 0
|
|
24
|
+
[ -n "$repo" ] || exit 0
|
|
25
|
+
|
|
26
|
+
if [ -n "$NAVORI_AUDITS_ROOT" ]; then
|
|
27
|
+
audits_root=$NAVORI_AUDITS_ROOT
|
|
28
|
+
else
|
|
29
|
+
[ -n "$HOME" ] || exit 0
|
|
30
|
+
audits_root=$HOME/.navori/audits
|
|
31
|
+
fi
|
|
32
|
+
log_file=$audits_root/$repo/session-$session_id.log
|
|
33
|
+
|
|
34
|
+
[ -f "$log_file" ] || exit 0
|
|
35
|
+
|
|
36
|
+
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null) || ts=""
|
|
37
|
+
printf '%s\n' "$(jq -cn --arg ts "$ts" --arg r "$reason" \
|
|
38
|
+
'{ts:$ts,event:"session-end",reason:$r}' 2>/dev/null)" >> "$log_file" 2>/dev/null
|
|
39
|
+
|
|
40
|
+
exit 0
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# navori — audit-mode trigger (UserPromptSubmit)
|
|
3
|
+
#
|
|
4
|
+
# Detects an audit-mode invocation in the user's prompt and asks Claude to
|
|
5
|
+
# CONFIRM with the user before anything is recorded. It never activates the
|
|
6
|
+
# mode by itself: a false positive must die in the question, leaving no state
|
|
7
|
+
# on disk.
|
|
8
|
+
#
|
|
9
|
+
# While the mode is active it appends the typed prompt to the session's
|
|
10
|
+
# append-only log. Writes are O_APPEND only; the log is never re-read to be
|
|
11
|
+
# rewritten, so parallel subagents cannot corrupt it and a crashed session
|
|
12
|
+
# still leaves a valid (merely shorter) file.
|
|
13
|
+
#
|
|
14
|
+
# FAIL-OPEN ABSOLUTE: this hook runs on every prompt. Any error, any missing
|
|
15
|
+
# dependency, any odd path exits 0 silently. It must never be the reason a
|
|
16
|
+
# session fails to start.
|
|
17
|
+
|
|
18
|
+
set +e
|
|
19
|
+
|
|
20
|
+
emit_and_exit() { printf '%s\n' "$1"; exit 0; }
|
|
21
|
+
|
|
22
|
+
# Is `audit` actually available in the CLI on PATH?
|
|
23
|
+
#
|
|
24
|
+
# The hook orders the agent to run `navori audit --start`, and that resolves the
|
|
25
|
+
# PUBLISHED binary, never a working tree's build. When the installed version
|
|
26
|
+
# predates the subcommand, citty prints the help and exits 0 — so an agent that
|
|
27
|
+
# checks the exit code reads a silent no-op as success and reports a recording
|
|
28
|
+
# that never started. Match the subcommand inside the CLI's own USAGE line
|
|
29
|
+
# instead of trusting the status.
|
|
30
|
+
#
|
|
31
|
+
# Returns 1 when the subcommand is absent AND when the check itself cannot run
|
|
32
|
+
# (no binary on PATH, no USAGE line). Both collapse into "could not confirm",
|
|
33
|
+
# which is what the caller's message must say: claiming "your version is old"
|
|
34
|
+
# would be wrong for a machine with no navori installed at all.
|
|
35
|
+
audit_subcommand_available() {
|
|
36
|
+
command -v navori >/dev/null 2>&1 || return 1
|
|
37
|
+
usage=$(navori --help 2>/dev/null | grep -m1 '^USAGE' 2>/dev/null) || return 1
|
|
38
|
+
[ -n "$usage" ] || return 1
|
|
39
|
+
# Normalize separators so the token matches at any position: `USAGE navori
|
|
40
|
+
# init|add|audit` -> `|USAGE|navori|init|add|audit|`.
|
|
41
|
+
tokens=$(printf '%s' "$usage" | tr ' ' '|' 2>/dev/null) || return 1
|
|
42
|
+
case "|$tokens|" in
|
|
43
|
+
*"|audit|"*) return 0 ;;
|
|
44
|
+
*) return 1 ;;
|
|
45
|
+
esac
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
payload=$(cat 2>/dev/null) || exit 0
|
|
49
|
+
[ -n "$payload" ] || exit 0
|
|
50
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
51
|
+
|
|
52
|
+
# The typed text, under whichever key the host uses.
|
|
53
|
+
#
|
|
54
|
+
# Reading only `.user_prompt` recorded EMPTY prompts against a real session:
|
|
55
|
+
# the hook fired, matched, and appended `{"event":"prompt","prompt":""}` — the
|
|
56
|
+
# field simply wasn't there. An audit whose whole job is attribution cannot
|
|
57
|
+
# silently log blanks, and `jq`'s `//` makes tolerating both names free.
|
|
58
|
+
#
|
|
59
|
+
# Order matters: the more specific `user_prompt` wins when both are present, so
|
|
60
|
+
# a host that ships both never gets the wrong one.
|
|
61
|
+
prompt=$(printf '%s' "$payload" | jq -r '.user_prompt // .prompt // ""' 2>/dev/null) || exit 0
|
|
62
|
+
session_id=$(printf '%s' "$payload" | jq -r '.session_id // ""' 2>/dev/null) || exit 0
|
|
63
|
+
cwd=$(printf '%s' "$payload" | jq -r '.cwd // ""' 2>/dev/null) || exit 0
|
|
64
|
+
[ -n "$session_id" ] || exit 0
|
|
65
|
+
# The id composes `log_file` below, so a path-shaped one would escape the
|
|
66
|
+
# audit root. The CLI validates it too (#503) — this guard is here so the
|
|
67
|
+
# hook does not DEPEND on that: "safe because the other layer cannot create
|
|
68
|
+
# the case" is the coupling that let three delete paths drift apart. Same
|
|
69
|
+
# character class the CLI enforces; anything else means the payload is not
|
|
70
|
+
# what we think it is, so do nothing rather than guess.
|
|
71
|
+
case "$session_id" in
|
|
72
|
+
*[!A-Za-z0-9_-]*) exit 0 ;;
|
|
73
|
+
esac
|
|
74
|
+
[ -n "$cwd" ] || cwd=$PWD
|
|
75
|
+
|
|
76
|
+
repo=$(basename "$cwd" 2>/dev/null) || exit 0
|
|
77
|
+
[ -n "$repo" ] || exit 0
|
|
78
|
+
|
|
79
|
+
if [ -n "$NAVORI_AUDITS_ROOT" ]; then
|
|
80
|
+
audits_root=$NAVORI_AUDITS_ROOT
|
|
81
|
+
else
|
|
82
|
+
[ -n "$HOME" ] || exit 0
|
|
83
|
+
audits_root=$HOME/.navori/audits
|
|
84
|
+
fi
|
|
85
|
+
log_file=$audits_root/$repo/session-$session_id.log
|
|
86
|
+
|
|
87
|
+
lower=$(printf '%s' "$prompt" | tr '[:upper:]' '[:lower:]' 2>/dev/null) || exit 0
|
|
88
|
+
|
|
89
|
+
# Deliberately tolerant: a spurious match costs one question, while a missed
|
|
90
|
+
# one costs the whole recording.
|
|
91
|
+
case "$lower" in
|
|
92
|
+
*"audit mode"*|*"audit-mode"*|*"modo audit"*|*"modo auditoría"*|*"modo auditoria"*) matched=1 ;;
|
|
93
|
+
*) matched=0 ;;
|
|
94
|
+
esac
|
|
95
|
+
|
|
96
|
+
case "$lower" in
|
|
97
|
+
*apaga*|*apagar*|*desactiva*|*"salir de"*|*detén*|*deten*|*stop*|*"turn off"*|*disable*) off_intent=1 ;;
|
|
98
|
+
*) off_intent=0 ;;
|
|
99
|
+
esac
|
|
100
|
+
|
|
101
|
+
if [ -f "$log_file" ]; then
|
|
102
|
+
# Active: record the human's own words — they entered the model's context,
|
|
103
|
+
# so they cost tokens and belong in the audit.
|
|
104
|
+
#
|
|
105
|
+
# `transcript_path` rides along because the payload is the ONLY place it is
|
|
106
|
+
# stated. Without it the reader has to guess the transcript's location by
|
|
107
|
+
# re-deriving Claude Code's undocumented directory encoding (see paths.ts),
|
|
108
|
+
# and a guess that misses costs the whole report.
|
|
109
|
+
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null) || ts=""
|
|
110
|
+
transcript=$(printf '%s' "$payload" | jq -r '.transcript_path // ""' 2>/dev/null) || transcript=""
|
|
111
|
+
printf '%s\n' "$(jq -cn --arg ts "$ts" --arg ev "prompt" --arg p "$prompt" --arg tr "$transcript" \
|
|
112
|
+
'{ts:$ts,event:$ev,prompt:$p} + (if $tr == "" then {} else {transcript:$tr} end)' 2>/dev/null)" >> "$log_file" 2>/dev/null
|
|
113
|
+
|
|
114
|
+
if [ "$matched" = "1" ] && [ "$off_intent" = "1" ]; then
|
|
115
|
+
if audit_subcommand_available; then
|
|
116
|
+
emit_and_exit "[navori audit-mode] The user asked to turn audit-mode OFF. Before doing anything, ask them explicitly: \"audit mode will be turned off, continue?\". Only if they confirm, run: navori audit --stop $session_id (that seals the log and generates the report). Then check the output: it must name the report it wrote. If it prints the command list (USAGE) instead, the installed CLI has no such subcommand — tell the user and do NOT assume the session was closed. If they decline, leave the mode active and carry on with the task."
|
|
117
|
+
else
|
|
118
|
+
emit_and_exit "[navori audit-mode] The user asked to turn audit-mode OFF, but the available 'navori' could not be confirmed to ship the 'audit' subcommand: it may not be installed at all, or it may predate that command. Do NOT run 'navori audit --stop' blindly: if the binary exists but is old, it prints its help and exits 0, which looks like success without being one. Tell the user what happened and that the session log stays intact on disk, so the report can still be generated once the CLI is up to date. Then carry on with the task."
|
|
119
|
+
fi
|
|
120
|
+
fi
|
|
121
|
+
exit 0
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
if [ "$matched" = "1" ] && [ "$off_intent" = "0" ]; then
|
|
125
|
+
if audit_subcommand_available; then
|
|
126
|
+
emit_and_exit "[navori audit-mode] An audit-mode invocation was detected in the prompt. Before activating anything, ask the user explicitly: \"an audit mode invocation was detected, continue?\". Only if they confirm, run: navori audit --start $session_id (that creates the session log). Then check the output: it must name the log file it created. If it prints the command list (USAGE) instead, the installed CLI has no such subcommand — tell the user and do NOT assume the mode is active. If they decline, run nothing and carry on with the task."
|
|
127
|
+
else
|
|
128
|
+
emit_and_exit "[navori audit-mode] An audit-mode invocation was detected, but the available 'navori' could not be confirmed to ship the 'audit' subcommand: it may not be installed at all, or it may predate that command. Do NOT run 'navori audit --start' blindly: if the binary exists but is old, it prints its help and exits 0, which looks like success without being one. Tell the user that nothing was activated, and why. Then carry on with the task."
|
|
129
|
+
fi
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
exit 0
|