@ulysses-ai/create-workspace 0.16.0-beta.0 → 0.17.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 +2 -2
- package/lib/init.mjs +19 -0
- package/package.json +1 -1
- package/template/.claude/hooks/session-end.mjs +68 -2
- package/template/.claude/rules/config-review.md.skip +29 -0
- package/template/.claude/rules/forge-operations.md +107 -0
- package/template/.claude/rules/goal-driven-work.md +48 -4
- package/template/.claude/rules/workspace-structure.md +38 -0
- package/template/.claude/scripts/cleanup-work-session.mjs +164 -26
- package/template/.claude/scripts/forges/github.mjs +210 -0
- package/template/.claude/scripts/forges/gitlab.mjs +19 -0
- package/template/.claude/scripts/forges/interface.mjs +113 -0
- package/template/.claude/settings.json +5 -13
- package/template/.claude/skills/complete-work/SKILL.md +73 -42
- package/template/.claude/skills/maintenance/SKILL.md +32 -6
- package/template/.claude/skills/pause-work/SKILL.md +24 -7
- package/template/.claude/skills/release/SKILL.md +7 -5
- package/template/.claude/skills/workspace-init/SKILL.md +32 -0
- package/template/.claudeignore +3 -0
- package/template/CLAUDE.md.tmpl +1 -0
- package/template/CODEBASE.md.tmpl +13 -0
- package/template/repo-claude.md.tmpl +10 -0
- package/template/workspace.json.tmpl +2 -1
- package/template/.claude/hooks/worktree-create.mjs +0 -53
package/README.md
CHANGED
|
@@ -89,8 +89,8 @@ Four things, in the order you'll touch them:
|
|
|
89
89
|
A scaffolded workspace with:
|
|
90
90
|
|
|
91
91
|
- **14 skills** covering the workflow lifecycle, releases, handoffs, and maintenance
|
|
92
|
-
- **
|
|
93
|
-
- **
|
|
92
|
+
- **9 active rules** + **9 optional `.skip` rules** for behaviors you can opt into
|
|
93
|
+
- **9 hooks** for SessionStart, SubagentStart, PreCompact, and the rest of the small set the conventions rely on
|
|
94
94
|
- A **`shared-context/`** memory system with three visibility levels: locked (team truths), root (team-visible ephemerals), user-scoped (personal)
|
|
95
95
|
- Conventions for **multi-repo work sessions** with isolated git worktrees, parallelizable from separate terminals
|
|
96
96
|
|
package/lib/init.mjs
CHANGED
|
@@ -105,6 +105,25 @@ export async function initWorkspace(targetDir) {
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
// Set up .claudeignore
|
|
109
|
+
const payloadClaudeignore = join(payloadDir, '.claudeignore');
|
|
110
|
+
const claudeignorePath = join(targetDir, '.claudeignore');
|
|
111
|
+
if (existsSync(payloadClaudeignore)) {
|
|
112
|
+
if (existsSync(claudeignorePath)) {
|
|
113
|
+
const existing = readFileSync(claudeignorePath, 'utf-8');
|
|
114
|
+
const template = readFileSync(payloadClaudeignore, 'utf-8');
|
|
115
|
+
const existingLines = new Set(existing.split('\n').map(l => l.trim()));
|
|
116
|
+
const newLines = template.split('\n').filter(l => l.trim() && !existingLines.has(l.trim()));
|
|
117
|
+
if (newLines.length > 0) {
|
|
118
|
+
writeFileSync(claudeignorePath, existing.trimEnd() + '\n\n# From workspace template\n' + newLines.join('\n') + '\n');
|
|
119
|
+
console.log(' Merged template entries into .claudeignore');
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
cpSync(payloadClaudeignore, claudeignorePath);
|
|
123
|
+
console.log(' Created .claudeignore');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
108
127
|
console.log(`
|
|
109
128
|
Workspace initialized (v${toVersion}).
|
|
110
129
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// SessionEnd hook — mark this chat's `ended` timestamp in the session
|
|
3
|
-
// tracker
|
|
4
|
-
|
|
3
|
+
// tracker, append a small safety-net note to the session.md body, and
|
|
4
|
+
// write a disk-durable reflection record if the session.md ## Progress
|
|
5
|
+
// section contains heuristic correction-pattern sentences.
|
|
6
|
+
import { appendFileSync, mkdirSync, existsSync, readFileSync, writeFileSync } from 'fs';
|
|
5
7
|
import { join } from 'path';
|
|
6
8
|
import { execSync } from 'child_process';
|
|
7
9
|
import {
|
|
@@ -71,6 +73,70 @@ if (pointer && sessionId) {
|
|
|
71
73
|
} catch {
|
|
72
74
|
// Non-fatal
|
|
73
75
|
}
|
|
76
|
+
|
|
77
|
+
// Disk-durable reflection sub-step (BP-10).
|
|
78
|
+
// Read the ## Progress section of session.md (last 2000 chars max),
|
|
79
|
+
// scan for heuristic correction-pattern sentences, and write any
|
|
80
|
+
// candidates to workspace-scratchpad/session-reflect.json. The file
|
|
81
|
+
// is gitignored (workspace-scratchpad/ is in _gitignore). We do NOT
|
|
82
|
+
// emit additionalContext for this — per the canonical known limitation,
|
|
83
|
+
// additionalContext does not always reach Claude. Disk is the primary
|
|
84
|
+
// durable output.
|
|
85
|
+
try {
|
|
86
|
+
const sessionContent = readFileSync(trackerPath, 'utf8');
|
|
87
|
+
|
|
88
|
+
// Extract ## Progress section — find the section and take last 2000 chars.
|
|
89
|
+
const progressMatch = sessionContent.match(/^## Progress\s*\n([\s\S]*?)(?=\n^##|\s*$)/m);
|
|
90
|
+
const progressText = progressMatch
|
|
91
|
+
? progressMatch[1].slice(-2000)
|
|
92
|
+
: sessionContent.slice(-2000);
|
|
93
|
+
|
|
94
|
+
// Split into sentences on '. ' or '.\n' boundaries.
|
|
95
|
+
const sentences = progressText
|
|
96
|
+
.split(/(?<=\.)\s+|\n/)
|
|
97
|
+
.map(s => s.trim())
|
|
98
|
+
.filter(s => s.length > 10);
|
|
99
|
+
|
|
100
|
+
// Correction-pattern keywords — case-insensitive.
|
|
101
|
+
const correctionPatterns = [
|
|
102
|
+
/actually/i,
|
|
103
|
+
/instead of/i,
|
|
104
|
+
/the right way is/i,
|
|
105
|
+
/i was wrong/i,
|
|
106
|
+
/correction:/i,
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
const candidates = sentences
|
|
110
|
+
.filter(sentence => correctionPatterns.some(re => re.test(sentence)))
|
|
111
|
+
.map(text => ({ text, source: '## Progress' }));
|
|
112
|
+
|
|
113
|
+
if (candidates.length > 0) {
|
|
114
|
+
if (!existsSync(scratchpadDir)) mkdirSync(scratchpadDir, { recursive: true });
|
|
115
|
+
const reflectPath = join(scratchpadDir, 'session-reflect.json');
|
|
116
|
+
|
|
117
|
+
// Read existing records to append (create-or-append pattern).
|
|
118
|
+
let records = [];
|
|
119
|
+
if (existsSync(reflectPath)) {
|
|
120
|
+
try {
|
|
121
|
+
records = JSON.parse(readFileSync(reflectPath, 'utf8'));
|
|
122
|
+
if (!Array.isArray(records)) records = [records];
|
|
123
|
+
} catch {
|
|
124
|
+
records = [];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
records.push({
|
|
129
|
+
sessionId: sessionId || `ts-${Date.now()}`,
|
|
130
|
+
date: new Date().toISOString(),
|
|
131
|
+
workSession: pointer.name,
|
|
132
|
+
candidates,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
writeFileSync(reflectPath, JSON.stringify(records, null, 2), 'utf8');
|
|
136
|
+
}
|
|
137
|
+
} catch {
|
|
138
|
+
// Non-fatal — reflection is best-effort.
|
|
139
|
+
}
|
|
74
140
|
}
|
|
75
141
|
}
|
|
76
142
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Config Review Cadence
|
|
2
|
+
|
|
3
|
+
Opt-in reminder to review `.claude` component files on a regular schedule. Activate by removing the `.skip` extension (`mv config-review.md.skip config-review.md`). Add it back to deactivate.
|
|
4
|
+
|
|
5
|
+
## When to review
|
|
6
|
+
|
|
7
|
+
Review the files in `.claude/rules/`, `.claude/skills/*/SKILL.md`, `.claude/agents/*.md`, and `.claude/hooks/*.mjs` after each major model release and at least every 180 days. Claude Code evolves quickly — conventions that matched platform behavior six months ago may no longer be accurate, optimal, or even meaningful.
|
|
8
|
+
|
|
9
|
+
The 180-day threshold is not arbitrary: it corresponds to roughly two major Claude model generations. Conventions written for an older model generation can silently mis-steer the newer one without anyone noticing, because the file still runs without error.
|
|
10
|
+
|
|
11
|
+
## Connection to /maintenance
|
|
12
|
+
|
|
13
|
+
The `/maintenance` skill's **Component age check** (step 7 in the Cleanup section) surfaces which files have drifted past 180 days by reading their frontmatter `updated:` field. Files without an `updated:` field are skipped — the check is incremental and only flags files that have opted in by carrying the field.
|
|
14
|
+
|
|
15
|
+
When `/maintenance` reports stale components, the recommended action is to open each flagged file, read it against the current Claude Code documentation and platform behavior, update the content where needed, and bump `updated:` to today.
|
|
16
|
+
|
|
17
|
+
## Why this ships as .skip
|
|
18
|
+
|
|
19
|
+
Review cadence is org-specific. A solo maintainer doing active weekly development may want a shorter cadence; a team with a slower release cycle may need a longer one; some workspaces may defer review entirely to a dedicated maintenance session. Making this rule mandatory by default would encode one team's preference as a universal constraint — exactly the failure mode described in `product-bias-risk.md`.
|
|
20
|
+
|
|
21
|
+
Activate the rule only if your team wants the reminder to appear in every session. If 180 days is wrong for your cadence, edit the threshold in the rule body after activating it.
|
|
22
|
+
|
|
23
|
+
## Activating
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
mv .claude/rules/config-review.md.skip .claude/rules/config-review.md
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Once active, this reminder loads into every session context. To deactivate, rename it back.
|
|
@@ -0,0 +1,107 @@
|
|
|
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.
|
|
@@ -15,6 +15,7 @@ If any of the three fails, prefer plain session work or a single skill invocatio
|
|
|
15
15
|
## File layout
|
|
16
16
|
|
|
17
17
|
- One `goal-{topic}.md` artifact at the top of the active worktree, alongside `session.md`. One goal per worktree.
|
|
18
|
+
- The artifact's frontmatter holds machine state; its body holds the human-readable goal statement, per-phase intent, and a mandatory `## Start command` section (see "Kicking off the goal") with the literal `/goal "..."` invocation the user runs to start the loop.
|
|
18
19
|
- Phase output artifacts live as siblings. `research-*.md` and `crossref-*.md` are goal-native (produced by `parallel-research` and `crossref` phase types). `design-*.md` and `plan-*.md` are pre-existing session-artifact patterns that `type: skill` phases reuse when the wrapped skill is `superpowers:brainstorming` or `superpowers:writing-plans`; they are not goal-specific.
|
|
19
20
|
- The artifact is tracked on the session branch and lives there until `/complete-work` runs. It is removed from the branch before the final PR alongside other session artifacts.
|
|
20
21
|
|
|
@@ -106,6 +107,19 @@ The evaluator's "no, awaiting review" response after a gated phase does not bypa
|
|
|
106
107
|
|
|
107
108
|
`gate: auto` is allowed in the format but discouraged in v1. Earn it after the workflow has been exercised at least once on the work in question.
|
|
108
109
|
|
|
110
|
+
## Gate-budget interaction and turn-budget sizing
|
|
111
|
+
|
|
112
|
+
A goal's `turn_budget` is a backstop that counts every orchestrator turn — including the short "awaiting your gate decision" exchanges that happen at every `gate: review` phase. The `/goal` evaluator re-pings the session whenever it tries to settle without the completion condition being met, and each ping consumes a turn. Concretely: a multi-phase gated goal whose author is away for stretches can spend a meaningful fraction of its budget *idling at gates* rather than advancing work. A run with six `gate: review` phases burned roughly half of a 150-turn budget on gate-idle pings before the actual work completed.
|
|
113
|
+
|
|
114
|
+
This is a structural property of `/goal` + review gates, not a per-goal accident. Plan for it:
|
|
115
|
+
|
|
116
|
+
- **Drop `gate: review` from early phases when the human is expected to be away for stretches.** Research, crossref, spec, and plan phases produce artifacts that the final session→main PR consumes anyway. The integration-branch model (`main` untouched until `/complete-work` opens the final PR for human review) already provides one strong human review point; piling per-phase gates on top of that, with no human watching, just burns budget. Set those phases to `gate: auto` when the run will be unattended.
|
|
117
|
+
- **Keep `gate: review` for phases with irreversible side effects or for phases whose outcome reshapes subsequent phases.** A spec gate that lets the human reprioritize the ranked execution list before `executing-plans` walks it is worth its cost; a research-synthesis gate that just rubber-stamps a matrix the human will see again in the final PR is not.
|
|
118
|
+
- **Size `turn_budget` for the worst case you actually expect.** If every phase is `gate: auto`: budget ≈ (estimated work-turns) × 1.2 (small headroom for retries). If any phases are `gate: review` and the human may be unavailable for hours: multiply the work-turn estimate by **2–3×** to absorb gate-idle pings, or raise the budget mid-goal by editing the goal artifact's `completion_condition` and `turn_budget` fields. The Stop-hook condition string is fixed from the original `/goal` invocation; raising `turn_budget` in the artifact keeps the auditable source-of-truth correct for resume but does not change the running evaluator's text — the user can re-run the (updated) `## Start command` to refresh it after `/goal clear`.
|
|
119
|
+
- **If the goal stops on the backstop mid-work because of gate-idle waste, that is the system working as designed.** `claude --resume` plus re-running the `## Start command` continues from durable phase state; phase artifacts and per-phase `status: complete` markers survive. Do not treat backstop-stop as a failure of the goal.
|
|
120
|
+
|
|
121
|
+
This guidance is workspace-side mitigation only. The underlying friction — the evaluator pinging during gate-idle — is `/goal` harness behavior, not workspace code. A cleaner fix (suspend the evaluator at review gates so it does not re-ping until a new user message arrives) is tracked separately and would obsolete the multiplier above when it lands.
|
|
122
|
+
|
|
109
123
|
## Integration branch and per-phase sub-PRs
|
|
110
124
|
|
|
111
125
|
While a `/goal`-driven session is running, the session branch (`feature/{session-name}`) acts as the goal's integration branch. Main is untouched until `/complete-work` opens the final session→main PR for human review. This is the key autonomy boundary: phase agents can merge their own work, repeatedly, throughout the goal — but only into the integration branch, never into main.
|
|
@@ -192,9 +206,31 @@ All phases in goal-<topic>.md show status: complete. Phase artifacts exist at: <
|
|
|
192
206
|
|
|
193
207
|
Fill in `<topic>`, paths, and `<N>` per goal. Anchor on artifacts and committed state, not on feelings.
|
|
194
208
|
|
|
209
|
+
## Kicking off the goal
|
|
210
|
+
|
|
211
|
+
`/goal` is a Claude Code built-in that the **user** types — the agent cannot invoke it. So the moment the artifact is ready is the load-bearing hand-off, and the artifact itself carries the instruction rather than relying on an agent chat message that vanishes on the next compaction or resume.
|
|
212
|
+
|
|
213
|
+
Every `goal-{topic}.md` body MUST include a `## Start command` section containing the literal, copy-paste-ready invocation:
|
|
214
|
+
|
|
215
|
+
````markdown
|
|
216
|
+
## Start command
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
/goal "All phases in goal-<topic>.md show status: complete. Phase artifacts exist at: <paths>. The /complete-work skill has produced release notes and opened the final PR; the PR URL appeared in the transcript. Or stop after <N> turns."
|
|
220
|
+
```
|
|
221
|
+
````
|
|
222
|
+
|
|
223
|
+
Rules for the `## Start command`:
|
|
224
|
+
|
|
225
|
+
- It is the `completion_condition` flattened to a **single line** and wrapped in `/goal "..."`. The frontmatter `completion_condition:` (a multi-line folded scalar) is the auditable source of truth; the start command is its runnable rendering. The two must express the same condition — if you edit one, re-derive the other.
|
|
226
|
+
- Flatten by collapsing the folded scalar's newlines to single spaces. Escape any embedded double quotes. Keep it within the 4000-character `/goal` limit.
|
|
227
|
+
- It lives in the body, not the frontmatter, because it is for a human to copy, not for machine parsing.
|
|
228
|
+
|
|
229
|
+
When the artifact is drafted and the user has reviewed it, the agent's hand-off is: point the user at the `## Start command` block and let them run it. Running it flips the goal from `status: pending` to `status: active` (the first `/goal` turn updates the frontmatter per the dispatch pattern). The agent never types `/goal` itself.
|
|
230
|
+
|
|
195
231
|
## Lifecycle integration
|
|
196
232
|
|
|
197
|
-
- `/goal` runs inside an active work session. It does NOT replace `/start-work`. The session is created the normal way, the goal artifact is drafted at the worktree top,
|
|
233
|
+
- `/goal` runs inside an active work session. It does NOT replace `/start-work`. The session is created the normal way, the goal artifact is drafted at the worktree top (including its `## Start command` block), and the user runs that block's `/goal "..."` command to kick off the loop.
|
|
198
234
|
- The goal artifact lives on the session branch and travels with `git push`. It survives across machines and `--resume`.
|
|
199
235
|
- `session.md`'s `## Tasks` should mirror the phase list at coarse grain (one task per phase) so `TodoWrite` shows high-level progress. The main agent updates `## Tasks` at phase transitions via the helper specified by the `task-list-mirroring` rule, in addition to updating `goal-{topic}.md`.
|
|
200
236
|
- `/pause-work` works without special handling. The goal-evaluator state resets on resume per the Claude Code docs; phase state is durable in the artifact.
|
|
@@ -218,9 +254,9 @@ When the goal artifact is itself the deliverable for a future session to execute
|
|
|
218
254
|
|
|
219
255
|
## Appendix: worked example
|
|
220
256
|
|
|
221
|
-
A complete `goal-evaluate-rate-limiting.md` illustrating all three phase types. The topic is intentionally generic — the example is reference material, not prescriptive.
|
|
257
|
+
A complete `goal-evaluate-rate-limiting.md` illustrating all three phase types. The topic is intentionally generic — the example is reference material, not prescriptive. (The appendix is fenced with four backticks so the example's own `## Start command` code block renders intact.)
|
|
222
258
|
|
|
223
|
-
|
|
259
|
+
````yaml
|
|
224
260
|
---
|
|
225
261
|
type: goal
|
|
226
262
|
topic: evaluate-rate-limiting
|
|
@@ -357,6 +393,14 @@ The api-gateway needs rate limiting before the next traffic step-up. This
|
|
|
357
393
|
goal runs the full arc from candidate-algorithm research through implementation
|
|
358
394
|
on the session branch.
|
|
359
395
|
|
|
396
|
+
## Start command
|
|
397
|
+
|
|
398
|
+
```
|
|
399
|
+
/goal "All 5 phases in goal-evaluate-rate-limiting.md show status: complete. Phase artifacts exist at: research-rate-limiting-strategies.md, crossref-existing-infrastructure.md, design-rate-limiting.md, plan-rate-limiting.md, and the implementation commits land on the session branch (visible in git log). The /complete-work skill has produced release notes and opened the final PR; the PR URL appeared in the transcript. Or stop after 60 turns."
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
This is the frontmatter `completion_condition` flattened to one line. Run it after reviewing the artifact; it flips the goal to `status: active`.
|
|
403
|
+
|
|
360
404
|
## Per-phase intent
|
|
361
405
|
|
|
362
406
|
1. **strategy-research** runs three researchers in parallel, one per
|
|
@@ -385,4 +429,4 @@ All phase agents and synthesizers run on Sonnet (the default for team work).
|
|
|
385
429
|
The main agent reading and orchestrating this goal runs on Opus. Haiku is
|
|
386
430
|
unused in this example; it would be appropriate for a phase whose sole job
|
|
387
431
|
is, e.g., scanning a tree and returning a tagged file list.
|
|
388
|
-
|
|
432
|
+
````
|
|
@@ -47,6 +47,16 @@ Canonical content is verbatim-loaded into every session via `CLAUDE.md` → `@wo
|
|
|
47
47
|
|
|
48
48
|
Inflight session state lives inside the session worktree at `work-sessions/{name}/workspace/session.md`, not in `workspace-context/`. Workspace-context is for knowledge that outlives any individual session.
|
|
49
49
|
|
|
50
|
+
## Dynamic context loading (hooks)
|
|
51
|
+
|
|
52
|
+
Two hooks extend static `CLAUDE.md` loading with context that varies per session and per invocation:
|
|
53
|
+
|
|
54
|
+
- **`session-start.mjs`** (`SessionStart` hook): reads the active session pointer from `workspace-scratchpad/` and injects the current session's name, branch, linked work item, and shared context catalog into Claude's context. The injection is conditional — if no session is active, or if the relevant fields are absent from the session frontmatter, nothing is added. This avoids noise in non-session contexts (e.g., a quick launcher query).
|
|
55
|
+
|
|
56
|
+
- **`subagent-start.mjs`** (`SubagentStart` hook): reads every file under `workspace-context/shared/locked/` and injects their content into subagent context. A `subagentContextMaxBytes` field in `workspace.json` (default 10240) acts as a byte-budget fallback — if the total locked content exceeds the budget, files are truncated in reverse-priority order rather than silently dropped. This ensures subagents that never load `CLAUDE.md` still receive canonical team truths.
|
|
57
|
+
|
|
58
|
+
Both hooks are at `.claude/hooks/session-start.mjs` and `.claude/hooks/subagent-start.mjs`. They are registered as Node.js scripts — cross-platform, no shell dependency.
|
|
59
|
+
|
|
50
60
|
## Spec and Plan Locations — MANDATORY OVERRIDE
|
|
51
61
|
|
|
52
62
|
**Specs, plans, and goal artifacts MUST be written at the top of the active session's workspace worktree, not to `docs/superpowers/` or any other location.**
|
|
@@ -97,3 +107,31 @@ Local-only personal drafts get an additional `local-only-` prefix (e.g., `local-
|
|
|
97
107
|
- `workspace-scratchpad/` is for disposable files only — session log, hook debug output, temporary pointers.
|
|
98
108
|
- Project worktrees are nested inside the workspace worktree's real `repos/` directory — no symlink.
|
|
99
109
|
- Hand edits to `index.md`, `canonical.md`, or any per-user `team-member/{user}/index.md` are overwritten by `build-workspace-context.mjs`. Update source files (or their `description:` frontmatter) instead.
|
|
110
|
+
|
|
111
|
+
## Per-repo commands
|
|
112
|
+
|
|
113
|
+
Per-repo test, lint, and build commands belong in `repos/{repo}/CLAUDE.md` under a `## Commands` section. This scopes Claude's command invocations to the specific repo rather than triggering monorepo-wide runs that may time out or produce irrelevant output. The `/workspace-init` skill scaffolds a blank `repos/{repo}/CLAUDE.md` stub with a `## Commands` placeholder — fill it in once the repo is cloned.
|
|
114
|
+
|
|
115
|
+
## Explore before editing
|
|
116
|
+
|
|
117
|
+
Before modifying files in a large or unfamiliar codebase, use read-only tools to map the affected surface. The workflow: dispatch a researcher-type subagent to read, grep, and navigate the codebase; have it return a summary of the affected files, callers, and dependencies; then edit only after the map is established.
|
|
118
|
+
|
|
119
|
+
The `researcher.md` agent enforces this pattern mechanically via `disallowedTools: [Edit, Write, Bash]` — it can read and search but cannot change anything. Use it for initial exploration, then hand the findings back to the main agent for the actual edit. This avoids partial edits that break callers, catches ripple effects before they happen, and keeps the edit surface as small as possible.
|
|
120
|
+
|
|
121
|
+
## Launching Claude from a project worktree
|
|
122
|
+
|
|
123
|
+
Claude can be launched from any directory, and it walks up the filesystem loading every `CLAUDE.md` it finds. This means starting `claude` from `work-sessions/{name}/workspace/repos/{repo}/` loads both the per-repo conventions (from `repos/{repo}/CLAUDE.md`, if it exists) and the full workspace conventions (from the workspace `CLAUDE.md` further up the tree) — all without extra configuration.
|
|
124
|
+
|
|
125
|
+
For repo-focused work — debugging a single service, reviewing a specific module, running targeted tests — launching from the project worktree gives Claude a tighter codebase context. It sees the repo's own file tree first and reaches workspace-level conventions by traversal. The session hooks still fire (they read from `workspace-scratchpad/`, which is always relative to the workspace root), and `session.md` and all session artifacts remain at the workspace worktree top.
|
|
126
|
+
|
|
127
|
+
This is purely a launch-point choice; no workspace configuration changes are needed to enable it.
|
|
128
|
+
|
|
129
|
+
## Grep vs LSP
|
|
130
|
+
|
|
131
|
+
Two complementary search strategies cover different parts of the navigation surface:
|
|
132
|
+
|
|
133
|
+
- **Grep / Ripgrep** — searches file content as text. Fast, requires no server, and works across any file type. Use for free-text pattern search: finding a string literal, locating config values, scanning comments, searching across heterogeneous files. The downside: no language awareness — a search for `_toMs` matches comments, string literals, and variable names alike, producing false positives that require manual filtering.
|
|
134
|
+
|
|
135
|
+
- **LSP tools (`mcp__lsp__*`)** — powered by a running Language Server Protocol server that has indexed the codebase. LSP understands the language's type system and scope rules, so `find-all-references` on `_toMs` returns only actual symbol usages, not textual coincidences. Go-to-definition, rename-symbol, and callers/callees are accurate even across files and module boundaries. The tradeoff: requires a running LSP MCP server configured in `.mcp.json`.
|
|
136
|
+
|
|
137
|
+
The practical rule: reach for Grep first when you don't know where to look or when the pattern is not a symbol. Switch to LSP when you have a specific symbol and need precise cross-file navigation — especially before refactoring or understanding a call graph. The `researcher.md` agent lists the LSP tool among its allowed tools; activating LSP requires adding the appropriate language server to `.mcp.json` (see the MCP servers step in `/workspace-init`).
|