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
@@ -1,6 +1,15 @@
1
1
  ---
2
2
  name: dispatcher
3
- description: Use when you want the orchestrator to pick the next repo to work on across your whole portfolio — it enumerates candidate repos below the confinement root, resolves free/busy from each repo's session.lock lease, ranks the FREE ones by backlog priority × staleness × readiness, recommends the single most worthwhile one via AskUserQuestion, atomically claims it, and routes you to the chosen entry command. Triggers: "what should I work on next", "dispatch me to a repo", "pick the next project", "run /dispatcher". <example>Context: operator finished a session and wants the next-best repo across the portfolio. user: "/dispatcher" assistant: "Ranked 18 free repos — top recommendation: Pencil-Designs (score 4.50, 90d stale). Confirm via the picker, I'll claim its lease atomically, then route you to /session deep."</example>
3
+ description: >
4
+ Use when you want the orchestrator to pick the next repo to work on across your whole portfolio — it
5
+ enumerates candidate repos below the confinement root, resolves free/busy from each repo's session.lock
6
+ lease, ranks the FREE ones by backlog priority × staleness × readiness, recommends the single most
7
+ worthwhile one via AskUserQuestion, atomically claims it, and routes you to the chosen entry command.
8
+ Triggers: "what should I work on next", "dispatch me to a repo", "pick the next project", "run
9
+ /dispatcher". <example>Context: operator finished a session and wants the next-best repo across the
10
+ portfolio. user: "/dispatcher" assistant: "Ranked 18 free repos — top recommendation: Pencil-Designs
11
+ (score 4.50, 90d stale). Confirm via the picker, I'll claim its lease atomically, then route you to
12
+ /session deep."</example>
4
13
  model: sonnet
5
14
  ---
6
15
 
@@ -0,0 +1,43 @@
1
+ ---
2
+ name: eli5
3
+ description: Explain a topic like I'm a 5 year old — restate my last output, or a named topic, in plain words without dropping a single fact. Use when the user types /eli5 [topic], or says an answer was too technical, too long, or unclear about what he now has to do.
4
+ model: inherit
5
+ tools: Read, Grep, Glob, Bash
6
+ ---
7
+
8
+ # eli5
9
+
10
+ Say it again in plain words. Same facts, in the order he needs them.
11
+
12
+ ## The frame
13
+
14
+ **Write for someone who knows this project but has not seen what you just saw.**
15
+
16
+ He owns this repo. He is not missing knowledge, he is missing observation — he did not watch the command run or read the file it points at. Write across, not down. `.claude/rules/ask-via-tool.md` § AUQ-006 and `skills/session-start/soul.md` § Register are the canonical statement of this register; apply them, do not restate them.
17
+
18
+ ## What to restate
19
+
20
+ - `/eli5` — my last substantial output in this conversation. If there is none yet, say so instead of inventing a topic.
21
+ - `/eli5 <topic>` — that topic (`$ARGUMENTS`), grounded in what THIS session already measured: name the file, command, or record it came from. If it was never measured here, say that rather than filling the gap from memory.
22
+
23
+ Answer in the operator's language: `owner.language` in `~/.config/session-orchestrator/owner.yaml`, falling back to `en` when that file is missing, unreadable, or the key is absent — and follow the operator's own language the moment he writes in another one.
24
+
25
+ ## Two limits, and they are the whole skill
26
+
27
+ **1. Say more simply what actually happens — introduce nothing that does not exist.**
28
+ Test: delete every noun the system does not contain. Sentence still true and complete → it was no analogy. Sentence collapses → describe what actually happens instead.
29
+ ✓ "Waiting means the other session finishes first." ✗ "Think of the session as a level crossing."
30
+
31
+ **2. Simplifying removes words, never facts.** If a path, a number, an error code, an identifier, or an instruction to act disappears, that is data loss, not simplification. The mechanical decider: **could the token you are about to cut ever appear in a `grep`? Then it stays.** `skills/session-start/soul.md` § "Never traded for brevity" outranks every brevity instruction in this file.
32
+
33
+ ## Shape
34
+
35
+ Line 1 answers the question he actually has: **do I have to do something now, and what happens if I don't?** Then the facts, in the order he needs them — not the order you found them.
36
+
37
+ This is a terminal, so there is no picture to draw. The second channel is the `preview` field on an `AskUserQuestion` option: when the options differ in something literal — a diff, a title, a config block, a file list — put that text there and he reads the thing instead of a description of it.
38
+
39
+ ## What this is not
40
+
41
+ - **Not shorter by default.** Eight lines before may be eight lines after. Reorder first; cut only filler.
42
+ - **Not a children's explanation.** The label is a poster, the reader is an expert who was not in the room.
43
+ - **Not a second attempt at the answer.** If the first answer was wrong, fix the answer — `/eli5` restates, it never re-derives.
@@ -248,8 +248,8 @@ Present extracted patterns to the user for confirmation. Use AskUserQuestion wit
248
248
  ```
249
249
  AskUserQuestion({
250
250
  questions: [{
251
- question: "Which learnings should be saved?\n\nExtracted patterns from session history:",
252
- header: "Evolve — Confirm Learnings",
251
+ question: "Which of the patterns extracted from this session's history should be saved?",
252
+ header: "Speichern?",
253
253
  options: [
254
254
  {
255
255
  label: "[type] subject",
@@ -455,19 +455,18 @@ Use AskUserQuestion with options:
455
455
  AskUserQuestion({
456
456
  questions: [{
457
457
  question: "What would you like to do with your learnings?",
458
- header: "Evolve — Review",
458
+ header: "Learnings",
459
459
  options: [
460
- { label: "Boost confidence", description: "Select learnings to boost (+0.15)" },
461
- { label: "Reduce confidence", description: "Select learnings to reduce (-0.2)" },
462
- { label: "Delete specific learnings", description: "Select learnings to remove" },
463
- { label: "Extend expiry", description: "Reset expires_at by learning-expiry-days from now" },
464
- { label: "Done — no changes", description: "Exit without changes" }
460
+ { label: "Confidence ändern", description: "Pick the learnings, then the direction: +0.15 or -0.2. Cheapest fix when a learning is merely mis-weighted." },
461
+ { label: "Ablauf verlängern", description: "Keeps a still-useful learning alive: its expiry date moves to today plus the configured window. Confidence is untouched." },
462
+ { label: "Delete specific learnings", description: "Takes the selected learnings out of the store. They are archived rather than shredded, but they stop influencing anything." },
463
+ { label: "Done — no changes", description: "Leaves the store exactly as it is and ends the review. Nothing is written." }
465
464
  ]
466
465
  }]
467
466
  })
468
467
  ```
469
468
 
470
- If user selects "Boost confidence", "Reduce confidence", "Delete specific learnings", or "Extend expiry", present a follow-up AskUserQuestion with `multiSelect: true` listing all learnings by `# | type | subject` so the user can select which ones to modify.
469
+ If user selects "Confidence ändern", "Ablauf verlängern", or "Delete specific learnings", present a follow-up AskUserQuestion with `multiSelect: true` listing all learnings by `# | type | subject` so the user can select which ones to modify. For "Confidence ändern" the same follow-up also asks for the direction — **Boost** (+0.15) or **Reduce** (-0.2). Both operations are unchanged; only the point at which the direction is chosen moved, because a single AskUserQuestion accepts at most 4 options and the previous list had 5.
471
470
 
472
471
  > On Codex CLI where AskUserQuestion is unavailable, present as a numbered Markdown list.
473
472
 
@@ -1,6 +1,14 @@
1
1
  ---
2
2
  name: frontmatter-guard
3
- description: Injects the canonical vault frontmatter schema snippet into agent prompts before any vault-write task, preventing malformed YAML frontmatter in Obsidian notes. <example>Context: wave-executor is about to dispatch a vault-mirror agent that writes learning notes under ~/Projects/vault/40-learnings/. user: "dispatch vault-write agent" assistant: "Injecting frontmatter-guard snippet into agent prompt (vault scope detected). Required fields: id, type, created, updated. Enum type: note|daily|project|person|reference|idea|learning|session." <commentary>The wave-executor pre-dispatch hook calls detectVaultTaskScope() — the fileScope contains /Projects/vault/40-learnings/ so the guard triggers and the snippet is prepended to the agent system prompt.</commentary></example>
3
+ description: >
4
+ Injects the canonical vault frontmatter schema snippet into agent prompts before any vault-write task,
5
+ preventing malformed YAML frontmatter in Obsidian notes. <example>Context: wave-executor is about to
6
+ dispatch a vault-mirror agent that writes learning notes under ~/Projects/vault/40-learnings/. user:
7
+ "dispatch vault-write agent" assistant: "Injecting frontmatter-guard snippet into agent prompt (vault
8
+ scope detected). Required fields: id, type, created, updated. Enum type:
9
+ note|daily|project|person|reference|idea|learning|session." <commentary>The wave-executor pre-dispatch
10
+ hook calls detectVaultTaskScope() — the fileScope contains /Projects/vault/40-learnings/ so the guard
11
+ triggers and the snippet is prepended to the agent system prompt.</commentary></example>
4
12
  model: inherit
5
13
  ---
6
14
 
@@ -6,7 +6,15 @@ model: haiku
6
6
  model-preference: sonnet
7
7
  model-preference-codex: gpt-5.4-mini
8
8
  model-preference-cursor: claude-sonnet-4-6
9
- description: Use this skill when performing VCS operations on GitLab or GitHub repositories — creating, updating, or closing issues and MRs, applying label taxonomy, running `glab`/`gh` CLI commands, or resolving project IDs dynamically. Acts as the single source of truth for CLI command syntax and label conventions; consuming skills reference this rather than duplicating logic. Triggers: "create a GitLab issue", "list open MRs", "apply priority label", "how do I resolve the project ID", "what's the carryover issue template". <example>Context: session-end needs to file a carryover issue for an incomplete task. user: "/close" assistant: "Creating carryover issue via glab with the Carryover Template from gitlab-ops — labels: carryover, priority::high."</example>
9
+ description: >
10
+ Use this skill when performing VCS operations on GitLab or GitHub repositories — creating, updating, or
11
+ closing issues and MRs, applying label taxonomy, running `glab`/`gh` CLI commands, or resolving project
12
+ paths dynamically. Acts as the single source of truth for CLI command syntax and label conventions;
13
+ consuming skills reference this rather than duplicating logic. Triggers: "create a GitLab issue", "list
14
+ open MRs", "apply priority label", "how do I resolve the project ID", "what's the carryover issue
15
+ template". <example>Context: session-end needs to file a carryover issue for an incomplete task. user:
16
+ "/close" assistant: "Creating carryover issue via glab with the Carryover Template from gitlab-ops —
17
+ labels: carryover, priority::high."</example>
10
18
  ---
11
19
 
12
20
  # VCS Operations Reference
@@ -37,7 +45,7 @@ syntax inline. This skill is the single source of truth for all VCS operations.
37
45
  When a skill needs VCS operations, include this reference block in its instructions:
38
46
 
39
47
  > **VCS Reference:** Detect the VCS platform per the "VCS Auto-Detection" section of the gitlab-ops skill.
40
- > Use CLI commands per the "Common CLI Commands" section. For cross-project queries, see "Dynamic Project Resolution."
48
+ > Use CLI commands per the "Common CLI Commands" section. For GitLab API operations, see "Canonical Project Identity."
41
49
 
42
50
  **Canonical commands:** All `glab` and `gh` command syntax — flags, output formats,
43
51
  pagination options — is defined in the "Common CLI Commands" section below. Consuming
@@ -49,34 +57,35 @@ command variant not listed there, add it to this file first, then reference it.
49
57
  - Any skill-specific *parameters* they pass to commands (e.g., label names, issue templates)
50
58
  - They should NOT include raw `glab`/`gh` invocations or detection snippets
51
59
 
52
- ## Dynamic Project Resolution
60
+ ## Canonical Project Identity
53
61
 
54
- Never hardcode project IDs. Resolve them at runtime and re-resolve live each session; never cache a project ID across sessions (a stale ID silently targets the wrong project on rename/fork/mirror-drift, and is the root cause behind the close-verification incident documented below).
62
+ GitLab REST endpoints accept a URL-encoded `namespace/project` path. Select the GitLab host and project path explicitly; never derive a numeric project ID from `glab repo view`, search `projects?search=`, or use `:id` placeholders. Those forms can resolve through the ambient working directory or a stale search result and target another project after a rename, fork, or scaffold.
55
63
 
56
- ### Current project
64
+ Set the identity once per operation sequence and reuse the encoded identifier without encoding it again:
57
65
 
58
66
  ```bash
59
- # GitLab — get numeric project ID
60
- glab repo view --output json | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])"
67
+ GITLAB_HOST="<selected GitLab hostname>"
68
+ GROUP_PATH="<selected group path>"
69
+ PROJECT_NAME="<selected project name>"
70
+ PROJECT_PATH="$GROUP_PATH/$PROJECT_NAME"
71
+ ENCODED_PROJECT_PATH="$(node -e 'process.stdout.write(encodeURIComponent(process.argv[1]))' "$PROJECT_PATH")"
72
+ ```
61
73
 
62
- # GitHub get owner/name identifier
63
- gh repo view --json nameWithOwner -q '.nameWithOwner'
74
+ For a link target in another project, use the same path-first shape instead of a numeric ID:
75
+
76
+ ```bash
77
+ TARGET_PROJECT_PATH="<target namespace>/<target project>"
78
+ TARGET_ENCODED_PROJECT_PATH="$(node -e 'process.stdout.write(encodeURIComponent(process.argv[1]))' "$TARGET_PROJECT_PATH")"
64
79
  ```
65
80
 
66
- ### Cross-project queries
81
+ Pass `--hostname "$GITLAB_HOST"` to every `glab api` call. The endpoint itself then pins the project, including directly after creating a repository when the current directory does not yet identify the new project.
67
82
 
68
- When a skill needs to reference other projects (e.g., from `cross-repos` in Session Config):
83
+ GitHub continues to use an `owner/repo` slug; `gh repo` takes it positionally and rejects `-R`:
69
84
 
70
85
  ```bash
71
- # GitLab resolve project ID by name
72
- glab api "projects?search=<project-name>" | python3 -c "import json,sys; [print(p['id'], p['path_with_namespace']) for p in json.load(sys.stdin)]"
73
-
74
- # GitHub — resolve repo details
75
- gh api "repos/<owner>/<name>" --jq '.full_name'
86
+ gh repo view --json nameWithOwner -q '.nameWithOwner'
76
87
  ```
77
88
 
78
- **Note:** Some API calls require numeric project IDs (GitLab) or `owner/repo` slugs (GitHub). Always resolve dynamically from the project name.
79
-
80
89
  ### Canonical enumeration pattern
81
90
 
82
91
  To enumerate ALL projects (or issues) in a group, a single page is never the whole result — paginate and guard against silent truncation:
@@ -85,7 +94,7 @@ To enumerate ALL projects (or issues) in a group, a single page is never the who
85
94
  # GitLab — paginate a group's projects, following x-next-page until empty
86
95
  page=1
87
96
  while [ -n "$page" ]; do
88
- resp=$(glab api "groups/<group-id>/projects?include_subgroups=true&per_page=100&page=$page" --include)
97
+ resp=$(glab api --hostname "$GITLAB_HOST" "groups/<group-id>/projects?simple=true&include_subgroups=true&per_page=100&page=$page" --include)
89
98
  # parse the response body ($resp) for project ids/paths here, deduping by id.
90
99
  # Then advance by reading the `x-next-page` response header — an empty value
91
100
  # means this was the last page, so the loop exits (the guard above is what breaks).
@@ -138,13 +147,16 @@ done
138
147
 
139
148
  ## Issue Linking (`blocks` / `is_blocked_by`)
140
149
 
141
- GitLab's native issue-link types `blocks` and `is_blocked_by` (`glab api -X POST projects/:id/issues/:issue_iid/links -f link_type=blocks|is_blocked_by`) are a **Premium/Ultimate license feature**. On a Free/Core-tier GitLab instance this call returns **HTTP 403** — a license-gate signal, not an auth/permission failure. Do not retry with different credentials or escalate as an auth bug.
150
+ GitLab's native issue-link types `blocks` and `is_blocked_by` (`glab api --silent --hostname "$GITLAB_HOST" -X POST "projects/${ENCODED_PROJECT_PATH}/issues/${ISSUE_IID}/links" -f target_project_id="$TARGET_ENCODED_PROJECT_PATH" -f target_issue_iid="$OTHER_ISSUE_IID" -f link_type="$LINK_TYPE"`) are a **Premium/Ultimate license feature**. Set `LINK_TYPE` to `blocks` or `is_blocked_by`; the target accepts an encoded project path, so no numeric project ID is needed. On a Free/Core-tier GitLab instance this call returns **HTTP 403** — a license-gate signal, not an auth/permission failure. Do not retry with different credentials or escalate as an auth bug.
142
151
 
143
152
  **Fallback (non-Premium instances):**
144
153
  1. **Use `relates_to` instead** — `link_type=relates_to` is available on every GitLab tier (no ordering semantics, just an unscoped relation). Same API shape, only the `link_type` value changes:
145
154
  ```bash
146
- glab api -X POST "projects/:id/issues/:issue_iid/links" \
147
- -f target_project_id=:id -f target_issue_iid=:other_iid -f link_type=relates_to
155
+ glab api --silent --hostname "$GITLAB_HOST" -X POST \
156
+ "projects/${ENCODED_PROJECT_PATH}/issues/${ISSUE_IID}/links" \
157
+ -f target_project_id="$TARGET_ENCODED_PROJECT_PATH" \
158
+ -f target_issue_iid="$OTHER_ISSUE_IID" \
159
+ -f link_type=relates_to
148
160
  ```
149
161
  2. **Document the blocking semantics in the issue body** — since `relates_to` carries no ordering meaning, add an explicit ordering note to both issues, e.g. `⚠ Ordering: erst #<blocker_iid>, dann dieses Issue — blocks-Link nicht verfügbar (non-Premium)`.
150
162
  3. **Recognize the 403 as a license signal, not an auth error** — before assuming a token/scope problem, try `relates_to` on the same project pair: if `relates_to` succeeds where `blocks`/`is_blocked_by` 403s, the license gate — not authentication — is the cause.
@@ -153,38 +165,40 @@ GitHub has no native issue-blocking relation at all — the body-ordering-note f
153
165
 
154
166
  ## Common CLI Commands
155
167
 
168
+ **Directive — consult this only for a command NOT listed below; every example here already complies.** Each repo-scoped `glab`/`gh` invocation carries `-R <OWNER>/<REPO>` (`glab` also accepts `GROUP/SUBGROUP/REPO` or a full remote URL — `resolveRepoSpec()` in `scripts/lib/vcs-repo-spec.mjs` produces the right spec per platform); without the flag the target is whatever the ambient cwd remote happens to be, which is the wrong project in a sibling worktree, an `/autopilot` child, or a fork. Exactly four exceptions, each probed against the binaries: `glab api`/`gh api` (no `--repo` exists — pin the host with `--hostname` from `resolveRepoHost()` instead), `gh repo <*>` (rejects `-R`; takes the repository positionally), a `glab repo` call that already names the repository positionally, and — conditionally, not subcommand-wide — `gh pr checks|view|diff|ready|merge|comment`, where `-R` is legal ONLY alongside the `<number>|<url>|<branch>` positional: `gh pr checks -R <OWNER>/<REPO> <BRANCH>` carries the flag, while a positional-less `gh pr checks -R <OWNER>/<REPO> --watch` exits 1 with `argument required when using the` `--repo` `flag` — so name the PR or drop the flag, and never derive this from `--help`, which lists `-R` under INHERITED FLAGS with no such qualifier.
169
+
156
170
  ### GitLab (glab)
157
171
 
158
172
  ```bash
159
173
  # Issues
160
- glab issue list --per-page 50 # All open issues
161
- glab issue list --label "status:ready" --per-page 10 # Ready to work on
162
- glab issue list --label "priority::high" --per-page 10 # High priority
163
- glab issue list --closed --per-page 10 # Recently closed
164
- glab issue view <IID> # View issue details
165
- glab issue view <IID> --comments # With comments
166
- glab issue create --title "title" --label "priority::high,status:ready"
167
- glab issue update <IID> --label "status:in-progress" # WARNING: --label REPLACES the full set — see caveat below
168
- glab issue close <IID> # then VERIFY: glab issue view <IID> must show state=closed
169
- glab issue note <IID> -m "Comment text" # Add comment
174
+ glab issue list -R <OWNER>/<REPO> --per-page 50 # All open issues
175
+ glab issue list -R <OWNER>/<REPO> --label "status:ready" --per-page 10 # Ready to work on
176
+ glab issue list -R <OWNER>/<REPO> --label "priority::high" --per-page 10 # High priority
177
+ glab issue list -R <OWNER>/<REPO> --closed --per-page 10 # Recently closed
178
+ glab issue view -R <OWNER>/<REPO> <IID> # View issue details
179
+ glab issue view -R <OWNER>/<REPO> <IID> --comments # With comments
180
+ glab issue create -R <OWNER>/<REPO> --title "title" --label "priority::high,status:ready"
181
+ glab issue update -R <OWNER>/<REPO> <IID> --label "status:in-progress" # WARNING: --label REPLACES the full set — see caveat below
182
+ glab issue close -R <OWNER>/<REPO> <IID> # then VERIFY: re-read the issue; it must show state=closed
183
+ glab issue note -R <OWNER>/<REPO> <IID> -m "Comment text" # Add comment
170
184
 
171
185
  # MRs
172
- glab mr list # Open MRs
173
- glab mr create --fill --draft # Create draft MR
174
- glab mr merge <MR_IID> # Merge MR
186
+ glab mr list -R <OWNER>/<REPO> # Open MRs
187
+ glab mr create -R <OWNER>/<REPO> --fill --draft # Create draft MR
188
+ glab mr merge -R <OWNER>/<REPO> <MR_IID> # Merge MR
175
189
 
176
190
  # Pipelines
177
- glab pipeline list --per-page 5 # Recent pipelines
178
- glab pipeline status <ID> # Pipeline details
191
+ glab pipeline list -R <OWNER>/<REPO> --per-page 5 # Recent pipelines
192
+ glab pipeline status -R <OWNER>/<REPO> <ID> # Pipeline details
179
193
 
180
- # API (reads host from git remote automatically)
181
- glab api "projects/$(glab repo view --output json | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])")/issues?state=opened&per_page=50"
182
- glab api "projects/$(glab repo view --output json | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])")/milestones?state=active"
194
+ # API (no --repo exists here — the encoded endpoint and explicit host identify the target)
195
+ glab api --hostname "$GITLAB_HOST" "projects/${ENCODED_PROJECT_PATH}/issues?state=opened&per_page=50"
196
+ glab api --hostname "$GITLAB_HOST" "projects/${ENCODED_PROJECT_PATH}/milestones?state=active"
183
197
  ```
184
198
 
185
199
  **Label update caveat (PUT-replaces, not additive):** `glab issue update --label` (and the underlying GitLab labels API) PUT-REPLACES the entire label set — it does not add to the existing set. To change a single label you must pass the FULL desired label list, or use the dedicated add/remove operations, which are themselves unreliable across `glab` versions. Preferred safe pattern: use `--label` (adds) together with `--unlabel` (removes) on `glab issue update` when your installed `glab` version supports both; otherwise read the current labels first, compute the full new set, and PUT once. The same PUT-replace semantics apply to `glab mr update --label`.
186
200
 
187
- **Close verification:** after `glab issue close <IID>`, always verify the close actually landed — re-read the issue (`glab issue view <IID>`) and confirm `state: closed` in the output. A stale/wrong project ID or a silent 404 can report local success while closing nothing; a documented incident closed 32 issues into the void this way (project ID pointed at the wrong project see "Dynamic Project Resolution" above for the re-resolve-each-session rule that prevents it).
201
+ **Close verification:** after `glab issue close <IID>`, always verify the close actually landed — re-read the issue (`glab issue view <IID>`) and confirm `state: closed` in the output. A stale or wrong project path, or a silent 404, can report local success while closing nothing; use the canonical project identity above for API operations rather than resolving a numeric ID.
188
202
 
189
203
  **Commit-body close-keyword footgun:** GitLab (and GitHub) auto-close an issue when a commit pushed to the default branch contains a close keyword — `close`/`closes`/`closed`/`fix`/`fixes`/`fixed`/`resolve`/`resolves`/`resolved` — followed by `#N` ANYWHERE in the commit body, not just the subject line. This fires even inside a negation ("does NOT close #N") — the platform pattern-matches the keyword + issue reference; it does not parse English negation, so the negation offers no protection. Rule: when a commit body needs to MENTION an issue without closing intent, always use a non-closing reference — `refs #N`, `part of #N`, `siehe #N` — never a close-keyword verb next to the number, negated or not.
190
204
 
@@ -194,27 +208,27 @@ glab api "projects/$(glab repo view --output json | python3 -c "import json,sys;
194
208
 
195
209
  ```bash
196
210
  # Issues
197
- gh issue list --limit 50 # All open issues
198
- gh issue list --label "status:ready" --limit 10 # Ready to work on
199
- gh issue list --label "priority::high" --limit 10 # High priority
200
- gh issue list --state closed --limit 10 # Recently closed
201
- gh issue view <NUMBER> # View issue details
202
- gh issue view <NUMBER> --comments # With comments
203
- gh issue create --title "title" --label "priority::high,status:ready"
204
- gh issue edit <NUMBER> --add-label "status:in-progress"
205
- gh issue close <NUMBER>
206
- gh issue comment <NUMBER> --body "Comment text" # Add comment
211
+ gh issue list -R <OWNER>/<REPO> --limit 50 # All open issues
212
+ gh issue list -R <OWNER>/<REPO> --label "status:ready" --limit 10 # Ready to work on
213
+ gh issue list -R <OWNER>/<REPO> --label "priority::high" --limit 10 # High priority
214
+ gh issue list -R <OWNER>/<REPO> --state closed --limit 10 # Recently closed
215
+ gh issue view -R <OWNER>/<REPO> <NUMBER> # View issue details
216
+ gh issue view -R <OWNER>/<REPO> <NUMBER> --comments # With comments
217
+ gh issue create -R <OWNER>/<REPO> --title "title" --label "priority::high,status:ready"
218
+ gh issue edit -R <OWNER>/<REPO> <NUMBER> --add-label "status:in-progress"
219
+ gh issue close -R <OWNER>/<REPO> <NUMBER>
220
+ gh issue comment -R <OWNER>/<REPO> <NUMBER> --body "Comment text" # Add comment
207
221
 
208
222
  # PRs
209
- gh pr list --state open # Open PRs
210
- gh pr create --fill --draft # Create draft PR
211
- gh pr merge <NUMBER> # Merge PR
223
+ gh pr list -R <OWNER>/<REPO> --state open # Open PRs
224
+ gh pr create -R <OWNER>/<REPO> --fill --draft # Create draft PR
225
+ gh pr merge -R <OWNER>/<REPO> <NUMBER> # Merge PR
212
226
 
213
227
  # Workflows (CI equivalent)
214
- gh run list --limit 5 # Recent workflow runs
215
- gh run view <RUN_ID> # Run details
228
+ gh run list -R <OWNER>/<REPO> --limit 5 # Recent workflow runs
229
+ gh run view -R <OWNER>/<REPO> <RUN_ID> # Run details
216
230
 
217
- # API
231
+ # API (no --repo exists here — the endpoint path IS the target; pin the host with --hostname)
218
232
  gh api "repos/{owner}/{repo}/issues?state=open&per_page=50"
219
233
  gh api "repos/{owner}/{repo}/milestones?state=open"
220
234
  ```
@@ -367,8 +381,8 @@ Read .gitlab/merge_request_templates/Default.md
367
381
  Read .github/PULL_REQUEST_TEMPLATE.md
368
382
 
369
383
  # 2. Then create — hook now passes
370
- glab mr create --title "..." --description "..."
371
- gh pr create --title "..." --body "..."
384
+ glab mr create -R <OWNER>/<REPO> --title "..." --description "..."
385
+ gh pr create -R <OWNER>/<REPO> --title "..." --body "..."
372
386
  ```
373
387
 
374
388
  ### Cross-References
@@ -1,6 +1,15 @@
1
1
  ---
2
2
  name: gitlab-portfolio
3
- description: Use when you need a single-pane cross-repo health view across all vault-registered GitLab and GitHub projects. Discovers repos from `_overview.md` frontmatter in `<vault>/01-projects/*/`, aggregates open issues, MRs, critical labels, and stale signals via parallel `glab`/`gh` calls, then writes an idempotent `_PORTFOLIO.md` dashboard. Runs automatically at session-start Phase 2 when `gitlab-portfolio.enabled=true`. Triggers: "show portfolio status", "refresh the portfolio dashboard", "which repos have critical issues", "run /portfolio". <example>Context: session-start, gitlab-portfolio.enabled=true, vault has 5 registered repos. user: "/session deep" assistant: "Portfolio: 3 critical issues across 2 repos — run /portfolio for details. Dashboard written to vault/01-projects/_PORTFOLIO.md."</example>
3
+ description: >
4
+ Use when you need a single-pane cross-repo health view across all vault-registered GitLab and GitHub
5
+ projects. Discovers repos from `_overview.md` frontmatter in `<vault>/01-projects/*/`, aggregates open
6
+ issues, MRs, critical labels, and stale signals via parallel `glab`/`gh` calls, then writes an
7
+ idempotent `_PORTFOLIO.md` dashboard. Runs automatically at session-start Phase 2 when
8
+ `gitlab-portfolio.enabled=true`. Triggers: "show portfolio status", "refresh the portfolio dashboard",
9
+ "which repos have critical issues", "run /portfolio". <example>Context: session-start,
10
+ gitlab-portfolio.enabled=true, vault has 5 registered repos. user: "/session deep" assistant:
11
+ "Portfolio: 3 critical issues across 2 repos — run /portfolio for details. Dashboard written to
12
+ vault/01-projects/_PORTFOLIO.md."</example>
4
13
  model: sonnet
5
14
  ---
6
15
 
@@ -66,7 +66,7 @@ Walk the decision tree **one question at a time**. For each branch, in order:
66
66
  AskUserQuestion({
67
67
  questions: [{
68
68
  question: "Your code cancels whole Orders, but the PRD says a customer can cancel one line item. Which is the real model?",
69
- header: "Cancellation Scope",
69
+ header: "Cancel scope",
70
70
  options: [
71
71
  { label: "Line-item cancellation (Recommended)", description: "Matches the PRD intent. Cost: new partial-refund path + Order stays open after one item is voided." },
72
72
  { label: "Whole-order only", description: "Matches today's code. Cost: contradicts the stated user story — re-scope the PRD." },
@@ -118,12 +118,12 @@ Per the user's configured behaviour, the grill ends with a hand-off and an OPTIO
118
118
  AskUserQuestion({
119
119
  questions: [{
120
120
  question: "Grill complete. How do you want to proceed?",
121
- header: "Grill Hand-off",
121
+ header: "Hand-off",
122
122
  options: [
123
- { label: "Write grill summary + hand off to /plan feature (Recommended)", description: "Persist resolved decisions to docs/specs/, then formalize into a PRD." },
124
- { label: "Write grill summary only", description: "Keep the resolved decisions as a reference; no further step now." },
125
- { label: "Hand off to /plan feature — no file", description: "Carry the resolved decisions straight into planning; nothing persisted." },
126
- { label: "Done — no file, no hand-off", description: "The grilling itself was the value; leave no artifact." }
123
+ { label: "Summary + /plan feature (Recommended)", description: "Writes the resolved decisions to docs/specs/ (a folder of markdown notes), then hands them to planning. Cost: one file." },
124
+ { label: "Write grill summary only", description: "Same file, no planning step. The decisions keep, so you can hand them off later." },
125
+ { label: "Hand off to /plan feature — no file", description: "Carries the decisions straight into planning. They then live only in this conversation." },
126
+ { label: "Done — no file, no hand-off", description: "The grilling itself was the value. Nothing is written, nothing is handed on." }
127
127
  ],
128
128
  multiSelect: false
129
129
  }]
@@ -76,6 +76,22 @@ This is the tightest of the orchestrator's budgets by design. A turn is structur
76
76
  - Shape: explain which tactic you are applying and why it bites here, name the branches of the decision tree you are deferring, define unfamiliar terms on first use.
77
77
  - Escalation: `expand <topic>` — see § Escalation above.
78
78
 
79
+ ### Register — how a sentence reads
80
+
81
+ The budgets above set *how much* you say; the register sets *how*. It is
82
+ defined once, in `skills/session-start/soul.md` § "Register — how a sentence
83
+ reads", and binds here unchanged: the frame ("write for someone who knows this
84
+ project but has not seen what you just saw"), the plain-words test with its
85
+ five worked cases, and its precedence over § "Never traded for brevity" above.
86
+ Read it there. It is not repeated here on purpose — the § Output Levels intro
87
+ sentence already exists in four copies across the four souls with nothing
88
+ checking their parity, and a fifth copied rule would drift the same way. A
89
+ pointer cannot.
90
+
91
+ Note the one place register and adversarial posture meet: a challenge is
92
+ plainer, never softer. "Say it more simply" never becomes "say it less
93
+ sharply" — the contradiction still lands, in shorter words.
94
+
79
95
  ### Companion dials
80
96
 
81
97
  Same file, same lookup, same fallback-to-default rule:
@@ -246,8 +246,8 @@ const peerWarning = wt.activePeer
246
246
 
247
247
  AskUserQuestion({
248
248
  questions: [{
249
- question: `Stale auto-promoted worktree found: ${path.basename(wt.wtPath)} (age ${ageDays}d, branch=${wt.branch}).${peerWarning} Remove?`,
250
- header: "Stale-Worktree",
249
+ question: `Auto-promoted worktree ${path.basename(wt.wtPath)} is ${ageDays}d old (branch ${wt.branch}).${peerWarning} Remove?`,
250
+ header: "Worktree",
251
251
  multiSelect: false,
252
252
  options: [
253
253
  {
@@ -296,11 +296,24 @@ After completing all four phases, report:
296
296
 
297
297
  This advances the auto-dream cadence marker (`readDreamSignals` → `lastCleanupAt` in `scripts/lib/auto-dream.mjs`) so `shouldDispatchAutoDream` does not fire a false nudge on the next session.
298
298
 
299
- **Signalling contract (coordinator responsibility at session-end Phase 3.7):**
300
- - Set `ranMemoryCleanupThisSession = true` when `/memory-cleanup` ran this session in ANY mode or with ANY outcome.
301
- - Pass this flag to `stampMemoryCleanup()` from `scripts/lib/memory-cleanup-stamp.mjs` before emitting the session record (see `skills/session-end/session-metrics-write.md` § 1-pre).
302
- - Do NOT distinguish between "applied changes" and "healthy no-op" — both count.
303
- - Do NOT set `memory_cleanup_at` to `null`; simply omit the field when cleanup did not run.
299
+ **Signalling contract emit the event, do not rely on remembering.**
300
+
301
+ As the LAST step of every completed run — dry-run, apply-pending, or healthy no-op alike — emit the completion event. This is mandatory and it is the whole mechanism; there is no second, prose-only path that also works:
302
+
303
+ ```bash
304
+ node scripts/emit-event.mjs \
305
+ --type orchestrator.memory.cleanup_completed \
306
+ --payload "{\"semantic_session_id\":\"<the session: value from STATE.md frontmatter>\",\"mode\":\"<dry-run|apply-pending|no-op>\"}"
307
+ ```
308
+
309
+ `scripts/emit-session.mjs` then DERIVES `memory_cleanup_at` from that event at session-close time via `deriveMemoryCleanupSignal()` (`scripts/lib/memory-cleanup-stamp.mjs`), matching events whose `timestamp` falls inside the session's own `[started_at, completed_at]` window. Nothing downstream depends on the coordinator recalling that a cleanup happened.
310
+
311
+ - **Do NOT distinguish "applied changes" from "healthy no-op"** — both count as a run, so both emit. Put the distinction in `mode`, never in whether you emit.
312
+ - **Do NOT hand-append to `events.jsonl`.** Route through `emit-event.mjs` → `emitEvent()`; hand-rolled appenders drift from the canonical record shape (the `stop` vs `orchestrator.session.stopped` divergence, #609).
313
+ - **`semantic_session_id` is the semantic id** (`main-2026-08-17-session-1`), not the UUID — `sessions.jsonl` `session_id` lives in that same space, and the matcher compares against it. Omitting the field is tolerated (the event is then claimed on the time window alone), but supplying it is what makes attribution exact when two sessions overlap.
314
+ - **An explicit `memory_cleanup_at` already on the record WINS** over derivation and is never overwritten. That path exists for backfills and tests, not for normal operation.
315
+
316
+ **Why this is mechanical and not a prose instruction:** it used to be one. On 2026-08-14 a `/memory-cleanup` ran and produced a documented yield, the coordinator did not execute the prose step, and all three session records of that day carried `memory_cleanup_at: null` — so the session-start banner reported "last cleanup 29 days ago" while the operator's own notes said 3. `stampMemoryCleanup()` had zero production callers at the time; every reference to it was an instruction asking an LLM to remember. Same failure class as the STATE.md write-race that Epic #583 replaced with a lock: Disziplin statt Mechanik.
304
317
 
305
318
  ## Anti-Patterns
306
319
 
@@ -2,12 +2,14 @@
2
2
  name: npm-publish
3
3
  user-invocable: true
4
4
  model: sonnet
5
- description: Use when publishing this package to npm — a version release (npm publish), verifying the registry/pi.dev listing, or diagnosing npm auth failures (E403 2FA/token errors). Token-based flow via NPM_TOKEN in .env.local with a temp userconfig, leakage-gate greps before every publish, post-publish verification and marker/badge upkeep. Trigger on "publish to npm", "npm release", "E403 publish error".
5
+ description: Use when publishing this package to npm — a version release (npm publish), verifying the registry/pi.dev listing, or diagnosing npm auth failures (E403 2FA/token errors). Token-based flow via NPM_TOKEN in .env.local with a temp userconfig, the leakage gate before every publish, post-publish verification and marker/badge upkeep. Trigger on "publish to npm", "npm release", "E403 publish error".
6
6
  ---
7
7
 
8
- # npm-publish — Token-based publish runbook
8
+ # npm-publish — token auth, and the calls the script cannot make
9
9
 
10
- > Companion to `docs/distribution/npm-publish-checklist.md` (the original 7-step operator runbook). This skill adds the token-auth mechanics and the failure-mode diagnosis learned during the v3.16.0 first publish (2026-07-19).
10
+ > **The release itself is `/release` → `scripts/release.mjs`.** That script mechanizes the whole sequence: version surfaces, CHANGELOG gate, drift sweep, tag/registry collision, CI, leakage gate, publish, the target-confirmed npm receipt boundary, tag-after-receipt, push to both remotes, GitHub-release handling, and live-site poll. This skill does not restate it.
11
+ >
12
+ > What lives here is the half a script cannot own: the **token setup**, the **auth failure diagnosis**, and the **judgement calls** — which version, what a leak means, when to abort rather than repair.
11
13
 
12
14
  ## Why this skill exists
13
15
 
@@ -24,60 +26,20 @@ Create at https://www.npmjs.com/settings/<user>/tokens → Generate New Token
24
26
 
25
27
  ## Auth resolution order
26
28
 
27
- 1. `NPM_TOKEN` in `.env.local` at the repo root (gitignored — verify with `git check-ignore .env.local` before writing; also confirm no `.env` pattern in the `files` whitelist of package.json).
29
+ 1. `NPM_TOKEN` in `.env.local` at the repo root (gitignored — verify with `git check-ignore .env.local` before writing; also confirm no `.env` pattern in the `files` whitelist of package.json). `scripts/release.mjs` refuses to read the token if that ignore check fails.
28
30
  2. Interactive fallback: operator runs `npm publish --access public` in a real terminal (only works when account 2FA is enrolled — OTP prompt appears).
29
31
 
30
32
  **Never** put the token in the tracked `.npmrc` (it holds `ignore-scripts=true` per SEC-020 and is committed), never persist it into `~/.npmrc`, never echo it into logs.
31
33
 
32
- ## Canonical path: `scripts/release.mjs` (Release als ein Dispatch, #978)
34
+ ## The three judgement calls
33
35
 
34
- Since v3.19.0 the release is ONE dispatch the script mechanizes every step below plus the
35
- version-surface sync this skill previously left to operator memory (the gap that let v3.18.0
36
- ship tagged but unpublished):
36
+ The script gates mechanics. These three are yours, and it will not make them for you.
37
37
 
38
- ```bash
39
- node scripts/release.mjs --set-version X.Y.Z # rewrite all 12 version literals (10 files) + codex cachebuster + lock sync
40
- # … author CHANGELOG entry + README highlights (enforced by --check) …
41
- node scripts/release.mjs --check # preflight: surfaces, CHANGELOG, tag/registry collision, CI green, leakage gate
42
- node scripts/release.mjs --publish # token publish → registry verify → tag AFTER publish → push origin+github
43
- ```
38
+ **1. Which version is the right one.** Semver per `.claude/rules/development.md` § Package Lifecycle & Versioning: patch = fixes/docs/internal refactor; minor = additive and backwards-compatible; major = removed or renamed exports, or changed runtime behaviour — and a major never merges without a migration guide and a `BREAKING CHANGE:` footer. The script validates the *shape* `X.Y.Z` and nothing about whether the number matches the diff. Read the CHANGELOG entry you just wrote and ask whether a consumer pinning `^` would be broken by it; if yes, the bump is a major regardless of how small the diff looks.
44
39
 
45
- The tag is created only AFTER a registry-verified publish — never before. The manual flow
46
- below remains as the fallback and as documentation of what the script does.
40
+ **2. What a leak means when one is found.** A hit from the leakage gate is not a pattern to silence. Decide which of two it is: a real leak (fix `package.json` `files`, re-pack, re-check) or genuine over-matching (fix `LEAKAGE_PATTERNS` in `scripts/release.mjs` **with a test**). There is no third option, and neither is "publish anyway and clean it up in the next version" an npm publish is not revocable, and unpublishing burns the version number permanently. Operator handling detail: `docs/distribution/npm-publish-checklist.md` § 3.
47
41
 
48
- ## Publish flow (manual fallback)
49
-
50
- ```bash
51
- # 1. Pre-flight (first publish: expect E404 = name free; upgrade: expect the previous version)
52
- npm view session-orchestrator version
53
-
54
- # 2. Leakage gate — every grep MUST print 0 (from docs/distribution/npm-publish-checklist.md)
55
- npm pack --dry-run 2>&1 | grep -cE "npm notice.* tests/"
56
- npm pack --dry-run 2>&1 | grep -c "npm notice.*\.orchestrator/"
57
- npm pack --dry-run 2>&1 | grep -cE "npm notice.*[[:space:]]\.claude/"
58
- npm pack --dry-run 2>&1 | grep -c "npm notice.*\.github/"
59
- npm pack --dry-run 2>&1 | grep -c "node_modules"
60
- npm pack --dry-run 2>&1 | grep -ci "\.env"
61
- npm pack --dry-run 2>&1 | grep -ci "owner\.yaml"
62
-
63
- # 3. Publish via temp userconfig (never a persistent npmrc)
64
- NPM_TOKEN=$(grep '^NPM_TOKEN=' .env.local | cut -d= -f2-)
65
- TMPRC=$(mktemp) && printf '//registry.npmjs.org/:_authToken=%s\n' "$NPM_TOKEN" > "$TMPRC" && chmod 600 "$TMPRC"
66
- npm publish --access public --userconfig "$TMPRC"; rm "$TMPRC"
67
-
68
- # 4. Verify
69
- npm view session-orchestrator version # must print the new version
70
- ```
71
-
72
- Success marker: `+ session-orchestrator@<version>` on the publish output.
73
-
74
- ## Post-publish checklist
75
-
76
- 1. **Verify registry**: `npm view session-orchestrator version dist.unpackedSize keywords` — `pi-package` keyword must be present.
77
- 2. **pi.dev gallery**: indexing is asynchronous — check https://pi.dev/packages later; do not block on it.
78
- 3. **Marker upkeep** (first publish only — done in v3.16.0): README install matrix + npm badge, `site/index.html` install section, `docs/pi-setup.md` availability paragraph.
79
- 4. **Rotate/delete the token** at https://www.npmjs.com/settings/<user>/tokens once the release is done — especially if the token value ever transited chat, a screenshot, or any log. A token pasted into a conversation is burned: rotate immediately after use.
80
- 5. Update the release issue / CHANGELOG if the publish was part of a tracked release.
42
+ **3. When to abort instead of repair.** Abort — do not patch forward — when the failure is upstream of the target-confirmed npm receipt: a red preflight row, a lagging `github` mirror, CI not green on the exact commit, a dead token, or a publish that did not issue the target receipt. These are cheap to fix and re-run from the top. Repair-in-place is only appropriate *after* that receipt, where the version is already immutable: registry propagation, a missing GitHub release, or a lagging site deploy can be reconciled because npm already has the correct artifact. When `--publish` reports **Post-publish reconciliation required**, **do not rerun `--publish`**; repair the listed state directly. `commands/release.md` § Abort criteria is the operative list.
81
43
 
82
44
  ## Failure-mode table
83
45
 
@@ -85,13 +47,23 @@ Success marker: `+ session-orchestrator@<version>` on the publish output.
85
47
  |---|---|---|
86
48
  | `E403 ... Two-factor authentication or granular access token with bypass 2fa enabled is required` — no OTP prompt | Account has no 2FA enrolled AND token (if any) lacks Bypass-2FA | Create granular token with all four requirements above, or enroll 2FA |
87
49
  | Same E403 despite a fresh token | Token created without the Bypass-2FA checkbox, or Read-only, or package-scoped on a first publish | Re-create: RW + All packages + Bypass-2FA |
88
- | `E404` on `npm view` after publish | Registry propagation (rare, seconds) or publish actually failed | Re-check the publish output for `+ <name>@<version>` |
50
+ | `npm whoami` silent or non-zero | Token expired, or `.env.local` missing | Re-create the token; do not proceed the preflight fails this row on purpose |
51
+ | `E404` on `npm view` after a target-confirmed publish receipt | Registry propagation (rare, seconds) | Let the script finish its tag/push/GitHub/site tail, then reconcile the registry result; do **not** rerun `--publish` |
89
52
  | `ENEEDAUTH` | No login/token at all | Token flow above, or `npm login` |
90
53
  | OTP prompt appears but flow is non-interactive (`!`-prefix, script) | No TTY for the prompt | Use the token flow, or a real terminal |
91
54
 
55
+ ## Post-publish — the human half
56
+
57
+ `--publish` attempts registry verification and polls the live site itself. A target-confirmed receipt plus a delayed registry result is a reconciliation outcome, not a failed publish or a retry instruction. What still needs a person:
58
+
59
+ 1. **Rotate/delete the token** at https://www.npmjs.com/settings/<user>/tokens. A token that ever transited a conversation, a screenshot, or any log is burned — rotate immediately.
60
+ 2. **pi.dev gallery**: indexing is asynchronous — check https://pi.dev/packages later; do not block on it.
61
+ 3. **Marker upkeep** on a first publish only (done in v3.16.0): README install matrix + npm badge, `site/index.html` install section, `docs/pi-setup.md` availability paragraph.
62
+ 4. Update the release issue if the publish was part of tracked work.
63
+
92
64
  ## Security invariants
93
65
 
94
66
  - `.env.local` is gitignored AND absent from the npm `files` whitelist — verify both before writing a token into it.
95
- - Temp userconfig: `chmod 600`, deleted immediately after publish.
67
+ - Temp userconfig: `chmod 600`, deleted in a `finally` block immediately after publish.
96
68
  - The leakage gate runs before EVERY publish, not only the first.
97
69
  - npm's own recommendation for unattended CI/CD is **Trusted Publishing** (OIDC) — evaluate it if publishing ever moves into CI (ref: https://docs.npmjs.com/about-access-tokens).
@@ -96,10 +96,10 @@ For each `${PERM_NAME}` in `$MISSING`:
96
96
  ```
97
97
  AskUserQuestion({
98
98
  questions: [{
99
- question: `${PERM_NAME} permission is required but not granted. Open System Settings > Privacy & Security > ${PERM_NAME}, enable the terminal entry, then confirm here.`,
100
- header: `Missing Permission: ${PERM_NAME}`,
99
+ question: `${PERM_NAME} is not granted. Enable the terminal entry under System Settings > Privacy & Security, then confirm.`,
100
+ header: "Zugriff",
101
101
  options: [
102
- { label: "Granted — continue (Recommended)", description: `I have enabled ${PERM_NAME} in System Settings.` },
102
+ { label: "Granted — continue (Recommended)", description: "I enabled the terminal entry in that pane — the driver then checks again and carries on if the grant took effect." },
103
103
  { label: "Skip this run", description: "Abort peekaboo-driver. Test-runner will record a framework-error finding." }
104
104
  ],
105
105
  multiSelect: false