@sienklogic/plan-build-run 2.49.0 → 2.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/package.json +1 -1
- package/plugins/copilot-pbr/plugin.json +1 -1
- package/plugins/copilot-pbr/skills/build/SKILL.md +10 -0
- package/plugins/copilot-pbr/skills/plan/SKILL.md +43 -53
- package/plugins/cursor-pbr/.cursor-plugin/plugin.json +1 -1
- package/plugins/cursor-pbr/skills/build/SKILL.md +10 -0
- package/plugins/cursor-pbr/skills/plan/SKILL.md +43 -53
- package/plugins/pbr/.claude-plugin/plugin.json +1 -1
- package/plugins/pbr/scripts/lib/build.js +236 -1
- package/plugins/pbr/scripts/pbr-tools.js +17 -3
- package/plugins/pbr/skills/build/SKILL.md +11 -4
- package/plugins/pbr/skills/milestone/SKILL.md +4 -183
- package/plugins/pbr/skills/plan/SKILL.md +47 -140
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ All notable changes to Plan-Build-Run will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.50.0](https://github.com/SienkLogic/plan-build-run/compare/plan-build-run-v2.49.0...plan-build-run-v2.50.0) (2026-03-01)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **52-04:** replace 4 milestone banners with tmpl refs; condense 5 plan SKILL.md sections to < 600 lines ([3e383c4](https://github.com/SienkLogic/plan-build-run/commit/3e383c45d4cfdb51298a2d2531f51752132368a0))
|
|
14
|
+
* **53-01:** add cleanup section and CRITICAL markers to build, consolidate plan pre-planner briefing ([7c14693](https://github.com/SienkLogic/plan-build-run/commit/7c14693e7790728e00d5e4d080e19f54648bc156))
|
|
15
|
+
* **53-02:** GREEN - implement ciPoll and rollback in lib/build.js ([d2ffa30](https://github.com/SienkLogic/plan-build-run/commit/d2ffa309eab8bf10981e88ded12c8e2b4029ad28))
|
|
16
|
+
* **53-02:** register ci-poll and rollback in pbr-tools.js dispatcher ([88c551e](https://github.com/SienkLogic/plan-build-run/commit/88c551e82d3acb1d807bd302c758d041cb0577c0))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
* **52-05:** remove blank lines to bring build SKILL.md to 868 lines (< 870 target) ([6cd9549](https://github.com/SienkLogic/plan-build-run/commit/6cd954972c33540462cb08f6a192604879b8e6df))
|
|
22
|
+
|
|
8
23
|
## [2.49.0](https://github.com/SienkLogic/plan-build-run/compare/plan-build-run-v2.48.0...plan-build-run-v2.49.0) (2026-03-01)
|
|
9
24
|
|
|
10
25
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pbr",
|
|
3
3
|
"displayName": "Plan-Build-Run",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.50.0",
|
|
5
5
|
"description": "Plan-Build-Run — Structured development workflow for GitHub Copilot CLI. Solves context rot through disciplined agent delegation, structured planning, atomic execution, and goal-backward verification.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "SienkLogic",
|
|
@@ -669,6 +669,8 @@ This ensures that `/pbr:review` after a `--gaps-only` build sees the updated ver
|
|
|
669
669
|
|
|
670
670
|
**8-pre-c. Codebase map incremental update (conditional):**
|
|
671
671
|
|
|
672
|
+
**CRITICAL (no hook): Run codebase map update if conditions are met. Do NOT skip this step.**
|
|
673
|
+
|
|
672
674
|
Only run if ALL of these are true:
|
|
673
675
|
- `.planning/codebase/` directory exists (project was previously scanned with `/pbr:scan`)
|
|
674
676
|
- Build was not aborted
|
|
@@ -779,6 +781,8 @@ Write `.planning/.auto-next` containing the next logical command (e.g., `/pbr:pl
|
|
|
779
781
|
|
|
780
782
|
**8e-ii. Check Pending Todos:**
|
|
781
783
|
|
|
784
|
+
**CRITICAL (no hook): Check pending todos after build. Do NOT skip this step.**
|
|
785
|
+
|
|
782
786
|
After completing the build, check if any pending todos are now satisfied:
|
|
783
787
|
|
|
784
788
|
1. Check if `.planning/todos/pending/` exists and contains files
|
|
@@ -895,3 +899,9 @@ If `git.branching` is `phase` but we're not on the phase branch:
|
|
|
895
899
|
| `.planning/STATE.md` | Updated progress | Steps 6f, 8b |
|
|
896
900
|
| `.planning/.auto-next` | Next command signal (if auto_continue enabled) | Step 8e |
|
|
897
901
|
| Project source files | Actual code | Step 6 (executors) |
|
|
902
|
+
|
|
903
|
+
---
|
|
904
|
+
|
|
905
|
+
## Cleanup
|
|
906
|
+
|
|
907
|
+
Delete `.planning/.active-skill` if it exists. This must happen on all paths (success, partial, and failure) before reporting results.
|
|
@@ -243,61 +243,51 @@ After the researcher completes, check the agent output for a completion marker:
|
|
|
243
243
|
|
|
244
244
|
---
|
|
245
245
|
|
|
246
|
-
### Step 4.5:
|
|
247
|
-
|
|
248
|
-
Before invoking the planner, scan `.planning/seeds/` for seeds whose trigger matches the current phase:
|
|
249
|
-
|
|
250
|
-
1. Glob for `.planning/seeds/*.md`
|
|
251
|
-
2. For each seed file, read its frontmatter and check the `trigger` field
|
|
252
|
-
3. A seed matches if ANY of these are true:
|
|
253
|
-
- `trigger` equals the phase slug (e.g., `trigger: authentication`) — **preferred**
|
|
254
|
-
- `trigger` is a substring of the phase directory name (e.g., `trigger: auth` matches `03-authentication`)
|
|
255
|
-
- `trigger` equals the current phase number as integer (e.g., `trigger: 3`) — backward compatible but NOT recommended for new seeds (breaks with decimal phases like 3.1)
|
|
256
|
-
- `trigger` equals `*` (always matches)
|
|
257
|
-
4. If matching seeds are found, present them to the user:
|
|
258
|
-
```
|
|
259
|
-
Found {N} seeds related to Phase {NN}:
|
|
260
|
-
- {seed_name}: {seed description}
|
|
261
|
-
- {seed_name}: {seed description}
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
Use the yes-no-pick pattern from `skills/shared/gate-prompts.md`:
|
|
265
|
-
question: "Include these {N} seeds in planning?"
|
|
266
|
-
header: "Seeds?"
|
|
267
|
-
options:
|
|
268
|
-
- label: "Yes, all" description: "Include all {N} matching seeds"
|
|
269
|
-
- label: "Let me pick" description: "Choose which seeds to include"
|
|
270
|
-
- label: "No" description: "Proceed without seeds"
|
|
271
|
-
5. If "Yes, all": include all matching seed content in the planner's context
|
|
272
|
-
6. If "Let me pick": present individual seeds for selection
|
|
273
|
-
7. If "No" or "Other": proceed without seeds
|
|
274
|
-
8. If no matching seeds found: proceed silently
|
|
246
|
+
### Step 4.5: Pre-Planner Briefing (delegated)
|
|
275
247
|
|
|
276
|
-
|
|
248
|
+
**CRITICAL (no hook): Run pre-planner briefing before spawning the planner. Do NOT skip this step.**
|
|
249
|
+
|
|
250
|
+
Consolidate seed scanning and deferred idea surfacing into a single lightweight Task():
|
|
251
|
+
|
|
252
|
+
```
|
|
253
|
+
Task({
|
|
254
|
+
subagent_type: "pbr:general",
|
|
255
|
+
model: "haiku",
|
|
256
|
+
prompt: "Pre-planner briefing for Phase {NN} ({phase-slug}).
|
|
257
|
+
|
|
258
|
+
1. SEED SCANNING:
|
|
259
|
+
Run: `node ${PLUGIN_ROOT}/scripts/pbr-tools.js seeds match {phase-slug} {phase-number}`
|
|
260
|
+
If `matched` is non-empty, output a ## Seeds section listing each seed name, description, and content.
|
|
261
|
+
If empty, output: ## Seeds\nNo matching seeds found.
|
|
262
|
+
|
|
263
|
+
2. DEFERRED IDEAS:
|
|
264
|
+
Read `.planning/CONTEXT.md`. If it has a section containing 'deferred' or 'ideas' (case-insensitive),
|
|
265
|
+
extract items that mention Phase {NN} or keywords matching the phase slug.
|
|
266
|
+
If relevant items found, output a ## Deferred Ideas section listing them.
|
|
267
|
+
If none found, output: ## Deferred Ideas\nNo relevant deferred items.
|
|
268
|
+
|
|
269
|
+
Output format: Return both sections as markdown. End with ## BRIEFING COMPLETE."
|
|
270
|
+
})
|
|
271
|
+
```
|
|
277
272
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
- label: "No" description: "Proceed without deferred ideas"
|
|
297
|
-
5. If "Yes": append the relevant deferred items to the context bundle for the planner prompt (add them to the `<project_context>` block under a `Deferred ideas to consider:` heading)
|
|
298
|
-
6. If "No" or no relevant items found: proceed without changes
|
|
299
|
-
|
|
300
|
-
This is a lightweight relevance filter — do NOT invoke an agent for this. Just match keywords from the deferred items against the phase goal and requirement text.
|
|
273
|
+
After the Task() completes:
|
|
274
|
+
- If `## Seeds` section contains matches: present them to the user via the yes-no-pick pattern from `skills/shared/gate-prompts.md`:
|
|
275
|
+
question: "Include these {N} seeds in planning?"
|
|
276
|
+
header: "Seeds?"
|
|
277
|
+
options:
|
|
278
|
+
- label: "Yes, all" description: "Include all {N} matching seeds"
|
|
279
|
+
- label: "Let me pick" description: "Choose which seeds to include"
|
|
280
|
+
- label: "No" description: "Proceed without seeds"
|
|
281
|
+
- If "Yes, all": include seed content in planner context
|
|
282
|
+
- If "Let me pick": present individual seeds for selection
|
|
283
|
+
- If "No": proceed without seeds
|
|
284
|
+
|
|
285
|
+
- If `## Deferred Ideas` section has items: present via the yes-no pattern from `skills/shared/gate-prompts.md`:
|
|
286
|
+
question: "Include these deferred ideas in planning context?"
|
|
287
|
+
- If "Yes": append to planner context under `Deferred ideas to consider:`
|
|
288
|
+
- If "No": proceed without changes
|
|
289
|
+
|
|
290
|
+
- If both sections are empty: proceed silently to Step 5 (no AskUserQuestion needed)
|
|
301
291
|
|
|
302
292
|
---
|
|
303
293
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pbr",
|
|
3
3
|
"displayName": "Plan-Build-Run",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.50.0",
|
|
5
5
|
"description": "Plan-Build-Run — Structured development workflow for Cursor. Solves context rot through disciplined subagent delegation, structured planning, atomic execution, and goal-backward verification.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "SienkLogic",
|
|
@@ -670,6 +670,8 @@ This ensures that `/pbr:review` after a `--gaps-only` build sees the updated ver
|
|
|
670
670
|
|
|
671
671
|
**8-pre-c. Codebase map incremental update (conditional):**
|
|
672
672
|
|
|
673
|
+
**CRITICAL (no hook): Run codebase map update if conditions are met. Do NOT skip this step.**
|
|
674
|
+
|
|
673
675
|
Only run if ALL of these are true:
|
|
674
676
|
- `.planning/codebase/` directory exists (project was previously scanned with `/pbr:scan`)
|
|
675
677
|
- Build was not aborted
|
|
@@ -780,6 +782,8 @@ Write `.planning/.auto-next` containing the next logical command (e.g., `/pbr:pl
|
|
|
780
782
|
|
|
781
783
|
**8e-ii. Check Pending Todos:**
|
|
782
784
|
|
|
785
|
+
**CRITICAL (no hook): Check pending todos after build. Do NOT skip this step.**
|
|
786
|
+
|
|
783
787
|
After completing the build, check if any pending todos are now satisfied:
|
|
784
788
|
|
|
785
789
|
1. Check if `.planning/todos/pending/` exists and contains files
|
|
@@ -894,3 +898,9 @@ If `git.branching` is `phase` but we're not on the phase branch:
|
|
|
894
898
|
| `.planning/STATE.md` | Updated progress | Steps 6f, 8b |
|
|
895
899
|
| `.planning/.auto-next` | Next command signal (if auto_continue enabled) | Step 8e |
|
|
896
900
|
| Project source files | Actual code | Step 6 (executors) |
|
|
901
|
+
|
|
902
|
+
---
|
|
903
|
+
|
|
904
|
+
## Cleanup
|
|
905
|
+
|
|
906
|
+
Delete `.planning/.active-skill` if it exists. This must happen on all paths (success, partial, and failure) before reporting results.
|
|
@@ -244,61 +244,51 @@ After the researcher completes, check the agent output for a completion marker:
|
|
|
244
244
|
|
|
245
245
|
---
|
|
246
246
|
|
|
247
|
-
### Step 4.5:
|
|
248
|
-
|
|
249
|
-
Before invoking the planner, scan `.planning/seeds/` for seeds whose trigger matches the current phase:
|
|
250
|
-
|
|
251
|
-
1. Glob for `.planning/seeds/*.md`
|
|
252
|
-
2. For each seed file, read its frontmatter and check the `trigger` field
|
|
253
|
-
3. A seed matches if ANY of these are true:
|
|
254
|
-
- `trigger` equals the phase slug (e.g., `trigger: authentication`) — **preferred**
|
|
255
|
-
- `trigger` is a substring of the phase directory name (e.g., `trigger: auth` matches `03-authentication`)
|
|
256
|
-
- `trigger` equals the current phase number as integer (e.g., `trigger: 3`) — backward compatible but NOT recommended for new seeds (breaks with decimal phases like 3.1)
|
|
257
|
-
- `trigger` equals `*` (always matches)
|
|
258
|
-
4. If matching seeds are found, present them to the user:
|
|
259
|
-
```
|
|
260
|
-
Found {N} seeds related to Phase {NN}:
|
|
261
|
-
- {seed_name}: {seed description}
|
|
262
|
-
- {seed_name}: {seed description}
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
Use the yes-no-pick pattern from `skills/shared/gate-prompts.md`:
|
|
266
|
-
question: "Include these {N} seeds in planning?"
|
|
267
|
-
header: "Seeds?"
|
|
268
|
-
options:
|
|
269
|
-
- label: "Yes, all" description: "Include all {N} matching seeds"
|
|
270
|
-
- label: "Let me pick" description: "Choose which seeds to include"
|
|
271
|
-
- label: "No" description: "Proceed without seeds"
|
|
272
|
-
5. If "Yes, all": include all matching seed content in the planner's context
|
|
273
|
-
6. If "Let me pick": present individual seeds for selection
|
|
274
|
-
7. If "No" or "Other": proceed without seeds
|
|
275
|
-
8. If no matching seeds found: proceed silently
|
|
247
|
+
### Step 4.5: Pre-Planner Briefing (delegated)
|
|
276
248
|
|
|
277
|
-
|
|
249
|
+
**CRITICAL (no hook): Run pre-planner briefing before spawning the planner. Do NOT skip this step.**
|
|
250
|
+
|
|
251
|
+
Consolidate seed scanning and deferred idea surfacing into a single lightweight Task():
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
Task({
|
|
255
|
+
subagent_type: "pbr:general",
|
|
256
|
+
model: "haiku",
|
|
257
|
+
prompt: "Pre-planner briefing for Phase {NN} ({phase-slug}).
|
|
258
|
+
|
|
259
|
+
1. SEED SCANNING:
|
|
260
|
+
Run: `node ${PLUGIN_ROOT}/scripts/pbr-tools.js seeds match {phase-slug} {phase-number}`
|
|
261
|
+
If `matched` is non-empty, output a ## Seeds section listing each seed name, description, and content.
|
|
262
|
+
If empty, output: ## Seeds\nNo matching seeds found.
|
|
263
|
+
|
|
264
|
+
2. DEFERRED IDEAS:
|
|
265
|
+
Read `.planning/CONTEXT.md`. If it has a section containing 'deferred' or 'ideas' (case-insensitive),
|
|
266
|
+
extract items that mention Phase {NN} or keywords matching the phase slug.
|
|
267
|
+
If relevant items found, output a ## Deferred Ideas section listing them.
|
|
268
|
+
If none found, output: ## Deferred Ideas\nNo relevant deferred items.
|
|
269
|
+
|
|
270
|
+
Output format: Return both sections as markdown. End with ## BRIEFING COMPLETE."
|
|
271
|
+
})
|
|
272
|
+
```
|
|
278
273
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
- label: "No" description: "Proceed without deferred ideas"
|
|
298
|
-
5. If "Yes": append the relevant deferred items to the context bundle for the planner prompt (add them to the `<project_context>` block under a `Deferred ideas to consider:` heading)
|
|
299
|
-
6. If "No" or no relevant items found: proceed without changes
|
|
300
|
-
|
|
301
|
-
This is a lightweight relevance filter — do NOT invoke an agent for this. Just match keywords from the deferred items against the phase goal and requirement text.
|
|
274
|
+
After the Task() completes:
|
|
275
|
+
- If `## Seeds` section contains matches: present them to the user via the yes-no-pick pattern from `skills/shared/gate-prompts.md`:
|
|
276
|
+
question: "Include these {N} seeds in planning?"
|
|
277
|
+
header: "Seeds?"
|
|
278
|
+
options:
|
|
279
|
+
- label: "Yes, all" description: "Include all {N} matching seeds"
|
|
280
|
+
- label: "Let me pick" description: "Choose which seeds to include"
|
|
281
|
+
- label: "No" description: "Proceed without seeds"
|
|
282
|
+
- If "Yes, all": include seed content in planner context
|
|
283
|
+
- If "Let me pick": present individual seeds for selection
|
|
284
|
+
- If "No": proceed without seeds
|
|
285
|
+
|
|
286
|
+
- If `## Deferred Ideas` section has items: present via the yes-no pattern from `skills/shared/gate-prompts.md`:
|
|
287
|
+
question: "Include these deferred ideas in planning context?"
|
|
288
|
+
- If "Yes": append to planner context under `Deferred ideas to consider:`
|
|
289
|
+
- If "No": proceed without changes
|
|
290
|
+
|
|
291
|
+
- If both sections are empty: proceed silently to Step 5 (no AskUserQuestion needed)
|
|
302
292
|
|
|
303
293
|
---
|
|
304
294
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pbr",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.50.0",
|
|
4
4
|
"description": "Plan-Build-Run — Structured development workflow for Claude Code. Solves context rot through disciplined subagent delegation, structured planning, atomic execution, and goal-backward verification.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "SienkLogic",
|
|
@@ -12,10 +12,13 @@
|
|
|
12
12
|
* checkpointInit(phaseSlug, plans, planningDir) — Initialize checkpoint manifest
|
|
13
13
|
* checkpointUpdate(phaseSlug, opts, planningDir) — Update checkpoint manifest
|
|
14
14
|
* seedsMatch(phaseSlug, phaseNumber, planningDir) — Find matching seed files
|
|
15
|
+
* ciPoll(runId, timeoutSecs, planningDir) — Single-check GitHub Actions CI run status
|
|
16
|
+
* rollback(manifestPath, planningDir) — Rollback failed plan via checkpoint manifest
|
|
15
17
|
*/
|
|
16
18
|
|
|
17
19
|
const fs = require('fs');
|
|
18
20
|
const path = require('path');
|
|
21
|
+
const { execSync } = require('child_process');
|
|
19
22
|
|
|
20
23
|
/**
|
|
21
24
|
* Resolve the .planning directory from the given planningDir or env/cwd fallback.
|
|
@@ -441,6 +444,236 @@ function parseSeedFrontmatter(content) {
|
|
|
441
444
|
return result;
|
|
442
445
|
}
|
|
443
446
|
|
|
447
|
+
// ---------------------------------------------------------------------------
|
|
448
|
+
// ciPoll
|
|
449
|
+
// ---------------------------------------------------------------------------
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Single-check GitHub Actions CI run status (not a polling loop).
|
|
453
|
+
* The build skill calls this repeatedly when needed.
|
|
454
|
+
*
|
|
455
|
+
* @param {string|null} runId - GitHub Actions run ID
|
|
456
|
+
* @param {number} [timeoutSecs=300] - Max seconds allowed (used only to detect timeout already exceeded)
|
|
457
|
+
* @param {string} [planningDir] - Unused; kept for signature consistency
|
|
458
|
+
* @returns {{ status: string, conclusion: string|null, url: string, next_action: string, elapsed_seconds: number, error?: string }}
|
|
459
|
+
*/
|
|
460
|
+
function ciPoll(runId, timeoutSecs, _planningDir) {
|
|
461
|
+
const startMs = Date.now();
|
|
462
|
+
const timeout = (typeof timeoutSecs === 'number' && !isNaN(timeoutSecs)) ? timeoutSecs : 300;
|
|
463
|
+
|
|
464
|
+
if (!runId) {
|
|
465
|
+
return {
|
|
466
|
+
status: 'error',
|
|
467
|
+
conclusion: null,
|
|
468
|
+
url: '',
|
|
469
|
+
next_action: 'abort',
|
|
470
|
+
elapsed_seconds: 0,
|
|
471
|
+
error: 'Missing run-id. Usage: ci-poll <run-id> [--timeout <seconds>]'
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// If timeout already exceeded (caller sets 0 to signal timeout)
|
|
476
|
+
if (timeout <= 0) {
|
|
477
|
+
return {
|
|
478
|
+
status: 'timed_out',
|
|
479
|
+
conclusion: null,
|
|
480
|
+
url: '',
|
|
481
|
+
next_action: 'abort',
|
|
482
|
+
elapsed_seconds: 0,
|
|
483
|
+
error: 'CI timeout exceeded'
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
let raw;
|
|
488
|
+
try {
|
|
489
|
+
raw = execSync(`gh run view ${runId} --json status,conclusion,url`, {
|
|
490
|
+
encoding: 'utf8',
|
|
491
|
+
timeout: 10000
|
|
492
|
+
});
|
|
493
|
+
} catch (e) {
|
|
494
|
+
const elapsed = Math.round((Date.now() - startMs) / 1000);
|
|
495
|
+
return {
|
|
496
|
+
status: 'error',
|
|
497
|
+
conclusion: null,
|
|
498
|
+
url: '',
|
|
499
|
+
next_action: 'abort',
|
|
500
|
+
elapsed_seconds: elapsed,
|
|
501
|
+
error: e.message || 'gh command failed'
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
let parsed;
|
|
506
|
+
try {
|
|
507
|
+
parsed = JSON.parse(raw);
|
|
508
|
+
} catch (_e) {
|
|
509
|
+
const elapsed = Math.round((Date.now() - startMs) / 1000);
|
|
510
|
+
return {
|
|
511
|
+
status: 'error',
|
|
512
|
+
conclusion: null,
|
|
513
|
+
url: '',
|
|
514
|
+
next_action: 'abort',
|
|
515
|
+
elapsed_seconds: elapsed,
|
|
516
|
+
error: 'Failed to parse gh output: ' + raw
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const elapsed = Math.round((Date.now() - startMs) / 1000);
|
|
521
|
+
const ghStatus = parsed.status || '';
|
|
522
|
+
const conclusion = parsed.conclusion || null;
|
|
523
|
+
const url = parsed.url || '';
|
|
524
|
+
|
|
525
|
+
if (ghStatus === 'completed') {
|
|
526
|
+
const passed = conclusion === 'success';
|
|
527
|
+
return {
|
|
528
|
+
status: passed ? 'passed' : 'failed',
|
|
529
|
+
conclusion,
|
|
530
|
+
url,
|
|
531
|
+
next_action: passed ? 'continue' : 'abort',
|
|
532
|
+
elapsed_seconds: elapsed
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// in_progress, queued, waiting, etc.
|
|
537
|
+
return {
|
|
538
|
+
status: ghStatus,
|
|
539
|
+
conclusion: null,
|
|
540
|
+
url,
|
|
541
|
+
next_action: 'wait',
|
|
542
|
+
elapsed_seconds: elapsed
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// ---------------------------------------------------------------------------
|
|
547
|
+
// rollback
|
|
548
|
+
// ---------------------------------------------------------------------------
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Rollback a failed plan using checkpoint manifest.
|
|
552
|
+
* Reads manifest, runs git reset --soft, invalidates downstream SUMMARY.md files.
|
|
553
|
+
*
|
|
554
|
+
* @param {string} manifestPath - Absolute path to .checkpoint-manifest.json
|
|
555
|
+
* @param {string} [planningDir]
|
|
556
|
+
* @returns {{ ok: boolean, rolled_back_to: string|null, plans_invalidated: string[], files_deleted: string[], warnings: string[], error?: string }}
|
|
557
|
+
*/
|
|
558
|
+
function rollback(manifestPath, _planningDir) {
|
|
559
|
+
// Step 1: Read manifest
|
|
560
|
+
if (!fs.existsSync(manifestPath)) {
|
|
561
|
+
return {
|
|
562
|
+
ok: false,
|
|
563
|
+
rolled_back_to: null,
|
|
564
|
+
plans_invalidated: [],
|
|
565
|
+
files_deleted: [],
|
|
566
|
+
warnings: [],
|
|
567
|
+
error: 'Manifest not found: ' + manifestPath
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
let manifest;
|
|
572
|
+
try {
|
|
573
|
+
const raw = fs.readFileSync(manifestPath, 'utf8');
|
|
574
|
+
manifest = JSON.parse(raw);
|
|
575
|
+
} catch (e) {
|
|
576
|
+
return {
|
|
577
|
+
ok: false,
|
|
578
|
+
rolled_back_to: null,
|
|
579
|
+
plans_invalidated: [],
|
|
580
|
+
files_deleted: [],
|
|
581
|
+
warnings: [],
|
|
582
|
+
error: 'Cannot parse manifest: ' + e.message
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Step 2: Extract last_good_commit
|
|
587
|
+
const lastGoodCommit = manifest.last_good_commit || null;
|
|
588
|
+
if (!lastGoodCommit) {
|
|
589
|
+
return {
|
|
590
|
+
ok: false,
|
|
591
|
+
rolled_back_to: null,
|
|
592
|
+
plans_invalidated: [],
|
|
593
|
+
files_deleted: [],
|
|
594
|
+
warnings: [],
|
|
595
|
+
error: 'No rollback point available (last_good_commit is missing or null). Use abort instead.'
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const phaseDir = path.dirname(manifestPath);
|
|
600
|
+
const filesDeleted = [];
|
|
601
|
+
const plansInvalidated = [];
|
|
602
|
+
const warnings = [];
|
|
603
|
+
|
|
604
|
+
// Step 3: git reset --soft to last good commit
|
|
605
|
+
try {
|
|
606
|
+
execSync(`git reset --soft ${lastGoodCommit}`, { encoding: 'utf8', timeout: 15000 });
|
|
607
|
+
} catch (e) {
|
|
608
|
+
return {
|
|
609
|
+
ok: false,
|
|
610
|
+
rolled_back_to: null,
|
|
611
|
+
plans_invalidated: [],
|
|
612
|
+
files_deleted: [],
|
|
613
|
+
warnings: [],
|
|
614
|
+
error: 'git reset --soft failed: ' + e.message
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Step 4: Identify failed plan
|
|
619
|
+
const failedPlan = manifest.failed_plan || null;
|
|
620
|
+
|
|
621
|
+
// Step 5: Delete failed plan's SUMMARY.md if it exists
|
|
622
|
+
if (failedPlan) {
|
|
623
|
+
const failedSummary = path.join(phaseDir, `SUMMARY-${failedPlan}.md`);
|
|
624
|
+
if (fs.existsSync(failedSummary)) {
|
|
625
|
+
try {
|
|
626
|
+
fs.unlinkSync(failedSummary);
|
|
627
|
+
filesDeleted.push(failedSummary);
|
|
628
|
+
} catch (e) {
|
|
629
|
+
warnings.push(`Could not delete ${failedSummary}: ${e.message}`);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Step 6: Scan for downstream plans and invalidate them
|
|
635
|
+
// downstream_of maps planId -> [deps]. A plan is downstream if failedPlan is in its deps.
|
|
636
|
+
const downstreamOf = manifest.downstream_of || {};
|
|
637
|
+
const resolved = Array.isArray(manifest.checkpoints_resolved) ? manifest.checkpoints_resolved : [];
|
|
638
|
+
|
|
639
|
+
for (const planId of resolved) {
|
|
640
|
+
if (planId === failedPlan) continue;
|
|
641
|
+
const deps = downstreamOf[planId] || [];
|
|
642
|
+
if (failedPlan && deps.includes(failedPlan)) {
|
|
643
|
+
plansInvalidated.push(planId);
|
|
644
|
+
const summaryPath = path.join(phaseDir, `SUMMARY-${planId}.md`);
|
|
645
|
+
if (fs.existsSync(summaryPath)) {
|
|
646
|
+
try {
|
|
647
|
+
fs.unlinkSync(summaryPath);
|
|
648
|
+
filesDeleted.push(summaryPath);
|
|
649
|
+
} catch (e) {
|
|
650
|
+
warnings.push(`Could not delete ${summaryPath}: ${e.message}`);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Step 7: Write updated manifest back
|
|
657
|
+
const updatedManifest = Object.assign({}, manifest, {
|
|
658
|
+
checkpoints_resolved: resolved.filter(p =>
|
|
659
|
+
p !== failedPlan && !plansInvalidated.includes(p)
|
|
660
|
+
)
|
|
661
|
+
});
|
|
662
|
+
try {
|
|
663
|
+
fs.writeFileSync(manifestPath, JSON.stringify(updatedManifest, null, 2), 'utf8');
|
|
664
|
+
} catch (e) {
|
|
665
|
+
warnings.push('Could not update manifest: ' + e.message);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
return {
|
|
669
|
+
ok: true,
|
|
670
|
+
rolled_back_to: lastGoodCommit,
|
|
671
|
+
plans_invalidated: plansInvalidated,
|
|
672
|
+
files_deleted: filesDeleted,
|
|
673
|
+
warnings
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
|
|
444
677
|
// ---------------------------------------------------------------------------
|
|
445
678
|
// Exports
|
|
446
679
|
// ---------------------------------------------------------------------------
|
|
@@ -450,5 +683,7 @@ module.exports = {
|
|
|
450
683
|
summaryGate,
|
|
451
684
|
checkpointInit,
|
|
452
685
|
checkpointUpdate,
|
|
453
|
-
seedsMatch
|
|
686
|
+
seedsMatch,
|
|
687
|
+
ciPoll,
|
|
688
|
+
rollback
|
|
454
689
|
};
|
|
@@ -163,7 +163,9 @@ const {
|
|
|
163
163
|
summaryGate: _summaryGate,
|
|
164
164
|
checkpointInit: _checkpointInit,
|
|
165
165
|
checkpointUpdate: _checkpointUpdate,
|
|
166
|
-
seedsMatch: _seedsMatch
|
|
166
|
+
seedsMatch: _seedsMatch,
|
|
167
|
+
ciPoll: _ciPoll,
|
|
168
|
+
rollback: _rollback
|
|
167
169
|
} = require('./lib/build');
|
|
168
170
|
|
|
169
171
|
// --- Local LLM imports (not extracted — separate module tree) ---
|
|
@@ -341,6 +343,8 @@ function summaryGate(phaseSlug, planId) { return _summaryGate(phaseSlug, planId,
|
|
|
341
343
|
function checkpointInit(phaseSlug, plans) { return _checkpointInit(phaseSlug, plans, planningDir); }
|
|
342
344
|
function checkpointUpdate(phaseSlug, opts) { return _checkpointUpdate(phaseSlug, opts, planningDir); }
|
|
343
345
|
function seedsMatch(phaseSlug, phaseNum) { return _seedsMatch(phaseSlug, phaseNum, planningDir); }
|
|
346
|
+
function ciPoll(runId, timeoutSecs) { return _ciPoll(runId, timeoutSecs, planningDir); }
|
|
347
|
+
function rollbackPlan(manifestPath) { return _rollback(manifestPath, planningDir); }
|
|
344
348
|
|
|
345
349
|
// --- validateProject stays here (cross-cutting across modules) ---
|
|
346
350
|
|
|
@@ -819,6 +823,16 @@ async function main() {
|
|
|
819
823
|
} else {
|
|
820
824
|
error('Usage: seeds match <phase-slug> <phase-number>'); process.exit(1);
|
|
821
825
|
}
|
|
826
|
+
} else if (command === 'ci-poll') {
|
|
827
|
+
const runId = args[1];
|
|
828
|
+
const timeoutIdx = args.indexOf('--timeout');
|
|
829
|
+
const timeoutSecs = timeoutIdx !== -1 ? parseInt(args[timeoutIdx + 1], 10) : 300;
|
|
830
|
+
if (!runId) { error('Usage: pbr-tools.js ci-poll <run-id> [--timeout <seconds>]'); return; }
|
|
831
|
+
output(ciPoll(runId, timeoutSecs));
|
|
832
|
+
} else if (command === 'rollback') {
|
|
833
|
+
const manifestPath = args[1];
|
|
834
|
+
if (!manifestPath) { error('Usage: pbr-tools.js rollback <manifest-path>'); return; }
|
|
835
|
+
output(rollbackPlan(manifestPath));
|
|
822
836
|
} else if (command === 'context-triage') {
|
|
823
837
|
const options = {};
|
|
824
838
|
const agentsIdx = args.indexOf('--agents-done');
|
|
@@ -842,7 +856,7 @@ async function main() {
|
|
|
842
856
|
} else if (command === 'validate-project') {
|
|
843
857
|
output(validateProject());
|
|
844
858
|
} else {
|
|
845
|
-
error(`Unknown command: ${args.join(' ')}\nCommands: state load|check-progress|update|patch|advance-plan|record-metric, config validate|load-defaults|save-defaults|resolve-depth, validate-project, migrate [--dry-run] [--force], init execute-phase|plan-phase|quick|verify-work|resume|progress, state-bundle <phase>, plan-index, frontmatter, must-haves, phase-info, phase add|remove|list, roadmap update-status|update-plans, history append|load, todo list|get|add|done, event, llm health|status|classify|score-source|classify-error|summarize|metrics [--session <ISO>]|adjust-thresholds, learnings ingest|query|check-thresholds, milestone-stats <version>, context-triage [--agents-done N] [--plans-total N] [--step NAME]
|
|
859
|
+
error(`Unknown command: ${args.join(' ')}\nCommands: state load|check-progress|update|patch|advance-plan|record-metric, config validate|load-defaults|save-defaults|resolve-depth, validate-project, migrate [--dry-run] [--force], init execute-phase|plan-phase|quick|verify-work|resume|progress, state-bundle <phase>, plan-index, frontmatter, must-haves, phase-info, phase add|remove|list, roadmap update-status|update-plans, history append|load, todo list|get|add|done, event, llm health|status|classify|score-source|classify-error|summarize|metrics [--session <ISO>]|adjust-thresholds, learnings ingest|query|check-thresholds, milestone-stats <version>, context-triage [--agents-done N] [--plans-total N] [--step NAME], ci-poll <run-id> [--timeout <seconds>], rollback <manifest-path>`);
|
|
846
860
|
}
|
|
847
861
|
} catch (e) {
|
|
848
862
|
error(e.message);
|
|
@@ -850,6 +864,6 @@ async function main() {
|
|
|
850
864
|
}
|
|
851
865
|
|
|
852
866
|
if (require.main === module || process.argv[1] === __filename) { main().catch(err => { process.stderr.write(err.message + '\n'); process.exit(1); }); }
|
|
853
|
-
module.exports = { KNOWN_AGENTS, initExecutePhase, initPlanPhase, initQuick, initVerifyWork, initResume, initProgress, initStateBundle: stateBundle, stateBundle, statePatch, stateAdvancePlan, stateRecordMetric, parseStateMd, parseRoadmapMd, parseYamlFrontmatter, parseMustHaves, countMustHaves, stateLoad, stateCheckProgress, configLoad, configClearCache, configValidate, lockedFileUpdate, planIndex, determinePhaseStatus, findFiles, atomicWrite, tailLines, frontmatter, mustHavesCollect, phaseInfo, stateUpdate, roadmapUpdateStatus, roadmapUpdatePlans, updateLegacyStateField, updateFrontmatterField, updateTableRow, findRoadmapRow, resolveDepthProfile, DEPTH_PROFILE_DEFAULTS, historyAppend, historyLoad, VALID_STATUS_TRANSITIONS, validateStatusTransition, writeActiveSkill, validateProject, phaseAdd, phaseRemove, phaseList, loadUserDefaults, saveUserDefaults, mergeUserDefaults, USER_DEFAULTS_PATH, todoList, todoGet, todoAdd, todoDone, migrate, spotCheck, referenceGet, milestoneStats, contextTriage, stalenessCheck, summaryGate, checkpointInit, checkpointUpdate, seedsMatch };
|
|
867
|
+
module.exports = { KNOWN_AGENTS, initExecutePhase, initPlanPhase, initQuick, initVerifyWork, initResume, initProgress, initStateBundle: stateBundle, stateBundle, statePatch, stateAdvancePlan, stateRecordMetric, parseStateMd, parseRoadmapMd, parseYamlFrontmatter, parseMustHaves, countMustHaves, stateLoad, stateCheckProgress, configLoad, configClearCache, configValidate, lockedFileUpdate, planIndex, determinePhaseStatus, findFiles, atomicWrite, tailLines, frontmatter, mustHavesCollect, phaseInfo, stateUpdate, roadmapUpdateStatus, roadmapUpdatePlans, updateLegacyStateField, updateFrontmatterField, updateTableRow, findRoadmapRow, resolveDepthProfile, DEPTH_PROFILE_DEFAULTS, historyAppend, historyLoad, VALID_STATUS_TRANSITIONS, validateStatusTransition, writeActiveSkill, validateProject, phaseAdd, phaseRemove, phaseList, loadUserDefaults, saveUserDefaults, mergeUserDefaults, USER_DEFAULTS_PATH, todoList, todoGet, todoAdd, todoDone, migrate, spotCheck, referenceGet, milestoneStats, contextTriage, stalenessCheck, summaryGate, checkpointInit, checkpointUpdate, seedsMatch, ciPoll, rollbackPlan };
|
|
854
868
|
// NOTE: validateProject, phaseAdd, phaseRemove, phaseList were previously CLI-only (not exported).
|
|
855
869
|
// They are now exported for testability. This is additive and backwards-compatible.
|
|
@@ -374,7 +374,6 @@ Use AskUserQuestion with the three options. Route:
|
|
|
374
374
|
- Between waves: verify no file conflicts from parallel executors (`git status` for uncommitted changes)
|
|
375
375
|
|
|
376
376
|
**Read executor deviations:**
|
|
377
|
-
|
|
378
377
|
After all executors in the wave complete, read all SUMMARY frontmatter and:
|
|
379
378
|
- Collect `deferred` items into a running list (append to `.checkpoint-manifest.json` deferred array)
|
|
380
379
|
- Flag any deviation-rule-4 (architectural) stops — these require user attention
|
|
@@ -395,9 +394,7 @@ Wave {W} Results:
|
|
|
395
394
|
|
|
396
395
|
**Skip if** the depth profile has `features.inline_verify: false`.
|
|
397
396
|
|
|
398
|
-
To check: use the resolved depth profile. Only `comprehensive` mode enables inline verification by default.
|
|
399
|
-
|
|
400
|
-
When inline verification is enabled, each completed plan gets a targeted verification pass before the orchestrator proceeds to the next wave. This catches issues early — before dependent plans build on a broken foundation.
|
|
397
|
+
To check: use the resolved depth profile. Only `comprehensive` mode enables inline verification by default. When inline verification is enabled, each completed plan gets a targeted verification pass before the orchestrator proceeds to the next wave — catching issues early before dependent plans build on a broken foundation.
|
|
401
398
|
|
|
402
399
|
For each plan that completed successfully in this wave:
|
|
403
400
|
|
|
@@ -643,6 +640,8 @@ This ensures that `/pbr:review` after a `--gaps-only` build sees the updated ver
|
|
|
643
640
|
|
|
644
641
|
**8-pre-c. Codebase map incremental update (conditional):**
|
|
645
642
|
|
|
643
|
+
**CRITICAL (no hook): Run codebase map update if conditions are met. Do NOT skip this step.**
|
|
644
|
+
|
|
646
645
|
Only run if ALL of these are true:
|
|
647
646
|
- `.planning/codebase/` directory exists (project was previously scanned with `/pbr:scan`)
|
|
648
647
|
- Build was not aborted
|
|
@@ -753,6 +752,8 @@ Write `.planning/.auto-next` containing the next logical command (e.g., `/pbr:pl
|
|
|
753
752
|
|
|
754
753
|
**8e-ii. Check Pending Todos:**
|
|
755
754
|
|
|
755
|
+
**CRITICAL (no hook): Check pending todos after build. Do NOT skip this step.**
|
|
756
|
+
|
|
756
757
|
After completing the build, check if any pending todos are now satisfied:
|
|
757
758
|
|
|
758
759
|
1. Check if `.planning/todos/pending/` exists and contains files
|
|
@@ -869,3 +870,9 @@ If `git.branching` is `phase` but we're not on the phase branch:
|
|
|
869
870
|
| `.planning/STATE.md` | Updated progress | Steps 6f, 8b |
|
|
870
871
|
| `.planning/.auto-next` | Next command signal (if auto_continue enabled) | Step 8e |
|
|
871
872
|
| Project source files | Actual code | Step 6 (executors) |
|
|
873
|
+
|
|
874
|
+
---
|
|
875
|
+
|
|
876
|
+
## Cleanup
|
|
877
|
+
|
|
878
|
+
Delete `.planning/.active-skill` if it exists. This must happen on all paths (success, partial, and failure) before reporting results.
|
|
@@ -162,39 +162,7 @@ Start a new milestone cycle with new phases.
|
|
|
162
162
|
docs(planning): start milestone "{name}" (phases {start}-{end})
|
|
163
163
|
```
|
|
164
164
|
|
|
165
|
-
10. **Confirm** with branded output
|
|
166
|
-
```
|
|
167
|
-
╔══════════════════════════════════════════════════════════════╗
|
|
168
|
-
║ PLAN-BUILD-RUN ► MILESTONE CREATED ✓ ║
|
|
169
|
-
╚══════════════════════════════════════════════════════════════╝
|
|
170
|
-
|
|
171
|
-
**Milestone: {name}** — {count} phases
|
|
172
|
-
|
|
173
|
-
Phases:
|
|
174
|
-
{N}. {name}
|
|
175
|
-
{N+1}. {name}
|
|
176
|
-
...
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
╔══════════════════════════════════════════════════════════════╗
|
|
181
|
-
║ ▶ NEXT UP ║
|
|
182
|
-
╚══════════════════════════════════════════════════════════════╝
|
|
183
|
-
|
|
184
|
-
**Phase {N}: {name}** — start with discussion or planning
|
|
185
|
-
|
|
186
|
-
`/pbr:discuss {N}`
|
|
187
|
-
|
|
188
|
-
<sub>`/clear` first → fresh context window</sub>
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
**Also available:**
|
|
193
|
-
- `/pbr:plan {N}` — skip discussion, plan directly
|
|
194
|
-
- `/pbr:status` — see project status
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
```
|
|
165
|
+
10. **Confirm** with branded output — read `skills/milestone/templates/new-output.md.tmpl` and fill in `{name}` (milestone name), `{count}` (phase count), `{N}` (first phase number).
|
|
198
166
|
|
|
199
167
|
---
|
|
200
168
|
|
|
@@ -482,42 +450,7 @@ If `config.deployment.smoke_test_command` is set and non-empty:
|
|
|
482
450
|
|
|
483
451
|
This is advisory only — the milestone is already archived. Surface it as a potential issue for the user to investigate.
|
|
484
452
|
|
|
485
|
-
10. **Confirm** with branded output
|
|
486
|
-
```
|
|
487
|
-
╔══════════════════════════════════════════════════════════════╗
|
|
488
|
-
║ PLAN-BUILD-RUN ► MILESTONE COMPLETE 🎉 ║
|
|
489
|
-
╚══════════════════════════════════════════════════════════════╝
|
|
490
|
-
|
|
491
|
-
**{version}**
|
|
492
|
-
|
|
493
|
-
Stats:
|
|
494
|
-
- {count} phases, {count} plans
|
|
495
|
-
- {count} commits, {lines} lines of code
|
|
496
|
-
- {duration} days
|
|
497
|
-
|
|
498
|
-
Archived to: .planning/milestones/{version}/
|
|
499
|
-
Git tag: {version}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
╔══════════════════════════════════════════════════════════════╗
|
|
504
|
-
║ ▶ NEXT UP ║
|
|
505
|
-
╚══════════════════════════════════════════════════════════════╝
|
|
506
|
-
|
|
507
|
-
**Start the next milestone** — plan new features
|
|
508
|
-
|
|
509
|
-
`/pbr:milestone new`
|
|
510
|
-
|
|
511
|
-
<sub>`/clear` first → fresh context window</sub>
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
**Also available:**
|
|
516
|
-
- `/pbr:status` — see project status
|
|
517
|
-
- `/pbr:help` — see all commands
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
```
|
|
453
|
+
10. **Confirm** with branded output — read `skills/milestone/templates/complete-output.md.tmpl` and fill in `{version}`, `{count}` (phases, plans, commits), `{lines}`, `{duration}`.
|
|
521
454
|
|
|
522
455
|
---
|
|
523
456
|
|
|
@@ -561,91 +494,7 @@ Verify milestone completion with cross-phase integration checks.
|
|
|
561
494
|
|
|
562
495
|
**Spot-check:** After writing, verify `.planning/{version}-MILESTONE-AUDIT.md` exists on disk using Glob. If missing, re-attempt the write. If still missing, display an error and include findings inline.
|
|
563
496
|
|
|
564
|
-
7. **Report to user** using branded banners
|
|
565
|
-
|
|
566
|
-
**If PASSED:**
|
|
567
|
-
```
|
|
568
|
-
╔══════════════════════════════════════════════════════════════╗
|
|
569
|
-
║ PLAN-BUILD-RUN ► MILESTONE AUDIT PASSED ✓ ║
|
|
570
|
-
╚══════════════════════════════════════════════════════════════╝
|
|
571
|
-
|
|
572
|
-
All phases verified, integration checks passed, requirements covered.
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
╔══════════════════════════════════════════════════════════════╗
|
|
577
|
-
║ ▶ NEXT UP ║
|
|
578
|
-
╚══════════════════════════════════════════════════════════════╝
|
|
579
|
-
|
|
580
|
-
**Complete the milestone** — archive and tag
|
|
581
|
-
|
|
582
|
-
`/pbr:milestone complete {version}`
|
|
583
|
-
|
|
584
|
-
<sub>`/clear` first → fresh context window</sub>
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
**Also available:**
|
|
589
|
-
- `/pbr:milestone gaps` — address any minor issues first
|
|
590
|
-
- `/pbr:status` — see project status
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
```
|
|
594
|
-
|
|
595
|
-
**If GAPS FOUND:**
|
|
596
|
-
```
|
|
597
|
-
╔══════════════════════════════════════════════════════════════╗
|
|
598
|
-
║ PLAN-BUILD-RUN ► MILESTONE AUDIT — GAPS FOUND ⚠ ║
|
|
599
|
-
╚══════════════════════════════════════════════════════════════╝
|
|
600
|
-
|
|
601
|
-
Found {count} gaps:
|
|
602
|
-
- {gap 1}
|
|
603
|
-
- {gap 2}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
╔══════════════════════════════════════════════════════════════╗
|
|
608
|
-
║ ▶ NEXT UP ║
|
|
609
|
-
╚══════════════════════════════════════════════════════════════╝
|
|
610
|
-
|
|
611
|
-
**Close the gaps** — create fix phases
|
|
612
|
-
|
|
613
|
-
`/pbr:milestone gaps`
|
|
614
|
-
|
|
615
|
-
<sub>`/clear` first → fresh context window</sub>
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
**Also available:**
|
|
620
|
-
- `/pbr:milestone complete` — proceed despite gaps
|
|
621
|
-
- `/pbr:status` — see project status
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
```
|
|
625
|
-
|
|
626
|
-
**If TECH DEBT:**
|
|
627
|
-
```
|
|
628
|
-
╔══════════════════════════════════════════════════════════════╗
|
|
629
|
-
║ PLAN-BUILD-RUN ► MILESTONE AUDIT — TECH DEBT ⚠ ║
|
|
630
|
-
╚══════════════════════════════════════════════════════════════╝
|
|
631
|
-
|
|
632
|
-
Milestone functional but has {count} tech debt items.
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
╔══════════════════════════════════════════════════════════════╗
|
|
637
|
-
║ ▶ NEXT UP ║
|
|
638
|
-
╚══════════════════════════════════════════════════════════════╝
|
|
639
|
-
|
|
640
|
-
**Address tech debt or proceed**
|
|
641
|
-
|
|
642
|
-
`/pbr:milestone gaps` — create cleanup phases
|
|
643
|
-
`/pbr:milestone complete` — proceed as-is
|
|
644
|
-
|
|
645
|
-
<sub>`/clear` first → fresh context window</sub>
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
```
|
|
497
|
+
7. **Report to user** using branded banners — read `skills/milestone/templates/audit-output.md.tmpl`. The template contains all 3 variants (PASSED, GAPS FOUND, TECH DEBT). Select the appropriate section based on audit result. Fill in `{version}`, `{count}`, `{gap 1}`, `{gap 2}` as applicable.
|
|
649
498
|
|
|
650
499
|
---
|
|
651
500
|
|
|
@@ -725,35 +574,7 @@ Create phases to close gaps found during an audit.
|
|
|
725
574
|
docs(planning): add gap-closure phases from milestone audit
|
|
726
575
|
```
|
|
727
576
|
|
|
728
|
-
9. **Confirm** with branded output
|
|
729
|
-
```
|
|
730
|
-
╔══════════════════════════════════════════════════════════════╗
|
|
731
|
-
║ PLAN-BUILD-RUN ► GAP PHASES CREATED ✓ ║
|
|
732
|
-
╚══════════════════════════════════════════════════════════════╝
|
|
733
|
-
|
|
734
|
-
Created {count} gap-closure phase(s):
|
|
735
|
-
- Phase {N}: {name}
|
|
736
|
-
- Phase {N+1}: {name}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
╔══════════════════════════════════════════════════════════════╗
|
|
741
|
-
║ ▶ NEXT UP ║
|
|
742
|
-
╚══════════════════════════════════════════════════════════════╝
|
|
743
|
-
|
|
744
|
-
**Plan the first gap-closure phase**
|
|
745
|
-
|
|
746
|
-
`/pbr:plan {N}`
|
|
747
|
-
|
|
748
|
-
<sub>`/clear` first → fresh context window</sub>
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
**Also available:**
|
|
753
|
-
- `/pbr:status` — see project status
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
```
|
|
577
|
+
9. **Confirm** with branded output — read `skills/milestone/templates/gaps-output.md.tmpl` and fill in `{count}` (gap-closure phases created), `{N}` (first gap phase number), `{name}` (phase name).
|
|
757
578
|
|
|
758
579
|
---
|
|
759
580
|
|
|
@@ -165,34 +165,7 @@ Collect all of this into a context bundle that will be passed to subagents.
|
|
|
165
165
|
|
|
166
166
|
**IMPORTANT**: This step is FREE (no subagents). It happens entirely inline.
|
|
167
167
|
|
|
168
|
-
Before spawning any agents, present
|
|
169
|
-
|
|
170
|
-
```
|
|
171
|
-
Phase {N}: {Name}
|
|
172
|
-
Goal: {from roadmap}
|
|
173
|
-
|
|
174
|
-
My assumptions about this phase:
|
|
175
|
-
|
|
176
|
-
1. **Approach**: I'm assuming we'll {approach}
|
|
177
|
-
- Correct? [yes/no/adjust]
|
|
178
|
-
|
|
179
|
-
2. **Key technology**: I'm assuming we'll use {tech}
|
|
180
|
-
- Correct? [yes/no/adjust]
|
|
181
|
-
|
|
182
|
-
3. **Architecture**: I'm assuming {architectural assumption}
|
|
183
|
-
- Correct? [yes/no/adjust]
|
|
184
|
-
|
|
185
|
-
4. **Scope boundary**: I'm assuming {scope assumption}
|
|
186
|
-
- Correct? [yes/no/adjust]
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
For each assumption the user corrects:
|
|
190
|
-
- Record the correction
|
|
191
|
-
- These corrections become additional CONTEXT.md entries
|
|
192
|
-
|
|
193
|
-
After all assumptions are confirmed/corrected:
|
|
194
|
-
- Update `.planning/CONTEXT.md` with any new locked decisions
|
|
195
|
-
- Continue to Step 4
|
|
168
|
+
Before spawning any agents, present 4 assumptions to the user — one each for: approach (how the phase will be implemented), key technology, architecture, and scope boundary. For each, ask the user to confirm or correct. Record corrections as new CONTEXT.md locked decisions. After all assumptions are confirmed/corrected, continue to Step 4.
|
|
196
169
|
|
|
197
170
|
---
|
|
198
171
|
|
|
@@ -252,101 +225,61 @@ After the researcher completes, check the Task() output for a completion marker:
|
|
|
252
225
|
|
|
253
226
|
---
|
|
254
227
|
|
|
255
|
-
### Step 4.5:
|
|
256
|
-
|
|
257
|
-
Before spawning the planner, scan `.planning/seeds/` for seeds whose trigger matches the current phase:
|
|
258
|
-
|
|
259
|
-
1. Run seed matcher CLI:
|
|
260
|
-
```bash
|
|
261
|
-
node ${CLAUDE_PLUGIN_ROOT}/scripts/pbr-tools.js seeds match {phase-slug} {phase-number}
|
|
262
|
-
```
|
|
263
|
-
Returns `{ matched: [{ name, description, trigger, path }] }`.
|
|
264
|
-
2. If `matched` is empty: proceed silently.
|
|
265
|
-
3. If `matched` is non-empty: present to user (see step 4 below).
|
|
266
|
-
4. If matching seeds are found, present them to the user:
|
|
267
|
-
```
|
|
268
|
-
Found {N} seeds related to Phase {NN}:
|
|
269
|
-
- {seed_name}: {seed description}
|
|
270
|
-
- {seed_name}: {seed description}
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
Use AskUserQuestion (pattern: yes-no-pick from `skills/shared/gate-prompts.md`):
|
|
274
|
-
question: "Include these {N} seeds in planning?"
|
|
275
|
-
header: "Seeds?"
|
|
276
|
-
options:
|
|
277
|
-
- label: "Yes, all" description: "Include all {N} matching seeds"
|
|
278
|
-
- label: "Let me pick" description: "Choose which seeds to include"
|
|
279
|
-
- label: "No" description: "Proceed without seeds"
|
|
280
|
-
5. If "Yes, all": include all matching seed content in the planner's context
|
|
281
|
-
6. If "Let me pick": present individual seeds for selection
|
|
282
|
-
7. If "No" or "Other": proceed without seeds
|
|
283
|
-
8. If no matching seeds found: proceed silently
|
|
284
|
-
|
|
285
|
-
---
|
|
286
|
-
|
|
287
|
-
### Step 4.6: Surface Deferred Ideas (inline, before planning)
|
|
288
|
-
|
|
289
|
-
Before spawning the planner, check `.planning/CONTEXT.md` for deferred ideas that may be relevant to this phase:
|
|
290
|
-
|
|
291
|
-
1. If `.planning/CONTEXT.md` does NOT exist, skip this step silently
|
|
292
|
-
2. If it exists, scan for sections named "Deferred Ideas", "Deferred", "Ideas", or "Seeds" (case-insensitive heading match)
|
|
293
|
-
3. For each deferred item found, check relevance to the current phase by comparing the item text against the phase goal, requirements, and slug
|
|
294
|
-
4. If relevant deferred items are found, present them to the user:
|
|
295
|
-
```
|
|
296
|
-
Found {N} deferred idea(s) from previous discussions that may be relevant to Phase {NN}:
|
|
297
|
-
- {deferred item summary}
|
|
298
|
-
- {deferred item summary}
|
|
299
|
-
```
|
|
300
|
-
Use AskUserQuestion (pattern: yes-no from `skills/shared/gate-prompts.md`):
|
|
301
|
-
question: "Include these deferred ideas in the planning context?"
|
|
302
|
-
header: "Deferred Ideas"
|
|
303
|
-
options:
|
|
304
|
-
- label: "Yes" description: "Pass relevant deferred ideas to the planner"
|
|
305
|
-
- label: "No" description: "Proceed without deferred ideas"
|
|
306
|
-
5. If "Yes": append the relevant deferred items to the context bundle for the planner prompt (add them to the `<project_context>` block under a `Deferred ideas to consider:` heading)
|
|
307
|
-
6. If "No" or no relevant items found: proceed without changes
|
|
308
|
-
|
|
309
|
-
This is a lightweight relevance filter — do NOT spawn a subagent for this. Just match keywords from the deferred items against the phase goal and requirement text.
|
|
310
|
-
|
|
311
|
-
---
|
|
312
|
-
|
|
313
|
-
### Step 5: Planning (delegated)
|
|
228
|
+
### Step 4.5: Pre-Planner Briefing (delegated)
|
|
314
229
|
|
|
315
|
-
|
|
230
|
+
**CRITICAL (no hook): Run pre-planner briefing before spawning the planner. Do NOT skip this step.**
|
|
316
231
|
|
|
317
|
-
|
|
232
|
+
Consolidate seed scanning and deferred idea surfacing into a single lightweight Task():
|
|
318
233
|
|
|
319
|
-
|
|
234
|
+
```
|
|
235
|
+
Task({
|
|
236
|
+
subagent_type: "pbr:general",
|
|
237
|
+
model: "haiku",
|
|
238
|
+
prompt: "Pre-planner briefing for Phase {NN} ({phase-slug}).
|
|
239
|
+
|
|
240
|
+
1. SEED SCANNING:
|
|
241
|
+
Run: `node ${CLAUDE_PLUGIN_ROOT}/scripts/pbr-tools.js seeds match {phase-slug} {phase-number}`
|
|
242
|
+
If `matched` is non-empty, output a ## Seeds section listing each seed name, description, and content.
|
|
243
|
+
If empty, output: ## Seeds\nNo matching seeds found.
|
|
244
|
+
|
|
245
|
+
2. DEFERRED IDEAS:
|
|
246
|
+
Read `.planning/CONTEXT.md`. If it has a section containing 'deferred' or 'ideas' (case-insensitive),
|
|
247
|
+
extract items that mention Phase {NN} or keywords matching the phase slug.
|
|
248
|
+
If relevant items found, output a ## Deferred Ideas section listing them.
|
|
249
|
+
If none found, output: ## Deferred Ideas\nNo relevant deferred items.
|
|
250
|
+
|
|
251
|
+
Output format: Return both sections as markdown. End with ## BRIEFING COMPLETE."
|
|
252
|
+
})
|
|
253
|
+
```
|
|
320
254
|
|
|
321
|
-
|
|
322
|
-
|
|
255
|
+
After the Task() completes:
|
|
256
|
+
- If `## Seeds` section contains matches: present them to the user via AskUserQuestion (pattern: yes-no-pick from `skills/shared/gate-prompts.md`):
|
|
257
|
+
question: "Include these {N} seeds in planning?"
|
|
258
|
+
header: "Seeds?"
|
|
259
|
+
options:
|
|
260
|
+
- label: "Yes, all" description: "Include all {N} matching seeds"
|
|
261
|
+
- label: "Let me pick" description: "Choose which seeds to include"
|
|
262
|
+
- label: "No" description: "Proceed without seeds"
|
|
263
|
+
- If "Yes, all": include seed content in planner context
|
|
264
|
+
- If "Let me pick": present individual seeds for selection
|
|
265
|
+
- If "No": proceed without seeds
|
|
323
266
|
|
|
324
|
-
|
|
267
|
+
- If `## Deferred Ideas` section has items: present via AskUserQuestion (pattern: yes-no from `skills/shared/gate-prompts.md`):
|
|
268
|
+
question: "Include these deferred ideas in planning context?"
|
|
269
|
+
- If "Yes": append to planner context under `Deferred ideas to consider:`
|
|
270
|
+
- If "No": proceed without changes
|
|
325
271
|
|
|
326
|
-
|
|
327
|
-
- subagent_type: "pbr:planner"
|
|
328
|
-
- Prompt includes: "You are the ARCHITECT role in a planning team. Focus on: structure, file boundaries, dependency ordering, wave assignment. Write your output to `.planning/phases/{NN}-{slug}/team/architect-PLAN.md`. Do NOT write final PLAN.md files -- your output will be synthesized."
|
|
329
|
-
- Include phase goal, research doc paths, CONTEXT.md path in the prompt
|
|
272
|
+
- If both sections are empty: proceed silently to Step 5 (no AskUserQuestion needed)
|
|
330
273
|
|
|
331
|
-
|
|
332
|
-
- subagent_type: "pbr:planner"
|
|
333
|
-
- Prompt includes: "You are the SECURITY REVIEWER role in a planning team. Focus on: authentication checks, input validation tasks, secrets handling, permission boundaries. Write your output to `.planning/phases/{NN}-{slug}/team/security-PLAN.md`. Do NOT write final PLAN.md files."
|
|
334
|
-
- Include same context as Agent 1
|
|
274
|
+
---
|
|
335
275
|
|
|
336
|
-
|
|
337
|
-
- subagent_type: "pbr:planner"
|
|
338
|
-
- Prompt includes: "You are the TEST DESIGNER role in a planning team. Focus on: test strategy, coverage targets, edge cases, which tasks should use TDD, integration test boundaries. Write your output to `.planning/phases/{NN}-{slug}/team/test-PLAN.md`. Do NOT write final PLAN.md files."
|
|
339
|
-
- Include same context as Agent 1
|
|
276
|
+
### Step 5: Planning (delegated)
|
|
340
277
|
|
|
341
|
-
|
|
342
|
-
4. Display to the user: `◐ Spawning synthesizer...`
|
|
278
|
+
#### Team Mode (--teams)
|
|
343
279
|
|
|
344
|
-
|
|
345
|
-
- subagent_type: "pbr:synthesizer"
|
|
346
|
-
- Prompt: "Read all files in `.planning/phases/{NN}-{slug}/team/`. Synthesize them into unified PLAN.md files in `.planning/phases/{NN}-{slug}/`. The architect output provides structure, the security output adds security-related tasks or checks, and the test output informs TDD flags and test tasks. Resolve any contradictions by preferring the architect's structure with security and test additions."
|
|
347
|
-
5. Proceed to plan checking as normal
|
|
280
|
+
If `--teams` flag is set OR `config.parallelization.use_teams` is true, spawn 3 parallel planner agents (architect, security, test) then a synthesizer to merge their outputs. See `references/agent-teams.md` for agent role definitions, output paths (`.planning/phases/{NN}-{slug}/team/`), and prompt content for each role.
|
|
348
281
|
|
|
349
|
-
If `--teams` is NOT set and `config.parallelization.use_teams` is false or unset, proceed with the
|
|
282
|
+
If `--teams` is NOT set and `config.parallelization.use_teams` is false or unset, proceed with the single-planner flow below.
|
|
350
283
|
|
|
351
284
|
#### Single-Planner Flow (default)
|
|
352
285
|
|
|
@@ -501,25 +434,7 @@ Follow the revision loop pattern with:
|
|
|
501
434
|
|
|
502
435
|
**If approval is needed:**
|
|
503
436
|
|
|
504
|
-
Present a summary of all plans to the user:
|
|
505
|
-
|
|
506
|
-
```
|
|
507
|
-
Phase {N}: {name}
|
|
508
|
-
Plans: {count}
|
|
509
|
-
|
|
510
|
-
Plan {phase}-01: {plan name} (Wave {W}, {task_count} tasks)
|
|
511
|
-
Must-haves: {list truths}
|
|
512
|
-
Files: {list files_modified}
|
|
513
|
-
Tasks:
|
|
514
|
-
1. {task name}
|
|
515
|
-
2. {task name}
|
|
516
|
-
|
|
517
|
-
Plan {phase}-02: {plan name} (Wave {W}, {task_count} tasks)
|
|
518
|
-
...
|
|
519
|
-
|
|
520
|
-
Wave execution order:
|
|
521
|
-
Wave 1: Plan 01, Plan 02 (parallel)
|
|
522
|
-
Wave 2: Plan 03 (depends on 01, 02)
|
|
437
|
+
Present a summary of all plans to the user. For each plan include: plan name, wave, task count, must-haves, files_modified. For each task include the task name. Add a wave execution order summary (Wave 1: Plan 01, 02 (parallel), Wave 2: Plan 03, etc.).
|
|
523
438
|
|
|
524
439
|
Use AskUserQuestion (pattern: approve-revise-abort from `skills/shared/gate-prompts.md`):
|
|
525
440
|
question: "Approve these {count} plans for Phase {N}?"
|
|
@@ -528,7 +443,6 @@ Use AskUserQuestion (pattern: approve-revise-abort from `skills/shared/gate-prom
|
|
|
528
443
|
- label: "Approve" description: "Proceed to build phase"
|
|
529
444
|
- label: "Request changes" description: "Discuss adjustments before proceeding"
|
|
530
445
|
- label: "Abort" description: "Cancel planning for this phase"
|
|
531
|
-
```
|
|
532
446
|
|
|
533
447
|
**If user selects 'Request changes' or 'Other':**
|
|
534
448
|
- Discuss what needs to change
|
|
@@ -539,14 +453,7 @@ Use AskUserQuestion (pattern: approve-revise-abort from `skills/shared/gate-prom
|
|
|
539
453
|
- **CONTEXT.md compliance reporting**: If `.planning/CONTEXT.md` exists, compare all locked decisions against the generated plans. Print: "CONTEXT.md compliance: {M}/{N} locked decisions mapped to tasks" where M = locked decisions that are reflected in at least one task, N = total locked decisions. If any locked decisions are unmapped, list them as warnings.
|
|
540
454
|
- **Dependency fingerprinting**: For each dependency phase (phases that this phase depends on, per ROADMAP.md):
|
|
541
455
|
1. Find all SUMMARY.md files in the dependency phase directory
|
|
542
|
-
2. Compute a
|
|
543
|
-
3. Add a `dependency_fingerprints` field to each plan's YAML frontmatter:
|
|
544
|
-
```yaml
|
|
545
|
-
dependency_fingerprints:
|
|
546
|
-
"01-01": "len:4856-mod:2025-02-08T09:40"
|
|
547
|
-
"01-02": "len:4375-mod:2025-02-08T09:43"
|
|
548
|
-
```
|
|
549
|
-
4. This allows the build skill to detect if dependency phases were re-built after this plan was created
|
|
456
|
+
2. Compute a fingerprint string for each: `"len:{bytes}-mod:{mtime}"` and add as a `dependency_fingerprints` map in each plan's YAML frontmatter — this allows the build skill to detect stale plans if dependencies were rebuilt.
|
|
550
457
|
- **Update ROADMAP.md Progress table** (REQUIRED — do this BEFORE updating STATE.md):
|
|
551
458
|
|
|
552
459
|
**Tooling shortcut**: Use the CLI for atomic updates:
|