cc-workspace 4.1.4 → 4.2.1

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 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
- │ │ └── ... <- 11 scripts
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. The `block-orchestrator-writes.sh` hook dynamically
164
- detects if the target path is in a repo (presence of `.git/`).
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
- | **block-orchestrator-writes** | `PreToolUse` Write\|Edit\|MultiEdit | Blocks writes in repos, allows orchestrator/. **Agent frontmatter only** (team-lead) — not in settings.json, so teammates can write freely. |
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
@@ -225,7 +225,8 @@ function generateSettings(orchDir) {
225
225
  // block-orchestrator-writes.sh is NOT here — it's in team-lead agent
226
226
  // frontmatter only. Putting it in settings.json would block teammates
227
227
  // from writing in their worktrees.
228
- withMatcher("Teammate", "validate-spawn-prompt.sh", 5)
228
+ withMatcher("Teammate", "validate-spawn-prompt.sh", 5),
229
+ withMatcher("Bash", "guard-session-checkout.sh", 5)
229
230
  ],
230
231
  SessionStart: [
231
232
  withoutMatcher("session-start-context.sh", 10)
@@ -256,65 +257,10 @@ function generateSettings(orchDir) {
256
257
  fs.writeFileSync(path.join(orchDir, ".claude", "settings.json"), JSON.stringify(settings, null, 2) + "\n");
257
258
  }
258
259
 
259
- // ─── Block hook (inline, always regenerated) ────────────────
260
- function generateBlockHook(hooksDir) {
261
- const blockHook = `#!/usr/bin/env bash
262
- # block-orchestrator-writes.sh v${PKG.version}
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
- }
260
+ // ─── Block hook ──────────────────────────────────────────────
261
+ // block-orchestrator-writes is now ONLY in team-lead agent frontmatter.
262
+ // It is NOT in settings.json (would be inherited by teammates, blocking their writes).
263
+ // The generateBlockHook() function was removed in v4.1.4.
318
264
 
319
265
  // ─── CLAUDE.md content ──────────────────────────────────────
320
266
  function claudeMdContent() {
@@ -356,6 +302,7 @@ Run once. Idempotent — can be re-run to re-diagnose.
356
302
  - Templates: \`./templates/\`
357
303
  - Service profiles: \`./plans/service-profiles.md\`
358
304
  - Active plans: \`./plans/*.md\`
305
+ - Active sessions: \`./.sessions/*.json\`
359
306
 
360
307
  ## Skills (9)
361
308
  - **dispatch-feature**: 4 modes, clarify → plan → waves → collect → verify
@@ -382,6 +329,8 @@ Run once. Idempotent — can be re-run to re-diagnose.
382
329
  11. Compact after each cycle
383
330
  12. Hooks are warning-only — never blocking
384
331
  13. Retrospective cycle after each completed feature
332
+ 14. Session branches for parallel isolation — teammates use session/{name}, never create own branches
333
+ 15. Never \`git checkout -b\` in repos — use \`git branch\` (no checkout) to avoid disrupting parallel sessions
385
334
  `;
386
335
  }
387
336
 
@@ -398,9 +347,9 @@ function planTemplateContent() {
398
347
  [Clarify answers]
399
348
 
400
349
  ## Impacted services
401
- | Service | Impacted | Branch | Teammate | Status |
402
- |---------|----------|--------|----------|--------|
403
- | | yes/no | feature/[name] | | ⏳ |
350
+ | Service | Impacted | Session Branch | Teammate | Status |
351
+ |---------|----------|----------------|----------|--------|
352
+ | | yes/no | session/[name] | | ⏳ |
404
353
 
405
354
  ## Waves
406
355
  - Wave 1: [producers]
@@ -518,6 +467,13 @@ function updateLocal() {
518
467
  ok("_TEMPLATE.md updated");
519
468
  }
520
469
 
470
+ // ── .sessions/ (create if missing) ──
471
+ const sessionsDir = path.join(orchDir, ".sessions");
472
+ if (!fs.existsSync(sessionsDir)) {
473
+ mkdirp(sessionsDir);
474
+ ok(".sessions/ created");
475
+ }
476
+
521
477
  // ── NEVER touch: workspace.md, constitution.md, plans/*.md, service-profiles.md ──
522
478
  info(`${c.dim}workspace.md, constitution.md, plans/ — preserved${c.reset}`);
523
479
 
@@ -534,6 +490,7 @@ function setupWorkspace(workspacePath, projectName) {
534
490
  mkdirp(path.join(orchDir, ".claude", "hooks"));
535
491
  mkdirp(path.join(orchDir, "plans"));
536
492
  mkdirp(path.join(orchDir, "templates"));
493
+ mkdirp(path.join(orchDir, ".sessions"));
537
494
  ok("Structure created");
538
495
 
539
496
  // ── Templates ──
@@ -615,6 +572,7 @@ function setupWorkspace(workspacePath, projectName) {
615
572
  if (!fs.existsSync(gi)) {
616
573
  fs.writeFileSync(gi, [
617
574
  ".claude/bash-commands.log", ".claude/worktrees/", ".claude/modified-files.log",
575
+ ".sessions/",
618
576
  "plans/*.md", "!plans/_TEMPLATE.md", "!plans/service-profiles.md", ""
619
577
  ].join("\n"));
620
578
  ok(".gitignore");
@@ -745,6 +703,7 @@ function doctor() {
745
703
  check("plans/", fs.existsSync(path.join(cwd, "plans")), "missing");
746
704
  check("templates/", fs.existsSync(path.join(cwd, "templates")), "missing");
747
705
  check(".claude/hooks/", fs.existsSync(path.join(cwd, ".claude", "hooks")), "missing");
706
+ check(".sessions/", fs.existsSync(path.join(cwd, ".sessions")), "missing — run: npx cc-workspace update");
748
707
  const configured = !fs.readFileSync(path.join(cwd, "workspace.md"), "utf8").includes("[UNCONFIGURED]");
749
708
  check("workspace.md configured", configured, "[UNCONFIGURED] — run: claude --agent workspace-init");
750
709
  } else if (hasOrch) {
@@ -766,6 +725,37 @@ function doctor() {
766
725
  log("");
767
726
  }
768
727
 
728
+ // ─── Session helpers ─────────────────────────────────────────
729
+ function findOrchDir() {
730
+ const cwd = process.cwd();
731
+ if (fs.existsSync(path.join(cwd, "workspace.md"))) return cwd;
732
+ if (fs.existsSync(path.join(cwd, "orchestrator", "workspace.md")))
733
+ return path.join(cwd, "orchestrator");
734
+ return null;
735
+ }
736
+
737
+ function readSessions(orchDir) {
738
+ const sessDir = path.join(orchDir, ".sessions");
739
+ if (!fs.existsSync(sessDir)) return [];
740
+ return fs.readdirSync(sessDir)
741
+ .filter(f => f.endsWith(".json"))
742
+ .map(f => {
743
+ try {
744
+ return JSON.parse(fs.readFileSync(path.join(sessDir, f), "utf8"));
745
+ } catch { return null; }
746
+ })
747
+ .filter(Boolean);
748
+ }
749
+
750
+ function sessionStatusBadge(status) {
751
+ const badges = {
752
+ active: `${c.green}active${c.reset}`,
753
+ closed: `${c.dim}closed${c.reset}`,
754
+ closing: `${c.yellow}closing${c.reset}`,
755
+ };
756
+ return badges[status] || status;
757
+ }
758
+
769
759
  // ─── CLI ────────────────────────────────────────────────────
770
760
  const args = process.argv.slice(2);
771
761
  const command = args[0];
@@ -832,6 +822,15 @@ switch (command) {
832
822
  log(` ${c.cyan}npx cc-workspace doctor${c.reset}`);
833
823
  log(` Check all components are installed and consistent.`);
834
824
  log("");
825
+ log(` ${c.cyan}npx cc-workspace session list${c.reset}`);
826
+ log(` Show all active sessions and their branches.`);
827
+ log("");
828
+ log(` ${c.cyan}npx cc-workspace session status${c.reset} ${c.dim}<name>${c.reset}`);
829
+ log(` Show detailed session info: commits per repo on session branch.`);
830
+ log("");
831
+ log(` ${c.cyan}npx cc-workspace session close${c.reset} ${c.dim}<name>${c.reset}`);
832
+ log(` Interactive close: create PRs, delete branches, clean up.`);
833
+ log("");
835
834
  log(` ${c.cyan}npx cc-workspace version${c.reset}`);
836
835
  log(` Show package and installed versions.`);
837
836
  log("");
@@ -845,6 +844,184 @@ switch (command) {
845
844
  break;
846
845
  }
847
846
 
847
+ case "session": {
848
+ const subCmd = args[1];
849
+ const orchDir = findOrchDir();
850
+ if (!orchDir) {
851
+ fail("No orchestrator/ found. Run from workspace root or orchestrator/.");
852
+ process.exit(1);
853
+ }
854
+
855
+ switch (subCmd) {
856
+ case "list": {
857
+ log(BANNER_SMALL);
858
+ step("Active sessions");
859
+ const sessions = readSessions(orchDir);
860
+ if (sessions.length === 0) {
861
+ info(`${c.dim}No sessions found in .sessions/${c.reset}`);
862
+ } else {
863
+ for (const s of sessions) {
864
+ const repoCount = Object.keys(s.repos || {}).length;
865
+ log(` ${c.bold}${s.name}${c.reset} ${sessionStatusBadge(s.status)} ${c.dim}created: ${s.created}${c.reset} ${c.dim}repos: ${repoCount}${c.reset}`);
866
+ for (const [name, repo] of Object.entries(s.repos || {})) {
867
+ const branchIcon = repo.branch_created ? `${c.green}✓${c.reset}` : `${c.red}✗${c.reset}`;
868
+ log(` ${c.dim}├──${c.reset} ${name} ${repo.session_branch} ${branchIcon}`);
869
+ }
870
+ }
871
+ }
872
+ log("");
873
+ break;
874
+ }
875
+
876
+ case "status": {
877
+ const sessionName = args[2];
878
+ if (!sessionName) {
879
+ fail("Usage: cc-workspace session status <name>");
880
+ process.exit(1);
881
+ }
882
+ log(BANNER_SMALL);
883
+ const sessionFile = path.join(orchDir, ".sessions", `${sessionName}.json`);
884
+ if (!fs.existsSync(sessionFile)) {
885
+ fail(`Session "${sessionName}" not found`);
886
+ process.exit(1);
887
+ }
888
+ const session = JSON.parse(fs.readFileSync(sessionFile, "utf8"));
889
+ step(`Session: ${session.name}`);
890
+ log(` ${c.dim}Status${c.reset} ${sessionStatusBadge(session.status)}`);
891
+ log(` ${c.dim}Created${c.reset} ${session.created}`);
892
+ log("");
893
+
894
+ for (const [name, repo] of Object.entries(session.repos || {})) {
895
+ const repoPath = path.resolve(orchDir, repo.path);
896
+ log(` ${c.bold}${name}${c.reset} ${c.dim}(${repo.path})${c.reset}`);
897
+ log(` source: ${repo.source_branch} → session: ${repo.session_branch}`);
898
+ if (repo.branch_created && fs.existsSync(path.join(repoPath, ".git"))) {
899
+ try {
900
+ const commits = execSync(
901
+ `git -C "${repoPath}" log ${repo.session_branch} --oneline --not ${repo.source_branch} 2>/dev/null || echo "(no commits yet)"`,
902
+ { encoding: "utf8", timeout: 5000 }
903
+ ).trim();
904
+ log(` ${c.dim}commits:${c.reset}`);
905
+ for (const line of commits.split("\n").slice(0, 10)) {
906
+ log(` ${line}`);
907
+ }
908
+ } catch {
909
+ log(` ${c.yellow}(could not read commits)${c.reset}`);
910
+ }
911
+ }
912
+ log("");
913
+ }
914
+ break;
915
+ }
916
+
917
+ case "close": {
918
+ const sessionName = args[2];
919
+ if (!sessionName) {
920
+ fail("Usage: cc-workspace session close <name>");
921
+ process.exit(1);
922
+ }
923
+ const sessionFile = path.join(orchDir, ".sessions", `${sessionName}.json`);
924
+ if (!fs.existsSync(sessionFile)) {
925
+ fail(`Session "${sessionName}" not found`);
926
+ process.exit(1);
927
+ }
928
+ const session = JSON.parse(fs.readFileSync(sessionFile, "utf8"));
929
+
930
+ log(BANNER_SMALL);
931
+ step(`Closing session: ${session.name}`);
932
+ log("");
933
+
934
+ const readline = require("readline");
935
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
936
+ const ask = (q) => new Promise(r => rl.question(q, r));
937
+
938
+ (async () => {
939
+ // Step 1: offer to create PRs
940
+ for (const [name, repo] of Object.entries(session.repos || {})) {
941
+ if (!repo.branch_created) continue;
942
+ const repoPath = path.resolve(orchDir, repo.path);
943
+ const answer = await ask(
944
+ ` Create PR ${c.cyan}${repo.session_branch}${c.reset} → ${c.cyan}${repo.source_branch}${c.reset} in ${c.bold}${name}${c.reset}? [y/N] `
945
+ );
946
+ if (answer.toLowerCase() === "y") {
947
+ try {
948
+ const result = execSync(
949
+ `cd "${repoPath}" && gh pr create --base "${repo.source_branch}" --head "${repo.session_branch}" --title "${session.name}: ${name}" --body "Session: ${session.name}"`,
950
+ { encoding: "utf8", timeout: 30000 }
951
+ );
952
+ ok(`PR created: ${result.trim()}`);
953
+ } catch (e) {
954
+ fail(`PR creation failed: ${e.stderr || e.message}`);
955
+ }
956
+ }
957
+ }
958
+
959
+ // Step 2: offer to delete session branches
960
+ for (const [name, repo] of Object.entries(session.repos || {})) {
961
+ if (!repo.branch_created) continue;
962
+ const repoPath = path.resolve(orchDir, repo.path);
963
+ // Check for unpushed commits before offering deletion
964
+ let unpushed = "";
965
+ try {
966
+ unpushed = execSync(
967
+ `git -C "${repoPath}" log "${repo.session_branch}" --oneline --not --remotes 2>/dev/null`,
968
+ { encoding: "utf8", timeout: 5000 }
969
+ ).trim();
970
+ } catch { /* branch may not have remote tracking */ }
971
+ if (unpushed) {
972
+ warn(`${name}: ${unpushed.split("\\n").length} unpushed commit(s) on ${repo.session_branch}`);
973
+ }
974
+ const answer = await ask(
975
+ ` 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] `
976
+ );
977
+ if (answer.toLowerCase() === "y") {
978
+ try {
979
+ // Use -D (force) — user already confirmed, branch may not be merged yet
980
+ execSync(`git -C "${repoPath}" branch -D "${repo.session_branch}"`,
981
+ { encoding: "utf8", timeout: 5000 });
982
+ ok(`Branch deleted in ${name}`);
983
+ } catch (e) {
984
+ fail(`Branch deletion failed in ${name}: ${e.stderr || e.message}`);
985
+ }
986
+ }
987
+ }
988
+
989
+ // Step 3: offer to delete session JSON
990
+ const answer = await ask(
991
+ ` Delete session file ${c.dim}.sessions/${sessionName}.json${c.reset}? [y/N] `
992
+ );
993
+ if (answer.toLowerCase() === "y") {
994
+ fs.unlinkSync(sessionFile);
995
+ ok("Session file deleted");
996
+ } else {
997
+ session.status = "closed";
998
+ fs.writeFileSync(sessionFile, JSON.stringify(session, null, 2) + "\n");
999
+ ok("Session marked as closed");
1000
+ }
1001
+
1002
+ rl.close();
1003
+ log("");
1004
+ })();
1005
+ break;
1006
+ }
1007
+
1008
+ default:
1009
+ if (!subCmd) {
1010
+ log(BANNER_SMALL);
1011
+ log(`\n ${c.bold}Usage:${c.reset}`);
1012
+ log(` ${c.cyan}cc-workspace session list${c.reset} ${c.dim}Show active sessions${c.reset}`);
1013
+ log(` ${c.cyan}cc-workspace session status${c.reset} ${c.dim}<name>${c.reset} ${c.dim}Detailed session info${c.reset}`);
1014
+ log(` ${c.cyan}cc-workspace session close${c.reset} ${c.dim}<name>${c.reset} ${c.dim}Interactive close${c.reset}`);
1015
+ log("");
1016
+ } else {
1017
+ fail(`Unknown session subcommand: ${subCmd}`);
1018
+ log(` Usage: ${c.cyan}cc-workspace session${c.reset} ${c.dim}list | status <name> | close <name>${c.reset}`);
1019
+ process.exit(1);
1020
+ }
1021
+ }
1022
+ break;
1023
+ }
1024
+
848
1025
  default: {
849
1026
  fail(`Unknown command: ${command}`);
850
1027
  log(` Run: ${c.cyan}npx cc-workspace --help${c.reset}`);
@@ -16,14 +16,31 @@ 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
+ **CRITICAL**: Do NOT run `git checkout` in the main repo. Do NOT use `git -C ../repo checkout`.
23
+ You are already in an isolated worktree — all git commands run HERE, not in the main repo.
24
+
25
+ 1. **FIRST**: Switch to the session branch inside your worktree:
26
+ `git checkout session/{name}` (safe — you're in a worktree)
27
+ 2. **Verify**: `git branch --show-current` must show `session/{name}`
28
+ 3. If checkout fails: `git fetch origin session/{name}` then retry
29
+ 4. **Do NOT stay on `worktree-agent-*` branches** — always switch to the session branch
30
+ 5. **Commit after each logical unit** — never wait until the end
31
+ 6. **Before reporting back**: `git status` must show clean working tree.
32
+ If anything is uncommitted: COMMIT IT NOW before reporting.
33
+
19
34
  ## Workflow
20
- 1. Read the repo's CLAUDE.md follow its conventions strictly
21
- 2. Implement the assigned tasks from the plan
22
- 3. Use the **LSP tool** for code navigation (go-to-definition, find-references)
23
- 4. Run existing tests fix any regressions you introduce
24
- 5. Identify and remove dead code exposed by your changes
25
- 6. Commit on the feature branch with conventional commits
26
- 7. Report back: files changed, tests pass/fail, dead code found, blockers
35
+ 1. Check out the session branch (see Git workflow above)
36
+ 2. Read the repo's CLAUDE.md follow its conventions strictly
37
+ 3. Implement the assigned tasks from the plan
38
+ 4. Use the **LSP tool** for code navigation (go-to-definition, find-references)
39
+ 5. Run existing tests fix any regressions you introduce
40
+ 6. Identify and remove dead code exposed by your changes
41
+ 7. Commit on the session branch with conventional commits — after each unit, not at the end
42
+ 8. Before reporting: `git status` — must be clean. `git log --oneline -5` — include in report
43
+ 9. Report back: files changed, tests pass/fail, dead code found, commits (hash+message), blockers
27
44
 
28
45
  ## Rules
29
46
  - Follow existing patterns in the codebase — consistency over preference
@@ -61,6 +61,67 @@ 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 include these EXACT instructions:
106
+ ```
107
+ CRITICAL: Do NOT run `git checkout` in the main repo. Do NOT use `git -C ../repo checkout`.
108
+ You are in an isolated worktree — all git commands run HERE.
109
+ 1. git checkout session/{name} (switch to session branch inside your worktree)
110
+ 2. git branch --show-current (verify: must show session/{name})
111
+ 3. Do NOT stay on worktree-agent-* branches — use the session branch.
112
+ Branch session/{name} ALREADY EXISTS. ALL commits go on this branch.
113
+ ```
114
+
115
+ ### During collection
116
+ - Verify commits are on the session branch via Task subagent:
117
+ `git -C ../[repo] log session/{name} --oneline`
118
+ - If a teammate committed on a different branch, flag it as a blocker
119
+
120
+ ### Session close
121
+ Session close is handled by the CLI: `cc-workspace session close {name}`
122
+ The team-lead does NOT close sessions — the user does via the CLI.
123
+ Close requires user approval before each action (PR, branch delete, JSON delete).
124
+
64
125
  ## Auto-discovery of repos
65
126
 
66
127
  On startup AND during config:
@@ -102,10 +163,16 @@ Explore subagents are read-only, they do NOT need a worktree.
102
163
  ## Commit granularity enforcement
103
164
 
104
165
  When collecting teammate reports:
166
+ - **FIRST: verify commits exist on the session branch** — spawn a Task subagent (Bash):
167
+ `git -C ../[repo] log session/{name} --oneline -10`
168
+ If 0 commits → the teammate forgot to commit. DO NOT accept the report.
169
+ Re-dispatch with explicit instruction to checkout + commit.
105
170
  - **Check commit count vs plan** — the plan defines N commit units, the teammate must have N+ commits
106
171
  - **Flag giant commits** — any commit >400 lines gets flagged in the session log
107
172
  - **If a teammate made a single commit for all tasks**: ask them to split via SendMessage
108
173
  before accepting the wave as complete
174
+ - **If teammate reports "done" but 0 commits**: the worktree was likely cleaned up.
175
+ Check if the worktree still exists. If lost, re-dispatch entirely.
109
176
  - **Progress tracker** in the plan must be updated after each teammate report
110
177
 
111
178
  ## What you NEVER do
@@ -117,9 +184,12 @@ When collecting teammate reports:
117
184
  - Accept a single giant commit covering multiple tasks — enforce atomic commits
118
185
  - Let the context grow (compact after each cycle)
119
186
  - Launch wave 2 before wave 1 has reported
187
+ - Create branches with `git checkout -b` in repos — always `git branch` (no checkout)
188
+ - Let teammates create their own branches — they use the session branch
120
189
 
121
190
  ## What you CAN write
122
191
  - Plans in `./plans/`
192
+ - Session files in `./.sessions/`
123
193
  - `./workspace.md` and `./constitution.md`
124
194
  - Any file in your orchestrator/ directory
125
195
 
@@ -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 11 hooks | List the missing ones |
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 (11/11) ✅ OK ║
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,29 @@ 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
+ CRITICAL: Do NOT run `git checkout` in the main repo. Do NOT use `git -C ../repo checkout`.
19
+ You are already in an isolated worktree — all git commands run HERE, not in the main repo.
20
+
21
+ 1. FIRST THING: Switch to the session branch inside your worktree:
22
+ git checkout session/{session-name}
23
+ (This is safe — you are in a worktree, not the main repo)
24
+ 2. Verify you are on the right branch:
25
+ git branch --show-current (must show: session/{session-name})
26
+ 3. If checkout fails with "did not match any file(s)":
27
+ git fetch origin session/{session-name}
28
+ git checkout session/{session-name}
29
+ 4. Commit AFTER EACH logical unit — do NOT wait until the end
30
+ 5. Before reporting back, verify ALL changes are committed:
31
+ git status (must show: nothing to commit, working tree clean)
32
+ 6. If git status shows uncommitted changes when you're done: COMMIT THEM NOW
33
+
34
+ Branch `session/{session-name}` ALREADY EXISTS. Do NOT create other branches.
35
+ Do NOT create branches named worktree-agent-* — use the session branch.
36
+
14
37
  ## Constitution (non-negotiable)
15
38
  [paste all rules from your workspace's constitution.md]
16
39
 
@@ -30,7 +53,8 @@ You are teammate-[service]. Read the CLAUDE.md in your repo first.
30
53
  6. **Atomic commits** — follow the commit plan below
31
54
  7. If you hit an architectural decision NOT covered by the plan: STOP and
32
55
  report the dilemma instead of guessing
33
- 8. Report back: files created/modified, tests pass/fail, dead code found,
56
+ 8. **Before reporting back**: run `git status` — if anything is uncommitted, commit it NOW
57
+ 9. Report back: files created/modified, tests pass/fail, dead code found,
34
58
  commits made (hash + message), blockers
35
59
 
36
60
  ## Commit strategy (mandatory)
@@ -43,7 +67,6 @@ You are teammate-[service]. Read the CLAUDE.md in your repo first.
43
67
  - **Commit message format**: `feat(domain): description` or `fix(domain): description`
44
68
  - **Each commit must compile and pass tests** — no broken intermediate states
45
69
  - **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
70
  ```
48
71
 
49
72
  ## Frontend teammate spawn template
@@ -51,6 +74,29 @@ You are teammate-[service]. Read the CLAUDE.md in your repo first.
51
74
  ```
52
75
  You are teammate-[service]. Read the CLAUDE.md in your repo first.
53
76
 
77
+ ## Git workflow (CRITICAL — read first)
78
+ You are working in a temporary worktree. If you don't commit, YOUR WORK WILL BE LOST
79
+ when the worktree is cleaned up.
80
+
81
+ CRITICAL: Do NOT run `git checkout` in the main repo. Do NOT use `git -C ../repo checkout`.
82
+ You are already in an isolated worktree — all git commands run HERE, not in the main repo.
83
+
84
+ 1. FIRST THING: Switch to the session branch inside your worktree:
85
+ git checkout session/{session-name}
86
+ (This is safe — you are in a worktree, not the main repo)
87
+ 2. Verify you are on the right branch:
88
+ git branch --show-current (must show: session/{session-name})
89
+ 3. If checkout fails with "did not match any file(s)":
90
+ git fetch origin session/{session-name}
91
+ git checkout session/{session-name}
92
+ 4. Commit AFTER EACH logical unit — do NOT wait until the end
93
+ 5. Before reporting back, verify ALL changes are committed:
94
+ git status (must show: nothing to commit, working tree clean)
95
+ 6. If git status shows uncommitted changes when you're done: COMMIT THEM NOW
96
+
97
+ Branch `session/{session-name}` ALREADY EXISTS. Do NOT create other branches.
98
+ Do NOT create branches named worktree-agent-* — use the session branch.
99
+
54
100
  ## Constitution (non-negotiable)
55
101
  [paste all rules from your workspace's constitution.md]
56
102
 
@@ -72,8 +118,9 @@ You are teammate-[service]. Read the CLAUDE.md in your repo first.
72
118
  6. List any dead code (unused components, composables, store actions, CSS)
73
119
  7. **Atomic commits** — follow the commit plan below
74
120
  8. If you hit an architectural decision NOT covered by the plan: STOP and escalate
75
- 9. Report back: files created/modified, tests pass/fail, dead code found,
76
- UX compliance, commits made (hash + message), blockers
121
+ 9. **Before reporting back**: run `git status` — if anything is uncommitted, commit it NOW
122
+ 10. Report back: files created/modified, tests pass/fail, dead code found,
123
+ UX compliance, commits made (hash + message), blockers
77
124
 
78
125
  ## Commit strategy (mandatory)
79
126
  - **One commit per logical unit** — each task = one commit minimum
@@ -86,7 +133,6 @@ You are teammate-[service]. Read the CLAUDE.md in your repo first.
86
133
  - **Commit message format**: `feat(domain): description` or `fix(domain): description`
87
134
  - **Each commit must compile and pass tests** — no broken intermediate states
88
135
  - **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
136
  ```
91
137
 
92
138
  ## Infra/Config teammate spawn template
@@ -94,6 +140,29 @@ You are teammate-[service]. Read the CLAUDE.md in your repo first.
94
140
  ```
95
141
  You are teammate-[service]. Read the CLAUDE.md in your repo first.
96
142
 
143
+ ## Git workflow (CRITICAL — read first)
144
+ You are working in a temporary worktree. If you don't commit, YOUR WORK WILL BE LOST
145
+ when the worktree is cleaned up.
146
+
147
+ CRITICAL: Do NOT run `git checkout` in the main repo. Do NOT use `git -C ../repo checkout`.
148
+ You are already in an isolated worktree — all git commands run HERE, not in the main repo.
149
+
150
+ 1. FIRST THING: Switch to the session branch inside your worktree:
151
+ git checkout session/{session-name}
152
+ (This is safe — you are in a worktree, not the main repo)
153
+ 2. Verify you are on the right branch:
154
+ git branch --show-current (must show: session/{session-name})
155
+ 3. If checkout fails with "did not match any file(s)":
156
+ git fetch origin session/{session-name}
157
+ git checkout session/{session-name}
158
+ 4. Commit AFTER EACH logical unit — do NOT wait until the end
159
+ 5. Before reporting back, verify ALL changes are committed:
160
+ git status (must show: nothing to commit, working tree clean)
161
+ 6. If git status shows uncommitted changes when you're done: COMMIT THEM NOW
162
+
163
+ Branch `session/{session-name}` ALREADY EXISTS. Do NOT create other branches.
164
+ Do NOT create branches named worktree-agent-* — use the session branch.
165
+
97
166
  ## Constitution (non-negotiable)
98
167
  [paste all rules from your workspace's constitution.md]
99
168
 
@@ -108,9 +177,9 @@ You are teammate-[service]. Read the CLAUDE.md in your repo first.
108
177
  5. **Atomic commits** — one commit per logical config change
109
178
  6. Commit message format: `chore(service): description`
110
179
  7. If you hit an architectural decision NOT covered by the plan: STOP and escalate
111
- 8. Report back: files modified, consistency check results,
180
+ 8. **Before reporting back**: run `git status` — if anything is uncommitted, commit it NOW
181
+ 9. Report back: files modified, consistency check results,
112
182
  commits made (hash + message), blockers
113
- - Branch: `feature/[name]`
114
183
  ```
115
184
 
116
185
  ## Explore/Haiku subagent template (read-only)
@@ -134,3 +203,6 @@ When a teammate reports back:
134
203
  - **Architectural decision not in plan** (blocking): STOP the wave, escalate to user
135
204
  - **No report after extended time**: send a status request via SendMessage
136
205
  - **Max re-dispatches per teammate per wave**: 2. After that, escalate to user.
206
+ - **0 commits reported**: the teammate likely forgot to commit. Check the worktree
207
+ with a Task subagent before accepting the report. If changes exist uncommitted,
208
+ re-dispatch with explicit instruction to commit.
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env bash
2
+ # guard-session-checkout.sh
3
+ # PreToolUse hook (matcher: Bash): blocks `git checkout session/` in main repos.
4
+ # Session branches must only be checked out INSIDE worktrees, never in main repos
5
+ # (doing so disrupts other parallel sessions).
6
+ # v4.2.1: hard guardrail — this hook BLOCKS (exit 0 + deny JSON).
7
+ set -euo pipefail
8
+
9
+ INPUT=$(cat)
10
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null) || true
11
+
12
+ [ -z "$COMMAND" ] && exit 0
13
+
14
+ # Only care about git checkout/switch targeting session/ branches
15
+ if ! echo "$COMMAND" | grep -qE 'git\s+(checkout|switch)\s+.*session/' 2>/dev/null; then
16
+ exit 0
17
+ fi
18
+
19
+ # Pattern 1: git -C <repo> checkout session/ — always wrong (targets main repo from outside)
20
+ if echo "$COMMAND" | grep -qE 'git\s+-C\s+\S+\s+(checkout|switch)\s+.*session/' 2>/dev/null; then
21
+ printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"BLOCKED: git checkout session/ with -C targets the main repo directly. This disrupts other parallel sessions. You are in a worktree — run `git checkout session/{name}` from INSIDE your worktree (without -C) instead. If you are not in a worktree, something is wrong with your isolation setup."}}'
22
+ exit 0
23
+ fi
24
+
25
+ # Pattern 2: git checkout session/ without -C — check if we are in a main repo
26
+ # In a worktree, .git is a FILE (gitdir pointer). In a main repo, .git is a DIRECTORY.
27
+ if [ -d ".git" ] && [ ! -f ".git" ]; then
28
+ printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"BLOCKED: git checkout session/ detected in a main repo (not a worktree). Checking out a session branch in the main repo disrupts other parallel sessions. You must work in an isolated worktree. If you are a teammate, your worktree should already exist — run `git checkout session/{name}` from inside it."}}'
29
+ exit 0
30
+ fi
31
+
32
+ # We are in a worktree (.git is a file) — allow the checkout
33
+ exit 0
@@ -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. For each service with status ✅, identify the feature branch name
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: feature/[name] → [target]
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] | feature/[x] | ✅/❌ | none/[list] | ✅/❌ | ✅/❌ |
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 paths
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-workspace",
3
- "version": "4.1.4",
3
+ "version": "4.2.1",
4
4
  "description": "Claude Code multi-workspace orchestrator — skills, hooks, agents, and templates for multi-service projects",
5
5
  "bin": {
6
6
  "cc-workspace": "./bin/cli.js"