@ulysses-ai/create-workspace 0.17.0-beta.0 → 0.19.0-beta.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 (117) hide show
  1. package/README.md +3 -3
  2. package/lib/init.mjs +4 -1
  3. package/lib/payload.mjs +18 -1
  4. package/lib/payload.test.mjs +55 -0
  5. package/lib/scaffold.mjs +23 -6
  6. package/lib/scaffold.test.mjs +59 -0
  7. package/package.json +1 -1
  8. package/template/CLAUDE.md.tmpl +19 -2
  9. package/template/{.claude → _claude}/hooks/_utils.mjs +1 -1
  10. package/template/_claude/hooks/repo-write-detection.mjs +204 -0
  11. package/template/{.claude → _claude}/hooks/session-start.mjs +35 -1
  12. package/template/_claude/hooks/subagent-start.mjs +111 -0
  13. package/template/{.claude → _claude}/lib/session-frontmatter.mjs +28 -0
  14. package/template/{.claude → _claude}/rules/coherent-revisions.md +1 -1
  15. package/template/_claude/rules/forge-operations.md +57 -0
  16. package/template/_claude/rules/git-conventions.md +39 -0
  17. package/template/_claude/rules/goal-driven-work.md +24 -0
  18. package/template/_claude/rules/honest-pushback.md +56 -0
  19. package/template/_claude/rules/memory-guidance.md +66 -0
  20. package/template/{.claude → _claude}/rules/superpowers-workflow.md.skip +1 -1
  21. package/template/{.claude → _claude}/rules/task-list-mirroring.md +6 -0
  22. package/template/_claude/rules/work-item-tracking.md +48 -0
  23. package/template/_claude/rules/workspace-structure.md +79 -0
  24. package/template/{.claude → _claude}/scripts/build-workspace-context.mjs +86 -30
  25. package/template/_claude/scripts/chat-record.mjs +315 -0
  26. package/template/_claude/scripts/cleanup-work-session.mjs +436 -0
  27. package/template/_claude/scripts/context-footprint.mjs +391 -0
  28. package/template/{.claude → _claude}/scripts/forges/github.mjs +46 -0
  29. package/template/{.claude → _claude}/scripts/forges/gitlab.mjs +3 -2
  30. package/template/{.claude → _claude}/scripts/forges/interface.mjs +13 -0
  31. package/template/{.claude → _claude}/scripts/generate-claude-local.mjs +21 -2
  32. package/template/_claude/scripts/migrate-sessions.mjs +1571 -0
  33. package/template/{.claude → _claude}/scripts/migrate-to-workspace-context.mjs +7 -2
  34. package/template/_claude/scripts/task-pr.mjs +447 -0
  35. package/template/_claude/scripts/task-worktree.mjs +525 -0
  36. package/template/{.claude → _claude}/scripts/trackers/github-issues.mjs +11 -0
  37. package/template/{.claude → _claude}/scripts/trackers/interface.mjs +8 -0
  38. package/template/_claude/scripts/workspace-diagnostics.mjs +654 -0
  39. package/template/{.claude → _claude}/skills/braindump/SKILL.md +12 -4
  40. package/template/{.claude → _claude}/skills/build-docs-site/SKILL.md +5 -5
  41. package/template/{.claude → _claude}/skills/build-docs-site/templates/spec.md.tmpl +1 -1
  42. package/template/_claude/skills/complete-work/SKILL.md +452 -0
  43. package/template/_claude/skills/context-placement/SKILL.md +202 -0
  44. package/template/{.claude/rules/goal-driven-work.md → _claude/skills/goal-driven-work/SKILL.md} +46 -19
  45. package/template/{.claude → _claude}/skills/handoff/SKILL.md +12 -4
  46. package/template/{.claude → _claude}/skills/maintenance/SKILL.md +56 -17
  47. package/template/_claude/skills/migrate-sessions/SKILL.md +70 -0
  48. package/template/{.claude → _claude}/skills/pause-work/SKILL.md +9 -1
  49. package/template/_claude/skills/release/SKILL.md +91 -0
  50. package/template/{.claude → _claude}/skills/start-work/SKILL.md +89 -7
  51. package/template/{.claude → _claude}/skills/workspace-init/SKILL.md +3 -1
  52. package/template/{.claude → _claude}/skills/workspace-update/SKILL.md +4 -0
  53. package/template/_gitignore +9 -0
  54. package/template/workspace.json.tmpl +4 -3
  55. package/template/.claude/hooks/repo-write-detection.mjs +0 -107
  56. package/template/.claude/hooks/subagent-start.mjs +0 -44
  57. package/template/.claude/rules/forge-operations.md +0 -107
  58. package/template/.claude/rules/git-conventions.md +0 -34
  59. package/template/.claude/rules/honest-pushback.md +0 -56
  60. package/template/.claude/rules/memory-guidance.md +0 -109
  61. package/template/.claude/rules/work-item-tracking.md +0 -90
  62. package/template/.claude/rules/workspace-structure.md +0 -137
  63. package/template/.claude/scripts/cleanup-work-session.mjs +0 -247
  64. package/template/.claude/skills/complete-work/SKILL.md +0 -498
  65. package/template/.claude/skills/release/SKILL.md +0 -151
  66. /package/template/{.claude → _claude}/agents/aside-researcher.md +0 -0
  67. /package/template/{.claude → _claude}/agents/implementer.md +0 -0
  68. /package/template/{.claude → _claude}/agents/researcher.md +0 -0
  69. /package/template/{.claude → _claude}/agents/reviewer.md +0 -0
  70. /package/template/{.claude → _claude}/hooks/bash-output-advisory.mjs +0 -0
  71. /package/template/{.claude → _claude}/hooks/post-compact.mjs +0 -0
  72. /package/template/{.claude → _claude}/hooks/pre-compact.mjs +0 -0
  73. /package/template/{.claude → _claude}/hooks/session-end.mjs +0 -0
  74. /package/template/{.claude → _claude}/hooks/version-freshness-check.mjs +0 -0
  75. /package/template/{.claude → _claude}/hooks/workspace-update-check.mjs +0 -0
  76. /package/template/{.claude → _claude}/lib/freshness.mjs +0 -0
  77. /package/template/{.claude → _claude}/lib/registry-check.mjs +0 -0
  78. /package/template/{.claude → _claude}/lib/require-node.mjs +0 -0
  79. /package/template/{.claude → _claude}/recipes/migrate-from-notion.md +0 -0
  80. /package/template/{.claude → _claude}/rules/agent-rules.md.skip +0 -0
  81. /package/template/{.claude → _claude}/rules/cloud-infrastructure.md.skip +0 -0
  82. /package/template/{.claude → _claude}/rules/config-review.md.skip +0 -0
  83. /package/template/{.claude → _claude}/rules/documentation.md.skip +0 -0
  84. /package/template/{.claude → _claude}/rules/local-dev-environment.md.skip +0 -0
  85. /package/template/{.claude → _claude}/rules/product-integrity.md.skip +0 -0
  86. /package/template/{.claude → _claude}/rules/scope-guard.md.skip +0 -0
  87. /package/template/{.claude → _claude}/rules/token-economics.md.skip +0 -0
  88. /package/template/{.claude → _claude}/scripts/add-repo-to-session.mjs +0 -0
  89. /package/template/{.claude → _claude}/scripts/capture-context.mjs +0 -0
  90. /package/template/{.claude → _claude}/scripts/create-work-session.mjs +0 -0
  91. /package/template/{.claude → _claude}/scripts/migrate-canonical-priority.mjs +0 -0
  92. /package/template/{.claude → _claude}/scripts/migrate-claude-md-freshness-include.mjs +0 -0
  93. /package/template/{.claude → _claude}/scripts/migrate-open-work.mjs +0 -0
  94. /package/template/{.claude → _claude}/scripts/migrate-session-layout.mjs +0 -0
  95. /package/template/{.claude → _claude}/scripts/sweep-references.mjs +0 -0
  96. /package/template/{.claude → _claude}/scripts/sync-tasks.mjs +0 -0
  97. /package/template/{.claude → _claude}/settings.json +0 -0
  98. /package/template/{.claude → _claude}/skills/aside/SKILL.md +0 -0
  99. /package/template/{.claude → _claude}/skills/build-docs-site/checklists/framing.md +0 -0
  100. /package/template/{.claude → _claude}/skills/build-docs-site/checklists/pitfalls.md +0 -0
  101. /package/template/{.claude → _claude}/skills/build-docs-site/checklists/review.md +0 -0
  102. /package/template/{.claude → _claude}/skills/build-docs-site/scripts/bulk-fill-migration.py +0 -0
  103. /package/template/{.claude → _claude}/skills/build-docs-site/scripts/forbidden-word-grep.mjs +0 -0
  104. /package/template/{.claude → _claude}/skills/build-docs-site/scripts/leak-grep.mjs +0 -0
  105. /package/template/{.claude → _claude}/skills/build-docs-site/templates/custom.css.tmpl +0 -0
  106. /package/template/{.claude → _claude}/skills/build-docs-site/templates/docusaurus.config.ts.tmpl +0 -0
  107. /package/template/{.claude → _claude}/skills/build-docs-site/templates/primitives/Arrow.tsx +0 -0
  108. /package/template/{.claude → _claude}/skills/build-docs-site/templates/primitives/Box.tsx +0 -0
  109. /package/template/{.claude → _claude}/skills/build-docs-site/templates/primitives/DiagramContainer.tsx +0 -0
  110. /package/template/{.claude → _claude}/skills/build-docs-site/templates/primitives/Region.tsx +0 -0
  111. /package/template/{.claude → _claude}/skills/build-docs-site/templates/primitives/SectionTitle.tsx +0 -0
  112. /package/template/{.claude → _claude}/skills/build-docs-site/templates/primitives/tokens.ts +0 -0
  113. /package/template/{.claude → _claude}/skills/build-docs-site/templates/sidebars.ts.tmpl +0 -0
  114. /package/template/{.claude → _claude}/skills/promote/SKILL.md +0 -0
  115. /package/template/{.claude → _claude}/skills/setup-tracker/SKILL.md +0 -0
  116. /package/template/{.claude → _claude}/skills/sync-work/SKILL.md +0 -0
  117. /package/template/{.mcp.json → _mcp.json} +0 -0
@@ -1,498 +0,0 @@
1
- ---
2
- name: complete-work
3
- description: Finalize a work session — rebase, synthesize release notes from spec/plan/session tracker/commits, create PRs with unified presentation. Handles all project repos and workspace repo. Use when work on a session is done.
4
- ---
5
-
6
- # Complete Work
7
-
8
- Finalize the active work session. Handles all project repos (code changes, release notes, PRs) and the workspace repo (context processing, PR). Presents a unified summary with a single merge approval, then tears down the session folder.
9
-
10
- ## Flow
11
-
12
- ### Step 1: Detect context
13
-
14
- Read the active-session pointer from `.claude/.active-session.json` in the current worktree.
15
- If no active session: "No active work session. Nothing to complete."
16
-
17
- Read the full session tracker at `work-sessions/{session-name}/workspace/session.md` (use the frontmatter helper in `.claude/lib/session-frontmatter.mjs` — scripts and hooks use `_utils.mjs` which wraps it).
18
-
19
- Determine paths:
20
- - Session folder: `work-sessions/{session-name}/`
21
- - Workspace worktree: `work-sessions/{session-name}/workspace/`
22
- - Project worktrees: `work-sessions/{session-name}/workspace/repos/{repo}/` for each repo in the tracker's `repos:` list
23
- - Read each repo's default branch from workspace.json (`repos.{repo}.branch`)
24
- - **Release-notes base directory.** Read `workspace.releaseNotesDir` from `workspace.json` (default `workspace-context/release-notes` if the field is absent). Throughout this skill `{releaseNotesDir}` refers to that resolved value; every branch-note path is `{releaseNotesDir}/unreleased/{repo}/…`. Never use a bare `release-notes/`.
25
-
26
- ### Step 2: Rebase project repos
27
-
28
- For each repo in the tracker's `repos:`:
29
- ```bash
30
- # {repo-branch} = repos.{repo}.branch from workspace.json
31
- cd work-sessions/{session-name}/workspace/repos/{repo}
32
- git fetch origin
33
- git rebase origin/{repo-branch}
34
- ```
35
- If conflicts arise in any repo, STOP and present them to the user. Do not auto-resolve.
36
-
37
- ### Step 3: Capture final discussion state
38
-
39
- Run `/braindump` to capture any final discussion/reasoning to the session tracker body.
40
- If the user declines or there's nothing to capture, skip.
41
-
42
- ### Step 4: Flush task list to session.md
43
-
44
- Before reading sources for synthesis, flush current `TodoWrite` state to `## Tasks` per the `task-list-mirroring` rule. This ensures the synthesis in Step 6 sees the final state:
45
-
46
- ```bash
47
- cd work-sessions/{session-name}/workspace
48
- echo '<JSON-of-current-todos>' | node .claude/scripts/sync-tasks.mjs --write session.md
49
- ```
50
-
51
- Mark `Complete work` as `in_progress` in the JSON before flushing — the rest of this skill IS the act of completing.
52
-
53
- ### Step 5: Gather source material
54
-
55
- Formally read ALL sources before synthesizing — do not write release notes from memory alone:
56
-
57
- 1. **Session tracker** at `work-sessions/{session-name}/workspace/session.md` — read the full body (frontmatter is machine state, body is human content)
58
-
59
- 2. **Session-scoped specs/plans/goal artifacts** at the top of the session worktree:
60
- - `work-sessions/{session-name}/workspace/design-*.md` files
61
- - `work-sessions/{session-name}/workspace/plan-*.md` files
62
- - `work-sessions/{session-name}/workspace/goal-*.md` files
63
- - `work-sessions/{session-name}/workspace/research-*.md` files
64
- - `work-sessions/{session-name}/workspace/crossref-*.md` files
65
- - Read each one fully
66
-
67
- 3. **Handoffs** — any workspace-context entries referencing this branch:
68
- ```bash
69
- grep -rl "branch: {branch}" workspace-context/
70
- ```
71
- Read each matching file.
72
-
73
- 4. **Branch commit logs** (per repo):
74
- ```bash
75
- # For each repo in the tracker's repos list:
76
- cd work-sessions/{session-name}/workspace/repos/{repo}
77
- git log origin/{repo-branch}..HEAD --oneline
78
- ```
79
-
80
- ### Step 6: Synthesize release notes
81
-
82
- Branch notes are written to the **workspace** repo, not the project repo. They are an internal retrospection artifact consumed by `/release` at release time; the project repo only ever receives a `CHANGELOG.md` entry. This separation keeps dogfood content out of public project repos between feature merge and the next release cut.
83
-
84
- For each repo in the tracker's `repos:` list that has commits beyond the base branch:
85
-
86
- ```bash
87
- cd work-sessions/{session-name}/workspace/repos/{repo}
88
- COMMIT_ID=$(git rev-parse --short HEAD)
89
- cd ../.. # back to the workspace worktree
90
- mkdir -p {releaseNotesDir}/unreleased/{repo-name}
91
- ```
92
-
93
- **File 1: `{releaseNotesDir}/unreleased/{repo-name}/branch-release-notes-{COMMIT_ID}.md`** (relative to the workspace worktree)
94
- ```markdown
95
- ---
96
- branch: {branch}
97
- repo: {repo-name}
98
- type: {feature|fix|chore}
99
- author: {user}
100
- date: {YYYY-MM-DD}
101
- ---
102
-
103
- ## {Human-readable title}
104
-
105
- {Coherent narrative synthesized from tracker + spec + plan + commits.
106
- Written from scratch per coherent-revisions rule.}
107
- ```
108
-
109
- **File 2: `{releaseNotesDir}/unreleased/{repo-name}/branch-release-questions-{COMMIT_ID}.md`**
110
- ```markdown
111
- ---
112
- branch: {branch}
113
- repo: {repo-name}
114
- author: {user}
115
- date: {YYYY-MM-DD}
116
- ---
117
-
118
- ## Open Questions
119
-
120
- {Only genuinely open questions — not things resolved during implementation.}
121
- ```
122
-
123
- The `repo:` frontmatter field is what `/release` uses to know which project repo's `CHANGELOG.md` should consume each note. The directory name is the same as the field for redundancy.
124
-
125
- After all repos are processed, commit once on the workspace branch:
126
- ```bash
127
- cd work-sessions/{session-name}/workspace
128
- git add {releaseNotesDir}/unreleased/
129
- git commit -m "docs: add release notes for {branch}"
130
- ```
131
-
132
- If a repo has no commits beyond the base, skip release notes for it.
133
-
134
- ### Step 7: Remove session artifacts from the workspace branch
135
-
136
- The entire `work-sessions/{session-name}/` folder is removed by the cleanup script in Step 12. Before that happens, make sure everything worth preserving has landed in release notes (Step 6) — once Step 6 has run, the tracker, specs, plans, and goal artifacts have served their purpose.
137
-
138
- **Goal sub-branch pre-flight (only when a `goal-*.md` artifact is present).** A `/goal`-driven session can produce per-phase sub-branches for code phases (any phase declaring `integration.strategy: sub-branch` in the goal artifact). Those sub-branches must be merged into the session branch before completion, or their work is lost when the session folder is torn down. Before stripping anything, check each repo in the session — the workspace worktree itself and every `repos/{repo}/` project worktree:
139
-
140
- ```bash
141
- cd work-sessions/{session-name}/workspace/repos/{repo} # repeat for the workspace worktree too
142
- session_branch=$(git rev-parse --abbrev-ref HEAD)
143
- for sub in $(git branch --format='%(refname:short)' --list "${session_branch}-*"); do
144
- git merge-base --is-ancestor "$sub" "$session_branch" || echo "UNMERGED: $sub"
145
- done
146
- ```
147
-
148
- If any `UNMERGED:` lines print, abort completion and show the list. The user merges the intended sub-branches into the session branch, or closes abandoned ones, then re-runs `/complete-work`. When no `goal-*.md` artifact is present, this check is a no-op and completion proceeds normally.
149
-
150
- Session content lives at the top of the workspace worktree on the session branch. Once the pre-flight passes, remove these files from the branch before the final push so main's top level stays free of session artifacts:
151
-
152
- ```bash
153
- cd work-sessions/{session-name}/workspace
154
- git rm -f session.md 2>/dev/null || true
155
- git rm -f design-*.md 2>/dev/null || true
156
- git rm -f plan-*.md 2>/dev/null || true
157
- git rm -f goal-*.md 2>/dev/null || true
158
- git rm -f research-*.md 2>/dev/null || true
159
- git rm -f crossref-*.md 2>/dev/null || true
160
- git commit -m "chore: remove session artifacts before PR" 2>/dev/null || true
161
- ```
162
-
163
- The `|| true` guards keep this idempotent — if a file is already gone (e.g., a session without specs or goals), the step is a no-op. The commit is skipped when there's nothing staged.
164
-
165
- This commit persists in the branch's history. On squash merge or rebase merge, branch history collapses to one clean commit on main with no session artifacts. On merge commits, branch history is reachable but the final tree on main shows no session content.
166
-
167
- > **No version bump here.** Versions are bumped at release time by `/release`, which consumes accumulated unreleased branch notes into a single `CHANGELOG.md` entry per project repo. `/complete-work` only writes branch notes; it does not modify any project repo's `package.json`. This avoids version drift when multiple feature branches land between releases.
168
-
169
- ### Step 8: Detect remote type per repo
170
-
171
- For each repo in the tracker's `repos:` plus the workspace repo, determine the remote type. This drives how Step 9 and Step 10 push and merge.
172
-
173
- ```bash
174
- cd work-sessions/{session-name}/workspace/repos/{repo}
175
- git remote get-url origin 2>&1
176
- ```
177
-
178
- Classify the result:
179
-
180
- - **GitHub remote** — URL contains `github.com` or `gh repo view` succeeds against origin → use the PR flow (Step 9a, Step 10a).
181
- - **Local / bare remote** — URL is a filesystem path (starts with `/`, `./`, `file://`, or points at a `.git` bare mirror) → use the local merge flow (Step 9b, Step 10b).
182
- - **Other remote** (e.g., GitLab, Bitbucket, self-hosted) — no `gh` support → fall back to the local merge flow (Step 9b, Step 10b), and mention it in the final summary.
183
- - **No remote at all** — "No remote configured for {repo}. Want me to create one on GitHub, add an existing URL, or keep the session local (push/merge inside the local clone only)?" Act on the user's choice. Never silently skip push.
184
-
185
- ### Step 9: Push all repos
186
-
187
- #### Step 9a: GitHub remotes
188
-
189
- ```bash
190
- # Each project repo with a GitHub remote
191
- cd work-sessions/{session-name}/workspace/repos/{repo}
192
- git push -u origin {branch}
193
-
194
- # Workspace repo — from the workspace worktree
195
- cd work-sessions/{session-name}/workspace
196
- git add .
197
- git commit -m "chore: finalize context for {session-name}"
198
- git push -u origin {branch}
199
- ```
200
-
201
- #### Step 9b: Local/bare remotes
202
-
203
- ```bash
204
- # Push the feature branch to the bare remote so it exists there
205
- cd work-sessions/{session-name}/workspace/repos/{repo}
206
- git push -u origin {branch}
207
-
208
- # Workspace repo — same commit + push pattern
209
- cd work-sessions/{session-name}/workspace
210
- git add .
211
- git commit -m "chore: finalize context for {session-name}"
212
- git push -u origin {branch}
213
- ```
214
-
215
- The push shape is the same as 9a — what differs is the merge mechanics in Step 10b.
216
-
217
- ### Step 10: Merge and present unified summary
218
-
219
- #### Step 10a: GitHub remotes — create PRs, unified summary, merge
220
-
221
- Create one PR per project repo plus one workspace PR. PR operations go through the forge adapter (`.claude/scripts/forges/interface.mjs`), not `gh` directly — see `.claude/rules/forge-operations.md` for the contract. The adapter resolves the target repo from `workspace.forge.repo` or the local git remote.
222
-
223
- ```javascript
224
- import { createForge } from './.claude/scripts/forges/interface.mjs';
225
- import { readFileSync } from 'node:fs';
226
-
227
- const ws = JSON.parse(readFileSync('workspace.json', 'utf-8'));
228
- const forge = createForge(ws.workspace?.forge);
229
-
230
- // For each repo in the tracker's repos with a GitHub remote, from
231
- // work-sessions/{session-name}/workspace/repos/{repo}:
232
- const projectPr = await forge.prCreate({
233
- title: `${type}: ${description}`,
234
- body: prBody, // synthesized release notes + verification section
235
- });
236
-
237
- // Workspace PR — from the workspace worktree:
238
- const workspacePr = await forge.prCreate({
239
- title: `context: ${sessionName} work session`,
240
- body: workspacePrBody,
241
- });
242
- ```
243
-
244
- Present unified summary:
245
- ```
246
- Work session complete:
247
-
248
- PROJECT: {repo-1}
249
- PR #{n}: {type}: {description}
250
- Branch: {branch} → {repo-1-branch}
251
- Changes:
252
- - {bullet points from release notes}
253
- Release notes: branch-release-notes-{COMMIT_ID}.md
254
-
255
- PROJECT: {repo-2}
256
- PR #{m}: {type}: {description}
257
- Branch: {branch} → {repo-2-branch}
258
- Changes:
259
- - {bullet points from release notes}
260
-
261
- WORKSPACE: {workspace-name}
262
- PR #{p}: context: {session-name} work session
263
- Branch: {branch} → main
264
-
265
- Merge all? [Y/n]
266
- ```
267
-
268
- If yes — merge all PRs atomically through the forge adapter:
269
-
270
- ```javascript
271
- // For each project PR returned from Step 10a's prCreate calls:
272
- await forge.prMerge({ id: projectPr.id, strategy: 'squash', deleteBranch: true });
273
-
274
- // Workspace PR:
275
- await forge.prMerge({ id: workspacePr.id, strategy: 'squash', deleteBranch: true });
276
- ```
277
-
278
- `strategy: 'squash'` matches the workspace convention from `post-release-discipline` (`create-ulysses-workspace` requires linear history, so squash is the only strategy that merges cleanly; squash also lifts the PR body into the commit message). `deleteBranch: true` cleans the remote feature branch on success.
279
-
280
- Then pull all repos to their default branches (still plain git):
281
-
282
- ```bash
283
- # For each repo in the tracker's repos:
284
- cd repos/{repo} && git pull origin {repo-branch}
285
- cd {main-workspace-root} && git pull origin main
286
- ```
287
-
288
- **Step 10a.1: Tag the merge commit (release sessions only, project repos with `package.json`)**
289
-
290
- The next three sub-substeps run only when the session branch starts with `release/` — the convention for release sessions (e.g., `release/v0.15.0-beta.0`). For feature, bugfix, and chore sessions, skip 10a.1, 10a.2, and 10a.3 entirely; non-release sessions don't trigger publishes. Detection is purely by branch prefix.
291
-
292
- Derive the version tag from the branch name by stripping the `release/` prefix (so `release/v0.15.0-beta.0` yields `v0.15.0-beta.0`). For each project repo whose `package.json` declares a `version` field, verify that version matches the derived tag. The workspace repo is **never** tagged — only project repos with publishable `package.json` files get tagged, since the tag triggers `.github/workflows/publish.yml` in that project repo. If a project repo's `package.json` version doesn't match the release tag, skip that repo with a warning rather than failing the whole completion flow — the mismatch usually means `/release` was run against a different version than the branch name suggests, and the user needs to investigate before publishing.
293
-
294
- Before tagging, preflight against origin: if `v{version}` already exists remotely, surface the conflict to the user with three explicit recovery options — **Reuse** (skip to 10a.2 if the existing tag points at the right commit), **Replace** (`git push origin --delete v{version}` then re-run 10a.1), or **Investigate** (`forge.releaseView({ tag: 'v{version}', repo: '{org}/{repo}' })` — or `gh release view v{version}` as a manual fallback — to see what shipped). Do **not** silently force-push the tag; an existing tag means a published artifact, and overwriting it without confirmation can corrupt the npm registry's view of the release history.
295
-
296
- If the tag is absent on origin, tag the merge commit (HEAD on `{default-branch}` after the prior `git pull origin {default-branch}`) and push the tag. The tag push triggers `.github/workflows/publish.yml`.
297
-
298
- ```bash
299
- # Detect: only run for release sessions.
300
- if [[ ! "$branch" =~ ^release/ ]]; then
301
- # Not a release session — skip 10a.1, 10a.2, 10a.3.
302
- return
303
- fi
304
-
305
- # Extract the version from the branch name (release/v{X} → v{X}).
306
- version_tag="${branch#release/}" # e.g. "v0.15.0-beta.0"
307
-
308
- # For each project repo with a package.json containing a version field:
309
- for repo in {project-repos-with-package-json}; do
310
- cd repos/{repo}
311
-
312
- # Verify package.json version matches the tag.
313
- pkg_version=$(node -p "require('./package.json').version")
314
- expected_version="${version_tag#v}"
315
- if [ "$pkg_version" != "$expected_version" ]; then
316
- echo "Skipping {repo}: package.json version ($pkg_version) does not match release tag ($expected_version)."
317
- continue
318
- fi
319
-
320
- # Preflight: does the tag already exist on origin?
321
- if git ls-remote --exit-code origin "refs/tags/$version_tag" >/dev/null 2>&1; then
322
- # Tag exists. Surface to user with three options:
323
- # 1. Reuse — skip to 10a.2 if the existing tag points at the right commit.
324
- # 2. Replace — `git push origin --delete $version_tag` then re-run 10a.1.
325
- # 3. Investigate — call forge.releaseView({ tag: '$version_tag' }) — or
326
- # `gh release view $version_tag` as a manual fallback — to see what shipped.
327
- # Do NOT silently force-push.
328
- echo "Tag $version_tag already exists on origin. Aborting with recovery options."
329
- return 1
330
- fi
331
-
332
- # Tag the merge commit (HEAD on default branch after the prior `git pull`).
333
- git tag "$version_tag"
334
- git push origin "$version_tag" # Triggers .github/workflows/publish.yml
335
- done
336
- ```
337
-
338
- **Step 10a.2: Watch the publish workflow (release sessions only)**
339
-
340
- For each project repo tagged in 10a.1, find and follow the `publish.yml` workflow run on GitHub. The workflow takes a moment to register against the new tag — poll up to 5 times with a 3-second backoff before giving up. Once the run is found, attach with `workflowRunWatch` so the maintainer sees progress live alongside the unified summary. The adapter's `exitStatus: true` makes the underlying `gh run watch --exit-status` exit non-zero on workflow failure; the adapter returns the exit code via `res.exitCode` instead of throwing, so a failure does **not** abort the rest of `/complete-work` — the maintainer still needs to see the unified summary, including the failure URL, to decide whether to rerun, redo the release, or roll the tag back. If no run registers within the retry window, log a warning with the manual investigation command and continue.
341
-
342
- ```javascript
343
- import { createForge } from './.claude/scripts/forges/interface.mjs';
344
- import { readFileSync } from 'node:fs';
345
-
346
- const ws = JSON.parse(readFileSync('workspace.json', 'utf-8'));
347
- const forge = createForge(ws.workspace?.forge);
348
-
349
- // Retry up to 5 times with 3-second backoff — the run takes a moment to register.
350
- let run = null;
351
- for (let i = 0; i < 5; i++) {
352
- run = await forge.workflowRunFind({
353
- workflow: 'publish.yml',
354
- branch: versionTag, // e.g. 'v0.15.0-beta.0'
355
- repo: `${org}/${repo}`,
356
- limit: 1,
357
- });
358
- if (run) break;
359
- await new Promise(r => setTimeout(r, 3000));
360
- }
361
-
362
- if (!run) {
363
- console.warn(`Warning: no publish workflow run found for ${versionTag} after 15s. Investigate via 'gh run list --workflow publish.yml --branch ${versionTag}'.`);
364
- } else {
365
- const result = await forge.workflowRunWatch({
366
- runId: run.runId,
367
- repo: `${org}/${repo}`,
368
- exitStatus: true,
369
- });
370
- // result.exitCode === 0 on success; non-zero on workflow failure (NOT thrown).
371
- // result.exitCode and run.url feed into the unified summary in Step 10a.3.
372
- }
373
- ```
374
-
375
- **Step 10a.3: Update the unified summary (release sessions only)**
376
-
377
- The unified summary block presented earlier in Step 10a already has a section per project repo. For release sessions, append a `PUBLISH` section per tagged project repo to the same summary — this goes inside the existing summary, not in a new location, so the maintainer sees one consolidated report covering merges, tags, and npm publishes:
378
-
379
- ```
380
- PUBLISH ({repo}):
381
- Tag: v{version}
382
- Workflow: {run-url}
383
- Status: success | failure
384
- Published: {dist-tag}@{version} on npm
385
- ```
386
-
387
- Pull `Status` from the watch result's `exitCode` (success when `result.exitCode === 0`, failure otherwise). Pull `Workflow` from `run.url` captured in 10a.2. Pull `Published: {dist-tag}@{version}` from the workflow's published-package output if available; if the workflow failed before publishing, omit the `Published:` line and rely on `Status: failure` plus the workflow URL to point the maintainer at the failure.
388
-
389
- #### Step 10b: Local / bare / other remotes — local merge flow
390
-
391
- No PRs are created — these remotes don't have a PR concept (or we don't have a client wired up for them). Present an adjusted summary:
392
-
393
- ```
394
- Work session complete:
395
-
396
- PROJECT: {repo-1} (local remote)
397
- Branch: {branch} → {repo-1-branch}
398
- Changes:
399
- - {bullet points from release notes}
400
- Release notes: branch-release-notes-{COMMIT_ID}.md
401
-
402
- PROJECT: {repo-2} (local remote)
403
- Branch: {branch} → {repo-2-branch}
404
- Changes:
405
- - {bullet points from release notes}
406
-
407
- WORKSPACE: {workspace-name} (local remote)
408
- Branch: {branch} → main
409
-
410
- Merge all locally? [Y/n]
411
- ```
412
-
413
- If yes — fast-forward merge on each remote, delete the feature branch, pull the source clone:
414
- ```bash
415
- # For each repo in the tracker's repos with a local/bare remote:
416
- cd work-sessions/{session-name}/workspace/repos/{repo}
417
- git push origin HEAD:{repo-branch} # fast-forward the default branch
418
- git push origin --delete {branch} # remove the feature branch from the remote
419
- cd repos/{repo} && git checkout {repo-branch} && git pull origin {repo-branch}
420
-
421
- # Workspace repo — same pattern from the workspace worktree
422
- cd work-sessions/{session-name}/workspace
423
- git push origin HEAD:main
424
- git push origin --delete {branch}
425
- cd {main-workspace-root} && git pull origin main
426
- ```
427
-
428
- If the fast-forward push fails because the remote's default branch has moved ahead, STOP and present the divergence — the user decides whether to rebase and retry or handle it another way. Do not auto-resolve.
429
-
430
- For repos with no remote at all (user chose "keep local"): skip push entirely. The branch lives only in the source clone after cleanup merges it:
431
- ```bash
432
- cd repos/{repo} && git merge --ff-only {branch}
433
- ```
434
-
435
- ### Step 11: Close the linked issue on the tracker
436
-
437
- If the session tracker has a `workItem:` field AND `workspace.tracker` is configured, close the linked issue via the adapter after all PRs have merged:
438
-
439
- ```javascript
440
- import { createTracker } from './.claude/scripts/trackers/interface.mjs';
441
- import { readFileSync } from 'node:fs';
442
- const ws = JSON.parse(readFileSync('workspace.json', 'utf-8'));
443
- if (ws.workspace?.tracker) {
444
- const tracker = createTracker(ws.workspace.tracker);
445
- const comment = [
446
- `**Completed by @${currentUser}**`,
447
- '',
448
- 'Merged PRs:',
449
- ...mergedPrs.map(p => `- ${p.repo}: ${p.url}`),
450
- '',
451
- releaseSummary, // 1-3 sentence synthesis of what shipped, drawn from release notes
452
- ].join('\n');
453
- await tracker.closeIssue(workItem, { comment });
454
- }
455
- ```
456
-
457
- If `workItem:` is unset, skip the close — this was a blank session.
458
-
459
- If the close call fails (tracker unreachable, auth expired), report the error in the unified summary but do not block Step 12 cleanup. The issue can be closed manually via the GitHub UI; no data is at risk.
460
-
461
- ### Step 12: Cleanup
462
-
463
- Run the cleanup helper script from the main workspace root:
464
- ```bash
465
- node .claude/scripts/cleanup-work-session.mjs --session-name "{session-name}"
466
- ```
467
-
468
- The script tears down in the **mandatory** order:
469
- 1. Remove each nested project worktree from its project repo
470
- 2. Remove the workspace worktree from the workspace repo
471
- 3. `git worktree prune` on each project repo (belt-and-suspenders for orphan records)
472
- 4. Delete local branches in all repos
473
- 5. `rm -rf work-sessions/{session-name}/` — the tracker, specs, plans, and any local-only artifacts vanish. Their content was already archived into release notes in Step 6.
474
-
475
- Workspace-first removal silently deletes the nested project worktrees' `.git` files and leaves orphan worktree records in the project repos. The script enforces the safe order.
476
-
477
- Verify workspace root is still on main:
478
- ```bash
479
- git branch --show-current # should be "main"
480
- ```
481
-
482
- ## Handling Unformal Work Sessions
483
-
484
- If /complete-work is called but changes were made without a formal work session (no branch, changes on default branch):
485
-
486
- Ask: "These changes weren't part of a formal work session. What do you want to do?"
487
- - **Accept as work** — create a session retroactively, proceed with normal completion
488
- - **Stash for later** — create a user-scoped handoff describing what was done, stash the changes
489
- - **Hand off to someone** — create a team-visible handoff at root workspace-context/ for another member to pick up
490
- - **Revert** — undo the changes (with confirmation)
491
-
492
- ## Notes
493
- - Branch release notes live in the WORKSPACE repo at `{releaseNotesDir}/unreleased/{repo-name}/` (resolved from `workspace.json` → `workspace.releaseNotesDir`, default `workspace-context/release-notes`) — never in project repos. Project repos only ever see code commits and (at release time) `CHANGELOG.md` entries written by `/release`.
494
- - The session tracker's body is the primary source for release note synthesis — it captures the full session history alongside specs and plans
495
- - All repos get PRed and merged together — one approval for all
496
- - Version bumps happen in `/release`, not `/complete-work` — this avoids version drift when multiple feature branches land between releases
497
- - The teardown order is mandatory: project worktrees first, then workspace worktree, then prune, then delete the session folder
498
- - Context consumption, cleanup, and auto-committing release notes are intentional workflow behavior — these bypass normal commit conventions by design
@@ -1,151 +0,0 @@
1
- ---
2
- name: release
3
- description: Prepend a new CHANGELOG.md entry per project repo by synthesizing unreleased branch notes. Deletes consumed branch notes and synthesizes workspace-context into canonical (locked) entries. Use at release time.
4
- ---
5
-
6
- # Release
7
-
8
- Synthesize unreleased branch notes (in the **workspace** repo) into a concise, user-facing entry at the top of each project repo's `CHANGELOG.md`. Delete the consumed branch notes from the workspace. Bump the project repo's `package.json` version. In parallel, promote resolved workspace-context into canonical (locked) team knowledge.
9
-
10
- ## Why this shape
11
-
12
- Branch notes are detailed dogfood-retrospective artifacts that should not bloat public project repos. By keeping them in the workspace repo until release time, the project repo stays lean — it only ever sees code commits and `CHANGELOG.md` entries. A single `CHANGELOG.md` with one concise entry per version is what users of a published package actually want. Branch notes remain the input format for `/complete-work` (they capture per-session detail at the right moment), but they live in the workspace and are consumed-then-deleted by `/release`.
13
-
14
- Versions are bumped here, not in `/complete-work`, because version semantics describe what shipped — accumulated changes since the last release — not the timing of any individual feature merge.
15
-
16
- ## Parameters
17
- - `/release {version}` — create a release entry for a specific version
18
- - `/release` — ask for the version
19
-
20
- ## Flow
21
-
22
- **Step 1: Determine version and repo**
23
- If no version parameter: ask "What version is this release? (e.g., 1.2.0)"
24
-
25
- Check `workspace.json` for `releaseMode`:
26
- - **per-repo** (default): ask which repo to release
27
- - **workspace**: process all repos together
28
- - **ask**: "Process all repos together or individually?"
29
-
30
- **Release-notes base directory.** Read `workspace.releaseNotesDir` from `workspace.json` (default `workspace-context/release-notes` if the field is absent). Throughout this skill `{releaseNotesDir}` refers to that resolved value; every branch-note path is `{releaseNotesDir}/unreleased/{repo}/…`. Never use a bare `release-notes/`.
31
-
32
- **Step 2: Read unreleased notes**
33
- Branch notes live in the **workspace** repo, written there by `/complete-work`. For each target repo, list the workspace's unreleased subdirectory for that project:
34
- ```bash
35
- ls {releaseNotesDir}/unreleased/{repo}/
36
- ```
37
- Read all `branch-release-notes-*.md` and `branch-release-questions-*.md` files.
38
-
39
- If no unreleased files exist for a target repo: "No unreleased notes found for {repo}. Nothing to release."
40
-
41
- The frontmatter `repo:` field on each branch-notes file confirms which project repo the notes belong to — match that to the directory name as a sanity check. Notes mismatched on `repo:` are a sign of manual file moves; surface to the user.
42
-
43
- **Step 3: Group and organize**
44
- Group notes by `type:` frontmatter (feature, fix, chore). Within each group, order chronologically by date. This ordering drives bullet sequence in the synthesized entry.
45
-
46
- **Step 4: Handle questions**
47
- Present all open questions from `branch-release-questions-*.md` files:
48
- "These questions are still open from development. For each one:"
49
- - **Answer** — provide the answer, remove from questions
50
- - **Defer** — keep as a "Known issues" sub-bullet in the CHANGELOG entry
51
- - **Discard** — no longer relevant
52
-
53
- **Step 5: Synthesize the CHANGELOG entry**
54
-
55
- Read the current `repos/{repo}/CHANGELOG.md` (if it exists) so the new entry matches the existing voice and structure. If no CHANGELOG exists, create one with a short header explaining that entries are written for package users, not contributors.
56
-
57
- Prepend a new section at the top of the changelog body (after the header, before any existing version entries). Write it user-facing — what shipped, not how it shipped:
58
-
59
- ```markdown
60
- ## v{version} — {YYYY-MM-DD}
61
-
62
- - {Concise bullet per meaningful change. Features, fixes, and chores interleaved
63
- by significance, not by category. Each bullet is one sentence or short paragraph
64
- in plain user-facing language: "the CLI now supports X", "corrected Y behavior
65
- on Z", not "we decided" or "the team merged." Deduplicate related items.
66
- Write from scratch per the coherent-revisions rule.}
67
-
68
- ### Known issues
69
- - {Deferred questions from Step 4, if any. Omit this subsection when empty.}
70
- ```
71
-
72
- The entry stays short. If a change needs more detail, reference the repo's docs or a dedicated design doc — do not inline session-level retrospection into the public changelog.
73
-
74
- **Step 6: Delete consumed branch notes from the workspace**
75
- ```bash
76
- rm {releaseNotesDir}/unreleased/{repo}/branch-release-*
77
- # If the directory is now empty, remove it too:
78
- rmdir {releaseNotesDir}/unreleased/{repo} 2>/dev/null || true
79
- ```
80
- The branch notes were an intermediate capture; their content is now in the CHANGELOG entry and their raw form in git history. They do not survive into the project repo.
81
-
82
- **Step 7: Commit the CHANGELOG entry to the project repo**
83
- ```bash
84
- cd repos/{repo}
85
- git add CHANGELOG.md
86
- git commit -m "docs: v{version} changelog entry"
87
- ```
88
- This commit lands on the project repo's source clone (which stays on its default branch). The user pushes it when ready — `/release` does not push automatically.
89
-
90
- **Step 7b: Bump package.json version (project repo)**
91
- If the project repo has a `package.json` with a `version` field, update it to match the release version:
92
- ```bash
93
- cd repos/{repo}
94
- # Update "version": "..." in package.json to the release version
95
- git add package.json
96
- git commit -m "chore: bump version to v{version}"
97
- ```
98
- Skip this step if the repo has no package.json or no version field.
99
-
100
- **Step 7c: Commit the consumed-notes deletion in the workspace**
101
- ```bash
102
- # From the workspace root
103
- git add {releaseNotesDir}/unreleased/
104
- git commit -m "release: consume {repo} branch notes for v{version}"
105
- ```
106
- Workspace and project repos have separate commits — they are separate git histories.
107
-
108
- **Step 8: Consume project-scoped specs**
109
- Project-scoped specs and plans in `workspace-context/team-member/{user}/` (ongoing) that are fully covered by this release:
110
- - Consume into the CHANGELOG entry (their content is now captured there)
111
- - Remove the source files
112
- - If partially covered: rewrite the spec to reflect only what remains unimplemented
113
-
114
- **Step 9: Synthesize workspace-context for canonical promotion**
115
- Process ephemeral workspace-context entries:
116
-
117
- 1. List all ephemeral entries with `lifecycle: resolved` (across `shared/` and any `team-member/{user}/`).
118
- 2. For each, determine:
119
- - Does an existing locked entry cover this topic? → Merge into it (enrich)
120
- - Are there related resolved entries? → Combine into a new locked entry
121
- - Is it stale/fully consumed by release notes? → Archive or delete
122
- - Is it unresolvable but still valuable? → Move to `team-member/{user}/` ongoing or keep at `shared/` root ephemeral
123
- 3. For merged/new locked entries:
124
- - Set `state: locked`, `type: synthesized` (or `type: reference` for clean truths)
125
- - Write to `workspace-context/shared/locked/{bare-name}.md` — locked files use bare names (location signals the type), so strip any `braindump_/handoff_/research_` prefix when promoting
126
- - Write concise, focused content — team truths, not session history
127
- 4. Regenerate auto-files so `canonical.md` and `index.md` reflect the new locked content:
128
- ```bash
129
- node .claude/scripts/build-workspace-context.mjs --write --root .
130
- ```
131
- 5. Commit:
132
- ```bash
133
- git add workspace-context/
134
- git commit -m "release: synthesize workspace-context for v{version}"
135
- ```
136
-
137
- **Step 10: Report**
138
- "Release v{version} complete for {repo}. {N} branch notes consumed into CHANGELOG.md. {M} context entries synthesized into {K} locked entries."
139
-
140
- ## Notes
141
-
142
- - Release entries live in `CHANGELOG.md` at the project repo root — one file, one concise entry per version. No `release-notes/v*.md`, no `release-notes/archive/`.
143
- - Branch notes live in the WORKSPACE at `{releaseNotesDir}/unreleased/{repo}/` (resolved from `workspace.json` → `workspace.releaseNotesDir`, default `workspace-context/release-notes`). `/complete-work` writes them; `/release` consumes and deletes them. They never reach project repos.
144
- - Versions are bumped here, not in `/complete-work`. This keeps the version semantics aligned with what actually shipped (accumulated changes since last release).
145
- - The public repo stays lean. Detailed per-branch retrospection exists in workspace git history (the consumed-notes commit) but is not surfaced as standalone files in either repo.
146
- - Context synthesis happens in the WORKSPACE repo — Step 7c (consumed-notes) and Step 9 (workspace-context synthesis) are separate workspace commits.
147
- - Per-repo is the default — each project repo has its own release cadence.
148
- - The coherent-revisions rule applies: write the CHANGELOG entry from scratch, don't concatenate branch notes.
149
- - Tagging happens in `/complete-work`, not here. When the session branch starts with `release/`, `/complete-work` tags the merge commit on the project repo's default branch and pushes the tag, which triggers `.github/workflows/publish.yml` to publish to npm. `/release` produces the synthesis (CHANGELOG entry + version bump + consumed-notes deletion); `/complete-work` does the push, PR, merge, and tag.
150
- - Do not run `npm publish` locally. The publish workflow is the only path that exercises OIDC trusted publishing — local publish requires 2FA OTP and bypasses that. If the workflow fails, investigate via `gh run view`; do not fall back to local publish.
151
- - Recovery from a failed publish. Transient failure: rerun via `gh run rerun {run_id}`. Content failure: delete the tag (`git push origin --delete v{version} && git tag -d v{version}`), then redo the release in a new release session — `/start-work`, then `/release v{version}`, then `/complete-work`. Once a version is published to npm, that version is committed on the registry; bump and start a new release.