cc-workspace 4.1.4 → 4.2.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 +88 -10
- package/bin/cli.js +238 -62
- package/global-skills/agents/implementer.md +19 -7
- package/global-skills/agents/team-lead.md +63 -0
- package/global-skills/agents/workspace-init.md +24 -6
- package/global-skills/cross-service-check/SKILL.md +6 -1
- package/global-skills/dispatch-feature/SKILL.md +40 -0
- package/global-skills/dispatch-feature/references/anti-patterns.md +9 -0
- package/global-skills/dispatch-feature/references/spawn-templates.md +55 -7
- package/global-skills/hooks/session-start-context.sh +18 -0
- package/global-skills/hooks/validate-spawn-prompt.sh +8 -0
- package/global-skills/merge-prep/SKILL.md +7 -4
- package/global-skills/refresh-profiles/SKILL.md +3 -3
- package/global-skills/templates/workspace.template.md +4 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -88,13 +88,13 @@ my-workspace/
|
|
|
88
88
|
│ ├── .claude/
|
|
89
89
|
│ │ ├── settings.json <- env vars + hooks
|
|
90
90
|
│ │ └── hooks/
|
|
91
|
-
│ │ ├── block-orchestrator-writes.sh
|
|
92
91
|
│ │ ├── session-start-context.sh
|
|
93
92
|
│ │ ├── validate-spawn-prompt.sh
|
|
94
|
-
│ │ └── ... <-
|
|
93
|
+
│ │ └── ... <- 9 scripts (all warning-only)
|
|
95
94
|
│ ├── CLAUDE.md <- orchestrator profile
|
|
96
95
|
│ ├── workspace.md <- filled by workspace-init
|
|
97
96
|
│ ├── constitution.md <- filled by workspace-init
|
|
97
|
+
│ ├── .sessions/ <- session state (gitignored, created per session)
|
|
98
98
|
│ ├── templates/
|
|
99
99
|
│ │ ├── workspace.template.md
|
|
100
100
|
│ │ ├── constitution.template.md
|
|
@@ -110,6 +110,70 @@ my-workspace/
|
|
|
110
110
|
|
|
111
111
|
---
|
|
112
112
|
|
|
113
|
+
## Parallel sessions (branch isolation)
|
|
114
|
+
|
|
115
|
+
Run multiple features in parallel without branch conflicts.
|
|
116
|
+
|
|
117
|
+
### The problem
|
|
118
|
+
|
|
119
|
+
When two orchestrator sessions dispatch teammates to the same repo, branches get mixed:
|
|
120
|
+
teammates from session B may branch off session A's code. The result is interleaved commits
|
|
121
|
+
and confused agents.
|
|
122
|
+
|
|
123
|
+
### The solution
|
|
124
|
+
|
|
125
|
+
Each feature gets a **session** — a named scope that maps to a dedicated `session/{name}`
|
|
126
|
+
branch in each impacted repo. Branches are created from a configurable **source branch**
|
|
127
|
+
(e.g., `preprod`, `develop`) defined per repo in `workspace.md`.
|
|
128
|
+
|
|
129
|
+
### Setup source branches (one time)
|
|
130
|
+
|
|
131
|
+
In `workspace.md`, add the `Source Branch` column to the service map:
|
|
132
|
+
|
|
133
|
+
```markdown
|
|
134
|
+
| Service | Repo | Type | CLAUDE.md | Source Branch | Description |
|
|
135
|
+
|----------|-------------|----------|-----------|---------------|----------------|
|
|
136
|
+
| api | ../api | backend | ✓ | preprod | REST API |
|
|
137
|
+
| frontend | ../frontend | frontend | ✓ | preprod | Vue/Quasar SPA |
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### How it works
|
|
141
|
+
|
|
142
|
+
1. The team-lead identifies impacted repos during planning (Phase 2)
|
|
143
|
+
2. After plan approval, **Phase 2.5** creates a session:
|
|
144
|
+
- Writes `.sessions/{name}.json` with impacted repos only
|
|
145
|
+
- Spawns a Task subagent to run `git branch session/{name} {source}` in each repo
|
|
146
|
+
- Uses `git branch` (NOT `git checkout -b`) to avoid disrupting other sessions
|
|
147
|
+
3. Teammates receive the session branch in their spawn prompt — they do NOT create their own branches
|
|
148
|
+
4. PRs go from `session/{name}` → `source_branch` (never to main directly)
|
|
149
|
+
|
|
150
|
+
### Session CLI commands
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
cc-workspace session list # show active sessions + branches
|
|
154
|
+
cc-workspace session status feature-auth # commits per repo on session branch
|
|
155
|
+
cc-workspace session close feature-auth # interactive: create PRs, delete branches, clean up
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`session close` asks for confirmation before every action (PR creation, branch deletion, JSON cleanup).
|
|
159
|
+
|
|
160
|
+
### Parallel workflow
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# Terminal 1
|
|
164
|
+
cd orchestrator/
|
|
165
|
+
claude --agent team-lead
|
|
166
|
+
# → "Implement OAuth" → creates session/feature-auth in api/ + frontend/
|
|
167
|
+
|
|
168
|
+
# Terminal 2
|
|
169
|
+
cd orchestrator/
|
|
170
|
+
claude --agent team-lead
|
|
171
|
+
# → "Add billing" → creates session/feature-billing in api/ + frontend/
|
|
172
|
+
# Both sessions are fully isolated — different branches, no conflicts
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
113
177
|
## How it works
|
|
114
178
|
|
|
115
179
|
### The problem
|
|
@@ -149,6 +213,7 @@ parallel in each repo via Agent Teams.
|
|
|
149
213
|
```
|
|
150
214
|
CLARIFY -> ask max 5 questions if ambiguity
|
|
151
215
|
PLAN -> write the plan in ./plans/, wait for approval
|
|
216
|
+
SESSION -> create session branches in impacted repos (Phase 2.5)
|
|
152
217
|
SPAWN -> Wave 1: API/data in parallel
|
|
153
218
|
Wave 2: frontend with validated API contract
|
|
154
219
|
Wave 3: infra/config if applicable
|
|
@@ -160,14 +225,13 @@ REPORT -> final summary
|
|
|
160
225
|
### Security — path-aware writes
|
|
161
226
|
|
|
162
227
|
The orchestrator can write in `orchestrator/` (plans, workspace.md, constitution.md)
|
|
163
|
-
but never in sibling repos.
|
|
164
|
-
|
|
228
|
+
but never in sibling repos. A `PreToolUse` hook in the team-lead agent frontmatter
|
|
229
|
+
dynamically checks if the target path is inside orchestrator/ before allowing writes.
|
|
165
230
|
|
|
166
231
|
Protection layers:
|
|
167
232
|
1. `disallowedTools: Bash` in agent frontmatter
|
|
168
233
|
2. `tools` whitelist in agent frontmatter (note: `allowed-tools` is the skill equivalent)
|
|
169
|
-
3. `PreToolUse` path-aware hook in agent frontmatter
|
|
170
|
-
4. `block-orchestrator-writes.sh` hook in .claude/hooks/
|
|
234
|
+
3. `PreToolUse` path-aware hook in agent frontmatter (team-lead only — teammates write freely in their worktrees)
|
|
171
235
|
|
|
172
236
|
---
|
|
173
237
|
|
|
@@ -200,15 +264,15 @@ next one starts. The plan on disk is the source of truth.
|
|
|
200
264
|
|
|
201
265
|
---
|
|
202
266
|
|
|
203
|
-
## The 9 hooks + 1 agent-level hook
|
|
267
|
+
## The 9 hooks (settings.json) + 1 agent-level hook (team-lead frontmatter)
|
|
204
268
|
|
|
205
269
|
All hooks in settings.json are **non-blocking** (exit 0 + warning). No hook blocks the session.
|
|
206
270
|
|
|
207
271
|
| Hook | Event | Effect |
|
|
208
272
|
|------|-------|--------|
|
|
209
|
-
| **
|
|
210
|
-
| **validate-spawn-prompt** | `PreToolUse` Teammate | Warning if missing context (rules, UX, tasks) |
|
|
211
|
-
| **session-start-context** | `SessionStart` | Injects active plans + first session detection |
|
|
273
|
+
| **path-aware write guard** | `PreToolUse` Write\|Edit\|MultiEdit | Blocks writes outside orchestrator/. **Agent frontmatter only** (team-lead) — not in settings.json, so teammates write freely in worktrees. |
|
|
274
|
+
| **validate-spawn-prompt** | `PreToolUse` Teammate | Warning if missing context (rules, UX, tasks, session branch) |
|
|
275
|
+
| **session-start-context** | `SessionStart` | Injects active plans + active sessions + first session detection |
|
|
212
276
|
| **user-prompt-guard** | `UserPromptSubmit` | Warning if code requested in a repo |
|
|
213
277
|
| **subagent-start-context** | `SubagentStart` | Injects active plan + constitution |
|
|
214
278
|
| **permission-auto-approve** | `PermissionRequest` | Auto-approve Read/Glob/Grep |
|
|
@@ -339,6 +403,20 @@ Both `init` and `update` are safe to re-run:
|
|
|
339
403
|
|
|
340
404
|
---
|
|
341
405
|
|
|
406
|
+
## Changelog v4.1.4 -> v4.2.0
|
|
407
|
+
|
|
408
|
+
| # | Feature | Detail |
|
|
409
|
+
|---|---------|--------|
|
|
410
|
+
| 1 | **Session management** | Branch isolation for parallel features. Each session creates `session/{name}` branches in impacted repos only. `git branch` (no checkout) to avoid disrupting other sessions. |
|
|
411
|
+
| 2 | **Source branch per repo** | `workspace.md` service map now includes a `Source Branch` column (e.g., `preprod`, `develop`). Session branches are created from this branch. |
|
|
412
|
+
| 3 | **Phase 2.5 in dispatch** | New phase between Plan and Dispatch: creates session JSON + branches via Task subagent. |
|
|
413
|
+
| 4 | **CLI session commands** | `cc-workspace session list/status/close`. Close is interactive — asks confirmation before PRs, branch deletion, JSON cleanup. |
|
|
414
|
+
| 5 | **Session-aware hooks** | `session-start-context.sh` detects active sessions. `validate-spawn-prompt.sh` warns if session branch missing from spawn prompt. |
|
|
415
|
+
| 6 | **Spawn templates updated** | Teammates use `session/{name}` branch (already exists). No more `feature/[name]` — teammates never create their own branches. |
|
|
416
|
+
| 7 | **merge-prep session-aware** | Reads `.sessions/` for branch names and source branches. PRs target source branch, not hardcoded main. |
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
342
420
|
## Changelog v4.1.0 -> v4.1.4
|
|
343
421
|
|
|
344
422
|
| # | Fix | Detail |
|
package/bin/cli.js
CHANGED
|
@@ -256,65 +256,10 @@ function generateSettings(orchDir) {
|
|
|
256
256
|
fs.writeFileSync(path.join(orchDir, ".claude", "settings.json"), JSON.stringify(settings, null, 2) + "\n");
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
-
// ─── Block hook
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
# PreToolUse hook: blocks writes to sibling repos. Allows writes within orchestrator/.
|
|
264
|
-
set -euo pipefail
|
|
265
|
-
|
|
266
|
-
INPUT=$(cat)
|
|
267
|
-
|
|
268
|
-
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // empty' 2>/dev/null) || FILE_PATH=""
|
|
269
|
-
|
|
270
|
-
if [ -z "$FILE_PATH" ]; then
|
|
271
|
-
cat << 'EOF'
|
|
272
|
-
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Cannot determine target path. Delegate to a teammate."}}
|
|
273
|
-
EOF
|
|
274
|
-
exit 0
|
|
275
|
-
fi
|
|
276
|
-
|
|
277
|
-
ORCH_DIR="\${CLAUDE_PROJECT_DIR:-.}"
|
|
278
|
-
ORCH_ABS="$(cd "$ORCH_DIR" 2>/dev/null && pwd)" || ORCH_ABS=""
|
|
279
|
-
|
|
280
|
-
if [ -d "$(dirname "$FILE_PATH")" ]; then
|
|
281
|
-
TARGET_ABS="$(cd "$(dirname "$FILE_PATH")" 2>/dev/null && pwd)/$(basename "$FILE_PATH")"
|
|
282
|
-
else
|
|
283
|
-
TARGET_ABS="$FILE_PATH"
|
|
284
|
-
fi
|
|
285
|
-
|
|
286
|
-
if [ -n "$ORCH_ABS" ]; then
|
|
287
|
-
case "$TARGET_ABS" in
|
|
288
|
-
"$ORCH_ABS"/*)
|
|
289
|
-
exit 0
|
|
290
|
-
;;
|
|
291
|
-
esac
|
|
292
|
-
fi
|
|
293
|
-
|
|
294
|
-
PARENT_DIR="$(dirname "$ORCH_ABS" 2>/dev/null)" || PARENT_DIR=""
|
|
295
|
-
if [ -n "$PARENT_DIR" ]; then
|
|
296
|
-
for repo_dir in "$PARENT_DIR"/*/; do
|
|
297
|
-
[ -d "$repo_dir/.git" ] || continue
|
|
298
|
-
REPO_ABS="$(cd "$repo_dir" 2>/dev/null && pwd)"
|
|
299
|
-
case "$TARGET_ABS" in
|
|
300
|
-
"$REPO_ABS"/*)
|
|
301
|
-
REPO_NAME=$(basename "$REPO_ABS")
|
|
302
|
-
cat << EOF
|
|
303
|
-
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"BLOCKED: Cannot write in repo $REPO_NAME/. Delegate to a teammate via Agent Teams."}}
|
|
304
|
-
EOF
|
|
305
|
-
exit 0
|
|
306
|
-
;;
|
|
307
|
-
esac
|
|
308
|
-
done
|
|
309
|
-
fi
|
|
310
|
-
|
|
311
|
-
cat << 'EOF'
|
|
312
|
-
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"BLOCKED: Write target is outside orchestrator/. Delegate to a teammate."}}
|
|
313
|
-
EOF
|
|
314
|
-
exit 0
|
|
315
|
-
`;
|
|
316
|
-
fs.writeFileSync(path.join(hooksDir, "block-orchestrator-writes.sh"), blockHook, { mode: 0o755 });
|
|
317
|
-
}
|
|
259
|
+
// ─── Block hook ──────────────────────────────────────────────
|
|
260
|
+
// block-orchestrator-writes is now ONLY in team-lead agent frontmatter.
|
|
261
|
+
// It is NOT in settings.json (would be inherited by teammates, blocking their writes).
|
|
262
|
+
// The generateBlockHook() function was removed in v4.1.4.
|
|
318
263
|
|
|
319
264
|
// ─── CLAUDE.md content ──────────────────────────────────────
|
|
320
265
|
function claudeMdContent() {
|
|
@@ -356,6 +301,7 @@ Run once. Idempotent — can be re-run to re-diagnose.
|
|
|
356
301
|
- Templates: \`./templates/\`
|
|
357
302
|
- Service profiles: \`./plans/service-profiles.md\`
|
|
358
303
|
- Active plans: \`./plans/*.md\`
|
|
304
|
+
- Active sessions: \`./.sessions/*.json\`
|
|
359
305
|
|
|
360
306
|
## Skills (9)
|
|
361
307
|
- **dispatch-feature**: 4 modes, clarify → plan → waves → collect → verify
|
|
@@ -382,6 +328,8 @@ Run once. Idempotent — can be re-run to re-diagnose.
|
|
|
382
328
|
11. Compact after each cycle
|
|
383
329
|
12. Hooks are warning-only — never blocking
|
|
384
330
|
13. Retrospective cycle after each completed feature
|
|
331
|
+
14. Session branches for parallel isolation — teammates use session/{name}, never create own branches
|
|
332
|
+
15. Never \`git checkout -b\` in repos — use \`git branch\` (no checkout) to avoid disrupting parallel sessions
|
|
385
333
|
`;
|
|
386
334
|
}
|
|
387
335
|
|
|
@@ -398,9 +346,9 @@ function planTemplateContent() {
|
|
|
398
346
|
[Clarify answers]
|
|
399
347
|
|
|
400
348
|
## Impacted services
|
|
401
|
-
| Service | Impacted | Branch | Teammate | Status |
|
|
402
|
-
|
|
403
|
-
| | yes/no |
|
|
349
|
+
| Service | Impacted | Session Branch | Teammate | Status |
|
|
350
|
+
|---------|----------|----------------|----------|--------|
|
|
351
|
+
| | yes/no | session/[name] | | ⏳ |
|
|
404
352
|
|
|
405
353
|
## Waves
|
|
406
354
|
- Wave 1: [producers]
|
|
@@ -518,6 +466,13 @@ function updateLocal() {
|
|
|
518
466
|
ok("_TEMPLATE.md updated");
|
|
519
467
|
}
|
|
520
468
|
|
|
469
|
+
// ── .sessions/ (create if missing) ──
|
|
470
|
+
const sessionsDir = path.join(orchDir, ".sessions");
|
|
471
|
+
if (!fs.existsSync(sessionsDir)) {
|
|
472
|
+
mkdirp(sessionsDir);
|
|
473
|
+
ok(".sessions/ created");
|
|
474
|
+
}
|
|
475
|
+
|
|
521
476
|
// ── NEVER touch: workspace.md, constitution.md, plans/*.md, service-profiles.md ──
|
|
522
477
|
info(`${c.dim}workspace.md, constitution.md, plans/ — preserved${c.reset}`);
|
|
523
478
|
|
|
@@ -534,6 +489,7 @@ function setupWorkspace(workspacePath, projectName) {
|
|
|
534
489
|
mkdirp(path.join(orchDir, ".claude", "hooks"));
|
|
535
490
|
mkdirp(path.join(orchDir, "plans"));
|
|
536
491
|
mkdirp(path.join(orchDir, "templates"));
|
|
492
|
+
mkdirp(path.join(orchDir, ".sessions"));
|
|
537
493
|
ok("Structure created");
|
|
538
494
|
|
|
539
495
|
// ── Templates ──
|
|
@@ -615,6 +571,7 @@ function setupWorkspace(workspacePath, projectName) {
|
|
|
615
571
|
if (!fs.existsSync(gi)) {
|
|
616
572
|
fs.writeFileSync(gi, [
|
|
617
573
|
".claude/bash-commands.log", ".claude/worktrees/", ".claude/modified-files.log",
|
|
574
|
+
".sessions/",
|
|
618
575
|
"plans/*.md", "!plans/_TEMPLATE.md", "!plans/service-profiles.md", ""
|
|
619
576
|
].join("\n"));
|
|
620
577
|
ok(".gitignore");
|
|
@@ -745,6 +702,7 @@ function doctor() {
|
|
|
745
702
|
check("plans/", fs.existsSync(path.join(cwd, "plans")), "missing");
|
|
746
703
|
check("templates/", fs.existsSync(path.join(cwd, "templates")), "missing");
|
|
747
704
|
check(".claude/hooks/", fs.existsSync(path.join(cwd, ".claude", "hooks")), "missing");
|
|
705
|
+
check(".sessions/", fs.existsSync(path.join(cwd, ".sessions")), "missing — run: npx cc-workspace update");
|
|
748
706
|
const configured = !fs.readFileSync(path.join(cwd, "workspace.md"), "utf8").includes("[UNCONFIGURED]");
|
|
749
707
|
check("workspace.md configured", configured, "[UNCONFIGURED] — run: claude --agent workspace-init");
|
|
750
708
|
} else if (hasOrch) {
|
|
@@ -766,6 +724,37 @@ function doctor() {
|
|
|
766
724
|
log("");
|
|
767
725
|
}
|
|
768
726
|
|
|
727
|
+
// ─── Session helpers ─────────────────────────────────────────
|
|
728
|
+
function findOrchDir() {
|
|
729
|
+
const cwd = process.cwd();
|
|
730
|
+
if (fs.existsSync(path.join(cwd, "workspace.md"))) return cwd;
|
|
731
|
+
if (fs.existsSync(path.join(cwd, "orchestrator", "workspace.md")))
|
|
732
|
+
return path.join(cwd, "orchestrator");
|
|
733
|
+
return null;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function readSessions(orchDir) {
|
|
737
|
+
const sessDir = path.join(orchDir, ".sessions");
|
|
738
|
+
if (!fs.existsSync(sessDir)) return [];
|
|
739
|
+
return fs.readdirSync(sessDir)
|
|
740
|
+
.filter(f => f.endsWith(".json"))
|
|
741
|
+
.map(f => {
|
|
742
|
+
try {
|
|
743
|
+
return JSON.parse(fs.readFileSync(path.join(sessDir, f), "utf8"));
|
|
744
|
+
} catch { return null; }
|
|
745
|
+
})
|
|
746
|
+
.filter(Boolean);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function sessionStatusBadge(status) {
|
|
750
|
+
const badges = {
|
|
751
|
+
active: `${c.green}active${c.reset}`,
|
|
752
|
+
closed: `${c.dim}closed${c.reset}`,
|
|
753
|
+
closing: `${c.yellow}closing${c.reset}`,
|
|
754
|
+
};
|
|
755
|
+
return badges[status] || status;
|
|
756
|
+
}
|
|
757
|
+
|
|
769
758
|
// ─── CLI ────────────────────────────────────────────────────
|
|
770
759
|
const args = process.argv.slice(2);
|
|
771
760
|
const command = args[0];
|
|
@@ -832,6 +821,15 @@ switch (command) {
|
|
|
832
821
|
log(` ${c.cyan}npx cc-workspace doctor${c.reset}`);
|
|
833
822
|
log(` Check all components are installed and consistent.`);
|
|
834
823
|
log("");
|
|
824
|
+
log(` ${c.cyan}npx cc-workspace session list${c.reset}`);
|
|
825
|
+
log(` Show all active sessions and their branches.`);
|
|
826
|
+
log("");
|
|
827
|
+
log(` ${c.cyan}npx cc-workspace session status${c.reset} ${c.dim}<name>${c.reset}`);
|
|
828
|
+
log(` Show detailed session info: commits per repo on session branch.`);
|
|
829
|
+
log("");
|
|
830
|
+
log(` ${c.cyan}npx cc-workspace session close${c.reset} ${c.dim}<name>${c.reset}`);
|
|
831
|
+
log(` Interactive close: create PRs, delete branches, clean up.`);
|
|
832
|
+
log("");
|
|
835
833
|
log(` ${c.cyan}npx cc-workspace version${c.reset}`);
|
|
836
834
|
log(` Show package and installed versions.`);
|
|
837
835
|
log("");
|
|
@@ -845,6 +843,184 @@ switch (command) {
|
|
|
845
843
|
break;
|
|
846
844
|
}
|
|
847
845
|
|
|
846
|
+
case "session": {
|
|
847
|
+
const subCmd = args[1];
|
|
848
|
+
const orchDir = findOrchDir();
|
|
849
|
+
if (!orchDir) {
|
|
850
|
+
fail("No orchestrator/ found. Run from workspace root or orchestrator/.");
|
|
851
|
+
process.exit(1);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
switch (subCmd) {
|
|
855
|
+
case "list": {
|
|
856
|
+
log(BANNER_SMALL);
|
|
857
|
+
step("Active sessions");
|
|
858
|
+
const sessions = readSessions(orchDir);
|
|
859
|
+
if (sessions.length === 0) {
|
|
860
|
+
info(`${c.dim}No sessions found in .sessions/${c.reset}`);
|
|
861
|
+
} else {
|
|
862
|
+
for (const s of sessions) {
|
|
863
|
+
const repoCount = Object.keys(s.repos || {}).length;
|
|
864
|
+
log(` ${c.bold}${s.name}${c.reset} ${sessionStatusBadge(s.status)} ${c.dim}created: ${s.created}${c.reset} ${c.dim}repos: ${repoCount}${c.reset}`);
|
|
865
|
+
for (const [name, repo] of Object.entries(s.repos || {})) {
|
|
866
|
+
const branchIcon = repo.branch_created ? `${c.green}✓${c.reset}` : `${c.red}✗${c.reset}`;
|
|
867
|
+
log(` ${c.dim}├──${c.reset} ${name} ${repo.session_branch} ${branchIcon}`);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
log("");
|
|
872
|
+
break;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
case "status": {
|
|
876
|
+
const sessionName = args[2];
|
|
877
|
+
if (!sessionName) {
|
|
878
|
+
fail("Usage: cc-workspace session status <name>");
|
|
879
|
+
process.exit(1);
|
|
880
|
+
}
|
|
881
|
+
log(BANNER_SMALL);
|
|
882
|
+
const sessionFile = path.join(orchDir, ".sessions", `${sessionName}.json`);
|
|
883
|
+
if (!fs.existsSync(sessionFile)) {
|
|
884
|
+
fail(`Session "${sessionName}" not found`);
|
|
885
|
+
process.exit(1);
|
|
886
|
+
}
|
|
887
|
+
const session = JSON.parse(fs.readFileSync(sessionFile, "utf8"));
|
|
888
|
+
step(`Session: ${session.name}`);
|
|
889
|
+
log(` ${c.dim}Status${c.reset} ${sessionStatusBadge(session.status)}`);
|
|
890
|
+
log(` ${c.dim}Created${c.reset} ${session.created}`);
|
|
891
|
+
log("");
|
|
892
|
+
|
|
893
|
+
for (const [name, repo] of Object.entries(session.repos || {})) {
|
|
894
|
+
const repoPath = path.resolve(orchDir, repo.path);
|
|
895
|
+
log(` ${c.bold}${name}${c.reset} ${c.dim}(${repo.path})${c.reset}`);
|
|
896
|
+
log(` source: ${repo.source_branch} → session: ${repo.session_branch}`);
|
|
897
|
+
if (repo.branch_created && fs.existsSync(path.join(repoPath, ".git"))) {
|
|
898
|
+
try {
|
|
899
|
+
const commits = execSync(
|
|
900
|
+
`git -C "${repoPath}" log ${repo.session_branch} --oneline --not ${repo.source_branch} 2>/dev/null || echo "(no commits yet)"`,
|
|
901
|
+
{ encoding: "utf8", timeout: 5000 }
|
|
902
|
+
).trim();
|
|
903
|
+
log(` ${c.dim}commits:${c.reset}`);
|
|
904
|
+
for (const line of commits.split("\n").slice(0, 10)) {
|
|
905
|
+
log(` ${line}`);
|
|
906
|
+
}
|
|
907
|
+
} catch {
|
|
908
|
+
log(` ${c.yellow}(could not read commits)${c.reset}`);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
log("");
|
|
912
|
+
}
|
|
913
|
+
break;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
case "close": {
|
|
917
|
+
const sessionName = args[2];
|
|
918
|
+
if (!sessionName) {
|
|
919
|
+
fail("Usage: cc-workspace session close <name>");
|
|
920
|
+
process.exit(1);
|
|
921
|
+
}
|
|
922
|
+
const sessionFile = path.join(orchDir, ".sessions", `${sessionName}.json`);
|
|
923
|
+
if (!fs.existsSync(sessionFile)) {
|
|
924
|
+
fail(`Session "${sessionName}" not found`);
|
|
925
|
+
process.exit(1);
|
|
926
|
+
}
|
|
927
|
+
const session = JSON.parse(fs.readFileSync(sessionFile, "utf8"));
|
|
928
|
+
|
|
929
|
+
log(BANNER_SMALL);
|
|
930
|
+
step(`Closing session: ${session.name}`);
|
|
931
|
+
log("");
|
|
932
|
+
|
|
933
|
+
const readline = require("readline");
|
|
934
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
935
|
+
const ask = (q) => new Promise(r => rl.question(q, r));
|
|
936
|
+
|
|
937
|
+
(async () => {
|
|
938
|
+
// Step 1: offer to create PRs
|
|
939
|
+
for (const [name, repo] of Object.entries(session.repos || {})) {
|
|
940
|
+
if (!repo.branch_created) continue;
|
|
941
|
+
const repoPath = path.resolve(orchDir, repo.path);
|
|
942
|
+
const answer = await ask(
|
|
943
|
+
` Create PR ${c.cyan}${repo.session_branch}${c.reset} → ${c.cyan}${repo.source_branch}${c.reset} in ${c.bold}${name}${c.reset}? [y/N] `
|
|
944
|
+
);
|
|
945
|
+
if (answer.toLowerCase() === "y") {
|
|
946
|
+
try {
|
|
947
|
+
const result = execSync(
|
|
948
|
+
`cd "${repoPath}" && gh pr create --base "${repo.source_branch}" --head "${repo.session_branch}" --title "${session.name}: ${name}" --body "Session: ${session.name}"`,
|
|
949
|
+
{ encoding: "utf8", timeout: 30000 }
|
|
950
|
+
);
|
|
951
|
+
ok(`PR created: ${result.trim()}`);
|
|
952
|
+
} catch (e) {
|
|
953
|
+
fail(`PR creation failed: ${e.stderr || e.message}`);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// Step 2: offer to delete session branches
|
|
959
|
+
for (const [name, repo] of Object.entries(session.repos || {})) {
|
|
960
|
+
if (!repo.branch_created) continue;
|
|
961
|
+
const repoPath = path.resolve(orchDir, repo.path);
|
|
962
|
+
// Check for unpushed commits before offering deletion
|
|
963
|
+
let unpushed = "";
|
|
964
|
+
try {
|
|
965
|
+
unpushed = execSync(
|
|
966
|
+
`git -C "${repoPath}" log "${repo.session_branch}" --oneline --not --remotes 2>/dev/null`,
|
|
967
|
+
{ encoding: "utf8", timeout: 5000 }
|
|
968
|
+
).trim();
|
|
969
|
+
} catch { /* branch may not have remote tracking */ }
|
|
970
|
+
if (unpushed) {
|
|
971
|
+
warn(`${name}: ${unpushed.split("\\n").length} unpushed commit(s) on ${repo.session_branch}`);
|
|
972
|
+
}
|
|
973
|
+
const answer = await ask(
|
|
974
|
+
` Delete branch ${c.cyan}${repo.session_branch}${c.reset} in ${c.bold}${name}${c.reset}?${unpushed ? ` ${c.red}(has unpushed commits)${c.reset}` : ""} [y/N] `
|
|
975
|
+
);
|
|
976
|
+
if (answer.toLowerCase() === "y") {
|
|
977
|
+
try {
|
|
978
|
+
// Use -D (force) — user already confirmed, branch may not be merged yet
|
|
979
|
+
execSync(`git -C "${repoPath}" branch -D "${repo.session_branch}"`,
|
|
980
|
+
{ encoding: "utf8", timeout: 5000 });
|
|
981
|
+
ok(`Branch deleted in ${name}`);
|
|
982
|
+
} catch (e) {
|
|
983
|
+
fail(`Branch deletion failed in ${name}: ${e.stderr || e.message}`);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// Step 3: offer to delete session JSON
|
|
989
|
+
const answer = await ask(
|
|
990
|
+
` Delete session file ${c.dim}.sessions/${sessionName}.json${c.reset}? [y/N] `
|
|
991
|
+
);
|
|
992
|
+
if (answer.toLowerCase() === "y") {
|
|
993
|
+
fs.unlinkSync(sessionFile);
|
|
994
|
+
ok("Session file deleted");
|
|
995
|
+
} else {
|
|
996
|
+
session.status = "closed";
|
|
997
|
+
fs.writeFileSync(sessionFile, JSON.stringify(session, null, 2) + "\n");
|
|
998
|
+
ok("Session marked as closed");
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
rl.close();
|
|
1002
|
+
log("");
|
|
1003
|
+
})();
|
|
1004
|
+
break;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
default:
|
|
1008
|
+
if (!subCmd) {
|
|
1009
|
+
log(BANNER_SMALL);
|
|
1010
|
+
log(`\n ${c.bold}Usage:${c.reset}`);
|
|
1011
|
+
log(` ${c.cyan}cc-workspace session list${c.reset} ${c.dim}Show active sessions${c.reset}`);
|
|
1012
|
+
log(` ${c.cyan}cc-workspace session status${c.reset} ${c.dim}<name>${c.reset} ${c.dim}Detailed session info${c.reset}`);
|
|
1013
|
+
log(` ${c.cyan}cc-workspace session close${c.reset} ${c.dim}<name>${c.reset} ${c.dim}Interactive close${c.reset}`);
|
|
1014
|
+
log("");
|
|
1015
|
+
} else {
|
|
1016
|
+
fail(`Unknown session subcommand: ${subCmd}`);
|
|
1017
|
+
log(` Usage: ${c.cyan}cc-workspace session${c.reset} ${c.dim}list | status <name> | close <name>${c.reset}`);
|
|
1018
|
+
process.exit(1);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
break;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
848
1024
|
default: {
|
|
849
1025
|
fail(`Unknown command: ${command}`);
|
|
850
1026
|
log(` Run: ${c.cyan}npx cc-workspace --help${c.reset}`);
|
|
@@ -16,14 +16,26 @@ maxTurns: 50
|
|
|
16
16
|
|
|
17
17
|
You are a focused implementer. You receive tasks and deliver clean code.
|
|
18
18
|
|
|
19
|
+
## Git workflow (CRITICAL — do this first)
|
|
20
|
+
You are in a temporary worktree. If you don't commit, YOUR WORK WILL BE LOST.
|
|
21
|
+
|
|
22
|
+
1. **FIRST**: check out the session branch specified in your instructions:
|
|
23
|
+
`git checkout session/{name}`
|
|
24
|
+
2. **Verify**: `git branch --show-current` must show `session/{name}`
|
|
25
|
+
3. **Commit after each logical unit** — never wait until the end
|
|
26
|
+
4. **Before reporting back**: `git status` must show clean working tree.
|
|
27
|
+
If anything is uncommitted: COMMIT IT NOW before reporting.
|
|
28
|
+
|
|
19
29
|
## Workflow
|
|
20
|
-
1.
|
|
21
|
-
2.
|
|
22
|
-
3.
|
|
23
|
-
4.
|
|
24
|
-
5.
|
|
25
|
-
6.
|
|
26
|
-
7.
|
|
30
|
+
1. Check out the session branch (see Git workflow above)
|
|
31
|
+
2. Read the repo's CLAUDE.md — follow its conventions strictly
|
|
32
|
+
3. Implement the assigned tasks from the plan
|
|
33
|
+
4. Use the **LSP tool** for code navigation (go-to-definition, find-references)
|
|
34
|
+
5. Run existing tests — fix any regressions you introduce
|
|
35
|
+
6. Identify and remove dead code exposed by your changes
|
|
36
|
+
7. Commit on the session branch with conventional commits — after each unit, not at the end
|
|
37
|
+
8. Before reporting: `git status` — must be clean. `git log --oneline -5` — include in report
|
|
38
|
+
9. Report back: files changed, tests pass/fail, dead code found, commits (hash+message), blockers
|
|
27
39
|
|
|
28
40
|
## Rules
|
|
29
41
|
- Follow existing patterns in the codebase — consistency over preference
|
|
@@ -61,6 +61,60 @@ On startup, check if `./workspace.md` contains `[UNCONFIGURED]`.
|
|
|
61
61
|
| **C — Go direct** | Immediate dispatch, no interactive plan |
|
|
62
62
|
| **D — Single-service** | 1 repo, no waves, for targeted fixes |
|
|
63
63
|
|
|
64
|
+
## Session management
|
|
65
|
+
|
|
66
|
+
Sessions provide branch isolation when running multiple features in parallel.
|
|
67
|
+
Each session maps to a `session/{name}` branch in each impacted repo.
|
|
68
|
+
|
|
69
|
+
### On startup: detect active sessions
|
|
70
|
+
After loading workspace.md, scan `./.sessions/` for active session JSON files.
|
|
71
|
+
If active sessions exist, display them:
|
|
72
|
+
> Active sessions: [name1] (repos: api, front), [name2] (repos: api)
|
|
73
|
+
|
|
74
|
+
### Creating a session (Phase 2.5 — after Plan approval, before Dispatch)
|
|
75
|
+
1. Derive the session name from the feature name (slugified, e.g., `feature-auth`)
|
|
76
|
+
2. Read `workspace.md` for the source branch per repo (Source Branch column)
|
|
77
|
+
3. Identify which repos are impacted by the plan
|
|
78
|
+
4. Write `.sessions/{name}.json`:
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"name": "feature-auth",
|
|
82
|
+
"created": "2026-02-25",
|
|
83
|
+
"status": "active",
|
|
84
|
+
"repos": {
|
|
85
|
+
"api": {
|
|
86
|
+
"path": "../api",
|
|
87
|
+
"source_branch": "preprod",
|
|
88
|
+
"session_branch": "session/feature-auth",
|
|
89
|
+
"branch_created": true
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
5. Spawn a Task subagent (with Bash) to create the branches:
|
|
95
|
+
- `git -C ../[repo] branch session/{name} {source_branch}` for each repo
|
|
96
|
+
- CRITICAL: use `git branch` (NOT `git checkout -b`) — checkout would
|
|
97
|
+
disrupt other sessions' working directories
|
|
98
|
+
- If the branch already exists, verify it and skip creation
|
|
99
|
+
6. Verify branches were created (Task subagent reports back)
|
|
100
|
+
7. Update the session JSON: set `branch_created: true` for each successful branch
|
|
101
|
+
|
|
102
|
+
### During dispatch
|
|
103
|
+
- Include the session branch in every teammate spawn prompt
|
|
104
|
+
- Teammates use the session branch — they do NOT create their own branches
|
|
105
|
+
- The spawn prompt must say: "Branch session/{name} ALREADY EXISTS.
|
|
106
|
+
Create your worktree from this branch. ALL commits go on this branch."
|
|
107
|
+
|
|
108
|
+
### During collection
|
|
109
|
+
- Verify commits are on the session branch via Task subagent:
|
|
110
|
+
`git -C ../[repo] log session/{name} --oneline`
|
|
111
|
+
- If a teammate committed on a different branch, flag it as a blocker
|
|
112
|
+
|
|
113
|
+
### Session close
|
|
114
|
+
Session close is handled by the CLI: `cc-workspace session close {name}`
|
|
115
|
+
The team-lead does NOT close sessions — the user does via the CLI.
|
|
116
|
+
Close requires user approval before each action (PR, branch delete, JSON delete).
|
|
117
|
+
|
|
64
118
|
## Auto-discovery of repos
|
|
65
119
|
|
|
66
120
|
On startup AND during config:
|
|
@@ -102,10 +156,16 @@ Explore subagents are read-only, they do NOT need a worktree.
|
|
|
102
156
|
## Commit granularity enforcement
|
|
103
157
|
|
|
104
158
|
When collecting teammate reports:
|
|
159
|
+
- **FIRST: verify commits exist on the session branch** — spawn a Task subagent (Bash):
|
|
160
|
+
`git -C ../[repo] log session/{name} --oneline -10`
|
|
161
|
+
If 0 commits → the teammate forgot to commit. DO NOT accept the report.
|
|
162
|
+
Re-dispatch with explicit instruction to checkout + commit.
|
|
105
163
|
- **Check commit count vs plan** — the plan defines N commit units, the teammate must have N+ commits
|
|
106
164
|
- **Flag giant commits** — any commit >400 lines gets flagged in the session log
|
|
107
165
|
- **If a teammate made a single commit for all tasks**: ask them to split via SendMessage
|
|
108
166
|
before accepting the wave as complete
|
|
167
|
+
- **If teammate reports "done" but 0 commits**: the worktree was likely cleaned up.
|
|
168
|
+
Check if the worktree still exists. If lost, re-dispatch entirely.
|
|
109
169
|
- **Progress tracker** in the plan must be updated after each teammate report
|
|
110
170
|
|
|
111
171
|
## What you NEVER do
|
|
@@ -117,9 +177,12 @@ When collecting teammate reports:
|
|
|
117
177
|
- Accept a single giant commit covering multiple tasks — enforce atomic commits
|
|
118
178
|
- Let the context grow (compact after each cycle)
|
|
119
179
|
- Launch wave 2 before wave 1 has reported
|
|
180
|
+
- Create branches with `git checkout -b` in repos — always `git branch` (no checkout)
|
|
181
|
+
- Let teammates create their own branches — they use the session branch
|
|
120
182
|
|
|
121
183
|
## What you CAN write
|
|
122
184
|
- Plans in `./plans/`
|
|
185
|
+
- Session files in `./.sessions/`
|
|
123
186
|
- `./workspace.md` and `./constitution.md`
|
|
124
187
|
- Any file in your orchestrator/ directory
|
|
125
188
|
|
|
@@ -39,9 +39,10 @@ Check silently (no questions to the user):
|
|
|
39
39
|
| 4 | `./templates/workspace.template.md` exists | Flag |
|
|
40
40
|
| 5 | `./templates/constitution.template.md` exists | Flag |
|
|
41
41
|
| 6 | `./.claude/settings.json` exists and contains `AGENT_TEAMS` + `SUBAGENT_MODEL` | Regenerate if missing |
|
|
42
|
-
| 7 | `./.claude/hooks/` contains all
|
|
42
|
+
| 7 | `./.claude/hooks/` contains all 9 hooks | List the missing ones |
|
|
43
43
|
| 8 | All hooks are executable (`chmod +x`) | Auto-fix |
|
|
44
44
|
| 9 | `./CLAUDE.md` exists | Flag |
|
|
45
|
+
| 10 | `./.sessions/` exists | Create the directory |
|
|
45
46
|
|
|
46
47
|
### Phase 2: Global diagnostic
|
|
47
48
|
|
|
@@ -83,10 +84,10 @@ Re-run: npx cc-workspace update --force
|
|
|
83
84
|
4. Present a summary table to the user:
|
|
84
85
|
|
|
85
86
|
```
|
|
86
|
-
| Repo | Type | CLAUDE.md | .claude/ | Tests | Git clean |
|
|
87
|
-
|
|
88
|
-
| apidocker | Laravel | ✅ | ✅ hooks:3 | ✅ pest | ✅ |
|
|
89
|
-
| frontend | Vue/Quasar | ✅ | ❌ | ✅ vitest | ⚠️ 12 files |
|
|
87
|
+
| Repo | Type | CLAUDE.md | Source Branch | .claude/ | Tests | Git clean |
|
|
88
|
+
|------|------|-----------|---------------|----------|-------|-----------|
|
|
89
|
+
| apidocker | Laravel | ✅ | preprod | ✅ hooks:3 | ✅ pest | ✅ |
|
|
90
|
+
| frontend | Vue/Quasar | ✅ | preprod | ❌ | ✅ vitest | ⚠️ 12 files |
|
|
90
91
|
```
|
|
91
92
|
|
|
92
93
|
5. Regenerate `./plans/service-profiles.md` with all collected info
|
|
@@ -119,6 +120,19 @@ If some repos don't have a `CLAUDE.md`:
|
|
|
119
120
|
|
|
120
121
|
### Phase 4: Interactive configuration
|
|
121
122
|
|
|
123
|
+
**If `./workspace.md` is already configured** (no `[UNCONFIGURED]`):
|
|
124
|
+
|
|
125
|
+
Check for missing columns or outdated info:
|
|
126
|
+
1. If the service map is missing the `Source Branch` column:
|
|
127
|
+
- For each repo, detect the likely source branch:
|
|
128
|
+
- `git -C ../repo symbolic-ref refs/remotes/origin/HEAD 2>/dev/null` (strip refs/remotes/origin/)
|
|
129
|
+
- Or check for common branches: `preprod`, `develop`, `main`, `master`
|
|
130
|
+
- Or read from existing sections (e.g., "Branches de travail" notes)
|
|
131
|
+
- Present the proposed source branches and ask for confirmation
|
|
132
|
+
- Add the `Source Branch` column to the existing service map table
|
|
133
|
+
2. If new repos are detected (not in the service map), propose adding them
|
|
134
|
+
3. Update the orchestrator version in the header
|
|
135
|
+
|
|
122
136
|
**If `./workspace.md` contains `[UNCONFIGURED]`:**
|
|
123
137
|
|
|
124
138
|
1. Read `./templates/workspace.template.md` — this is the reference structure.
|
|
@@ -134,6 +148,10 @@ If some repos don't have a `CLAUDE.md`:
|
|
|
134
148
|
**Service map section:**
|
|
135
149
|
- Present detected repos with their auto-detected type
|
|
136
150
|
- Pre-fill roles based on CLAUDE.md files read in Phase 3
|
|
151
|
+
- For each repo, ask the **Source Branch** (the integration branch from which
|
|
152
|
+
session branches will be created, e.g., `preprod`, `develop`, `main`).
|
|
153
|
+
Pre-fill by detecting the default branch: `git -C ../repo symbolic-ref refs/remotes/origin/HEAD 2>/dev/null`
|
|
154
|
+
or fallback to `main`
|
|
137
155
|
- Ask for confirmation + corrections
|
|
138
156
|
- For each service: role in one sentence
|
|
139
157
|
|
|
@@ -175,7 +193,7 @@ Present a summary table:
|
|
|
175
193
|
╠═══════════════════════════════════════════════════════╣
|
|
176
194
|
║ ║
|
|
177
195
|
║ Orchestrator structure ✅ OK ║
|
|
178
|
-
║ Hooks (
|
|
196
|
+
║ Hooks (9/9) ✅ OK ║
|
|
179
197
|
║ Settings (Agent Teams) ✅ OK ║
|
|
180
198
|
║ Global components ✅ OK (or ⚠️ details) ║
|
|
181
199
|
║ workspace.md ✅ Configured ║
|
|
@@ -19,7 +19,12 @@ Scope: ONLY inter-service alignment. Not code quality, not bugs.
|
|
|
19
19
|
|
|
20
20
|
## Setup
|
|
21
21
|
|
|
22
|
-
Read `./workspace.md` for the service map
|
|
22
|
+
1. Read `./workspace.md` for the service map
|
|
23
|
+
2. Check `./.sessions/` for the active session related to the current feature
|
|
24
|
+
- If a session exists, all checks MUST run against the **session branch**
|
|
25
|
+
(e.g., `session/feature-auth`), not against `main` or the default branch
|
|
26
|
+
- Use `git -C ../[repo] show session/{name}:[file]` to read files from the
|
|
27
|
+
session branch without checking it out
|
|
23
28
|
|
|
24
29
|
## Checks (parallel Explore subagents via Task, Haiku)
|
|
25
30
|
|
|
@@ -88,6 +88,41 @@ vs done per service, visible at a glance.
|
|
|
88
88
|
|
|
89
89
|
Independent services go in the same wave. Save. **Present plan, wait for approval.**
|
|
90
90
|
|
|
91
|
+
## Phase 2.5: Session setup (branch isolation)
|
|
92
|
+
|
|
93
|
+
After plan approval and before dispatch:
|
|
94
|
+
|
|
95
|
+
1. Derive session name from feature name (slug: lowercase, hyphens, no spaces)
|
|
96
|
+
2. Read `./workspace.md` — extract the `Source Branch` column for each service
|
|
97
|
+
3. Identify repos impacted by the plan
|
|
98
|
+
4. Write `./.sessions/{session-name}.json`:
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"name": "{session-name}",
|
|
102
|
+
"created": "{date}",
|
|
103
|
+
"status": "active",
|
|
104
|
+
"repos": {
|
|
105
|
+
"{service}": {
|
|
106
|
+
"path": "../{service}",
|
|
107
|
+
"source_branch": "{from workspace.md}",
|
|
108
|
+
"session_branch": "session/{session-name}",
|
|
109
|
+
"branch_created": false
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
5. Spawn a **Task subagent with Bash** to create branches in each impacted repo:
|
|
115
|
+
```
|
|
116
|
+
git -C ../[repo] branch session/{name} {source_branch}
|
|
117
|
+
```
|
|
118
|
+
- Use `git branch` — NOT `git checkout -b` (checkout disrupts other sessions)
|
|
119
|
+
- If the branch already exists, verify it points to the right source and skip
|
|
120
|
+
- The Task subagent reports success/failure per repo
|
|
121
|
+
6. Update the session JSON: set `branch_created: true` for each successful branch
|
|
122
|
+
|
|
123
|
+
> **Why a Task subagent?** The team-lead has `disallowedTools: Bash`.
|
|
124
|
+
> Branch creation requires shell access, so it must be delegated.
|
|
125
|
+
|
|
91
126
|
## Phase 3: Spawn teammates (Agent Teams)
|
|
92
127
|
|
|
93
128
|
Use the **Teammate** tool to spawn teammates. Each teammate is an independent
|
|
@@ -101,6 +136,8 @@ For EVERY teammate, include in the spawn prompt:
|
|
|
101
136
|
3. API contract (if applicable)
|
|
102
137
|
4. Instruction to read repo CLAUDE.md first
|
|
103
138
|
5. Instruction to escalate architectural decisions not in the plan
|
|
139
|
+
6. Session branch: `session/{name}` — tell the teammate this branch ALREADY EXISTS,
|
|
140
|
+
all commits go on it, they must NOT create other branches
|
|
104
141
|
|
|
105
142
|
See @references/spawn-templates.md for the full templates per service type
|
|
106
143
|
(backend, frontend, infra, explore).
|
|
@@ -148,6 +185,9 @@ On each teammate report:
|
|
|
148
185
|
2. Update the **progress tracker** table (commits done / planned)
|
|
149
186
|
3. Note dead code found
|
|
150
187
|
4. Verify commit count and sizes — flag if a teammate made a single giant commit
|
|
188
|
+
4b. Verify commits are on the session branch (not on a rogue branch):
|
|
189
|
+
- Spawn a Task subagent (Bash): `git -C ../[repo] log session/{name} --oneline -5`
|
|
190
|
+
- If teammate committed on a different branch, flag it as a blocker
|
|
151
191
|
5. If a teammate failed → analyze, correct plan, re-dispatch
|
|
152
192
|
6. **Session log** entry: `[HH:MM] teammate-[service]: [status], [N] commits, [N] files, tests [pass/fail]`
|
|
153
193
|
7. If current wave done → launch next wave
|
|
@@ -18,6 +18,10 @@ Reference file for dispatch-feature. Loaded on-demand when Claude needs reminder
|
|
|
18
18
|
10. **NEVER keep code details in your context** — summarize to 3 lines in the plan, then compact
|
|
19
19
|
11. **NEVER assume repos are listed in workspace.md** — scan `../` at feature start for
|
|
20
20
|
any new `.git` repos that may have appeared since last configuration
|
|
21
|
+
12. **NEVER let teammates create their own branches** — they must use the session branch
|
|
22
|
+
(`session/{name}`). The team-lead creates it during Phase 2.5.
|
|
23
|
+
13. **NEVER use `git checkout -b` in repos** — use `git branch {name} {source}` (no checkout).
|
|
24
|
+
Checkout changes the working directory, which disrupts other sessions running in parallel.
|
|
21
25
|
|
|
22
26
|
## Common mistakes to watch for
|
|
23
27
|
|
|
@@ -31,3 +35,8 @@ Reference file for dispatch-feature. Loaded on-demand when Claude needs reminder
|
|
|
31
35
|
| Giant commit (500+ lines) | PR unreadable, impossible to review | Split into atomic commits (~300 lines max) per logical unit |
|
|
32
36
|
| Single commit at the end | All-or-nothing, no partial rollback | Commit after each logical unit — data, logic, API, tests |
|
|
33
37
|
| Task without commit boundary | Teammate guesses the split | Plan must define commit units per task |
|
|
38
|
+
| Teammate creates own branch | Report shows commits on `feature/xxx` instead of `session/xxx` | Re-dispatch with explicit session branch instruction |
|
|
39
|
+
| `git checkout -b` in repo | Other session's worktree on wrong branch | Always use `git branch` (no checkout) for session branch creation |
|
|
40
|
+
| No session created before dispatch | Branches mixed between parallel sessions | Always run Phase 2.5 before Phase 3 |
|
|
41
|
+
| Teammate reports done with 0 commits | Worktree cleaned up, changes LOST | Verify commits on session branch before accepting report |
|
|
42
|
+
| Teammate didn't checkout session branch | Commits on wrong branch or detached HEAD | Git workflow section must be FIRST in spawn prompt |
|
|
@@ -11,6 +11,21 @@ Reference file for dispatch-feature. Loaded on-demand, not at skill activation.
|
|
|
11
11
|
```
|
|
12
12
|
You are teammate-[service]. Read the CLAUDE.md in your repo first.
|
|
13
13
|
|
|
14
|
+
## Git workflow (CRITICAL — read first)
|
|
15
|
+
You are working in a temporary worktree. If you don't commit, YOUR WORK WILL BE LOST
|
|
16
|
+
when the worktree is cleaned up.
|
|
17
|
+
|
|
18
|
+
1. FIRST THING: check out the session branch:
|
|
19
|
+
git checkout session/{session-name}
|
|
20
|
+
2. Verify you are on the right branch:
|
|
21
|
+
git branch --show-current (must show: session/{session-name})
|
|
22
|
+
3. Commit AFTER EACH logical unit — do NOT wait until the end
|
|
23
|
+
4. Before reporting back, verify ALL changes are committed:
|
|
24
|
+
git status (must show: nothing to commit, working tree clean)
|
|
25
|
+
5. If git status shows uncommitted changes when you're done: COMMIT THEM NOW
|
|
26
|
+
|
|
27
|
+
Branch `session/{session-name}` ALREADY EXISTS. Do NOT create other branches.
|
|
28
|
+
|
|
14
29
|
## Constitution (non-negotiable)
|
|
15
30
|
[paste all rules from your workspace's constitution.md]
|
|
16
31
|
|
|
@@ -30,7 +45,8 @@ You are teammate-[service]. Read the CLAUDE.md in your repo first.
|
|
|
30
45
|
6. **Atomic commits** — follow the commit plan below
|
|
31
46
|
7. If you hit an architectural decision NOT covered by the plan: STOP and
|
|
32
47
|
report the dilemma instead of guessing
|
|
33
|
-
8.
|
|
48
|
+
8. **Before reporting back**: run `git status` — if anything is uncommitted, commit it NOW
|
|
49
|
+
9. Report back: files created/modified, tests pass/fail, dead code found,
|
|
34
50
|
commits made (hash + message), blockers
|
|
35
51
|
|
|
36
52
|
## Commit strategy (mandatory)
|
|
@@ -43,7 +59,6 @@ You are teammate-[service]. Read the CLAUDE.md in your repo first.
|
|
|
43
59
|
- **Commit message format**: `feat(domain): description` or `fix(domain): description`
|
|
44
60
|
- **Each commit must compile and pass tests** — no broken intermediate states
|
|
45
61
|
- **Commit as you go** — do NOT accumulate all changes for a single final commit
|
|
46
|
-
- Branch: `feature/[name]` — create it on your first commit
|
|
47
62
|
```
|
|
48
63
|
|
|
49
64
|
## Frontend teammate spawn template
|
|
@@ -51,6 +66,21 @@ You are teammate-[service]. Read the CLAUDE.md in your repo first.
|
|
|
51
66
|
```
|
|
52
67
|
You are teammate-[service]. Read the CLAUDE.md in your repo first.
|
|
53
68
|
|
|
69
|
+
## Git workflow (CRITICAL — read first)
|
|
70
|
+
You are working in a temporary worktree. If you don't commit, YOUR WORK WILL BE LOST
|
|
71
|
+
when the worktree is cleaned up.
|
|
72
|
+
|
|
73
|
+
1. FIRST THING: check out the session branch:
|
|
74
|
+
git checkout session/{session-name}
|
|
75
|
+
2. Verify you are on the right branch:
|
|
76
|
+
git branch --show-current (must show: session/{session-name})
|
|
77
|
+
3. Commit AFTER EACH logical unit — do NOT wait until the end
|
|
78
|
+
4. Before reporting back, verify ALL changes are committed:
|
|
79
|
+
git status (must show: nothing to commit, working tree clean)
|
|
80
|
+
5. If git status shows uncommitted changes when you're done: COMMIT THEM NOW
|
|
81
|
+
|
|
82
|
+
Branch `session/{session-name}` ALREADY EXISTS. Do NOT create other branches.
|
|
83
|
+
|
|
54
84
|
## Constitution (non-negotiable)
|
|
55
85
|
[paste all rules from your workspace's constitution.md]
|
|
56
86
|
|
|
@@ -72,8 +102,9 @@ You are teammate-[service]. Read the CLAUDE.md in your repo first.
|
|
|
72
102
|
6. List any dead code (unused components, composables, store actions, CSS)
|
|
73
103
|
7. **Atomic commits** — follow the commit plan below
|
|
74
104
|
8. If you hit an architectural decision NOT covered by the plan: STOP and escalate
|
|
75
|
-
9.
|
|
76
|
-
|
|
105
|
+
9. **Before reporting back**: run `git status` — if anything is uncommitted, commit it NOW
|
|
106
|
+
10. Report back: files created/modified, tests pass/fail, dead code found,
|
|
107
|
+
UX compliance, commits made (hash + message), blockers
|
|
77
108
|
|
|
78
109
|
## Commit strategy (mandatory)
|
|
79
110
|
- **One commit per logical unit** — each task = one commit minimum
|
|
@@ -86,7 +117,6 @@ You are teammate-[service]. Read the CLAUDE.md in your repo first.
|
|
|
86
117
|
- **Commit message format**: `feat(domain): description` or `fix(domain): description`
|
|
87
118
|
- **Each commit must compile and pass tests** — no broken intermediate states
|
|
88
119
|
- **Commit as you go** — do NOT accumulate all changes for a single final commit
|
|
89
|
-
- Branch: `feature/[name]` — create it on your first commit
|
|
90
120
|
```
|
|
91
121
|
|
|
92
122
|
## Infra/Config teammate spawn template
|
|
@@ -94,6 +124,21 @@ You are teammate-[service]. Read the CLAUDE.md in your repo first.
|
|
|
94
124
|
```
|
|
95
125
|
You are teammate-[service]. Read the CLAUDE.md in your repo first.
|
|
96
126
|
|
|
127
|
+
## Git workflow (CRITICAL — read first)
|
|
128
|
+
You are working in a temporary worktree. If you don't commit, YOUR WORK WILL BE LOST
|
|
129
|
+
when the worktree is cleaned up.
|
|
130
|
+
|
|
131
|
+
1. FIRST THING: check out the session branch:
|
|
132
|
+
git checkout session/{session-name}
|
|
133
|
+
2. Verify you are on the right branch:
|
|
134
|
+
git branch --show-current (must show: session/{session-name})
|
|
135
|
+
3. Commit AFTER EACH logical unit — do NOT wait until the end
|
|
136
|
+
4. Before reporting back, verify ALL changes are committed:
|
|
137
|
+
git status (must show: nothing to commit, working tree clean)
|
|
138
|
+
5. If git status shows uncommitted changes when you're done: COMMIT THEM NOW
|
|
139
|
+
|
|
140
|
+
Branch `session/{session-name}` ALREADY EXISTS. Do NOT create other branches.
|
|
141
|
+
|
|
97
142
|
## Constitution (non-negotiable)
|
|
98
143
|
[paste all rules from your workspace's constitution.md]
|
|
99
144
|
|
|
@@ -108,9 +153,9 @@ You are teammate-[service]. Read the CLAUDE.md in your repo first.
|
|
|
108
153
|
5. **Atomic commits** — one commit per logical config change
|
|
109
154
|
6. Commit message format: `chore(service): description`
|
|
110
155
|
7. If you hit an architectural decision NOT covered by the plan: STOP and escalate
|
|
111
|
-
8.
|
|
156
|
+
8. **Before reporting back**: run `git status` — if anything is uncommitted, commit it NOW
|
|
157
|
+
9. Report back: files modified, consistency check results,
|
|
112
158
|
commits made (hash + message), blockers
|
|
113
|
-
- Branch: `feature/[name]`
|
|
114
159
|
```
|
|
115
160
|
|
|
116
161
|
## Explore/Haiku subagent template (read-only)
|
|
@@ -134,3 +179,6 @@ When a teammate reports back:
|
|
|
134
179
|
- **Architectural decision not in plan** (blocking): STOP the wave, escalate to user
|
|
135
180
|
- **No report after extended time**: send a status request via SendMessage
|
|
136
181
|
- **Max re-dispatches per teammate per wave**: 2. After that, escalate to user.
|
|
182
|
+
- **0 commits reported**: the teammate likely forgot to commit. Check the worktree
|
|
183
|
+
with a Task subagent before accepting the report. If changes exist uncommitted,
|
|
184
|
+
re-dispatch with explicit instruction to commit.
|
|
@@ -36,6 +36,24 @@ if [ -d "$PROJECT_DIR/plans" ]; then
|
|
|
36
36
|
fi
|
|
37
37
|
fi
|
|
38
38
|
|
|
39
|
+
# Detect active sessions
|
|
40
|
+
SESSIONS_DIR="$PROJECT_DIR/.sessions"
|
|
41
|
+
if [ -d "$SESSIONS_DIR" ]; then
|
|
42
|
+
ACTIVE_SESSIONS=""
|
|
43
|
+
for session_file in "$SESSIONS_DIR"/*.json; do
|
|
44
|
+
[ -f "$session_file" ] || continue
|
|
45
|
+
SESSION_NAME=$(jq -r '.name // empty' "$session_file" 2>/dev/null) || continue
|
|
46
|
+
SESSION_STATUS=$(jq -r '.status // "unknown"' "$session_file" 2>/dev/null) || continue
|
|
47
|
+
[ "$SESSION_STATUS" != "active" ] && continue
|
|
48
|
+
SESSION_REPOS=$(jq -r '[.repos | keys[]] | join(", ")' "$session_file" 2>/dev/null) || SESSION_REPOS="?"
|
|
49
|
+
ACTIVE_SESSIONS+=" - $SESSION_NAME (repos: $SESSION_REPOS)\n"
|
|
50
|
+
done
|
|
51
|
+
if [ -n "$ACTIVE_SESSIONS" ]; then
|
|
52
|
+
OUTPUT+="[Session context] Active sessions:\n$ACTIVE_SESSIONS"
|
|
53
|
+
OUTPUT+="Session branches are already created. Teammates must use these branches.\n\n"
|
|
54
|
+
fi
|
|
55
|
+
fi
|
|
56
|
+
|
|
39
57
|
# Check workspace.md exists
|
|
40
58
|
if [ ! -f "$PROJECT_DIR/workspace.md" ]; then
|
|
41
59
|
OUTPUT+="[WARNING] No workspace.md found. Run setup-workspace.sh first.\n"
|
|
@@ -67,6 +67,14 @@ if ! echo "$PROMPT" | grep -qiE '(escalat|STOP and report|STOP and escalate|repo
|
|
|
67
67
|
WARNINGS+="- Missing escalation instruction. Include: 'If you hit an architectural decision NOT covered by the plan: STOP and escalate.'\n"
|
|
68
68
|
fi
|
|
69
69
|
|
|
70
|
+
# Check 7: Session branch instruction (if sessions exist)
|
|
71
|
+
SESSIONS_DIR="${CLAUDE_PROJECT_DIR:-.}/.sessions"
|
|
72
|
+
if [ -d "$SESSIONS_DIR" ] && ls "$SESSIONS_DIR"/*.json >/dev/null 2>&1; then
|
|
73
|
+
if ! echo "$PROMPT" | grep -qiE '(session/|session branch|ALREADY EXISTS.*branch)' 2>/dev/null; then
|
|
74
|
+
WARNINGS+="- Active sessions exist but no session branch found in spawn prompt. Include the session branch instruction.\n"
|
|
75
|
+
fi
|
|
76
|
+
fi
|
|
77
|
+
|
|
70
78
|
# Report — ALL checks are warnings only (v4.0)
|
|
71
79
|
ISSUES=""
|
|
72
80
|
[ -n "$BLOCKERS" ] && ISSUES+="$BLOCKERS"
|
|
@@ -18,9 +18,12 @@ You prepare the merge. You do NOT merge. You produce a merge readiness report.
|
|
|
18
18
|
|
|
19
19
|
## Phase 1: Collect branch info
|
|
20
20
|
|
|
21
|
-
1. Read `./workspace.md` for the service map
|
|
21
|
+
1. Read `./workspace.md` for the service map (including Source Branch column)
|
|
22
22
|
2. Read `./plans/{feature-name}.md` for the active plan
|
|
23
|
-
3.
|
|
23
|
+
3. Check `.sessions/` for the session associated with this feature
|
|
24
|
+
- If a session exists, use `session/{name}` as the branch name
|
|
25
|
+
- Use the session's `source_branch` as the merge target (not hardcoded `main`)
|
|
26
|
+
4. For each service with status ✅, identify the session branch name
|
|
24
27
|
|
|
25
28
|
## Phase 2: Conflict detection
|
|
26
29
|
|
|
@@ -38,7 +41,7 @@ For lightweight read-only cross-checks, spawn Explore subagents (Task, Haiku).
|
|
|
38
41
|
For each service, generate a PR summary:
|
|
39
42
|
|
|
40
43
|
```markdown
|
|
41
|
-
### [service] — PR:
|
|
44
|
+
### [service] — PR: session/[name] → [source_branch]
|
|
42
45
|
|
|
43
46
|
**Changes**: [N] files modified, [M] files created, [D] files deleted
|
|
44
47
|
|
|
@@ -72,7 +75,7 @@ Write to `./plans/{feature-name}.md`:
|
|
|
72
75
|
### Merge readiness
|
|
73
76
|
| Service | Branch | Up-to-date | Conflicts | Tests | Ready |
|
|
74
77
|
|---------|--------|-----------|-----------|-------|-------|
|
|
75
|
-
| [name] |
|
|
78
|
+
| [name] | session/[x] | ✅/❌ | none/[list] | ✅/❌ | ✅/❌ |
|
|
76
79
|
|
|
77
80
|
### Merge order
|
|
78
81
|
1. [service] — [branch] → [target]
|
|
@@ -13,10 +13,10 @@ allowed-tools: Read, Write, Glob, Grep
|
|
|
13
13
|
|
|
14
14
|
# Refresh Service Profiles
|
|
15
15
|
|
|
16
|
-
1. Read `./workspace.md` to get the list of repos and their
|
|
16
|
+
1. Read `./workspace.md` to get the list of repos, their paths, and their **source branches**
|
|
17
17
|
2. For each repo listed, read its CLAUDE.md
|
|
18
|
-
3. Extract per repo: stack, patterns, auth, tests, conventions, special notes
|
|
19
|
-
4. Write result to `./plans/service-profiles.md`
|
|
18
|
+
3. Extract per repo: stack, patterns, auth, tests, conventions, source branch, special notes
|
|
19
|
+
4. Write result to `./plans/service-profiles.md` — include the source branch per repo
|
|
20
20
|
5. Add today's date in the header
|
|
21
21
|
|
|
22
22
|
If a repo doesn't exist or has no CLAUDE.md, mark it as "[not found]".
|
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
|
|
9
9
|
## Service map
|
|
10
10
|
<!-- Auto-generated from sibling repo scan. Review and add descriptions. -->
|
|
11
|
-
| Service | Repo | Type | CLAUDE.md | Description |
|
|
12
|
-
|
|
11
|
+
| Service | Repo | Type | CLAUDE.md | Source Branch | Description |
|
|
12
|
+
|---------|------|------|-----------|---------------|-------------|
|
|
13
13
|
<!-- [AUTO_DISCOVERED_REPOS] -->
|
|
14
|
+
<!-- Source Branch: the integration branch per repo (e.g., preprod, develop, main).
|
|
15
|
+
Session branches are created from this branch. Never push directly to it. -->
|
|
14
16
|
|
|
15
17
|
## Inter-service relationships
|
|
16
18
|
<!-- Describe the request flow between services -->
|
package/package.json
CHANGED