navori 0.7.4 → 0.7.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.
@@ -37,7 +37,7 @@ hypothetical future abstractions or optional edge cases as BLOCKER.
37
37
  1. `CLAUDE.md` carries the repo's context — it is already in your context when your host injects it; read it from disk ONLY if your host did not inject it.
38
38
  2. Work on ONE scoped question (the orchestrator already handed you the scope). If you discover it's actually >2 independent questions, return them as a list so the orchestrator distributes them across parallel researchers — don't chain them in series yourself.
39
39
  3. Run the search:
40
- - 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.
40
+ - 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. In auto mode the shell additionally pays a classifier round-trip per command — measured, a native search answers in ~0.08s against ~0.20s (p75 1.83s) for the same search through the shell — so the native lane is cheaper in every mode and much cheaper there.
41
41
  - 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.
42
42
  - 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.
43
43
  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).
@@ -0,0 +1,38 @@
1
+ # Shared armed-audit consumption (#597, #599) — inlined into each consuming hook
2
+ # at render time (see lib/hook-includes.ts). Single source of truth for the flag
3
+ # protocol so the two consumers cannot drift apart:
4
+ #
5
+ # · SessionStart — arm BEFORE opening the session (original #597 flow).
6
+ # · UserPromptSubmit — arm the RUNNING session: `navori audit --arm` (from
7
+ # another terminal, or in-session via `! navori audit --arm`) and the NEXT
8
+ # message activates recording (#599). No restart, no lost context.
9
+ #
10
+ # The flag is `<audits-root>/<repo>/.armed`, written by `navori audit --arm`.
11
+ # Consumption comes FIRST: the flag arms exactly ONE session. If `--start` then
12
+ # fails, the arm is lost rather than latched — a flag that survives a failure
13
+ # would fire on some later unrelated session, which is worse than asking the
14
+ # user to arm again.
15
+ #
16
+ # CALLER CONTRACT: $1 is a session id ALREADY validated against the shared
17
+ # charset (#503) — this function trusts it into a command line, so an unvalidated
18
+ # id must never reach here. $2 is the payload's cwd (#454: never
19
+ # CLAUDE_PROJECT_DIR — they differ in worktrees, and --arm wrote the flag under
20
+ # the name basename(cwd) resolves to). $3 is the audits root.
21
+ #
22
+ # Fail-open and silent: returns 0 ONLY when audit-mode was actually started, so
23
+ # the caller can announce it; every other path returns 1 and changes nothing.
24
+ # Safe under `set -euo pipefail` and `set +e` alike.
25
+ navori_audit_consume_armed() {
26
+ narm_sid=$1
27
+ narm_cwd=$2
28
+ narm_root=$3
29
+ [ -n "$narm_sid" ] && [ -n "$narm_cwd" ] && [ -n "$narm_root" ] || return 1
30
+ narm_repo=$(basename "$narm_cwd" 2>/dev/null) || return 1
31
+ [ -n "$narm_repo" ] || return 1
32
+ narm_file=$narm_root/$narm_repo/.armed
33
+ [ -f "$narm_file" ] || return 1
34
+ command -v navori >/dev/null 2>&1 || return 1
35
+ rm -f "$narm_file" 2>/dev/null || true
36
+ navori audit --start "$narm_sid" --cwd "$narm_cwd" >/dev/null 2>&1 || return 1
37
+ return 0
38
+ }
@@ -61,6 +61,20 @@ else
61
61
  fi
62
62
  log_file=$audits_root/$repo/session-$session_id.log
63
63
 
64
+ # ─── Armed audit-mode for the RUNNING session (#599) ─────────────────────────
65
+ # `navori audit --arm` (another terminal, or `! navori audit --arm` in-session)
66
+ # → the NEXT prompt lands here, consumes the flag and starts recording. This is
67
+ # the UX the SessionStart-only flow lacked: no closing and reopening a session
68
+ # that is already warm. Costs one stat per prompt when not armed. The stdout
69
+ # line is deliberate — a UserPromptSubmit hook's stdout is injected as context,
70
+ # so the model learns it is being recorded the moment it starts to be.
71
+ # Mid-session coverage is already modeled by the report (recorder horizon), so a
72
+ # log that starts at prompt N is a smaller log, never a broken one.
73
+ # navori:include audit-arm
74
+ if navori_audit_consume_armed "$session_id" "$cwd" "$audits_root"; then
75
+ printf 'navori: audit-mode ACTIVE from this message on (armed via navori audit --arm; the hook ran --start %s).\n' "$session_id"
76
+ fi
77
+
64
78
  # Not marked → not recording. This is also what makes the hook free outside
65
79
  # audit-mode: one stat and out.
66
80
  [ -f "$log_file" ] || exit 0
@@ -58,6 +58,37 @@ trap navori_audit_on_exit EXIT
58
58
  ctx=""
59
59
  add() { ctx="${ctx}${1}"$'\n'; }
60
60
 
61
+ # ─── Armed audit-mode (#597/#599): consume the flag `navori audit --arm` left.
62
+ # The consumption protocol lives in the shared partial (also inlined into the
63
+ # UserPromptSubmit recorder, which covers the RUNNING session); this hook covers
64
+ # "armed before the session opened".
65
+ # navori:include audit-arm
66
+ _armed_root=${NAVORI_AUDITS_ROOT:-${HOME:-}/.navori/audits}
67
+ # The authoritative repo comes from the payload's `cwd`, same as the recorder
68
+ # partial (#454): the hook process can start somewhere other than the session's
69
+ # repo, and `--arm` wrote the flag under the name `basename(cwd)` resolves to.
70
+ _armed_cwd=""
71
+ if command -v jq >/dev/null 2>&1; then
72
+ _armed_cwd=$(printf '%s' "$payload" | jq -r '.cwd // ""' 2>/dev/null || true)
73
+ fi
74
+ [ -n "$_armed_cwd" ] || _armed_cwd=${CLAUDE_PROJECT_DIR:-$PWD}
75
+ _armed_sid=""
76
+ if command -v jq >/dev/null 2>&1; then
77
+ _armed_sid=$(printf '%s' "$payload" | jq -r '.session_id // ""' 2>/dev/null || true)
78
+ fi
79
+ # Same charset guard the CLI and the recorder apply (#503): a path-shaped id
80
+ # means the payload is not what we think it is — do nothing rather than guess.
81
+ case "$_armed_sid" in
82
+ "" | *[!A-Za-z0-9_-]*) : ;;
83
+ *)
84
+ if navori_audit_consume_armed "$_armed_sid" "$_armed_cwd" "$_armed_root"; then
85
+ # Tell the MODEL, not just the log: the session should know it is being
86
+ # recorded, and the user should see the activation in the first turn.
87
+ add "navori: audit-mode ACTIVE for this session (armed via 'navori audit --arm'; the hook ran --start ${_armed_sid})."
88
+ fi
89
+ ;;
90
+ esac
91
+
61
92
  # UNTRUSTED-DATA FENCE (#511). Two of the three things this hook injects are
62
93
  # repository CONTENT, not harness instruction: commit subjects and the body of
63
94
  # `progress/current.md`. Anyone who can push can write either, and both land at
@@ -115,28 +115,54 @@ is_blank() { ! grep -q '[^[:space:]]' "$1" 2>/dev/null; }
115
115
  problems=""
116
116
  note() { problems="${problems}${problems:+; }$1"; }
117
117
 
118
- # nullglob, so an absent report class yields zero iterations instead of a
119
- # literal pattern (bash) or a hard "no matches found" abort (zsh, which also
120
- # has no `shopt` under `set -e` that unknown command killed the whole hook
121
- # there, #391). Each shell spells the option its own way.
122
- if [ -n "${ZSH_VERSION:-}" ]; then setopt NULL_GLOB; else shopt -s nullglob; fi
118
+ # Only handoffs touched inside this window are checked (48h in minutes).
119
+ #
120
+ # The hook fires on SubagentStop, so the handoff it exists to police is the one
121
+ # just written. It used to check the WHOLE directory, which nothing prunes, and
122
+ # that broke it three ways on every repo measured (#606):
123
+ #
124
+ # 1. Handoffs written under an older format failed forever — 44 files in one
125
+ # repo, 19 in another. The `clean` verdict this script calls "the whole
126
+ # value of recording this hook" was unreachable in all four.
127
+ # 2. The stamp compares the full problem string, so a genuinely broken new
128
+ # handoff injected N+1 paths (44 of them from August in one repo) and
129
+ # fixing it re-injected the N old ones. The real warning arrived buried.
130
+ # 3. Two greps per file, unbounded: the median run tracked the file count
131
+ # almost linearly — 46 files/165ms, 57/197ms, 80/333ms, 138/419ms — making
132
+ # this the most expensive hook of the six, the other five sitting at
133
+ # 16-59ms.
134
+ #
135
+ # 48h and not less because a session can be long (10h41m measured) and a handoff
136
+ # written early in one must still be checked when it ends. Everything older is
137
+ # closed work: PRs in the repo this was measured on merge in under 10 hours,
138
+ # p90 8.5h.
139
+ HANDOFF_WINDOW_MIN=2880
140
+
141
+ # `find` replaces the globs rather than filtering inside them: it does the
142
+ # selection in ONE fork per directory instead of two greps per stale file, which
143
+ # is the cost above. Read through `while read` (not `$(...)` word-splitting) so
144
+ # a path with spaces survives, and via process substitution so `note` writes to
145
+ # the real `problems` and not to a subshell's copy.
146
+ #
123
147
  # Reports are named by their PATH, not their basename: with more than one
124
148
  # progress dir in play, `impl_x.md` alone wouldn't say which one to open.
125
149
  for dir in "${dirs[@]}"; do
126
- for f in "$dir"/impl_*.md; do
150
+ while IFS= read -r f; do
151
+ [ -n "$f" ] || continue
127
152
  if is_blank "$f"; then
128
153
  note "$f vacío"
129
154
  elif ! grep -qiE '^\*{0,2}status:?\*{0,2}' "$f" 2>/dev/null; then
130
155
  note "$f sin línea 'Status:'"
131
156
  fi
132
- done
133
- for f in "$dir"/review_*.md; do
157
+ done < <(find "$dir" -maxdepth 1 -name 'impl_*.md' -mmin -"$HANDOFF_WINDOW_MIN" 2>/dev/null)
158
+ while IFS= read -r f; do
159
+ [ -n "$f" ] || continue
134
160
  if is_blank "$f"; then
135
161
  note "$f vacío"
136
162
  elif ! grep -qE 'APPROVED|CHANGES_REQUESTED' "$f" 2>/dev/null; then
137
163
  note "$f sin veredicto (APPROVED/CHANGES_REQUESTED)"
138
164
  fi
139
- done
165
+ done < <(find "$dir" -maxdepth 1 -name 'review_*.md' -mmin -"$HANDOFF_WINDOW_MIN" 2>/dev/null)
140
166
  done
141
167
 
142
168
  # `clean` vs `dirty` is the whole value of recording this hook: it is the only
@@ -4,3 +4,5 @@ On Claude, a `SessionStart` hook injects the live context — branch, recent com
4
4
 
5
5
  1. **Healthy config**: run `navori doctor` if `navori.config.json` / `.claude/` look inconsistent, or to confirm the declared quality gates can actually run.
6
6
  2. **Scoped task**: one **user** task at a time; decompose and parallelize per your orchestrator role.
7
+
8
+ **Audit mode on request**: if the user asks for it in any wording ("audit mode", "con auditoría", "navori audit"), run `navori audit --start <this session's id>` as the FIRST action of that turn — before loading skills or starting any pipeline — and confirm the log path it prints. The phrase never activates anything by itself (by design); the command is the only switch. If the injected context already says audit-mode is ACTIVE (armed via `navori audit --arm` — which also applies to a RUNNING session on its next message), it is running — do not start it again, and never tell the user to close and reopen the session for this.
@@ -7,5 +7,10 @@ Before closing the session:
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
9
  5. **Conventional commit**: `feat|fix|chore|docs(scope): message`, atomic, in the language defined by the config's `commits`.
10
+ 6. **Park on base**: once the cycle's work is committed and its branch pushed (PR opened when the flow calls for one), leave the repo standing on the base branch, synced: `git switch {{branchBase}}` then `git pull --ff-only`. The point is where the NEXT session starts from — a repo parked on last week's feature branch breeds branches cut from stale bases. Rules that make it safe:
11
+ - **Never delete the feature branch.** This is position hygiene, not history hygiene; the branch stays for its pending merge and for `babysit-prs`.
12
+ - Only with a **clean working tree** and the cycle's commits pushed. Anything unpushed or uncommitted → do NOT switch; say what was left and leave parking to the user.
13
+ - `--ff-only`, always: the base must never receive a surprise merge from a parking step. If it doesn't fast-forward, report it instead of resolving it here.
14
+ - If another session may be alive on this same working tree (a second terminal on this repo), switching yanks the branch out from under it — when in doubt, skip and say so.
10
15
 
11
16
  **R1 lean close** — the three conditions are verifiable, so this is not a judgment call: the session ran the **R1** route, it covered **one** user task, and its diff touches no critical area (`{{project.criticalAreas}}`). All three hold → skip step 2 when nothing was committed, and whatever ceremony another block exempts under this same name. It never exempts the quality gate, nor the `history.md` entry whenever there WAS a commit: a change that shipped leaves a trace, however trivial.
@@ -35,6 +35,7 @@
35
35
  "Bash(git commit:*)",
36
36
  "Bash(git restore --staged:*)",
37
37
  "Bash(git switch:*)",
38
+ "Bash(git pull --ff-only*)",
38
39
  "Bash(git checkout -b:*)",
39
40
  "Bash(git push -u origin HEAD)",
40
41
  "Read",
@@ -20,8 +20,10 @@ Confirm every pointer with a cheap search. If the code contradicts memory, corre
20
20
 
21
21
  Use it when you know a literal token: name, import, config key, error string.
22
22
 
23
+ Native `Grep` first — it IS ripgrep, pre-approved, ~0.08s vs ~0.20s (p75 1.83s) by shell, which in auto mode also pays a classifier round-trip. Shell `rg` is the fallback (git history, context flags), not the default.
24
+
23
25
  1. Start narrow: file, directory or type obtained in Rung 0.
24
- 2. Ask first for files (`rg -l`) or `file:line` with at most two lines of context.
26
+ 2. Ask first for files (`Grep` files mode; `rg -l` via shell) or `file:line` with at most two lines of context.
25
27
  3. Dedup before reading.
26
28
  4. Open only the span that confirms the hit.
27
29