session-orchestrator 3.20.0 → 3.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.cursor/rules/030-wave-execution.mdc +10 -8
  5. package/CHANGELOG.md +414 -0
  6. package/README.md +16 -11
  7. package/agents/analyst.md +1 -1
  8. package/agents/architect-reviewer.md +1 -1
  9. package/agents/code-implementer.md +4 -2
  10. package/agents/db-specialist.md +1 -1
  11. package/agents/dialectic-deriver.md +1 -1
  12. package/agents/docs-writer.md +1 -1
  13. package/agents/memory-proposal-collector.md +1 -1
  14. package/agents/qa-strategist.md +1 -1
  15. package/agents/security-reviewer.md +1 -1
  16. package/agents/session-reviewer.md +42 -1
  17. package/agents/skill-applied-judge.md +1 -1
  18. package/agents/test-writer.md +1 -1
  19. package/agents/ui-developer.md +1 -1
  20. package/agents/ux-evaluator.md +1 -1
  21. package/commands/release.md +60 -0
  22. package/docs/migration-v3.md +9 -6
  23. package/docs/persona-panel.md +3 -1
  24. package/docs/scope-collision-guard.md +167 -0
  25. package/hooks/enforce-scope.mjs +103 -3
  26. package/hooks/hooks-codex.json +1 -1
  27. package/hooks/hooks.json +11 -1
  28. package/hooks/on-session-end.mjs +52 -5
  29. package/hooks/on-stop.mjs +127 -12
  30. package/hooks/post-bash-write-verify.mjs +8 -32
  31. package/hooks/pre-task-scope-disjoint.mjs +1042 -0
  32. package/package.json +1 -1
  33. package/pi/prompts/release.md +12 -0
  34. package/scripts/autopilot.mjs +3 -1
  35. package/scripts/emit-session.mjs +42 -0
  36. package/scripts/export-hw-learnings.mjs +61 -2
  37. package/scripts/lib/autopilot/worktree-pipeline.mjs +5 -5
  38. package/scripts/lib/backlog-scan.mjs +106 -15
  39. package/scripts/lib/build-live-signals.mjs +7 -3
  40. package/scripts/lib/ci-status-banner.mjs +207 -23
  41. package/scripts/lib/git-config-drift.mjs +471 -0
  42. package/scripts/lib/harness-audit/categories/category6.mjs +65 -12
  43. package/scripts/lib/io.mjs +193 -7
  44. package/scripts/lib/learnings/select.mjs +157 -3
  45. package/scripts/lib/memory-cleanup-stamp.mjs +132 -8
  46. package/scripts/lib/mirror-issues-banner.mjs +266 -0
  47. package/scripts/lib/named-vault-resolver.mjs +105 -16
  48. package/scripts/lib/peer-cards/schema.mjs +6 -2
  49. package/scripts/lib/reconcile/writer.mjs +69 -30
  50. package/scripts/lib/redact-spans.mjs +89 -0
  51. package/scripts/lib/scope-baseline.mjs +77 -17
  52. package/scripts/lib/scope-gate.mjs +658 -0
  53. package/scripts/lib/secret-masker.mjs +262 -0
  54. package/scripts/lib/session-lock.mjs +34 -10
  55. package/scripts/lib/session-registry.mjs +9 -1
  56. package/scripts/lib/state-md/mission-status.mjs +21 -12
  57. package/scripts/lib/tmux-layout/vcs-detector.mjs +108 -4
  58. package/scripts/lib/validate/check-agents.mjs +77 -5
  59. package/scripts/lib/validate/check-commands.mjs +2 -20
  60. package/scripts/lib/validate/check-doc-cli-commands.mjs +514 -0
  61. package/scripts/lib/validate/check-hooks-symmetry.mjs +18 -0
  62. package/scripts/lib/validate/check-owner-leakage.mjs +185 -17
  63. package/scripts/lib/validate/check-skills.mjs +191 -0
  64. package/scripts/lib/validate/check-test-git-config-target.mjs +665 -0
  65. package/scripts/lib/validate/check-unicode-safety.mjs +22 -2
  66. package/scripts/lib/validate/check-untracked-test-deps.mjs +925 -0
  67. package/scripts/lib/validate/check-unwired-features.mjs +219 -11
  68. package/scripts/lib/validate/check-vcs-repo-flag.mjs +965 -0
  69. package/scripts/lib/validate/frontmatter-block.mjs +61 -0
  70. package/scripts/lib/validate/tier-inference.mjs +46 -8
  71. package/scripts/lib/vault-mirror/namespace.mjs +146 -1
  72. package/scripts/lib/vault-mirror/process.mjs +264 -31
  73. package/scripts/lib/vault-mirror/render-sessions.mjs +115 -4
  74. package/scripts/lib/vault-status/board-writer.mjs +300 -56
  75. package/scripts/lib/vault-status/narrative-mirror.mjs +119 -5
  76. package/scripts/lib/vcs-repo-spec.mjs +500 -19
  77. package/scripts/print-learnings-index.mjs +30 -3
  78. package/scripts/release.mjs +616 -61
  79. package/scripts/site-numbers.mjs +1049 -0
  80. package/scripts/validate-plugin.mjs +61 -0
  81. package/scripts/validate-wave-scope.mjs +281 -12
  82. package/scripts/vault-mirror.mjs +26 -1
  83. package/skills/_shared/monitor-patterns.md +24 -4
  84. package/skills/_shared/state-ownership.md +17 -0
  85. package/skills/claude-md-drift-check/SKILL.md +9 -1
  86. package/skills/debug/SKILL.md +4 -1
  87. package/skills/discovery/issue-templates.md +4 -4
  88. package/skills/discovery/probes-code.md +2 -2
  89. package/skills/discovery/probes-feature.md +6 -6
  90. package/skills/discovery/probes-infra.md +2 -2
  91. package/skills/discovery/probes-session.md +5 -5
  92. package/skills/dispatcher/SKILL.md +10 -1
  93. package/skills/frontmatter-guard/SKILL.md +9 -1
  94. package/skills/gitlab-ops/SKILL.md +49 -39
  95. package/skills/gitlab-portfolio/SKILL.md +10 -1
  96. package/skills/memory-cleanup/SKILL.md +18 -5
  97. package/skills/npm-publish/SKILL.md +22 -50
  98. package/skills/persona-panel/SKILL.md +3 -1
  99. package/skills/plan/mode-new.md +23 -5
  100. package/skills/repo-audit/SKILL.md +10 -1
  101. package/skills/session-end/SKILL.md +44 -2
  102. package/skills/session-end/metrics-collection.md +1 -1
  103. package/skills/session-end/session-metrics-write.md +4 -10
  104. package/skills/session-plan/SKILL.md +2 -2
  105. package/skills/session-plan/wave-template.md +1 -1
  106. package/skills/session-start/SKILL.md +13 -1
  107. package/skills/spinout/SKILL.md +5 -1
  108. package/skills/sunset-review/SKILL.md +11 -1
  109. package/skills/tmux-layout/SKILL.md +7 -2
  110. package/skills/vault-mirror/SKILL.md +10 -1
  111. package/skills/vault-sync/SKILL.md +10 -1
  112. package/skills/vault-sync/validator.mjs +55 -6
  113. package/skills/wave-executor/wave-loop.md +42 -12
  114. package/scripts/lib/mission-status-schema.mjs +0 -114
@@ -113,11 +113,11 @@ Export Status: exported/public | internal
113
113
  1. List open feature/enhancement issues via the VCS CLI (syntax reference: `skills/gitlab-ops/SKILL.md` § "Common CLI Commands" — do not duplicate CLI flags here beyond what's needed to name the call):
114
114
  ```bash
115
115
  # GitLab
116
- glab issue list --label "feature" --per-page 100
117
- glab issue list --label "enhancement" --per-page 100
116
+ glab issue list -R <OWNER>/<REPO> --label "feature" --per-page 100
117
+ glab issue list -R <OWNER>/<REPO> --label "enhancement" --per-page 100
118
118
  # GitHub
119
- gh issue list --label "feature" --limit 100
120
- gh issue list --label "enhancement" --limit 100
119
+ gh issue list -R <OWNER>/<REPO> --label "feature" --limit 100
120
+ gh issue list -R <OWNER>/<REPO> --label "enhancement" --limit 100
121
121
  ```
122
122
 
123
123
  2. Extract theme keywords from each issue's title (and first paragraph of body where available): lowercase, strip stop-words, tokenize on non-alphanumeric boundaries. Group issues that share two or more significant keywords (e.g. "export", "csv", "invoice") into a candidate cluster.
@@ -125,10 +125,10 @@ gh issue list --label "enhancement" --limit 100
125
125
  3. For each candidate cluster with 3 or more issues, check whether ANY member issue is already linked to an epic (`epic` label, a GitLab Epic relationship, or an in-body reference such as `part of #<epic-iid>` / `Epic: #<epic-iid>`):
126
126
  ```bash
127
127
  # GitLab -- inspect labels + description for epic linkage
128
- glab issue view <IID>
128
+ glab issue view -R <OWNER>/<REPO> <IID>
129
129
  Grep pattern: (^Labels:.*\bepic\b|part of #|Epic:\s*#)
130
130
  # GitHub -- same check against issue body/labels
131
- gh issue view <NUMBER>
131
+ gh issue view -R <OWNER>/<REPO> <NUMBER>
132
132
  Grep pattern: (^labels:.*\bepic\b|part of #|Epic:\s*#)
133
133
  ```
134
134
 
@@ -10,10 +10,10 @@
10
10
 
11
11
  ```bash
12
12
  # GitLab: query recent pipeline status
13
- glab pipeline list --per-page 10
13
+ glab pipeline list -R <OWNER>/<REPO> --per-page 10
14
14
 
15
15
  # GitHub: query recent workflow runs
16
- gh run list --limit 10
16
+ gh run list -R <OWNER>/<REPO> --limit 10
17
17
 
18
18
  # Parse output for:
19
19
  # - Repeated failures (same pipeline failing 3+ times in a row)
@@ -55,9 +55,9 @@ Grep pattern: <claimed_addition>
55
55
  git show <commit_hash> -- <relevant_files>
56
56
 
57
57
  # "Closes #N" -> verify acceptance criteria from issue #N are met
58
- gh issue view <N> --json body -q '.body'
58
+ gh issue view -R <OWNER>/<REPO> <N> --json body -q '.body'
59
59
  # or
60
- glab issue view <N>
60
+ glab issue view -R <OWNER>/<REPO> <N>
61
61
 
62
62
  # Step 3: Cross-reference claims against actual changes
63
63
  git diff <commit_hash>~1..<commit_hash>
@@ -83,10 +83,10 @@ Evidence: <what was found or not found>
83
83
 
84
84
  ```bash
85
85
  # GitLab: list open issues sorted by last update
86
- glab issue list --per-page 100 | head -50
86
+ glab issue list -R <OWNER>/<REPO> --per-page 100 | head -50
87
87
 
88
88
  # GitHub: list open issues sorted by last update
89
- gh issue list --limit 100 --json number,title,labels,updatedAt,assignees --jq '.[] | select(.updatedAt < "<30_days_ago_iso>")'
89
+ gh issue list -R <OWNER>/<REPO> --limit 100 --json number,title,labels,updatedAt,assignees --jq '.[] | select(.updatedAt < "<30_days_ago_iso>")'
90
90
 
91
91
  # Flag:
92
92
  # - Issues with no activity in stale-issue-days (default: 30 days)
@@ -131,7 +131,7 @@ for issue in issues:
131
131
  "
132
132
 
133
133
  # GitHub: fetch issue bodies and parse cross-references
134
- gh issue list --limit 100 --json number,body --jq '.[] | {number, body}' | python3 -c "
134
+ gh issue list -R <OWNER>/<REPO> --limit 100 --json number,body --jq '.[] | {number, body}' | python3 -c "
135
135
  import json, sys, re
136
136
  for line in sys.stdin:
137
137
  issue = json.loads(line)
@@ -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
 
@@ -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
+ IDs 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
@@ -57,9 +65,9 @@ Never hardcode project IDs. Resolve them at runtime — and re-resolve live each
57
65
 
58
66
  ```bash
59
67
  # GitLab — get numeric project ID
60
- glab repo view --output json | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])"
68
+ glab repo view -R <OWNER>/<REPO> --output json | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])"
61
69
 
62
- # GitHub — get owner/name identifier
70
+ # GitHub — get owner/name identifier (gh repo takes the repo POSITIONALLY; it rejects -R)
63
71
  gh repo view --json nameWithOwner -q '.nameWithOwner'
64
72
  ```
65
73
 
@@ -153,33 +161,35 @@ GitHub has no native issue-blocking relation at all — the body-ordering-note f
153
161
 
154
162
  ## Common CLI Commands
155
163
 
164
+ **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.
165
+
156
166
  ### GitLab (glab)
157
167
 
158
168
  ```bash
159
169
  # 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
170
+ glab issue list -R <OWNER>/<REPO> --per-page 50 # All open issues
171
+ glab issue list -R <OWNER>/<REPO> --label "status:ready" --per-page 10 # Ready to work on
172
+ glab issue list -R <OWNER>/<REPO> --label "priority::high" --per-page 10 # High priority
173
+ glab issue list -R <OWNER>/<REPO> --closed --per-page 10 # Recently closed
174
+ glab issue view -R <OWNER>/<REPO> <IID> # View issue details
175
+ glab issue view -R <OWNER>/<REPO> <IID> --comments # With comments
176
+ glab issue create -R <OWNER>/<REPO> --title "title" --label "priority::high,status:ready"
177
+ glab issue update -R <OWNER>/<REPO> <IID> --label "status:in-progress" # WARNING: --label REPLACES the full set — see caveat below
178
+ glab issue close -R <OWNER>/<REPO> <IID> # then VERIFY: re-read the issue; it must show state=closed
179
+ glab issue note -R <OWNER>/<REPO> <IID> -m "Comment text" # Add comment
170
180
 
171
181
  # MRs
172
- glab mr list # Open MRs
173
- glab mr create --fill --draft # Create draft MR
174
- glab mr merge <MR_IID> # Merge MR
182
+ glab mr list -R <OWNER>/<REPO> # Open MRs
183
+ glab mr create -R <OWNER>/<REPO> --fill --draft # Create draft MR
184
+ glab mr merge -R <OWNER>/<REPO> <MR_IID> # Merge MR
175
185
 
176
186
  # Pipelines
177
- glab pipeline list --per-page 5 # Recent pipelines
178
- glab pipeline status <ID> # Pipeline details
187
+ glab pipeline list -R <OWNER>/<REPO> --per-page 5 # Recent pipelines
188
+ glab pipeline status -R <OWNER>/<REPO> <ID> # Pipeline details
179
189
 
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"
190
+ # API (no --repo exists here the endpoint path IS the target; pin the host with --hostname)
191
+ glab api "projects/$(glab repo view -R <OWNER>/<REPO> --output json | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])")/issues?state=opened&per_page=50"
192
+ glab api "projects/$(glab repo view -R <OWNER>/<REPO> --output json | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])")/milestones?state=active"
183
193
  ```
184
194
 
185
195
  **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`.
@@ -194,27 +204,27 @@ glab api "projects/$(glab repo view --output json | python3 -c "import json,sys;
194
204
 
195
205
  ```bash
196
206
  # 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
207
+ gh issue list -R <OWNER>/<REPO> --limit 50 # All open issues
208
+ gh issue list -R <OWNER>/<REPO> --label "status:ready" --limit 10 # Ready to work on
209
+ gh issue list -R <OWNER>/<REPO> --label "priority::high" --limit 10 # High priority
210
+ gh issue list -R <OWNER>/<REPO> --state closed --limit 10 # Recently closed
211
+ gh issue view -R <OWNER>/<REPO> <NUMBER> # View issue details
212
+ gh issue view -R <OWNER>/<REPO> <NUMBER> --comments # With comments
213
+ gh issue create -R <OWNER>/<REPO> --title "title" --label "priority::high,status:ready"
214
+ gh issue edit -R <OWNER>/<REPO> <NUMBER> --add-label "status:in-progress"
215
+ gh issue close -R <OWNER>/<REPO> <NUMBER>
216
+ gh issue comment -R <OWNER>/<REPO> <NUMBER> --body "Comment text" # Add comment
207
217
 
208
218
  # 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
219
+ gh pr list -R <OWNER>/<REPO> --state open # Open PRs
220
+ gh pr create -R <OWNER>/<REPO> --fill --draft # Create draft PR
221
+ gh pr merge -R <OWNER>/<REPO> <NUMBER> # Merge PR
212
222
 
213
223
  # Workflows (CI equivalent)
214
- gh run list --limit 5 # Recent workflow runs
215
- gh run view <RUN_ID> # Run details
224
+ gh run list -R <OWNER>/<REPO> --limit 5 # Recent workflow runs
225
+ gh run view -R <OWNER>/<REPO> <RUN_ID> # Run details
216
226
 
217
- # API
227
+ # API (no --repo exists here — the endpoint path IS the target; pin the host with --hostname)
218
228
  gh api "repos/{owner}/{repo}/issues?state=open&per_page=50"
219
229
  gh api "repos/{owner}/{repo}/milestones?state=open"
220
230
  ```
@@ -367,8 +377,8 @@ Read .gitlab/merge_request_templates/Default.md
367
377
  Read .github/PULL_REQUEST_TEMPLATE.md
368
378
 
369
379
  # 2. Then create — hook now passes
370
- glab mr create --title "..." --description "..."
371
- gh pr create --title "..." --body "..."
380
+ glab mr create -R <OWNER>/<REPO> --title "..." --description "..."
381
+ gh pr create -R <OWNER>/<REPO> --title "..." --body "..."
372
382
  ```
373
383
 
374
384
  ### 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
 
@@ -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, tag-after-publish, push to both remotes, 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 publish: a red preflight row, a lagging `github` mirror, CI not green on the exact commit, a dead token. These are cheap to fix and re-run from the top. Repair-in-place is only ever appropriate *after* a verified publish, where the version is already immutable: a missing GitHub release or a lagging site deploy can be reconciled, because npm already has the correct artifact. The dividing line is whether the registry has accepted the tarball — before that point, restart; after it, reconcile. `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 |
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 |
88
51
  | `E404` on `npm view` after publish | Registry propagation (rare, seconds) or publish actually failed | Re-check the publish output for `+ <name>@<version>` |
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` verifies the registry and polls the live site itself, and prints the rest. 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).
@@ -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
@@ -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** — 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 "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,11 @@ 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
132
137
  (
133
138
  echo "$TYPE_CHOICE" # e.g., "1" for nextjs-saas
134
139
  echo "$STYLE_CHOICE" # e.g., "1" for vega (only if nextjs-saas)
@@ -143,7 +148,7 @@ Map gathered answers to script input choices:
143
148
  Check exit code. Confirm repo exists:
144
149
 
145
150
  ```bash
146
- glab repo view $GROUP/$PROJECT_NAME
151
+ glab repo view "$GROUP_PATH/$PROJECT_NAME"
147
152
  ```
148
153
 
149
154
  ### Step 3: Adjust visibility
@@ -151,9 +156,22 @@ glab repo view $GROUP/$PROJECT_NAME
151
156
  If visibility is not `internal` (the default):
152
157
 
153
158
  ```bash
154
- glab repo edit --visibility private # or --visibility public
159
+ # There is no `glab repo edit`, and `glab repo update` carries no --visibility
160
+ # flag (its FLAGS are --archive/--defaultBranch/-d/--description). Go through the
161
+ # API, addressing the project by its URL-encoded path — `projects/:id` resolves
162
+ # from the CWD remote, which is the wrong project right after scaffolding.
163
+ ENCODED="${GROUP_PATH}%2F${PROJECT_NAME}"
164
+ glab api -X PUT "projects/${ENCODED}" -f visibility=private # or visibility=public
165
+
166
+ # Verify (this GET is the read-only proof the PUT landed). Note `glab api` has
167
+ # no --jq flag — that is `gh api`'s. Pipe to jq instead.
168
+ glab api "projects/${ENCODED}" | jq -r '.visibility'
155
169
  ```
156
170
 
171
+ > The GET path above is verified against glab 1.91.0; the PUT is the documented
172
+ > GitLab API shape but was **not** executed during authoring (write operations
173
+ > were out of scope). Confirm with the GET before relying on it.
174
+
157
175
  For public/OSS, also configure GitHub mirror if applicable.
158
176
 
159
177
  ### Step 4: Set branch protection
@@ -274,11 +292,11 @@ Use AskUserQuestion to present the full issue structure:
274
292
 
275
293
  ```bash
276
294
  # Create epic
277
- glab issue create --title "$EPIC_TITLE" --description "$EPIC_DESC" \
295
+ glab issue create -R "$GROUP_PATH/$PROJECT_NAME" --title "$EPIC_TITLE" --description "$EPIC_DESC" \
278
296
  --label "type:epic,priority::$PRIORITY" --milestone "$MILESTONE"
279
297
 
280
298
  # Create sub-issues
281
- glab issue create --title "$ISSUE_TITLE" --description "$ISSUE_DESC" \
299
+ glab issue create -R "$GROUP_PATH/$PROJECT_NAME" --title "$ISSUE_TITLE" --description "$ISSUE_DESC" \
282
300
  --label "type:feature,priority::$PRIORITY,status:ready,area:$AREA,appetite:$APPETITE"
283
301
  ```
284
302
 
@@ -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
  ---
@@ -780,11 +780,53 @@ git push origin HEAD
780
780
  ```
781
781
 
782
782
  ### 4.4 GitHub Mirror (if configured in Session Config)
783
+
784
+ Three states, three DISTINGUISHABLE outcomes. The predecessor of this block
785
+ (`git remote get-url github 2>/dev/null && git push github HEAD 2>/dev/null || echo "GitHub mirror: not configured"`)
786
+ collapsed a **failed push** into `GitHub mirror: not configured` and exited 0 — git's real
787
+ error went to `/dev/null`, so a broken mirror was indistinguishable from an unconfigured one
788
+ (`.claude/rules/bash-harness-pitfalls.md` — "Silence is not success"). That matters more once
789
+ anything is wired to the mirror (e.g. a Vercel Git deploy): a silently-failing push means the
790
+ downstream artifact never updates and nobody is told.
791
+
792
+ Run it verbatim — `tests/skills/session-end/github-mirror-push.test.mjs` extracts the block
793
+ between the markers and executes it, so no second copy of this command may exist.
794
+
783
795
  ```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"
796
+ # --- github-mirror-push:begin ---
797
+ # Only attempt if 'mirror: github' is in Session Config.
798
+ # State 0: not a git repository at all → loud WARN, exit 1. This state was MISSED
799
+ # in the first version and is the reason it is listed first now: outside
800
+ # a repo, `git remote get-url` fails with "fatal: not a git repository",
801
+ # which is indistinguishable from "no such remote" by exit code alone.
802
+ # The block then announced "no 'github' remote configured — skipping
803
+ # (not an error)" and exited 0 — fail-open, in the very fix written to
804
+ # close a fail-open. Found by an adversarial reviewer, not by the author.
805
+ # State 1: no 'github' remote → informational, exit 0 (legitimate for consumer repos)
806
+ # State 2: push succeeded → confirmation WITH the pushed SHA, exit 0
807
+ # State 3: push FAILED → loud WARN on stderr WITH git's real output, exit 1
808
+ if ! git_dir=$(git rev-parse --git-dir 2>&1); then
809
+ echo "WARN GitHub mirror: not a git repository — cannot mirror anything." >&2
810
+ echo " git said: ${git_dir}" >&2
811
+ exit 1
812
+ elif ! mirror_url=$(git remote get-url github 2>&1); then
813
+ echo "GitHub mirror: no 'github' remote configured — skipping (not an error)."
814
+ echo " git said: ${mirror_url}" >&2
815
+ elif push_out=$(git push github HEAD 2>&1); then
816
+ echo "GitHub mirror: pushed $(git rev-parse HEAD) -> ${mirror_url}"
817
+ else
818
+ echo "WARN GitHub mirror PUSH FAILED: $(git rev-parse HEAD) is NOT on ${mirror_url}" >&2
819
+ echo "${push_out}" >&2
820
+ echo "WARN Mirror is stale — anything wired to it (site deploy) will not update." >&2
821
+ exit 1
822
+ fi
823
+ # --- github-mirror-push:end ---
786
824
  ```
787
825
 
826
+ State 3 exits non-zero on purpose: it is the only machine-readable signal that the mirror is
827
+ behind. Report it to the operator in the session summary; do not retry silently and do not
828
+ swallow it with `|| true`.
829
+
788
830
  ## Phase 4a: Auto-Promoted Worktree Cleanup (#575 P3.2)
789
831
 
790
832
  > Skip if `persistence: false` in Session Config. Skip silently if the current worktree is NOT an Auto-promoted sibling (the common case).
@@ -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)