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
@@ -40,7 +40,9 @@ catalog rather than referencing the plugin copy in place.
40
40
  Install:
41
41
  ```bash
42
42
  mkdir -p .claude/personas
43
- cp "$(claude plugin dir session-orchestrator)/skills/persona-panel/presets/"*.md .claude/personas/
43
+ # Claude Code has no `plugin dir` subcommand — resolve the install path from the cache.
44
+ SO_DIR="$(dirname "$(find ~/.claude/plugins/cache -path '*session-orchestrator*' -name package.json 2>/dev/null | head -1)")"
45
+ cp "$SO_DIR/skills/persona-panel/presets/"*.md .claude/personas/
44
46
  ```
45
47
 
46
48
  ## Phase 0: Bootstrap Gate
@@ -281,17 +281,17 @@ If any criterion is FAIL:
281
281
  2. Revise the affected PRD sections
282
282
  3. Re-submit to the reviewer
283
283
 
284
- Maximum 3 iterations. After 3 iterations with remaining issues, present them to the user via AskUserQuestion:
284
+ Maximum 3 iterations. After 3 iterations with remaining issues, list the flagged points in plain text (they are context, not a choice), then ask via AskUserQuestion:
285
285
 
286
286
  ```
287
287
  AskUserQuestion({
288
288
  questions: [{
289
- question: "The PRD reviewer flagged these remaining issues after 3 revision rounds:\n\n[list issues]\n\nHow do you want to proceed?",
289
+ question: "The reviewer still flags [N] points after 3 revision rounds. Proceed anyway?",
290
290
  header: "PRD Review",
291
291
  options: [
292
- { label: "Accept as-is (Recommended)", description: "Issues are minor, proceed with current PRD." },
293
- { label: "Manual edit", description: "I'll edit the PRD myself before continuing." },
294
- { label: "Re-run review", description: "Try one more revision round." }
292
+ { label: "Accept as-is (Recommended)", description: "Three rounds did not close them, so a fourth probably will not. The flags then stay in the PRD and travel into the issues filed from it." },
293
+ { label: "Manual edit", description: "You edit the PRD yourself; the flow waits, then re-runs the reviewer on your version." },
294
+ { label: "Re-run review", description: "One more revision round. Cost: another reviewer pass, and the same points may come back unchanged." }
295
295
  ],
296
296
  multiSelect: false
297
297
  }]
@@ -300,16 +300,16 @@ AskUserQuestion({
300
300
 
301
301
  ### 5.3 User Review Gate
302
302
 
303
- After the reviewer passes (or user accepts), present the final PRD:
303
+ After the reviewer passes (or user accepts), present the final PRD in plain text — path plus a short section summary — then ask:
304
304
 
305
305
  ```
306
306
  AskUserQuestion({
307
307
  questions: [{
308
- question: "PRD is ready for your review. It has been saved to [path].\n\nPlease review the document and confirm.",
308
+ question: "Approve the PRD at [path]?",
309
309
  header: "PRD Approval",
310
310
  options: [
311
- { label: "Approve PRD (Recommended)", description: "PRD looks good, proceed to issue creation." },
312
- { label: "Request changes", description: "I have feedback let me describe what to change." }
311
+ { label: "Approve PRD (Recommended)", description: "Nothing further is checked after this: approval commits the PRD to HEAD (Phase 5.5), then issue creation starts." },
312
+ { label: "Request changes", description: "Describe what to change; the PRD is rewritten and comes back here. No limit on rounds." }
313
313
  ],
314
314
  multiSelect: false
315
315
  }]
@@ -395,18 +395,20 @@ Assign labels from the standard taxonomy:
395
395
 
396
396
  ### 6.3 User Review
397
397
 
398
- Present the full issue structure via AskUserQuestion before creating anything:
398
+ Present the full issue structure via AskUserQuestion before creating anything. The table is the text that is about to be filed, so it belongs in `preview` — not in the question:
399
399
 
400
400
  ```
401
401
  AskUserQuestion({
402
402
  questions: [{
403
- question: "Proposed issue structure:\n\n**Epic:** [title]\n\n| # | Sub-Issue | Priority | Labels | Blocked By |\n|---|----------|----------|--------|------------|\n| 1 | [title] | critical | [labels] | — |\n| 2 | [title] | high | [labels] | #1 |\n| ... | ... | ... | ... | ... |\n\nTotal: [N] issues. Confirm or adjust.",
404
- header: "Issue Review",
403
+ question: "Create these [N] issues from the PRD?",
404
+ header: "Issues",
405
405
  options: [
406
- { label: "Create all issues (Recommended)", description: "Proceed with the proposed structure." },
407
- { label: "Adjust priorities", description: "I want to change some priorities before creating." },
408
- { label: "Remove issues", description: "Some issues should not be created." },
409
- { label: "Cancel", description: "Do not create any issues." }
406
+ { label: "Create all [N] (Recommended)",
407
+ description: "Priorities and blocked-by links come straight from the approved PRD. Cost: one API call per issue, ~1s apart.",
408
+ preview: "**Epic:** [title]\n\n| # | Sub-Issue | Priority | Labels | Blocked By |\n|---|----------|----------|--------|------------|\n| 1 | [title] | critical | [labels] | — |\n| 2 | [title] | high | [labels] | #1 |\n| ... | ... | ... | ... | ... |" },
409
+ { label: "Adjust priorities", description: "Same [N] issues, different priority labels. Name them and this question comes back with the table updated." },
410
+ { label: "Remove issues", description: "Name the ones to drop; the rest are created unchanged." },
411
+ { label: "Cancel", description: "Nothing is created. The PRD stays committed, so Phase 6 can run again later." }
410
412
  ],
411
413
  multiSelect: false
412
414
  }]
@@ -127,7 +127,7 @@ Apply per gitlab-ops skill label taxonomy:
127
127
 
128
128
  ### User Review Gate
129
129
 
130
- Present the full issue structure via AskUserQuestion before creation:
130
+ Present the full issue structure via the AskUserQuestion payload in `SKILL.md` § 6.3 — the issue table belongs in the option's `preview` field, not in the question text:
131
131
 
132
132
  - Epic title and description
133
133
  - Each sub-issue: title, priority, labels, dependency links
@@ -38,7 +38,7 @@ Agent({ subagent_type: "Explore", description: "Check ecosystem for conflicts",
38
38
  3. **Target audience** — Options informed by market research agent. User selects or provides custom.
39
39
  4. **User-Story-Schicht** — "User-Story-Schicht für dieses Feature erzeugen?" Immer fragen (kein Audience-Heuristik-Gate). Drei Antwortoptionen: **Ja (Als/möchte/damit)** — klassische Persona-Story-Form; **Ja (job-story)** — job-story-Form ("When [situation], I want [motivation], so I can [outcome]"); **Nein** — byte-identisches Status-quo-Verhalten. Bei einer der beiden "Ja"-Optionen emittiert die PRD eine optionale ## User Stories Sektion (je Story ein ↳ AC-Pointer) in der gewählten Form; bei "Nein" wird die Sektion vollständig weggelassen.
40
40
  5. **Core problem being solved** — Open-ended. Claude suggests structure if answer is vague.
41
- 6. **GitLab group** — Discover available groups dynamically. Run `ls $BASELINE_PATH/templates/` for project types, and check for a groups config in `$BASELINE_PATH/config/` or use `glab group list` to discover GitLab groups. Present findings via AskUserQuestion.
41
+ 6. **GitLab group** — Select the GitLab host explicitly, then discover available groups dynamically. Run `ls $BASELINE_PATH/templates/` for project types, and check for a groups config in `$BASELINE_PATH/config/` or run `glab api --hostname "$GITLAB_HOST" "groups?per_page=100&min_access_level=10"` to discover GitLab groups — read each entry's `full_path` field. (`glab` has no `group` subcommand at all — invoking one exits 1 with `Unknown command "group"`.) Present findings via AskUserQuestion.
42
42
 
43
43
  ### Wave 2 — Technical Details (5 questions, dynamic per archetype)
44
44
 
@@ -129,6 +129,17 @@ Map gathered answers to script input choices:
129
129
  # 4. Map user's selected style name → numeric choice for STYLE_CHOICE (if applicable)
130
130
  # 5. Map user's selected group name → numeric choice for GROUP_CHOICE
131
131
  # Do NOT hardcode numeric mappings — they must be derived from the script.
132
+ #
133
+ # GROUP_CHOICE is a MENU INDEX, never a namespace. Keep the group's real
134
+ # namespace in a separate variable — every later step addresses the project as
135
+ # "<group-path>/<project>", and a numeric index there silently targets nothing.
136
+ GROUP_PATH="$(...)" # e.g., "products" — the full_path of the chosen group
137
+
138
+ # These values identify the NEW project, not the directory in which this plan runs.
139
+ # Select the host with the group; do not let glab infer it from an ambient remote.
140
+ GITLAB_HOST="<selected GitLab hostname>"
141
+ PROJECT_PATH="$GROUP_PATH/$PROJECT_NAME"
142
+ ENCODED_PROJECT_PATH="$(node -e 'process.stdout.write(encodeURIComponent(process.argv[1]))' "$PROJECT_PATH")"
132
143
  (
133
144
  echo "$TYPE_CHOICE" # e.g., "1" for nextjs-saas
134
145
  echo "$STYLE_CHOICE" # e.g., "1" for vega (only if nextjs-saas)
@@ -140,10 +151,12 @@ Map gathered answers to script input choices:
140
151
 
141
152
  ### Step 2: Verify success
142
153
 
143
- Check exit code. Confirm repo exists:
154
+ Check the setup script exit code. Confirm the selected path exists without fetching a full REST project object:
144
155
 
145
156
  ```bash
146
- glab repo view $GROUP/$PROJECT_NAME
157
+ glab api --hostname "$GITLAB_HOST" graphql \
158
+ -f query='query($fullPath: ID!) { project(fullPath: $fullPath) { fullPath } }' \
159
+ -f fullPath="$PROJECT_PATH" | jq -er '.data.project.fullPath'
147
160
  ```
148
161
 
149
162
  ### Step 3: Adjust visibility
@@ -151,15 +164,30 @@ glab repo view $GROUP/$PROJECT_NAME
151
164
  If visibility is not `internal` (the default):
152
165
 
153
166
  ```bash
154
- glab repo edit --visibility private # or --visibility public
167
+ # There is no `glab repo edit`, and `glab repo update` carries no --visibility
168
+ # flag (its FLAGS are --archive/--defaultBranch/-d/--description). The encoded
169
+ # endpoint and explicit host target the new project independently of the CWD.
170
+ glab api --silent --hostname "$GITLAB_HOST" -X PUT \
171
+ "projects/${ENCODED_PROJECT_PATH}" \
172
+ -f visibility=private # or visibility=public
173
+
174
+ # Fetch only the scalar needed to verify the mutation, never a full REST object.
175
+ glab api --hostname "$GITLAB_HOST" graphql \
176
+ -f query='query($fullPath: ID!) { project(fullPath: $fullPath) { visibility } }' \
177
+ -f fullPath="$PROJECT_PATH" | jq -er '.data.project.visibility'
155
178
  ```
156
179
 
180
+ > The GraphQL verification query is read-only and requests only `visibility`; the
181
+ > PUT's unused response is intentionally suppressed. Confirm the selected host and
182
+ > path before running the mutation.
183
+
157
184
  For public/OSS, also configure GitHub mirror if applicable.
158
185
 
159
186
  ### Step 4: Set branch protection
160
187
 
161
188
  ```bash
162
- glab api -X PUT projects/:id/protected_branches \
189
+ glab api --silent --hostname "$GITLAB_HOST" -X POST \
190
+ "projects/${ENCODED_PROJECT_PATH}/protected_branches" \
163
191
  -f name=main \
164
192
  -f push_access_level=30 \
165
193
  -f merge_access_level=30
@@ -264,7 +292,7 @@ Always use the `priority::<level>` format in VCS CLI commands, not P0/P1/P2/P3.
264
292
 
265
293
  ### Step 4: Present for user confirmation
266
294
 
267
- Use AskUserQuestion to present the full issue structure:
295
+ Use the AskUserQuestion payload in `SKILL.md` § 6.3 verbatim — the issue table belongs in the option's `preview` field, not in the question text:
268
296
 
269
297
  - Epic title and description
270
298
  - Sub-issues with: title, priority, labels, dependency links
@@ -274,11 +302,11 @@ Use AskUserQuestion to present the full issue structure:
274
302
 
275
303
  ```bash
276
304
  # Create epic
277
- glab issue create --title "$EPIC_TITLE" --description "$EPIC_DESC" \
305
+ glab issue create -R "https://${GITLAB_HOST}/${PROJECT_PATH}" --title "$EPIC_TITLE" --description "$EPIC_DESC" \
278
306
  --label "type:epic,priority::$PRIORITY" --milestone "$MILESTONE"
279
307
 
280
308
  # Create sub-issues
281
- glab issue create --title "$ISSUE_TITLE" --description "$ISSUE_DESC" \
309
+ glab issue create -R "https://${GITLAB_HOST}/${PROJECT_PATH}" --title "$ISSUE_TITLE" --description "$ISSUE_DESC" \
282
310
  --label "type:feature,priority::$PRIORITY,status:ready,area:$AREA,appetite:$APPETITE"
283
311
  ```
284
312
 
@@ -287,10 +315,12 @@ glab issue create --title "$ISSUE_TITLE" --description "$ISSUE_DESC" \
287
315
  For issues with technical dependencies, set `blocks`/`is-blocked-by` relationships:
288
316
 
289
317
  ```bash
290
- # Issue #2 is blocked by Issue #1
291
- glab api -X POST projects/:id/issues/:issue2_iid/links \
292
- -f target_project_id=:id \
293
- -f target_issue_iid=:issue1_iid \
318
+ # Issue #2 is blocked by Issue #1 in this project. An encoded project path is
319
+ # valid for target_project_id, so no numeric project ID is needed.
320
+ glab api --silent --hostname "$GITLAB_HOST" -X POST \
321
+ "projects/${ENCODED_PROJECT_PATH}/issues/${ISSUE_2_IID}/links" \
322
+ -f target_project_id="$ENCODED_PROJECT_PATH" \
323
+ -f target_issue_iid="$ISSUE_1_IID" \
294
324
  -f link_type=is_blocked_by
295
325
  ```
296
326
 
@@ -69,6 +69,18 @@ The active level is `efficiency.output-level` in `~/.config/session-orchestrator
69
69
  - Shape: name the alternatives you rejected and why, spell out the appetite and the scope cuts, define unfamiliar terms on first use.
70
70
  - Escalation: `expand <topic>` — see § Escalation above.
71
71
 
72
+ ### Register — how a sentence reads
73
+
74
+ The budgets above set *how much* you say; the register sets *how*. It is
75
+ defined once, in `skills/session-start/soul.md` § "Register — how a sentence
76
+ reads", and binds here unchanged: the frame ("write for someone who knows this
77
+ project but has not seen what you just saw"), the plain-words test with its
78
+ five worked cases, and its precedence over § "Never traded for brevity" above.
79
+ Read it there. It is not repeated here on purpose — the § Output Levels intro
80
+ sentence already exists in four copies across the four souls with nothing
81
+ checking their parity, and a fifth copied rule would drift the same way. A
82
+ pointer cannot.
83
+
72
84
  ### Companion dials
73
85
 
74
86
  Same file, same lookup, same fallback-to-default rule:
@@ -243,12 +243,12 @@ For each batch (proposals sliced into groups of 4):
243
243
  ```
244
244
  AskUserQuestion({
245
245
  questions: [{
246
- question: "Which rule proposals should be written to .claude/rules/? (batch K of N)",
247
- header: "Reconcile — Approve Rule Proposals",
246
+ question: "Batch K of N — which rule proposals should be written into .claude/rules/?",
247
+ header: "Regeln",
248
248
  options: [
249
249
  {
250
250
  label: "<slug>.md (confidence: 0.72)",
251
- description: "Learning: <learningKey> | Path: .claude/rules/<slug>.md | <first 100 chars of rendered content>"
251
+ description: "From learning <learningKey>. Becomes a file under .claude/rules/ where this repo keeps its rules. Text: <first 100 chars of rendered content>"
252
252
  },
253
253
  ...up to 4 options per batch...
254
254
  {
@@ -1,6 +1,15 @@
1
1
  ---
2
2
  name: repo-audit
3
- description: Use this skill when the user wants to audit a repository for baseline compliance, check code quality, security posture, CI/CD setup, testing, documentation, and ecosystem configuration. Runs 9 checklist categories and emits a Markdown report plus JSON sidecar at .orchestrator/metrics/repo-audit-<timestamp>.json. <example>Context: User is in a project repo and wants a baseline compliance check. user: "/repo-audit" assistant: "Running repo-audit across 9 categories — Configuration, Code Quality, Git Hygiene, CI/CD, Testing, Security, Documentation, Clank Integration (optional), and MCP Configuration. Will produce a Markdown checklist report and JSON sidecar." <commentary>The user wants a compliance check; this skill is appropriate because it runs all 9 categories with pass/fail/warn/skipped statuses and writes structured output.</commentary></example>
3
+ description: >
4
+ Use this skill when the user wants to audit a repository for baseline compliance, check code quality,
5
+ security posture, CI/CD setup, testing, documentation, and ecosystem configuration. Runs 9 checklist
6
+ categories and emits a Markdown report plus JSON sidecar at
7
+ .orchestrator/metrics/repo-audit-<timestamp>.json. <example>Context: User is in a project repo and wants
8
+ a baseline compliance check. user: "/repo-audit" assistant: "Running repo-audit across 9 categories —
9
+ Configuration, Code Quality, Git Hygiene, CI/CD, Testing, Security, Documentation, Clank Integration
10
+ (optional), and MCP Configuration. Will produce a Markdown checklist report and JSON sidecar."
11
+ <commentary>The user wants a compliance check; this skill is appropriate because it runs all 9
12
+ categories with pass/fail/warn/skipped statuses and writes structured output.</commentary></example>
4
13
  model: inherit
5
14
  color: cyan
6
15
  ---
@@ -141,7 +141,8 @@ For every `SPIRAL` or `FAILED` agent surfaced in the walk above, ALSO append a c
141
141
  ```js
142
142
  import { appendWhatNotToRetryOnDisk } from '${PLUGIN_ROOT}/scripts/lib/state-md.mjs';
143
143
 
144
- // `parsed` = parseStateMd(STATE.md); session id from the `session:` frontmatter field.
144
+ // `parsed` = parseStateMd(STATE.md); `session:` is an attribution/history label.
145
+ // It records this entry's provenance only and never authorizes lock ownership.
145
146
  const sessionId = parsed.frontmatter.session ?? 'unknown-session';
146
147
  const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
147
148
 
@@ -646,7 +647,7 @@ import { planTailPhases } from '${PLUGIN_ROOT}/scripts/lib/session-end/phase-ski
646
647
  const { plan, skippedReport } = await planTailPhases({
647
648
  repoRoot: process.cwd(),
648
649
  config, // parsed Session Config (from $CONFIG)
649
- sessionId, // session.lock `session_id` / STATE.md `session:` field (or null)
650
+ sessionId, // physical session.lock `session_id` only (or null), never STATE.md `session`
650
651
  platform, // 'claude' | 'codex' | 'cursor'
651
652
  });
652
653
  // plan: Array<{ phase, run, reason, inputSource }>, already in ascending phase order.
@@ -733,20 +734,21 @@ After STATE.md is finalized with `status: completed` (Phase 3.4) and Recommendat
733
734
 
734
735
  ```javascript
735
736
  import { release } from 'scripts/lib/session-lock.mjs';
736
- // sessionId = the session identifier established by session-start Phase 1.2 acquire()
737
- // and stored in .orchestrator/session.lock (session_id field); matches the
738
- // STATE.md frontmatter `session:` field written during Pre-Wave 1b initialization.
739
- const result = release({ sessionId, repoRoot: process.cwd() });
737
+ // sessionId is the physical raw value established by session-start Phase 1.2
738
+ // and stored in .orchestrator/session.lock `session_id`. It is not STATE.md
739
+ // `session:` or `semantic_session_id`, both of which are attribution labels.
740
+ const rawSessionId = sessionId;
741
+ const result = release({ sessionId: rawSessionId, repoRoot: process.cwd() });
740
742
  // result.ok is always true unless a filesystem error occurred.
741
743
  // result.deleted === true → lock file removed successfully.
742
- // result.deleted === false → lock was absent or belonged to a different session_id (silent-OK).
744
+ // result.deleted === false → lock was absent or had a different raw session_id.
743
745
  ```
744
746
 
745
- If `result.deleted === false`, log `info: session-lock not released — already absent or session_id mismatch (no action needed)` and continue. This is a non-error state.
747
+ If `result.deleted === false`, log `info: session-lock not released — already absent or raw session_id mismatch` and continue. An active lock whose raw id differs is ambiguous: do **not** retry release with an equal `semantic_session_id`, STATE.md `session`, or owner proof. Leave that live lock for its TTL/Reaper lifecycle.
746
748
 
747
749
  If `result.ok === false` (rare filesystem error), log `⚠ session-lock: release failed — <result.reason>` and continue. Do NOT block the close for a lock-release failure — the TTL provides automatic expiry for the next session.
748
750
 
749
- The lock is released here — AFTER all STATE.md writes are complete and BEFORE the commit is staged in Phase 4.1. This ordering ensures a clean handover: the lock file is absent from the working tree when the commit is assembled, so it is not accidentally staged.
751
+ The lock is released here — AFTER all STATE.md writes are complete and BEFORE the commit is staged in Phase 4.1. This ordering ensures a clean handover when the current raw owner releases it: the lock file is absent from the working tree when the commit is assembled, so it is not accidentally staged.
750
752
 
751
753
  ## Phase 4: Commit & Push
752
754
 
@@ -780,11 +782,53 @@ git push origin HEAD
780
782
  ```
781
783
 
782
784
  ### 4.4 GitHub Mirror (if configured in Session Config)
785
+
786
+ Three states, three DISTINGUISHABLE outcomes. The predecessor of this block
787
+ (`git remote get-url github 2>/dev/null && git push github HEAD 2>/dev/null || echo "GitHub mirror: not configured"`)
788
+ collapsed a **failed push** into `GitHub mirror: not configured` and exited 0 — git's real
789
+ error went to `/dev/null`, so a broken mirror was indistinguishable from an unconfigured one
790
+ (`.claude/rules/bash-harness-pitfalls.md` — "Silence is not success"). That matters more once
791
+ anything is wired to the mirror (e.g. a Vercel Git deploy): a silently-failing push means the
792
+ downstream artifact never updates and nobody is told.
793
+
794
+ Run it verbatim — `tests/skills/session-end/github-mirror-push.test.mjs` extracts the block
795
+ between the markers and executes it, so no second copy of this command may exist.
796
+
783
797
  ```bash
784
- # Only attempt if 'mirror: github' is in Session Config AND remote exists
785
- git remote get-url github 2>/dev/null && git push github HEAD 2>/dev/null || echo "GitHub mirror: not configured"
798
+ # --- github-mirror-push:begin ---
799
+ # Only attempt if 'mirror: github' is in Session Config.
800
+ # State 0: not a git repository at all → loud WARN, exit 1. This state was MISSED
801
+ # in the first version and is the reason it is listed first now: outside
802
+ # a repo, `git remote get-url` fails with "fatal: not a git repository",
803
+ # which is indistinguishable from "no such remote" by exit code alone.
804
+ # The block then announced "no 'github' remote configured — skipping
805
+ # (not an error)" and exited 0 — fail-open, in the very fix written to
806
+ # close a fail-open. Found by an adversarial reviewer, not by the author.
807
+ # State 1: no 'github' remote → informational, exit 0 (legitimate for consumer repos)
808
+ # State 2: push succeeded → confirmation WITH the pushed SHA, exit 0
809
+ # State 3: push FAILED → loud WARN on stderr WITH git's real output, exit 1
810
+ if ! git_dir=$(git rev-parse --git-dir 2>&1); then
811
+ echo "WARN GitHub mirror: not a git repository — cannot mirror anything." >&2
812
+ echo " git said: ${git_dir}" >&2
813
+ exit 1
814
+ elif ! mirror_url=$(git remote get-url github 2>&1); then
815
+ echo "GitHub mirror: no 'github' remote configured — skipping (not an error)."
816
+ echo " git said: ${mirror_url}" >&2
817
+ elif push_out=$(git push github HEAD 2>&1); then
818
+ echo "GitHub mirror: pushed $(git rev-parse HEAD) -> ${mirror_url}"
819
+ else
820
+ echo "WARN GitHub mirror PUSH FAILED: $(git rev-parse HEAD) is NOT on ${mirror_url}" >&2
821
+ echo "${push_out}" >&2
822
+ echo "WARN Mirror is stale — anything wired to it (site deploy) will not update." >&2
823
+ exit 1
824
+ fi
825
+ # --- github-mirror-push:end ---
786
826
  ```
787
827
 
828
+ State 3 exits non-zero on purpose: it is the only machine-readable signal that the mirror is
829
+ behind. Report it to the operator in the session summary; do not retry silently and do not
830
+ swallow it with `|| true`.
831
+
788
832
  ## Phase 4a: Auto-Promoted Worktree Cleanup (#575 P3.2)
789
833
 
790
834
  > Skip if `persistence: false` in Session Config. Skip silently if the current worktree is NOT an Auto-promoted sibling (the common case).
@@ -848,15 +892,24 @@ if (!promoted) {
848
892
  When the worktree is dirty (uncommitted, untracked, OR unpushed), render this AUQ via the coordinator's `AskUserQuestion` tool. The AUQ is coordinator-only — per `.claude/rules/ask-via-tool.md` AUQ-004, dispatched agents cannot call AUQ. Calling `git worktree remove --force` without explicit operator confirmation would violate PSA-003 (destructive action safeguards) — the dirty state may contain another session's work-in-progress or unmerged commits.
849
893
 
850
894
  ```js
895
+ // What is actually at stake, shown beside the options via `preview` (AUQ-006):
896
+ // the operator must see WHICH changes he would lose before he authorises the delete.
897
+ // Capped at 10 lines so the preview never outgrows the option list next to it.
898
+ const dirtyDetail = execFileSync('git', ['-C', promoted.wtPath, 'status', '--short', '--branch'], { encoding: 'utf8' })
899
+ .trim()
900
+ .split('\n')
901
+ .slice(0, 10)
902
+ .join('\n');
903
+
851
904
  AskUserQuestion({
852
905
  questions: [{
853
906
  question: `Auto-promoted worktree at ${promoted.wtPath} has uncommitted/untracked/unpushed changes. How should I proceed?`,
854
- header: "Worktree-Cleanup",
907
+ header: "Worktree",
855
908
  multiSelect: false,
856
909
  options: [
857
- { label: "Behalten (Recommended)", description: "Keep the worktree as-is. No cleanup. Review and remove manually later." },
858
- { label: "Löschen", description: "I confirm the changes are handled or expendable. Run 'git worktree remove --force' on the worktree." },
859
- { label: "Manuell", description: "Exit /close. I will inspect the worktree before re-running /close." },
910
+ { label: "Behalten (Recommended)", description: "Keeps the worktree exactly as it is nothing is deleted, and you can still remove it by hand later.", preview: `Stays on disk:\n${dirtyDetail}` },
911
+ { label: "Löschen", description: "I confirm the changes are handled or expendable. Run 'git worktree remove --force' on the worktree.", preview: `Deleted with the worktree:\n${dirtyDetail}` },
912
+ { label: "Manuell", description: "Exit /close. I will inspect the worktree before re-running /close.", preview: `You would inspect this first:\n${dirtyDetail}` },
860
913
  ],
861
914
  }],
862
915
  });
@@ -865,8 +918,8 @@ AskUserQuestion({
865
918
  **Codex CLI / Cursor IDE fallback** (numbered Markdown list):
866
919
 
867
920
  ```
868
- Worktree cleanup options:
869
- 1. **Behalten (Recommended)** — Keep the worktree as-is. No cleanup. Review and remove manually later.
921
+ Worktree cleanup options (the changes at stake are the `git status --short --branch` lines printed above):
922
+ 1. **Behalten (Recommended)** — Keeps the worktree exactly as it is; nothing is deleted, and you can still remove it by hand later.
870
923
  2. **Löschen** — I confirm the changes are handled or expendable. Run 'git worktree remove --force'.
871
924
  3. **Manuell** — Exit /close. I will inspect the worktree before re-running /close.
872
925
  Reply with the number of your choice.
@@ -984,16 +1037,38 @@ if (sweep) {
984
1037
  **Ordering (load-bearing):** run this as the LAST issue-creating action of Phase 5 — after step 3, after "Discovery Issue Creation", after step 4 — and re-read the counter file at that moment. Those steps can themselves push new entries into `overflow[]`; draining early would leave them unfiled.
985
1038
 
986
1039
  ```js
987
- import { readBudgetState, budgetStatePath } from '${PLUGIN_ROOT}/scripts/lib/issue-budget.mjs';
988
- const state = readBudgetState(repoRoot, sessionId); // { sessionId, count, exempt, overflow: [...] }
1040
+ import { readFileSync } from 'node:fs';
1041
+ import {
1042
+ readBudgetState,
1043
+ budgetStatePath,
1044
+ resolveIssueBudgetSessionId,
1045
+ } from '${PLUGIN_ROOT}/scripts/lib/issue-budget.mjs';
1046
+
1047
+ // `sessionId` is the physical raw lock/registry identity from session-start.
1048
+ const rawSessionId = sessionId;
1049
+ let currentSession = null;
1050
+ try {
1051
+ currentSession = JSON.parse(
1052
+ readFileSync(`${repoRoot}/.orchestrator/current-session.json`, 'utf8'),
1053
+ );
1054
+ } catch { /* no verified semantic accounting bridge */ }
1055
+ const accountingSessionId = resolveIssueBudgetSessionId(rawSessionId, currentSession);
1056
+ const state = readBudgetState(repoRoot, accountingSessionId);
1057
+ // { sessionId, count, exempt, overflow: [...] }
989
1058
  ```
990
1059
 
1060
+ `accountingSessionId` may be semantic only after
1061
+ `currentSession.session_id === rawSessionId`; this is budget accounting, not
1062
+ lock/registry ownership. When that proof is absent it remains the raw id.
1063
+ A host rotation that changes both raw and semantic values has no guaranteed
1064
+ budget continuity.
1065
+
991
1066
  - **`issue-budget.overflow: collect-issue` (default)** — create exactly ONE issue:
992
- - Title: `[Backlog-Sammel] <session-id>, <N> zurückgestellte Punkte`
1067
+ - Title: `[Backlog-Sammel] <accountingSessionId>, <N> zurückgestellte Punkte`
993
1068
  - Labels: `type::backlog`, `priority::low`
994
1069
  - Body: a Markdown checklist with one `- [ ]` line per `overflow[]` entry (`title` when present, otherwise the truncated `command`, plus its `at` timestamp).
995
1070
  - This collector issue is itself EXEMPT from the cap (`[Backlog-Sammel]` is in the exemption list in `scripts/lib/issue-budget.mjs`), so it always lands even at count == max.
996
- - **`issue-budget.overflow: vault-note`** — create NO issue. Write one Markdown file `vault/00-inbox/<session-id>-backlog-sammel.md` (path relative to `vault-integration.vault-dir`) with valid vault frontmatter and the same checklist body.
1071
+ - **`issue-budget.overflow: vault-note`** — create NO issue. Write one Markdown file `vault/00-inbox/<accountingSessionId>-backlog-sammel.md` (path relative to `vault-integration.vault-dir`) with valid vault frontmatter and the same checklist body.
997
1072
  - After the artefact exists, reset `overflow` to `[]` in the counter file and record the collector issue ID / note path in the Phase 6 Final Report under `### Zurückgestellt (issue-budget)`.
998
1073
  - **Never exempt-by-accident:** the cap never applied to `priority::critical`, the carryover class (`[Carryover]`, SPIRAL/FAILED, `type::carryover`), or `broken-window` closure issues, so nothing on the Phase 1.65 carry-list can ever appear in `overflow[]`. The promises at Phase 1.8 ("SPIRAL / FAILED agent carryover … non-deselectable") and the Critical Rule "ALWAYS create issues for unfinished PLANNED work" stay intact by construction.
999
1074
  - Fail-open: a missing or malformed counter file means "no overflow" — log a WARN and continue the close.
@@ -1092,7 +1167,7 @@ Present to the user:
1092
1167
  | `phase-3-7a-recommendations.md` § 3.7b | Phase 3.7b full procedural body — `withDurableCommit` invocation for `sessions.jsonl` + `STATE.md` (#490 AC2), `enabled:false` local no-op, autopilot.jsonl exclusion note |
1093
1168
  | (inline) Phase 3.7c | Vault Board → Closed (#674) — `mirrorBoard({ explicitStatus: 'closed' })` transitions this repo's board row to `closed`; gated on `vault-integration.enabled`, generator-marked + idempotent, non-blocking, ordered after 3.7b and before 3.7d/3.4/3.8 |
1094
1169
  | (inline) Phase 3.7d | Session-Eval (opt-in — #803) — `node scripts/eval-session.mjs --json` scores the just-closed session; gated on `eval.enabled` + `eval.mode != off` (parsed by `scripts/lib/config/eval.mjs`), optional `eval-judge` dispatch + `writeEvalReport`, advisory/never-blocks-close, ordered after 3.7 (record must exist) and before 3.4/Phase 4 (record committed with the session). Full flow in `skills/eval/SKILL.md` |
1095
- | (inline) Phase 3.8 | Session Lock Release — `release()` call, silent-OK on mismatch/absent, non-fatal on fs-error, ordering note (after STATE.md writes, before Phase 4 commit staging) |
1170
+ | (inline) Phase 3.8 | Session Lock Release — `release()` uses the physical raw `session_id`; raw mismatch/absent is non-fatal but never repaired with semantic labels or proof (live ambiguity remains for TTL/Reaper); fs-errors are non-fatal; runs after STATE.md writes and before Phase 4 commit staging |
1096
1171
 
1097
1172
  ## Anti-Patterns
1098
1173
 
@@ -185,4 +185,4 @@ Finalize session metrics by reading the wave data accumulated during execution:
185
185
  > - `open_questions_asked` / `open_questions_answered` / `open_questions_deferred` (#773): the three open-question counts from the Phase 1.65 gate's AUQ Call 2 (identical to the `questions_*` payload fields on the `orchestrator.handover.gated` event). Top-level, additive, non-negative integers. Populate ONLY when the gate ran an interactive triage ("Closen + Triage" path). OMIT all three (do NOT write `0`) when the gate was skipped (fail-open / headless / disabled) or took the fast-path — absent = "not measured", `0` = "measured, zero questions". Validator accepts absent/null/non-negative-integer.
186
186
  > - `stagnation_events`: populated ONLY when ≥1 stagnation event was logged to `events.jsonl` during this session. When `total == 0`, the field is omitted from the JSONL entry.
187
187
  > - `grounding_injections`: populated ONLY when ≥1 `orchestrator.grounding.injected` event was logged to `events.jsonl` during this session. When `count == 0`, the field is omitted from the JSONL entry.
188
- > - `memory_cleanup_at`: populated whenever `/memory-cleanup` ran **THIS session** in ANY mode dry-run, apply-pending, OR healthy no-op (MEMORY.md already healthy, no files mutated). Set `memory_cleanup_at = completed_at` so the auto-dream cadence marker (`readDreamSignals` → `lastCleanupAt`) advances and `shouldDispatchAutoDream` does not fire a false nudge. **A no-op is still a cleanup; the cadence marker MUST advance.** Use `stampMemoryCleanup(record, { ranCleanup: true, completedAt: record.completed_at })` from `scripts/lib/memory-cleanup-stamp.mjs` — this is the testable, no-throw seam that applies the stamp. Omit the field (do NOT set it to null) when `/memory-cleanup` did not run this session. (#699)
188
+ > - `memory_cleanup_at`: **derived by the writer, not supplied by the coordinator.** `scripts/emit-session.mjs` sets it to `completed_at` whenever an `orchestrator.memory.cleanup_completed` event for THIS session sits in `events.jsonl` — emitted by every `/memory-cleanup` run in ANY mode (dry-run, apply-pending, OR healthy no-op). **A no-op is still a cleanup; it still emits, so the cadence marker (`readDreamSignals` → `lastCleanupAt`) still advances and `shouldDispatchAutoDream` does not fire a false nudge.** No event field absent (never `null`). An explicit value already on the record wins and is not overwritten. Do NOT hand-call `stampMemoryCleanup()` here — the coordinator-supplied-boolean form was removed on 2026-08-17 after it silently failed for a real cleanup on 2026-08-14. (#699)
@@ -81,7 +81,25 @@ The proposals queue is populated mid-session by wave-executor agents calling `no
81
81
  }
82
82
  ```
83
83
 
84
- Then iterate `batches` and emit one `AskUserQuestion` per batch with `header: "Memory Confirm Proposals (Batch N of M)"`. Option label format: `[<type-12>] | <subject-40> | conf=X.XX`. Option description: `evidence: <first 60 chars of insight>`. `multiSelect: true`.
84
+ Then iterate `batches` and emit one `AskUserQuestion` per batch. The verbatim template is `agents/memory-proposal-collector.md` § AUQ Question Template keep the two in step:
85
+
86
+ ```javascript
87
+ AskUserQuestion({
88
+ questions: [{
89
+ header: "Memory",
90
+ question: "Batch <N> of <M> — which of these learnings should be stored permanently?",
91
+ options: [
92
+ // one entry per proposal in this batch (max 4)
93
+ // label + description formats are LOCKED by D3 — see that file, do not restate them here
94
+ { label: "[type ] | subject(40) | conf=X.XX", description: "evidence: <first 60 chars of insight>" },
95
+ ...
96
+ ],
97
+ multiSelect: true
98
+ }]
99
+ })
100
+ ```
101
+
102
+ The batch counter moved out of `header` and into the question because `header` is cut off after 12 characters — `Memory — Confirm Proposals (Batch N of M)` reached the operator as `Memory — Con`.
85
103
 
86
104
  5. After all batches answered, partition the queue into `approved` (any option selected across all batches) and `rejected` (all unselected).
87
105
 
@@ -337,7 +355,24 @@ After the auto-dialectic nudge decision is made (Phase 3.6.7), and when the reco
337
355
  }
338
356
  ```
339
357
 
340
- Iterate `batches` and emit one `AskUserQuestion` per batch with `header: "Reconciliation — Confirm Rule Proposals (Batch N of M)"`. Option label format: `<slug-40> | conf=<confidence>`. Option description: first 80 chars of the rendered `content` (the rule prose preview). `multiSelect: true`.
358
+ Iterate `batches` and emit one `AskUserQuestion` per batch:
359
+
360
+ ```javascript
361
+ AskUserQuestion({
362
+ questions: [{
363
+ header: "Regeln",
364
+ question: "Batch <N> of <M> — which rule proposals should be written into .claude/rules/?",
365
+ options: [
366
+ // one entry per proposal in this batch (max 4)
367
+ { label: "<slug-40>", description: "Confidence <confidence>. First 80 chars of the rendered rule text: <…>" },
368
+ ...
369
+ ],
370
+ multiSelect: true
371
+ }]
372
+ })
373
+ ```
374
+
375
+ The batch counter moved out of `header` and into the question because `header` is cut off after 12 characters — `Reconciliation — Confirm Rule Proposals (Batch N of M)` reached the operator as `Reconciliati`. The rendered `content` shown in the description is the rule prose that will land on disk.
341
376
 
342
377
  6. After all batches are answered, partition proposals into `approved` (any option selected across all batches) and `rejected` (all unselected). Proposals the operator rejected join the engine's `rejected` array for archival.
343
378
 
@@ -19,19 +19,13 @@
19
19
 
20
20
  1. Ensure `.orchestrator/metrics/` directory exists: `mkdir -p .orchestrator/metrics`
21
21
 
22
- 1-pre. **`memory_cleanup_at` always-stamp (#699)** — Before emitting the record, if `/memory-cleanup` ran THIS session in any mode (dry-run, apply-pending, OR healthy no-op), stamp `memory_cleanup_at = completed_at` on the record using `stampMemoryCleanup()` from `scripts/lib/memory-cleanup-stamp.mjs`:
22
+ 1-pre. **`memory_cleanup_at` is DERIVED, not remembered (#699 + 2026-08-17 follow-up)** — there is **no coordinator step here any more**. Do not set a `ranMemoryCleanupThisSession` boolean and do not call `stampMemoryCleanup()` by hand at session-end.
23
23
 
24
- ```js
25
- import { stampMemoryCleanup } from '../../scripts/lib/memory-cleanup-stamp.mjs';
24
+ `scripts/emit-session.mjs` derives the field itself: it calls `deriveMemoryCleanupSignal()` (`scripts/lib/memory-cleanup-stamp.mjs`), which reads the sibling `events.jsonl` for `orchestrator.memory.cleanup_completed` records whose `timestamp` falls inside this session's own `[started_at, completed_at]` window (and whose `semantic_session_id`, when present, matches). The emitting side is the LAST step of every `/memory-cleanup` run — see `skills/memory-cleanup/SKILL.md` § "Session-End Signal".
26
25
 
27
- // ranCleanup: true when /memory-cleanup ran this session (ANY outcome)
28
- metricsEntry = stampMemoryCleanup(metricsEntry, {
29
- ranCleanup: ranMemoryCleanupThisSession, // boolean
30
- completedAt: metricsEntry.completed_at,
31
- });
32
- ```
26
+ **Contract:** a no-op run (MEMORY.md already healthy, no files mutated) is still a cleanup, so it still emits and therefore still stamps. When `/memory-cleanup` did not run, no event exists, nothing is derived, and the field is simply absent — never `null`. An EXPLICIT `memory_cleanup_at` already present on the record WINS over derivation and is never overwritten; that path is for backfills and tests, not for normal operation.
33
27
 
34
- **Contract:** A no-op run (MEMORY.md already healthy, no files mutated) is still a cleanup `memory_cleanup_at` MUST be stamped. This advances `readDreamSignals` `lastCleanupAt` in `auto-dream.mjs` so `shouldDispatchAutoDream` does not fire a false nudge. When `/memory-cleanup` did not run, omit the field entirely (do NOT set null). The helper is no-throw and pure it returns the record unchanged on bad inputs.
28
+ **Why this stopped being a coordinator instruction.** It was one until 2026-08-17, and it measurably failed: a `/memory-cleanup` ran on 2026-08-14 with a documented yield, the prose step above was not executed, and all three session records of that day carried `memory_cleanup_at: null` so the session-start banner reported "last cleanup 29 days ago" against the operator's own "3 days". `stampMemoryCleanup()` had **zero production callers** at that point; every reference to it was an instruction asking an LLM to remember. Same failure class as the STATE.md write-race Epic #583 replaced with a lock: Disziplin statt Mechanik.
35
29
 
36
30
  > **#701.2 DOC NOTE — `completed_at >= started_at` guard:** This invariant is enforced mechanically by `scripts/emit-session.mjs`. The writer applies `clampTimestampsMonotonic()` (from `scripts/lib/session-schema/timestamps.mjs`) before `validateSession()`, clamping any inversion of `completed_at < started_at` to `started_at` and recording forensics in `_clamped: true` / `_original_completed_at`. Previously-inverted entries (e.g. `main-2026-06-21-session-4`) are already corrected. **No per-session coordinator action is needed** — the writer enforces the invariant at write time. Do not add defensive clamping logic here; the canonical guard lives in `emit-session.mjs`.
37
31
 
@@ -275,7 +275,7 @@ mission-status:
275
275
  - `status`: always `brainstormed` at plan emission. Terminal values are updated at gate transitions by wave-executor: `brainstormed` → `validated` (user confirms via `/go`) → `in-dev` (agent dispatched) → `testing` (Quality wave) → `completed` (Quality gate green). session-end Phase 1.9 reads the current value to classify the item.
276
276
 
277
277
  **Transition gates (summary):**
278
- At plan time, all items start at `brainstormed`. When the user runs `/go` to approve the plan, wave-executor updates each item to `validated`. When an agent for a wave-plan item is dispatched, wave-executor updates that item to `in-dev`. When the Quality wave begins, items from prior waves move to `testing`. When the Quality gate passes, items finalize at `completed`. Rollback to `brainstormed` is permitted from any state. All transitions are validated against the schema in `scripts/lib/mission-status-schema.mjs`.
278
+ At plan time, all items start at `brainstormed`. When the user runs `/go` to approve the plan, wave-executor updates each item to `validated`. When an agent for a wave-plan item is dispatched, wave-executor updates that item to `in-dev`. When the Quality wave begins, items from prior waves move to `testing`. When the Quality gate passes, items finalize at `completed`. Rollback to `brainstormed` is permitted from any state. This ordering is **coordinator convention, not a mechanical gate** — nothing validates a transition before it is written (see "Default and transitions" below).
279
279
 
280
280
  **Omission rule:** When the plan has 0 wave-plan items (e.g., pure express-path coord-direct with no sub-agent tasks), do NOT emit the `### Wave-Plan Mission Status (machine-readable)` block.
281
281
 
@@ -298,7 +298,7 @@ Every wave-plan item carries a `status` field drawn from a 5-value enum. The fie
298
298
  - **Default at plan creation:** `brainstormed` — all items start here.
299
299
  - **Transitions are coordinator-level orchestration** (not inside individual agent prompts). See `skills/wave-executor/SKILL.md` "Mission-Status Updates (#340)" for when each transition fires.
300
300
  - **Rollback:** any item may return to `brainstormed` from any state (e.g. if work is discarded or re-planned).
301
- - **Schema validation:** transitions are validated against `scripts/lib/mission-status-schema.mjs` before being written to STATE.md.
301
+ - **No mechanical validation by design.** The `status` values come from the 5-value enum in the table above, but nothing checks a transition before it is written. `setMissionStatus` (`scripts/lib/state-md/mission-status.mjs`) mirrors whatever string it is handed onto BOTH the body section and the frontmatter array, deliberately without an enum gate: gating it would reintroduce the exact body-says-X/frontmatter-says-Y divergence that sync exists to remove. An out-of-enum value therefore lands visibly on both surfaces instead of being silently rejected on one. Keeping the enum honest is the coordinator's job.
302
302
 
303
303
  #### Status field in wave-plan items
304
304
 
@@ -18,7 +18,7 @@ For each wave, define agents with:
18
18
 
19
19
  - `Isolation: worktree` means the wave-executor will pass `isolation: "worktree"` to the Agent tool, giving each agent its own git worktree copy
20
20
  - `MaxTurns` is enforced via the agent prompt — wave-executor includes a turn limit instruction in each agent's prompt
21
- - `status` is the mission-status enum value for this wave-plan item (#340). Always `brainstormed` in the initial plan. Wave-executor updates it at gate transitions (validated → in-dev → testing → completed). Values are validated against `scripts/lib/mission-status-schema.mjs`. Rollback to `brainstormed` is allowed from any state.
21
+ - `status` is the mission-status enum value for this wave-plan item (#340). Always `brainstormed` in the initial plan. Wave-executor updates it at gate transitions (validated → in-dev → testing → completed). Rollback to `brainstormed` is allowed from any state. The five values are listed in `SKILL.md` § Mission-Status Enum; nothing validates them mechanically — `setMissionStatus` writes the string it is given to both STATE.md surfaces on purpose, so keeping the value in-enum is the coordinator's job.
22
22
 
23
23
  > **Deconfliction rule:** Before finalizing agent specs for a wave, verify that no two agents in the same wave list overlapping `Files:` paths. If overlap is found, either merge the agents into one or move one task to a later wave. Two agents editing the same file in parallel causes merge conflicts that require manual resolution.
24
24