navori 0.4.1 → 0.4.2
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/commit-pr-pilot.md +16 -6
- package/dist/assets/core/core-assets/agents/explorer.md +2 -2
- package/dist/assets/core/core-assets/agents/implementer.md +2 -2
- package/dist/assets/core/core-assets/agents/leader.md +3 -1
- package/dist/assets/core/core-assets/agents/researcher.md +2 -2
- package/dist/assets/core/core-assets/agents/reviewer.md +17 -6
- package/dist/assets/core/core-assets/agents/ticket-audit.md +1 -1
- package/dist/assets/core/core-assets/hooks/_partials/extract-cmd.sh +21 -0
- package/dist/assets/core/core-assets/hooks/_partials/gate-trigger.sh +59 -0
- package/dist/assets/core/core-assets/hooks/guard-destructive.sh +8 -19
- package/dist/assets/core/core-assets/hooks/quality-gate-pre-commit.sh +20 -76
- package/dist/assets/core/core-assets/hooks/session-start-context.sh +17 -1
- package/dist/assets/core/core-assets/hooks/subagent-stop-handoff.sh +1 -1
- package/dist/assets/core/core-assets/managed/cierre-sesion.md +1 -1
- package/dist/assets/core/core-assets/managed/codex-cross-review.md +14 -0
- package/dist/assets/core/core-assets/managed/orquestacion.md +4 -2
- package/dist/assets/core/core-assets/skills/dominio.md +67 -0
- package/dist/assets/core/core-assets/skills/ticket-intake.md +3 -3
- package/dist/assets/plugins/acli/plugin.json +14 -2
- package/dist/assets/plugins/codegraph/plugin.json +2 -1
- package/dist/assets/plugins/engram/plugin.json +10 -3
- package/dist/assets/plugins/gh/plugin.json +2 -1
- package/dist/assets/plugins/jscpd/plugin.json +2 -1
- package/dist/assets/plugins/jscpd/scripts/check-jscpd.sh +22 -80
- package/dist/assets/plugins/semgrep/plugin.json +4 -2
- package/dist/assets/plugins/semgrep/scripts/check-semgrep.sh +23 -82
- package/dist/index.js +381 -313
- package/package.json +3 -2
|
@@ -47,21 +47,31 @@ Open that specific file and confirm its verdict is `APPROVED` and that its scope
|
|
|
47
47
|
|
|
48
48
|
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`.
|
|
49
49
|
|
|
50
|
-
**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). 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):
|
|
50
|
+
**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
51
|
|
|
52
52
|
```bash
|
|
53
53
|
# 1) COVERAGE: shipping files the receipt never listed → reviewer never saw them.
|
|
54
|
-
#
|
|
55
|
-
#
|
|
54
|
+
# EXACT same diff set the reviewer captured — tracked-vs-target + untracked,
|
|
55
|
+
# minus the harness's own progress/ files (same grep the receipt applies) — so
|
|
56
|
+
# the sets line up 1:1 with no spurious mismatches. A git-persisted progress/
|
|
57
|
+
# update never counts as "uncovered"; deletions DO stay in the set (the receipt
|
|
58
|
+
# records them as `deleted <path>`) so a removed file can't ship unreviewed.
|
|
56
59
|
comm -23 \
|
|
57
|
-
<({ git diff --name-only "origin/{{prTarget}}"; git ls-files --others --exclude-standard; }
|
|
60
|
+
<({ git diff --name-only "origin/{{prTarget}}"; git ls-files --others --exclude-standard; } \
|
|
61
|
+
| sort -u | grep -vE '^(\.claude/progress/|progress/)') \
|
|
58
62
|
<(grep -v '^#' .claude/progress/receipt.txt | sed 's/^[^ ]* //' | sort -u)
|
|
59
63
|
|
|
60
|
-
# 2) DRIFT: a reviewed file whose bytes changed since the review.
|
|
64
|
+
# 2) DRIFT: a reviewed file whose bytes changed since the review. A `deleted`
|
|
65
|
+
# marker means the reviewer signed off on the removal → drift only if the file
|
|
66
|
+
# came back.
|
|
61
67
|
while IFS= read -r line; do
|
|
62
68
|
case "$line" in ''|'#'*) continue ;; esac
|
|
63
69
|
blob=${line%% *}; path=${line#* }
|
|
64
|
-
[ "$
|
|
70
|
+
if [ "$blob" = deleted ]; then
|
|
71
|
+
[ -e "$path" ] && echo "DRIFT: $path (reappeared since review)"
|
|
72
|
+
else
|
|
73
|
+
[ "$(git hash-object "$path" 2>/dev/null)" = "$blob" ] || echo "DRIFT: $path"
|
|
74
|
+
fi
|
|
65
75
|
done < .claude/progress/receipt.txt
|
|
66
76
|
```
|
|
67
77
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: explorer
|
|
3
3
|
description: Broad map of an area or module of the repo. Returns structure, dependencies, and entry points. Does not modify code.
|
|
4
|
-
tools: Read, Glob, Grep, Bash
|
|
4
|
+
tools: Read, Glob, Grep, Bash, Write
|
|
5
5
|
model: {{models.explorer}}
|
|
6
6
|
effort: {{effort.explorer}}
|
|
7
7
|
---
|
|
@@ -25,7 +25,7 @@ If the question is specific ("where is X?"), it's not you — it's `researcher`.
|
|
|
25
25
|
|
|
26
26
|
1. Read `CLAUDE.md` to understand the repo's conventions.
|
|
27
27
|
2. Define the scope: a folder, a logical module, a file pattern. The orchestrator should hand it to you precisely; if it arrives ambiguous, return `blocked` naming the options (folder X / module Y / pattern Z) so it re-sends it scoped — don't guess.
|
|
28
|
-
3. Walk from the entry points (routes, module root exports, `index.ts`) toward the leaves. For each level, list files and their brief role. Apply `.claude/skills/structural-search.md` to locate shapes and entry points without reading whole files.
|
|
28
|
+
3. Walk from the entry points (routes, module root exports, `index.ts`) toward the leaves. For each level, list files and their brief role. Apply `.claude/skills/structural-search/SKILL.md` to locate shapes and entry points without reading whole files.
|
|
29
29
|
4. Identify reverse dependencies: which external modules consume this module? That indicates the "blast radius" of changing something here.
|
|
30
30
|
5. Write `.claude/progress/explore_<area>.md`:
|
|
31
31
|
|
|
@@ -26,7 +26,7 @@ You execute **a single** task from start to verification. You don't orchestrate,
|
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
- `Expected files: <list>`
|
|
29
|
-
3. **Implement** following the repo's flow (the leader's "Project rules" define the concrete pattern: layers, libs, paths, naming). To locate the code to touch, apply `.claude/skills/structural-search.md`: open only the confirmed span, don't read whole files by reflex.
|
|
29
|
+
3. **Implement** following the repo's flow (the leader's "Project rules" define the concrete pattern: layers, libs, paths, naming). To locate the code to touch, apply `.claude/skills/structural-search/SKILL.md`: open only the confirmed span, don't read whole files by reflex.
|
|
30
30
|
4. **Quality gate** (mandatory before returning):
|
|
31
31
|
|
|
32
32
|
```bash
|
|
@@ -70,7 +70,7 @@ No speculative abstractions: no interface / layer / flag with a single "just in
|
|
|
70
70
|
|
|
71
71
|
## Evidence-based completion (gate before the report)
|
|
72
72
|
|
|
73
|
-
Before returning `done -> .claude/progress/impl_<feature>.md`, apply `.claude/skills/verify-before-done.md`. Summary of the Iron Law:
|
|
73
|
+
Before returning `done -> .claude/progress/impl_<feature>.md`, apply `.claude/skills/verify-before-done/SKILL.md`. Summary of the Iron Law:
|
|
74
74
|
|
|
75
75
|
| Claim you're going to make | Required output | Not sufficient |
|
|
76
76
|
|---|---|---|
|
|
@@ -93,10 +93,12 @@ Expected files:
|
|
|
93
93
|
|
|
94
94
|
- `.claude/progress/audit_ticket_<TICKET-ID>.md` — deep analysis of one ticket (`ticket-audit`)
|
|
95
95
|
- `.claude/progress/audit_deep_<scope>.md` — deep read-only audit of a module/area/repo with no ticket (`auditor`)
|
|
96
|
+
- `.claude/progress/plan_<scope>.md` — the `auditor`'s prioritized plan that accompanies a deep audit
|
|
96
97
|
- `.claude/progress/explore_<topic>.md` — broad map (`explorer`)
|
|
97
98
|
- `.claude/progress/research_<question>.md` — scoped question (`researcher`)
|
|
98
99
|
- `.claude/progress/impl_<feature>.md` — the `implementer`'s report (includes its `Status: DONE | BLOCKED`)
|
|
99
100
|
- `.claude/progress/review_<feature>.md` — the `reviewer`'s verdict
|
|
101
|
+
- `.claude/progress/receipt.txt` — the `reviewer`'s content receipt on `APPROVED` (binds the diff to the reviewed bytes; consumed by `commit-pr-pilot`)
|
|
100
102
|
|
|
101
103
|
**Path separation (don't mix):** `.claude/progress/` is ONLY for these ephemeral handoffs between agents. The **session state** (current task, plan, blockers) lives in `progress/current.md` (repo root, persists in git) and you consolidate it **YOU, only**: subagents never write it. When an `implementer` reports `blocked` in its `impl_<feature>.md`, you record the blocker in `progress/current.md` along with the next step.
|
|
102
104
|
|
|
@@ -105,7 +107,7 @@ Expected files:
|
|
|
105
107
|
When `.claude/progress/review_<feature>.md` contains `APPROVED`:
|
|
106
108
|
|
|
107
109
|
1. Invoke `commit-pr-pilot` to draft the title + body following the repo's format and open the PR.
|
|
108
|
-
2. Pre-flight on you before invoking:
|
|
110
|
+
2. Pre-flight on you before invoking: you're not on `{{branchBase}}`, `{{qualityGate.fast}}` green this turn, `gh auth status` ok. (Do NOT require a clean working tree — the pilot's trigger IS an uncommitted diff ready to commit, and the pilot, not you, owns that commit.)
|
|
109
111
|
3. Return to the user only the PR URL + title.
|
|
110
112
|
|
|
111
113
|
If the review returned `CHANGES_REQUESTED`, do NOT invoke `commit-pr-pilot`: launch another `implementer` with the list of changes and restart the cycle.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: researcher
|
|
3
3
|
description: Read-only investigation of a scoped question. Reads the repo, writes findings to a file. Does not modify code.
|
|
4
|
-
tools: Read, Glob, Grep, Bash
|
|
4
|
+
tools: Read, Glob, Grep, Bash, Write
|
|
5
5
|
model: {{models.researcher}}
|
|
6
6
|
effort: {{effort.researcher}}
|
|
7
7
|
---
|
|
@@ -28,7 +28,7 @@ If the question is broad ("map the whole module X for me"), it's not you — it'
|
|
|
28
28
|
3. Run the search:
|
|
29
29
|
- Primary method: the native `Grep` (content) and `Glob` (files by name/pattern) tools. They're read-only, fast (ripgrep), and don't ask for permission.
|
|
30
30
|
- Fallback only for what the tools don't cover (git history with `git grep`, FS metadata with `find`): shell commands. Chained with pipes/redirects they ask for confirmation, so reserve the shell for when `Grep`/`Glob` fall short.
|
|
31
|
-
- For semantic questions (not just string match), apply `.claude/skills/structural-search.md`: locate the right region and open only the confirmed span; don't read whole files by reflex.
|
|
31
|
+
- For semantic questions (not just string match), apply `.claude/skills/structural-search/SKILL.md`: locate the right region and open only the confirmed span; don't read whole files by reflex.
|
|
32
32
|
4. Validate each finding: open the file, confirm the match means what it seems (sometimes a `grep` matches comments or strings unrelated to the concept).
|
|
33
33
|
5. Write `.claude/progress/research_<question-slug>.md`:
|
|
34
34
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: reviewer
|
|
3
3
|
description: Strict reviewer. Approves or rejects the implementer's work against CLAUDE.md. Does not edit code.
|
|
4
|
-
tools: Read, Glob, Grep, Bash
|
|
4
|
+
tools: Read, Glob, Grep, Bash, Write
|
|
5
5
|
model: {{models.reviewer}}
|
|
6
6
|
effort: {{effort.reviewer}}
|
|
7
7
|
---
|
|
@@ -26,11 +26,16 @@ You are a strict reviewer. Your only function is to **approve or reject**. You d
|
|
|
26
26
|
git status --short
|
|
27
27
|
git fetch origin {{prTarget}} --quiet
|
|
28
28
|
git diff --stat
|
|
29
|
-
|
|
29
|
+
# two-dot: the FULL working tree vs the target (committed AND uncommitted),
|
|
30
|
+
# the exact set the receipt fingerprints below. Three-dot (`...HEAD`) would show
|
|
31
|
+
# only committed changes, but in the harness the diff is still uncommitted — so
|
|
32
|
+
# the review command would read empty while the receipt signs the working tree.
|
|
33
|
+
git diff "origin/{{prTarget}}"
|
|
34
|
+
git ls-files --others --exclude-standard # untracked files (new, not yet staged)
|
|
30
35
|
```
|
|
31
36
|
|
|
32
37
|
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.
|
|
33
|
-
4. Apply `.claude/skills/verify-before-done.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.
|
|
38
|
+
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.
|
|
34
39
|
|
|
35
40
|
### Pass 1 — Spec compliance
|
|
36
41
|
|
|
@@ -52,7 +57,7 @@ Does the diff do EXACTLY what was asked? You don't review style yet.
|
|
|
52
57
|
|
|
53
58
|
Does the code match the repo's conventions? Here you do review style/naming/types.
|
|
54
59
|
|
|
55
|
-
Apply `.claude/skills/review-diff.md` — the full checklist by dimensions, with severities. Its CRITICAL/HIGH map to the ≥80 issues below; MEDIUM to the informational observations. Summary of the minimum to validate against `CLAUDE.md` and the leader's "Project rules":
|
|
60
|
+
Apply `.claude/skills/review-diff/SKILL.md` — the full checklist by dimensions, with severities. Its CRITICAL/HIGH map to the ≥80 issues below; MEDIUM to the informational observations. Summary of the minimum to validate against `CLAUDE.md` and the leader's "Project rules":
|
|
56
61
|
|
|
57
62
|
- **Conventions**: naming, path aliases, folder structure.
|
|
58
63
|
- **Centralized types**: no inline `type`/`interface` where the convention says "outside".
|
|
@@ -87,12 +92,18 @@ printf '# navori-receipt v1 feature=<feature>\n' > .claude/progress/receipt.txt
|
|
|
87
92
|
| sort -u \
|
|
88
93
|
| grep -vE '^(\.claude/progress/|progress/)' \
|
|
89
94
|
| while IFS= read -r f; do
|
|
90
|
-
[ -f "$f" ]
|
|
95
|
+
if [ -f "$f" ]; then
|
|
96
|
+
printf '%s %s\n' "$(git hash-object "$f")" "$f" # live file → blob sha
|
|
97
|
+
else
|
|
98
|
+
printf 'deleted %s\n' "$f" # removed file → deletion marker
|
|
99
|
+
fi
|
|
91
100
|
done >> .claude/progress/receipt.txt
|
|
92
101
|
```
|
|
93
102
|
|
|
94
103
|
It captures the working-tree bytes under review (committed **and** uncommitted). The `grep -v` drops the harness's own ephemeral progress files (the receipt, `impl_*`, `review_*`) — they never get committed, so fingerprinting them would be self-referential noise. Skip the whole step for `CHANGES_REQUESTED` — a rejected diff has nothing to bind.
|
|
95
104
|
|
|
105
|
+
A **removed** file has no bytes to hash, so it's recorded as `deleted <path>` instead of a blob sha. Keeping the deletion **in** the receipt is what closes the RDD cycle: the `commit-pr-pilot` coverage check is path-based, so it still sees the path (a deletion can't ship unreviewed), and both its drift check and the pre-commit hook read the `deleted` marker as "must stay absent" — flagging drift only if the file reappears. The shipping set the pilot compares against is then byte-for-byte the set you signed here (same `grep -vE`, deletions included), so a git-persisted `progress/` update or a removed file never shows up as "uncovered" and livelocks the close.
|
|
106
|
+
|
|
96
107
|
### Confidence scoring per finding (Pass 2)
|
|
97
108
|
|
|
98
109
|
Each issue is scored 0-100. Only issues ≥80 block APPROVED. Issues 50-79 are listed as "informational observations" (they don't block). <50 = don't report.
|
|
@@ -164,7 +175,7 @@ CHANGES_REQUESTED -> .claude/progress/review_<feature>.md
|
|
|
164
175
|
|
|
165
176
|
- ❌ Never skip Pass 1 (spec compliance). If the code is pretty but doesn't do what was asked, it's `CHANGES_REQUESTED`.
|
|
166
177
|
- ❌ Never include as a blocker (in "Issues ≥80") a finding with confidence <80.
|
|
167
|
-
- ✅ Apply `.claude/skills/verify-before-done.md` before marking APPROVED: each `[x]` must be backed by evidence run this turn (not from the implementer's cached report).
|
|
178
|
+
- ✅ Apply `.claude/skills/verify-before-done/SKILL.md` before marking APPROVED: each `[x]` must be backed by evidence run this turn (not from the implementer's cached report).
|
|
168
179
|
- ❌ Never approve with `{{qualityGate.full}}` red.
|
|
169
180
|
- ❌ Never approve if the new code **adds new errors or warnings** vs baseline.
|
|
170
181
|
- ❌ Never approve new code with explicit or implicit `any` without a valid `// any justified: <reason>`.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ticket-audit
|
|
3
3
|
description: Deep analysis of a complex ticket before implementing. Produces audit_ticket_<ID>.md with root cause, affected areas, and a decomposition plan.
|
|
4
|
-
tools: Read, Glob, Grep, Bash
|
|
4
|
+
tools: Read, Glob, Grep, Bash, Write
|
|
5
5
|
model: {{models.ticketAudit}}
|
|
6
6
|
effort: {{effort.ticketAudit}}
|
|
7
7
|
---
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Shared hook boilerplate — inlined into each hook at render time (see the
|
|
2
|
+
# include directive in the source scripts + lib/hook-includes.ts). Single source
|
|
3
|
+
# of truth for the sibling gate scripts; DO NOT copy this body back into a hook
|
|
4
|
+
# by hand (that is the drift #225/#261 removed).
|
|
5
|
+
#
|
|
6
|
+
# PreToolUse(Bash) passes the tool input on stdin. Extract .tool_input.command
|
|
7
|
+
# WITHOUT hard-depending on jq (NOT preinstalled on macOS): try jq, then node
|
|
8
|
+
# (Claude Code's own runtime), then a best-effort sed unwrap. No command
|
|
9
|
+
# extracted → empty $cmd, and each caller decides what that means (the gate
|
|
10
|
+
# scripts scan defensively; guard-destructive waves the command through).
|
|
11
|
+
payload=$(cat)
|
|
12
|
+
extract_cmd() {
|
|
13
|
+
if command -v jq >/dev/null 2>&1; then
|
|
14
|
+
printf '%s' "$payload" | jq -r '.tool_input.command // empty' 2>/dev/null && return 0
|
|
15
|
+
fi
|
|
16
|
+
if command -v node >/dev/null 2>&1; then
|
|
17
|
+
printf '%s' "$payload" | node -e 'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>{try{process.stdout.write(String(JSON.parse(s)?.tool_input?.command??""))}catch{}})' 2>/dev/null && return 0
|
|
18
|
+
fi
|
|
19
|
+
printf '%s' "$payload" | sed -n 's/.*"command"[[:space:]]*:[[:space:]]*"\(.*\)".*/\1/p'
|
|
20
|
+
}
|
|
21
|
+
cmd=$(extract_cmd)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Shared gate detector — inlined into each hook at render time (see the include
|
|
2
|
+
# directive in the source scripts + lib/hook-includes.ts). The caller MUST set
|
|
3
|
+
# $TRIGGER_RE (an ERE) before the include; it decides which git ops this hook
|
|
4
|
+
# gates. Single source of truth for the FIX B/C wrapper-peeling logic; DO NOT
|
|
5
|
+
# copy this body into a hook by hand.
|
|
6
|
+
#
|
|
7
|
+
# Detect whether $1 (a possibly-compound command) invokes a gated operation.
|
|
8
|
+
# Splits $1 on the shell separators && || ; | and newlines, strips leading
|
|
9
|
+
# whitespace plus wrapper words (`(`, `\`, `command `) and `VAR=value` env
|
|
10
|
+
# prefixes from each segment, and returns 0 if ANY segment STARTS with a gated
|
|
11
|
+
# `git …` invocation on a word boundary (matched by $TRIGGER_RE). Replaces
|
|
12
|
+
# literal-prefix `case` matching, which silently skipped the gate for
|
|
13
|
+
# `cd x && git commit`, `echo y; git push`, or a leading space (#88: NEVER skip
|
|
14
|
+
# the gate silently). Matching a segment START means a quoted `echo "git commit"`
|
|
15
|
+
# does NOT trigger it. Known limitation: it cannot see through `sh -c`, `eval`,
|
|
16
|
+
# or obfuscation — a seatbelt, not a sandbox.
|
|
17
|
+
is_scan_trigger() {
|
|
18
|
+
local input="$1" segment
|
|
19
|
+
# FIX B: join `\<newline>` continuations into a space FIRST, so a command
|
|
20
|
+
# split across lines with a trailing backslash stays ONE logical segment
|
|
21
|
+
# (otherwise the subcommand/flag lands in a segment not starting with git).
|
|
22
|
+
input="${input//\\$'\n'/ }"
|
|
23
|
+
input="${input//&&/$'\n'}"
|
|
24
|
+
input="${input//||/$'\n'}"
|
|
25
|
+
input="${input//;/$'\n'}"
|
|
26
|
+
input="${input//|/$'\n'}"
|
|
27
|
+
# `<<<` feeds the already-expanded value as data — no re-evaluation — so a
|
|
28
|
+
# command that contains backticks/$() is inspected, never executed.
|
|
29
|
+
while IFS= read -r segment; do
|
|
30
|
+
segment="${segment#"${segment%%[![:space:]]*}"}" # strip leading ws
|
|
31
|
+
# FIX C: peel wrappers so `(git …`, `\git`, `command git …` and
|
|
32
|
+
# `VAR=val git …` all reduce to a plain `git …` before matching.
|
|
33
|
+
while [[ "$segment" == \(* ]]; do # strip leading ( runs
|
|
34
|
+
segment="${segment#\(}"
|
|
35
|
+
segment="${segment#"${segment%%[![:space:]]*}"}"
|
|
36
|
+
done
|
|
37
|
+
segment="${segment#\\}" # strip a leading backslash (\git)
|
|
38
|
+
while [[ "$segment" =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; do # strip VAR=val prefixes
|
|
39
|
+
case "$segment" in
|
|
40
|
+
*[[:space:]]*)
|
|
41
|
+
segment="${segment#*[[:space:]]}"
|
|
42
|
+
segment="${segment#"${segment%%[![:space:]]*}"}"
|
|
43
|
+
;;
|
|
44
|
+
*) segment=""; break ;;
|
|
45
|
+
esac
|
|
46
|
+
done
|
|
47
|
+
if [[ "$segment" == command\ * ]]; then # strip a leading `command ` word
|
|
48
|
+
segment="${segment#command }"
|
|
49
|
+
segment="${segment#"${segment%%[![:space:]]*}"}"
|
|
50
|
+
fi
|
|
51
|
+
# FIX C: allow git global options between `git` and the subcommand
|
|
52
|
+
# (`git -c k=v commit`, `git -C /repo push`). $TRIGGER_RE's trailing boundary
|
|
53
|
+
# keeps `git commitgraph` / `git config …` from matching.
|
|
54
|
+
if printf '%s' "$segment" | grep -qE "$TRIGGER_RE"; then
|
|
55
|
+
return 0
|
|
56
|
+
fi
|
|
57
|
+
done <<< "$input"
|
|
58
|
+
return 1
|
|
59
|
+
}
|
|
@@ -21,27 +21,16 @@
|
|
|
21
21
|
# bottom — `$cmd` is already parsed and in scope there.
|
|
22
22
|
set -euo pipefail
|
|
23
23
|
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
# still inspects the command instead of failing open.
|
|
29
|
-
payload=$(cat)
|
|
30
|
-
extract_cmd() {
|
|
31
|
-
if command -v jq >/dev/null 2>&1; then
|
|
32
|
-
printf '%s' "$payload" | jq -r '.tool_input.command // empty' 2>/dev/null && return 0
|
|
33
|
-
fi
|
|
34
|
-
if command -v node >/dev/null 2>&1; then
|
|
35
|
-
printf '%s' "$payload" | node -e 'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>{try{process.stdout.write(String(JSON.parse(s)?.tool_input?.command??""))}catch{}})' 2>/dev/null && return 0
|
|
36
|
-
fi
|
|
37
|
-
# No JSON parser on PATH: pull the "command" string out with sed. Best-effort
|
|
38
|
-
# (won't handle a literal embedded quote), but far better than failing open.
|
|
39
|
-
printf '%s' "$payload" | sed -n 's/.*"command"[[:space:]]*:[[:space:]]*"\(.*\)".*/\1/p'
|
|
40
|
-
}
|
|
41
|
-
cmd=$(extract_cmd)
|
|
24
|
+
# Command extraction (payload → $cmd). Shared body, single source of truth.
|
|
25
|
+
# A missing JSON parser used to make this guard wave every command through; the
|
|
26
|
+
# shared extractor falls back to sed so it still inspects the command.
|
|
27
|
+
# navori:include extract-cmd
|
|
42
28
|
[ -z "$cmd" ] && exit 0
|
|
43
29
|
|
|
44
|
-
|
|
30
|
+
# branchBase is shell-quoted at render time via the shq: marker (#197): a
|
|
31
|
+
# hostile branchBase in navori.config.json lands here as an inert literal,
|
|
32
|
+
# never executable.
|
|
33
|
+
base={{shq:branchBase}}
|
|
45
34
|
|
|
46
35
|
block() {
|
|
47
36
|
echo "[navori] BLOQUEADO por guard-destructive: $1" >&2
|
|
@@ -9,20 +9,8 @@
|
|
|
9
9
|
# bottom — they keep `cmd` and the original exit codes in scope.
|
|
10
10
|
set -euo pipefail
|
|
11
11
|
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# unwrap. If nothing extracts a command $cmd stays empty and the gate skips.
|
|
15
|
-
payload=$(cat)
|
|
16
|
-
extract_cmd() {
|
|
17
|
-
if command -v jq >/dev/null 2>&1; then
|
|
18
|
-
printf '%s' "$payload" | jq -r '.tool_input.command // empty' 2>/dev/null && return 0
|
|
19
|
-
fi
|
|
20
|
-
if command -v node >/dev/null 2>&1; then
|
|
21
|
-
printf '%s' "$payload" | node -e 'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>{try{process.stdout.write(String(JSON.parse(s)?.tool_input?.command??""))}catch{}})' 2>/dev/null && return 0
|
|
22
|
-
fi
|
|
23
|
-
printf '%s' "$payload" | sed -n 's/.*"command"[[:space:]]*:[[:space:]]*"\(.*\)".*/\1/p'
|
|
24
|
-
}
|
|
25
|
-
cmd=$(extract_cmd)
|
|
12
|
+
# Command extraction (payload → $cmd). Shared body, single source of truth.
|
|
13
|
+
# navori:include extract-cmd
|
|
26
14
|
|
|
27
15
|
# Detect the project's REAL package manager from lockfiles / package.json, so a
|
|
28
16
|
# gate command hardcoded to one PM (e.g. `pnpm run ...`) can still run in a repo
|
|
@@ -59,7 +47,8 @@ run_gate() {
|
|
|
59
47
|
# --- content receipt (RDD) --------------------------------------------------
|
|
60
48
|
# Backstop that binds the commit to the exact bytes the reviewer approved. The
|
|
61
49
|
# reviewer writes a receipt (`<blob-sha> <path>` lines, one per approved file,
|
|
62
|
-
# via `git hash-object`
|
|
50
|
+
# via `git hash-object`, plus `deleted <path>` for a file it approved removing)
|
|
51
|
+
# when it marks APPROVED; the commit-pr-pilot recomputes
|
|
63
52
|
# it before committing and consumes it after. THIS is the mechanical net for a
|
|
64
53
|
# direct `git commit` that skips the pilot: if an approved file's content
|
|
65
54
|
# drifted since the review (rebase, human tweak, follow-up edit), block. It
|
|
@@ -99,6 +88,10 @@ check_content_receipt() {
|
|
|
99
88
|
path=${line#* } # path = the rest (may contain spaces)
|
|
100
89
|
[ -n "$blob" ] && [ "$blob" != "$path" ] || continue
|
|
101
90
|
printf '%s\n' "$commit_set" | grep -qxF "$path" || continue # not in this commit → ignore
|
|
91
|
+
if [ "$blob" = deleted ]; then # reviewer approved the removal
|
|
92
|
+
[ -e "$path" ] && drift="${drift} - ${path} (reappeared)"$'\n' # drift only if it came back
|
|
93
|
+
continue
|
|
94
|
+
fi
|
|
102
95
|
now=$(git hash-object "$path" 2>/dev/null || true)
|
|
103
96
|
if [ "$now" != "$blob" ]; then
|
|
104
97
|
drift="${drift} - ${path}"$'\n'
|
|
@@ -108,75 +101,26 @@ check_content_receipt() {
|
|
|
108
101
|
if [ -n "$drift" ]; then
|
|
109
102
|
echo "[navori] APPROVED content changed since review (receipt mismatch). Commit BLOCKED." >&2
|
|
110
103
|
printf '%s' "$drift" >&2
|
|
111
|
-
echo "[navori] Re-run the reviewer over the current diff
|
|
104
|
+
echo "[navori] Re-run the reviewer over the current diff. To bypass, run the commit yourself outside the agent." >&2
|
|
112
105
|
echo "[navori] To clear a stale receipt: rm $receipt" >&2
|
|
113
106
|
exit 2
|
|
114
107
|
fi
|
|
115
108
|
}
|
|
116
109
|
|
|
117
|
-
#
|
|
118
|
-
#
|
|
119
|
-
|
|
120
|
-
#
|
|
121
|
-
# segment, and returns 0 if ANY segment STARTS with `git commit` on
|
|
122
|
-
# a word boundary. Replaces literal-prefix `case` matching, which silently
|
|
123
|
-
# skipped the gate for `cd x && git commit`, `echo y; git commit`, or a leading
|
|
124
|
-
# space (#88: NEVER skip the gate silently). Because it matches a segment START,
|
|
125
|
-
# a quoted `echo "git commit"` does NOT trigger it. Known limitation: it cannot
|
|
126
|
-
# see through `sh -c`, `eval`, or obfuscation — a seatbelt, not a sandbox.
|
|
127
|
-
# The IDENTICAL function body lives in the sibling gate scripts (they render
|
|
128
|
-
# standalone, so there is no shared lib to import):
|
|
129
|
-
# plugins/jscpd/scripts/check-jscpd.sh
|
|
130
|
-
# plugins/semgrep/scripts/check-semgrep.sh
|
|
131
|
-
is_git_commit() {
|
|
132
|
-
local input="$1" segment
|
|
133
|
-
# FIX B: join `\<newline>` continuations into a space FIRST, so a command
|
|
134
|
-
# split across lines with a trailing backslash stays ONE logical segment
|
|
135
|
-
# (otherwise the subcommand/flag lands in a segment not starting with git).
|
|
136
|
-
input="${input//\\$'\n'/ }"
|
|
137
|
-
input="${input//&&/$'\n'}"
|
|
138
|
-
input="${input//||/$'\n'}"
|
|
139
|
-
input="${input//;/$'\n'}"
|
|
140
|
-
input="${input//|/$'\n'}"
|
|
141
|
-
# `<<<` feeds the already-expanded value as data — no re-evaluation — so a
|
|
142
|
-
# command that contains backticks/$() is inspected, never executed.
|
|
143
|
-
while IFS= read -r segment; do
|
|
144
|
-
segment="${segment#"${segment%%[![:space:]]*}"}" # strip leading ws
|
|
145
|
-
# FIX C: peel wrappers so `(git …`, `\git`, `command git …` and
|
|
146
|
-
# `VAR=val git …` all reduce to a plain `git …` before matching.
|
|
147
|
-
while [[ "$segment" == \(* ]]; do # strip leading ( runs
|
|
148
|
-
segment="${segment#\(}"
|
|
149
|
-
segment="${segment#"${segment%%[![:space:]]*}"}"
|
|
150
|
-
done
|
|
151
|
-
segment="${segment#\\}" # strip a leading backslash (\git)
|
|
152
|
-
while [[ "$segment" =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; do # strip VAR=val prefixes
|
|
153
|
-
case "$segment" in
|
|
154
|
-
*[[:space:]]*)
|
|
155
|
-
segment="${segment#*[[:space:]]}"
|
|
156
|
-
segment="${segment#"${segment%%[![:space:]]*}"}"
|
|
157
|
-
;;
|
|
158
|
-
*) segment=""; break ;;
|
|
159
|
-
esac
|
|
160
|
-
done
|
|
161
|
-
if [[ "$segment" == command\ * ]]; then # strip a leading `command ` word
|
|
162
|
-
segment="${segment#command }"
|
|
163
|
-
segment="${segment#"${segment%%[![:space:]]*}"}"
|
|
164
|
-
fi
|
|
165
|
-
# FIX C: allow git global options between `git` and the subcommand
|
|
166
|
-
# (`git -c k=v commit`, `git -C /repo push`). Trailing boundary keeps
|
|
167
|
-
# `git commitgraph` / `git config …` from matching as commit.
|
|
168
|
-
if printf '%s' "$segment" | grep -qE '^git([[:space:]]+-[a-zA-Z-]+(=[^[:space:]]+)?([[:space:]]+[^-][^[:space:]]*)?)*[[:space:]]+commit([[:space:]]|$)'; then
|
|
169
|
-
return 0
|
|
170
|
-
fi
|
|
171
|
-
done <<< "$input"
|
|
172
|
-
return 1
|
|
173
|
-
}
|
|
110
|
+
# Gate to `git commit` only. $TRIGGER_RE is consumed by the shared detector
|
|
111
|
+
# inlined below.
|
|
112
|
+
TRIGGER_RE='^git([[:space:]]+-[a-zA-Z-]+(=[^[:space:]]+)?([[:space:]]+[^-][^[:space:]]*)?)*[[:space:]]+commit([[:space:]]|$)'
|
|
113
|
+
# navori:include gate-trigger
|
|
174
114
|
|
|
175
|
-
if
|
|
115
|
+
if is_scan_trigger "$cmd"; then
|
|
176
116
|
# Content-bind first: refuse to commit bytes that drifted from the approval
|
|
177
117
|
# before spending the fast gate on them.
|
|
178
118
|
check_content_receipt
|
|
179
|
-
|
|
119
|
+
# qualityGate.fast is shell-quoted at render time via the shq: marker (#197).
|
|
120
|
+
# The gate string is still `eval`'d by run_gate below (running the gate is the
|
|
121
|
+
# feature), but quoting it here means a hostile qualityGate.fast survives as one
|
|
122
|
+
# literal token instead of injecting commands at variable-assignment time.
|
|
123
|
+
gate={{shq:qualityGate.fast}}
|
|
180
124
|
gate_bin="${gate%% *}"
|
|
181
125
|
if command -v "$gate_bin" >/dev/null 2>&1; then
|
|
182
126
|
run_gate "$gate"
|
|
@@ -192,7 +136,7 @@ if is_git_commit "$cmd"; then
|
|
|
192
136
|
run_gate "$detected_pm ${gate#* }"
|
|
193
137
|
else
|
|
194
138
|
echo "[navori] quality-gate NO ejecutado: '$gate_bin' no está en PATH y no hay un package manager alternativo detectado que pueda correrlo." >&2
|
|
195
|
-
echo "[navori] Commit BLOQUEADO para no saltarnos el gate en silencio. Instala '$gate_bin' o
|
|
139
|
+
echo "[navori] Commit BLOQUEADO para no saltarnos el gate en silencio. Instala '$gate_bin', o si de verdad quieres saltártelo corre el commit tú mismo fuera del agente." >&2
|
|
196
140
|
exit 2
|
|
197
141
|
fi
|
|
198
142
|
fi
|
|
@@ -29,7 +29,9 @@ add() { ctx="${ctx}${1}"$'\n'; }
|
|
|
29
29
|
|
|
30
30
|
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
31
31
|
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo '?')
|
|
32
|
-
|
|
32
|
+
# branchBase is shell-quoted at render time via the shq: marker (#197) so an
|
|
33
|
+
# untrusted branchBase can't inject a command here.
|
|
34
|
+
base={{shq:branchBase}}
|
|
33
35
|
if [ "$branch" = "$base" ]; then
|
|
34
36
|
add "Branch: ${branch} ⚠️ on the base branch — create a working branch before committing."
|
|
35
37
|
else
|
|
@@ -59,6 +61,20 @@ if [ -n "$current" ]; then
|
|
|
59
61
|
fi
|
|
60
62
|
fi
|
|
61
63
|
|
|
64
|
+
# Workspace Dominio: canonical cross-repo knowledge for the workspace this repo
|
|
65
|
+
# belongs to (e.g. "coachee = user-profile.kind"), so agents don't relearn it
|
|
66
|
+
# wrong in every repo. The CLI owns the resolution (which workspace is cwd in +
|
|
67
|
+
# read the index); the hook stays dumb. Cheap pre-check first so the common
|
|
68
|
+
# no-workspace case never spawns the binary, and `|| true` so a missing/broken
|
|
69
|
+
# `navori` never blocks session startup. (spec 0011 §6.1)
|
|
70
|
+
if [ -d "$HOME/.navori/workspaces" ] && command -v navori >/dev/null 2>&1; then
|
|
71
|
+
dominio=$(navori dominio inject 2>/dev/null || true)
|
|
72
|
+
if [ -n "$dominio" ]; then
|
|
73
|
+
add ""
|
|
74
|
+
add "$dominio"
|
|
75
|
+
fi
|
|
76
|
+
fi
|
|
77
|
+
|
|
62
78
|
[ -n "$ctx" ] || exit 0
|
|
63
79
|
|
|
64
80
|
# Emit the JSON safely: node (best escaping) → jq → give up (exit 0, no context).
|
|
@@ -47,7 +47,7 @@ shopt -s nullglob
|
|
|
47
47
|
for f in "$dir"/impl_*.md; do
|
|
48
48
|
if is_blank "$f"; then
|
|
49
49
|
note "$(basename "$f") vacío"
|
|
50
|
-
elif ! grep -
|
|
50
|
+
elif ! grep -qiE '^\*{0,2}status:?\*{0,2}' "$f" 2>/dev/null; then
|
|
51
51
|
note "$(basename "$f") sin línea 'Status:'"
|
|
52
52
|
fi
|
|
53
53
|
done
|
|
@@ -6,4 +6,4 @@ Before closing the session:
|
|
|
6
6
|
2. **History**: add an entry in `progress/history.md` with `## YYYY-MM-DD HH:MM <agent> — <summary>` + changes + gate status.
|
|
7
7
|
3. **Clear current**: leave `progress/current.md` at `idle` or with the explicit next step.
|
|
8
8
|
4. **No temporaries**: delete scratch files; don't leave `console.log`, `debugger`, or commented-out code.
|
|
9
|
-
5. **Conventional commit**: `feat|fix|chore|docs(scope): message`,
|
|
9
|
+
5. **Conventional commit**: `feat|fix|chore|docs(scope): message`, atomic, in the language defined by the config's `commits`. Never commit `.claude/` or `CLAUDE.md`.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
## Cross-model review (Codex second opinion)
|
|
2
|
+
|
|
3
|
+
This repo renders the `codex` engine, so a second opinion from a **different provider** is one command away. After your `reviewer` approves a non-trivial diff — or on any change touching a critical area — you MAY have Codex review the SAME diff against this repo's own standards (already rendered in `AGENTS.md` + `.codex/agents/reviewer.toml`):
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
CODEX_HOME=$(pwd)/.codex codex exec --sandbox read-only "revisa el diff origin/{{prTarget}}...HEAD según los estándares del repo"
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
- **Read-only:** Codex inspects, never edits or commits, and needs no approvals.
|
|
10
|
+
- The verdict lands on **stdout**; progress noise goes to stderr.
|
|
11
|
+
- Auth via `CODEX_API_KEY` or a prior `codex login`. Don't pass `--model` — Codex's default is correct.
|
|
12
|
+
- **Advisory, not a gate:** a second lens on the diff. Weigh its findings against your `reviewer`'s and decide; it doesn't block the PR on its own.
|
|
13
|
+
|
|
14
|
+
Reach for it in `criticalAreas`, on high-blast-radius changes, or when the user asks for a cross-check — not on every trivial diff.
|
|
@@ -37,7 +37,7 @@ Fan-out is a lever, not a toll — so when you do delegate, hand the smallest en
|
|
|
37
37
|
|
|
38
38
|
- **Peel off the mechanical first.** Copies, renames, scaffolding, JSON/string edits → do them yourself in R1 or send them to a low-tier agent; never bundle them into an `implementer`'s encargo, where they inflate its context and its run without raising quality.
|
|
39
39
|
- **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.
|
|
40
|
-
- **Tier by sub-task, not by round.**
|
|
40
|
+
- **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).
|
|
41
41
|
- **One-pass review on small/medium diffs.** Fix minor review findings yourself instead of spending a re-verification round; reserve a re-review for a risky fix that touched shared machinery.
|
|
42
42
|
|
|
43
43
|
### Continuous execution (don't pause between tasks)
|
|
@@ -46,4 +46,6 @@ Once the plan/scope is approved (R2+), execute ALL sub-tasks without confirming
|
|
|
46
46
|
|
|
47
47
|
### Synthesis without broken telephone
|
|
48
48
|
|
|
49
|
-
Instruct subagents to **write to `.claude/progress/<file>.md`**; you receive only `done -> file`. That folder is ONLY for ephemeral agent handoffs (`audit_*`, `explore_*`, `research_*`, `impl_*`, `review_
|
|
49
|
+
Instruct subagents to **write to `.claude/progress/<file>.md`**; you receive only `done -> file`. That folder is ONLY for ephemeral agent handoffs (`audit_*`, `plan_*`, `explore_*`, `research_*`, `impl_*`, `review_*`, `receipt.txt`); **session state** (task, plan, blockers) lives in `progress/current.md` (root, git-persisted) and you consolidate it, never the subagents — each `implementer` reports its state (including `blocked`) in its own `impl_<feature>.md`. Verify the diff/evidence yourself; don't trust the report blindly. To close the cycle, invoke `commit-pr-pilot` — when `review_<feature>.md` says `APPROVED` (R2+), or directly for a trivial R1 diff that never went through a `reviewer`. The pilot gates the PR on `{{qualityGate.full}}` (green over the shipping diff — the reviewer's Pass-2 evidence in R2+, or the pilot's own run in R1). Pre-flight: not on `{{branchBase}}`, `gh auth status` ok (no clean-working-tree check — the pilot's trigger IS the uncommitted diff, and the pilot owns that commit). If `CHANGES_REQUESTED`, 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.
|
|
50
|
+
|
|
51
|
+
**Second opinion (post-`APPROVED`).** On a non-trivial diff — or any change touching a critical area — if this repo also renders the `codex` engine, a review from a **different provider** is one command away: see the **Cross-model review** sub-block in `.claude/agents/leader.md`.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dominio
|
|
3
|
+
description: Use when you discover — or need — a durable fact that spans multiple repos of a workspace (data model, business rule, migration, cross-service contract, shared gotcha). The Dominio is the workspace's canonical knowledge base; read it before assuming a model, and promote such facts into it instead of only saving to session memory.
|
|
4
|
+
type: reference
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# dominio — the workspace knowledge base
|
|
8
|
+
|
|
9
|
+
Canonical, cross-repo facts for a workspace live as markdown under
|
|
10
|
+
`~/.navori/workspaces/<name>/dominio/`: one file per entry (`<id>.md`) plus a
|
|
11
|
+
derived index (`DOMINIO.md`) that is injected at session start. This is where a
|
|
12
|
+
fact like *"`coach`/`coachee` no longer exist — it's `user-profile.kind`"* lives
|
|
13
|
+
once for every repo, so it isn't relearned wrong in each one.
|
|
14
|
+
|
|
15
|
+
## Read first
|
|
16
|
+
|
|
17
|
+
The Dominio index is injected at the top of each session for repos that belong to
|
|
18
|
+
a workspace. **Before assuming a data model, business rule or cross-service
|
|
19
|
+
contract, check it.** Open the full entry (`navori dominio show <id>`, or read
|
|
20
|
+
`workspace://<name>/dominio/<id>.md`) when you need the detail behind an index
|
|
21
|
+
line.
|
|
22
|
+
|
|
23
|
+
## When to promote a fact (all THREE must hold)
|
|
24
|
+
|
|
25
|
+
1. **Durable** — it won't change next sprint; a structural fact, not a transient state.
|
|
26
|
+
2. **Transversal** — it applies to **≥2 repos** of the workspace. Test: *"would an agent in ANOTHER repo get this wrong without it?"*
|
|
27
|
+
3. **Canonical** — it's a fact/rule, not a task, a log, or an opinion.
|
|
28
|
+
|
|
29
|
+
If it passes, write it to the Dominio (below). If not, it belongs elsewhere.
|
|
30
|
+
|
|
31
|
+
### Do NOT put in the Dominio
|
|
32
|
+
|
|
33
|
+
Ticket status / progress / TODOs → `progress/`. Session scratch → engram.
|
|
34
|
+
Single-repo detail → that repo's `CLAUDE.md`. Personal preferences → engram.
|
|
35
|
+
Volatile values (versions, counts). **Secrets — never.**
|
|
36
|
+
|
|
37
|
+
## How to write an entry
|
|
38
|
+
|
|
39
|
+
Create `~/.navori/workspaces/<name>/dominio/<slug>.md` (`<slug>` is a stable
|
|
40
|
+
kebab-case id = the filename). Keep it to **one fact, short**:
|
|
41
|
+
|
|
42
|
+
```markdown
|
|
43
|
+
---
|
|
44
|
+
id: user-profile-model
|
|
45
|
+
title: Modelo user-profile
|
|
46
|
+
type: migration # architecture | business-rule | migration | gotcha | glossary
|
|
47
|
+
applies-to: [nexus, webapp, dashboard, mobile] # repos, or "all"
|
|
48
|
+
status: canonical # canonical | deprecated | superseded
|
|
49
|
+
supersedes: [] # ids this entry replaces
|
|
50
|
+
updated: 2026-07-30
|
|
51
|
+
updated_by: <you>
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
<the fact>. **Por qué:** <reason>. **Cómo aplica:** <what to do differently>.
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Then run `navori dominio reindex` to refresh the index.
|
|
58
|
+
|
|
59
|
+
## Curate — update, don't pile up
|
|
60
|
+
|
|
61
|
+
- **Update > duplicate.** Search existing entries first (`navori dominio list`);
|
|
62
|
+
edit the matching one and bump `updated`, don't add a second.
|
|
63
|
+
- **Retire, don't delete.** When a fact is replaced, set the old entry
|
|
64
|
+
`status: superseded` and point the new one's `supersedes:` at it — the history
|
|
65
|
+
keeps an agent from rediscovering the old model.
|
|
66
|
+
- **`navori dominio doctor`** validates coherence (all warnings). Reindex after
|
|
67
|
+
any change.
|