@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.
- package/README.md +3 -3
- package/lib/init.mjs +4 -1
- package/lib/payload.mjs +18 -1
- package/lib/payload.test.mjs +55 -0
- package/lib/scaffold.mjs +23 -6
- package/lib/scaffold.test.mjs +59 -0
- package/package.json +1 -1
- package/template/CLAUDE.md.tmpl +19 -2
- package/template/{.claude → _claude}/hooks/_utils.mjs +1 -1
- package/template/_claude/hooks/repo-write-detection.mjs +204 -0
- package/template/{.claude → _claude}/hooks/session-start.mjs +35 -1
- package/template/_claude/hooks/subagent-start.mjs +111 -0
- package/template/{.claude → _claude}/lib/session-frontmatter.mjs +28 -0
- package/template/{.claude → _claude}/rules/coherent-revisions.md +1 -1
- package/template/_claude/rules/forge-operations.md +57 -0
- package/template/_claude/rules/git-conventions.md +39 -0
- package/template/_claude/rules/goal-driven-work.md +24 -0
- package/template/_claude/rules/honest-pushback.md +56 -0
- package/template/_claude/rules/memory-guidance.md +66 -0
- package/template/{.claude → _claude}/rules/superpowers-workflow.md.skip +1 -1
- package/template/{.claude → _claude}/rules/task-list-mirroring.md +6 -0
- package/template/_claude/rules/work-item-tracking.md +48 -0
- package/template/_claude/rules/workspace-structure.md +79 -0
- package/template/{.claude → _claude}/scripts/build-workspace-context.mjs +86 -30
- package/template/_claude/scripts/chat-record.mjs +315 -0
- package/template/_claude/scripts/cleanup-work-session.mjs +436 -0
- package/template/_claude/scripts/context-footprint.mjs +391 -0
- package/template/{.claude → _claude}/scripts/forges/github.mjs +46 -0
- package/template/{.claude → _claude}/scripts/forges/gitlab.mjs +3 -2
- package/template/{.claude → _claude}/scripts/forges/interface.mjs +13 -0
- package/template/{.claude → _claude}/scripts/generate-claude-local.mjs +21 -2
- package/template/_claude/scripts/migrate-sessions.mjs +1571 -0
- package/template/{.claude → _claude}/scripts/migrate-to-workspace-context.mjs +7 -2
- package/template/_claude/scripts/task-pr.mjs +447 -0
- package/template/_claude/scripts/task-worktree.mjs +525 -0
- package/template/{.claude → _claude}/scripts/trackers/github-issues.mjs +11 -0
- package/template/{.claude → _claude}/scripts/trackers/interface.mjs +8 -0
- package/template/_claude/scripts/workspace-diagnostics.mjs +654 -0
- package/template/{.claude → _claude}/skills/braindump/SKILL.md +12 -4
- package/template/{.claude → _claude}/skills/build-docs-site/SKILL.md +5 -5
- package/template/{.claude → _claude}/skills/build-docs-site/templates/spec.md.tmpl +1 -1
- package/template/_claude/skills/complete-work/SKILL.md +452 -0
- package/template/_claude/skills/context-placement/SKILL.md +202 -0
- package/template/{.claude/rules/goal-driven-work.md → _claude/skills/goal-driven-work/SKILL.md} +46 -19
- package/template/{.claude → _claude}/skills/handoff/SKILL.md +12 -4
- package/template/{.claude → _claude}/skills/maintenance/SKILL.md +56 -17
- package/template/_claude/skills/migrate-sessions/SKILL.md +70 -0
- package/template/{.claude → _claude}/skills/pause-work/SKILL.md +9 -1
- package/template/_claude/skills/release/SKILL.md +91 -0
- package/template/{.claude → _claude}/skills/start-work/SKILL.md +89 -7
- package/template/{.claude → _claude}/skills/workspace-init/SKILL.md +3 -1
- package/template/{.claude → _claude}/skills/workspace-update/SKILL.md +4 -0
- package/template/_gitignore +9 -0
- package/template/workspace.json.tmpl +4 -3
- package/template/.claude/hooks/repo-write-detection.mjs +0 -107
- package/template/.claude/hooks/subagent-start.mjs +0 -44
- package/template/.claude/rules/forge-operations.md +0 -107
- package/template/.claude/rules/git-conventions.md +0 -34
- package/template/.claude/rules/honest-pushback.md +0 -56
- package/template/.claude/rules/memory-guidance.md +0 -109
- package/template/.claude/rules/work-item-tracking.md +0 -90
- package/template/.claude/rules/workspace-structure.md +0 -137
- package/template/.claude/scripts/cleanup-work-session.mjs +0 -247
- package/template/.claude/skills/complete-work/SKILL.md +0 -498
- package/template/.claude/skills/release/SKILL.md +0 -151
- /package/template/{.claude → _claude}/agents/aside-researcher.md +0 -0
- /package/template/{.claude → _claude}/agents/implementer.md +0 -0
- /package/template/{.claude → _claude}/agents/researcher.md +0 -0
- /package/template/{.claude → _claude}/agents/reviewer.md +0 -0
- /package/template/{.claude → _claude}/hooks/bash-output-advisory.mjs +0 -0
- /package/template/{.claude → _claude}/hooks/post-compact.mjs +0 -0
- /package/template/{.claude → _claude}/hooks/pre-compact.mjs +0 -0
- /package/template/{.claude → _claude}/hooks/session-end.mjs +0 -0
- /package/template/{.claude → _claude}/hooks/version-freshness-check.mjs +0 -0
- /package/template/{.claude → _claude}/hooks/workspace-update-check.mjs +0 -0
- /package/template/{.claude → _claude}/lib/freshness.mjs +0 -0
- /package/template/{.claude → _claude}/lib/registry-check.mjs +0 -0
- /package/template/{.claude → _claude}/lib/require-node.mjs +0 -0
- /package/template/{.claude → _claude}/recipes/migrate-from-notion.md +0 -0
- /package/template/{.claude → _claude}/rules/agent-rules.md.skip +0 -0
- /package/template/{.claude → _claude}/rules/cloud-infrastructure.md.skip +0 -0
- /package/template/{.claude → _claude}/rules/config-review.md.skip +0 -0
- /package/template/{.claude → _claude}/rules/documentation.md.skip +0 -0
- /package/template/{.claude → _claude}/rules/local-dev-environment.md.skip +0 -0
- /package/template/{.claude → _claude}/rules/product-integrity.md.skip +0 -0
- /package/template/{.claude → _claude}/rules/scope-guard.md.skip +0 -0
- /package/template/{.claude → _claude}/rules/token-economics.md.skip +0 -0
- /package/template/{.claude → _claude}/scripts/add-repo-to-session.mjs +0 -0
- /package/template/{.claude → _claude}/scripts/capture-context.mjs +0 -0
- /package/template/{.claude → _claude}/scripts/create-work-session.mjs +0 -0
- /package/template/{.claude → _claude}/scripts/migrate-canonical-priority.mjs +0 -0
- /package/template/{.claude → _claude}/scripts/migrate-claude-md-freshness-include.mjs +0 -0
- /package/template/{.claude → _claude}/scripts/migrate-open-work.mjs +0 -0
- /package/template/{.claude → _claude}/scripts/migrate-session-layout.mjs +0 -0
- /package/template/{.claude → _claude}/scripts/sweep-references.mjs +0 -0
- /package/template/{.claude → _claude}/scripts/sync-tasks.mjs +0 -0
- /package/template/{.claude → _claude}/settings.json +0 -0
- /package/template/{.claude → _claude}/skills/aside/SKILL.md +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/checklists/framing.md +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/checklists/pitfalls.md +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/checklists/review.md +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/scripts/bulk-fill-migration.py +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/scripts/forbidden-word-grep.mjs +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/scripts/leak-grep.mjs +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/templates/custom.css.tmpl +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/templates/docusaurus.config.ts.tmpl +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/templates/primitives/Arrow.tsx +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/templates/primitives/Box.tsx +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/templates/primitives/DiagramContainer.tsx +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/templates/primitives/Region.tsx +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/templates/primitives/SectionTitle.tsx +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/templates/primitives/tokens.ts +0 -0
- /package/template/{.claude → _claude}/skills/build-docs-site/templates/sidebars.ts.tmpl +0 -0
- /package/template/{.claude → _claude}/skills/promote/SKILL.md +0 -0
- /package/template/{.claude → _claude}/skills/setup-tracker/SKILL.md +0 -0
- /package/template/{.claude → _claude}/skills/sync-work/SKILL.md +0 -0
- /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),
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
- **
|
|
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.
|
package/template/_gitignore
CHANGED
|
@@ -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
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
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
|