context-planning 0.7.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/LICENSE +21 -0
- package/README.md +454 -0
- package/bin/commands/_helpers.js +53 -0
- package/bin/commands/_usage.js +67 -0
- package/bin/commands/capture.js +46 -0
- package/bin/commands/codebase-status.js +41 -0
- package/bin/commands/complete-milestone.js +57 -0
- package/bin/commands/config.js +70 -0
- package/bin/commands/doctor.js +139 -0
- package/bin/commands/gsd-import.js +90 -0
- package/bin/commands/inbox.js +81 -0
- package/bin/commands/index.js +33 -0
- package/bin/commands/init.js +87 -0
- package/bin/commands/install.js +43 -0
- package/bin/commands/scaffold-codebase.js +53 -0
- package/bin/commands/scaffold-milestone.js +58 -0
- package/bin/commands/scaffold-phase.js +65 -0
- package/bin/commands/status.js +42 -0
- package/bin/commands/statusline.js +108 -0
- package/bin/commands/tick.js +49 -0
- package/bin/commands/version.js +9 -0
- package/bin/commands/worktree.js +218 -0
- package/bin/commands/write-summary.js +54 -0
- package/bin/cp.cmd +2 -0
- package/bin/cp.js +54 -0
- package/commands/cp/capture.md +107 -0
- package/commands/cp/complete-milestone.md +166 -0
- package/commands/cp/execute-phase.md +220 -0
- package/commands/cp/map-codebase.md +211 -0
- package/commands/cp/new-milestone.md +136 -0
- package/commands/cp/new-project.md +132 -0
- package/commands/cp/plan-phase.md +195 -0
- package/commands/cp/progress.md +147 -0
- package/commands/cp/quick.md +104 -0
- package/commands/cp/resume.md +125 -0
- package/commands/cp/write-summary.md +33 -0
- package/docs/MIGRATION-v0.5.md +140 -0
- package/docs/architecture.md +189 -0
- package/docs/superpowers/plans/2026-05-20-v0-7-plan-16-01-design-md-infrastructure.md +1064 -0
- package/docs/superpowers/plans/2026-05-20-v0-7-plan-16-02-review-log-infrastructure.md +418 -0
- package/docs/superpowers/plans/2026-05-20-v0-7-plan-16-03-key-decisions-hard-block.md +295 -0
- package/docs/superpowers/specs/2026-05-20-generic-provider-harness-detection-design.md +380 -0
- package/docs/superpowers/specs/2026-05-20-v0-7-design-capture-design.md +400 -0
- package/docs/writing-providers.md +76 -0
- package/install/aider.js +204 -0
- package/install/claude.js +116 -0
- package/install/common.js +65 -0
- package/install/copilot.js +86 -0
- package/install/cursor.js +120 -0
- package/install/echo-provider.js +50 -0
- package/lib/codebase-mapper.js +169 -0
- package/lib/detect.js +280 -0
- package/lib/frontmatter.js +72 -0
- package/lib/gsd-compat.js +165 -0
- package/lib/import.js +543 -0
- package/lib/inbox.js +226 -0
- package/lib/lifecycle.js +929 -0
- package/lib/merge.js +157 -0
- package/lib/milestone.js +595 -0
- package/lib/paths.js +191 -0
- package/lib/provider.js +168 -0
- package/lib/roadmap.js +134 -0
- package/lib/state.js +99 -0
- package/lib/worktree.js +253 -0
- package/package.json +45 -0
- package/templates/DESIGN.md +78 -0
- package/templates/INBOX.md +13 -0
- package/templates/MILESTONE-CONTEXT.md +40 -0
- package/templates/MILESTONES.md +29 -0
- package/templates/PLAN.md +84 -0
- package/templates/PROJECT.md +43 -0
- package/templates/REVIEW-LOG.md +38 -0
- package/templates/ROADMAP.md +34 -0
- package/templates/STATE.md +78 -0
- package/templates/SUMMARY.md +75 -0
- package/templates/codebase/ARCHITECTURE.md +30 -0
- package/templates/codebase/CONCERNS.md +30 -0
- package/templates/codebase/CONVENTIONS.md +30 -0
- package/templates/codebase/INTEGRATIONS.md +30 -0
- package/templates/codebase/STACK.md +26 -0
- package/templates/codebase/STRUCTURE.md +32 -0
- package/templates/codebase/TESTING.md +39 -0
- package/templates/config.json +173 -0
- package/templates/phase-PLAN.md +32 -0
- package/templates/quick-PLAN.md +24 -0
- package/templates/quick-SUMMARY.md +25 -0
|
@@ -0,0 +1,1064 @@
|
|
|
1
|
+
---
|
|
2
|
+
phase: 16-design-capture-infrastructure
|
|
3
|
+
plan: "01"
|
|
4
|
+
type: execute
|
|
5
|
+
wave: 1
|
|
6
|
+
depends_on: []
|
|
7
|
+
files_modified:
|
|
8
|
+
- templates/DESIGN.md
|
|
9
|
+
- lib/paths.js
|
|
10
|
+
- lib/lifecycle.js
|
|
11
|
+
- lib/milestone.js
|
|
12
|
+
- test/unit-design.js
|
|
13
|
+
- package.json
|
|
14
|
+
- .github/skills/cp-new-milestone/SKILL.md
|
|
15
|
+
- .github/skills/cp-plan-phase/SKILL.md
|
|
16
|
+
autonomous: true
|
|
17
|
+
requirements: []
|
|
18
|
+
user_setup: []
|
|
19
|
+
must_haves:
|
|
20
|
+
truths:
|
|
21
|
+
- "scaffold-phase emits DESIGN.md alongside PLAN.md"
|
|
22
|
+
- "scaffold-milestone creates .planning/milestones/<slug>/DESIGN.md"
|
|
23
|
+
- "aggregateSummaries surfaces phaseDesignRefs[]"
|
|
24
|
+
- "completeMilestone promotes MILESTONE-CONTEXT.md into milestone DESIGN.md"
|
|
25
|
+
- "templates/DESIGN.md follows the ADR + SP-brainstorm union from the spec"
|
|
26
|
+
artifacts:
|
|
27
|
+
- "templates/DESIGN.md exists with all required sections"
|
|
28
|
+
- "test/unit-design.js passes with no failures"
|
|
29
|
+
- "npm test all green (20 files now)"
|
|
30
|
+
key_links:
|
|
31
|
+
- "docs/superpowers/specs/2026-05-20-v0-7-design-capture-design.md (spec)"
|
|
32
|
+
- "docs/superpowers/plans/2026-05-20-v0-7-plan-16-01-design-md-infrastructure.md (SP-side pointer)"
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
<objective>
|
|
36
|
+
Plan 16-01: DESIGN.md Infrastructure (Phase 16, milestone v0.7 Design Capture)
|
|
37
|
+
|
|
38
|
+
Purpose: Add the milestone-tier and phase-tier `DESIGN.md` files to cp's
|
|
39
|
+
scaffold + aggregation pipeline so SP brainstorming output has a persistent,
|
|
40
|
+
structured home. Implements the DESIGN.md slice of the v0.7 spec.
|
|
41
|
+
|
|
42
|
+
Output: `templates/DESIGN.md` + path helpers + scaffold extensions +
|
|
43
|
+
aggregator extension + promotion at milestone close + tests + skill docs.
|
|
44
|
+
</objective>
|
|
45
|
+
|
|
46
|
+
<execution_context>
|
|
47
|
+
@.planning/config.json
|
|
48
|
+
</execution_context>
|
|
49
|
+
|
|
50
|
+
<context>
|
|
51
|
+
@.planning/PROJECT.md
|
|
52
|
+
@.planning/ROADMAP.md
|
|
53
|
+
@.planning/STATE.md
|
|
54
|
+
@docs/superpowers/specs/2026-05-20-v0-7-design-capture-design.md
|
|
55
|
+
</context>
|
|
56
|
+
|
|
57
|
+
<tasks>
|
|
58
|
+
|
|
59
|
+
<!--
|
|
60
|
+
Plan format: SP writing-plans bite-sized checkbox style. The
|
|
61
|
+
subagent-driven-development skill should treat each `### Task N` block as
|
|
62
|
+
one fresh-subagent dispatch. Each step inside the task is a single action
|
|
63
|
+
(2-5 min). Each task ends with a commit step.
|
|
64
|
+
-->
|
|
65
|
+
|
|
66
|
+
# Implementation Plan (Bite-Sized Tasks)
|
|
67
|
+
|
|
68
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task.
|
|
69
|
+
|
|
70
|
+
## File Structure (read before starting)
|
|
71
|
+
|
|
72
|
+
| File | Action | Responsibility |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| `templates/DESIGN.md` | Create | Union ADR + SP-brainstorm template (one file, both tiers, `{{TIER_KEY}}` substitution) |
|
|
75
|
+
| `lib/paths.js` | Modify | Add `designFile()`, `milestoneSlug()`, `milestoneDir()`, `milestoneDesignFile()` helpers + exports |
|
|
76
|
+
| `lib/lifecycle.js` | Modify | Extend `scaffoldPhase()` to emit DESIGN.md action; extend `scaffoldMilestone()` to emit milestone dir + DESIGN.md action; extend `completeMilestone()` to call new promotion step |
|
|
77
|
+
| `lib/milestone.js` | Modify | Extend `aggregateSummaries()` output with `phaseDesignRefs[]` field; new `promoteMilestoneContext()` helper |
|
|
78
|
+
| `test/unit-design.js` | Create | New test file (path helpers, scaffold extensions, aggregator promotion) |
|
|
79
|
+
| `package.json` | Modify | Add new test to `npm test` script |
|
|
80
|
+
| `.github/skills/cp-new-milestone/SKILL.md` | Modify | Step 3 references both MILESTONE-CONTEXT.md and milestone DESIGN.md |
|
|
81
|
+
| `.github/skills/cp-plan-phase/SKILL.md` | Modify | Insert new Step 3.5 |
|
|
82
|
+
|
|
83
|
+
**Out of scope (handled in 16-02 / 16-03):** REVIEW-LOG.md, `writeSummary()` validation.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Task 1: Create templates/DESIGN.md
|
|
88
|
+
|
|
89
|
+
**Files:**
|
|
90
|
+
- Create: `templates/DESIGN.md`
|
|
91
|
+
|
|
92
|
+
- [ ] **Step 1: Write the template file** with the following exact content:
|
|
93
|
+
|
|
94
|
+
```markdown
|
|
95
|
+
---
|
|
96
|
+
# Tier marker: cp scaffold substitutes one of:
|
|
97
|
+
# phase: "{{PHASE_NUM}}" (for phase-tier DESIGN.md)
|
|
98
|
+
# milestone_slug: "{{MILESTONE_SLUG}}" (for milestone-tier DESIGN.md)
|
|
99
|
+
{{TIER_KEY}}
|
|
100
|
+
milestone: {{MILESTONE_NAME}}
|
|
101
|
+
status: proposed
|
|
102
|
+
created: {{DATE}}
|
|
103
|
+
updated: {{DATE}}
|
|
104
|
+
deciders: []
|
|
105
|
+
supersedes: []
|
|
106
|
+
superseded_by: null
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
# Design: {{TITLE}}
|
|
110
|
+
|
|
111
|
+
## Status
|
|
112
|
+
|
|
113
|
+
{Proposed | Accepted on YYYY-MM-DD | Superseded by …}
|
|
114
|
+
|
|
115
|
+
## Context
|
|
116
|
+
|
|
117
|
+
<!-- Forces driving this design: constraints, prior decisions, requirements. -->
|
|
118
|
+
|
|
119
|
+
## Decision
|
|
120
|
+
|
|
121
|
+
<!-- What we decided. Short, declarative. -->
|
|
122
|
+
|
|
123
|
+
## Consequences
|
|
124
|
+
|
|
125
|
+
### Positive
|
|
126
|
+
-
|
|
127
|
+
|
|
128
|
+
### Negative
|
|
129
|
+
-
|
|
130
|
+
|
|
131
|
+
### Neutral
|
|
132
|
+
-
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Architecture
|
|
137
|
+
|
|
138
|
+
<!-- Boxes-and-lines, ASCII diagrams welcome. -->
|
|
139
|
+
|
|
140
|
+
## Components
|
|
141
|
+
|
|
142
|
+
<!-- Each unit: name, purpose, public interface, dependencies. -->
|
|
143
|
+
|
|
144
|
+
## Data Flow
|
|
145
|
+
|
|
146
|
+
<!-- How data moves through the components. -->
|
|
147
|
+
|
|
148
|
+
## Error Handling
|
|
149
|
+
|
|
150
|
+
<!-- Failure modes and recovery. -->
|
|
151
|
+
|
|
152
|
+
## Testing Strategy
|
|
153
|
+
|
|
154
|
+
<!-- Unit / integration / e2e split, coverage targets. -->
|
|
155
|
+
|
|
156
|
+
## Alternatives Considered
|
|
157
|
+
|
|
158
|
+
### Option A — <name>
|
|
159
|
+
|
|
160
|
+
**Pros:**
|
|
161
|
+
|
|
162
|
+
**Cons:**
|
|
163
|
+
|
|
164
|
+
**Verdict:** rejected because…
|
|
165
|
+
|
|
166
|
+
## Open Questions
|
|
167
|
+
|
|
168
|
+
- [ ]
|
|
169
|
+
|
|
170
|
+
## References
|
|
171
|
+
|
|
172
|
+
-
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
- [ ] **Step 2: Verify the file**
|
|
176
|
+
|
|
177
|
+
Run: `node -e "const fs=require('fs'); const t=fs.readFileSync('templates/DESIGN.md','utf8'); if (!t.includes('{{TIER_KEY}}')||!t.includes('## Architecture')||!t.includes('## Alternatives Considered')) { console.error('template missing required substitutions'); process.exit(1); } console.log('OK');"`
|
|
178
|
+
|
|
179
|
+
Expected: `OK`
|
|
180
|
+
|
|
181
|
+
- [ ] **Step 3: Commit**
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
git add templates/DESIGN.md
|
|
185
|
+
git commit -m "cp(16-01): add templates/DESIGN.md (union ADR + SP brainstorm)"
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Task 2: Add path helpers in lib/paths.js
|
|
191
|
+
|
|
192
|
+
**Files:**
|
|
193
|
+
- Modify: `lib/paths.js` (insert four new functions after `summaryFile`, around line 102)
|
|
194
|
+
|
|
195
|
+
- [ ] **Step 1: Open `lib/paths.js` and insert these four helpers immediately after the `summaryFile` function (and before `findPhaseDir`):**
|
|
196
|
+
|
|
197
|
+
```javascript
|
|
198
|
+
/** Full path to a phase DESIGN.md file. Resolves the phase dir on disk first
|
|
199
|
+
* so callers can pass just the phase number. Returns null if no phase dir. */
|
|
200
|
+
function designFile(phaseNumOrSlug, root = repoRoot()) {
|
|
201
|
+
const dir = findPhaseDir(phaseNumOrSlug, root);
|
|
202
|
+
if (!dir) return null;
|
|
203
|
+
return path.join(dir, 'DESIGN.md');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Slugify a milestone name (e.g. "v0.7 Design Capture" -> "v0-7-design-capture"). */
|
|
207
|
+
function milestoneSlug(name) {
|
|
208
|
+
return String(name)
|
|
209
|
+
.toLowerCase()
|
|
210
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
211
|
+
.replace(/^-+|-+$/g, '')
|
|
212
|
+
.slice(0, 60) || 'milestone';
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Full path to a milestone directory: .planning/milestones/<slug>/ */
|
|
216
|
+
function milestoneDir(milestoneName, root = repoRoot()) {
|
|
217
|
+
return path.join(planningDir(root), 'milestones', milestoneSlug(milestoneName));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Full path to a milestone DESIGN.md file. */
|
|
221
|
+
function milestoneDesignFile(milestoneName, root = repoRoot()) {
|
|
222
|
+
return path.join(milestoneDir(milestoneName, root), 'DESIGN.md');
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
- [ ] **Step 2: Add the four new names to the `module.exports` block at the bottom of the file**
|
|
227
|
+
|
|
228
|
+
Open the `module.exports = { ... }` block (currently around lines 134-152). Insert these four names after `summaryFile` and before `findPhaseDir`:
|
|
229
|
+
|
|
230
|
+
```javascript
|
|
231
|
+
summaryFile,
|
|
232
|
+
designFile,
|
|
233
|
+
milestoneSlug,
|
|
234
|
+
milestoneDir,
|
|
235
|
+
milestoneDesignFile,
|
|
236
|
+
findPhaseDir,
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
- [ ] **Step 3: Smoke-test the helpers**
|
|
240
|
+
|
|
241
|
+
Run: `node -e "const p=require('./lib/paths'); console.log(p.milestoneSlug('v0.7 Design Capture')); console.log(p.milestoneDir('v0.7 Design Capture','/tmp')); console.log(p.milestoneDesignFile('v0.7 Design Capture','/tmp')); console.log(p.designFile('99','/tmp'));"`
|
|
242
|
+
|
|
243
|
+
Expected (Windows path separators):
|
|
244
|
+
```
|
|
245
|
+
v0-7-design-capture
|
|
246
|
+
\tmp\.planning\milestones\v0-7-design-capture
|
|
247
|
+
\tmp\.planning\milestones\v0-7-design-capture\DESIGN.md
|
|
248
|
+
null
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
(`designFile` returns `null` because `/tmp` has no phase 99 dir — that's correct.)
|
|
252
|
+
|
|
253
|
+
- [ ] **Step 4: Confirm existing tests still pass**
|
|
254
|
+
|
|
255
|
+
Run: `npm test`
|
|
256
|
+
|
|
257
|
+
Expected: all suites pass — `Passed: N Failed: 0` per file. No path-helper changes should break anything.
|
|
258
|
+
|
|
259
|
+
- [ ] **Step 5: Commit**
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
git add lib/paths.js
|
|
263
|
+
git commit -m "cp(16-01): add designFile / milestoneSlug / milestoneDir / milestoneDesignFile helpers"
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Task 3: Write failing tests for path helpers (new test/unit-design.js)
|
|
269
|
+
|
|
270
|
+
**Files:**
|
|
271
|
+
- Create: `test/unit-design.js`
|
|
272
|
+
|
|
273
|
+
- [ ] **Step 1: Write the test file**
|
|
274
|
+
|
|
275
|
+
```javascript
|
|
276
|
+
'use strict';
|
|
277
|
+
|
|
278
|
+
const fs = require('fs');
|
|
279
|
+
const os = require('os');
|
|
280
|
+
const path = require('path');
|
|
281
|
+
const paths = require('../lib/paths');
|
|
282
|
+
|
|
283
|
+
let passed = 0, failed = 0;
|
|
284
|
+
const tracked = [];
|
|
285
|
+
|
|
286
|
+
function ok(label, cond, extra) {
|
|
287
|
+
if (cond) { passed++; console.log(` \u2713 ${label}`); return; }
|
|
288
|
+
failed++;
|
|
289
|
+
console.log(` \u2717 ${label}${extra ? ` (${extra})` : ''}`);
|
|
290
|
+
}
|
|
291
|
+
function mktmp(prefix) {
|
|
292
|
+
const d = fs.mkdtempSync(path.join(os.tmpdir(), `cp-${prefix}-`));
|
|
293
|
+
tracked.push(d);
|
|
294
|
+
return d;
|
|
295
|
+
}
|
|
296
|
+
function section(label) { console.log(`\n=== ${label} ===`); }
|
|
297
|
+
|
|
298
|
+
// =============================================================
|
|
299
|
+
section('lib/paths: milestoneSlug');
|
|
300
|
+
{
|
|
301
|
+
ok('basic slug', paths.milestoneSlug('v0.7 Design Capture') === 'v0-7-design-capture');
|
|
302
|
+
ok('trims', paths.milestoneSlug(' Spaces ') === 'spaces');
|
|
303
|
+
ok('punctuation collapses', paths.milestoneSlug('v1.0 — Final!') === 'v1-0-final');
|
|
304
|
+
ok('empty -> fallback', paths.milestoneSlug('') === 'milestone');
|
|
305
|
+
ok('only punctuation -> fallback', paths.milestoneSlug('---') === 'milestone');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
section('lib/paths: milestoneDir / milestoneDesignFile');
|
|
309
|
+
{
|
|
310
|
+
const root = mktmp('paths');
|
|
311
|
+
const md = paths.milestoneDir('v0.7 Design Capture', root);
|
|
312
|
+
ok('milestoneDir contains slug', md.endsWith(path.join('milestones', 'v0-7-design-capture')));
|
|
313
|
+
const mdf = paths.milestoneDesignFile('v0.7 Design Capture', root);
|
|
314
|
+
ok('milestoneDesignFile is DESIGN.md inside milestoneDir',
|
|
315
|
+
mdf === path.join(md, 'DESIGN.md'));
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
section('lib/paths: designFile resolves phase dir');
|
|
319
|
+
{
|
|
320
|
+
const root = mktmp('paths-df');
|
|
321
|
+
ok('null when no phase exists', paths.designFile('16', root) === null);
|
|
322
|
+
|
|
323
|
+
const phaseDir = path.join(root, '.planning', 'phases', '16-design-capture-infrastructure');
|
|
324
|
+
fs.mkdirSync(phaseDir, { recursive: true });
|
|
325
|
+
fs.writeFileSync(path.join(phaseDir, 'PLAN.md'), '');
|
|
326
|
+
|
|
327
|
+
const df = paths.designFile('16', root);
|
|
328
|
+
ok('resolves to DESIGN.md inside the phase dir',
|
|
329
|
+
df === path.join(phaseDir, 'DESIGN.md'));
|
|
330
|
+
|
|
331
|
+
const df2 = paths.designFile('16-design-capture-infrastructure', root);
|
|
332
|
+
ok('resolves by slug too', df2 === path.join(phaseDir, 'DESIGN.md'));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// =============================================================
|
|
336
|
+
// Cleanup
|
|
337
|
+
for (const d of tracked) fs.rmSync(d, { recursive: true, force: true });
|
|
338
|
+
console.log(`\nPassed: ${passed} Failed: ${failed}`);
|
|
339
|
+
process.exit(failed === 0 ? 0 : 1);
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
- [ ] **Step 2: Run the test**
|
|
343
|
+
|
|
344
|
+
Run: `node test/unit-design.js`
|
|
345
|
+
|
|
346
|
+
Expected: `Passed: 9 Failed: 0`
|
|
347
|
+
|
|
348
|
+
- [ ] **Step 3: Wire it into `npm test`**
|
|
349
|
+
|
|
350
|
+
Open `package.json`. Find the `test` script (one long string ending with `&& node test/unit-worktree.js`). Append ` && node test/unit-design.js` before the closing quote.
|
|
351
|
+
|
|
352
|
+
The full new test script should end with:
|
|
353
|
+
|
|
354
|
+
```
|
|
355
|
+
... && node test/unit-installers.js && node test/unit-worktree.js && node test/unit-design.js"
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
- [ ] **Step 4: Verify the full suite runs the new file**
|
|
359
|
+
|
|
360
|
+
Run (PowerShell): `npm test 2>&1 | Select-String "unit-design|=== lib/paths"`
|
|
361
|
+
|
|
362
|
+
Expected: at least one matching line (the section heading or a passing assertion from unit-design.js).
|
|
363
|
+
|
|
364
|
+
- [ ] **Step 5: Commit**
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
git add test/unit-design.js package.json
|
|
368
|
+
git commit -m "cp(16-01): add test/unit-design.js + wire into npm test"
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Task 4: Extend scaffoldPhase to write DESIGN.md
|
|
374
|
+
|
|
375
|
+
**Files:**
|
|
376
|
+
- Modify: `lib/lifecycle.js` (function `scaffoldPhase`, around lines 555-588)
|
|
377
|
+
|
|
378
|
+
- [ ] **Step 1: Add a failing test**
|
|
379
|
+
|
|
380
|
+
Append BEFORE the cleanup block (the `for (const d of tracked)` loop) of `test/unit-design.js`:
|
|
381
|
+
|
|
382
|
+
```javascript
|
|
383
|
+
// =============================================================
|
|
384
|
+
section('lib/lifecycle: scaffoldPhase emits DESIGN.md');
|
|
385
|
+
{
|
|
386
|
+
const lifecycle = require('../lib/lifecycle');
|
|
387
|
+
const root = mktmp('scaffold-design');
|
|
388
|
+
fs.mkdirSync(path.join(root, '.planning'), { recursive: true });
|
|
389
|
+
fs.writeFileSync(path.join(root, '.planning', 'ROADMAP.md'),
|
|
390
|
+
'# Roadmap\n\n## Phases\n\n### 🚧 Test Milestone (In Progress)\n');
|
|
391
|
+
|
|
392
|
+
const r = lifecycle.scaffoldPhase(root, '99', { name: 'design test', plans: 1 });
|
|
393
|
+
ok('scaffoldPhase ok', r.ok === true);
|
|
394
|
+
|
|
395
|
+
const planPath = path.join(r.phaseDir, 'PLAN.md');
|
|
396
|
+
ok('PLAN.md exists', fs.existsSync(planPath));
|
|
397
|
+
|
|
398
|
+
const designPath = path.join(r.phaseDir, 'DESIGN.md');
|
|
399
|
+
ok('DESIGN.md exists', fs.existsSync(designPath));
|
|
400
|
+
|
|
401
|
+
const design = fs.readFileSync(designPath, 'utf8');
|
|
402
|
+
ok('DESIGN.md has phase: "99" frontmatter', /^phase:\s*"99"\s*$/m.test(design));
|
|
403
|
+
ok('DESIGN.md has milestone: Test Milestone', /^milestone:\s*Test Milestone\s*$/m.test(design));
|
|
404
|
+
ok('DESIGN.md title substituted', /^# Design: Phase 99: design test\s*$/m.test(design));
|
|
405
|
+
ok('DESIGN.md has Status section', design.includes('## Status'));
|
|
406
|
+
ok('DESIGN.md has Architecture section', design.includes('## Architecture'));
|
|
407
|
+
ok('DESIGN.md has no unsubstituted placeholders',
|
|
408
|
+
!design.includes('{{') && !design.includes('}}'),
|
|
409
|
+
`found: ${(design.match(/\{\{[^}]+\}\}/g)||[]).join(',')}`);
|
|
410
|
+
|
|
411
|
+
const wrote = r.actions.find((a) => a.path === designPath);
|
|
412
|
+
ok('actions include DESIGN.md write', !!wrote && wrote.kind === 'write');
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
- [ ] **Step 2: Run to confirm it fails**
|
|
417
|
+
|
|
418
|
+
Run: `node test/unit-design.js`
|
|
419
|
+
|
|
420
|
+
Expected: 9 passes from prior + `✗ DESIGN.md exists` and following assertions fail.
|
|
421
|
+
|
|
422
|
+
- [ ] **Step 3: Implement DESIGN.md write in `scaffoldPhase`**
|
|
423
|
+
|
|
424
|
+
Open `lib/lifecycle.js`. Find function `scaffoldPhase` (around line 429). Locate the `const actions = [ ... ]` block (around lines 571-574) that contains the roadmap and PLAN.md write actions.
|
|
425
|
+
|
|
426
|
+
Replace:
|
|
427
|
+
|
|
428
|
+
```javascript
|
|
429
|
+
const actions = [
|
|
430
|
+
{ path: roadmapPath, before: roadmapBefore, after: roadmapAfter, kind: 'write' },
|
|
431
|
+
{ path: planPath, before: null, after: planRendered, kind: 'write' },
|
|
432
|
+
];
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
With:
|
|
436
|
+
|
|
437
|
+
```javascript
|
|
438
|
+
// DESIGN.md (v0.7): persistent phase-tier design doc, populated by SP
|
|
439
|
+
// brainstorming during /cp-plan-phase Step 3.5.
|
|
440
|
+
const designPath = path.join(phaseDirPath, 'DESIGN.md');
|
|
441
|
+
const designTemplate = paths.readTemplate('DESIGN.md');
|
|
442
|
+
const designRendered = designTemplate
|
|
443
|
+
.replace(/\{\{TIER_KEY\}\}/g, `phase: "${numStr}"`)
|
|
444
|
+
.replace(/\{\{MILESTONE_NAME\}\}/g, activeName)
|
|
445
|
+
.replace(/\{\{MILESTONE_SLUG\}\}/g, paths.milestoneSlug(activeName))
|
|
446
|
+
.replace(/\{\{PHASE_NUM\}\}/g, numStr)
|
|
447
|
+
.replace(/\{\{TITLE\}\}/g, `Phase ${numStr}: ${cleanName}`)
|
|
448
|
+
.replace(/\{\{DATE\}\}/g, todayStr);
|
|
449
|
+
|
|
450
|
+
const actions = [
|
|
451
|
+
{ path: roadmapPath, before: roadmapBefore, after: roadmapAfter, kind: 'write' },
|
|
452
|
+
{ path: planPath, before: null, after: planRendered, kind: 'write' },
|
|
453
|
+
{ path: designPath, before: null, after: designRendered, kind: 'write' },
|
|
454
|
+
];
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
- [ ] **Step 4: Run the test to verify it passes**
|
|
458
|
+
|
|
459
|
+
Run: `node test/unit-design.js`
|
|
460
|
+
|
|
461
|
+
Expected: `Passed: 18 Failed: 0` (9 prior + 9 new).
|
|
462
|
+
|
|
463
|
+
- [ ] **Step 5: Full suite green**
|
|
464
|
+
|
|
465
|
+
Run: `npm test`
|
|
466
|
+
|
|
467
|
+
Expected: all 20 test files green.
|
|
468
|
+
|
|
469
|
+
- [ ] **Step 6: Commit**
|
|
470
|
+
|
|
471
|
+
```bash
|
|
472
|
+
git add lib/lifecycle.js test/unit-design.js
|
|
473
|
+
git commit -m "cp(16-01): scaffoldPhase emits DESIGN.md alongside PLAN.md"
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## Task 5: Extend scaffoldMilestone to create milestones/<slug>/DESIGN.md
|
|
479
|
+
|
|
480
|
+
**Files:**
|
|
481
|
+
- Modify: `lib/lifecycle.js` (function `scaffoldMilestone`, around lines 359-410)
|
|
482
|
+
|
|
483
|
+
- [ ] **Step 1: Failing test**
|
|
484
|
+
|
|
485
|
+
Append to `test/unit-design.js` BEFORE the cleanup block:
|
|
486
|
+
|
|
487
|
+
```javascript
|
|
488
|
+
// =============================================================
|
|
489
|
+
section('lib/lifecycle: scaffoldMilestone emits milestones/<slug>/DESIGN.md');
|
|
490
|
+
{
|
|
491
|
+
const lifecycle = require('../lib/lifecycle');
|
|
492
|
+
const root = mktmp('scaffold-milestone-design');
|
|
493
|
+
fs.mkdirSync(path.join(root, '.planning'), { recursive: true });
|
|
494
|
+
fs.writeFileSync(path.join(root, '.planning', 'ROADMAP.md'),
|
|
495
|
+
'# Roadmap\n\n## Phases\n');
|
|
496
|
+
|
|
497
|
+
const r = lifecycle.scaffoldMilestone(root, 'v0.7 Design Capture');
|
|
498
|
+
ok('scaffoldMilestone ok', r.ok === true);
|
|
499
|
+
|
|
500
|
+
const roadmap = fs.readFileSync(path.join(root, '.planning', 'ROADMAP.md'), 'utf8');
|
|
501
|
+
ok('roadmap has milestone heading', roadmap.includes('v0.7 Design Capture'));
|
|
502
|
+
|
|
503
|
+
const mdPath = paths.milestoneDesignFile('v0.7 Design Capture', root);
|
|
504
|
+
ok('milestone DESIGN.md exists', fs.existsSync(mdPath));
|
|
505
|
+
|
|
506
|
+
const design = fs.readFileSync(mdPath, 'utf8');
|
|
507
|
+
ok('milestone DESIGN has milestone_slug frontmatter',
|
|
508
|
+
/^milestone_slug:\s*"v0-7-design-capture"\s*$/m.test(design));
|
|
509
|
+
ok('milestone DESIGN title substituted',
|
|
510
|
+
/^# Design: v0\.7 Design Capture\s*$/m.test(design));
|
|
511
|
+
ok('milestone DESIGN has no unsubstituted placeholders',
|
|
512
|
+
!design.includes('{{') && !design.includes('}}'));
|
|
513
|
+
|
|
514
|
+
const wrote = r.actions.find((a) => a.path === mdPath);
|
|
515
|
+
ok('actions include milestone DESIGN.md write', !!wrote && wrote.kind === 'write');
|
|
516
|
+
}
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
- [ ] **Step 2: Run to confirm it fails**
|
|
520
|
+
|
|
521
|
+
Run: `node test/unit-design.js`
|
|
522
|
+
|
|
523
|
+
Expected: 18 prior pass + `✗ milestone DESIGN.md exists` and following fail.
|
|
524
|
+
|
|
525
|
+
- [ ] **Step 3: Implement milestone DESIGN.md write in `scaffoldMilestone`**
|
|
526
|
+
|
|
527
|
+
Open `lib/lifecycle.js`. Find function `scaffoldMilestone` (around line 359). Locate the closing block (around lines 405-409):
|
|
528
|
+
|
|
529
|
+
```javascript
|
|
530
|
+
const actions = [{ path: roadmapPath, before, after, kind: 'write' }];
|
|
531
|
+
if (!dryRun) {
|
|
532
|
+
writeFile(roadmapPath, after);
|
|
533
|
+
}
|
|
534
|
+
return { ok: true, milestone: cleanName, status, actions, dryRun: dryRun || undefined };
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
Replace with:
|
|
539
|
+
|
|
540
|
+
```javascript
|
|
541
|
+
// Milestone DESIGN.md (v0.7): persistent milestone-tier design doc.
|
|
542
|
+
const todayStr = (options.today || new Date().toISOString().slice(0, 10));
|
|
543
|
+
const mdSlug = paths.milestoneSlug(cleanName);
|
|
544
|
+
const mdPath = paths.milestoneDesignFile(cleanName, root);
|
|
545
|
+
const designTemplate = paths.readTemplate('DESIGN.md');
|
|
546
|
+
const designRendered = designTemplate
|
|
547
|
+
.replace(/\{\{TIER_KEY\}\}/g, `milestone_slug: "${mdSlug}"`)
|
|
548
|
+
.replace(/\{\{MILESTONE_NAME\}\}/g, cleanName)
|
|
549
|
+
.replace(/\{\{MILESTONE_SLUG\}\}/g, mdSlug)
|
|
550
|
+
.replace(/\{\{PHASE_NUM\}\}/g, '')
|
|
551
|
+
.replace(/\{\{TITLE\}\}/g, cleanName)
|
|
552
|
+
.replace(/\{\{DATE\}\}/g, todayStr);
|
|
553
|
+
|
|
554
|
+
const actions = [
|
|
555
|
+
{ path: roadmapPath, before, after, kind: 'write' },
|
|
556
|
+
{ path: mdPath, before: null, after: designRendered, kind: 'write' },
|
|
557
|
+
];
|
|
558
|
+
if (!dryRun) {
|
|
559
|
+
fs.mkdirSync(path.dirname(mdPath), { recursive: true });
|
|
560
|
+
for (const a of actions) writeFile(a.path, a.after);
|
|
561
|
+
}
|
|
562
|
+
return { ok: true, milestone: cleanName, status, actions, dryRun: dryRun || undefined };
|
|
563
|
+
}
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
- [ ] **Step 4: Run tests**
|
|
567
|
+
|
|
568
|
+
Run: `node test/unit-design.js`
|
|
569
|
+
|
|
570
|
+
Expected: `Passed: 25 Failed: 0` (18 prior + 7 new).
|
|
571
|
+
|
|
572
|
+
- [ ] **Step 5: Full suite green**
|
|
573
|
+
|
|
574
|
+
Run: `npm test`
|
|
575
|
+
|
|
576
|
+
Expected: all green.
|
|
577
|
+
|
|
578
|
+
- [ ] **Step 6: Commit**
|
|
579
|
+
|
|
580
|
+
```bash
|
|
581
|
+
git add lib/lifecycle.js test/unit-design.js
|
|
582
|
+
git commit -m "cp(16-01): scaffoldMilestone creates milestones/<slug>/DESIGN.md"
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
---
|
|
586
|
+
|
|
587
|
+
## Task 6: Extend aggregateSummaries to surface phaseDesignRefs[]
|
|
588
|
+
|
|
589
|
+
**Files:**
|
|
590
|
+
- Modify: `lib/milestone.js` (function `aggregateSummaries`, around line 199; possibly `readSummaries` around line 161)
|
|
591
|
+
|
|
592
|
+
- [ ] **Step 1: Inspect the current `aggregateSummaries` shape**
|
|
593
|
+
|
|
594
|
+
Run: `node -e "const m=require('./lib/milestone'); const a=m.aggregateSummaries([]); console.log(JSON.stringify(a, null, 2));"`
|
|
595
|
+
|
|
596
|
+
Expected: an object — note the EXACT keys it returns (camelCase vs kebab-case, etc.) so the new field matches existing style.
|
|
597
|
+
|
|
598
|
+
- [ ] **Step 2: Confirm `readSummaries` exposes a phase path**
|
|
599
|
+
|
|
600
|
+
Run: `node -e "const s=require('fs').readFileSync('lib/milestone.js','utf8'); const fn=s.slice(s.indexOf('function readSummaries')); console.log(fn.slice(0, fn.indexOf('\\nfunction ')));"`
|
|
601
|
+
|
|
602
|
+
Note whether each summary object has a `phasePath`, `phaseDir`, `path`, or similar field. If none exposes the phase dir, edit `readSummaries` to add `phasePath: <dirOfThisSummary>` to each returned object.
|
|
603
|
+
|
|
604
|
+
- [ ] **Step 3: Add phaseDesignRefs gathering in `aggregateSummaries`**
|
|
605
|
+
|
|
606
|
+
Open `lib/milestone.js`. Find `function aggregateSummaries(summaries)` (around line 199). At the very end, BEFORE the `return` statement that builds the result object, add:
|
|
607
|
+
|
|
608
|
+
```javascript
|
|
609
|
+
// v0.7: scan each summary's phase dir for a DESIGN.md and emit a ref
|
|
610
|
+
// (deduped by phase since multiple plans share one DESIGN.md per phase).
|
|
611
|
+
const path = require('path');
|
|
612
|
+
const fs = require('fs');
|
|
613
|
+
const _designSeen = new Set();
|
|
614
|
+
const phaseDesignRefs = [];
|
|
615
|
+
for (const s of summaries) {
|
|
616
|
+
if (!s) continue;
|
|
617
|
+
const phasePath = s.phasePath || s.phaseDir || s.path || null;
|
|
618
|
+
if (!phasePath) continue;
|
|
619
|
+
if (_designSeen.has(s.phase)) continue;
|
|
620
|
+
const designPath = path.join(phasePath, 'DESIGN.md');
|
|
621
|
+
if (fs.existsSync(designPath)) {
|
|
622
|
+
_designSeen.add(s.phase);
|
|
623
|
+
phaseDesignRefs.push({ phase: s.phase, path: designPath });
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
Add `phaseDesignRefs` to the returned object (as a sibling of the existing fields like `subsystems`, `tags`, etc.). Example:
|
|
629
|
+
|
|
630
|
+
```javascript
|
|
631
|
+
return {
|
|
632
|
+
subsystems,
|
|
633
|
+
tags,
|
|
634
|
+
// ... existing fields ...
|
|
635
|
+
phaseDesignRefs,
|
|
636
|
+
};
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
- [ ] **Step 4: Add a test for both populated and empty cases**
|
|
640
|
+
|
|
641
|
+
Append to `test/unit-design.js` BEFORE the cleanup block:
|
|
642
|
+
|
|
643
|
+
```javascript
|
|
644
|
+
// =============================================================
|
|
645
|
+
section('lib/milestone: aggregateSummaries surfaces phaseDesignRefs[]');
|
|
646
|
+
{
|
|
647
|
+
const milestone = require('../lib/milestone');
|
|
648
|
+
const root = mktmp('agg-design');
|
|
649
|
+
const phasePath = path.join(root, '.planning', 'phases', '16-test-phase');
|
|
650
|
+
fs.mkdirSync(phasePath, { recursive: true });
|
|
651
|
+
fs.writeFileSync(path.join(phasePath, 'DESIGN.md'), '# Design: Phase 16\n');
|
|
652
|
+
|
|
653
|
+
const summaries = [
|
|
654
|
+
{ phase: '16', plan: '01', phasePath, data: { subsystem: 'tooling', 'key-decisions': ['dec1'] } },
|
|
655
|
+
{ phase: '16', plan: '02', phasePath, data: { subsystem: 'tooling', 'key-decisions': ['dec2'] } },
|
|
656
|
+
];
|
|
657
|
+
|
|
658
|
+
const agg = milestone.aggregateSummaries(summaries);
|
|
659
|
+
ok('phaseDesignRefs key exists', Array.isArray(agg.phaseDesignRefs));
|
|
660
|
+
ok('phaseDesignRefs deduped to 1 entry per phase', agg.phaseDesignRefs.length === 1);
|
|
661
|
+
ok('phaseDesignRefs[0].phase = "16"',
|
|
662
|
+
agg.phaseDesignRefs[0] && agg.phaseDesignRefs[0].phase === '16');
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
section('lib/milestone: aggregateSummaries empty when no DESIGN.md');
|
|
666
|
+
{
|
|
667
|
+
const milestone = require('../lib/milestone');
|
|
668
|
+
const root = mktmp('agg-nodes');
|
|
669
|
+
const phasePath = path.join(root, '.planning', 'phases', '17-no-design');
|
|
670
|
+
fs.mkdirSync(phasePath, { recursive: true });
|
|
671
|
+
// No DESIGN.md.
|
|
672
|
+
const summaries = [{ phase: '17', plan: '01', phasePath, data: {} }];
|
|
673
|
+
const agg = milestone.aggregateSummaries(summaries);
|
|
674
|
+
ok('phaseDesignRefs empty when no DESIGN', agg.phaseDesignRefs.length === 0);
|
|
675
|
+
}
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
- [ ] **Step 5: Run test**
|
|
679
|
+
|
|
680
|
+
Run: `node test/unit-design.js`
|
|
681
|
+
|
|
682
|
+
Expected: `Passed: 29 Failed: 0` (25 prior + 4 new).
|
|
683
|
+
|
|
684
|
+
If the test fails with `phaseDesignRefs key exists`: check that the loop in Step 3 looks up the correct field name (`s.phasePath` vs `s.phaseDir`). Update both the test fixture and the aggregator to use whatever `readSummaries` actually exposes.
|
|
685
|
+
|
|
686
|
+
- [ ] **Step 6: Full suite**
|
|
687
|
+
|
|
688
|
+
Run: `npm test`
|
|
689
|
+
|
|
690
|
+
Expected: all green.
|
|
691
|
+
|
|
692
|
+
- [ ] **Step 7: Commit**
|
|
693
|
+
|
|
694
|
+
```bash
|
|
695
|
+
git add lib/milestone.js test/unit-design.js
|
|
696
|
+
git commit -m "cp(16-01): aggregateSummaries surfaces phaseDesignRefs[]"
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
---
|
|
700
|
+
|
|
701
|
+
## Task 7: Promote MILESTONE-CONTEXT.md into milestone DESIGN.md at close
|
|
702
|
+
|
|
703
|
+
**Files:**
|
|
704
|
+
- Modify: `lib/milestone.js` — add `promoteMilestoneContext()` helper
|
|
705
|
+
- Modify: `lib/lifecycle.js` — call it from `completeMilestone()` around line 827
|
|
706
|
+
|
|
707
|
+
- [ ] **Step 1: Add the helper to `lib/milestone.js`**
|
|
708
|
+
|
|
709
|
+
Open `lib/milestone.js`. After the closing `}` of `aggregateSummaries` (around line 270, before `renderDigest`), add:
|
|
710
|
+
|
|
711
|
+
```javascript
|
|
712
|
+
/**
|
|
713
|
+
* Promote .planning/MILESTONE-CONTEXT.md (transient) into the milestone-tier
|
|
714
|
+
* DESIGN.md as a "Brainstorm transcript" appendix. Returns { action, path,
|
|
715
|
+
* after, contextPath } or null if there's nothing to promote.
|
|
716
|
+
*
|
|
717
|
+
* Caller is responsible for writing `after` to `path` and deleting
|
|
718
|
+
* `contextPath` (so cp can do both inside a writeBatch for atomicity).
|
|
719
|
+
*/
|
|
720
|
+
function promoteMilestoneContext(root, milestoneName, options = {}) {
|
|
721
|
+
const fs = require('fs');
|
|
722
|
+
const path = require('path');
|
|
723
|
+
const paths = require('./paths');
|
|
724
|
+
|
|
725
|
+
const contextPath = path.join(paths.planningDir(root), 'MILESTONE-CONTEXT.md');
|
|
726
|
+
if (!fs.existsSync(contextPath)) return null;
|
|
727
|
+
|
|
728
|
+
const body = fs.readFileSync(contextPath, 'utf8').trim();
|
|
729
|
+
if (!body) return null;
|
|
730
|
+
|
|
731
|
+
const designPath = paths.milestoneDesignFile(milestoneName, root);
|
|
732
|
+
const exists = fs.existsSync(designPath);
|
|
733
|
+
|
|
734
|
+
let after;
|
|
735
|
+
if (exists) {
|
|
736
|
+
const current = fs.readFileSync(designPath, 'utf8').replace(/\n+$/, '');
|
|
737
|
+
after = `${current}\n\n## Brainstorm transcript\n\n${body}\n`;
|
|
738
|
+
} else {
|
|
739
|
+
after = [
|
|
740
|
+
'---',
|
|
741
|
+
`milestone_slug: "${paths.milestoneSlug(milestoneName)}"`,
|
|
742
|
+
`milestone: ${milestoneName}`,
|
|
743
|
+
'status: accepted',
|
|
744
|
+
`created: ${options.today || new Date().toISOString().slice(0, 10)}`,
|
|
745
|
+
'---',
|
|
746
|
+
'',
|
|
747
|
+
`# Design: ${milestoneName}`,
|
|
748
|
+
'',
|
|
749
|
+
'## Brainstorm transcript',
|
|
750
|
+
'',
|
|
751
|
+
body,
|
|
752
|
+
'',
|
|
753
|
+
].join('\n');
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
return { action: exists ? 'appended' : 'created', path: designPath, after, contextPath };
|
|
757
|
+
}
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
- [ ] **Step 2: Export it**
|
|
761
|
+
|
|
762
|
+
In the `module.exports = { ... }` block at the bottom of `lib/milestone.js`, add `promoteMilestoneContext`.
|
|
763
|
+
|
|
764
|
+
- [ ] **Step 3: Wire into `completeMilestone`**
|
|
765
|
+
|
|
766
|
+
Open `lib/lifecycle.js`. Find `function completeMilestone` (around line 763). Find the comment `// 3. Delete MILESTONE-CONTEXT.md if present` (around line 827).
|
|
767
|
+
|
|
768
|
+
Find the lines that push a `delete` action for `milestoneContextPath`. Replace those lines with:
|
|
769
|
+
|
|
770
|
+
```javascript
|
|
771
|
+
// 3. Promote MILESTONE-CONTEXT.md into the milestone DESIGN.md, then
|
|
772
|
+
// delete the transient file (v0.7 design-capture).
|
|
773
|
+
const promotion = milestone.promoteMilestoneContext(root, milestoneName, { today });
|
|
774
|
+
if (promotion) {
|
|
775
|
+
actions.push({ path: promotion.path, before: null, after: promotion.after, kind: 'write' });
|
|
776
|
+
actions.push({ path: promotion.contextPath, kind: 'delete' });
|
|
777
|
+
} else if (fs.existsSync(milestoneContextPath)) {
|
|
778
|
+
actions.push({ path: milestoneContextPath, kind: 'delete' });
|
|
779
|
+
}
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
(Confirm `milestone` is already imported at the top of `lib/lifecycle.js` — search for `require('./milestone')`. It should be.)
|
|
783
|
+
|
|
784
|
+
- [ ] **Step 4: Test promotion**
|
|
785
|
+
|
|
786
|
+
Append to `test/unit-design.js` BEFORE the cleanup block:
|
|
787
|
+
|
|
788
|
+
```javascript
|
|
789
|
+
// =============================================================
|
|
790
|
+
section('lib/milestone: promoteMilestoneContext');
|
|
791
|
+
{
|
|
792
|
+
const milestoneLib = require('../lib/milestone');
|
|
793
|
+
const root = mktmp('promote');
|
|
794
|
+
fs.mkdirSync(path.join(root, '.planning'), { recursive: true });
|
|
795
|
+
|
|
796
|
+
ok('null when no context', milestoneLib.promoteMilestoneContext(root, 'X') === null);
|
|
797
|
+
|
|
798
|
+
fs.writeFileSync(path.join(root, '.planning', 'MILESTONE-CONTEXT.md'), ' \n');
|
|
799
|
+
ok('null when context empty', milestoneLib.promoteMilestoneContext(root, 'X') === null);
|
|
800
|
+
|
|
801
|
+
fs.writeFileSync(path.join(root, '.planning', 'MILESTONE-CONTEXT.md'),
|
|
802
|
+
'# Brainstorm\n\nQ: what?\nA: this.\n');
|
|
803
|
+
const r1 = milestoneLib.promoteMilestoneContext(root, 'v0.7 Test');
|
|
804
|
+
ok('action = created', r1 && r1.action === 'created');
|
|
805
|
+
ok('after has Brainstorm transcript heading', r1.after.includes('## Brainstorm transcript'));
|
|
806
|
+
ok('after has Q&A body', r1.after.includes('Q: what?'));
|
|
807
|
+
ok('after has slug frontmatter', r1.after.includes('milestone_slug: "v0-7-test"'));
|
|
808
|
+
|
|
809
|
+
fs.mkdirSync(path.dirname(r1.path), { recursive: true });
|
|
810
|
+
fs.writeFileSync(r1.path, '---\nmilestone: v0.7 Test\n---\n\n# Design: v0.7 Test\n\n## Status\nAccepted\n');
|
|
811
|
+
const r2 = milestoneLib.promoteMilestoneContext(root, 'v0.7 Test');
|
|
812
|
+
ok('action = appended', r2 && r2.action === 'appended');
|
|
813
|
+
ok('appended preserves Status section', r2.after.includes('## Status') && r2.after.includes('Accepted'));
|
|
814
|
+
ok('appended adds Brainstorm transcript', r2.after.includes('## Brainstorm transcript'));
|
|
815
|
+
}
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
- [ ] **Step 5: Run test**
|
|
819
|
+
|
|
820
|
+
Run: `node test/unit-design.js`
|
|
821
|
+
|
|
822
|
+
Expected: `Passed: 38 Failed: 0` (29 prior + 9 new).
|
|
823
|
+
|
|
824
|
+
- [ ] **Step 6: Confirm dryrun-complete-milestone.js still works**
|
|
825
|
+
|
|
826
|
+
Run: `node test/dryrun-complete-milestone.js`
|
|
827
|
+
|
|
828
|
+
Expected: passes. If action-count assertions fail, update them to account for the new `write DESIGN.md` action that now appears alongside the existing `delete CONTEXT` action.
|
|
829
|
+
|
|
830
|
+
- [ ] **Step 7: Full suite green**
|
|
831
|
+
|
|
832
|
+
Run: `npm test`
|
|
833
|
+
|
|
834
|
+
- [ ] **Step 8: Commit**
|
|
835
|
+
|
|
836
|
+
```bash
|
|
837
|
+
git add lib/milestone.js lib/lifecycle.js test/unit-design.js
|
|
838
|
+
git commit -m "cp(16-01): promote MILESTONE-CONTEXT.md into milestone DESIGN at close"
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
---
|
|
842
|
+
|
|
843
|
+
## Task 8: Update cp-new-milestone and cp-plan-phase skill docs
|
|
844
|
+
|
|
845
|
+
**Files:**
|
|
846
|
+
- Modify: `.github/skills/cp-new-milestone/SKILL.md` (Step 3, around lines 53-65)
|
|
847
|
+
- Modify: `.github/skills/cp-plan-phase/SKILL.md` (insert Step 3.5 after current Step 3)
|
|
848
|
+
|
|
849
|
+
- [ ] **Step 1: Check whether commands/ has canonical copies**
|
|
850
|
+
|
|
851
|
+
Run: `Get-ChildItem commands\ -ErrorAction SilentlyContinue | Where-Object Name -Match 'cp-new-milestone|cp-plan-phase'`
|
|
852
|
+
|
|
853
|
+
If files exist in `commands/`, those are the canonical source — edit THEM and re-run `node bin/cp.js install copilot` to propagate to `.github/skills/`. If `commands/` has no copies, edit `.github/skills/...SKILL.md` directly (those edits are local since `.github/skills/` is gitignored, but they're still effective for this machine).
|
|
854
|
+
|
|
855
|
+
- [ ] **Step 2: Update cp-new-milestone Step 3**
|
|
856
|
+
|
|
857
|
+
Open `.github/skills/cp-new-milestone/SKILL.md`. Find `## Step 3 — Delegate brainstorming`. Replace its body (the paragraphs before `## Step 4 — Update PROJECT.md`) with:
|
|
858
|
+
|
|
859
|
+
```markdown
|
|
860
|
+
Invoke the provider's `brainstorm` skill (e.g. Superpowers' `brainstorming`),
|
|
861
|
+
passing the milestone name + the user's stated intent + a short summary of
|
|
862
|
+
the project context (Core Value, last 3 validated requirements).
|
|
863
|
+
|
|
864
|
+
Goal of the brainstorm: a clear, scoped specification for the milestone.
|
|
865
|
+
|
|
866
|
+
**v0.7 design-capture (TWO destinations):**
|
|
867
|
+
1. Save the FULL brainstorm transcript (verbatim Q&A) to
|
|
868
|
+
`.planning/MILESTONE-CONTEXT.md`. This is the unedited working file.
|
|
869
|
+
2. Save the structured ADR summary (Status / Context / Decision /
|
|
870
|
+
Consequences / Architecture / etc.) to
|
|
871
|
+
`.planning/milestones/<slug>/DESIGN.md`. `cp scaffold-milestone`
|
|
872
|
+
already created the empty template — SP brainstorming overwrites it
|
|
873
|
+
with the populated version using its `path:` override parameter.
|
|
874
|
+
|
|
875
|
+
At `cp complete-milestone`, MILESTONE-CONTEXT.md is automatically
|
|
876
|
+
promoted into the milestone DESIGN.md as a "Brainstorm transcript"
|
|
877
|
+
appendix and the transient file is deleted.
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
- [ ] **Step 3: Insert Step 3.5 in cp-plan-phase**
|
|
881
|
+
|
|
882
|
+
Open `.github/skills/cp-plan-phase/SKILL.md`. Find `## Step 3 — Resolve the plan skill`. Immediately BEFORE `## Step 4 — Delegate to the plan skill`, insert:
|
|
883
|
+
|
|
884
|
+
```markdown
|
|
885
|
+
## Step 3.5 — Delegate to brainstorming for DESIGN.md (v0.7)
|
|
886
|
+
|
|
887
|
+
Before invoking the plan skill, invoke the provider's `brainstorm` skill
|
|
888
|
+
to fill in the phase-tier DESIGN.md.
|
|
889
|
+
|
|
890
|
+
- `cp scaffold-phase` already created an empty template at
|
|
891
|
+
`.planning/phases/{phase-dir}/DESIGN.md`.
|
|
892
|
+
- Pass to the brainstorm skill:
|
|
893
|
+
- The phase Goal, Success Criteria, and Requirements (from ROADMAP.md)
|
|
894
|
+
- The milestone DESIGN.md at `.planning/milestones/<slug>/DESIGN.md` as
|
|
895
|
+
context (so the phase design stays consistent with the milestone)
|
|
896
|
+
- A `path:` override pointing to
|
|
897
|
+
`.planning/phases/{phase-dir}/DESIGN.md` so SP writes there directly
|
|
898
|
+
instead of `docs/superpowers/specs/...`
|
|
899
|
+
- Do NOT touch frontmatter keys cp populated (`phase`, `milestone`,
|
|
900
|
+
`status`, `created`). SP fills in Status, Context, Decision,
|
|
901
|
+
Consequences, Architecture, Components, Data Flow, Error Handling,
|
|
902
|
+
Testing Strategy, Alternatives Considered, Open Questions, References.
|
|
903
|
+
|
|
904
|
+
If the brainstorm skill is unavailable (provider = manual), skip this
|
|
905
|
+
step — DESIGN.md stays empty and the user can fill it later.
|
|
906
|
+
|
|
907
|
+
The DESIGN.md becomes a context input to the plan skill in Step 4.
|
|
908
|
+
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
- [ ] **Step 4: Add DESIGN.md to Step 4's "Pass to the plan skill" list**
|
|
912
|
+
|
|
913
|
+
Still in `cp-plan-phase/SKILL.md`, find `## Step 4 — Delegate to the plan skill`. In the bullet list under "Pass to the plan skill:", add as the FIRST bullet:
|
|
914
|
+
|
|
915
|
+
```markdown
|
|
916
|
+
- The phase DESIGN.md at `.planning/phases/{phase-dir}/DESIGN.md` (v0.7)
|
|
917
|
+
as the architectural source-of-truth; plan tasks should align with the
|
|
918
|
+
Decision and Components sections.
|
|
919
|
+
```
|
|
920
|
+
|
|
921
|
+
- [ ] **Step 5: Sanity-check edits**
|
|
922
|
+
|
|
923
|
+
Run: `node -e "const fs=require('fs'); const f1=fs.readFileSync('.github/skills/cp-new-milestone/SKILL.md','utf8'); const f2=fs.readFileSync('.github/skills/cp-plan-phase/SKILL.md','utf8'); if (!f1.includes('MILESTONE-CONTEXT.md') || !f1.includes('milestones/<slug>/DESIGN.md')) { console.error('cp-new-milestone Step 3 missing'); process.exit(1); } if (!f2.includes('## Step 3.5') || !f2.includes('DESIGN.md')) { console.error('cp-plan-phase Step 3.5 missing'); process.exit(1); } console.log('OK');"`
|
|
924
|
+
|
|
925
|
+
Expected: `OK`
|
|
926
|
+
|
|
927
|
+
- [ ] **Step 6: Commit**
|
|
928
|
+
|
|
929
|
+
If you edited `.github/skills/...` directly (gitignored), no commit. If you edited `commands/cp-*.md`:
|
|
930
|
+
|
|
931
|
+
```bash
|
|
932
|
+
git add commands/cp-new-milestone.md commands/cp-plan-phase.md
|
|
933
|
+
git commit -m "cp(16-01): update cp-new-milestone Step 3 + add cp-plan-phase Step 3.5 for DESIGN.md"
|
|
934
|
+
```
|
|
935
|
+
|
|
936
|
+
---
|
|
937
|
+
|
|
938
|
+
## Task 9: Run coverage and confirm thresholds
|
|
939
|
+
|
|
940
|
+
- [ ] **Step 1: Run the coverage gate**
|
|
941
|
+
|
|
942
|
+
Run: `npm run coverage:ci`
|
|
943
|
+
|
|
944
|
+
Expected: tests all pass, c8 summary shows lines ≥85% and branches ≥75%. New `lib/paths.js` helpers and `lib/milestone.js` extensions are exercised by `test/unit-design.js`, so coverage should hold.
|
|
945
|
+
|
|
946
|
+
- [ ] **Step 2: If coverage drops, add targeted tests in unit-design.js to cover the missed branches; commit**
|
|
947
|
+
|
|
948
|
+
```bash
|
|
949
|
+
git add test/unit-design.js
|
|
950
|
+
git commit -m "cp(16-01): backfill coverage for promoteMilestoneContext edge cases"
|
|
951
|
+
```
|
|
952
|
+
|
|
953
|
+
---
|
|
954
|
+
|
|
955
|
+
## Task 10: Dogfood — backfill DESIGN.md for phase 16 and milestone v0.7
|
|
956
|
+
|
|
957
|
+
**Files:**
|
|
958
|
+
- Create: `.planning/phases/16-design-capture-infrastructure/DESIGN.md` (manually — scaffold-phase ran before this code existed)
|
|
959
|
+
- Create: `.planning/milestones/v0-7-design-capture/DESIGN.md` (likewise)
|
|
960
|
+
|
|
961
|
+
- [ ] **Step 1: Run scaffold-phase against a throwaway to confirm template renders**
|
|
962
|
+
|
|
963
|
+
Run: `node bin/cp.js scaffold-phase 99 --name "smoke test" --dry-run`
|
|
964
|
+
|
|
965
|
+
Expected: dryrun output lists THREE actions (ROADMAP edit, PLAN.md write, DESIGN.md write).
|
|
966
|
+
|
|
967
|
+
- [ ] **Step 2: Backfill the phase-16 DESIGN.md**
|
|
968
|
+
|
|
969
|
+
Run (PowerShell):
|
|
970
|
+
|
|
971
|
+
```powershell
|
|
972
|
+
node -e @"
|
|
973
|
+
const fs = require('fs');
|
|
974
|
+
const path = require('path');
|
|
975
|
+
const paths = require('./lib/paths');
|
|
976
|
+
const root = process.cwd();
|
|
977
|
+
const tpl = paths.readTemplate('DESIGN.md');
|
|
978
|
+
const rendered = tpl
|
|
979
|
+
.replace(/\{\{TIER_KEY\}\}/g, 'phase: \"16\"')
|
|
980
|
+
.replace(/\{\{MILESTONE_NAME\}\}/g, 'v0.7 Design Capture')
|
|
981
|
+
.replace(/\{\{MILESTONE_SLUG\}\}/g, 'v0-7-design-capture')
|
|
982
|
+
.replace(/\{\{PHASE_NUM\}\}/g, '16')
|
|
983
|
+
.replace(/\{\{TITLE\}\}/g, 'Phase 16: design capture infrastructure')
|
|
984
|
+
.replace(/\{\{DATE\}\}/g, new Date().toISOString().slice(0,10));
|
|
985
|
+
const out = path.join(root, '.planning', 'phases', '16-design-capture-infrastructure', 'DESIGN.md');
|
|
986
|
+
if (!fs.existsSync(out)) { fs.writeFileSync(out, rendered); console.log('wrote', out); } else { console.log('exists', out); }
|
|
987
|
+
"@
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
- [ ] **Step 3: Backfill the milestone v0.7 DESIGN.md**
|
|
991
|
+
|
|
992
|
+
Run (PowerShell):
|
|
993
|
+
|
|
994
|
+
```powershell
|
|
995
|
+
node -e @"
|
|
996
|
+
const fs = require('fs');
|
|
997
|
+
const paths = require('./lib/paths');
|
|
998
|
+
const tpl = paths.readTemplate('DESIGN.md');
|
|
999
|
+
const root = process.cwd();
|
|
1000
|
+
const slug = paths.milestoneSlug('v0.7 Design Capture');
|
|
1001
|
+
const rendered = tpl
|
|
1002
|
+
.replace(/\{\{TIER_KEY\}\}/g, 'milestone_slug: \"' + slug + '\"')
|
|
1003
|
+
.replace(/\{\{MILESTONE_NAME\}\}/g, 'v0.7 Design Capture')
|
|
1004
|
+
.replace(/\{\{MILESTONE_SLUG\}\}/g, slug)
|
|
1005
|
+
.replace(/\{\{PHASE_NUM\}\}/g, '')
|
|
1006
|
+
.replace(/\{\{TITLE\}\}/g, 'v0.7 Design Capture')
|
|
1007
|
+
.replace(/\{\{DATE\}\}/g, new Date().toISOString().slice(0,10));
|
|
1008
|
+
const out = paths.milestoneDesignFile('v0.7 Design Capture', root);
|
|
1009
|
+
fs.mkdirSync(require('path').dirname(out), { recursive: true });
|
|
1010
|
+
if (!fs.existsSync(out)) { fs.writeFileSync(out, rendered); console.log('wrote', out); } else { console.log('exists', out); }
|
|
1011
|
+
"@
|
|
1012
|
+
```
|
|
1013
|
+
|
|
1014
|
+
- [ ] **Step 4: Backfill content from the spec**
|
|
1015
|
+
|
|
1016
|
+
Open `.planning/milestones/v0-7-design-capture/DESIGN.md`. Replace the empty template body (keep cp-managed frontmatter at the top) with the content from `docs/superpowers/specs/2026-05-20-v0-7-design-capture-design.md`.
|
|
1017
|
+
|
|
1018
|
+
- [ ] **Step 5: No commit — `.planning/` is gitignored in this repo**
|
|
1019
|
+
|
|
1020
|
+
Confirm via `git status .planning/` (expect empty output).
|
|
1021
|
+
|
|
1022
|
+
---
|
|
1023
|
+
|
|
1024
|
+
## Verification — End-of-plan checklist
|
|
1025
|
+
|
|
1026
|
+
- [ ] `npm test` — all 20 test files green
|
|
1027
|
+
- [ ] `npm run coverage:ci` — lines ≥85%, branches ≥75%
|
|
1028
|
+
- [ ] `node bin/cp.js scaffold-phase 99 --name "smoke" --dry-run` shows DESIGN.md in actions
|
|
1029
|
+
- [ ] `node bin/cp.js scaffold-milestone "smoke milestone" --dry-run` shows milestone DESIGN.md in actions
|
|
1030
|
+
- [ ] `node bin/cp.js complete-milestone --dry-run` (against a fixture with MILESTONE-CONTEXT.md) shows PROMOTE write + delete actions
|
|
1031
|
+
- [ ] `templates/DESIGN.md` has Status, Context, Decision, Consequences, Architecture, Components, Data Flow, Error Handling, Testing Strategy, Alternatives Considered, Open Questions, References
|
|
1032
|
+
- [ ] `lib/paths.js` exports `designFile`, `milestoneSlug`, `milestoneDir`, `milestoneDesignFile`
|
|
1033
|
+
- [ ] `lib/milestone.js` exports `promoteMilestoneContext`; `aggregateSummaries(...)` includes `phaseDesignRefs`
|
|
1034
|
+
- [ ] `.github/skills/cp-new-milestone/SKILL.md` Step 3 mentions both destinations
|
|
1035
|
+
- [ ] `.github/skills/cp-plan-phase/SKILL.md` has new Step 3.5
|
|
1036
|
+
|
|
1037
|
+
After all boxes ticked → tick plan 16-01 via `cp tick 16-01 --no-commit`, write SUMMARY via `cp write-summary 16-01 --from <json>`, then proceed to plan 16-02 (REVIEW-LOG.md infrastructure).
|
|
1038
|
+
|
|
1039
|
+
</tasks>
|
|
1040
|
+
|
|
1041
|
+
<verification>
|
|
1042
|
+
Before declaring plan complete:
|
|
1043
|
+
- [ ] `npm test` — all 20 test files green (was 19; unit-design.js added)
|
|
1044
|
+
- [ ] `npm run coverage:ci` — passes 85L/75B gate
|
|
1045
|
+
- [ ] `node bin/cp.js scaffold-phase 99 --name smoke --dry-run` lists DESIGN.md
|
|
1046
|
+
- [ ] `node bin/cp.js scaffold-milestone "smoke" --dry-run` lists milestone DESIGN.md
|
|
1047
|
+
</verification>
|
|
1048
|
+
|
|
1049
|
+
<success_criteria>
|
|
1050
|
+
- All tasks completed; per-task commits in git log
|
|
1051
|
+
- All verification checks pass
|
|
1052
|
+
- No errors or warnings introduced
|
|
1053
|
+
- templates/DESIGN.md is the ADR + SP union from the spec
|
|
1054
|
+
- lib/paths.js exports four new helpers; existing tests unchanged
|
|
1055
|
+
- lib/lifecycle.scaffoldPhase + scaffoldMilestone emit DESIGN.md actions
|
|
1056
|
+
- lib/milestone.aggregateSummaries surfaces phaseDesignRefs[]
|
|
1057
|
+
- lib/lifecycle.completeMilestone promotes MILESTONE-CONTEXT.md before deleting it
|
|
1058
|
+
- SUMMARY.md required key-decisions enforcement deferred to 16-03 (out of scope)
|
|
1059
|
+
- REVIEW-LOG.md infrastructure deferred to 16-02 (out of scope)
|
|
1060
|
+
</success_criteria>
|
|
1061
|
+
|
|
1062
|
+
<output>
|
|
1063
|
+
After completion, create `.planning/phases/16-design-capture-infrastructure/16-01-SUMMARY.md` via `cp write-summary 16-01 --from <json>`.
|
|
1064
|
+
</output>
|