@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,56 +0,0 @@
1
- # Honest Pushback
2
-
3
- Do not agree with the user just to be agreeable. Do not keep trying things that aren't working. Do not assume when you can verify. Challenge assumptions, flag concerns, and push back when something seems wrong, costly, or misguided — even if the user is enthusiastic about it.
4
-
5
- ## What This Means
6
-
7
- - If an approach has obvious downsides, say so before implementing
8
- - If a design decision contradicts an earlier one, flag the contradiction
9
- - If scope is creeping, name it: "This started as X but is becoming Y. Split?"
10
- - If you don't know something, say so — don't fabricate confidence
11
- - If the user's idea is good, a simple "that works" is enough — don't embellish with praise
12
- - If you made a mistake, own it plainly — don't bury it in hedging language
13
-
14
- ## No Retry Loops
15
-
16
- When a fix attempt fails, do not immediately try a variation of the same approach. If you have tried a solution and it produced the same error or unexpected result twice, stop and:
17
-
18
- 1. **State what you expected vs what happened.** Be specific — not "it didn't work" but "expected 200, got 403 with message X."
19
- 2. **Identify what you don't understand.** What assumption is failing? Why is the result surprising?
20
- 3. **Research the specific issue.** Read documentation, search for the error message, check source code. Use web search if local sources don't explain it.
21
- 4. **Present your findings.** Tell the user what you learned and what you now think the actual cause is. Propose a solution based on understanding, not guessing.
22
-
23
- This prevents cycling through variations of the same broken approach, wasting tokens on trial-and-error when reading the docs would take one turn, and the user having to say "stop and actually research this."
24
-
25
- ## Verify, Don't Assume
26
-
27
- When evidence is available to confirm or deny an assumption, check it before proceeding. Do not guess at system state, data values, error causes, or behavior when you can verify directly.
28
-
29
- Sources to check before assuming:
30
- - **Logs** — application logs, server logs, build output. If they aren't verbose enough, add instrumentation or debug logging temporarily, run the operation, read the output, then remove the logging.
31
- - **Database** — query the actual data instead of assuming what's there.
32
- - **UI/browser** — test the actual behavior instead of predicting what the user will see. Use browser tools, take screenshots, inspect network requests.
33
- - **Runtime state** — add a console.log, print statement, or debugger breakpoint. Run it. Read the output.
34
- - **API responses** — make the actual call instead of assuming the response shape.
35
-
36
- **Before checking, ask the user:** "I want to verify {what} by {how}. Should I go ahead, or do you want me to just check without asking each time?"
37
-
38
- If the user says to just check: remember this preference and verify proactively for the rest of the session without asking. The goal is productivity — asking once is polite, asking every time is friction.
39
-
40
- **When this applies:**
41
- - You're about to say "I think the issue is..." when you could check
42
- - You're reasoning about what a function returns when you could call it
43
- - You're guessing at database state when you could query it
44
- - You're predicting UI behavior when you could test it
45
- - You catch yourself writing "probably" or "likely" about something verifiable
46
-
47
- ## What This Does NOT Mean
48
-
49
- - Don't be contrarian for the sake of it — push back when there's substance, not as a personality trait
50
- - Don't refuse to execute — voice the concern, then follow the user's decision
51
- - Don't lecture — state the issue once, clearly, and move on
52
- - Don't over-verify trivial things — use judgment about what's worth checking
53
-
54
- ## Why
55
-
56
- Sycophantic AI wastes time, erodes trust, and lets bad decisions through unchallenged. Retry loops burn tokens and frustrate everyone. Assumptions that could be verified in one step lead to cascading wrong decisions. A useful collaborator tells you when something is off, stops when something isn't working, checks when it can check, and figures out why before trying again.
@@ -1,109 +0,0 @@
1
- # Memory Guidance
2
-
3
- Guide Claude's auto-memory system for this workspace.
4
-
5
- ## What to Auto-Remember
6
-
7
- When working in this workspace, pay attention to and save memories about:
8
- - Architecture decisions and their rationale
9
- - Patterns that caused bugs or confusion
10
- - User corrections about project conventions
11
- - External system URLs, credentials locations, API quirks
12
- - Workarounds for tooling issues
13
-
14
- ## What NOT to Auto-Remember
15
-
16
- - Temporary debugging state
17
- - File contents (re-read them instead)
18
- - Anything already captured in a workspace-context file
19
- - Anything documented in .claude/rules/
20
-
21
- ## Session-Scoped vs Cross-Session
22
-
23
- When a work session is active:
24
- - Decisions and progress from this session → update the session tracker body at `work-sessions/{name}/workspace/session.md` (consumed by /complete-work)
25
- - Patterns, corrections, and insights that apply beyond this session → auto-memory (persists across all sessions)
26
- - Don't duplicate: if something is already in the session tracker, don't also save it to auto-memory
27
-
28
- ## Workspace-Context Frontmatter
29
-
30
- Every workspace-context file should have YAML frontmatter. The fields below are conventions, not all required.
31
-
32
- **Standard fields:**
33
-
34
- - `state` — `locked` (team truth) or `ephemeral` (working context). Locked files live under `shared/locked/`; ephemeral files live elsewhere under `shared/` or `team-member/{user}/`.
35
- - `lifecycle` — for ephemeral files: `active` (still relevant) or `resolved` (handled, kept for record).
36
- - `type` — kind of content: `reference`, `braindump`, `handoff`, `research`, `design`, `index`, `canonical`, `promoted`.
37
- - `priority` — for locked files only: `critical` (always loaded into canonical) or `reference` (eligible for trim/stub under canonical budget pressure). Default when absent is `critical`. See `build-workspace-context.mjs` for selection semantics.
38
- - `topic` — kebab-case slug matching the filename (after the type prefix, when one is present).
39
- - `author` — username scope owner. Required for `team-member/{user}/` files.
40
- - `updated` — ISO date of last meaningful edit. `/maintenance` flags stale `lifecycle: active` files based on this.
41
-
42
- **Index-feeding field:**
43
-
44
- - `description` — one-line summary, used verbatim by `workspace-context/index.md` and per-user team-member indexes. When omitted, the index falls back to the first sentence of the body, then the filename slug (with the `braindump_`/`handoff_`/`research_` prefix stripped). Adding a `description:` to a file with a weak fallback is the cheapest way to improve the index.
45
-
46
- **Optional confidence marker:**
47
-
48
- - `confidence` — `high` | `medium` | `low`. Apply to research, design, and exploration files where the conclusions might still shift. Skip on locked files (locked = high by definition) and on workflow artifacts like handoffs and braindumps. The frontmatter integrity check in `/maintenance` validates the value if present.
49
-
50
- **Example for a research file:**
51
-
52
- ```yaml
53
- ---
54
- state: ephemeral
55
- lifecycle: active
56
- type: research
57
- topic: vector-search-evaluation
58
- description: Evaluation of FAISS for workspace-context — concluded NL index is sufficient at our scale.
59
- author: alex
60
- confidence: medium
61
- updated: 2026-04-25
62
- ---
63
- ```
64
-
65
- ## Workspace-Context Auto-Generated Files
66
-
67
- A single generator at `.claude/scripts/build-workspace-context.mjs` produces three artifacts in one pass:
68
-
69
- - `workspace-context/index.md` — navigation catalog of everything under `shared/` (locked files first, then the rest). Imported by the workspace-level `CLAUDE.md`.
70
- - `workspace-context/canonical.md` — verbatim concatenation of `shared/locked/*.md` so team truths are loaded into every session prompt. Also imported by `CLAUDE.md`.
71
- - `workspace-context/team-member/{user}/index.md` — per-user navigation catalog, one per team member. Imported by each user's gitignored `CLAUDE.local.md`.
72
-
73
- Gitignored files (e.g. anything matching `local-only-*`) are excluded automatically, and `workspace-context/.indexignore` adds path-prefix excludes for tracked files that shouldn't appear in the shared index (e.g. archived release notes).
74
-
75
- When `workspace-context/canonical.md` exceeds `workspace.canonicalBudgetBytes` (default 40960), the builder honors per-file `priority` and section-level `<!-- canonical:trim --> ... <!-- canonical:end-trim -->` markers to fit: `priority: reference` files are trimmed first, then stubbed, while `priority: critical` files are always included in full. `/maintenance` audits the budget and offers a triage flow when over.
76
-
77
- ```bash
78
- node .claude/scripts/build-workspace-context.mjs --check --root . # exits 1 if any artifact is stale or missing
79
- node .claude/scripts/build-workspace-context.mjs --write --root . # regenerate all three
80
- ```
81
-
82
- `/maintenance` checks staleness in audit mode and regenerates in cleanup mode. Hand edits to `index.md`, `canonical.md`, or any `team-member/{user}/index.md` are overwritten — update source files (or their `description:` frontmatter) instead.
83
-
84
- ## What Belongs in Canonical
85
-
86
- Canonical content is loaded verbatim into every session prompt. It frames how Claude reads the rest of the conversation, so the bar for what goes in is narrower than the bar for `shared/` (root) or `team-member/{user}/`. The principle: canonical should describe what *is* and what *to do*, not what *to think*.
87
-
88
- **Belongs in canonical (`shared/locked/`):**
89
-
90
- - **Facts about the system** — current architecture, supported targets, naming conventions, where things live, what's published.
91
- - **Hard constraints** — "must work on Windows and macOS," "PRs only, no direct push to main," "this directory is gitignored." Constraints scope the solution space without prejudging which solution to pick.
92
- - **Process rules** — workflow discipline that applies regardless of the task. Branch protection, release cadence, mandatory review gates.
93
- - **Settled-rejection guardrails** — "we evaluated approach X, rejected because Y, do not propose this again." Saves Claude from spending tokens re-proposing already-evaluated options. The guardrail must include the *why*, so future-Claude can recognize when an edge case actually warrants revisiting the rejection.
94
- - **Meta-principles for debiasing** — explicit reminders to widen evaluation, like the dogfood-bias risk doc. Anti-shading by design.
95
-
96
- **Does NOT belong in canonical:**
97
-
98
- - **Opinions on open technical questions** — "library X is better than Y for this kind of problem," "approach A is preferred over B." These shape Claude's reasoning starting point so it begins from the conclusion rather than reasoning toward it. If the team has a strong preference, write it as a constraint ("use X, not Y") with the reason — or keep it in `shared/` (root) where it is reference material but not always-loaded.
99
- - **Conclusions Claude might be asked to question** — "we believe the architecture is correct," "feature Z is the right shape." If a topic is the subject of ongoing design work, locking a conclusion biases the discussion before it starts.
100
- - **Personal preferences** — what one contributor finds elegant or annoying. Belongs in `team-member/{user}/`.
101
- - **Status snapshots that age fast** — sprint-current priorities, "we're working on X this week." `project-status.md` is the bounded exception for high-level project status; finer-grained "what's in flight" lives in the tracker.
102
-
103
- **The test before promoting to canonical:**
104
-
105
- > If Claude read this for the first time during a session about an unrelated topic, would it (a) help frame the problem correctly, or (b) push Claude toward a particular answer to a question that hasn't been asked yet?
106
-
107
- (a) is canonical. (b) is `shared/` (root) at most, more often `team-member/{user}/`.
108
-
109
- The distinction matters because pre-loaded conclusions in always-loaded context don't read like opinions to Claude — they read like ground truth. A reference doc Claude *finds* during research is weighed against the question; a canonical doc loaded before the question is asked frames what Claude considers in the first place. `/release` and `/promote` should apply this test before locking content.
@@ -1,90 +0,0 @@
1
- # Work Item Tracking
2
-
3
- When a workspace has an issue tracker configured, all work items — bugs, features, chores — live in that tracker. Skills and scripts read and write the tracker through the adapter at `.claude/scripts/trackers/{type}.mjs`. There is no local file that mirrors the tracker's state.
4
-
5
- ## Why external-first
6
-
7
- - **Atomic assignment.** Two teammates can't accidentally start the same ticket — the tracker is the source of truth for "who has this."
8
- - **Real-time state.** Status changes propagate to the whole team the moment they happen, not after a commit + push.
9
- - **Tool parity.** Humans and Claude see the same list of tickets in the same place.
10
-
11
- ## Configuration
12
-
13
- `workspace.json` → `workspace.tracker`:
14
-
15
- ```json
16
- {
17
- "workspace": {
18
- "tracker": {
19
- "type": "github-issues",
20
- "repo": "your-org/your-workspace"
21
- }
22
- }
23
- }
24
- ```
25
-
26
- - `type` — identifies the adapter module at `.claude/scripts/trackers/{type}.mjs`. Only `github-issues` ships in the template; others are additive.
27
- - `repo` — adapter-specific. For `github-issues`, the owner/name slug of the repo where issues live. `"auto"` resolves to the workspace's own git remote.
28
-
29
- Absence of `workspace.tracker` means tracking is disabled. Skills handle this by falling back to a blank/describe-the-work flow — they do not fabricate a local mirror.
30
-
31
- ## Adapter interface (for Claude)
32
-
33
- Import from `.claude/scripts/trackers/interface.mjs`:
34
-
35
- ```javascript
36
- import { createTracker, AlreadyAssignedError } from '.claude/scripts/trackers/interface.mjs';
37
-
38
- const tracker = createTracker(workspace.tracker);
39
-
40
- const mine = await tracker.listAssignedToMe(); // Issue[]
41
- const open = await tracker.listUnassigned(); // Issue[]
42
- const issue = await tracker.claim('gh:42'); // throws AlreadyAssignedError on contention
43
- const created = await tracker.createIssue({ title, body, labels: ['feat', 'P2'], milestone: 'Backlog' });
44
- await tracker.comment('gh:42', 'paused here; see branch X');
45
- await tracker.closeIssue('gh:42', { comment: 'shipped in PR #99' });
46
-
47
- // Setup-time: idempotent milestone / label creation
48
- await tracker.ensureLabels(); // creates bug/feat/chore/P1/P2/P3 if absent
49
- await tracker.ensureMilestone({ title: 'Backlog', description: 'Triage later' });
50
- ```
51
-
52
- All skills that touch work items use this interface. Adapters are not called directly.
53
-
54
- ## Session linkage
55
-
56
- When `/start-work` links a session to a tracker issue, the session tracker's frontmatter gets:
57
-
58
- ```yaml
59
- workItem: gh:42
60
- ```
61
-
62
- The value is the adapter-prefixed issue ID. This survives adapter swaps — replacing the GitHub adapter with a Linear adapter later doesn't require re-linking session trackers (though the prefix changes for *new* sessions).
63
-
64
- ## When to create issues
65
-
66
- - **User describes new work during `/start-work`** → skill calls `createIssue` after session creation.
67
- - **Bug or feature discovered mid-session** → Claude proactively asks "Create an issue for this? [Y/n]"; if yes, calls `createIssue` and links the session (if it's scoped to this session) or leaves it unassigned (if it's a future concern).
68
- - **Never during braindumps or handoffs** — those are discussion artifacts, not work items. Action items can later graduate to issues during `/start-work`.
69
-
70
- ## When NOT to maintain local state
71
-
72
- - Do not create, write to, or read `workspace-context/open-work.md`. That file is deprecated.
73
- - Do not write ticket state into `session.md` frontmatter beyond the `workItem:` pointer. Status, assignment, milestone, and labels live in the tracker.
74
- - Do not cache issue bodies locally. Always fetch via `tracker.getIssue(id)` when the content is needed.
75
-
76
- ## Skill behavior
77
-
78
- Skills that interact with the tracker:
79
-
80
- - **`/setup-tracker`** — configures `workspace.json` → `tracker` block, calls `ensureLabels()`.
81
- - **`/start-work`** — fetches assigned-to-me first; falls back to unassigned; claims atomically on pick. Records `workItem:` in session tracker.
82
- - **`/pause-work`** — comments the pause capture on the linked issue.
83
- - **`/complete-work`** — closes the linked issue after PRs merge, with a final comment linking them.
84
- - **`/workspace-init`** — prompts to run `/setup-tracker` at the end of init. Does not pre-populate tickets.
85
-
86
- ## What this rule does NOT do
87
-
88
- - Does not prescribe a specific tracker type. Adapter choice is per workspace.
89
- - Does not prescribe label or milestone schemas beyond the six standard labels (`bug`, `feat`, `chore`, `P1`, `P2`, `P3`) created by `ensureLabels()`. Teams with existing trackers can skip label creation during setup.
90
- - Does not replace tracker-native features (comments, reactions, linked PRs) — use the tracker's UI for those.
@@ -1,137 +0,0 @@
1
- # Workspace Structure
2
-
3
- This workspace follows the claude-workspace convention. All paths are relative to the workspace root.
4
-
5
- ## Directory Layout
6
-
7
- | Directory | Purpose | Tracked in git? |
8
- |-----------|---------|-----------------|
9
- | `repos/` | Source clones of project repositories (one per repo, stays on default branch) | No (gitignored, lazy) |
10
- | `work-sessions/` | Per-session folders — one folder per active or paused work session | No (gitignored entirely at the launcher) |
11
- | `work-sessions/{name}/workspace/` | Workspace worktree for this session, on the session branch | Yes — on the session branch, not on main |
12
- | `work-sessions/{name}/workspace/session.md` | Unified session tracker at the top of the session branch (frontmatter = machine state, body = human content) | Yes — on the session branch |
13
- | `work-sessions/{name}/workspace/design-*.md` | Specs for this session — consumed into release notes by /complete-work | Yes — on the session branch |
14
- | `work-sessions/{name}/workspace/plan-*.md` | Plans for this session — consumed into release notes by /complete-work | Yes — on the session branch |
15
- | `work-sessions/{name}/workspace/goal-*.md` | Goal artifacts for /goal-driven multi-phase work — consumed into release notes by /complete-work | Yes — on the session branch |
16
- | `work-sessions/{name}/workspace/research-*.md` | Phase-output research artifacts produced by goal-driven sessions — consumed into release notes by /complete-work | Yes — on the session branch |
17
- | `work-sessions/{name}/workspace/crossref-*.md` | Phase-output crossref artifacts produced by goal-driven sessions — consumed into release notes by /complete-work | Yes — on the session branch |
18
- | `work-sessions/{name}/workspace/repos/` | Real directory holding nested project worktrees for this session | No (gitignored) |
19
- | `work-sessions/{name}/workspace/repos/{repo}/` | Project worktree nested inside the workspace worktree | No (gitignored) |
20
- | `workspace-context/` | Team knowledge and per-user context | Yes |
21
- | `workspace-context/shared/` | Team-visible content — handoffs, braindumps, research, references | Yes |
22
- | `workspace-context/shared/locked/` | Canonical team truths — auto-concatenated into `canonical.md` and loaded into every session | Yes |
23
- | `workspace-context/team-member/{user}/` | Per-user working context — default destination for personal captures | Yes |
24
- | `workspace-context/index.md` | Auto-generated navigation catalog of `shared/` (locked first, then ephemerals) | Yes |
25
- | `workspace-context/canonical.md` | Auto-generated verbatim concatenation of `shared/locked/*.md` | Yes |
26
- | `workspace-context/team-member/{user}/index.md` | Auto-generated per-user navigation catalog | Yes |
27
- | `workspace-context/.indexignore` | Path prefixes to exclude from `index.md` (e.g., archived release notes) | Yes |
28
- | `workspace-context/release-notes/` | Per-branch release-note artifacts — `unreleased/` and `archive/` | Yes |
29
- | `workspace-scratchpad/` | Disposable workspace-scoped files — session log, hook debug output | No (gitignored, lazy) |
30
- | `CLAUDE.md` | Workspace launcher prompt — imports `canonical.md` and `index.md` | Yes |
31
- | `CLAUDE.local.md` | Per-user prompt — imports `team-member/{user}/index.md` | No (gitignored) |
32
- | `.claude/` | Claude Code configuration — rules, agents, skills, hooks, scripts, lib | Yes (except settings.local.json) |
33
-
34
- Session content (tracker, specs, plans) lives at the top of each session's workspace worktree. It is tracked on the session branch, not on main. Pushing the session branch carries durable session thinking across machines. When `/complete-work` finalizes the session, it synthesizes the content into release notes and removes the files from the branch before the final PR so main's top level stays free of session artifacts.
35
-
36
- ## Workspace-Context Levels
37
-
38
- Three layers, in increasing trust order:
39
-
40
- | Level | Path | What lives there | How it gets there |
41
- |-------|------|-----------------|--------------------|
42
- | Personal | `team-member/{user}/` | Per-user braindumps, handoffs, research notes | Default destination for `/braindump`, `/handoff`, `/aside` |
43
- | Shared | `shared/` (root) | Team-visible ephemerals — cross-team handoffs, post-release leftovers, references | Explicit choice via `--scope shared` or `/promote` |
44
- | Canonical | `shared/locked/` | Promoted truths — naming conventions, post-release discipline, project status | Promoted by `/release` (or `/promote` with explicit locked target) |
45
-
46
- Canonical content is verbatim-loaded into every session via `CLAUDE.md` → `@workspace-context/canonical.md`. Personal content is loaded only for the active user via the gitignored `CLAUDE.local.md`.
47
-
48
- Inflight session state lives inside the session worktree at `work-sessions/{name}/workspace/session.md`, not in `workspace-context/`. Workspace-context is for knowledge that outlives any individual session.
49
-
50
- ## Dynamic context loading (hooks)
51
-
52
- Two hooks extend static `CLAUDE.md` loading with context that varies per session and per invocation:
53
-
54
- - **`session-start.mjs`** (`SessionStart` hook): reads the active session pointer from `workspace-scratchpad/` and injects the current session's name, branch, linked work item, and shared context catalog into Claude's context. The injection is conditional — if no session is active, or if the relevant fields are absent from the session frontmatter, nothing is added. This avoids noise in non-session contexts (e.g., a quick launcher query).
55
-
56
- - **`subagent-start.mjs`** (`SubagentStart` hook): reads every file under `workspace-context/shared/locked/` and injects their content into subagent context. A `subagentContextMaxBytes` field in `workspace.json` (default 10240) acts as a byte-budget fallback — if the total locked content exceeds the budget, files are truncated in reverse-priority order rather than silently dropped. This ensures subagents that never load `CLAUDE.md` still receive canonical team truths.
57
-
58
- Both hooks are at `.claude/hooks/session-start.mjs` and `.claude/hooks/subagent-start.mjs`. They are registered as Node.js scripts — cross-platform, no shell dependency.
59
-
60
- ## Spec and Plan Locations — MANDATORY OVERRIDE
61
-
62
- **Specs, plans, and goal artifacts MUST be written at the top of the active session's workspace worktree, not to `docs/superpowers/` or any other location.**
63
-
64
- - Specs: `design-{topic}.md` at the top of `work-sessions/{session-name}/workspace/`
65
- - Plans: `plan-{topic}.md` at the top of `work-sessions/{session-name}/workspace/`
66
- - Goals: `goal-{topic}.md` at the top of `work-sessions/{session-name}/workspace/`, with goal-native phase outputs as `research-{topic}.md` and `crossref-{topic}.md` siblings. See `goal-driven-work.md` for the schema and when to reach for `/goal`.
67
-
68
- From inside the worktree, these are plain top-level files (`design-{topic}.md`, `plan-{topic}.md`, `goal-{topic}.md`) sitting alongside `CLAUDE.md` and `workspace.json`. They are tracked on the session branch and travel with the branch on `git push`.
69
-
70
- This overrides any default paths specified by external skills (e.g., Superpowers brainstorming defaults to `docs/superpowers/specs/`). Those skills state that user preferences override their defaults — this rule IS that override. Do not create `docs/superpowers/` directories. Do not write specs, plans, or goal artifacts anywhere other than the top of the active worktree.
71
-
72
- If a spec/plan/goal already exists for the current session, version it: `design-{topic}-v2.md`, `design-{topic}-v3.md`.
73
-
74
- `/complete-work` reads specs, plans, and goal artifacts (including `research-*.md` and `crossref-*.md` phase outputs) from the worktree to synthesize release notes, then removes them in a dedicated commit before the final PR so main's tree stays pristine.
75
-
76
- ## File Naming Conventions
77
-
78
- - Session folders: `work-sessions/{session-name}/`
79
- - Workspace worktrees: `work-sessions/{session-name}/workspace/`
80
- - Project worktrees: `work-sessions/{session-name}/workspace/repos/{repo-name}/`
81
- - Session trackers: `work-sessions/{session-name}/workspace/session.md`
82
- - Specs: `design-{topic}.md` (top of worktree)
83
- - Plans: `plan-{topic}.md` (top of worktree)
84
- - Goals: `goal-{topic}.md` (top of worktree)
85
- - Goal-native research outputs: `research-{topic}.md` (top of worktree)
86
- - Goal-native crossref outputs: `crossref-{topic}.md` (top of worktree)
87
-
88
- For ephemeral content under `shared/` and `team-member/{user}/`, the filename prefix signals the type:
89
-
90
- | Skill | Filename prefix |
91
- |-------|-----------------|
92
- | `/braindump` | `braindump_{topic}.md` |
93
- | `/handoff` | `handoff_{topic}.md` |
94
- | `/aside` (full mode, dispatches researcher) | `research_{topic}.md` |
95
- | `/aside --quick` | `braindump_{topic}.md` (with `variant: aside` in frontmatter) |
96
- | `/promote` | preserves source prefix |
97
- | `/release` | strips prefix when locking — `shared/locked/` files use bare names since location signals the type |
98
-
99
- Local-only personal drafts get an additional `local-only-` prefix (e.g., `local-only-braindump_x.md`) which keeps them gitignored until promoted.
100
-
101
- ## Rules
102
-
103
- - The workspace root stays on main — it is the launcher, not the workspace.
104
- - All real work happens in workspace worktrees at `work-sessions/{name}/workspace/`.
105
- - Session content (tracker, specs, plans) is written from inside the worktree and committed on the session branch. Writes from the launcher cannot reach files that live inside a worktree's git-path space.
106
- - Source clones at `repos/{repo-name}/` stay on their default branch — never checkout a feature branch there.
107
- - `workspace-scratchpad/` is for disposable files only — session log, hook debug output, temporary pointers.
108
- - Project worktrees are nested inside the workspace worktree's real `repos/` directory — no symlink.
109
- - Hand edits to `index.md`, `canonical.md`, or any per-user `team-member/{user}/index.md` are overwritten by `build-workspace-context.mjs`. Update source files (or their `description:` frontmatter) instead.
110
-
111
- ## Per-repo commands
112
-
113
- Per-repo test, lint, and build commands belong in `repos/{repo}/CLAUDE.md` under a `## Commands` section. This scopes Claude's command invocations to the specific repo rather than triggering monorepo-wide runs that may time out or produce irrelevant output. The `/workspace-init` skill scaffolds a blank `repos/{repo}/CLAUDE.md` stub with a `## Commands` placeholder — fill it in once the repo is cloned.
114
-
115
- ## Explore before editing
116
-
117
- Before modifying files in a large or unfamiliar codebase, use read-only tools to map the affected surface. The workflow: dispatch a researcher-type subagent to read, grep, and navigate the codebase; have it return a summary of the affected files, callers, and dependencies; then edit only after the map is established.
118
-
119
- The `researcher.md` agent enforces this pattern mechanically via `disallowedTools: [Edit, Write, Bash]` — it can read and search but cannot change anything. Use it for initial exploration, then hand the findings back to the main agent for the actual edit. This avoids partial edits that break callers, catches ripple effects before they happen, and keeps the edit surface as small as possible.
120
-
121
- ## Launching Claude from a project worktree
122
-
123
- Claude can be launched from any directory, and it walks up the filesystem loading every `CLAUDE.md` it finds. This means starting `claude` from `work-sessions/{name}/workspace/repos/{repo}/` loads both the per-repo conventions (from `repos/{repo}/CLAUDE.md`, if it exists) and the full workspace conventions (from the workspace `CLAUDE.md` further up the tree) — all without extra configuration.
124
-
125
- For repo-focused work — debugging a single service, reviewing a specific module, running targeted tests — launching from the project worktree gives Claude a tighter codebase context. It sees the repo's own file tree first and reaches workspace-level conventions by traversal. The session hooks still fire (they read from `workspace-scratchpad/`, which is always relative to the workspace root), and `session.md` and all session artifacts remain at the workspace worktree top.
126
-
127
- This is purely a launch-point choice; no workspace configuration changes are needed to enable it.
128
-
129
- ## Grep vs LSP
130
-
131
- Two complementary search strategies cover different parts of the navigation surface:
132
-
133
- - **Grep / Ripgrep** — searches file content as text. Fast, requires no server, and works across any file type. Use for free-text pattern search: finding a string literal, locating config values, scanning comments, searching across heterogeneous files. The downside: no language awareness — a search for `_toMs` matches comments, string literals, and variable names alike, producing false positives that require manual filtering.
134
-
135
- - **LSP tools (`mcp__lsp__*`)** — powered by a running Language Server Protocol server that has indexed the codebase. LSP understands the language's type system and scope rules, so `find-all-references` on `_toMs` returns only actual symbol usages, not textual coincidences. Go-to-definition, rename-symbol, and callers/callees are accurate even across files and module boundaries. The tradeoff: requires a running LSP MCP server configured in `.mcp.json`.
136
-
137
- The practical rule: reach for Grep first when you don't know where to look or when the pattern is not a symbol. Switch to LSP when you have a specific symbol and need precise cross-file navigation — especially before refactoring or understanding a call graph. The `researcher.md` agent lists the LSP tool among its allowed tools; activating LSP requires adding the appropriate language server to `.mcp.json` (see the MCP servers step in `/workspace-init`).
@@ -1,247 +0,0 @@
1
- #!/usr/bin/env node
2
- // Tear down a work session's worktrees, branches, and folder.
3
- //
4
- // Teardown order is MANDATORY:
5
- // 1. Remove each project worktree from its project repo
6
- // 2. Remove the workspace worktree from the workspace repo
7
- // 3. Prune each project repo (belt-and-suspenders)
8
- // 4. Delete all local branches
9
- // 5. Remove the whole work-sessions/{name}/ folder
10
- //
11
- // Workspace-first removal silently deletes the nested project worktrees'
12
- // .git files and leaves orphan worktree records in the project repos.
13
- // The safe order keeps both sides of the relationship in sync.
14
- //
15
- // State discovery is defensive: the session tracker (session.md) may have
16
- // been stripped by /complete-work Step 7 before this script runs, leaving
17
- // no `repos:` or `branch:` to read. When that happens we discover repos
18
- // from the work-sessions/{name}/workspace/repos/ directory listing and the
19
- // branch from `git branch --show-current` on the workspace worktree —
20
- // BEFORE removing anything. Without that, the per-repo loops silently
21
- // no-op and the script reports success while leaving orphans behind
22
- // (gh:119).
23
- //
24
- // `success: true` means VERIFIED: every project worktree record is gone
25
- // (no prunable entries left over), every local branch is deleted, and the
26
- // session folder is removed. The script post-verifies all of these and
27
- // surfaces any leftover state as an error rather than swallowing it.
28
- import '../lib/require-node.mjs';
29
- import { execSync } from 'child_process';
30
- import { existsSync, readdirSync, statSync } from 'fs';
31
- import { join } from 'path';
32
- import {
33
- getWorkspaceRoot,
34
- readSessionTracker,
35
- deleteSessionFolder,
36
- sessionFolderPath,
37
- normalizeRepos,
38
- } from '../hooks/_utils.mjs';
39
-
40
- const args = process.argv.slice(2);
41
- const getArg = (name) => {
42
- const idx = args.indexOf(`--${name}`);
43
- return idx >= 0 && args[idx + 1] ? args[idx + 1] : null;
44
- };
45
-
46
- const sessionName = getArg('session-name');
47
- if (!sessionName) {
48
- console.error('Usage: cleanup-work-session.mjs --session-name NAME');
49
- process.exit(1);
50
- }
51
-
52
- const root = getWorkspaceRoot(import.meta.url);
53
- const reposDir = join(root, 'repos');
54
- const sessionFolder = sessionFolderPath(root, sessionName);
55
- const wsWorktree = join(sessionFolder, 'workspace');
56
-
57
- const removed = [];
58
- const skipped = [];
59
- const errors = [];
60
-
61
- // === Discovery: where session.md is silent or missing, fall back to disk ===
62
- //
63
- // The tracker may have been stripped before this script runs. Read whatever
64
- // is still there, then fill gaps from the live worktree state.
65
-
66
- const tracker = readSessionTracker(root, sessionName);
67
- let repos = normalizeRepos(tracker?.repos);
68
- let branch = tracker?.branch || null;
69
-
70
- // If repos is empty, discover from work-sessions/{name}/workspace/repos/.
71
- // That directory is the workspace worktree's nested-project-worktrees dir;
72
- // each entry is one project repo this session checked out.
73
- if (repos.length === 0) {
74
- const nestedReposDir = join(wsWorktree, 'repos');
75
- if (existsSync(nestedReposDir)) {
76
- try {
77
- const discovered = readdirSync(nestedReposDir).filter((entry) => {
78
- try {
79
- return statSync(join(nestedReposDir, entry)).isDirectory();
80
- } catch {
81
- return false;
82
- }
83
- });
84
- if (discovered.length > 0) {
85
- repos = discovered;
86
- skipped.push({
87
- step: 'discovery',
88
- reason: `Tracker missing repos; discovered ${discovered.length} from ${nestedReposDir}: ${discovered.join(', ')}`,
89
- });
90
- }
91
- } catch (err) {
92
- skipped.push({ step: 'discovery', reason: `Failed to list ${nestedReposDir}: ${err.message}` });
93
- }
94
- }
95
- }
96
-
97
- // If branch is missing, ask the workspace worktree itself.
98
- if (!branch && existsSync(wsWorktree)) {
99
- try {
100
- branch = execSync('git rev-parse --abbrev-ref HEAD', { cwd: wsWorktree, stdio: 'pipe', encoding: 'utf-8' }).trim();
101
- if (branch) {
102
- skipped.push({ step: 'discovery', reason: `Tracker missing branch; discovered from worktree: ${branch}` });
103
- }
104
- } catch (err) {
105
- skipped.push({ step: 'discovery', reason: `Failed to read branch from ${wsWorktree}: ${err.message}` });
106
- }
107
- }
108
-
109
- // === Step 1: Remove each project worktree FIRST, from its project repo ===
110
- for (const repo of repos) {
111
- const projWorktree = join(wsWorktree, 'repos', repo);
112
- const repoDir = join(reposDir, repo);
113
- if (!existsSync(projWorktree)) {
114
- skipped.push({ step: 'remove-project-worktree', repo, reason: `${projWorktree} does not exist` });
115
- continue;
116
- }
117
- if (!existsSync(repoDir)) {
118
- errors.push(`Cannot remove ${repo} worktree: source clone missing at ${repoDir}`);
119
- continue;
120
- }
121
- try {
122
- execSync(`git worktree remove "${projWorktree}" --force`, { cwd: repoDir, stdio: 'pipe' });
123
- removed.push(`project worktree ${repo}`);
124
- } catch (err) {
125
- errors.push(`Failed to remove ${repo} worktree: ${err.message.trim()}`);
126
- }
127
- }
128
-
129
- // === Step 2: Remove the workspace worktree AFTER project worktrees are gone ===
130
- if (existsSync(wsWorktree)) {
131
- try {
132
- execSync(`git worktree remove "${wsWorktree}" --force`, { cwd: root, stdio: 'pipe' });
133
- removed.push('workspace worktree');
134
- } catch (err) {
135
- errors.push(`Failed to remove workspace worktree: ${err.message.trim()}`);
136
- }
137
- } else {
138
- skipped.push({ step: 'remove-workspace-worktree', reason: `${wsWorktree} does not exist` });
139
- }
140
-
141
- // === Step 3: Prune each project repo to mop up orphans ===
142
- for (const repo of repos) {
143
- const repoDir = join(reposDir, repo);
144
- if (!existsSync(repoDir)) continue;
145
- try {
146
- execSync('git worktree prune', { cwd: repoDir, stdio: 'pipe' });
147
- } catch (err) {
148
- // Prune is a safety net, but if it fails on a repo we touched, surface
149
- // it — verification below will catch leftover orphans either way.
150
- errors.push(`Prune failed in ${repo}: ${err.message.trim()}`);
151
- }
152
- }
153
-
154
- // === Step 4: Delete local branches ===
155
- if (branch) {
156
- for (const repo of repos) {
157
- const repoDir = join(reposDir, repo);
158
- if (!existsSync(repoDir)) continue;
159
- try {
160
- execSync(`git branch --list "${branch}"`, { cwd: repoDir, stdio: 'pipe', encoding: 'utf-8' });
161
- } catch {
162
- continue; // Repo broken; verification will catch downstream impact.
163
- }
164
- const exists = execSync(`git branch --list "${branch}"`, { cwd: repoDir, encoding: 'utf-8' }).trim();
165
- if (!exists) continue; // Already gone (e.g., gh pr merge --delete-branch did it).
166
- try {
167
- execSync(`git branch -D "${branch}"`, { cwd: repoDir, stdio: 'pipe' });
168
- } catch (err) {
169
- errors.push(`Failed to delete branch ${branch} in ${repo}: ${err.message.trim()}`);
170
- }
171
- }
172
- // Same for the workspace repo (root).
173
- try {
174
- const exists = execSync(`git branch --list "${branch}"`, { cwd: root, encoding: 'utf-8' }).trim();
175
- if (exists) {
176
- try {
177
- execSync(`git branch -D "${branch}"`, { cwd: root, stdio: 'pipe' });
178
- } catch (err) {
179
- errors.push(`Failed to delete branch ${branch} in workspace repo: ${err.message.trim()}`);
180
- }
181
- }
182
- } catch {
183
- // workspace root not a git repo — unusual, skip.
184
- }
185
- }
186
-
187
- // === Step 5: Delete the whole work-sessions/{name}/ folder ===
188
- try {
189
- deleteSessionFolder(root, sessionName);
190
- } catch (err) {
191
- errors.push(`Failed to delete session folder ${sessionFolder}: ${err.message.trim()}`);
192
- }
193
-
194
- // === Post-verification: success means VERIFIED, not "no try/catch threw" ===
195
- //
196
- // Without these checks, an empty repos list (the gh:119 root cause) lets
197
- // every silent skip add up to a "success" output while leaving orphans
198
- // behind. The verification turns silent skips into honest errors.
199
-
200
- if (existsSync(sessionFolder)) {
201
- errors.push(`Session folder still present after cleanup: ${sessionFolder}`);
202
- }
203
-
204
- const wsPath = wsWorktree; // canonical path the worktree had
205
- for (const repo of repos) {
206
- const repoDir = join(reposDir, repo);
207
- if (!existsSync(repoDir)) continue;
208
- let wtList = '';
209
- try {
210
- wtList = execSync('git worktree list --porcelain', { cwd: repoDir, encoding: 'utf-8' });
211
- } catch (err) {
212
- errors.push(`Could not list worktrees in ${repo}: ${err.message.trim()}`);
213
- continue;
214
- }
215
- if (wtList.includes('prunable')) {
216
- errors.push(`Prunable worktree record remains in ${repo} after cleanup (gh:119 symptom)`);
217
- }
218
- if (wtList.includes(wsPath)) {
219
- errors.push(`${repo} still has a worktree record referencing the session path`);
220
- }
221
- if (branch) {
222
- const branchStill = execSync(`git branch --list "${branch}"`, { cwd: repoDir, encoding: 'utf-8' }).trim();
223
- if (branchStill) {
224
- errors.push(`Branch ${branch} still present in ${repo} after cleanup`);
225
- }
226
- }
227
- }
228
-
229
- if (branch) {
230
- try {
231
- const branchStill = execSync(`git branch --list "${branch}"`, { cwd: root, encoding: 'utf-8' }).trim();
232
- if (branchStill) {
233
- errors.push(`Branch ${branch} still present in workspace repo after cleanup`);
234
- }
235
- } catch {
236
- // not a git repo — already noted
237
- }
238
- }
239
-
240
- console.log(JSON.stringify({
241
- success: errors.length === 0,
242
- removed,
243
- skipped: skipped.length > 0 ? skipped : undefined,
244
- errors: errors.length > 0 ? errors : undefined,
245
- }));
246
-
247
- if (errors.length > 0) process.exit(1);