cc-workspace 4.7.0 → 5.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +291 -0
  2. package/README.md +123 -41
  3. package/bin/cli.js +313 -134
  4. package/global-skills/agents/e2e-validator.md +151 -32
  5. package/global-skills/agents/implementer.md +80 -68
  6. package/global-skills/agents/reviewer.md +192 -0
  7. package/global-skills/agents/security-auditor.md +345 -0
  8. package/global-skills/agents/team-lead.md +93 -101
  9. package/global-skills/agents/workspace-init.md +16 -5
  10. package/global-skills/bootstrap-repo/SKILL.md +1 -0
  11. package/global-skills/cleanup/SKILL.md +35 -25
  12. package/global-skills/cross-service-check/SKILL.md +1 -0
  13. package/global-skills/cycle-retrospective/SKILL.md +6 -4
  14. package/global-skills/dispatch-feature/SKILL.md +225 -173
  15. package/global-skills/dispatch-feature/references/anti-patterns.md +52 -35
  16. package/global-skills/dispatch-feature/references/spawn-templates.md +140 -97
  17. package/global-skills/doctor/SKILL.md +124 -25
  18. package/global-skills/e2e-validator/references/container-strategies.md +55 -23
  19. package/global-skills/hooks/orphan-cleanup.sh +60 -0
  20. package/global-skills/hooks/permission-auto-approve.sh +61 -4
  21. package/global-skills/hooks/session-start-context.sh +10 -47
  22. package/global-skills/hooks/test_hooks.sh +242 -0
  23. package/global-skills/hooks/user-prompt-guard.sh +6 -6
  24. package/global-skills/hooks/validate-spawn-prompt.sh +40 -30
  25. package/global-skills/incident-debug/SKILL.md +1 -0
  26. package/global-skills/merge-prep/SKILL.md +1 -0
  27. package/global-skills/metrics/SKILL.md +139 -0
  28. package/global-skills/plan-review/SKILL.md +2 -1
  29. package/global-skills/qa-ruthless/SKILL.md +2 -0
  30. package/global-skills/refresh-profiles/SKILL.md +1 -0
  31. package/global-skills/rules/context-hygiene.md +4 -19
  32. package/global-skills/rules/model-routing.md +31 -18
  33. package/global-skills/session/SKILL.md +41 -20
  34. package/global-skills/templates/workspace.template.md +1 -1
  35. package/package.json +4 -3
@@ -1,25 +1,30 @@
1
1
  # Container Strategies — E2E Validator Reference
2
2
 
3
+ ## Key principle (v5)
4
+
5
+ **No worktrees.** The e2e-validator works directly on repos checked out at the correct branch
6
+ (merged source branch or session branch). Build contexts point to `../{repo}` directly.
7
+
3
8
  ## Overlay strategy (preferred — repos have docker-compose)
4
9
 
5
10
  When repos already have `docker-compose.yml`, generate an **overlay** file that:
6
11
  - Creates a shared e2e network
7
12
  - Overrides env vars for test isolation
8
13
  - Adds health checks if missing
9
- - Maps worktree paths as build contexts
14
+ - Uses `../{repo}` as build context (no /tmp/ paths)
10
15
 
11
16
  ### Template: docker-compose.e2e.yml (overlay)
12
17
 
13
18
  ```yaml
14
19
  # E2E overlay — use with: docker compose -f ../repo/docker-compose.yml -f ./e2e/docker-compose.e2e.yml up
15
20
  # Generated by e2e-validator agent
21
+ # Ports: check e2e-config.md for assigned ports — verify they are free before starting
16
22
 
17
23
  networks:
18
24
  e2e:
19
25
  driver: bridge
20
26
 
21
27
  services:
22
- # Override each service to join the e2e network and use test env
23
28
  api:
24
29
  networks:
25
30
  - e2e
@@ -62,26 +67,43 @@ services:
62
67
  - /var/lib/postgresql/data # RAM disk for speed
63
68
  ```
64
69
 
65
- ### Worktree build context override
66
-
67
- When testing session branches, override the build context to point to worktrees:
70
+ ### Build context — always point to repo directory
68
71
 
69
72
  ```yaml
70
73
  services:
71
74
  api:
72
75
  build:
73
- context: /tmp/e2e-api
76
+ context: ../api # repo at current checked-out branch (merged or session)
74
77
  dockerfile: Dockerfile
75
78
  frontend:
76
79
  build:
77
- context: /tmp/e2e-frontend
80
+ context: ../frontend
78
81
  dockerfile: Dockerfile
79
82
  ```
80
83
 
84
+ **Never use /tmp/ paths.** The e2e-validator checks out the correct branch directly in the repo.
85
+
81
86
  ## Standalone strategy (repos have NO docker-compose)
82
87
 
83
88
  Generate a complete `docker-compose.e2e.yml` from scratch.
84
89
 
90
+ ### Port assignment
91
+
92
+ Assign ports in `e2e-config.md` and check they are free before starting.
93
+ Prefer non-standard ports to avoid conflicts with local dev servers:
94
+
95
+ | Service type | Default E2E port |
96
+ |---|---|
97
+ | PHP/Laravel API | 18000 |
98
+ | Node.js API | 13000 |
99
+ | Vue/Quasar frontend | 19000 |
100
+ | React/Next frontend | 13001 |
101
+ | PostgreSQL | 15432 |
102
+ | MySQL | 13306 |
103
+ | Redis | 16379 |
104
+
105
+ Using non-standard ports reduces conflicts with local dev environments.
106
+
85
107
  ### Per-stack templates
86
108
 
87
109
  #### PHP/Laravel
@@ -89,10 +111,10 @@ Generate a complete `docker-compose.e2e.yml` from scratch.
89
111
  services:
90
112
  api:
91
113
  build:
92
- context: /tmp/e2e-api
114
+ context: ../api
93
115
  dockerfile: Dockerfile
94
116
  ports:
95
- - "8000:8000"
117
+ - "18000:8000"
96
118
  environment:
97
119
  - APP_ENV=testing
98
120
  - APP_KEY=${APP_KEY}
@@ -121,9 +143,9 @@ services:
121
143
  services:
122
144
  api:
123
145
  build:
124
- context: /tmp/e2e-api
146
+ context: ../api
125
147
  ports:
126
- - "3000:3000"
148
+ - "13000:3000"
127
149
  environment:
128
150
  - NODE_ENV=test
129
151
  - DATABASE_URL=postgresql://test:test@db:5432/app_test
@@ -143,11 +165,11 @@ services:
143
165
  services:
144
166
  frontend:
145
167
  build:
146
- context: /tmp/e2e-frontend
168
+ context: ../frontend
147
169
  ports:
148
- - "9000:9000"
170
+ - "19000:9000"
149
171
  environment:
150
- - VITE_API_URL=http://localhost:8000
172
+ - VITE_API_URL=http://localhost:18000
151
173
  healthcheck:
152
174
  test: ["CMD", "curl", "-sf", "http://localhost:9000"]
153
175
  interval: 5s
@@ -162,11 +184,11 @@ services:
162
184
  services:
163
185
  frontend:
164
186
  build:
165
- context: /tmp/e2e-frontend
187
+ context: ../frontend
166
188
  ports:
167
- - "3000:3000"
189
+ - "13001:3000"
168
190
  environment:
169
- - NEXT_PUBLIC_API_URL=http://localhost:8000
191
+ - NEXT_PUBLIC_API_URL=http://localhost:13000
170
192
  healthcheck:
171
193
  test: ["CMD", "curl", "-sf", "http://localhost:3000"]
172
194
  interval: 5s
@@ -180,9 +202,9 @@ services:
180
202
  services:
181
203
  api:
182
204
  build:
183
- context: /tmp/e2e-api
205
+ context: ../api
184
206
  ports:
185
- - "8000:8000"
207
+ - "18000:8000"
186
208
  environment:
187
209
  - DATABASE_URL=postgresql://test:test@db:5432/app_test
188
210
  - ENVIRONMENT=testing
@@ -203,9 +225,9 @@ services:
203
225
  services:
204
226
  api:
205
227
  build:
206
- context: /tmp/e2e-api
228
+ context: ../api
207
229
  ports:
208
- - "8080:8080"
230
+ - "18080:8080"
209
231
  environment:
210
232
  - DATABASE_URL=postgresql://test:test@db:5432/app_test
211
233
  - ENV=test
@@ -230,6 +252,8 @@ services:
230
252
  - POSTGRES_DB=app_test
231
253
  - POSTGRES_USER=test
232
254
  - POSTGRES_PASSWORD=test
255
+ ports:
256
+ - "15432:5432"
233
257
  healthcheck:
234
258
  test: ["CMD-SHELL", "pg_isready -U test"]
235
259
  interval: 3s
@@ -249,6 +273,8 @@ services:
249
273
  - MYSQL_USER=test
250
274
  - MYSQL_PASSWORD=test
251
275
  - MYSQL_ROOT_PASSWORD=root
276
+ ports:
277
+ - "13306:3306"
252
278
  healthcheck:
253
279
  test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
254
280
  interval: 3s
@@ -263,6 +289,8 @@ services:
263
289
  ```yaml
264
290
  redis:
265
291
  image: redis:7-alpine
292
+ ports:
293
+ - "16379:6379"
266
294
  healthcheck:
267
295
  test: ["CMD", "redis-cli", "ping"]
268
296
  interval: 3s
@@ -277,6 +305,8 @@ services:
277
305
  image: mongo:7
278
306
  environment:
279
307
  - MONGO_INITDB_DATABASE=app_test
308
+ ports:
309
+ - "127017:27017"
280
310
  healthcheck:
281
311
  test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
282
312
  interval: 3s
@@ -289,11 +319,12 @@ services:
289
319
 
290
320
  ## Startup sequence
291
321
 
292
- Always start in dependency order:
322
+ Always start in dependency order with global timeout (10 minutes max):
293
323
  1. Databases (db, redis, mongo) — wait for healthy
294
324
  2. Backend services (api) — wait for healthy
295
325
  3. Frontend services — wait for healthy
296
- 4. Run tests
326
+ 4. Seed test data if seeder exists
327
+ 5. Run tests
297
328
 
298
329
  ## Teardown
299
330
 
@@ -302,3 +333,4 @@ docker compose -f ./e2e/docker-compose.e2e.yml down -v --remove-orphans
302
333
  ```
303
334
 
304
335
  Always use `-v` to remove test volumes and `--remove-orphans` for cleanup.
336
+ No worktree removal needed — branches are restored via `git checkout` only.
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env bash
2
+ # orphan-cleanup.sh
3
+ # SessionStart hook (phase 1): cleans ONLY orphaned worktrees.
4
+ # v5.0: Active session worktrees (/tmp/{repo}-{session-name}/) are NEVER cleaned here.
5
+ # Only truly orphaned worktrees (no matching session JSON) are removed.
6
+ # Stdout on exit 0 is added as context visible to Claude.
7
+ set -euo pipefail
8
+
9
+ cat > /dev/null
10
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"
11
+ SESSIONS_DIR="$PROJECT_DIR/.sessions"
12
+ ORPHAN_COUNT=0
13
+
14
+ shopt -s nullglob
15
+ for wt in /tmp/*-session-* /tmp/e2e-*; do
16
+ [ -d "$wt" ] || continue
17
+
18
+ # Check if this worktree belongs to an active session
19
+ IS_ACTIVE_SESSION=0
20
+ if [ -d "$SESSIONS_DIR" ]; then
21
+ for session_file in "$SESSIONS_DIR"/*.json; do
22
+ [ -f "$session_file" ] || continue
23
+ SESSION_STATUS=$(jq -r '.status // "unknown"' "$session_file" 2>/dev/null) || continue
24
+ [ "$SESSION_STATUS" != "active" ] && continue
25
+ if jq -r '.repos[].worktree_path // empty' "$session_file" 2>/dev/null | grep -qF "$wt"; then
26
+ IS_ACTIVE_SESSION=1
27
+ break
28
+ fi
29
+ done
30
+ fi
31
+
32
+ # Skip active session worktrees — they persist until session close
33
+ [ "$IS_ACTIVE_SESSION" -eq 1 ] && continue
34
+
35
+ # Check if registered with a repo
36
+ REPO_FOUND=0
37
+ PARENT_DIR="$(cd "$PROJECT_DIR/.." 2>/dev/null && pwd)" || continue
38
+ for repo_dir in "$PARENT_DIR"/*/; do
39
+ [ -d "$repo_dir/.git" ] || continue
40
+ if git -C "$repo_dir" worktree list 2>/dev/null | grep -q "$wt"; then
41
+ if [ ! -f "$wt/.git" ]; then
42
+ git -C "$repo_dir" worktree remove "$wt" --force 2>/dev/null && ORPHAN_COUNT=$((ORPHAN_COUNT + 1))
43
+ REPO_FOUND=1
44
+ break
45
+ fi
46
+ REPO_FOUND=1
47
+ break
48
+ fi
49
+ done
50
+
51
+ if [ "$REPO_FOUND" -eq 0 ] && { [ -d "$wt/.git" ] || [ -f "$wt/.git" ]; }; then
52
+ rm -rf "$wt" 2>/dev/null && ORPHAN_COUNT=$((ORPHAN_COUNT + 1))
53
+ fi
54
+ done
55
+
56
+ if [ "$ORPHAN_COUNT" -gt 0 ]; then
57
+ echo "[Cleanup] Removed $ORPHAN_COUNT orphaned worktree(s) from /tmp/ (no matching active session)."
58
+ fi
59
+
60
+ exit 0
@@ -1,16 +1,73 @@
1
1
  #!/usr/bin/env bash
2
2
  # permission-auto-approve.sh
3
- # PermissionRequest hook: auto-approves Read/Glob/Grep to reduce friction.
3
+ # PermissionRequest hook: auto-approves Read/Glob/Grep and safe git/bash operations
4
+ # for the orchestrator (Opus) to reduce friction on exploration and git management.
4
5
  # Uses hookSpecificOutput JSON with decision.behavior: "allow".
6
+ #
7
+ # Security: compound commands (&&, ||, ;, |, backticks, $()) are NEVER auto-approved.
8
+ # Only single, known-safe commands get auto-approval.
5
9
  set -euo pipefail
6
10
 
7
11
  INPUT=$(cat)
8
12
  TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null) || true
9
13
 
14
+ ALLOW_JSON='{"hookSpecificOutput":{"hookEventName":"PermissionRequest","decision":{"behavior":"allow","toolNames":["Bash"]}}}'
15
+ ALLOW_READ='{"hookSpecificOutput":{"hookEventName":"PermissionRequest","decision":{"behavior":"allow","toolNames":["Read","Glob","Grep"]}}}'
16
+
17
+ # Auto-approve read-only tools
10
18
  if echo "$TOOL_NAME" | grep -qE '^(Read|Glob|Grep)$'; then
11
- cat << 'EOF'
12
- {"hookSpecificOutput":{"hookEventName":"PermissionRequest","decision":{"behavior":"allow","toolNames":["Read","Glob","Grep"]}}}
13
- EOF
19
+ echo "$ALLOW_READ"
20
+ exit 0
21
+ fi
22
+
23
+ # Auto-approve Bash for safe git operations and test/typecheck commands in worktrees
24
+ if [ "$TOOL_NAME" = "Bash" ]; then
25
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null) || true
26
+
27
+ # SECURITY: reject compound commands — never auto-approve chained operations
28
+ # This prevents `git branch session/x && rm -rf /` style bypasses
29
+ # Exception: `cd /tmp/... && <safe-test-command>` is a legitimate micro-QA pattern
30
+ if echo "$COMMAND" | grep -qE '(&&|\|\||[;|]|`|\$\()'; then
31
+ # Allow: cd /tmp/{worktree} && {test-command}
32
+ if echo "$COMMAND" | grep -qE '^\s*cd\s+(/private)?/tmp/[a-zA-Z][a-zA-Z0-9_-]+\s+&&\s+(npm run (typecheck|test|lint)|php artisan test|go test|pytest|npx vitest|npx jest|yarn test|pnpm test)\b'; then
33
+ echo "$ALLOW_JSON"
34
+ exit 0
35
+ fi
36
+ # All other compound commands — do not auto-approve
37
+ exit 0
38
+ fi
39
+
40
+ # Safe git read operations (handles: git, git --no-pager, git -c key=val, git -C path)
41
+ if echo "$COMMAND" | grep -qE '^\s*git\s+(--no-pager\s+|(-[cC]\s+\S+\s+))*(log|status|diff|branch --list|branch --show-current|worktree list|show|rev-parse)\b'; then
42
+ echo "$ALLOW_JSON"
43
+ exit 0
44
+ fi
45
+
46
+ # Safe git branch creation (no checkout) — handles -C path prefix
47
+ # Only session/ prefixed branches
48
+ if echo "$COMMAND" | grep -qE '^\s*git\s+(--no-pager\s+|(-[cC]\s+\S+\s+))*branch\s+session/'; then
49
+ echo "$ALLOW_JSON"
50
+ exit 0
51
+ fi
52
+
53
+ # Safe git worktree add for session worktrees in /tmp/
54
+ if echo "$COMMAND" | grep -qE '^\s*git\s+((-[cC]\s+\S+\s+))*worktree\s+add\s+/tmp/'; then
55
+ echo "$ALLOW_JSON"
56
+ exit 0
57
+ fi
58
+
59
+ # Safe typecheck/test commands in /tmp/ worktrees (micro-QA)
60
+ # Compound check above already rejected chained commands
61
+ if echo "$COMMAND" | grep -qE '^\s*cd\s+(/private)?/tmp/[a-zA-Z]'; then
62
+ echo "$ALLOW_JSON"
63
+ exit 0
64
+ fi
65
+
66
+ # Safe git diff for debug artifact check in /tmp/ worktrees
67
+ if echo "$COMMAND" | grep -qE '^\s*git\s+-C\s+(/private)?/tmp/\S+\s+diff\b'; then
68
+ echo "$ALLOW_JSON"
69
+ exit 0
70
+ fi
14
71
  fi
15
72
 
16
73
  exit 0
@@ -1,46 +1,16 @@
1
1
  #!/usr/bin/env bash
2
2
  # session-start-context.sh
3
- # SessionStart hook: injects active plan context, repo status, and cleans orphan worktrees.
3
+ # SessionStart hook (phase 2): injects active plan context, repo status, session info.
4
+ # Orphan worktree cleanup is handled by orphan-cleanup.sh (separate hook).
4
5
  # Stdout on exit 0 is added as context visible to Claude.
5
6
  set -euo pipefail
6
7
 
7
8
  cat > /dev/null
8
9
  PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"
10
+ SESSIONS_DIR="$PROJECT_DIR/.sessions"
9
11
  OUTPUT=""
10
12
 
11
- # ── Orphan worktree cleanup ──────────────────────────────────
12
- # Clean /tmp/ worktrees that were left behind by crashed implementers.
13
- # These are safe to remove: they're temporary by design.
14
- ORPHAN_COUNT=0
15
- for wt in /tmp/*-session-* /tmp/e2e-* 2>/dev/null; do
16
- [ -d "$wt" ] || continue
17
- # Check if the worktree is registered with any repo
18
- REPO_FOUND=0
19
- PARENT_DIR="$(cd "$PROJECT_DIR/.." 2>/dev/null && pwd)" || continue
20
- for repo_dir in "$PARENT_DIR"/*/; do
21
- [ -d "$repo_dir/.git" ] || continue
22
- if git -C "$repo_dir" worktree list 2>/dev/null | grep -q "$wt"; then
23
- # Worktree is registered — check if it's stale (no git lock, no recent activity)
24
- if [ ! -f "$wt/.git" ]; then
25
- # Not a valid worktree anymore — remove registration
26
- git -C "$repo_dir" worktree remove "$wt" --force 2>/dev/null && ORPHAN_COUNT=$((ORPHAN_COUNT + 1))
27
- REPO_FOUND=1
28
- break
29
- fi
30
- REPO_FOUND=1
31
- break
32
- fi
33
- done
34
- if [ "$REPO_FOUND" -eq 0 ] && [ -d "$wt/.git" ] || [ -f "$wt/.git" ]; then
35
- # Unregistered worktree directory — likely a crash remnant. Remove it.
36
- rm -rf "$wt" 2>/dev/null && ORPHAN_COUNT=$((ORPHAN_COUNT + 1))
37
- fi
38
- done
39
- if [ "$ORPHAN_COUNT" -gt 0 ]; then
40
- OUTPUT+="[Cleanup] Removed $ORPHAN_COUNT orphan worktree(s) from /tmp/.\n"
41
- fi
42
-
43
- # ── Active plans ─────────────────────────────────────────────
13
+ # ── Active plans ─────────────────────────────────────────────────────────────
44
14
  if [ -d "$PROJECT_DIR/plans" ]; then
45
15
  ACTIVE_PLANS=$(find "$PROJECT_DIR/plans" -name '*.md' \
46
16
  ! -name '_TEMPLATE.md' \
@@ -49,26 +19,19 @@ if [ -d "$PROJECT_DIR/plans" ]; then
49
19
  | sort | tail -5)
50
20
 
51
21
  if [ -n "$ACTIVE_PLANS" ]; then
52
- FIRST_PLAN=""
53
22
  OUTPUT+="[Session context] Active plans with pending tasks:\n"
54
23
  while IFS= read -r plan; do
55
24
  PLAN_NAME=$(basename "$plan")
56
- [ -z "$FIRST_PLAN" ] && FIRST_PLAN="$PLAN_NAME"
57
25
  TODO=$(grep -c '⏳' "$plan" 2>/dev/null || echo "0")
58
26
  WIP=$(grep -c '🔄' "$plan" 2>/dev/null || echo "0")
59
27
  DONE=$(grep -c '✅' "$plan" 2>/dev/null || echo "0")
60
28
  OUTPUT+=" - $PLAN_NAME (⏳ $TODO pending, 🔄 $WIP in progress, ✅ $DONE done)\n"
61
29
  done <<< "$ACTIVE_PLANS"
62
30
  OUTPUT+="\nRead these plans to resume where you left off.\n"
63
-
64
- if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "$FIRST_PLAN" ]; then
65
- echo "ACTIVE_PLAN=$FIRST_PLAN" >> "$CLAUDE_ENV_FILE"
66
- fi
67
31
  fi
68
32
  fi
69
33
 
70
- # ── Active sessions ──────────────────────────────────────────
71
- SESSIONS_DIR="$PROJECT_DIR/.sessions"
34
+ # ── Active sessions ───────────────────────────────────────────────────────────
72
35
  if [ -d "$SESSIONS_DIR" ]; then
73
36
  ACTIVE_SESSIONS=""
74
37
  for session_file in "$SESSIONS_DIR"/*.json; do
@@ -81,16 +44,16 @@ if [ -d "$SESSIONS_DIR" ]; then
81
44
  done
82
45
  if [ -n "$ACTIVE_SESSIONS" ]; then
83
46
  OUTPUT+="[Session context] Active sessions:\n$ACTIVE_SESSIONS"
84
- OUTPUT+="Session branches are already created. Teammates must use these branches.\n\n"
47
+ OUTPUT+="Session branches and worktrees are already set up. Teammates use worktree_path from session JSON.\n\n"
85
48
  fi
86
49
  fi
87
50
 
88
- # ── Workspace check ──────────────────────────────────────────
51
+ # ── Workspace check ───────────────────────────────────────────────────────────
89
52
  if [ ! -f "$PROJECT_DIR/workspace.md" ]; then
90
- OUTPUT+="[WARNING] No workspace.md found. Run setup-workspace.sh first.\n"
53
+ OUTPUT+="[WARNING] No workspace.md found. Run: claude --agent workspace-init\n"
91
54
  fi
92
55
 
93
- # ── First-session detection ──────────────────────────────────
56
+ # ── First-session detection ───────────────────────────────────────────────────
94
57
  if [ -f "$PROJECT_DIR/workspace.md" ]; then
95
58
  if grep -q '\[UNCONFIGURED\]' "$PROJECT_DIR/workspace.md" 2>/dev/null; then
96
59
  OUTPUT+="[FIRST SESSION] workspace.md is not yet configured. Run: claude --agent workspace-init\n"
@@ -108,7 +71,7 @@ if [ -f "$PROJECT_DIR/workspace.md" ]; then
108
71
  fi
109
72
  fi
110
73
 
111
- # ── Auto-discovery: new repos ────────────────────────────────
74
+ # ── Auto-discovery: new repos ─────────────────────────────────────────────────
112
75
  if [ -f "$PROJECT_DIR/workspace.md" ] && ! grep -q '\[UNCONFIGURED\]' "$PROJECT_DIR/workspace.md" 2>/dev/null; then
113
76
  PARENT_DIR="$(cd "$PROJECT_DIR/.." 2>/dev/null && pwd)"
114
77
  NEW_REPOS=""