session-orchestrator 3.20.0 → 3.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.cursor/rules/000-session-orchestrator.mdc +3 -2
  5. package/.cursor/rules/030-wave-execution.mdc +10 -8
  6. package/.cursor/rules/040-discovery.mdc +6 -6
  7. package/.cursor/rules/050-plan.mdc +8 -8
  8. package/CHANGELOG.md +515 -0
  9. package/README.md +16 -11
  10. package/agents/analyst.md +1 -1
  11. package/agents/architect-reviewer.md +1 -1
  12. package/agents/code-implementer.md +4 -2
  13. package/agents/db-specialist.md +1 -1
  14. package/agents/dialectic-deriver.md +1 -1
  15. package/agents/docs-writer.md +1 -1
  16. package/agents/memory-proposal-collector.md +7 -5
  17. package/agents/qa-strategist.md +1 -1
  18. package/agents/security-reviewer.md +1 -1
  19. package/agents/session-reviewer.md +42 -1
  20. package/agents/skill-applied-judge.md +1 -1
  21. package/agents/test-writer.md +1 -1
  22. package/agents/ui-developer.md +1 -1
  23. package/agents/ux-evaluator.md +1 -1
  24. package/commands/eli5.md +33 -0
  25. package/commands/release.md +62 -0
  26. package/commands/test.md +2 -2
  27. package/docs/components.md +6 -5
  28. package/docs/migration-v3.md +9 -6
  29. package/docs/persona-panel.md +3 -1
  30. package/docs/scope-collision-guard.md +167 -0
  31. package/docs/session-config-reference.md +31 -8
  32. package/hooks/_lib/lock-bootstrap.mjs +19 -13
  33. package/hooks/enforce-scope.mjs +103 -3
  34. package/hooks/hooks-codex.json +1 -1
  35. package/hooks/hooks.json +21 -1
  36. package/hooks/on-session-end.mjs +76 -97
  37. package/hooks/on-session-start.mjs +195 -104
  38. package/hooks/on-stop.mjs +127 -12
  39. package/hooks/post-bash-write-verify.mjs +8 -32
  40. package/hooks/pre-auq-clarity.mjs +787 -0
  41. package/hooks/pre-bash-issue-budget.mjs +17 -18
  42. package/hooks/pre-task-scope-disjoint.mjs +1042 -0
  43. package/package.json +3 -1
  44. package/pi/prompts/eli5.md +12 -0
  45. package/pi/prompts/release.md +12 -0
  46. package/scripts/auq-audit.mjs +825 -0
  47. package/scripts/autopilot.mjs +10 -9
  48. package/scripts/emit-session.mjs +42 -0
  49. package/scripts/export-hw-learnings.mjs +61 -2
  50. package/scripts/lib/auq/clarity.mjs +1314 -0
  51. package/scripts/lib/auq/parse.mjs +1006 -0
  52. package/scripts/lib/auq/schema.mjs +1457 -0
  53. package/scripts/lib/autopilot/worktree-pipeline.mjs +5 -5
  54. package/scripts/lib/backlog-scan.mjs +106 -15
  55. package/scripts/lib/build-live-signals.mjs +7 -3
  56. package/scripts/lib/ci-status-banner.mjs +267 -77
  57. package/scripts/lib/config/dispatcher-autonomy-capture.mjs +32 -9
  58. package/scripts/lib/config/vault-integration.mjs +12 -1
  59. package/scripts/lib/dispatcher/rank.mjs +4 -7
  60. package/scripts/lib/gates/gate-full.mjs +3 -3
  61. package/scripts/lib/gates/gate-helpers.mjs +17 -6
  62. package/scripts/lib/git-config-drift.mjs +471 -0
  63. package/scripts/lib/harness-audit/categories/category6.mjs +65 -12
  64. package/scripts/lib/io.mjs +432 -7
  65. package/scripts/lib/issue-budget.mjs +63 -9
  66. package/scripts/lib/learnings/select.mjs +157 -3
  67. package/scripts/lib/memory-cleanup-stamp.mjs +132 -8
  68. package/scripts/lib/mirror-issues-banner.mjs +266 -0
  69. package/scripts/lib/named-vault-resolver.mjs +105 -16
  70. package/scripts/lib/owner-interview.mjs +78 -32
  71. package/scripts/lib/peer-cards/schema.mjs +6 -2
  72. package/scripts/lib/peer-discovery.mjs +73 -22
  73. package/scripts/lib/project-hygiene.mjs +64 -4
  74. package/scripts/lib/reconcile/renderer.mjs +17 -4
  75. package/scripts/lib/reconcile/writer.mjs +69 -30
  76. package/scripts/lib/redact-spans.mjs +89 -0
  77. package/scripts/lib/resource-probe/evaluate.mjs +330 -149
  78. package/scripts/lib/resource-probe/probe-platform.mjs +35 -0
  79. package/scripts/lib/resource-probe.mjs +18 -2
  80. package/scripts/lib/scope-baseline.mjs +77 -17
  81. package/scripts/lib/scope-gate.mjs +658 -0
  82. package/scripts/lib/secret-masker.mjs +262 -0
  83. package/scripts/lib/session-lock.mjs +34 -10
  84. package/scripts/lib/session-registry.mjs +9 -1
  85. package/scripts/lib/spiral-carryover.mjs +23 -2
  86. package/scripts/lib/state-md/mission-status.mjs +164 -58
  87. package/scripts/lib/tmux-layout/vcs-detector.mjs +108 -4
  88. package/scripts/lib/validate/check-agents.mjs +77 -5
  89. package/scripts/lib/validate/check-auq-clarity.mjs +274 -0
  90. package/scripts/lib/validate/check-commands.mjs +2 -20
  91. package/scripts/lib/validate/check-doc-cli-commands.mjs +514 -0
  92. package/scripts/lib/validate/check-hooks-symmetry.mjs +48 -0
  93. package/scripts/lib/validate/check-owner-leakage.mjs +185 -17
  94. package/scripts/lib/validate/check-rules.mjs +153 -9
  95. package/scripts/lib/validate/check-skills.mjs +191 -0
  96. package/scripts/lib/validate/check-test-git-config-target.mjs +665 -0
  97. package/scripts/lib/validate/check-unicode-safety.mjs +22 -2
  98. package/scripts/lib/validate/check-untracked-test-deps.mjs +925 -0
  99. package/scripts/lib/validate/check-unwired-features.mjs +219 -11
  100. package/scripts/lib/validate/check-vcs-repo-flag.mjs +965 -0
  101. package/scripts/lib/validate/frontmatter-block.mjs +61 -0
  102. package/scripts/lib/validate/tier-inference.mjs +46 -8
  103. package/scripts/lib/vault-backfill/glab.mjs +91 -58
  104. package/scripts/lib/vault-backfill/manifest.mjs +28 -8
  105. package/scripts/lib/vault-mirror/namespace.mjs +146 -1
  106. package/scripts/lib/vault-mirror/process.mjs +264 -31
  107. package/scripts/lib/vault-mirror/render-sessions.mjs +115 -4
  108. package/scripts/lib/vault-status/board-writer.mjs +300 -56
  109. package/scripts/lib/vault-status/narrative-mirror.mjs +119 -5
  110. package/scripts/lib/vcs-repo-spec.mjs +680 -30
  111. package/scripts/lib/wave-resource-gate.mjs +67 -73
  112. package/scripts/materialize-wave-scope.mjs +281 -0
  113. package/scripts/print-learnings-index.mjs +30 -3
  114. package/scripts/release.mjs +983 -107
  115. package/scripts/run-quality-gate.mjs +14 -0
  116. package/scripts/site-numbers.mjs +1049 -0
  117. package/scripts/validate-plugin.mjs +64 -0
  118. package/scripts/validate-wave-scope.mjs +286 -12
  119. package/scripts/vault-backfill.mjs +32 -5
  120. package/scripts/vault-mirror.mjs +26 -1
  121. package/skills/_shared/monitor-patterns.md +24 -4
  122. package/skills/_shared/parallel-aware-auq.md +30 -24
  123. package/skills/_shared/parallel-aware-preamble.md +31 -2
  124. package/skills/_shared/state-ownership.md +49 -6
  125. package/skills/bootstrap/SKILL.md +2 -1
  126. package/skills/brainstorm/SKILL.md +18 -18
  127. package/skills/brainstorm/soul.md +12 -0
  128. package/skills/claude-md-drift-check/SKILL.md +9 -1
  129. package/skills/debug/SKILL.md +4 -1
  130. package/skills/discovery/SKILL.md +28 -24
  131. package/skills/discovery/issue-templates.md +4 -4
  132. package/skills/discovery/probes-code.md +2 -2
  133. package/skills/discovery/probes-feature.md +6 -6
  134. package/skills/discovery/probes-infra.md +2 -2
  135. package/skills/discovery/probes-session.md +5 -5
  136. package/skills/dispatcher/SKILL.md +10 -1
  137. package/skills/eli5/SKILL.md +43 -0
  138. package/skills/evolve/SKILL.md +8 -9
  139. package/skills/frontmatter-guard/SKILL.md +9 -1
  140. package/skills/gitlab-ops/SKILL.md +73 -59
  141. package/skills/gitlab-portfolio/SKILL.md +10 -1
  142. package/skills/grill/SKILL.md +6 -6
  143. package/skills/grill/soul.md +16 -0
  144. package/skills/memory-cleanup/SKILL.md +20 -7
  145. package/skills/npm-publish/SKILL.md +23 -51
  146. package/skills/peekaboo-driver/SKILL.md +3 -3
  147. package/skills/persona-panel/SKILL.md +3 -1
  148. package/skills/plan/SKILL.md +18 -16
  149. package/skills/plan/mode-feature.md +1 -1
  150. package/skills/plan/mode-new.md +42 -12
  151. package/skills/plan/soul.md +12 -0
  152. package/skills/reconcile/SKILL.md +3 -3
  153. package/skills/repo-audit/SKILL.md +10 -1
  154. package/skills/session-end/SKILL.md +97 -22
  155. package/skills/session-end/metrics-collection.md +1 -1
  156. package/skills/session-end/phase-3-6-tail.md +37 -2
  157. package/skills/session-end/session-metrics-write.md +4 -10
  158. package/skills/session-plan/SKILL.md +2 -2
  159. package/skills/session-plan/wave-template.md +1 -1
  160. package/skills/session-start/SKILL.md +82 -36
  161. package/skills/session-start/phase-2-5-docs-planning.md +8 -8
  162. package/skills/session-start/phase-4-5-resource-health.md +82 -19
  163. package/skills/session-start/soul.md +110 -0
  164. package/skills/spinout/SKILL.md +5 -1
  165. package/skills/sunset-review/SKILL.md +11 -1
  166. package/skills/test-runner/SKILL.md +2 -2
  167. package/skills/tmux-layout/SKILL.md +7 -2
  168. package/skills/using-orchestrator/SKILL.md +1 -1
  169. package/skills/vault-mirror/SKILL.md +10 -1
  170. package/skills/vault-sync/SKILL.md +10 -1
  171. package/skills/vault-sync/validator.mjs +55 -6
  172. package/skills/wave-executor/wave-loop.md +64 -12
  173. package/skills/write-executable-plan/SKILL.md +6 -6
  174. package/scripts/lib/mission-status-schema.mjs +0 -114
  175. package/scripts/tests/fixtures/fetch-baseline/sample-rule.md +0 -8
  176. package/skills/vault-sync/tests/fixtures/archive-test-vault/90-archive/bad-archived.md +0 -8
  177. package/skills/vault-sync/tests/fixtures/archive-test-vault/_meta/.gitkeep +0 -0
  178. package/skills/vault-sync/tests/fixtures/archive-test-vault/live-note.md +0 -8
  179. package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/_meta/.gitkeep +0 -0
  180. package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/bad-type.md +0 -8
  181. package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/good-note.md +0 -8
  182. package/skills/vault-sync/tests/fixtures/clean-vault/.obsidian/config.md +0 -8
  183. package/skills/vault-sync/tests/fixtures/clean-vault/01-projects/foo/projects-baseline.md +0 -10
  184. package/skills/vault-sync/tests/fixtures/clean-vault/03-daily/daily-2026-04-13.md +0 -8
  185. package/skills/vault-sync/tests/fixtures/clean-vault/README.md +0 -3
  186. package/skills/vault-sync/tests/fixtures/clean-vault/hello-world.md +0 -11
  187. package/skills/vault-sync/tests/fixtures/dangling-link-vault/_meta/.gitkeep +0 -0
  188. package/skills/vault-sync/tests/fixtures/dangling-link-vault/has-dangling.md +0 -9
  189. package/skills/vault-sync/tests/fixtures/dangling-link-vault/real-target.md +0 -8
  190. package/skills/vault-sync/tests/fixtures/empty-vault/_meta/.gitkeep +0 -0
  191. package/skills/vault-sync/tests/fixtures/missing-field-vault/_meta/.gitkeep +0 -0
  192. package/skills/vault-sync/tests/fixtures/missing-field-vault/missing-id.md +0 -7
  193. package/skills/vault-sync/tests/fixtures/nested-tag-vault/03-daily/daily-2026-04-13.md +0 -9
  194. package/skills/vault-sync/tests/fixtures/nested-tag-vault/_meta/.gitkeep +0 -0
  195. package/skills/vault-sync/tests/fixtures/nested-tag-vault/nested-tags-note.md +0 -11
  196. package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/README.md +0 -3
  197. package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_MOC.md +0 -3
  198. package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_meta/.gitkeep +0 -0
  199. package/skills/vault-sync/tests/fixtures/with-moc-vault/_MOC.md +0 -11
  200. package/skills/vault-sync/tests/fixtures/with-moc-vault/_meta/.gitkeep +0 -0
  201. package/skills/vault-sync/tests/fixtures/with-moc-vault/hello-world.md +0 -11
  202. package/skills/vault-sync/tests/schema-drift.test.mjs +0 -133
@@ -18,6 +18,7 @@
18
18
  * cpu_load_5m: 1.8,
19
19
  * cpu_load_5m_pct: 45 | null, // 5m load-average as pct-of-cores; null on Windows/zero-load (#943)
20
20
  * claude_processes_count: 3 | null,
21
+ * peer_sessions_count: 2 | null, // live peer SESSIONS from the registry, self excluded (#1089)
21
22
  * codex_processes_count: 0 | null,
22
23
  * other_node_processes: 12 | null,
23
24
  * zombie_processes_count: 1 | null,
@@ -40,13 +41,14 @@
40
41
  */
41
42
 
42
43
  import os from 'node:os';
43
- import { ramSnapshot, cpuSnapshot, processCounts, swapUsedMb, memoryPressurePctFree, ramAvailableGb } from './resource-probe/probe-platform.mjs';
44
+ import { ramSnapshot, cpuSnapshot, processCounts, swapUsedMb, memoryPressurePctFree, ramAvailableGb, peerSessionsCount } from './resource-probe/probe-platform.mjs';
44
45
 
45
46
  // ---------------------------------------------------------------------------
46
47
  // Re-exports — preserve public API for all existing callers
47
48
  // ---------------------------------------------------------------------------
48
49
 
49
- export { evaluate } from './resource-probe/evaluate.mjs';
50
+ export { evaluate, PROCESSES_PER_SESSION, PRESSURE_HARD_PCT, PRESSURE_SOFT_PCT, PRESSURE_HEALTHY_PCT, DEFAULT_RESOURCE_THRESHOLDS } from './resource-probe/evaluate.mjs';
51
+ export { peerSessionsCount } from './resource-probe/probe-platform.mjs';
50
52
  export { parseEtimeToMinutes, countZombieProcesses, countProcessMatches, parseSwapUsageOutput, parseMemoryPressureOutput, parseVmStatAvailableGb } from './resource-probe/parsers.mjs';
51
53
 
52
54
  // ---------------------------------------------------------------------------
@@ -60,6 +62,9 @@ export { parseEtimeToMinutes, countZombieProcesses, countProcessMatches, parseSw
60
62
  * @param {boolean} [opts.skipExtendedSignals] — skip swap + memory_pressure calls (faster in tests)
61
63
  * @param {number|null} [opts.zombieThresholdMin] — when non-null, detect zombie Claude/Node
62
64
  * processes older than this many minutes with low CPU. Default null (feature disabled).
65
+ * @param {string|null} [opts.sessionId] — own session id, excluded from `peer_sessions_count`
66
+ * (#1089). Omit and the count simply includes every live registry entry, which
67
+ * over-counts by exactly one — pass it whenever the caller knows its own id.
63
68
  * @returns {Promise<object>}
64
69
  */
65
70
  export async function probe(opts = {}) {
@@ -85,6 +90,13 @@ export async function probe(opts = {}) {
85
90
  ? { claude_processes_count: null, codex_processes_count: null, other_node_processes: null, zombie_processes_count: null }
86
91
  : await processCounts(zombieThresholdMin);
87
92
 
93
+ // #1089: the registry-denominated concurrency signal. Gated on the same
94
+ // skipProcessCounts flag as the `ps` pass — both answer "who else is on this
95
+ // host", and tests that skip one always mean to skip the other.
96
+ const peer_sessions_count = opts.skipProcessCounts
97
+ ? null
98
+ : await peerSessionsCount({ sessionId: opts.sessionId ?? null });
99
+
88
100
  let swap_used_mb = null;
89
101
  let memory_pressure_pct_free = null;
90
102
  let ram_available_gb = null;
@@ -104,6 +116,10 @@ export async function probe(opts = {}) {
104
116
  cpu_load_5m,
105
117
  cpu_load_5m_pct,
106
118
  ...procs,
119
+ // Live peer SESSIONS (registry, self excluded) — the unit
120
+ // `concurrent-sessions-warn` is named for. null = registry unreadable, in
121
+ // which case evaluate() falls back to the rescaled process count (#1089).
122
+ peer_sessions_count,
107
123
  swap_used_mb,
108
124
  memory_pressure_pct_free,
109
125
  // macOS-only numeric available-RAM (free + reclaimable) from vm_stat; null
@@ -124,6 +124,7 @@ import { execFileSync } from 'node:child_process';
124
124
 
125
125
  import { parseStateMd, serializeStateMd, resolveStateMdPath, writeStateMd } from './state-md.mjs';
126
126
  import { pathMatchesPattern } from './scope-gate.mjs';
127
+ import { resolveBaselineRange, isQueryFailure } from './vcs-repo-spec.mjs';
127
128
 
128
129
  // ---------------------------------------------------------------------------
129
130
  // DRIFT_EXCLUDE_PATTERNS — the SINGLE filter source for both the denominator
@@ -472,23 +473,60 @@ export async function writeBaseline({ repoRoot, intent, ownerBoundary, plannedFi
472
473
  *
473
474
  * Skip precedence (first match wins, so `reason` is deterministic):
474
475
  * `no-state-md` → `unreadable-state-md` → `no-baseline` →
475
- * `stale-baseline` → `unresolvable-ref`
476
+ * `stale-baseline` → (`no-baseline-ref` | `unresolvable-ref`)
476
477
  *
477
- * `session-start-ref` handling — MISSING vs UNRESOLVABLE are different:
478
- * - Field absent from frontmatter → falls back to
479
- * `git diff --name-only origin/main...HEAD` (the documented fallback,
480
- * skills/session-end/plan-verification.md:37). This is NOT a skip
481
- * `refUsed` reports the fallback ref actually used.
482
- * - Field present but the diff against it fails (rebase, force-push,
483
- * deleted commit, …) skip with `reason: 'unresolvable-ref'`.
478
+ * `session-start-ref` handling — THREE outcomes, deliberately distinct.
479
+ * `skills/_shared/state-ownership.md:28` declares the field OPTIONAL
480
+ * ("readers MUST tolerate their absence"), so the absent-field branch is a
481
+ * REACHABLE production path, not a defensive corner:
482
+ *
483
+ * - **Field present**, diff against it succeeds measured; `refUsed`
484
+ * reports the bare ref verbatim (NOT a range — pre-existing contract,
485
+ * unchanged).
486
+ * - **Field absent** → the range is RESOLVED via `resolveBaselineRange()`
487
+ * (scripts/lib/vcs-repo-spec.mjs): preferred remote `R` →
488
+ * `refs/remotes/<R>/HEAD` → `refs/remotes/<R>/{main,master}` → local
489
+ * `{main,master}`. NOT a skip — `refUsed` reports the range actually
490
+ * resolved (`gitlab/main...HEAD`, `origin/master...HEAD`, …).
491
+ * This REPLACES the hard-coded `'origin/main...HEAD'` literal the
492
+ * function carried until #1039, which hard-coded BOTH the remote name
493
+ * AND the default branch in one string. In any repo whose remote is not
494
+ * named `origin`, or whose default branch is not `main`, that diff
495
+ * always failed → the tripwire skipped → and
496
+ * `skills/wave-executor/wave-loop.md` renders every `skipped === true`
497
+ * with NO WARN. A guard that neither bit nor reported that it was not
498
+ * biting.
499
+ * - **Field absent AND nothing resolves** (fresh `git init`, no remotes,
500
+ * no `main`/`master` anywhere) → skip with `reason: 'no-baseline-ref'`.
501
+ * This is an ABSENCE — there is genuinely no session base to measure
502
+ * against — and is deliberately NOT folded onto `unresolvable-ref`,
503
+ * which means the QUERY failed (not a git repo, git not on PATH, a
504
+ * present-but-dead `session-start-ref` after a rebase/force-push).
505
+ * The split follows `isQueryFailure()` from the same module, the single
506
+ * predicate that separates "I could not ask" from "I asked; the answer
507
+ * is no". Folding them was the pre-#1039 behaviour and made an operator
508
+ * unable to tell a broken checkout from a fresh repo.
509
+ *
510
+ * Named ceiling (BV-004) on the local-branch tail of the resolution chain:
511
+ * when the chain lands on a LOCAL `main`/`master` and HEAD is already that
512
+ * branch, `main...HEAD` has merge-base === HEAD and the diff is empty, so
513
+ * the ratio reads `0` instead of skipping. That is the honest floor of the
514
+ * available information (no remote-tracking ref exists to say where the
515
+ * session started) and it is harmless here because both a `0` ratio and a
516
+ * skip are silent under `wave-loop.md`'s WARN-on-breach rule. Revisit if
517
+ * this function ever gains a non-warn consumer that treats `skipped:false`
518
+ * as "measured successfully".
484
519
  *
485
520
  * @param {object} args
486
521
  * @param {string|undefined} args.repoRoot
487
522
  * @param {number} [args.threshold] — breach threshold, `>=` counts as
488
523
  * breached (default `2.0`).
489
- * @returns {{ ok: true, skipped: true, reason: 'no-state-md'|'unreadable-state-md'|'no-baseline'|'stale-baseline'|'unresolvable-ref' }
524
+ * @returns {{ ok: true, skipped: true, reason: 'no-state-md'|'unreadable-state-md'|'no-baseline'|'stale-baseline'|'no-baseline-ref'|'unresolvable-ref' }
490
525
  * | { ok: true, skipped: false, filesRatio: number, plannedFiles: number,
491
526
  * actualFiles: number, breached: boolean, threshold: number, refUsed: string }}
527
+ * Additive since #1039: `no-baseline-ref` is a NEW member of the skip-reason
528
+ * union. No existing member was renamed or removed, and the measured-result
529
+ * shape (all eight keys) is byte-identical to the pre-#1039 contract.
492
530
  */
493
531
  export function computeDrift({ repoRoot, threshold = 2.0 } = {}) {
494
532
  const parsedFm = readFrontmatterOrReason(repoRoot);
@@ -514,18 +552,40 @@ export function computeDrift({ repoRoot, threshold = 2.0 } = {}) {
514
552
  ? baseline.sessionStartRef
515
553
  : null;
516
554
 
517
- const diffArgs = rawRef !== null
518
- ? ['diff', '--name-only', `${rawRef}..HEAD`]
519
- : ['diff', '--name-only', 'origin/main...HEAD'];
520
- const refUsed = rawRef ?? 'origin/main...HEAD';
555
+ // Resolve the diff range. The `session-start-ref` branch keeps its exact
556
+ // pre-#1039 shape (two-dot `<ref>..HEAD`, `refUsed` = the bare ref); only
557
+ // the absent-field branch changed, from a hard-coded literal to a real
558
+ // resolution. See the JSDoc above for the three outcomes.
559
+ let diffRange;
560
+ let refUsed;
561
+ if (rawRef !== null) {
562
+ diffRange = `${rawRef}..HEAD`;
563
+ refUsed = rawRef;
564
+ } else {
565
+ const resolved = resolveBaselineRange({ repoRoot: cwd });
566
+ if (!resolved.ok) {
567
+ // `isQueryFailure()` is the frozen predicate that keeps the two causes
568
+ // apart: a broken/absent git ⇒ `unresolvable-ref` (degraded
569
+ // measurement), a repo that simply has no baseline ref ⇒
570
+ // `no-baseline-ref` (a real, benign repo state).
571
+ return {
572
+ ok: true,
573
+ skipped: true,
574
+ reason: isQueryFailure(resolved.reason) ? 'unresolvable-ref' : 'no-baseline-ref',
575
+ };
576
+ }
577
+ diffRange = resolved.range;
578
+ refUsed = resolved.range;
579
+ }
521
580
 
522
581
  let stdout;
523
582
  try {
524
- stdout = execFileSync('git', diffArgs, { cwd, encoding: 'utf8' });
583
+ stdout = execFileSync('git', ['diff', '--name-only', diffRange], { cwd, encoding: 'utf8' });
525
584
  } catch {
526
- // Ref present-but-unresolvable (rebase, force-push, deleted commit) OR
527
- // the fallback diff itself failed (e.g. no origin/main) both land in
528
- // the same skip bucket; neither can produce a trustworthy numerator.
585
+ // The range resolved but the diff against it failed — a present-but-dead
586
+ // `session-start-ref` (rebase, force-push, deleted commit), or a
587
+ // resolved base that vanished between resolution and diff. Neither can
588
+ // produce a trustworthy numerator.
529
589
  return { ok: true, skipped: true, reason: 'unresolvable-ref' };
530
590
  }
531
591