@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
@@ -0,0 +1,91 @@
1
+ ---
2
+ name: release
3
+ description: Cut a versioned release of one project repo — bump the version, merge it through a PR, tag it, and publish a forge release whose notes are generated from merged PRs. No release-notes files.
4
+ ---
5
+
6
+ # Release
7
+
8
+ Cut a versioned release of one project repo. The version bump travels through a PR like any other change; the tag marks the merge commit; the forge generates the release notes from merged PR titles. The workspace keeps no release-notes files of its own.
9
+
10
+ ## Why this shape
11
+
12
+ Release notes come from the forge. GitHub's generated notes (merged PR titles) are what users of a published release actually read; the detail behind each PR already lives in the issue and the PR body, so duplicating it into workspace files buys nothing. Projects that use changesets, semantic-release, or their own release tooling simply don't use this skill. A repo's `CHANGELOG.md`, if it has one, is historical — this skill never writes it.
13
+
14
+ ## Parameters
15
+
16
+ - `/release {version}` — release a specific version
17
+ - `/release` — ask for the version
18
+
19
+ ## Flow
20
+
21
+ **Step 1: Determine version and repo**
22
+
23
+ Ask which repo to release — read `repos` from `workspace.json` and default to the entry with `"primary": true`. If no version was given, ask the bump kind (patch/minor/major) after showing the merged PRs since the last tag, so the operator can judge the impact:
24
+
25
+ ```bash
26
+ git -C repos/{repo} describe --tags --abbrev=0
27
+ ```
28
+
29
+ Then call the forge adapter's `prList` with a merged-after search bounded by that tag's date (e.g. `merged:>{date}`). Pre-v1.0 breaking changes are a minor bump.
30
+
31
+ **Step 2: Preflight the tag**
32
+
33
+ If `v{version}` already exists on origin, stop and ask — reuse it, investigate with `forge.releaseView`, or pick another version. Never force-push a tag.
34
+
35
+ ```bash
36
+ git -C repos/{repo} ls-remote --exit-code origin refs/tags/v{version}
37
+ ```
38
+
39
+ **Step 3: Bump on a branch**
40
+
41
+ Create a task worktree for the release branch:
42
+
43
+ ```bash
44
+ node .claude/scripts/task-worktree.mjs --root . --create --repo "{repo}" --branch "release/v{version}"
45
+ ```
46
+
47
+ If the repo has a `package.json` with a `version`, set it to `{version}` (edit the JSON; keep formatting) and update `package-lock.json`'s top-level version fields if that file is present. Commit `chore: release v{version}`. If the repo has no version file, skip the commit — step 5 tags the current default-branch head instead.
48
+
49
+ **Step 4: Merge**
50
+
51
+ Push the branch and open a PR through the forge adapter — per-repo `createForge({ ...ws.workspace?.forge, repo: '{owner}/{name}' })` with head `release/v{version}`. Ask `Merge? [Y/n]`, then merge (squash, delete branch).
52
+
53
+ **Step 5: Tag and publish**
54
+
55
+ Pull the merge, tag it, and push the tag:
56
+
57
+ ```bash
58
+ git -C repos/{repo} pull --ff-only
59
+ git -C repos/{repo} tag v{version}
60
+ git -C repos/{repo} push origin v{version}
61
+ ```
62
+
63
+ Who creates the release depends on the repo. If `.github/workflows/publish.yml` exists and itself creates the release (it contains `gh release create`, `softprops/action-gh-release`, or `actions/create-release`), the workflow owns the release — do not call `releaseCreate`; racing it duplicates the release or fails. Instead find and watch its run with `workflowRunFind` / `workflowRunWatch` — retry the find up to 5 times with 3 s backoff (the run may not be registered the moment the tag lands); a failed run is reported to the operator, not thrown — then confirm the release exists with `forge.releaseView({ tag: 'v{version}', repo })`.
64
+
65
+ Otherwise the skill creates the release itself:
66
+
67
+ ```js
68
+ await forge.releaseCreate({ tag: 'v{version}', repo, generateNotes: true });
69
+ ```
70
+
71
+ If a `publish.yml` without release creation exists, still find and watch its run the same way.
72
+
73
+ **Step 6: Tear down and report**
74
+
75
+ Remove the release worktree:
76
+
77
+ ```bash
78
+ node .claude/scripts/task-worktree.mjs --root . --remove --repo "{repo}" --branch "release/v{version}" --delete-branch
79
+ ```
80
+
81
+ Report the PR, the tag, the release URL, and the publish status.
82
+
83
+ **Step 7: Update workspace release state**
84
+
85
+ If the workspace keeps release state in `workspace-context/` (for example a current-release line in a status file under `shared/locked/`), offer to update it — through a workspace task worktree and PR, never on the launcher.
86
+
87
+ ## Notes
88
+
89
+ - Never run `npm publish` locally. The publish workflow is the only path that exercises OIDC trusted publishing; a local publish requires a 2FA OTP and bypasses it. If the workflow fails, investigate via `gh run view` — do not fall back to a local publish.
90
+ - Recovery from a failed publish: transient failures rerun via `gh run rerun {run_id}`; content failures mean deleting the tag and redoing the release. Once a version is published to a registry, that version number is committed — bump and release a new version instead.
91
+ - Pre-v1.0 breaking changes are a minor bump, not major.
@@ -5,6 +5,8 @@ description: Begin or resume a work session. Creates a self-contained work-sessi
5
5
 
6
6
  # Start Work
7
7
 
8
+ Two lifecycles share this skill. `workspace.sessionModel` in `workspace.json` selects for new work: `"task"` routes new work to **Flow: Task** (session model v2 — no session folder, no `session.md`); absent or `"session"` keeps the existing flows below, unchanged. Resuming an existing `work-sessions/` session always uses the existing Resume flow regardless of the setting.
9
+
8
10
  Begin or resume a persistent work session. Each session lives in its own `work-sessions/{name}/` folder containing one workspace worktree, nested project worktrees, and a unified `session.md` tracker. Sessions can run in parallel from separate terminals.
9
11
 
10
12
  ## Parameters
@@ -13,6 +15,55 @@ Begin or resume a persistent work session. Each session lives in its own `work-s
13
15
  - `/start-work handoff` — list shared context to resume from
14
16
  - `/start-work all` — list active sessions across all users (for shared debugging or multi-user workspaces)
15
17
 
18
+ ## Flow: Task (session model v2)
19
+
20
+ New work as a task: one tracker issue, one branch, one worktree per repo the work touches. This flow creates no `work-sessions/` folder, no `session.md`, and seeds no task list — the issue, the branch, and the chat record are the entire state. The chat stays at the workspace root — `{launcher-root}`, the absolute path on the `Workspace root:` line the SessionStart hook injects (at /start-work time you are normally already there); the worktrees are reached by path.
21
+
22
+ If `workspace.tracker` is absent, say tracking is off and skip step 1 — but still ask for the type (`bug` / `feat` / `chore`) and a one-line description, because the type picks the branch prefix — then continue with steps 2–6. Tell the user plainly what that costs: without a tracker there is no `workItem`, the task is not recorded on the chat record, and `/complete-work` cannot find it from the launcher. It is completed either by running `/complete-work` from inside the worktree (cwd detection) or by opening the PR by hand.
23
+
24
+ 1. **Identify or create the tracker issue and claim it.** If the invocation's arguments already name an issue — `gh:N`, `#N`, or an issue URL — normalize it to the adapter's id (`#42` and a `.../issues/42` URL both mean `gh:42`), fetch it with `tracker.getIssue(id)`, claim it when it is not yet assigned to you (with the same `ALREADY_ASSIGNED` handling as the fallback pick below), and skip the candidate list entirely. Otherwise, list the candidates — the same adapter calls as Flow: Blank steps 3–6:
25
+
26
+ ```javascript
27
+ import { createTracker } from './.claude/scripts/trackers/interface.mjs';
28
+ import { readFileSync } from 'node:fs';
29
+ const ws = JSON.parse(readFileSync('workspace.json', 'utf-8'));
30
+ const tracker = createTracker(ws.workspace.tracker);
31
+ const assigned = await tracker.listAssignedToMe();
32
+ const candidates = assigned.length > 0 ? assigned : await tracker.listUnassigned();
33
+ ```
34
+
35
+ Present the list as Flow: Blank step 4 does. When the pick came from the unassigned fallback, claim it atomically and re-fetch on `ALREADY_ASSIGNED` exactly as Blank step 5 shows; for "something new", create and self-assign per Blank step 6:
36
+
37
+ ```javascript
38
+ const newIssue = await tracker.createIssue({
39
+ title: description,
40
+ body: `Created at /start-work by ${user}.`,
41
+ labels: [type, priority],
42
+ milestone: milestone || null,
43
+ });
44
+ await tracker.claim(newIssue.id);
45
+ ```
46
+
47
+ Remember `workItem: {issue.id}`.
48
+
49
+ 2. **Pick repo(s)** — the same numbered multi-select as Blank step 7 (e.g. `1,3` or `all`), defaulting to the repo marked `"primary": true` under `repos` in `workspace.json`, falling back to the first entry when none is marked. The list also offers the **workspace repo itself**, shown as `workspace (this repo)` and addressed as `.`. Include it when the task changes anything tracked in the workspace repo — `workspace-context/`, the workspace's own `.claude/` (rules, hooks, scripts, skills), or, in a dogfood workspace, mirrors of template changes. Steps 4 and 5 take `.` like any other repo name (`--repo "."`).
50
+
51
+ 3. **Propose the branch** — `{prefix}/{slug}` with the prefix from type (`feature/`, `bugfix/`, `chore/`), per the branch-naming step in Flow: Blank.
52
+
53
+ 4. **Create one worktree per repo the work touches:**
54
+ ```bash
55
+ node .claude/scripts/task-worktree.mjs --root . --create --repo "{repo}" --branch "{branch}"
56
+ ```
57
+ The script fetches origin best-effort (offline is fine) before choosing the base. A project repo's worktree lands at `repos/{repo}/.claude/worktrees/{slug}/` — Claude Code's native worktree location — based on `origin/{defaultBranch}` when that ref exists, and never tracking it. For `.` the worktree lands at `.claude/worktrees/{slug}/` — the same native location, one level up — based on the workspace origin's HEAD (falling back to `main`); the workspace's own `.gitignore` already covers the path.
58
+
59
+ 5. **Record the task on this chat's record** (only when a `workItem` exists — see the no-tracker note above):
60
+ ```bash
61
+ node .claude/scripts/chat-record.mjs --root . --add-task --chat "{chat}" --work-item "{workItem}" --branch "{branch}" --repo "{repo}"
62
+ ```
63
+ `{chat}` is the name from the `Chat record:` line the SessionStart hook injected into this conversation. If there is no such line, run `node .claude/scripts/chat-record.mjs --whoami --root .` first — compaction can drop the hook line, and this recovers the name by matching the chat's session id. When that too prints nothing, say so and skip recording rather than guessing a name.
64
+
65
+ 6. **Tell the user where the work happens:** the worktree path(s) above — edits belong there, not in the source clones at `repos/{repo}/`. Work continues from this chat by path. A chat started inside a **project** worktree would not load the workspace's conventions or hooks (a worktree is a context boundary), so staying here is the default. A `.` worktree does load a copy of the workspace's `CLAUDE.md`/`.claude/` — but with the worktree as root, so its chat records land in the worktree's own scratchpad rather than the launcher's; the task still belongs to this chat.
66
+
16
67
  ## Flow: No Parameter
17
68
 
18
69
  1. Read the current user from `.claude/settings.local.json` → `workspace.user`. If unset, behave as `/start-work all` (no user filter). If the user invoked `/start-work all`, also skip filtering.
@@ -40,7 +91,7 @@ Begin or resume a persistent work session. Each session lives in its own `work-s
40
91
  - Workspace: `work-sessions/{name}/workspace/`
41
92
  - For each repo in `repos:` frontmatter: `work-sessions/{name}/workspace/repos/{repo}/`
42
93
  - If any are missing, recreate from the branch
43
- 3. The session-start hook automatically registers each chat in the session tracker's `chatSessions` frontmatter when Claude opens in a worktree. Verify the current chat is registered — if not (e.g., the hook didn't fire), add an entry manually via the session-frontmatter helper.
94
+ 3. The session-start hook automatically registers each chat in the session tracker's `chatSessions` frontmatter when Claude opens in a worktree. Verify the current chat is registered — if not (e.g., the hook didn't fire), append an entry using the invocation shown under "Create work session" below (the helper is an importable library, not a CLI).
44
95
 
45
96
  Each `chatSessions` entry has this shape:
46
97
  ```yaml
@@ -55,7 +106,7 @@ Begin or resume a persistent work session. Each session lives in its own `work-s
55
106
  4. Update the tracker `status:` to `active` if it was `paused`
56
107
  5. Restore the task list from `## Tasks` per the `task-list-mirroring` rule:
57
108
  ```bash
58
- cd work-sessions/{name}/workspace
109
+ cd "work-sessions/{name}/workspace"
59
110
  node .claude/scripts/sync-tasks.mjs --read session.md
60
111
  ```
61
112
  Pass the parsed `todos` array to `TodoWrite` so the live UI matches the durable state. If the section is missing (legacy session predating this feature), seed it first via `--write` with an empty `todos` array — the helper will insert the bookends.
@@ -165,9 +216,40 @@ The script creates:
165
216
  - Active-session pointer at `work-sessions/{session-name}/workspace/.claude/.active-session.json`
166
217
  - Copies `settings.local.json` into the worktree if it exists at the workspace root
167
218
 
168
- If a `workItem:` was set in step 5 or 6, write it into the tracker's frontmatter via the session-frontmatter helper after creation. This is what `/pause-work` and `/complete-work` use to locate the linked issue.
219
+ If a `workItem:` was set in step 5 or 6, write it into the tracker's frontmatter after
220
+ creation. `/pause-work` and `/complete-work` both use this to locate the linked issue, and
221
+ a session created without it looks fine until one of them silently cannot find the ticket.
222
+
223
+ `.claude/lib/session-frontmatter.mjs` is a **library, not a CLI** — running it with flags
224
+ does nothing and exits 2. Import it:
225
+
226
+ ```bash
227
+ cd "work-sessions/{session-name}/workspace"
228
+ node --input-type=module -e '
229
+ import { updateSessionFile, readSessionFields } from "./.claude/lib/session-frontmatter.mjs";
230
+ updateSessionFile("session.md", { workItem: "{workItem}" });
231
+ console.log("workItem =", readSessionFields("session.md").workItem);
232
+ '
233
+ ```
234
+
235
+ Read the value back, as above, and confirm it before moving on — this write has failed
236
+ silently before (gh:143).
237
+
238
+ Register this chat in the tracker's `chatSessions` frontmatter. For new sessions, the session-start hook has already fired (before /start-work was invoked) but the session folder didn't exist yet. Find the current chat's UUID from the most recently modified `.jsonl` file in
239
+ `~/.claude/projects/{project-path}/` and append the entry with the same library:
240
+
241
+ ```bash
242
+ node --input-type=module -e '
243
+ import { updateSessionFile, readSessionFields } from "./.claude/lib/session-frontmatter.mjs";
244
+ const existing = readSessionFields("session.md").chatSessions || [];
245
+ updateSessionFile("session.md", {
246
+ chatSessions: [...existing, { id: "{uuid}", names: [], started: new Date().toISOString(), ended: null }],
247
+ });
248
+ '
249
+ ```
169
250
 
170
- Register this chat in the tracker's `chatSessions` frontmatter. For new sessions, the session-start hook has already fired (before /start-work was invoked) but the session folder didn't exist yet. Find the current chat's UUID from the most recently modified `.jsonl` file in `~/.claude/projects/{project-path}/` and add the entry manually via the session-frontmatter helper. Subsequent chats on this session will be registered automatically by the hook.
251
+ Append — never replace: the list is the session'"'"'s whole chat history. Subsequent chats are
252
+ registered automatically by the hook.
171
253
 
172
254
  The tracker already reflects the correct state — assignment happened in step 5 or 6 via `adapter.claim()`. Do not write to any local file mirror. There is no `open-work.md`.
173
255
 
@@ -177,7 +259,7 @@ After session creation, seed the `## Tasks` section in the new tracker so `TodoW
177
259
 
178
260
  ```bash
179
261
  # Build the seed from inside the worktree so the helper resolves workspace.json correctly.
180
- cd work-sessions/{session-name}/workspace
262
+ cd "work-sessions/{session-name}/workspace"
181
263
  echo '{"todos": []}' | node .claude/scripts/sync-tasks.mjs --write session.md
182
264
  ```
183
265
 
@@ -199,7 +281,7 @@ The auto-commit at the end of "Capture prior conversation context" picks up the
199
281
 
200
282
  ### Capture prior conversation context
201
283
 
202
- If brainstorming, spec writing, or design discussion happened in this conversation before `/start-work` was called, that reasoning needs to be captured into the session tracker body. Otherwise it will be lost when the conversation ends and `/complete-work` will produce thin release notes.
284
+ If brainstorming, spec writing, or design discussion happened in this conversation before `/start-work` was called, that reasoning needs to be captured into the session tracker body. Otherwise it will be lost when the conversation ends and `/complete-work` will write a thin PR body.
203
285
 
204
286
  Check: has the current conversation included substantive discussion (design decisions, requirements exploration, approach selection) before this point?
205
287
 
@@ -208,7 +290,7 @@ If yes:
208
290
  2. Write the summary into `work-sessions/{session-name}/workspace/session.md`'s body, in a `## Pre-session context` or `## Progress` section
209
291
  3. Auto-commit from inside the worktree so the capture lands on the session branch:
210
292
  ```bash
211
- cd work-sessions/{session-name}/workspace
293
+ cd "work-sessions/{session-name}/workspace"
212
294
  git add session.md
213
295
  git commit -m "chore: capture pre-session discussion for {session-name}"
214
296
  ```
@@ -56,6 +56,8 @@ For each repo in `workspace.json`:
56
56
  - If confirmed: `git clone {remote} repos/{name}`
57
57
  - If exists: report "repos/{name} already present"
58
58
 
59
+ While workspace.json is open, confirm the work lifecycle. `workspace.sessionModel` selects where `/start-work` routes new work: `"task"` (one tracker issue + one branch + one worktree per touched repo; recommended for new workspaces) or `"session"` (self-contained `work-sessions/{name}/` folders; the template default — keep it for teams already mid-flight on sessions). Ask: "Which lifecycle should new work use — task (recommended for a new workspace) or session (default)?" and write the answer to `workspace.sessionModel` only if it differs from the current value. Never flip an existing non-default value silently.
60
+
59
61
  ### Step 3: Identify documentation sources
60
62
 
61
63
  Ask the user:
@@ -447,6 +449,6 @@ This session is done. Start a fresh Claude Code session and run /start-work to b
447
449
  - Documentation sources are first-class — always ask, always confirm access, always report failures
448
450
  - Chat history scanning uses a manifest to survive auto-compaction
449
451
  - Existing worktrees are formalized with session markers, trackers, and linked chat history
450
- - **Subdirectory launch:** Once initialized, `claude` can be launched from `work-sessions/{name}/workspace/repos/{repo}/` instead of the workspace root. Claude walks up the filesystem loading every `CLAUDE.md` it finds, so starting from inside a project worktree loads both the per-repo conventions and the workspace conventions automatically. This is useful for repo-focused tasks — no configuration change needed, just a different launch point.
452
+ - **Launch point:** Launch `claude` from the workspace root. A worktree is a context boundary — `CLAUDE.md` discovery stops at its root and gitignored content is absent — so a chat started inside a project worktree (a session's `work-sessions/{name}/workspace/repos/{repo}/` or a task's `repos/{repo}/.claude/worktrees/{slug}/`) loads only that repo's own `CLAUDE.md`, without the workspace conventions or hooks. That is occasionally what you want for isolated, repo-only work; for normal work, stay at the root and reach worktrees by path.
451
453
  - **Skills are on-demand, not pre-loaded:** Skills are invoked explicitly by name (`/skill-name`) when needed; they are not loaded at session start. The `.skip` mechanism in `.claude/rules/` provides the analogous progressive-disclosure pattern for rules — a `.md.skip` file is present but inactive; rename it to `.md` to activate, rename it back to deactivate. Step 6 of this skill walks through the activation choices.
452
454
  - **`scope:` for path-scoped skills:** The `scope:` frontmatter field (shown as a commented-out example at the top of this file) restricts a skill so it activates only when the working directory is inside the declared path. Removing the `#` prefix from the example line turns this skill into a path-scoped skill — useful when you want a repo-specific skill available only from inside that repo's worktree.
@@ -119,6 +119,10 @@ git commit -m "chore: update workspace from template v{fromVersion} to v{toVersi
119
119
 
120
120
  Report: "Workspace updated to v{toVersion}. Restart Claude Code if rules or hooks changed."
121
121
 
122
+ ### Step 8: Session-model migration nudge
123
+
124
+ After the update is applied, if the sessions directory (`workspace.workSessionsDir`, default `work-sessions/`) has entries and `workspace.sessionModel` is not `"task"`, append one line to the report: "This workspace still has {N} session(s) under the session model — `/migrate-sessions` can inventory and drain them and switch to the task model whenever you're ready." Suggest only; the operator decides whether and when.
125
+
122
126
  ## Notes
123
127
 
124
128
  - The CLI (`npx @ulysses-ai/create-workspace --upgrade`) stages the payload. This skill processes it.
@@ -17,6 +17,10 @@ workspace-scratchpad/
17
17
  .claude/.active-session.json
18
18
  CLAUDE.local.md
19
19
 
20
+ # Worktrees of the workspace repo itself — task worktrees (repo ".") and
21
+ # Claude Code's native worktrees both live under .claude/worktrees/
22
+ .claude/worktrees/
23
+
20
24
  # Local-only convention — machine-local state, any depth
21
25
  local-only-*
22
26
 
@@ -26,3 +30,8 @@ local-only-*
26
30
  # OS
27
31
  .DS_Store
28
32
  Thumbs.db
33
+
34
+ # Per-user workspace-context indexes are generated per machine and read only
35
+ # through CLAUDE.local.md, which is itself gitignored — no mechanism reads
36
+ # another user's index. Regenerated by build-workspace-context.mjs on demand.
37
+ workspace-context/team-member/*/index.md
@@ -6,11 +6,12 @@
6
6
  "scratchpadDir": "workspace-scratchpad",
7
7
  "workSessionsDir": "work-sessions",
8
8
  "workspaceContextDir": "workspace-context",
9
- "releaseNotesDir": "workspace-context/release-notes",
10
- "canonicalBudgetBytes": 40960,
11
- "subagentContextMaxBytes": 10240,
9
+ "alwaysLoadedBudgetBytes": 65536,
10
+ "subagentContextMaxBytes": 32768,
11
+ "subagentInlineMaxBytes": 8192,
12
12
  "greeting": "Welcome back to {{project-name}}.",
13
13
  "releaseMode": "per-repo",
14
+ "sessionModel": "session",
14
15
  "tracker": null,
15
16
  "forge": { "type": "github" }
16
17
  },
@@ -1,107 +0,0 @@
1
- #!/usr/bin/env node
2
- // PreToolUse hook — enforce workspace root write restrictions and detect
3
- // out-of-session repo writes.
4
- //
5
- // New layout paths:
6
- // Workspace worktree: work-sessions/{name}/workspace/
7
- // Project worktree: work-sessions/{name}/workspace/repos/{repo}/
8
- // Bare clone: repos/{repo}/ (at workspace root)
9
- import { join, basename } from 'path';
10
- import {
11
- getWorkspaceRoot,
12
- readStdin,
13
- respond,
14
- getActiveSessionPointer,
15
- readSessionTracker,
16
- readJSON,
17
- getWorkspacePaths,
18
- } from './_utils.mjs';
19
-
20
- const root = getWorkspaceRoot(import.meta.url);
21
- const input = await readStdin();
22
- const toolName = input.tool_name || '';
23
-
24
- if (!['Bash', 'Edit', 'Write'].includes(toolName)) {
25
- respond();
26
- process.exit(0);
27
- }
28
-
29
- const toolInput = input.tool_input || {};
30
- const paths = [toolInput.file_path, toolInput.command, toolInput.path]
31
- .filter(Boolean)
32
- .join(' ')
33
- .replace(/\\/g, '/');
34
-
35
- // If we're in a workspace worktree, check for out-of-session repo writes
36
- const pointer = getActiveSessionPointer(root);
37
- if (pointer) {
38
- const mainRoot = pointer.rootPath || root;
39
- const config = readJSON(join(mainRoot, 'workspace.json'));
40
- const tracker = readSessionTracker(mainRoot, pointer.name);
41
-
42
- if (tracker && config?.repos) {
43
- // Find references to repos inside work-sessions/{name}/workspace/repos/{repo}/
44
- // and also the workspace-root repos/{repo}/ for direct writes.
45
- const wtMatch = paths.match(/work-sessions\/[^/\s]+\/workspace\/repos\/([^/\s]+)/);
46
- const cloneMatch = paths.match(/(?:^|\s|\/)repos\/([^/\s]+)/);
47
- const targetRepo = wtMatch ? wtMatch[1] : (cloneMatch ? cloneMatch[1] : null);
48
- if (targetRepo) {
49
- const sessionRepos = tracker.repos || [];
50
- if (config.repos[targetRepo] && !sessionRepos.includes(targetRepo)) {
51
- respond(`You're about to write to ${targetRepo}, which isn't part of this session. Consider adding it first so changes land on the session branch.`);
52
- process.exit(0);
53
- }
54
- }
55
- }
56
-
57
- respond();
58
- process.exit(0);
59
- }
60
-
61
- // We're at the main workspace root — restrict writes
62
-
63
- const { scratchpadDir } = getWorkspacePaths(root);
64
- const scratchpadName = scratchpadDir.slice(root.length + 1); // "workspace-scratchpad"
65
-
66
- // Allow writes to the workspace scratchpad
67
- if (paths.includes(scratchpadName)) {
68
- respond();
69
- process.exit(0);
70
- }
71
-
72
- // Allow writes to local-only-* files
73
- const filePathArg = toolInput.file_path || '';
74
- if (basename(filePathArg).startsWith('local-only-')) {
75
- respond();
76
- process.exit(0);
77
- }
78
-
79
- // For Bash commands, check if the command targets allowed paths
80
- if (toolName === 'Bash') {
81
- const cmd = toolInput.command || '';
82
- if (/^\s*(git|ls|cat|head|tail|grep|rg|find|echo|pwd|cd|which|node\s+-c)\b/.test(cmd)) {
83
- respond();
84
- process.exit(0);
85
- }
86
- if (cmd.includes(scratchpadName) || cmd.includes('local-only-')) {
87
- respond();
88
- process.exit(0);
89
- }
90
- // Allow helper script invocations from the workspace root
91
- if (/node\s+.*\.claude\/scripts\//.test(cmd)) {
92
- respond();
93
- process.exit(0);
94
- }
95
- }
96
-
97
- // Check if this write targets repos/, workspace-context/, work-sessions/, or template files
98
- const isRepoWrite = /(?:^|[\s/])repos\//.test(paths) || paths.includes('work-sessions/');
99
- const isContextWrite = paths.includes('workspace-context/') && !basename(filePathArg).startsWith('local-only-');
100
- const isTemplateWrite = paths.includes('.claude/') && !paths.includes(scratchpadName);
101
-
102
- if (isRepoWrite || isContextWrite || isTemplateWrite) {
103
- respond("You're on main. All work should happen in a workspace worktree. Run /start-work to create or resume a work session.");
104
- process.exit(0);
105
- }
106
-
107
- respond();
@@ -1,44 +0,0 @@
1
- #!/usr/bin/env node
2
- // SubagentStart hook — inject workspace-context/shared/locked/ into subagent context
3
- import { readdirSync, readFileSync, existsSync } from 'fs';
4
- import { join, basename } from 'path';
5
- import { getWorkspaceRoot, readJSON, respond } from './_utils.mjs';
6
-
7
- const root = getWorkspaceRoot(import.meta.url);
8
- const config = readJSON(join(root, 'workspace.json'));
9
- const lockedDir = join(root, 'workspace-context', 'shared', 'locked');
10
-
11
- const maxBytes = config?.workspace?.subagentContextMaxBytes || 10240;
12
-
13
- if (!existsSync(lockedDir)) {
14
- respond();
15
- process.exit(0);
16
- }
17
-
18
- const files = readdirSync(lockedDir)
19
- .filter(f => f.endsWith('.md') && f !== '.keep')
20
- .sort();
21
-
22
- if (files.length === 0) {
23
- respond();
24
- process.exit(0);
25
- }
26
-
27
- let context = '';
28
- for (const file of files) {
29
- const name = basename(file, '.md');
30
- const content = readFileSync(join(lockedDir, file), 'utf-8');
31
- context += `\n--- ${name} ---\n${content}\n`;
32
- }
33
-
34
- if (Buffer.byteLength(context) > maxBytes) {
35
- const summary = files.map(f => {
36
- const content = readFileSync(join(lockedDir, f), 'utf-8');
37
- const firstLine = content.split('\n').find(l => l.trim() && !l.startsWith('---'))?.replace(/^#*\s*/, '') || '';
38
- return `- ${basename(f, '.md')}: ${firstLine}`;
39
- }).join('\n');
40
-
41
- context = `[Locked shared context exceeds ${maxBytes} byte limit (${Buffer.byteLength(context)} bytes). Summary of ${files.length} files:]\n${summary}\n[Read individual files from workspace-context/shared/locked/ if you need full content.]`;
42
- }
43
-
44
- respond(context);
@@ -1,107 +0,0 @@
1
- Activate this rule if the workspace creates PRs, watches CI runs, or interacts with releases from skills. Sibling to `work-item-tracking.md` (which covers issues); together they cover everything a workspace needs to do against a code-hosting forge.
2
-
3
- # Forge Operations
4
-
5
- When a workspace has a forge configured, all pull-request, release, and workflow-run operations from skills and scripts go through the adapter at `.claude/scripts/forges/{type}.mjs`. Skills never call `gh` (or `glab`, or any forge CLI) inline.
6
-
7
- ## Why the abstraction
8
-
9
- - **Swap by config.** Moving from GitHub to GitLab is a `workspace.json` field change plus an adapter file — not a sweep across six skill files.
10
- - **Testable.** The adapter takes an injectable `spawnFn`; unit tests mock subprocess calls instead of running them.
11
- - **One vocabulary.** Skills reason about `forge.prCreate`, `forge.prMerge`, `forge.workflowRunFind`, `forge.workflowRunWatch`, `forge.releaseView` regardless of backend. Failure modes share typed errors (`PrNotFound`, `MergeRejected`, `WorkflowNotFound`, `ReleaseNotFound`) instead of every callsite parsing stderr.
12
-
13
- ## Configuration
14
-
15
- `workspace.json` → `workspace.forge`:
16
-
17
- ```json
18
- {
19
- "workspace": {
20
- "forge": {
21
- "type": "github"
22
- }
23
- }
24
- }
25
- ```
26
-
27
- - `type` — identifies the adapter module at `.claude/scripts/forges/{type}.mjs`. `github` is the default and the only fully-implemented adapter today. `gitlab.mjs` ships as a stub that throws `NOT_IMPLEMENTED` with a contribution pointer.
28
- - `repo` (optional) — adapter-specific. For `github`, the `owner/name` slug to target. When unset or `"auto"`, the adapter resolves the repo from the local git `origin` remote.
29
-
30
- Absence of `workspace.forge` is treated as `{ type: 'github' }` (back-compat for workspaces that predate the field). Setting `workspace.forge: false` explicitly disables forge operations — every adapter method then throws `FORGE_DISABLED`.
31
-
32
- ## Adapter interface (for Claude)
33
-
34
- Import from `.claude/scripts/forges/interface.mjs`:
35
-
36
- ```javascript
37
- import { createForge, PrNotFound, MergeRejected, WorkflowNotFound, ReleaseNotFound } from '.claude/scripts/forges/interface.mjs';
38
- import { readFileSync } from 'node:fs';
39
-
40
- const ws = JSON.parse(readFileSync('workspace.json', 'utf-8'));
41
- const forge = createForge(ws.workspace?.forge);
42
-
43
- // Pull request lifecycle
44
- const pr = await forge.prCreate({
45
- title: 'feat: add forge adapter',
46
- body: 'long-form body',
47
- draft: false, // omit or false for normal PRs; true for /pause-work drafts
48
- base: 'main', // optional; defaults to repo default branch
49
- head: 'feature/forge', // optional; defaults to current branch
50
- }); // → { id: 'owner/repo#42', url, number }
51
-
52
- await forge.prMerge({
53
- id: pr.id,
54
- strategy: 'squash', // 'merge' | 'squash' | 'rebase'
55
- deleteBranch: true,
56
- });
57
-
58
- const view = await forge.prView({ id: pr.id });
59
- // → { state, mergeable, mergeStateStatus, reviewDecision, ... }
60
-
61
- // Releases (lookup only — release creation lives in the workflow tag-push)
62
- const release = await forge.releaseView({ tag: 'v1.2.3' });
63
- // → { tag, name, url, publishedAt, isDraft, isPrerelease }
64
- // throws ReleaseNotFound if the tag has no release
65
-
66
- // Workflow runs (used by /complete-work to follow the publish workflow)
67
- const run = await forge.workflowRunFind({
68
- workflow: 'publish.yml',
69
- branch: 'v1.2.3',
70
- limit: 1,
71
- }); // → { runId, status, conclusion, url } | null
72
- const result = await forge.workflowRunWatch({
73
- runId: run.runId,
74
- exitStatus: true, // true: exit non-zero on workflow failure
75
- }); // → { exitCode } — does NOT throw on workflow failure
76
- ```
77
-
78
- All methods are async. Adapter-detectable failures throw typed errors; raw spawn failures throw `Error`.
79
-
80
- ## Skill behavior
81
-
82
- Skills that interact with forge operations:
83
-
84
- - **`/pause-work`** — creates draft PRs via `forge.prCreate({ draft: true })`.
85
- - **`/complete-work`** — creates PRs, merges them with `strategy: 'squash'`, and (release sessions only) finds + watches the publish workflow via `workflowRunFind` + `workflowRunWatch`. Uses `releaseView` to investigate existing tag conflicts before re-tagging.
86
-
87
- Skills outside that list do not call the forge adapter; they either don't touch the forge or they touch it for tracker-setup-specific operations that are deliberately scoped out (see below).
88
-
89
- ## What this rule does NOT cover
90
-
91
- - **Issue lifecycle.** Issues, comments, labels, milestones live in `work-item-tracking.md` via the tracker adapter at `.claude/scripts/trackers/{type}.mjs`. The two abstractions are intentionally separate.
92
- - **Tracker-setup repo configuration.** `/setup-tracker` uses `gh repo view --json hasIssuesEnabled` and `gh api repos/{slug} -X PATCH -f has_issues=true` to inspect and enable the Issues feature on a GitHub repo. Those are GitHub-API-specific setup operations, not the cross-cutting PR/release ops the forge abstraction targets. A GitLab user running `/setup-tracker` would follow a different setup flow entirely, so wrapping these in the forge adapter would create a leaky abstraction. They remain direct `gh` calls.
93
- - **`gh repo view` as a remote-type probe.** `/complete-work` uses `gh repo view` to detect whether a remote is a GitHub remote (separately from any PR operation that follows). This is a one-line capability check, not an operation that benefits from forge wrapping. It stays direct.
94
- - **Repo creation.** `gh repo create` (in `/sync-work` and `/workspace-init` setup narratives) is an interactive one-off used when a workspace lacks a remote. No forge adapter method for it — pointing users at a wrapped form when none exists would be worse than the current direct mention.
95
- - **Manual operator recovery.** `gh run rerun`, `gh run view`, `gh release view` referenced in `/release` recovery guidance are documented for an operator at a terminal investigating a failed publish. The forge adapter is for *skill code*, not the manual recovery prose.
96
-
97
- ## Migration
98
-
99
- Existing workspaces predate `workspace.forge`. They continue to work because `createForge(undefined)` defaults to `{ type: 'github' }`. The `/maintenance` audit surfaces a notice when `workspace.tracker.type === 'github-issues'` and `workspace.forge` is unset, suggesting the explicit value — a one-line `workspace.json` addition with no behavior change.
100
-
101
- A workspace switching to GitLab implements `.claude/scripts/forges/gitlab.mjs` against the interface (the stub file documents the shape) and sets `workspace.forge.type: 'gitlab'`. No skill rewrite is required; the abstraction does the routing.
102
-
103
- ## What this rule does NOT do
104
-
105
- - Does not prescribe a specific forge type. Adapter choice is per workspace.
106
- - Does not replace forge-native features (PR comments, review-requested webhooks, branch protection settings) — those remain UI / direct-CLI territory.
107
- - Does not promise that every `gh` capability is wrapped. The adapter covers the operations the template's skills actually perform. New operations land via additive interface methods, not by skills going around the adapter.
@@ -1,34 +0,0 @@
1
- # Git Conventions
2
-
3
- ## Branching
4
-
5
- - Prefixes: `feature/`, `bugfix/`, `chore/`
6
- - Names: kebab-case after prefix, no grouping/nesting
7
- - Examples: `feature/ble-provisioning`, `bugfix/mqtt-reconnect`
8
- - All branches merge to the repo's default branch
9
- - Branch names should be unique — if revisiting previous work, distinguish the new branch name
10
-
11
- ## Worktrees
12
-
13
- - Work sessions get N+1 worktrees: one for the workspace, plus one per project repo
14
- - Each session lives in a self-contained folder at `work-sessions/{session-name}/`
15
- - The workspace worktree is at `work-sessions/{session-name}/workspace/`
16
- - Project worktrees are nested inside the workspace worktree at `work-sessions/{session-name}/workspace/repos/{repo-name}/`
17
- - Example: for a session `fix-auth` on branch `bugfix/fix-auth` touching repos `my-app` and `my-api`:
18
- - `work-sessions/fix-auth/workspace/` — workspace worktree
19
- - `work-sessions/fix-auth/workspace/repos/my-app/` — project worktree
20
- - `work-sessions/fix-auth/workspace/repos/my-api/` — project worktree
21
- - The workspace repo's `.gitignore` pattern `repos` (no trailing slash) covers both the workspace root's `repos/` and the nested `repos/` inside every worktree
22
- - Source clones at `repos/{repo-name}/` (at the workspace root) stay on their default branch at all times
23
- - Remove worktrees when the work session is completed — use the cleanup helper to enforce the mandatory teardown order (project worktrees first, then workspace worktree, then prune)
24
-
25
- ## Branch Maintenance
26
-
27
- - Before creating a PR, fetch and rebase onto the latest parent branch
28
- - If conflicts arise during rebase, stop and present them to the user — do not auto-resolve
29
-
30
- ## Commits
31
-
32
- - Conventional commit format: `feat:`, `fix:`, `refactor:`, `chore:`, `docs:`
33
- - Never amend commits unless explicitly asked
34
- - Never force push unless explicitly asked