session-orchestrator 3.19.0 → 3.21.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 (158) 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/030-wave-execution.mdc +10 -8
  5. package/CHANGELOG.md +494 -0
  6. package/README.md +16 -11
  7. package/agents/analyst.md +1 -1
  8. package/agents/architect-reviewer.md +1 -1
  9. package/agents/code-implementer.md +4 -2
  10. package/agents/db-specialist.md +1 -1
  11. package/agents/dialectic-deriver.md +1 -1
  12. package/agents/docs-writer.md +1 -1
  13. package/agents/memory-proposal-collector.md +1 -1
  14. package/agents/qa-strategist.md +1 -1
  15. package/agents/security-reviewer.md +1 -1
  16. package/agents/session-reviewer.md +42 -1
  17. package/agents/skill-applied-judge.md +1 -1
  18. package/agents/test-writer.md +1 -1
  19. package/agents/ui-developer.md +1 -1
  20. package/agents/ux-evaluator.md +1 -1
  21. package/commands/release.md +60 -0
  22. package/commands/session.md +6 -2
  23. package/docs/USER-GUIDE.md +1 -1
  24. package/docs/instruction-delivery.md +350 -0
  25. package/docs/migration-v3.md +9 -6
  26. package/docs/persona-panel.md +3 -1
  27. package/docs/scope-collision-guard.md +167 -0
  28. package/docs/session-config-reference.md +1 -41
  29. package/docs/session-config-template.md +0 -23
  30. package/hooks/_lib/guard-source-loader.mjs +304 -91
  31. package/hooks/enforce-commands.mjs +216 -17
  32. package/hooks/enforce-scope.mjs +236 -12
  33. package/hooks/hooks-codex.json +1 -1
  34. package/hooks/hooks.json +11 -1
  35. package/hooks/on-session-end.mjs +52 -5
  36. package/hooks/on-session-start.mjs +7 -4
  37. package/hooks/on-stop.mjs +127 -12
  38. package/hooks/post-bash-write-verify.mjs +8 -32
  39. package/hooks/pre-bash-destructive-guard.mjs +146 -59
  40. package/hooks/pre-bash-sessions-ledger-guard.mjs +493 -66
  41. package/hooks/pre-task-scope-disjoint.mjs +1042 -0
  42. package/package.json +2 -2
  43. package/pi/prompts/release.md +12 -0
  44. package/scripts/autopilot.mjs +3 -1
  45. package/scripts/backfill-learnings-from-vault.mjs +967 -0
  46. package/scripts/emit-session.mjs +45 -40
  47. package/scripts/export-hw-learnings.mjs +61 -2
  48. package/scripts/lib/autopilot/worktree-pipeline.mjs +5 -5
  49. package/scripts/lib/backlog-scan.mjs +106 -15
  50. package/scripts/lib/build-live-signals.mjs +7 -3
  51. package/scripts/lib/ci-status-banner.mjs +207 -23
  52. package/scripts/lib/command-blocker.mjs +322 -62
  53. package/scripts/lib/git-config-drift.mjs +471 -0
  54. package/scripts/lib/hardening.mjs +9 -9
  55. package/scripts/lib/harness-audit/categories/category6.mjs +65 -12
  56. package/scripts/lib/io.mjs +193 -7
  57. package/scripts/lib/learnings/affinity.mjs +434 -0
  58. package/scripts/lib/learnings/candidates.mjs +736 -0
  59. package/scripts/lib/learnings/expiry-sweep.mjs +408 -53
  60. package/scripts/lib/learnings/judgment.mjs +782 -0
  61. package/scripts/lib/learnings/kebab.mjs +128 -0
  62. package/scripts/lib/learnings/select.mjs +704 -0
  63. package/scripts/lib/memory-cleanup-stamp.mjs +132 -8
  64. package/scripts/lib/mirror-issues-banner.mjs +266 -0
  65. package/scripts/lib/named-vault-resolver.mjs +105 -16
  66. package/scripts/lib/peer-cards/schema.mjs +6 -2
  67. package/scripts/lib/reconcile/emitter.mjs +107 -22
  68. package/scripts/lib/reconcile/engine.mjs +9 -15
  69. package/scripts/lib/reconcile/renderer.mjs +141 -25
  70. package/scripts/lib/reconcile/sanitize.mjs +518 -0
  71. package/scripts/lib/reconcile/writer.mjs +134 -1
  72. package/scripts/lib/redact-spans.mjs +89 -0
  73. package/scripts/lib/scope-baseline.mjs +77 -17
  74. package/scripts/lib/scope-gate.mjs +852 -72
  75. package/scripts/lib/secret-masker.mjs +262 -0
  76. package/scripts/lib/session-close-backfill.mjs +2 -2
  77. package/scripts/lib/session-lock.mjs +34 -10
  78. package/scripts/lib/session-record-repair.mjs +551 -0
  79. package/scripts/lib/session-registry.mjs +9 -1
  80. package/scripts/lib/session-schema/serializer.mjs +54 -0
  81. package/scripts/lib/session-schema.mjs +1 -0
  82. package/scripts/lib/session-token-rollup.mjs +68 -6
  83. package/scripts/lib/soul-resolve.mjs +12 -0
  84. package/scripts/lib/state-md/mission-status.mjs +21 -12
  85. package/scripts/lib/tmux-layout/telemetry.mjs +43 -10
  86. package/scripts/lib/tmux-layout/vcs-detector.mjs +108 -4
  87. package/scripts/lib/validate/check-agents.mjs +77 -5
  88. package/scripts/lib/validate/check-banner-parity.mjs +376 -0
  89. package/scripts/lib/validate/check-commands.mjs +2 -20
  90. package/scripts/lib/validate/check-doc-cli-commands.mjs +514 -0
  91. package/scripts/lib/validate/check-guard-requires-parity.mjs +1148 -0
  92. package/scripts/lib/validate/check-hooks-symmetry.mjs +18 -0
  93. package/scripts/lib/validate/check-learning-provenance.mjs +511 -0
  94. package/scripts/lib/validate/check-owner-leakage.mjs +188 -20
  95. package/scripts/lib/validate/check-rules.mjs +31 -5
  96. package/scripts/lib/validate/check-skills.mjs +191 -0
  97. package/scripts/lib/validate/check-test-git-config-target.mjs +665 -0
  98. package/scripts/lib/validate/check-unicode-safety.mjs +22 -2
  99. package/scripts/lib/validate/check-untracked-test-deps.mjs +925 -0
  100. package/scripts/lib/validate/check-unwired-features.mjs +757 -0
  101. package/scripts/lib/validate/check-vcs-repo-flag.mjs +965 -0
  102. package/scripts/lib/validate/frontmatter-block.mjs +61 -0
  103. package/scripts/lib/validate/tier-inference.mjs +46 -8
  104. package/scripts/lib/vault-mirror/namespace.mjs +146 -1
  105. package/scripts/lib/vault-mirror/process.mjs +264 -31
  106. package/scripts/lib/vault-mirror/render-sessions.mjs +115 -4
  107. package/scripts/lib/vault-status/board-writer.mjs +300 -56
  108. package/scripts/lib/vault-status/narrative-mirror.mjs +119 -5
  109. package/scripts/lib/vcs-repo-spec.mjs +500 -19
  110. package/scripts/print-applicable-rules.mjs +170 -7
  111. package/scripts/print-learnings-index.mjs +501 -0
  112. package/scripts/release.mjs +616 -61
  113. package/scripts/repair-invalid-sessions.mjs +209 -0
  114. package/scripts/site-numbers.mjs +1049 -0
  115. package/scripts/sweep-expired-learnings.mjs +192 -32
  116. package/scripts/validate-plugin.mjs +82 -0
  117. package/scripts/validate-wave-scope.mjs +281 -12
  118. package/scripts/vault-mirror.mjs +26 -1
  119. package/skills/_shared/monitor-patterns.md +24 -4
  120. package/skills/_shared/state-ownership.md +17 -0
  121. package/skills/brainstorm/soul.md +47 -1
  122. package/skills/claude-md-drift-check/SKILL.md +9 -1
  123. package/skills/debug/SKILL.md +4 -1
  124. package/skills/discovery/issue-templates.md +4 -4
  125. package/skills/discovery/probes-code.md +2 -2
  126. package/skills/discovery/probes-feature.md +6 -6
  127. package/skills/discovery/probes-infra.md +2 -2
  128. package/skills/discovery/probes-session.md +5 -5
  129. package/skills/dispatcher/SKILL.md +10 -1
  130. package/skills/evolve/SKILL.md +116 -18
  131. package/skills/frontmatter-guard/SKILL.md +9 -1
  132. package/skills/gitlab-ops/SKILL.md +54 -39
  133. package/skills/gitlab-portfolio/SKILL.md +10 -1
  134. package/skills/grill/soul.md +44 -1
  135. package/skills/memory-cleanup/SKILL.md +18 -5
  136. package/skills/npm-publish/SKILL.md +22 -50
  137. package/skills/persona-panel/SKILL.md +3 -1
  138. package/skills/plan/mode-new.md +23 -5
  139. package/skills/plan/soul.md +46 -3
  140. package/skills/repo-audit/SKILL.md +10 -1
  141. package/skills/session-end/SKILL.md +45 -26
  142. package/skills/session-end/metrics-collection.md +1 -1
  143. package/skills/session-end/phase-3-6-tail.md +30 -1
  144. package/skills/session-end/plan-verification.md +1 -5
  145. package/skills/session-end/session-metrics-write.md +6 -10
  146. package/skills/session-plan/SKILL.md +2 -2
  147. package/skills/session-plan/wave-template.md +1 -1
  148. package/skills/session-start/SKILL.md +15 -1
  149. package/skills/session-start/soul.md +41 -1
  150. package/skills/spinout/SKILL.md +5 -1
  151. package/skills/sunset-review/SKILL.md +11 -1
  152. package/skills/tmux-layout/SKILL.md +7 -2
  153. package/skills/vault-mirror/SKILL.md +10 -1
  154. package/skills/vault-sync/SKILL.md +10 -1
  155. package/skills/vault-sync/validator.mjs +55 -6
  156. package/skills/wave-executor/SKILL.md +1 -5
  157. package/skills/wave-executor/wave-loop.md +77 -82
  158. package/scripts/lib/mission-status-schema.mjs +0 -114
@@ -0,0 +1,89 @@
1
+ /**
2
+ * redact-spans.mjs — order-independent, overlap-safe span redaction primitive.
3
+ *
4
+ * Extracted verbatim from `scripts/lib/validate/check-owner-leakage.mjs` (issue
5
+ * #974) so that every redaction sink in the repo shares ONE implementation of the
6
+ * overlap-safe span merge rather than each re-deriving it (and each re-deriving
7
+ * the prefix/suffix bug described below). Pure and synchronous — no I/O, no
8
+ * module state, no dependencies — because hot-path consumers import it.
9
+ *
10
+ * Behaviour is unchanged from the original: this module is a move, not a rewrite.
11
+ *
12
+ * DELIBERATE SECOND COPY — `check-owner-leakage.mjs` still carries its OWN inline
13
+ * `redactSpans()` and must keep it. That scanner is a documented standalone
14
+ * single-file vendoring target (`.claude/rules/security.md` § "Owner-Privacy
15
+ * Pre-Commit Hook"): consumer repos copy that ONE file, so a static import of this
16
+ * module throws ERR_MODULE_NOT_FOUND in every vendored copy and blocks all commits
17
+ * (proven by the three cases in `tests/husky/pre-commit-owner-leakage.test.mjs`).
18
+ * Unlike CP11's confidential-names helpers, a redaction sink cannot degrade to
19
+ * inert — an absent redaction prints confidential names into a PUBLIC CI log.
20
+ * So: THIS module serves in-tree consumers, the inline copy serves the vendored
21
+ * path, and `tests/lib/redact-spans.test.mjs` pins the two byte-for-byte against
22
+ * each other. Edit one → that drift guard goes red until both agree again.
23
+ */
24
+
25
+ /**
26
+ * Redact every confidential-name span from `line`, ORDER-INDEPENDENTLY (Fix 1 + Fix 2).
27
+ *
28
+ * This is the single redaction sink for the confidential-names privacy invariant.
29
+ * In `check-owner-leakage.mjs` it is applied at the print choke-point over EVERY
30
+ * violation's lineContent — not only CP11 hits — because that scanner runs in a
31
+ * PUBLIC GitHub-Actions mirror: a confidential customer/repo name that co-occurs
32
+ * with a CP1–CP10 hit on the same line (e.g. a name beside an RFC1918 IP that
33
+ * fails CP8) would otherwise be echoed verbatim to the public CI log (Fix 1).
34
+ *
35
+ * ORDER-INDEPENDENCE (Fix 2): a naïve chain of `.replace()` calls is order-dependent
36
+ * — when one configured name is a PREFIX of another (`['acme','acme-corp-secret']`),
37
+ * redacting the shorter first destroys the longer's match and leaks a suffix residue
38
+ * (`[REDACTED]-corp-secret`). Instead we compute ALL match spans against the ORIGINAL
39
+ * (unmutated) string across every pattern, merge overlapping/adjacent intervals, and
40
+ * splice `[REDACTED]` per merged interval. No pattern ever sees a string another
41
+ * pattern already rewrote, so prefix/suffix overlap cannot leak regardless of list
42
+ * order.
43
+ *
44
+ * @param {string} line — the raw (already-trimmed) violation lineContent.
45
+ * @param {RegExp[]} patterns — confidential-name regexes (word-boundary, case-insensitive).
46
+ * @returns {string} the line with every configured name span replaced by [REDACTED].
47
+ */
48
+ export function redactSpans(line, patterns) {
49
+ if (!Array.isArray(patterns) || patterns.length === 0) return line;
50
+
51
+ // 1. Collect [start, end) spans of every match of every pattern against the
52
+ // ORIGINAL line (never a partially-mutated one). Global clone so exec() walks
53
+ // all matches; zero-width guard prevents an infinite loop on a degenerate regex.
54
+ const spans = [];
55
+ for (const re of patterns) {
56
+ const g = new RegExp(re.source, re.flags.includes('g') ? re.flags : re.flags + 'g');
57
+ let m;
58
+ while ((m = g.exec(line)) !== null) {
59
+ if (m[0].length === 0) {
60
+ g.lastIndex += 1;
61
+ continue;
62
+ }
63
+ spans.push([m.index, m.index + m[0].length]);
64
+ }
65
+ }
66
+ if (spans.length === 0) return line;
67
+
68
+ // 2. Merge overlapping / adjacent intervals (sorted by start).
69
+ spans.sort((a, b) => a[0] - b[0]);
70
+ const merged = [];
71
+ for (const [s, e] of spans) {
72
+ const last = merged[merged.length - 1];
73
+ if (last && s <= last[1]) {
74
+ last[1] = Math.max(last[1], e);
75
+ } else {
76
+ merged.push([s, e]);
77
+ }
78
+ }
79
+
80
+ // 3. Splice [REDACTED] per merged interval, left-to-right over the ORIGINAL line.
81
+ let out = '';
82
+ let cursor = 0;
83
+ for (const [s, e] of merged) {
84
+ out += line.slice(cursor, s) + '[REDACTED]';
85
+ cursor = e;
86
+ }
87
+ out += line.slice(cursor);
88
+ return out;
89
+ }
@@ -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