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
@@ -22,15 +22,18 @@ Fires when an `exclusive`-class session (`bootstrap`, `housekeeping`, `memory-cl
22
22
  ### Claude Code (AskUserQuestion)
23
23
 
24
24
  ```js
25
+ // Unpacked once so the question reads as one sentence instead of five key=value pairs.
26
+ const { mode, host, pid, worktreePath } = blockingSession;
27
+
25
28
  AskUserQuestion({
26
29
  questions: [{
27
- question: `An exclusive session is active in this repository (mode=${blockingSession.mode}, started ${ageHours}h ago, host=${blockingSession.host}, pid=${blockingSession.pid}, worktree=${blockingSession.worktreePath}). This blocks all other modes. How should I proceed?`,
28
- header: "Parallel-Exclusive",
30
+ question: `A ${mode} session (process ${pid} on ${host}) started ${ageHours}h ago in ${worktreePath}. What now?`,
31
+ header: "Repo belegt",
29
32
  multiSelect: false,
30
33
  options: [
31
- { label: "Warten (Recommended)", description: "Wait for the exclusive session to finish. The preamble will not retry automatically re-run the command after the other session closes." },
32
- { label: "Andere Session beenden", description: "I will close the other session myself, then re-run this command. The preamble surfaces but does NOT terminate the other session." },
33
- { label: "Abbrechen", description: "Exit cleanly. No STATE.md initialization, no lock acquired." },
34
+ { label: "Warten (Recommended)", description: "Nothing else can start here until that session closes. This command does not retry — run it again afterwards." },
35
+ { label: "Andere Session beenden", description: "You close it yourself, then run this command again. Nothing here stops the other session for you." },
36
+ { label: "Abbrechen", description: "Exit now. Nothing is written: no STATE.md, no lock." },
34
37
  ],
35
38
  }],
36
39
  });
@@ -39,19 +42,17 @@ AskUserQuestion({
39
42
  ### Codex CLI / Cursor IDE / Pi fallback (numbered Markdown list)
40
43
 
41
44
  ```
42
- Parallel-Exclusive conflict an exclusive session is active in this repository.
43
- Mode: <blockingSession.mode>
44
- Started: <ageHours>h ago (host=<host>, pid=<pid>)
45
- Worktree: <blockingSession.worktreePath>
46
- This blocks all other modes.
45
+ A <mode> session (process <pid> on <host>) started <ageHours>h ago in <worktreePath>. What now?
47
46
 
48
- 1. Warten (Recommended) — wait for the exclusive session to finish; re-run after it closes.
49
- 2. Andere Session beenden — I will close the other session myself.
50
- 3. Abbrechen — exit cleanly without initializing STATE.md.
47
+ 1. Warten (Recommended) — nothing else can start here until that session closes; this command does not retry, so run it again afterwards.
48
+ 2. Andere Session beenden — you close it yourself, then run this command again. Nothing here stops the other session for you.
49
+ 3. Abbrechen — exit now. Nothing is written: no STATE.md, no lock.
51
50
 
52
51
  Reply with the number of your choice.
53
52
  ```
54
53
 
54
+ The four slots are `blockingSession.mode`, `blockingSession.host`, `blockingSession.pid` and `blockingSession.worktreePath`; `<ageHours>` is the age of that session in hours.
55
+
55
56
  ### Outcome handling
56
57
 
57
58
  - **Warten** → exit Phase-0 cleanly with stderr note `parallel-aware: waiting on exclusive session_id=<id>`. No retry loop.
@@ -65,34 +66,39 @@ Fires when the caller is `parallel-ok`-class AND another `parallel-ok` session i
65
66
  ### Claude Code (AskUserQuestion)
66
67
 
67
68
  ```js
69
+ // Unpacked once so the question reads as one sentence instead of three key=value pairs.
70
+ const { mode, pid } = parallelPeer;
71
+
68
72
  AskUserQuestion({
69
73
  questions: [{
70
- question: `A compatible parallel session is active in this worktree (mode=${parallelPeer.mode}, started ${ageHours}h ago, pid=${parallelPeer.pid}). You can either: (a) auto-promote to a sibling worktree to run isolated, or (b) run in-place alongside the existing session (file conflicts likely). How should I proceed?`,
71
- header: "Worktree-Promo",
74
+ question: `A ${mode} session (process ${pid}) started ${ageHours}h ago in this same folder. Run separately or alongside?`,
75
+ header: "Wo starten?",
72
76
  multiSelect: false,
73
77
  options: [
74
- { label: "Worktree anlegen + starten (Recommended)", description: "Create a sibling git worktree at ../<repo-name>-<semantic-session-id>/ and start the new session there. Isolates file edits; recommended for parallel deep/feature sessions. Calls enterWorktree() from scripts/lib/autopilot/worktree-pipeline.mjs." },
75
- { label: "Manuell — in-place daneben", description: "Run in the current worktree alongside the existing session. File conflicts possible; PSA-001/002/004 discipline required. A Deviation is logged." },
76
- { label: "Abbrechen", description: "Exit cleanly. No STATE.md initialization." },
78
+ { label: "Worktree anlegen + starten (Recommended)", description: "Creates a second working folder beside this one and starts there isolates your edits, so nothing collides." },
79
+ { label: "Manuell — in-place daneben", description: "Both sessions write in this same folder conflicts are likely and you resolve them yourself. A Deviation is logged." },
80
+ { label: "Abbrechen", description: "Exit now. Nothing is written: no STATE.md, no lock." },
77
81
  ],
78
82
  }],
79
83
  });
80
84
  ```
81
85
 
86
+ The second working folder is a git worktree at `<basePath>/<repo-name>-<sessionId>/`; `enterWorktree()` from `scripts/lib/autopilot/worktree-pipeline.mjs` creates it (see Outcome handling below). Running in-place puts PSA-001/PSA-002/PSA-004 discipline on the operator.
87
+
82
88
  ### Codex CLI / Cursor IDE / Pi fallback (numbered Markdown list)
83
89
 
84
90
  ```
85
- Worktree-Promotion offer a compatible parallel session is active in this worktree.
86
- Peer mode: <parallelPeer.mode>
87
- Started: <ageHours>h ago (pid=<pid>)
91
+ A <mode> session (process <pid>) started <ageHours>h ago in this same folder. Run separately or alongside?
88
92
 
89
- 1. Worktree anlegen + starten (Recommended) — create sibling worktree and start isolated session.
90
- 2. Manuell — in-place daneben — run alongside (file conflicts possible, Deviation logged).
91
- 3. Abbrechen — exit cleanly.
93
+ 1. Worktree anlegen + starten (Recommended) — creates a second working folder beside this one and starts there; isolates your edits, so nothing collides.
94
+ 2. Manuell — in-place daneben — both sessions write in this folder; conflicts are likely and you resolve them. A Deviation is logged.
95
+ 3. Abbrechen — exit now. Nothing is written: no STATE.md, no lock.
92
96
 
93
97
  Reply with the number of your choice.
94
98
  ```
95
99
 
100
+ The two slots are `parallelPeer.mode` and `parallelPeer.pid`; `<ageHours>` is the age of that session in hours.
101
+
96
102
  ### Outcome handling
97
103
 
98
104
  - **Worktree anlegen + starten** → invoke `enterWorktree({ basePath, sessionId, branch, repoRoot })` from `scripts/lib/autopilot/worktree-pipeline.mjs`. The helper creates a sibling worktree at `<basePath>/<repo-name>-<sessionId>/`, runs idempotency + boundary checks, and logs a WARN line to stderr on fresh creation. Once the worktree exists, exit the current preamble flow — the new worktree's own session-start runs from scratch (Phase 1 onwards). On failure (`WorktreeBoundaryError` or `git worktree add` non-zero exit), emit a stderr warning `parallel-aware: enterWorktree failed: <error>; falling back to Manuell` and proceed via the Manuell path.
@@ -29,6 +29,33 @@ If an `exclusive`-class session is also active, the Exclusive-Conflict AUQ takes
29
29
  **Always-OK class** (`discovery`, `evolve`, `plan`, `repo-audit`, `portfolio`):
30
30
  The preamble passes through with zero AUQ regardless of other active sessions. Read-only modes never conflict.
31
31
 
32
+ ## Identity Binding for `findPeers` (#1085)
33
+
34
+ `mySessionId` / `callerSessionId` is a **hint for the caller's original
35
+ surface**, not a license to turn an attribution label into ownership. A native
36
+ raw id self-excludes on the discovered lock/registry surface directly. Given a
37
+ semantic hint, `findPeers` may translate it to a concrete local raw id only when
38
+ both proofs hold: `current-session.json` has the same semantic label **and** its
39
+ raw `session_id` exactly equals the readable local lock's raw `session_id`.
40
+
41
+ On a missing, malformed, or mismatched binding, `findPeers` must map nothing and
42
+ leave the discovered lock visible. The STATE.md surface always receives the
43
+ original hint and therefore compares STATE.md `session` as the attribution label
44
+ it is; it is never rewritten to a raw id. This guarded translation is only
45
+ self-exclusion for discovery, not lock/registry ownership and not a continuity
46
+ bridge across a host rotation that changes both values.
47
+
48
+ **What the binding does not prove.** Both files it reads are repo-global, so the
49
+ check establishes that they are mutually CONSISTENT — not that they describe
50
+ *this* process. Semantic labels are routinely shared between simultaneously live
51
+ sessions, and when a foreign live session wrote both files last under a label
52
+ equal to this hint, its raw id is filtered out and the peer disappears from the
53
+ result. Measured 2026-08-21: with a null hint the foreign peer is returned, with
54
+ the colliding semantic hint `peers` is empty. Treat a quiet `findPeers` result as
55
+ weaker evidence than a git or filesystem signal, and prefer passing the native
56
+ raw id whenever the caller has one. Closing this needs a per-process ownership
57
+ proof; see #1091.
58
+
32
59
  ## Preamble Algorithm
33
60
 
34
61
  Execute these steps in order. Any classification determines outcome.
@@ -152,8 +179,10 @@ findPeers(repoRoot, { mySessionId }) → peer = peers.find((p) => p.source === '
152
179
  ```js
153
180
  import { findPeers } from '../../scripts/lib/peer-discovery.mjs';
154
181
 
155
- // Inside Phase 1b, before writing STATE.md:
156
- const { peers } = await findPeers(repoRoot, { mySessionId: sessionId });
182
+ // Inside Phase 1b, before writing STATE.md. Preserve the original
183
+ // attribution-label hint for the STATE.md surface; findPeers guards any
184
+ // semantic→raw translation for discovered peers internally.
185
+ const { peers } = await findPeers(repoRoot, { mySessionId: callerSessionHint });
157
186
  const peer = peers.find((p) => p.source === 'state-md') ?? null;
158
187
  // Phase 1.2.1 consumes only the 'state-md' subset (STATE.md surface only).
159
188
  if (peer !== null) {
@@ -17,7 +17,7 @@ current-wave: <N>
17
17
  total-waves: <N>
18
18
  # Optional fields (schema-version 1, additive for backward-compat):
19
19
  updated: <ISO 8601 UTC> # last write timestamp, touched by any writer
20
- session: <session-id> # <branch>-<YYYY-MM-DD>-<mode>-<n> (semantic, since #573); legacy UUID-v4 also accepted by parseSessionId
20
+ session: <session-label> # attribution/history label; normally semantic since #573, legacy UUID-v4 remains readable; never a lock/registry ownership key
21
21
  session-start-ref: <sha> # git ref at session start
22
22
  ---
23
23
  ```
@@ -25,7 +25,7 @@ session-start-ref: <sha> # git ref at session start
25
25
  ### Required vs. optional fields
26
26
 
27
27
  - `schema-version`, `session-type`, `branch`, `issues`, `started_at`, `status`, `current-wave`, `total-waves` — **required** in every session-owned STATE.md.
28
- - `updated`, `session`, `session-start-ref` — **optional**. Added by #184. STATE.md files without these fields remain valid and should be treated as `updated: null` / `session: null`. Writers SHOULD populate these fields but readers MUST tolerate their absence. The `session` field's value format is `<branch>-<YYYY-MM-DD>-<mode>-<n>` since #573 (Epic #568 Parallel-Aware Sessions P2.2); pre-#573 files may contain a UUID-v4 — both formats are read via `parseSessionId()` from `scripts/lib/session-id.mjs` per PRD §3 P2 row 3 (backward-compat).
28
+ - `updated`, `session`, `session-start-ref` — **optional**. Added by #184. STATE.md files without these fields remain valid and should be treated as `updated: null` / `session: null`. Writers SHOULD populate these fields but readers MUST tolerate their absence. `session` is an attribution/history label, normally `<branch>-<YYYY-MM-DD>-<mode>-<n>` since #573 (Epic #568 Parallel-Aware Sessions P2.2); pre-#573 files may contain a UUID-v4 — both formats are read via `parseSessionId()` from `scripts/lib/session-id.mjs` per PRD §3 P2 row 3 (backward-compat). Neither form grants lock or registry ownership.
29
29
 
30
30
  The `session-type: none` + `status: idle` combination is used only for bootstrap-scaffolded placeholder files (no active session).
31
31
 
@@ -79,6 +79,32 @@ A log of unresolved, user-facing questions surfaced by wave agents during a sess
79
79
 
80
80
  Helpers: `readOpenQuestions` (pure), `appendOpenQuestion` (pure), `markOpenQuestionAnswered` (pure), `appendOpenQuestionOnDisk` (lock-guarded write), `markOpenQuestionAnsweredOnDisk` (lock-guarded write) — all exported from `scripts/lib/state-md.mjs`.
81
81
 
82
+ ## Session Identity and Lock Ownership (#1085)
83
+
84
+ This contract distinguishes a physical live-session key from labels that make a
85
+ session intelligible to people and history readers. It does not add an identity
86
+ layer.
87
+
88
+ - **`session_id` is the only live ownership key.** It is the native raw identity
89
+ supplied by the active harness, or a generated UUID when no trustworthy raw
90
+ identity is available. Lock acquisition, registry membership, self-exclusion,
91
+ proof checks, and lock release use this physical key.
92
+ - **`semantic_session_id` and STATE.md `session` are attribution/history
93
+ labels, never ownership.** They may describe the same work to a human, but
94
+ equality of either label cannot acquire, refresh, release, or reclaim a lock.
95
+ A legacy UUID in STATE.md remains readable only as historical data.
96
+ - **Never bridge a raw mismatch with a label or a proof.** If the current raw
97
+ id and a live lock's raw id differ, ownership is ambiguous. Leave the live
98
+ lock visible and let its TTL/Reaper lifecycle resolve it; do not substitute a
99
+ semantic match, STATE.md `session` match, or owner-proof match.
100
+ - **There is no `logical_session_id`.** A true cross-harness restart-continuity
101
+ contract requires a trusted native resume identifier and remains a follow-up.
102
+ In particular, a host rotation that changes both raw and semantic values has
103
+ no guaranteed continuity.
104
+
105
+ The peer-discovery and issue-budget procedures below apply these rules at their
106
+ narrow surfaces; neither creates a second ownership model.
107
+
82
108
  ## CCU-009 — Status = Index, Never History (#730/H6)
83
109
 
84
110
  > Adopted from an external-repo fleet-mining finding (2026-07-02): narrative
@@ -174,8 +200,8 @@ The `.orchestrator/session.lock` file is written mechanically by `hooks/_lib/loc
174
200
 
175
201
  ```json
176
202
  {
177
- "session_id": "<UUID-v4 OR semantic-id>",
178
- "semantic_session_id": "<branch>-<YYYY-MM-DD>-<mode>-<n>",
203
+ "session_id": "<native-raw-id OR generated-UUID>",
204
+ "semantic_session_id": "<attribution-label>",
179
205
  "started_at": "<ISO-8601 UTC>",
180
206
  "last_heartbeat": "<ISO-8601 UTC>",
181
207
  "mode": "deep|feature|housekeeping|session|...",
@@ -189,8 +215,8 @@ The `.orchestrator/session.lock` file is written mechanically by `hooks/_lib/loc
189
215
 
190
216
  | Field | Required since | Description |
191
217
  |---|---|---|
192
- | `session_id` | v1 | The session identifier (UUID-v4 on Claude Code, semantic on Codex/Cursor). |
193
- | `semantic_session_id` | v2 (Epic #583) | The semantic form (`<branch>-<YYYY-MM-DD>-<mode>-<n>`) **always present**, even when `session_id` is a UUID. Closes D4 gap: semantic-id branch was previously dead code on Claude Code (stdin always provides UUID). |
218
+ | `session_id` | v1 | The physical live lock/registry ownership key: a native raw harness identity, or a generated UUID when no trustworthy raw identity exists. Never use a semantic label here. |
219
+ | `semantic_session_id` | v2 (Epic #583) | An attribution/history label, normally `<branch>-<YYYY-MM-DD>-<mode>-<n>`, surfaced alongside the raw key. It never establishes lock or registry ownership, including when it equals STATE.md `session`. |
194
220
  | `started_at` | v1 | ISO-8601 timestamp when the lock was written. |
195
221
  | `last_heartbeat` | v2 (Epic #583) | ISO-8601 timestamp updated by the `SessionStart` hook and by `PostToolBatch`/`Stop` hooks. **Basis for liveness determination** — replaces PID-liveness (see below). |
196
222
  | `mode` | v1 | Session mode consulted by exclusivity-matrix. May be `"unknown"` in the provisional lock written by the hook before Session Config + AUQ have settled. |
@@ -219,3 +245,20 @@ Net: `pid` (field notes above) stays forensic-only; `last_heartbeat` freshness i
219
245
  ### Schema v1 → v2 backward-compat
220
246
 
221
247
  Readers (e.g., `readLock()` in `session-lock.mjs`, `discoverActiveSessions()`) MUST tolerate absent `last_heartbeat` and `semantic_session_id` fields (v1 locks written before Epic #583). When `last_heartbeat` is absent, fall back to TTL-based expiry from `started_at`. When `semantic_session_id` is absent, treat as unknown.
248
+
249
+ #### Schema v1 Sunset — evaluated 2026-08-15, tolerance RETAINED (#595)
250
+
251
+ The 90-day sunset window from Epic #583 (target 2026-08-25) came due and the removal was evaluated against the live fleet. **Verdict: keep the three reader tolerances; the blocker is not v1 data, it is a second production copy of the rule.**
252
+
253
+ **Precondition — zero v1 artefacts on disk (measured 2026-08-15, this host):**
254
+
255
+ - `find ~/Projects ~/.claude ~/.config /tmp/claude-501 -name 'session.lock' -not -path '*/node_modules/*'` → **12 files, 12/12 carry a non-empty `last_heartbeat`** (0 v1).
256
+ - `~/.config/session-orchestrator/sessions/active/*.json` → **3 entries, 3/3 carry the `mode` key** (0 v1).
257
+ - The only co-installed older plugin build (`~/.claude/plugins/cache/session-orchestrator/session-orchestrator/3.13.0`) already writes `last_heartbeat` (`session-lock.mjs:195`) and `mode` (`session-registry.mjs:209`) — **no v1 writer remains on this host.**
258
+
259
+ **Why the branches stay anyway:**
260
+
261
+ 1. **`parseLock()` / `isLockLive()` — the rule is duplicated.** `scripts/lib/harness-audit/categories/category4.mjs` `lockIsLive()` inlines the same `last_heartbeat ?? started_at` fallback, and `tests/lib/lock-ttl-parity.test.mjs` asserts the mirror and the SSOT return identical verdicts *for a v1 lock*. Dropping it in `session-lock.mjs` alone breaks that parity by construction. A measured removal attempt turned **18 tests red across 4 files** (`session-discovery` 9, `session-discovery-fallback` 6, `lock-ttl-parity` 1, `on-session-start` 2) — all outside the lock/registry module pair, all seeding v1-shaped fixtures.
262
+ 2. **`_validEntry()` optional `mode` — removal is a net safety LOSS.** Rejecting a mode-less registry entry drops a **live peer** from `readRegistry()`, making it invisible to the exclusivity matrix. An absent `mode` already degrades to the `parallel-ok` bucket, so strictening buys no detection and costs peer visibility — the wrong direction under `.claude/rules/development.md` § Guard & Threshold Design.
263
+
264
+ **What a real sunset needs (co-change set, one atomic MR):** `scripts/lib/session-lock.mjs` + `scripts/lib/harness-audit/categories/category4.mjs` (the mirror) + fixture updates in `tests/lib/session-discovery.test.mjs`, `tests/lib/session-discovery-fallback.test.mjs`, `tests/lib/lock-ttl-parity.test.mjs`, `tests/hooks/on-session-start.test.mjs`. Deleting the *mirror* in favour of importing the SSOT is the durable fix — the duplication, not the v1 data, is what keeps this class alive. The registry item should be closed as won't-do per point 2.
@@ -79,7 +79,6 @@ AskUserQuestion({
79
79
  question: "Leeres Repo erkannt. Basierend auf '<HEURISTIC_REASON>' empfehle ich **<RECOMMENDED_TIER>**. Passt das?",
80
80
  header: "Bootstrap",
81
81
  options: [
82
- { label: "<RECOMMENDED_TIER> (Empfohlen)", description: "<one-line description of what this tier scaffolds>" },
83
82
  { label: "fast", description: "Nur CLAUDE.md + .gitignore + README. Für Demos, Spikes, Playgrounds." },
84
83
  { label: "standard", description: "Fast + package.json/Manifest + TypeScript + Linting + Tests. Für MVPs und echte Produkte." },
85
84
  { label: "deep", description: "Standard + CI + CODEOWNERS + CHANGELOG. Für Production, Team, Langlebige Repos." },
@@ -90,6 +89,8 @@ AskUserQuestion({
90
89
  })
91
90
  ```
92
91
 
92
+ Before rendering: append ` (Empfohlen)` to whichever of the three tier labels equals `<RECOMMENDED_TIER>`, and move that option to position 1. The recommended tier is one of the three — listing it a fourth time as its own option made five options, one more than `AskUserQuestion` accepts, and repeated the same choice twice.
93
+
93
94
  If user selects "Abbrechen": stop. Report "Bootstrap abgebrochen. Kein Kommando wird ausgeführt." Do not continue.
94
95
 
95
96
  Store confirmed tier as `CONFIRMED_TIER`.
@@ -48,12 +48,12 @@ Run a single AUQ to let the user characterize the problem in their own words:
48
48
  ```
49
49
  AskUserQuestion({
50
50
  questions: [{
51
- question: "Describe the feature or problem you want to brainstorm. What's the core pain point it addresses, and what's still ambiguous to you?",
52
- header: "Feature Frame",
51
+ question: "What do you want to brainstorm, and what is still unclear about it?",
52
+ header: "Frame",
53
53
  options: [
54
- { label: "UX / user-facing surface is unclear", description: "You know what you want to build but not how it should work for the user." },
55
- { label: "Scope / boundaries are unclear", description: "You're not sure how much to build, or where one feature ends and another begins." },
56
- { label: "Multiple approaches exist need to pick one", description: "You see 2-3 ways to solve this and aren't sure which is best." },
54
+ { label: "The user-facing surface", description: "You know what to build but not how it should work for the user." },
55
+ { label: "Scope / boundaries", description: "You're not sure how much to build, or where one feature ends and another begins." },
56
+ { label: "Several approaches — pick one", description: "You see 2-3 ways to solve this and aren't sure which is best." },
57
57
  { label: "Other / describe below", description: "Free-form: describe the ambiguity in the next prompt." }
58
58
  ],
59
59
  multiSelect: false
@@ -98,9 +98,9 @@ Example round (adapt to the actual feature):
98
98
  AskUserQuestion({
99
99
  questions: [{
100
100
  question: "Who is the primary user of this feature, and how do they trigger it?",
101
- header: "User Surface — Round 2",
101
+ header: "User surface",
102
102
  options: [
103
- { label: "Authenticated user via UI action (Recommended)", description: "Pro: fits existing session model. Con: requires UI component work." },
103
+ { label: "Signed-in user, UI action (Recommended)", description: "Pro: fits existing session model. Con: requires UI component work." },
104
104
  { label: "Automated trigger (webhook, cron, event)", description: "Pro: no manual user step. Con: harder to debug and test." },
105
105
  { label: "Admin-only operation", description: "Pro: simpler access control. Con: limits who can self-serve." },
106
106
  { label: "Other / describe below", description: "Describe the trigger mechanism." }
@@ -128,12 +128,12 @@ Once the dialogue has enough signal, synthesize 2-3 concrete implementation appr
128
128
  ```
129
129
  AskUserQuestion({
130
130
  questions: [{
131
- question: "Based on your answers, here are the viable approaches. Which fits best?",
132
- header: "Design Approach",
131
+ question: "Which of these approaches fits best?",
132
+ header: "Approach",
133
133
  options: [
134
- { label: "Approach A — [1-sentence summary] (Recommended)", description: "Trade-offs: [key pro]. [key con]. Complexity: low/medium/high." },
135
- { label: "Approach B — [1-sentence summary]", description: "Trade-offs: [key pro]. [key con]. Complexity: low/medium/high." },
136
- { label: "Approach C — [1-sentence summary]", description: "Trade-offs: [key pro]. [key con]. Complexity: low/medium/high." }
134
+ { label: "A — [short summary] (Recommended)", description: "Best balance of value, reversibility and scope, because [key pro]. Cost: [key con]. Complexity: low/medium/high." },
135
+ { label: "B — [short summary]", description: "Trade-offs: [key pro]. Cost: [key con]. Complexity: low/medium/high." },
136
+ { label: "C — [short summary]", description: "Trade-offs: [key pro]. Cost: [key con]. Complexity: low/medium/high." }
137
137
  ],
138
138
  multiSelect: false
139
139
  }]
@@ -231,13 +231,13 @@ Present the spec path and summary to the user. Then ask via AUQ:
231
231
  ```
232
232
  AskUserQuestion({
233
233
  questions: [{
234
- question: "The design spec has been written to docs/specs/YYYY-MM-DD-<slug>-design.md. How do you want to proceed?",
235
- header: "Design Hand-off",
234
+ question: "The spec is at docs/specs/YYYY-MM-DD-<slug>-design.md. How do you want to proceed?",
235
+ header: "Hand-off",
236
236
  options: [
237
- { label: "Proceed to /plan feature (Recommended)", description: "Formalize this spec into a PRD with acceptance criteria and issue creation." },
238
- { label: "Proceed to /write-executable-plan", description: "Skip the formal PRD and go straight to an executable plan (issue #39, once shipped)." },
239
- { label: "Revise the spec", description: "I have feedback describe what to change and I'll update the spec." },
240
- { label: "Done for now", description: "Keep the spec as a reference; no immediate next step." }
237
+ { label: "Proceed to /plan feature (Recommended)", description: "Turns the spec into a PRD with acceptance criteria, then files the issues. Cost: one more question round." },
238
+ { label: "Proceed to /write-executable-plan", description: "Skips the formal PRD and writes a step-by-step executable plan instead — tracked in #39 (a numbered ticket, not yet released)." },
239
+ { label: "Revise the spec", description: "Describe what to change; the spec is updated and comes back here." },
240
+ { label: "Done for now", description: "The spec stays on disk as a reference. Nothing else runs." }
241
241
  ],
242
242
  multiSelect: false
243
243
  }]
@@ -62,6 +62,18 @@ The active level is `efficiency.output-level` in `~/.config/session-orchestrator
62
62
  - Shape: explain WHY each option is on the table, name the paths you already discarded and why, define unfamiliar terms on first use.
63
63
  - Escalation: `expand <topic>` — see § Escalation above.
64
64
 
65
+ ### Register — how a sentence reads
66
+
67
+ The budgets above set *how much* you say; the register sets *how*. It is
68
+ defined once, in `skills/session-start/soul.md` § "Register — how a sentence
69
+ reads", and binds here unchanged: the frame ("write for someone who knows this
70
+ project but has not seen what you just saw"), the plain-words test with its
71
+ five worked cases, and its precedence over § "Never traded for brevity" above.
72
+ Read it there. It is not repeated here on purpose — the § Output Levels intro
73
+ sentence already exists in four copies across the four souls with nothing
74
+ checking their parity, and a fifth copied rule would drift the same way. A
75
+ pointer cannot.
76
+
65
77
  ### Companion dials
66
78
 
67
79
  Same file, same lookup, same fallback-to-default rule:
@@ -1,6 +1,14 @@
1
1
  ---
2
2
  name: claude-md-drift-check
3
- description: Use when detecting drift between CLAUDE.md (or AGENTS.md, the Codex CLI alias) / _meta narrative and live repository state. Ten checks: absolute-path resolution, 01-projects/ count claims, issue-reference freshness, session-file existence, command-count sync, session-config-parity (mandatory template keys = error, opt-in gaps = warning), vault-dir-parity (CLAUDE.md vs AGENTS.md), generated-rule-staleness (WARN-only), rule-scoping (paths:/globs: frontmatter defects, dangling rule citations, zero-match globs), and docs-parity (docs/components.md count-claims vs on-disk counts, template-vs-reference config-key parity, stale .claude/metrics/ paths). Full per-check spec in the body table. Invoked as an opt-in session-end phase; mirrors vault-sync's lean JSON+exit-code contract.
3
+ description: >
4
+ Use when detecting drift between CLAUDE.md (or AGENTS.md, the Codex CLI alias) / _meta narrative and
5
+ live repository state. Ten checks: absolute-path resolution, 01-projects/ count claims, issue-reference
6
+ freshness, session-file existence, command-count sync, session-config-parity (mandatory template keys =
7
+ error, opt-in gaps = warning), vault-dir-parity (CLAUDE.md vs AGENTS.md), generated-rule-staleness
8
+ (WARN-only), rule-scoping (paths:/globs: frontmatter defects, dangling rule citations, zero-match
9
+ globs), and docs-parity (docs/components.md count-claims vs on-disk counts, template-vs-reference
10
+ config-key parity, stale .claude/metrics/ paths). Full per-check spec in the body table. Invoked as an
11
+ opt-in session-end phase; mirrors vault-sync's lean JSON+exit-code contract.
4
12
  model: haiku
5
13
  ---
6
14
 
@@ -1,6 +1,9 @@
1
1
  ---
2
2
  name: debug
3
- description: Use when encountering any bug, test failure, build break, or unexpected behavior — runs a 4-phase systematic debugging process before proposing any fix. Iron Law: NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST. Produces a `.orchestrator/debug/` artifact the fixer agent must reference.
3
+ description: >
4
+ Use when encountering any bug, test failure, build break, or unexpected behavior — runs a 4-phase
5
+ systematic debugging process before proposing any fix. Iron Law: NO FIXES WITHOUT ROOT CAUSE
6
+ INVESTIGATION FIRST. Produces a `.orchestrator/debug/` artifact the fixer agent must reference.
4
7
  model: inherit
5
8
  color: red
6
9
  tools: Read, Grep, Glob, Bash, Write
@@ -104,13 +104,13 @@ When the gate above is satisfied, present exactly this AskUserQuestion (AUQ-003
104
104
  ```
105
105
  AskUserQuestion({
106
106
  questions: [{
107
- question: "Scope `feature` was requested. How should this run handle feature discovery?",
108
- header: "Feature Scope",
107
+ question: "Scope `feature` was requested. How should this run handle it?",
108
+ header: "Scope",
109
109
  options: [
110
- { label: "Grounded scan (Recommended)", description: "Run the evidence-anchored feature probes (intent-drift, stubbed-dead-feature) on the probe→verify→triage rails." },
111
- { label: "Also judgment topics", description: "Grounded scan PLUS collect judgment-based product questions (opportunity framing, personas). A follow-up prompt after Phase 5 offers inline synthesis or hand-off to /brainstorm or /plan feature — judgment items never enter the verified-findings pipeline." },
112
- { label: "Route out", description: "No scan; hand off to /brainstorm (product ideation) or /grill (assumption stress-test) instead." },
113
- { label: "Skip", description: "Drop `feature` from this run's scope set." }
110
+ { label: "Grounded scan (Recommended)", description: "Every finding is tied to a file and line and is verified before it can become an issue. Cost: two extra probes (intent-drift, stubbed-dead-feature)." },
111
+ { label: "Also judgment topics", description: "Same scan, plus open product questions (opportunity framing, personas) kept as notes. They never become issues; after Phase 5 you pick where they go." },
112
+ { label: "Route out", description: "No scan at all. You get a pointer to /brainstorm (product ideation) or /grill (assumption stress-test) instead." },
113
+ { label: "Skip", description: "Drops `feature` (the probes for half-built and drifted features) from this run; the other scopes still run." }
114
114
  ],
115
115
  multiSelect: false
116
116
  }]
@@ -125,12 +125,12 @@ AskUserQuestion({
125
125
  ```
126
126
  AskUserQuestion({
127
127
  questions: [{
128
- question: "Judgment topics were collected alongside the grounded scan. How should they be handled?",
129
- header: "Judgment Topics",
128
+ question: "Where should the collected judgment topics go?",
129
+ header: "Topics",
130
130
  options: [
131
- { label: "Inline synthesis (Recommended)", description: "Sketch a lightweight OST/persona pass directly in the report's `### Judgment Topics (non-verified)` appendix explicitly marked non-verified, no separate skill invocation needed." },
132
- { label: "Route to /brainstorm", description: "Hand the collected topics off as pre-filled context to /brainstorm for a full Socratic ideation dialogue." },
133
- { label: "Route to /plan feature", description: "Hand the collected topics off as pre-filled context to /plan feature for feature-PRD scoping." }
131
+ { label: "Inline synthesis (Recommended)", description: "Sketches an outcome/persona pass into `### Judgment Topics (non-verified)` (a report section that never becomes issues). Cost: no second run." },
132
+ { label: "Route to /brainstorm", description: "Hands the topics to /brainstorm as its opening context, for a full question-and-answer design dialogue." },
133
+ { label: "Route to /plan feature", description: "Hands the topics to /plan feature as its opening context, for feature-PRD scoping." }
134
134
  ],
135
135
  multiSelect: false
136
136
  }]
@@ -370,14 +370,16 @@ For each Critical or High finding, use AskUserQuestion (on Codex CLI where AskUs
370
370
  ```
371
371
  AskUserQuestion({
372
372
  questions: [{
373
- question: "<finding title>\n\n<file_path>:<line_number>\n```\n<matched_text with +/-3 lines context>\n```\n\n<description>\n\nRecommended fix: <recommended_fix>",
374
- header: "<severity>",
373
+ question: "<severity> finding in <file_path> what should happen with it?",
374
+ header: "Finding",
375
375
  options: [
376
- { label: "Create issue (<severity>)", description: "Create a priority::<severity> issue for this finding" },
377
- { label: "Adjust priority", description: "Create issue with different priority" },
378
- { label: "Dismiss -- intentional", description: "This is by design, skip" },
379
- { label: "Dismiss -- false positive", description: "Detection was wrong, skip" }
380
- ]
376
+ { label: "Create issue (<severity>)", description: "Files it as priority::<severity>, so it is tracked outside this session. The code below is copied into the issue body.",
377
+ preview: "<finding title>\n\n<file_path>:<line_number>\n```\n<matched_text with +/-3 lines context>\n```\n\n<description>\n\nRecommended fix: <recommended_fix>" },
378
+ { label: "Adjust priority", description: "Same issue, a priority you pick — this question then comes back with the new label." },
379
+ { label: "Dismiss -- intentional", description: "The code is deliberate. Nothing is filed, and the finding stays only in this run's report." },
380
+ { label: "Dismiss -- false positive", description: "The probe misread the code. Nothing is filed; worth reporting if the same probe misfires again." }
381
+ ],
382
+ multiSelect: false
381
383
  }]
382
384
  })
383
385
  ```
@@ -391,13 +393,15 @@ Group remaining findings by category. For each category with medium/low findings
391
393
  ```
392
394
  AskUserQuestion({
393
395
  questions: [{
394
- question: "[N] medium/low findings in [category]:\n\n1. [title] -- [file_path]:[line] ([severity])\n2. [title] -- [file_path]:[line] ([severity])\n...",
395
- header: "[Category]",
396
+ question: "Create issues for all [N] medium/low findings in [category]?",
397
+ header: "Findings",
396
398
  options: [
397
- { label: "Accept all (Recommended)", description: "Create issues for all [N] findings" },
398
- { label: "Review individually", description: "Walk through each finding one by one" },
399
- { label: "Dismiss all", description: "Skip all medium/low findings in this category" }
400
- ]
399
+ { label: "Accept all (Recommended)", description: "Medium and low findings are cheap to file and cheap to close. Cost: [N] issues, roughly one second apart.",
400
+ preview: "1. [title] -- [file_path]:[line] ([severity])\n2. [title] -- [file_path]:[line] ([severity])\n..." },
401
+ { label: "Review individually", description: "One question per finding, same options as the critical ones. Cost: [N] more prompts." },
402
+ { label: "Dismiss all", description: "Nothing is filed for this category. The findings stay in this run's report only." }
403
+ ],
404
+ multiSelect: false
401
405
  }]
402
406
  })
403
407
  ```
@@ -79,7 +79,7 @@ Used for a single finding from a single probe.
79
79
  **CLI Example:**
80
80
  ```bash
81
81
  # GitHub
82
- gh issue create \
82
+ gh issue create -R <OWNER>/<REPO> \
83
83
  --title "[Discovery] <finding_title>" \
84
84
  --label "type:discovery,priority::<level>,area:<area>,status:ready" \
85
85
  --body "$(cat <<'EOF'
@@ -88,7 +88,7 @@ EOF
88
88
  )"
89
89
 
90
90
  # GitLab
91
- glab issue create \
91
+ glab issue create -R <OWNER>/<REPO> \
92
92
  --title "[Discovery] <finding_title>" \
93
93
  --label "type:discovery,priority::<level>,area:<area>,status:ready" \
94
94
  --description "$(cat <<'EOF'
@@ -159,7 +159,7 @@ Used when multiple related findings from the same probe or category are grouped
159
159
  **CLI Example:**
160
160
  ```bash
161
161
  # GitHub
162
- gh issue create \
162
+ gh issue create -R <OWNER>/<REPO> \
163
163
  --title "[Discovery] <category> audit: <summary>" \
164
164
  --label "type:discovery,priority::<level>,area:<area>,status:ready" \
165
165
  --body "$(cat <<'EOF'
@@ -168,7 +168,7 @@ EOF
168
168
  )"
169
169
 
170
170
  # GitLab
171
- glab issue create \
171
+ glab issue create -R <OWNER>/<REPO> \
172
172
  --title "[Discovery] <category> audit: <summary>" \
173
173
  --label "type:discovery,priority::<level>,area:<area>,status:ready" \
174
174
  --description "$(cat <<'EOF'
@@ -58,9 +58,9 @@ Grep pattern: (TODO|FIXME|HACK|XXX|TEMP|WORKAROUND)[\s:()\-]
58
58
  For each match, check whether a corresponding VCS issue exists:
59
59
  ```bash
60
60
  # Search for issue referencing the annotation text
61
- gh issue list --search "<annotation text>" --limit 5
61
+ gh issue list -R <OWNER>/<REPO> --search "<annotation text>" --limit 5
62
62
  # or
63
- glab issue list --search "<annotation text>" --per-page 5
63
+ glab issue list -R <OWNER>/<REPO> --search "<annotation text>" --per-page 5
64
64
  ```
65
65
 
66
66
  Flag annotations with no corresponding issue.
@@ -113,11 +113,11 @@ Export Status: exported/public | internal
113
113
  1. List open feature/enhancement issues via the VCS CLI (syntax reference: `skills/gitlab-ops/SKILL.md` § "Common CLI Commands" — do not duplicate CLI flags here beyond what's needed to name the call):
114
114
  ```bash
115
115
  # GitLab
116
- glab issue list --label "feature" --per-page 100
117
- glab issue list --label "enhancement" --per-page 100
116
+ glab issue list -R <OWNER>/<REPO> --label "feature" --per-page 100
117
+ glab issue list -R <OWNER>/<REPO> --label "enhancement" --per-page 100
118
118
  # GitHub
119
- gh issue list --label "feature" --limit 100
120
- gh issue list --label "enhancement" --limit 100
119
+ gh issue list -R <OWNER>/<REPO> --label "feature" --limit 100
120
+ gh issue list -R <OWNER>/<REPO> --label "enhancement" --limit 100
121
121
  ```
122
122
 
123
123
  2. Extract theme keywords from each issue's title (and first paragraph of body where available): lowercase, strip stop-words, tokenize on non-alphanumeric boundaries. Group issues that share two or more significant keywords (e.g. "export", "csv", "invoice") into a candidate cluster.
@@ -125,10 +125,10 @@ gh issue list --label "enhancement" --limit 100
125
125
  3. For each candidate cluster with 3 or more issues, check whether ANY member issue is already linked to an epic (`epic` label, a GitLab Epic relationship, or an in-body reference such as `part of #<epic-iid>` / `Epic: #<epic-iid>`):
126
126
  ```bash
127
127
  # GitLab -- inspect labels + description for epic linkage
128
- glab issue view <IID>
128
+ glab issue view -R <OWNER>/<REPO> <IID>
129
129
  Grep pattern: (^Labels:.*\bepic\b|part of #|Epic:\s*#)
130
130
  # GitHub -- same check against issue body/labels
131
- gh issue view <NUMBER>
131
+ gh issue view -R <OWNER>/<REPO> <NUMBER>
132
132
  Grep pattern: (^labels:.*\bepic\b|part of #|Epic:\s*#)
133
133
  ```
134
134
 
@@ -10,10 +10,10 @@
10
10
 
11
11
  ```bash
12
12
  # GitLab: query recent pipeline status
13
- glab pipeline list --per-page 10
13
+ glab pipeline list -R <OWNER>/<REPO> --per-page 10
14
14
 
15
15
  # GitHub: query recent workflow runs
16
- gh run list --limit 10
16
+ gh run list -R <OWNER>/<REPO> --limit 10
17
17
 
18
18
  # Parse output for:
19
19
  # - Repeated failures (same pipeline failing 3+ times in a row)
@@ -55,9 +55,9 @@ Grep pattern: <claimed_addition>
55
55
  git show <commit_hash> -- <relevant_files>
56
56
 
57
57
  # "Closes #N" -> verify acceptance criteria from issue #N are met
58
- gh issue view <N> --json body -q '.body'
58
+ gh issue view -R <OWNER>/<REPO> <N> --json body -q '.body'
59
59
  # or
60
- glab issue view <N>
60
+ glab issue view -R <OWNER>/<REPO> <N>
61
61
 
62
62
  # Step 3: Cross-reference claims against actual changes
63
63
  git diff <commit_hash>~1..<commit_hash>
@@ -83,10 +83,10 @@ Evidence: <what was found or not found>
83
83
 
84
84
  ```bash
85
85
  # GitLab: list open issues sorted by last update
86
- glab issue list --per-page 100 | head -50
86
+ glab issue list -R <OWNER>/<REPO> --per-page 100 | head -50
87
87
 
88
88
  # GitHub: list open issues sorted by last update
89
- gh issue list --limit 100 --json number,title,labels,updatedAt,assignees --jq '.[] | select(.updatedAt < "<30_days_ago_iso>")'
89
+ gh issue list -R <OWNER>/<REPO> --limit 100 --json number,title,labels,updatedAt,assignees --jq '.[] | select(.updatedAt < "<30_days_ago_iso>")'
90
90
 
91
91
  # Flag:
92
92
  # - Issues with no activity in stale-issue-days (default: 30 days)
@@ -131,7 +131,7 @@ for issue in issues:
131
131
  "
132
132
 
133
133
  # GitHub: fetch issue bodies and parse cross-references
134
- gh issue list --limit 100 --json number,body --jq '.[] | {number, body}' | python3 -c "
134
+ gh issue list -R <OWNER>/<REPO> --limit 100 --json number,body --jq '.[] | {number, body}' | python3 -c "
135
135
  import json, sys, re
136
136
  for line in sys.stdin:
137
137
  issue = json.loads(line)