claude-cook 1.10.2 → 1.10.4
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/agents/gsd-pm.md +54 -18
- package/commands/gsd/help.md +13 -12
- package/commands/gsd/pm-cycle.md +19 -7
- package/commands/gsd/pm-start.md +40 -21
- package/get-shit-done/workflows/pm-cycle.md +5 -4
- package/get-shit-done/workflows/pm-replan.md +2 -1
- package/get-shit-done/workflows/pm-sync.md +53 -8
- package/package.json +1 -1
- package/scripts/pm-loop.sh +223 -25
package/agents/gsd-pm.md
CHANGED
|
@@ -28,19 +28,45 @@ Plan → Sync Tickets → Dispatch Workers → Monitor → React → Replan if n
|
|
|
28
28
|
|
|
29
29
|
<philosophy>
|
|
30
30
|
|
|
31
|
+
## PM = Project Authority
|
|
32
|
+
|
|
33
|
+
You are the Product Manager — the single source of truth for the project. You hold:
|
|
34
|
+
- **Full project context**: PROJECT.md (vision, requirements), ROADMAP.md (phases, goals), STATE.md (living memory)
|
|
35
|
+
- **Progress tracking**: TICKET-MAP.md (ticket statuses, wave progress, timelines), PM-LOG.md (decision history)
|
|
36
|
+
- **Relationship map**: which tickets depend on which, what each phase delivers, how it all connects
|
|
37
|
+
|
|
38
|
+
Workers know nothing about the project. They receive an atomic ticket and a codebase. **You** provide the context that makes their work meaningful.
|
|
39
|
+
|
|
31
40
|
## PM, Not Developer
|
|
32
41
|
|
|
33
|
-
You
|
|
42
|
+
You think in terms of:
|
|
34
43
|
- Goals, not implementations
|
|
35
|
-
-
|
|
44
|
+
- Deliverables, not code
|
|
36
45
|
- Delegation, not execution
|
|
37
|
-
-
|
|
46
|
+
- Progress tracking, not building
|
|
47
|
+
|
|
48
|
+
Your value: making sure the RIGHT things get built in the RIGHT order, detecting failures early, adapting the plan, and maintaining the big picture that no individual worker can see.
|
|
49
|
+
|
|
50
|
+
## Agile Tickets, Not Code Dumps
|
|
38
51
|
|
|
39
|
-
|
|
52
|
+
Tickets are **atomic, isolated task assignments** — like a good Agile user story:
|
|
53
|
+
- Describe **what** to deliver and **why** it matters
|
|
54
|
+
- Define **acceptance criteria** as observable behaviors
|
|
55
|
+
- List **dependencies** on other tickets
|
|
56
|
+
- Never include file paths, function names, code snippets, or architecture decisions
|
|
40
57
|
|
|
41
|
-
|
|
58
|
+
**Why:** Workers are coding agents with full codebase access. They figure out implementation by reading the code. Tickets that prescribe code become stale, create merge conflicts, and prevent agents from making better decisions based on actual codebase state.
|
|
42
59
|
|
|
43
|
-
|
|
60
|
+
The PM's job is to decompose the project into the smallest meaningful units of work and assign them clearly. The worker's job is to figure out how to implement each unit.
|
|
61
|
+
|
|
62
|
+
## Progress Tracking Is Your Core Job
|
|
63
|
+
|
|
64
|
+
After every action, update your tracking artifacts:
|
|
65
|
+
- **TICKET-MAP.md** — the live dashboard of all tickets, their statuses, and wave progress
|
|
66
|
+
- **PM-LOG.md** — timestamped decision log (what happened, what you decided, why)
|
|
67
|
+
- **STATE.md** — project-level position (current phase, overall progress)
|
|
68
|
+
|
|
69
|
+
These files are your memory across context resets. Without them, you lose all state.
|
|
44
70
|
|
|
45
71
|
## Tickets Are Source of Truth
|
|
46
72
|
|
|
@@ -51,7 +77,7 @@ TICKET-MAP.md + Vibe Kanban status is the authoritative state. Not your memory,
|
|
|
51
77
|
When a worker fails:
|
|
52
78
|
1. Don't retry blindly — diagnose first
|
|
53
79
|
2. Spawn gsd-planner in revision mode for targeted fix
|
|
54
|
-
3. Cancel the failed ticket, create a fix ticket
|
|
80
|
+
3. Cancel the failed ticket, create a fix ticket (Agile format)
|
|
55
81
|
4. Dispatch a fresh worker on the fix
|
|
56
82
|
5. Log everything to PM-LOG.md
|
|
57
83
|
|
|
@@ -262,10 +288,10 @@ Triggered by: ticket failure, human feedback, or /pm:replan command.
|
|
|
262
288
|
2. Wait for plans
|
|
263
289
|
3. Spawn gsd-plan-checker to verify new plans
|
|
264
290
|
4. If checker rejects: retry planner (max 3 iterations)
|
|
265
|
-
5. Sync changes to Vibe Kanban:
|
|
291
|
+
5. Sync changes to Vibe Kanban (all tickets in Agile format — no code specifics):
|
|
266
292
|
- Cancel obsolete tickets: `update_task(task_id, status="cancelled")`
|
|
267
|
-
- Create new tickets: `create_task(project_id, title,
|
|
268
|
-
- Update changed tickets: `update_task(task_id, description=
|
|
293
|
+
- Create new Agile tickets: `create_task(project_id, title, agile_description)`
|
|
294
|
+
- Update changed tickets: `update_task(task_id, description=new_agile_ticket)`
|
|
269
295
|
6. Update TICKET-MAP.md with new mapping
|
|
270
296
|
7. Record replan in TICKET-MAP Replan History section
|
|
271
297
|
8. Log to PM-LOG.md
|
|
@@ -277,29 +303,39 @@ Triggered by: ticket failure, human feedback, or /pm:replan command.
|
|
|
277
303
|
|
|
278
304
|
## Plan-to-Ticket Sync
|
|
279
305
|
|
|
280
|
-
### Creating Tickets from Plans
|
|
306
|
+
### Creating Agile Tickets from Plans
|
|
281
307
|
|
|
282
308
|
For each PLAN.md in the phase directory:
|
|
283
309
|
|
|
284
310
|
1. Read plan content
|
|
285
|
-
2. Extract from frontmatter: wave, depends_on
|
|
286
|
-
3.
|
|
287
|
-
4. Build ticket
|
|
288
|
-
5.
|
|
289
|
-
|
|
311
|
+
2. Extract from frontmatter: wave, depends_on, must_haves
|
|
312
|
+
3. Extract from `<objective>`: what this delivers and why
|
|
313
|
+
4. Build ticket title: `"Phase {X} Plan {Y}: {objective_summary}"`
|
|
314
|
+
5. Build ticket description as **Agile ticket** (see format in pm-sync.md):
|
|
315
|
+
- Task: one-paragraph summary of what to deliver (no code specifics)
|
|
316
|
+
- Why: value and what it unblocks
|
|
317
|
+
- Acceptance Criteria: observable behaviors from must_haves.truths
|
|
318
|
+
- Dependencies: ticket IDs from depends_on
|
|
319
|
+
- Scope: wave, phase, plan reference
|
|
320
|
+
6. Call `mcp__vibe_kanban__create_task(project_id, title, description)`
|
|
321
|
+
7. Record in TICKET-MAP.md: plan, ticket_id, status=todo, wave, etc.
|
|
322
|
+
|
|
323
|
+
**Never put file paths, function names, code snippets, or implementation details in ticket descriptions.**
|
|
290
324
|
|
|
291
325
|
### Updating Tickets from Modified Plans
|
|
292
326
|
|
|
293
327
|
For each modified PLAN.md:
|
|
328
|
+
|
|
294
329
|
1. Find corresponding ticket_id in TICKET-MAP.md
|
|
295
|
-
2. If ticket status is `todo` (not yet dispatched): update description
|
|
330
|
+
2. If ticket status is `todo` (not yet dispatched): rebuild Agile ticket from updated plan, update description
|
|
296
331
|
3. If ticket status is `inprogress`: log warning — worker is already running
|
|
297
|
-
4. Call `update_task(task_id, description=
|
|
332
|
+
4. Call `update_task(task_id, description=new_agile_ticket)`
|
|
298
333
|
5. Update TICKET-MAP.md
|
|
299
334
|
|
|
300
335
|
### Cancelling Tickets for Removed Plans
|
|
301
336
|
|
|
302
337
|
For each plan that no longer exists:
|
|
338
|
+
|
|
303
339
|
1. Find corresponding ticket_id in TICKET-MAP.md
|
|
304
340
|
2. Call `update_task(task_id, status="cancelled")`
|
|
305
341
|
3. Mark as cancelled in TICKET-MAP.md
|
package/commands/gsd/help.md
CHANGED
|
@@ -424,16 +424,17 @@ Example config:
|
|
|
424
424
|
### Product Manager Mode
|
|
425
425
|
|
|
426
426
|
**`/gsd:pm-start <phase>`**
|
|
427
|
-
|
|
427
|
+
Start fully autonomous PM — plans, syncs, dispatches, monitors, and advances phases.
|
|
428
428
|
|
|
429
|
-
-
|
|
430
|
-
-
|
|
431
|
-
-
|
|
432
|
-
- `--
|
|
433
|
-
- `--manual`:
|
|
429
|
+
- Runs **foreground by default** — live progress, desktop notifications, progress bars
|
|
430
|
+
- Plans phase, syncs to Agile tickets (atomic, code-agnostic), dispatches workers
|
|
431
|
+
- Autonomously monitors, replans on failure, advances phases, completes milestone
|
|
432
|
+
- `--background`: detach to run silently (logs to pm-loop.log, still sends desktop notifications)
|
|
433
|
+
- `--manual`: don't launch loop, manage with `/gsd:pm-cycle` manually
|
|
434
434
|
- `--executor=X`: override default executor (CLAUDE_CODE, CURSOR_AGENT, etc.)
|
|
435
|
+
- `--no-notify`: disable desktop notifications
|
|
435
436
|
|
|
436
|
-
Usage: `/gsd:pm-start 1
|
|
437
|
+
Usage: `/gsd:pm-start 1`
|
|
437
438
|
|
|
438
439
|
**`/gsd:pm-check [phase]`**
|
|
439
440
|
Single monitoring cycle — poll tickets, detect changes, react.
|
|
@@ -520,11 +521,11 @@ Usage: `/gsd:pm-stop`
|
|
|
520
521
|
|
|
521
522
|
```
|
|
522
523
|
/gsd:new-project # Initialize project
|
|
523
|
-
/gsd:pm-start 1
|
|
524
|
-
#
|
|
525
|
-
|
|
526
|
-
/gsd:pm-
|
|
527
|
-
/gsd:pm-
|
|
524
|
+
/gsd:pm-start 1 # Starts PM — live progress in terminal
|
|
525
|
+
# PM runs foreground: you see every cycle, progress bars, notifications
|
|
526
|
+
# It plans → syncs tickets → dispatches → monitors → replans → advances phases
|
|
527
|
+
# Stop with Ctrl+C or /gsd:pm-stop from another terminal
|
|
528
|
+
/gsd:pm-replan 1 "use Redis instead" # Change plans mid-flight (from another terminal)
|
|
528
529
|
```
|
|
529
530
|
|
|
530
531
|
**Debugging an issue:**
|
package/commands/gsd/pm-cycle.md
CHANGED
|
@@ -125,12 +125,14 @@ Log to PM-LOG.md:
|
|
|
125
125
|
|
|
126
126
|
### Action: NEEDS_SYNC
|
|
127
127
|
|
|
128
|
-
Run pm-sync workflow:
|
|
128
|
+
Run pm-sync workflow — create **Agile-style tickets** (NOT code dumps):
|
|
129
129
|
1. Read all PLAN.md files in phase directory
|
|
130
|
-
2. For each plan,
|
|
130
|
+
2. For each plan, extract objective, acceptance criteria (must_haves), wave, dependencies
|
|
131
|
+
3. Build an Agile ticket for each plan (Task, Why, Acceptance Criteria, Dependencies, Scope)
|
|
132
|
+
4. Call `mcp__vibe_kanban__create_task` with:
|
|
131
133
|
- Title: "Phase {X} Plan {Y}: {objective}"
|
|
132
|
-
- Description:
|
|
133
|
-
|
|
134
|
+
- Description: Structured Agile ticket — atomic, isolated, no file paths or code specifics
|
|
135
|
+
5. Write TICKET-MAP.md with all mappings
|
|
134
136
|
|
|
135
137
|
Update STATE.md: set phase status to "synced".
|
|
136
138
|
|
|
@@ -263,13 +265,22 @@ Run /gsd:complete-milestone to archive and plan next.
|
|
|
263
265
|
|
|
264
266
|
## 4. Cycle Summary
|
|
265
267
|
|
|
266
|
-
After executing the action, output a
|
|
268
|
+
After executing the action, output a **rich cycle report** — the user sees this live in their terminal (pm-loop.sh runs foreground by default). Be informative, not terse.
|
|
267
269
|
|
|
268
270
|
```
|
|
269
|
-
|
|
271
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
272
|
+
PM ► {ACTION} — Phase {X}
|
|
273
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
274
|
+
|
|
275
|
+
{What happened this cycle — 2-3 sentences describing the action taken,
|
|
276
|
+
any decisions made, and what changed.}
|
|
277
|
+
|
|
278
|
+
Tickets: {done}/{total} done | {inprogress} running | {todo} queued
|
|
279
|
+
Wave: {current_wave}/{total_waves}
|
|
280
|
+
Next: {What the PM will do on the next cycle}
|
|
270
281
|
```
|
|
271
282
|
|
|
272
|
-
This is
|
|
283
|
+
**This is live user-facing output.** The PM loop runs in the foreground — the user is watching. Report progress actively: what you did, what's happening, what's next. Don't be silent.
|
|
273
284
|
|
|
274
285
|
</process>
|
|
275
286
|
|
|
@@ -281,4 +292,5 @@ This is the only user-visible output per cycle (pm-loop.sh captures it).
|
|
|
281
292
|
- **Log everything.** Every action gets a PM-LOG.md entry with timestamp.
|
|
282
293
|
- **Respect wave discipline.** Never dispatch wave N+1 until all wave N tickets are done.
|
|
283
294
|
- **Replan, don't panic.** Failed tickets get auto-replanned, not abandoned.
|
|
295
|
+
- **Report actively.** The user sees your output live. Tell them what's happening — don't be a silent background process.
|
|
284
296
|
</constraints>
|
package/commands/gsd/pm-start.md
CHANGED
|
@@ -38,9 +38,11 @@ Phase number: $ARGUMENTS (required — starting phase)
|
|
|
38
38
|
|
|
39
39
|
**Flags:**
|
|
40
40
|
- `--manual` — Don't launch the loop, manage manually with `/gsd:pm-cycle`
|
|
41
|
+
- `--background` — Run loop detached (default: foreground with live output)
|
|
41
42
|
- `--executor=X` — Override default executor (CLAUDE_CODE, CURSOR_AGENT, CODEX, etc.)
|
|
42
43
|
- `--skip-plan` — Skip initial planning, assume plans already exist
|
|
43
44
|
- `--max-iterations=N` — Safety cap on loop cycles (default: 0 = unlimited)
|
|
45
|
+
- `--no-notify` — Disable desktop notifications
|
|
44
46
|
</context>
|
|
45
47
|
|
|
46
48
|
<process>
|
|
@@ -123,15 +125,17 @@ If no plans exist OR user wants to re-plan:
|
|
|
123
125
|
|
|
124
126
|
**Key difference from /gsd:plan-phase:** After planning, continue to sync+dispatch instead of stopping.
|
|
125
127
|
|
|
126
|
-
## 5. Sync Plans to
|
|
128
|
+
## 5. Sync Plans to Agile Tickets
|
|
127
129
|
|
|
128
|
-
Follow the pm-sync workflow:
|
|
130
|
+
Follow the pm-sync workflow — create **Agile-style tickets**, not code dumps:
|
|
129
131
|
|
|
130
132
|
1. Read all PLAN.md files in the phase directory
|
|
131
|
-
2. For each plan:
|
|
133
|
+
2. For each plan, build an Agile ticket:
|
|
134
|
+
- Extract: objective, acceptance criteria (must_haves), wave, dependencies
|
|
132
135
|
- Build title: `"Phase {X} Plan {Y}: {objective}"`
|
|
133
|
-
- Build description:
|
|
134
|
-
-
|
|
136
|
+
- Build description: structured Agile ticket (Task, Why, Acceptance Criteria, Dependencies, Scope)
|
|
137
|
+
- **No file paths, function names, or code snippets** — tickets describe WHAT to deliver
|
|
138
|
+
- `create_task(project_id, title, agile_description)`
|
|
135
139
|
3. Write TICKET-MAP.md with all mappings
|
|
136
140
|
|
|
137
141
|
Present:
|
|
@@ -170,33 +174,48 @@ Present:
|
|
|
170
174
|
| ... | ... | ... | launched |
|
|
171
175
|
```
|
|
172
176
|
|
|
173
|
-
## 7. Launch PM Loop (default:
|
|
177
|
+
## 7. Launch PM Loop (default: foreground, live output)
|
|
174
178
|
|
|
175
|
-
### Autonomous Mode (default)
|
|
179
|
+
### Autonomous Mode (default) — FOREGROUND
|
|
176
180
|
|
|
177
|
-
Launch pm-loop.sh — the
|
|
181
|
+
Launch pm-loop.sh in the **foreground** — the user sees live progress, cycle-by-cycle output, progress bars, and desktop notifications for key events. The PM actively reports what it's doing.
|
|
178
182
|
|
|
179
183
|
```bash
|
|
180
|
-
|
|
181
|
-
echo $! > .planning/.pm-loop-pid
|
|
184
|
+
~/.claude/scripts/pm-loop.sh --phase={X} --interval={poll_interval} --max-iterations={max_iterations}
|
|
182
185
|
```
|
|
183
186
|
|
|
187
|
+
The loop runs in the current terminal. The user sees:
|
|
188
|
+
- Cycle headers with timestamps
|
|
189
|
+
- Full pm-cycle output (banners, ticket tables, decisions)
|
|
190
|
+
- Progress bars between cycles (done/running/todo counts)
|
|
191
|
+
- Countdown to next cycle
|
|
192
|
+
- Desktop notifications for: milestone complete, errors, phase advances
|
|
193
|
+
|
|
194
|
+
Stop with: `Ctrl+C` | `touch .planning/.pm-stop` | `/gsd:pm-stop`
|
|
195
|
+
|
|
196
|
+
**Note:** This is a blocking call — it takes over the terminal. The PM actively reports to the user rather than hiding in a log file.
|
|
197
|
+
|
|
198
|
+
### Background Mode (--background)
|
|
199
|
+
|
|
200
|
+
If the user explicitly passes `--background`, launch detached:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
~/.claude/scripts/pm-loop.sh --phase={X} --interval={poll_interval} --max-iterations={max_iterations} --background
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
This re-execs with nohup and exits immediately. Output goes to `.planning/pm-loop.log`.
|
|
207
|
+
|
|
208
|
+
Desktop notifications still work in background mode (macOS/Linux).
|
|
209
|
+
|
|
184
210
|
Present:
|
|
185
211
|
```
|
|
186
212
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
187
|
-
PM ►
|
|
188
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
213
|
+
PM ► BACKGROUND MODE
|
|
189
214
|
|
|
190
215
|
PM loop running (PID: {pid})
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
The PM will autonomously:
|
|
196
|
-
plan → sync → dispatch → monitor → replan → advance phases
|
|
197
|
-
|
|
198
|
-
Stop with: /gsd:pm-stop or touch .planning/.pm-stop
|
|
199
|
-
Status: /gsd:pm-status
|
|
216
|
+
Log: tail -f .planning/pm-loop.log
|
|
217
|
+
Stop: /gsd:pm-stop or touch .planning/.pm-stop
|
|
218
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
200
219
|
```
|
|
201
220
|
|
|
202
221
|
### Manual Mode (--manual)
|
|
@@ -51,14 +51,15 @@ MILESTONE_COMPLETE ──stop signal──→ EXIT
|
|
|
51
51
|
|
|
52
52
|
### NEEDS_SYNC
|
|
53
53
|
|
|
54
|
-
**Goal:** Create Vibe Kanban tickets for each plan.
|
|
54
|
+
**Goal:** Create Agile-style Vibe Kanban tickets for each plan.
|
|
55
55
|
|
|
56
56
|
Follow `pm-sync.md` workflow:
|
|
57
57
|
1. Read all PLAN.md files in phase directory
|
|
58
|
-
2. For each plan:
|
|
59
|
-
- Extract objective,
|
|
58
|
+
2. For each plan, build an **Agile ticket** (atomic, isolated, code-agnostic):
|
|
59
|
+
- Extract objective, acceptance criteria (must_haves), wave, dependencies
|
|
60
60
|
- Create VK ticket: `mcp__vibe_kanban__create_task(project_id, title, description)`
|
|
61
|
-
- Description =
|
|
61
|
+
- Description = structured Agile ticket (Task, Why, Acceptance Criteria, Dependencies, Scope)
|
|
62
|
+
- **Do NOT dump PLAN.md content** — tickets describe WHAT to deliver, not HOW to code it
|
|
62
63
|
3. Write TICKET-MAP.md with mapping table
|
|
63
64
|
4. Update STATE.md: phase status → "synced"
|
|
64
65
|
5. Log to PM-LOG.md
|
|
@@ -87,7 +87,8 @@ Action: Cancel all undone tickets, re-plan from phase goal.
|
|
|
87
87
|
|
|
88
88
|
6. Sync to Vibe Kanban:
|
|
89
89
|
- `update_task(failed_ticket_id, status="cancelled")`
|
|
90
|
-
-
|
|
90
|
+
- Build Agile ticket from fix plan (Task, Why, Acceptance Criteria, Dependencies, Scope — no code specifics)
|
|
91
|
+
- `create_task(project_id, fix_title, agile_ticket_description)` → new ticket
|
|
91
92
|
- Record in TICKET-MAP.md
|
|
92
93
|
|
|
93
94
|
7. Dispatch fix ticket if autonomous mode
|
|
@@ -58,20 +58,63 @@ Build three lists:
|
|
|
58
58
|
|
|
59
59
|
## 3. Create Tickets for New Plans
|
|
60
60
|
|
|
61
|
+
**Ticket philosophy:** Tickets are Agile-style task assignments — atomic, isolated, and code-agnostic. They describe **what** to deliver and **why**, not **how** to implement it. The worker agent reads the codebase to figure out implementation. This makes tickets robust to codebase changes and readable by any stakeholder.
|
|
62
|
+
|
|
61
63
|
For each new plan:
|
|
62
64
|
|
|
63
|
-
1. Read the
|
|
65
|
+
1. Read the PLAN.md content
|
|
64
66
|
2. Extract from frontmatter: `wave`, `depends_on`, `phase`, `plan`
|
|
65
|
-
3. Extract
|
|
66
|
-
4.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
3. Extract from `<objective>` section: what this plan accomplishes and why
|
|
68
|
+
4. Extract from `must_haves` frontmatter: truths (observable behaviors), artifacts (deliverables)
|
|
69
|
+
5. Build an **Agile ticket** (NOT a code dump):
|
|
70
|
+
|
|
71
|
+
- **title:** `"Phase {phase} Plan {plan}: {objective_summary}"`
|
|
72
|
+
- **description:** Structured Agile ticket (see format below)
|
|
73
|
+
|
|
74
|
+
6. Call `mcp__vibe_kanban__create_task(project_id, title, description)`
|
|
75
|
+
7. Record in TICKET-MAP.md:
|
|
71
76
|
```
|
|
72
77
|
| {plan} | {task_summary} | {ticket_uuid} | todo | — | {wave} | — | — | new |
|
|
73
78
|
```
|
|
74
79
|
|
|
80
|
+
### Agile Ticket Format
|
|
81
|
+
|
|
82
|
+
```markdown
|
|
83
|
+
## Task
|
|
84
|
+
|
|
85
|
+
{One-paragraph summary of what to deliver. Written as a user story or task statement.
|
|
86
|
+
Focus on the deliverable, not implementation details. No file paths, no function names, no code snippets.}
|
|
87
|
+
|
|
88
|
+
## Why
|
|
89
|
+
|
|
90
|
+
{Why this task matters for the project. What value it adds. What it unblocks.}
|
|
91
|
+
|
|
92
|
+
## Acceptance Criteria
|
|
93
|
+
|
|
94
|
+
- [ ] {Observable behavior 1 — from must_haves.truths}
|
|
95
|
+
- [ ] {Observable behavior 2}
|
|
96
|
+
- [ ] {Deliverable exists — from must_haves.artifacts, described generically}
|
|
97
|
+
|
|
98
|
+
## Dependencies
|
|
99
|
+
|
|
100
|
+
- {List ticket IDs this depends on, from depends_on field, or "None"}
|
|
101
|
+
|
|
102
|
+
## Scope
|
|
103
|
+
|
|
104
|
+
- Wave: {N}
|
|
105
|
+
- Phase: {phase_name}
|
|
106
|
+
- Plan ref: {plan_id} (see .planning/ for implementation details)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**What NOT to include in tickets:**
|
|
110
|
+
- File paths or directory structures
|
|
111
|
+
- Function/class/method names
|
|
112
|
+
- Code snippets or pseudocode
|
|
113
|
+
- Specific library API calls
|
|
114
|
+
- Internal architecture decisions
|
|
115
|
+
|
|
116
|
+
**Why:** Workers are coding agents with full codebase access. They figure out implementation from context. Tickets that prescribe code become stale, create merge conflicts, and prevent the agent from making better decisions based on actual codebase state.
|
|
117
|
+
|
|
75
118
|
</step>
|
|
76
119
|
|
|
77
120
|
<step name="sync_modified">
|
|
@@ -82,11 +125,13 @@ For each modified plan:
|
|
|
82
125
|
|
|
83
126
|
1. Find ticket_id from TICKET-MAP.md
|
|
84
127
|
2. Check current ticket status:
|
|
85
|
-
- If `todo`: safe to update → `update_task(task_id, description=
|
|
128
|
+
- If `todo`: safe to update → rebuild Agile ticket from updated PLAN.md, then `update_task(task_id, description=new_agile_ticket)`
|
|
86
129
|
- If `inprogress`: **WARNING** — worker is running. Log warning, do NOT update.
|
|
87
130
|
- If `done`/`cancelled`: skip (no point updating)
|
|
88
131
|
3. Update TICKET-MAP.md with notes
|
|
89
132
|
|
|
133
|
+
**Important:** Always regenerate the Agile ticket format from the updated PLAN.md. Never dump raw PLAN.md content into the ticket description.
|
|
134
|
+
|
|
90
135
|
</step>
|
|
91
136
|
|
|
92
137
|
<step name="sync_removed">
|
package/package.json
CHANGED
package/scripts/pm-loop.sh
CHANGED
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
# The PM brain handles the ENTIRE lifecycle autonomously:
|
|
10
10
|
# plan → sync tickets → dispatch workers → monitor → replan → advance phases → complete milestone
|
|
11
11
|
#
|
|
12
|
+
# RUNS IN FOREGROUND by default — you see live progress.
|
|
13
|
+
# Use --background to detach (output goes to .planning/pm-loop.log).
|
|
14
|
+
#
|
|
12
15
|
# Usage:
|
|
13
16
|
# ./scripts/pm-loop.sh [--phase=N] [--interval=60] [--max-iterations=50]
|
|
14
17
|
# PM_PHASE=1 PM_POLL_INTERVAL=30 ./scripts/pm-loop.sh
|
|
@@ -16,9 +19,7 @@
|
|
|
16
19
|
# Stop:
|
|
17
20
|
# touch .planning/.pm-stop
|
|
18
21
|
# OR run /gsd:pm-stop
|
|
19
|
-
#
|
|
20
|
-
# Background:
|
|
21
|
-
# nohup ./scripts/pm-loop.sh --phase=1 > .planning/pm-loop.log 2>&1 &
|
|
22
|
+
# OR Ctrl+C (foreground mode)
|
|
22
23
|
|
|
23
24
|
set -euo pipefail
|
|
24
25
|
|
|
@@ -27,6 +28,8 @@ set -euo pipefail
|
|
|
27
28
|
PHASE="${PM_PHASE:-}"
|
|
28
29
|
INTERVAL="${PM_POLL_INTERVAL:-60}"
|
|
29
30
|
MAX_ITERS="${PM_MAX_ITERATIONS:-0}" # 0 = unlimited
|
|
31
|
+
BACKGROUND=false
|
|
32
|
+
NOTIFY=true
|
|
30
33
|
|
|
31
34
|
for arg in "$@"; do
|
|
32
35
|
case $arg in
|
|
@@ -39,6 +42,12 @@ for arg in "$@"; do
|
|
|
39
42
|
--max-iterations=*)
|
|
40
43
|
MAX_ITERS="${arg#*=}"
|
|
41
44
|
;;
|
|
45
|
+
--background)
|
|
46
|
+
BACKGROUND=true
|
|
47
|
+
;;
|
|
48
|
+
--no-notify)
|
|
49
|
+
NOTIFY=false
|
|
50
|
+
;;
|
|
42
51
|
--help|-h)
|
|
43
52
|
echo "Usage: pm-loop.sh [--phase=N] [--interval=60] [--max-iterations=0]"
|
|
44
53
|
echo ""
|
|
@@ -46,17 +55,21 @@ for arg in "$@"; do
|
|
|
46
55
|
echo "reads state and decides what to do: plan, sync, dispatch, monitor,"
|
|
47
56
|
echo "replan, advance phases, or complete milestone."
|
|
48
57
|
echo ""
|
|
58
|
+
echo "Runs in FOREGROUND by default — you see live progress."
|
|
59
|
+
echo ""
|
|
49
60
|
echo "Options:"
|
|
50
61
|
echo " --phase=N Starting phase number (auto-detected if omitted)"
|
|
51
62
|
echo " --interval=N Seconds between cycles (default: 60)"
|
|
52
63
|
echo " --max-iterations=N Safety cap on cycles (default: 0 = unlimited)"
|
|
64
|
+
echo " --background Detach and run in background (logs to pm-loop.log)"
|
|
65
|
+
echo " --no-notify Disable desktop notifications"
|
|
53
66
|
echo ""
|
|
54
67
|
echo "Environment variables:"
|
|
55
68
|
echo " PM_PHASE Phase number"
|
|
56
69
|
echo " PM_POLL_INTERVAL Seconds between cycles (default: 60)"
|
|
57
70
|
echo " PM_MAX_ITERATIONS Max cycles (default: 0 = unlimited)"
|
|
58
71
|
echo ""
|
|
59
|
-
echo "Stop: touch .planning/.pm-stop OR /gsd:pm-stop"
|
|
72
|
+
echo "Stop: touch .planning/.pm-stop OR /gsd:pm-stop OR Ctrl+C"
|
|
60
73
|
exit 0
|
|
61
74
|
;;
|
|
62
75
|
*)
|
|
@@ -81,12 +94,112 @@ if ! command -v claude &> /dev/null; then
|
|
|
81
94
|
exit 1
|
|
82
95
|
fi
|
|
83
96
|
|
|
97
|
+
# ─── Background mode: re-exec detached ───────────────────────────
|
|
98
|
+
|
|
99
|
+
if [ "$BACKGROUND" = true ]; then
|
|
100
|
+
# Strip --background from args, re-exec with nohup
|
|
101
|
+
ARGS=()
|
|
102
|
+
for arg in "$@"; do
|
|
103
|
+
[ "$arg" != "--background" ] && ARGS+=("$arg")
|
|
104
|
+
done
|
|
105
|
+
nohup "$0" "${ARGS[@]}" > .planning/pm-loop.log 2>&1 &
|
|
106
|
+
BG_PID=$!
|
|
107
|
+
echo "$BG_PID" > .planning/.pm-loop-pid
|
|
108
|
+
echo "PM loop launched in background (PID: $BG_PID)"
|
|
109
|
+
echo "Log: tail -f .planning/pm-loop.log"
|
|
110
|
+
echo "Stop: touch .planning/.pm-stop"
|
|
111
|
+
exit 0
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
# ─── Desktop notification helper ─────────────────────────────────
|
|
115
|
+
|
|
116
|
+
notify() {
|
|
117
|
+
local title="$1"
|
|
118
|
+
local message="$2"
|
|
119
|
+
local urgency="${3:-normal}" # normal, critical
|
|
120
|
+
|
|
121
|
+
if [ "$NOTIFY" = false ]; then
|
|
122
|
+
return
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
# macOS
|
|
126
|
+
if command -v osascript &> /dev/null; then
|
|
127
|
+
osascript -e "display notification \"$message\" with title \"$title\"" 2>/dev/null || true
|
|
128
|
+
# Sound for critical events
|
|
129
|
+
if [ "$urgency" = "critical" ]; then
|
|
130
|
+
afplay /System/Library/Sounds/Glass.aiff 2>/dev/null &
|
|
131
|
+
fi
|
|
132
|
+
return
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
# Linux
|
|
136
|
+
if command -v notify-send &> /dev/null; then
|
|
137
|
+
local urgency_flag="normal"
|
|
138
|
+
[ "$urgency" = "critical" ] && urgency_flag="critical"
|
|
139
|
+
notify-send -u "$urgency_flag" "$title" "$message" 2>/dev/null || true
|
|
140
|
+
return
|
|
141
|
+
fi
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
# ─── Progress summary helper ─────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
print_progress() {
|
|
147
|
+
# Read TICKET-MAP.md for quick progress snapshot
|
|
148
|
+
local ticket_map=""
|
|
149
|
+
ticket_map=$(find .planning/phases/ -name "TICKET-MAP.md" -print -quit 2>/dev/null || true)
|
|
150
|
+
|
|
151
|
+
if [ -n "$ticket_map" ] && [ -f "$ticket_map" ]; then
|
|
152
|
+
local done_count=$(grep -c "| done " "$ticket_map" 2>/dev/null || echo "0")
|
|
153
|
+
local inprogress_count=$(grep -c "| inprogress " "$ticket_map" 2>/dev/null || echo "0")
|
|
154
|
+
local todo_count=$(grep -c "| todo " "$ticket_map" 2>/dev/null || echo "0")
|
|
155
|
+
local total=$((done_count + inprogress_count + todo_count))
|
|
156
|
+
|
|
157
|
+
if [ "$total" -gt 0 ]; then
|
|
158
|
+
# Build progress bar
|
|
159
|
+
local pct=0
|
|
160
|
+
if [ "$total" -gt 0 ]; then
|
|
161
|
+
pct=$((done_count * 100 / total))
|
|
162
|
+
fi
|
|
163
|
+
local bar_len=20
|
|
164
|
+
local filled=$((pct * bar_len / 100))
|
|
165
|
+
local empty=$((bar_len - filled))
|
|
166
|
+
local bar=""
|
|
167
|
+
for ((i=0; i<filled; i++)); do bar+="█"; done
|
|
168
|
+
for ((i=0; i<empty; i++)); do bar+="░"; done
|
|
169
|
+
|
|
170
|
+
echo " ┌─────────────────────────────────────────┐"
|
|
171
|
+
echo " │ Progress: [$bar] ${pct}%"
|
|
172
|
+
echo " │ Done: $done_count Running: $inprogress_count Todo: $todo_count Total: $total"
|
|
173
|
+
echo " └─────────────────────────────────────────┘"
|
|
174
|
+
fi
|
|
175
|
+
fi
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
# ─── Graceful Ctrl+C handling ────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
cleanup() {
|
|
181
|
+
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
|
|
182
|
+
echo ""
|
|
183
|
+
echo "[$TIMESTAMP] PM loop interrupted (Ctrl+C). Cleaning up..."
|
|
184
|
+
echo "" >> "$LOG_FILE"
|
|
185
|
+
echo "## [$TIMESTAMP] INTERRUPTED" >> "$LOG_FILE"
|
|
186
|
+
echo "" >> "$LOG_FILE"
|
|
187
|
+
echo "- PM loop interrupted by user" >> "$LOG_FILE"
|
|
188
|
+
echo "- Completed $CYCLE cycles" >> "$LOG_FILE"
|
|
189
|
+
rm -f "$PID_FILE"
|
|
190
|
+
notify "GSD PM" "PM loop stopped by user after $CYCLE cycles"
|
|
191
|
+
exit 0
|
|
192
|
+
}
|
|
193
|
+
trap cleanup SIGINT SIGTERM
|
|
194
|
+
|
|
84
195
|
# ─── Configuration ────────────────────────────────────────────────
|
|
85
196
|
|
|
86
197
|
STOP_FILE=".planning/.pm-stop"
|
|
87
198
|
PID_FILE=".planning/.pm-loop-pid"
|
|
88
199
|
LOG_FILE=".planning/PM-LOG.md"
|
|
89
200
|
CYCLE=0
|
|
201
|
+
ERROR_STREAK=0
|
|
202
|
+
MAX_ERROR_STREAK=5
|
|
90
203
|
|
|
91
204
|
# Clean any stale stop signal
|
|
92
205
|
rm -f "$STOP_FILE"
|
|
@@ -121,28 +234,50 @@ echo "- Phase: ${PHASE:-auto-detect}" >> "$LOG_FILE"
|
|
|
121
234
|
echo "- Interval: ${INTERVAL}s" >> "$LOG_FILE"
|
|
122
235
|
echo "- Max iterations: ${MAX_ITERS:-unlimited}" >> "$LOG_FILE"
|
|
123
236
|
|
|
124
|
-
# ───
|
|
237
|
+
# ─── Startup banner ──────────────────────────────────────────────
|
|
125
238
|
|
|
126
|
-
echo "
|
|
127
|
-
echo "
|
|
128
|
-
echo "
|
|
239
|
+
echo ""
|
|
240
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
241
|
+
echo " PM ► AUTONOMOUS MODE ACTIVE"
|
|
242
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
243
|
+
echo ""
|
|
244
|
+
echo " Phase: ${PHASE:-auto-detect}"
|
|
245
|
+
echo " Interval: ${INTERVAL}s"
|
|
246
|
+
echo " PID: $$"
|
|
129
247
|
if [ "$MAX_ITERS" -gt 0 ] 2>/dev/null; then
|
|
130
|
-
echo "
|
|
248
|
+
echo " Max iterations: $MAX_ITERS"
|
|
131
249
|
fi
|
|
132
|
-
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
133
250
|
echo ""
|
|
251
|
+
echo " Stop: Ctrl+C | touch .planning/.pm-stop | /gsd:pm-stop"
|
|
252
|
+
echo ""
|
|
253
|
+
echo " The PM will autonomously:"
|
|
254
|
+
echo " plan → sync → dispatch → monitor → replan → advance phases"
|
|
255
|
+
echo ""
|
|
256
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
257
|
+
echo ""
|
|
258
|
+
|
|
259
|
+
notify "GSD PM" "Autonomous PM started for Phase ${PHASE:-auto}" "normal"
|
|
260
|
+
|
|
261
|
+
# ─── Main loop ────────────────────────────────────────────────────
|
|
134
262
|
|
|
135
263
|
while true; do
|
|
136
264
|
# ── Check stop signal ──
|
|
137
265
|
if [ -f "$STOP_FILE" ]; then
|
|
138
266
|
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
|
|
139
|
-
echo "
|
|
267
|
+
echo ""
|
|
268
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
269
|
+
echo " PM ► STOPPED"
|
|
270
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
271
|
+
echo ""
|
|
272
|
+
echo " [$TIMESTAMP] Stop signal received after $CYCLE cycles."
|
|
273
|
+
echo ""
|
|
140
274
|
echo "" >> "$LOG_FILE"
|
|
141
275
|
echo "## [$TIMESTAMP] STOP" >> "$LOG_FILE"
|
|
142
276
|
echo "" >> "$LOG_FILE"
|
|
143
277
|
echo "- PM loop stopped by stop file" >> "$LOG_FILE"
|
|
144
278
|
echo "- Completed $CYCLE cycles" >> "$LOG_FILE"
|
|
145
279
|
rm -f "$STOP_FILE" "$PID_FILE"
|
|
280
|
+
notify "GSD PM" "PM loop stopped after $CYCLE cycles"
|
|
146
281
|
exit 0
|
|
147
282
|
fi
|
|
148
283
|
|
|
@@ -150,62 +285,125 @@ while true; do
|
|
|
150
285
|
CYCLE=$((CYCLE + 1))
|
|
151
286
|
if [ "$MAX_ITERS" -gt 0 ] 2>/dev/null && [ "$CYCLE" -gt "$MAX_ITERS" ]; then
|
|
152
287
|
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
|
|
153
|
-
echo "
|
|
288
|
+
echo ""
|
|
289
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
290
|
+
echo " PM ► MAX ITERATIONS REACHED"
|
|
291
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
292
|
+
echo ""
|
|
293
|
+
echo " Completed $((CYCLE - 1)) cycles. Safety cap: $MAX_ITERS."
|
|
294
|
+
echo ""
|
|
154
295
|
echo "" >> "$LOG_FILE"
|
|
155
296
|
echo "## [$TIMESTAMP] MAX_ITERATIONS" >> "$LOG_FILE"
|
|
156
297
|
echo "" >> "$LOG_FILE"
|
|
157
298
|
echo "- Reached max iterations: $MAX_ITERS" >> "$LOG_FILE"
|
|
158
299
|
rm -f "$PID_FILE"
|
|
300
|
+
notify "GSD PM" "PM loop reached max iterations ($MAX_ITERS)" "normal"
|
|
159
301
|
exit 0
|
|
160
302
|
fi
|
|
161
303
|
|
|
162
|
-
# ──
|
|
304
|
+
# ── Cycle header ──
|
|
163
305
|
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
|
|
164
|
-
echo "
|
|
306
|
+
echo "┌─────────────────────────────────────────────────────"
|
|
307
|
+
echo "│ Cycle #$CYCLE — $TIMESTAMP"
|
|
308
|
+
echo "└─────────────────────────────────────────────────────"
|
|
309
|
+
echo ""
|
|
165
310
|
|
|
166
311
|
# Build the prompt
|
|
167
|
-
|
|
312
|
+
# Only pass phase hint on cycle 1 — after that, pm-cycle auto-detects
|
|
313
|
+
# from STATE.md so it can advance through phases autonomously.
|
|
314
|
+
if [ "$CYCLE" -eq 1 ] && [ -n "$PHASE" ]; then
|
|
168
315
|
PROMPT="/gsd:pm-cycle $PHASE"
|
|
169
316
|
else
|
|
170
317
|
PROMPT="/gsd:pm-cycle"
|
|
171
318
|
fi
|
|
172
319
|
|
|
173
320
|
# Fresh Claude invocation — full brain, fresh 200k context
|
|
321
|
+
# Output streams directly to terminal (foreground mode)
|
|
322
|
+
CYCLE_START=$(date +%s)
|
|
174
323
|
claude -p --dangerously-skip-permissions "$PROMPT" 2>&1
|
|
175
324
|
EXIT_CODE=$?
|
|
325
|
+
CYCLE_END=$(date +%s)
|
|
326
|
+
CYCLE_DURATION=$((CYCLE_END - CYCLE_START))
|
|
327
|
+
|
|
328
|
+
echo ""
|
|
176
329
|
|
|
177
330
|
# ── Handle exit codes ──
|
|
178
331
|
case $EXIT_CODE in
|
|
179
332
|
0)
|
|
180
|
-
# Normal — more work to do
|
|
333
|
+
# Normal — more work to do
|
|
334
|
+
ERROR_STREAK=0
|
|
335
|
+
print_progress
|
|
336
|
+
echo ""
|
|
337
|
+
echo " Cycle #$CYCLE completed in ${CYCLE_DURATION}s. Next in ${INTERVAL}s..."
|
|
338
|
+
echo ""
|
|
181
339
|
;;
|
|
182
340
|
42)
|
|
183
|
-
# Milestone complete — all done
|
|
341
|
+
# Milestone complete — all done!
|
|
184
342
|
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
|
|
185
343
|
echo ""
|
|
186
|
-
echo "
|
|
344
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
345
|
+
echo " PM ► MILESTONE COMPLETE"
|
|
346
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
347
|
+
echo ""
|
|
348
|
+
echo " All phases complete after $CYCLE cycles."
|
|
349
|
+
echo " Run /gsd:complete-milestone to archive."
|
|
350
|
+
echo ""
|
|
351
|
+
print_progress
|
|
352
|
+
echo ""
|
|
187
353
|
echo "" >> "$LOG_FILE"
|
|
188
354
|
echo "## [$TIMESTAMP] LOOP_EXIT" >> "$LOG_FILE"
|
|
189
355
|
echo "" >> "$LOG_FILE"
|
|
190
356
|
echo "- Milestone complete after $CYCLE cycles" >> "$LOG_FILE"
|
|
191
357
|
rm -f "$PID_FILE"
|
|
358
|
+
notify "GSD PM" "Milestone complete! All phases done after $CYCLE cycles." "critical"
|
|
192
359
|
exit 0
|
|
193
360
|
;;
|
|
194
361
|
*)
|
|
195
362
|
# Error — log and continue (don't crash the loop)
|
|
363
|
+
ERROR_STREAK=$((ERROR_STREAK + 1))
|
|
196
364
|
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
|
|
197
|
-
echo "
|
|
365
|
+
echo " ⚠ Cycle #$CYCLE failed (exit code $EXIT_CODE, streak: $ERROR_STREAK/$MAX_ERROR_STREAK)"
|
|
198
366
|
echo "" >> "$LOG_FILE"
|
|
199
367
|
echo "## [$TIMESTAMP] ERROR" >> "$LOG_FILE"
|
|
200
368
|
echo "" >> "$LOG_FILE"
|
|
201
369
|
echo "- Claude exited with code $EXIT_CODE on cycle #$CYCLE" >> "$LOG_FILE"
|
|
370
|
+
echo "- Error streak: $ERROR_STREAK/$MAX_ERROR_STREAK" >> "$LOG_FILE"
|
|
371
|
+
|
|
372
|
+
# Notify on errors
|
|
373
|
+
notify "GSD PM" "Cycle #$CYCLE failed (code $EXIT_CODE)" "critical"
|
|
374
|
+
|
|
375
|
+
# Stop if too many consecutive errors
|
|
376
|
+
if [ "$ERROR_STREAK" -ge "$MAX_ERROR_STREAK" ]; then
|
|
377
|
+
echo ""
|
|
378
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
379
|
+
echo " PM ► ERROR — TOO MANY FAILURES"
|
|
380
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
381
|
+
echo ""
|
|
382
|
+
echo " $ERROR_STREAK consecutive errors. Stopping to prevent waste."
|
|
383
|
+
echo " Check .planning/PM-LOG.md for details."
|
|
384
|
+
echo " Fix the issue, then restart with /gsd:pm-start"
|
|
385
|
+
echo ""
|
|
386
|
+
echo "" >> "$LOG_FILE"
|
|
387
|
+
echo "## [$TIMESTAMP] ERROR_STREAK_HALT" >> "$LOG_FILE"
|
|
388
|
+
echo "" >> "$LOG_FILE"
|
|
389
|
+
echo "- Halted after $ERROR_STREAK consecutive errors" >> "$LOG_FILE"
|
|
390
|
+
rm -f "$PID_FILE"
|
|
391
|
+
notify "GSD PM" "PM halted: $ERROR_STREAK consecutive errors" "critical"
|
|
392
|
+
exit 1
|
|
393
|
+
fi
|
|
202
394
|
;;
|
|
203
395
|
esac
|
|
204
396
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
397
|
+
# ── Countdown to next cycle ──
|
|
398
|
+
for ((remaining=INTERVAL; remaining>0; remaining--)); do
|
|
399
|
+
# Check stop signal during sleep too
|
|
400
|
+
if [ -f "$STOP_FILE" ]; then
|
|
401
|
+
break
|
|
402
|
+
fi
|
|
403
|
+
# Show countdown every 15 seconds
|
|
404
|
+
if [ $((remaining % 15)) -eq 0 ] && [ "$remaining" -ne "$INTERVAL" ]; then
|
|
405
|
+
echo " ... next cycle in ${remaining}s"
|
|
406
|
+
fi
|
|
407
|
+
sleep 1
|
|
408
|
+
done
|
|
211
409
|
done
|