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.
Files changed (86) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +454 -0
  3. package/bin/commands/_helpers.js +53 -0
  4. package/bin/commands/_usage.js +67 -0
  5. package/bin/commands/capture.js +46 -0
  6. package/bin/commands/codebase-status.js +41 -0
  7. package/bin/commands/complete-milestone.js +57 -0
  8. package/bin/commands/config.js +70 -0
  9. package/bin/commands/doctor.js +139 -0
  10. package/bin/commands/gsd-import.js +90 -0
  11. package/bin/commands/inbox.js +81 -0
  12. package/bin/commands/index.js +33 -0
  13. package/bin/commands/init.js +87 -0
  14. package/bin/commands/install.js +43 -0
  15. package/bin/commands/scaffold-codebase.js +53 -0
  16. package/bin/commands/scaffold-milestone.js +58 -0
  17. package/bin/commands/scaffold-phase.js +65 -0
  18. package/bin/commands/status.js +42 -0
  19. package/bin/commands/statusline.js +108 -0
  20. package/bin/commands/tick.js +49 -0
  21. package/bin/commands/version.js +9 -0
  22. package/bin/commands/worktree.js +218 -0
  23. package/bin/commands/write-summary.js +54 -0
  24. package/bin/cp.cmd +2 -0
  25. package/bin/cp.js +54 -0
  26. package/commands/cp/capture.md +107 -0
  27. package/commands/cp/complete-milestone.md +166 -0
  28. package/commands/cp/execute-phase.md +220 -0
  29. package/commands/cp/map-codebase.md +211 -0
  30. package/commands/cp/new-milestone.md +136 -0
  31. package/commands/cp/new-project.md +132 -0
  32. package/commands/cp/plan-phase.md +195 -0
  33. package/commands/cp/progress.md +147 -0
  34. package/commands/cp/quick.md +104 -0
  35. package/commands/cp/resume.md +125 -0
  36. package/commands/cp/write-summary.md +33 -0
  37. package/docs/MIGRATION-v0.5.md +140 -0
  38. package/docs/architecture.md +189 -0
  39. package/docs/superpowers/plans/2026-05-20-v0-7-plan-16-01-design-md-infrastructure.md +1064 -0
  40. package/docs/superpowers/plans/2026-05-20-v0-7-plan-16-02-review-log-infrastructure.md +418 -0
  41. package/docs/superpowers/plans/2026-05-20-v0-7-plan-16-03-key-decisions-hard-block.md +295 -0
  42. package/docs/superpowers/specs/2026-05-20-generic-provider-harness-detection-design.md +380 -0
  43. package/docs/superpowers/specs/2026-05-20-v0-7-design-capture-design.md +400 -0
  44. package/docs/writing-providers.md +76 -0
  45. package/install/aider.js +204 -0
  46. package/install/claude.js +116 -0
  47. package/install/common.js +65 -0
  48. package/install/copilot.js +86 -0
  49. package/install/cursor.js +120 -0
  50. package/install/echo-provider.js +50 -0
  51. package/lib/codebase-mapper.js +169 -0
  52. package/lib/detect.js +280 -0
  53. package/lib/frontmatter.js +72 -0
  54. package/lib/gsd-compat.js +165 -0
  55. package/lib/import.js +543 -0
  56. package/lib/inbox.js +226 -0
  57. package/lib/lifecycle.js +929 -0
  58. package/lib/merge.js +157 -0
  59. package/lib/milestone.js +595 -0
  60. package/lib/paths.js +191 -0
  61. package/lib/provider.js +168 -0
  62. package/lib/roadmap.js +134 -0
  63. package/lib/state.js +99 -0
  64. package/lib/worktree.js +253 -0
  65. package/package.json +45 -0
  66. package/templates/DESIGN.md +78 -0
  67. package/templates/INBOX.md +13 -0
  68. package/templates/MILESTONE-CONTEXT.md +40 -0
  69. package/templates/MILESTONES.md +29 -0
  70. package/templates/PLAN.md +84 -0
  71. package/templates/PROJECT.md +43 -0
  72. package/templates/REVIEW-LOG.md +38 -0
  73. package/templates/ROADMAP.md +34 -0
  74. package/templates/STATE.md +78 -0
  75. package/templates/SUMMARY.md +75 -0
  76. package/templates/codebase/ARCHITECTURE.md +30 -0
  77. package/templates/codebase/CONCERNS.md +30 -0
  78. package/templates/codebase/CONVENTIONS.md +30 -0
  79. package/templates/codebase/INTEGRATIONS.md +30 -0
  80. package/templates/codebase/STACK.md +26 -0
  81. package/templates/codebase/STRUCTURE.md +32 -0
  82. package/templates/codebase/TESTING.md +39 -0
  83. package/templates/config.json +173 -0
  84. package/templates/phase-PLAN.md +32 -0
  85. package/templates/quick-PLAN.md +24 -0
  86. package/templates/quick-SUMMARY.md +25 -0
@@ -0,0 +1,295 @@
1
+ ---
2
+ phase: 16-design-capture-infrastructure
3
+ plan: "03"
4
+ type: execute
5
+ wave: 3
6
+ depends_on: ["16-01"]
7
+ files_modified:
8
+ - lib/milestone.js
9
+ - bin/commands/write-summary.js
10
+ - commands/cp/write-summary.md
11
+ - test/unit-design.js
12
+ - .planning/phases/15-*/summaries (10 backfills, gitignored)
13
+ autonomous: true
14
+ requirements: []
15
+ user_setup: []
16
+ must_haves:
17
+ truths:
18
+ - "cp write-summary exits 2 with exact error message if key-decisions empty"
19
+ - "cp-write-summary skill instructs callers to populate key-decisions"
20
+ - "existing v0.6 SUMMARYs backfilled with key-decisions (dogfood)"
21
+ artifacts:
22
+ - "lib/milestone.writeSummary throws ValidationError on empty key-decisions"
23
+ - "test/unit-design.js extended with validation assertions"
24
+ - "Exit code 2 from CLI on bad input"
25
+ key_links:
26
+ - "docs/superpowers/specs/2026-05-20-v0-7-design-capture-design.md"
27
+ ---
28
+
29
+ <objective>
30
+ Plan 16-03: key-decisions hard-block (Phase 16, milestone v0.7 Design Capture).
31
+
32
+ Purpose: Prevent silent "key-decisions: []" SUMMARYs that erase decision
33
+ rationale. `cp write-summary` MUST exit 2 with the exact error message:
34
+
35
+ Error: 'key-decisions' is required and must have ≥1 entry. See spec at
36
+ docs/superpowers/specs/2026-05-20-v0-7-design-capture-design.md
37
+
38
+ The validation runs in `lib/milestone.writeSummary`; the CLI handler
39
+ propagates the throw as exit code 2.
40
+
41
+ Scope: validation + skill update + backfill 10 existing v0.6 dogfood SUMMARYs.
42
+ </objective>
43
+
44
+ <execution_context>
45
+ @.planning/config.json
46
+ </execution_context>
47
+
48
+ <context>
49
+ @.planning/PROJECT.md
50
+ @.planning/ROADMAP.md
51
+ @docs/superpowers/specs/2026-05-20-v0-7-design-capture-design.md
52
+ @.planning/phases/16-design-capture-infrastructure/DESIGN.md
53
+ </context>
54
+
55
+ <tasks>
56
+
57
+ # Implementation Plan — Bite-Sized Tasks
58
+
59
+ > **For agentic workers:** REQUIRED SUB-SKILL: superpowers:subagent-driven-development.
60
+
61
+ ## File Structure
62
+
63
+ | File | Action | Responsibility |
64
+ |---|---|---|
65
+ | `lib/milestone.js` | Modify | `writeSummary()` throws `ValidationError` if `key-decisions` missing or empty |
66
+ | `bin/commands/write-summary.js` | Modify | Catch `ValidationError`; print exact error message; `process.exit(2)` |
67
+ | `commands/cp/write-summary.md` (if exists) | Modify | Skill doc — note hard-block, recommend minimum 1 decision per plan |
68
+ | `test/unit-design.js` | Modify | Append validation tests (empty array, missing key, valid input, exit code via subprocess) |
69
+ | Existing v0.6 SUMMARY files | Backfill (.planning gitignored) | Add at least 1 `key-decisions` entry to each existing SUMMARY |
70
+
71
+ ---
72
+
73
+ ## Task 1: Add validation in lib/milestone.writeSummary
74
+
75
+ **Files:**
76
+ - Modify: `lib/milestone.js`
77
+ - Modify: `test/unit-design.js`
78
+
79
+ - [ ] **Step 1: Inspect** — open `lib/milestone.js`, find `writeSummary` function. Note its exact signature and where it currently writes the YAML — validation must happen BEFORE write.
80
+
81
+ - [ ] **Step 2: Failing test** — append to `test/unit-design.js`:
82
+
83
+ ```javascript
84
+ section('lib/milestone: writeSummary validates key-decisions');
85
+ {
86
+ const milestoneLib = require('../lib/milestone');
87
+ const root = mktmp('ws-validate');
88
+ fs.mkdirSync(path.join(root, '.planning', 'phases', '50-test'), { recursive: true });
89
+
90
+ const empty = { phase: '50', plan: '01', 'key-decisions': [] };
91
+ let caught = null;
92
+ try { milestoneLib.writeSummary(root, '50-01', empty); }
93
+ catch (e) { caught = e; }
94
+ ok('empty key-decisions throws', caught !== null);
95
+ ok('error message mentions key-decisions',
96
+ caught && caught.message.includes("'key-decisions' is required"));
97
+ ok('error message references spec',
98
+ caught && caught.message.includes('docs/superpowers/specs/2026-05-20-v0-7-design-capture-design.md'));
99
+
100
+ const missing = { phase: '50', plan: '01' };
101
+ let caught2 = null;
102
+ try { milestoneLib.writeSummary(root, '50-01', missing); }
103
+ catch (e) { caught2 = e; }
104
+ ok('missing key-decisions throws', caught2 !== null);
105
+
106
+ const valid = { phase: '50', plan: '01', 'key-decisions': ['decision 1'] };
107
+ let caught3 = null;
108
+ try { milestoneLib.writeSummary(root, '50-01', valid); }
109
+ catch (e) { caught3 = e; }
110
+ ok('valid input does not throw', caught3 === null);
111
+ }
112
+ ```
113
+
114
+ - [ ] **Step 3: Run** — `node test/unit-design.js` — expect new section fails.
115
+
116
+ - [ ] **Step 4: Implement validation** — in `lib/milestone.js`, add a `ValidationError` class (or use a tagged Error) and inject validation at the TOP of `writeSummary` body:
117
+
118
+ ```javascript
119
+ class ValidationError extends Error {
120
+ constructor(message) { super(message); this.name = 'ValidationError'; this.code = 'EVALIDATION'; }
121
+ }
122
+
123
+ // inside writeSummary, before any write:
124
+ const kd = data && data['key-decisions'];
125
+ if (!Array.isArray(kd) || kd.length === 0) {
126
+ throw new ValidationError(
127
+ "Error: 'key-decisions' is required and must have ≥1 entry. See spec at docs/superpowers/specs/2026-05-20-v0-7-design-capture-design.md"
128
+ );
129
+ }
130
+ ```
131
+
132
+ Export `ValidationError` from `lib/milestone.js` so the CLI handler can `instanceof`-check it.
133
+
134
+ - [ ] **Step 5: Run test** — `node test/unit-design.js` — expect all pass.
135
+
136
+ - [ ] **Step 6: Full suite** — `npm test`. If existing tests in `test/unit-libs.js`, `test/dryrun-*.js`, or `test/unit-lifecycle.js` call `writeSummary` with empty/missing key-decisions, those tests will FAIL. For each failing test, EITHER add a stub `'key-decisions': ['test decision']` to its input data, OR (if the test was specifically asserting empty-input behavior) update it to assert the new throw.
137
+
138
+ - [ ] **Step 7: Commit**
139
+
140
+ ```bash
141
+ git add lib/milestone.js test/unit-design.js test/<any-other-modified-tests>
142
+ git commit -m "cp(16-03): writeSummary throws ValidationError on empty key-decisions
143
+
144
+ Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>"
145
+ ```
146
+
147
+ ---
148
+
149
+ ## Task 2: CLI handler exits 2 on validation error
150
+
151
+ **Files:**
152
+ - Modify: `bin/commands/write-summary.js`
153
+ - Modify: `test/unit-design.js`
154
+
155
+ - [ ] **Step 1: Read current handler** — open `bin/commands/write-summary.js`. Find where it calls `milestone.writeSummary(...)`. Wrap in try/catch.
156
+
157
+ - [ ] **Step 2: Add try/catch**
158
+
159
+ ```javascript
160
+ try {
161
+ milestone.writeSummary(root, planId, data);
162
+ } catch (err) {
163
+ if (err && (err.name === 'ValidationError' || err.code === 'EVALIDATION')) {
164
+ process.stderr.write(err.message + '\n');
165
+ process.exit(2);
166
+ }
167
+ throw err;
168
+ }
169
+ ```
170
+
171
+ - [ ] **Step 3: Subprocess test** — append to `test/unit-design.js`:
172
+
173
+ ```javascript
174
+ section('CLI: cp write-summary exits 2 on empty key-decisions');
175
+ {
176
+ const { spawnSync } = require('child_process');
177
+ const root = mktmp('ws-cli');
178
+ fs.mkdirSync(path.join(root, '.planning', 'phases', '50-test'), { recursive: true });
179
+ const json = path.join(root, 'bad.json');
180
+ fs.writeFileSync(json, JSON.stringify({ phase: '50', plan: '01', 'key-decisions': [] }));
181
+
182
+ const cpBin = path.join(__dirname, '..', 'bin', 'cp.js');
183
+ const r = spawnSync(process.execPath, [cpBin, 'write-summary', '50-01', '--from', json], { cwd: root, encoding: 'utf8' });
184
+ ok('exit code is 2', r.status === 2);
185
+ ok('stderr includes key-decisions error',
186
+ (r.stderr || '').includes("'key-decisions' is required"));
187
+ }
188
+ ```
189
+
190
+ - [ ] **Step 4: Run** — `node test/unit-design.js` — expect all pass.
191
+
192
+ - [ ] **Step 5: Full suite** — `npm test`. All green.
193
+
194
+ - [ ] **Step 6: Commit**
195
+
196
+ ```bash
197
+ git add bin/commands/write-summary.js test/unit-design.js
198
+ git commit -m "cp(16-03): write-summary CLI exits 2 on ValidationError
199
+
200
+ Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>"
201
+ ```
202
+
203
+ ---
204
+
205
+ ## Task 3: Update cp-write-summary skill doc
206
+
207
+ **Files:**
208
+ - Modify: `commands/cp/write-summary.md` (if exists; else `.github/skills/cp-write-summary/SKILL.md`)
209
+
210
+ - [ ] **Step 1: Locate the skill** — `Get-ChildItem commands/cp/ | Where-Object Name -Match write-summary`.
211
+
212
+ - [ ] **Step 2: Add a "Required keys" section** at the top:
213
+
214
+ ```markdown
215
+ ## Required keys (v0.7 hard-block)
216
+
217
+ - `key-decisions`: **REQUIRED**. Array with ≥1 entry. Each entry is one
218
+ sentence describing a non-trivial decision made during the plan
219
+ (architecture, library choice, trade-off, deferred work, etc.).
220
+ Trivial / mechanical steps do not count.
221
+
222
+ The cp CLI exits with code 2 and prints the following exact message if
223
+ this constraint is violated:
224
+
225
+ Error: 'key-decisions' is required and must have ≥1 entry. See spec at
226
+ docs/superpowers/specs/2026-05-20-v0-7-design-capture-design.md
227
+
228
+ If a plan genuinely had no decisions worth recording (e.g. a typo fix),
229
+ note that explicitly: `key-decisions: ['mechanical edits only — no design decisions']`.
230
+ ```
231
+
232
+ If the file doesn't exist, create it with that section plus a brief skeleton.
233
+
234
+ - [ ] **Step 3: Verify**
235
+
236
+ `node -e "const f=require('fs').readFileSync('commands/cp/write-summary.md','utf8'); if(!f.includes('Required keys')||!f.includes('key-decisions')){console.error('missing');process.exit(1);} console.log('OK');"`
237
+
238
+ - [ ] **Step 4: Commit**
239
+
240
+ ```bash
241
+ git add commands/cp/write-summary.md
242
+ git commit -m "cp(16-03): document key-decisions hard-block in cp-write-summary skill
243
+
244
+ Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>"
245
+ ```
246
+
247
+ ---
248
+
249
+ ## Task 4: Backfill existing v0.6 SUMMARY files
250
+
251
+ **Files:** `.planning/phases/15-*/15-*-SUMMARY.md` and prior phases (10 files; all gitignored).
252
+
253
+ - [ ] **Step 1: List all existing SUMMARY files** — `Get-ChildItem .planning/phases -Recurse -Filter "*-SUMMARY.md" | Select-Object FullName`.
254
+
255
+ - [ ] **Step 2: For each file, check if `key-decisions:` is empty (`[]`) or missing**. If so, edit to add at least one entry summarizing the plan's actual decisions (look at the plan's commit messages and the SUMMARY body for context).
256
+
257
+ This is a manual / semi-manual task. A minimum acceptable entry is one sentence describing the plan's primary architectural or process decision. For purely mechanical plans (typo fix, version bump), use: `key-decisions: ['mechanical edits only — no design decisions']`.
258
+
259
+ - [ ] **Step 3: After backfill, verify by running** — `cp progress` (or scan summaries) — no SUMMARY file has empty `key-decisions`.
260
+
261
+ - [ ] **Step 4: No commit** — `.planning/` is gitignored. Document backfill in plan SUMMARY.
262
+
263
+ ---
264
+
265
+ ## Task 5: Coverage gate + verification
266
+
267
+ - [ ] **Step 1:** `npm run coverage:ci` — exit 0; ≥85L / ≥75B.
268
+
269
+ - [ ] **Step 2: End-to-end verification** of v0.7:
270
+ - `node bin/cp.js scaffold-phase 99 --name smoke --dry-run` lists 4 actions
271
+ - `node bin/cp.js scaffold-milestone "v0.99 Smoke" --dry-run` lists 2 actions
272
+ - `echo '{"phase":"99","plan":"01"}' > bad.json; node bin/cp.js write-summary 99-01 --from bad.json` — exit 2, exact error message
273
+ - `echo '{"phase":"99","plan":"01","key-decisions":["dec1"]}' > good.json; node bin/cp.js write-summary 99-01 --from good.json` — exit 0
274
+
275
+ </tasks>
276
+
277
+ <verification>
278
+ - [ ] `npm test` — all 20 files `Failed: 0`
279
+ - [ ] `npm run coverage:ci` — ≥85L / ≥75B
280
+ - [ ] Empty key-decisions JSON → exit 2 + exact stderr message
281
+ - [ ] Valid key-decisions JSON → exit 0
282
+ - [ ] All existing SUMMARY files have ≥1 key-decisions entry
283
+ </verification>
284
+
285
+ <success_criteria>
286
+ - lib/milestone.writeSummary validates key-decisions; throws ValidationError
287
+ - bin/commands/write-summary.js catches and exits 2 with exact spec message
288
+ - cp-write-summary skill doc updated
289
+ - All v0.6 SUMMARYs backfilled
290
+ - v0.7 is feature-complete; ready for release
291
+ </success_criteria>
292
+
293
+ <output>
294
+ After completion: `cp write-summary 16-03 --from <json>` (with non-empty key-decisions!), then release v0.7.0.
295
+ </output>
@@ -0,0 +1,380 @@
1
+ # v0.5 — Generic provider / harness detection
2
+
3
+ **Status:** Design (approved 2026-05-20)
4
+ **Milestone:** v0.5
5
+ **Brainstorm session:** 2026-05-20, paired with user @sli
6
+ **Spec author:** Copilot CLI (Claude Opus 4.7)
7
+ **Implementation provider:** Superpowers (`writing-plans` → `subagent-driven-development`)
8
+
9
+ ---
10
+
11
+ ## 1. Problem statement
12
+
13
+ `cp` (context-planning) v0.4.5 ships a working but fragile detection layer:
14
+
15
+ - Workflow provider detection is **literal string matching** against a 5-element list of hardcoded sentinel paths under 5 hardcoded base dirs.
16
+ - Only one real workflow provider (Superpowers) is shipped; the schema is "generic" in theory but never proved with a second provider.
17
+ - AI harnesses (Copilot CLI, Claude Code, Cursor, Aider) are not modeled at all — detection doesn't know which harness's plugin marketplace it's scanning, so:
18
+ - When a new harness adds a plugin marketplace at a new path, cp needs a code/template change.
19
+ - `cp doctor` can't tell the user *which* harness their Superpowers install came from.
20
+ - Brownfield `.planning/config.json` files snapshot the default sentinel list at init time and never refresh — so the v0.4.5 sentinel additions are invisible to existing projects.
21
+
22
+ The user-visible symptom that surfaced v0.4.5 (`cp doctor` reports Superpowers as missing under Copilot CLI) will keep recurring with every new harness or marketplace layout unless detection is restructured around an extensible model.
23
+
24
+ ## 2. Goals
25
+
26
+ 1. **Harnesses × providers cross-product detection.** A single config-only edit adds support for a new harness or a new provider; no `lib/*.js` changes required.
27
+ 2. **Marketplace-wildcard support** (`installed-plugins/*/`) so forks of `superpowers-marketplace` (or any plugin distributor) work out of the box.
28
+ 3. **`cp doctor` enumerates everything found** — all harnesses, all providers, where each was located, what the active configuration resolves to.
29
+ 4. **Brownfield projects auto-heal** — first v0.5 invocation in a project init'd against v0.4.x silently merges new upstream defaults (sentinels, harnesses, new providers) into the local config and writes the result back, with a stderr notice.
30
+ 5. **Schema generality proven** — ship a second built-in provider (a no-op `echo-provider` stub) so the assertion "the schema isn't Superpowers-shaped" is testable end-to-end.
31
+
32
+ ## 3. Non-goals (v0.5)
33
+
34
+ - Full minimatch globbing (`**`, character classes). Trailing-`*` segment only.
35
+ - A real second workflow provider (BMAD / GSD-as-provider / etc.) — `echo-provider` is a schema test, not a product offering.
36
+ - `cp install <provider>` to auto-install Superpowers / others — out of scope.
37
+ - Phase-level workflow_provider override (e.g., "use Superpowers for plan, manual for execute").
38
+ - Telemetry / network calls — cp remains fully offline.
39
+
40
+ ## 4. User-facing surface
41
+
42
+ ### 4.1 Schema (`templates/config.json`, schema version 2)
43
+
44
+ ```json
45
+ "cp": {
46
+ "version": 2,
47
+ "workflow_provider": "superpowers",
48
+
49
+ "harnesses": {
50
+ "copilot": {
51
+ "description": "GitHub Copilot CLI",
52
+ "plugin_roots": ["~/.copilot/installed-plugins/*/"]
53
+ },
54
+ "claude": {
55
+ "description": "Claude Code",
56
+ "plugin_roots": ["~/.claude/plugins/*/", "~/.claude/skills/"]
57
+ },
58
+ "cursor": {
59
+ "description": "Cursor",
60
+ "plugin_roots": ["~/.cursor/extensions/*/"]
61
+ },
62
+ "aider": {
63
+ "description": "Aider (file-based, no plugin slot today)",
64
+ "plugin_roots": []
65
+ }
66
+ },
67
+
68
+ "providers": {
69
+ "superpowers": {
70
+ "description": "Jesse Vincent's Superpowers plugin (https://github.com/obra/superpowers)",
71
+ "plugin_shape": {
72
+ "dir_name": "superpowers",
73
+ "required_subdirs": [
74
+ "skills/writing-plans",
75
+ "skills/subagent-driven-development"
76
+ ]
77
+ },
78
+ "detect": {
79
+ "any_of": [
80
+ ".claude/plugins/superpowers",
81
+ ".claude/skills/superpowers",
82
+ ".github/skills/brainstorming",
83
+ ".github/skills/writing-plans",
84
+ ".github/skills/subagent-driven-development",
85
+ "installed-plugins/superpowers-marketplace/superpowers",
86
+ "installed-plugins/superpowers-marketplace/superpowers/skills/writing-plans",
87
+ "installed-plugins/superpowers-marketplace/superpowers/skills/subagent-driven-development"
88
+ ]
89
+ },
90
+ "skills": { "brainstorm": "brainstorming", "plan": "writing-plans", "execute": "subagent-driven-development", "execute_simple": "executing-plans", "review": "requesting-code-review", "receive_review": "receiving-code-review", "finish": "finishing-a-development-branch", "worktree": "using-git-worktrees", "tdd": "test-driven-development", "debug": "systematic-debugging", "verify": "verification-before-completion" }
91
+ },
92
+
93
+ "echo-provider": {
94
+ "description": "Schema-test stub. Echoes the role name. Not for end users.",
95
+ "plugin_shape": { "dir_name": "echo-provider", "required_subdirs": [] },
96
+ "skills": { "brainstorm": "echo", "plan": "echo", "execute": "echo", "execute_simple": "echo", "review": "echo", "receive_review": "echo", "finish": "echo", "worktree": "echo", "tdd": "echo", "debug": "echo", "verify": "echo" }
97
+ },
98
+
99
+ "manual": { "description": "Inline fallback...", "detect": { "always": true }, "skills": { ... unchanged ... }, "prompts": { ... unchanged ... } }
100
+ },
101
+
102
+ "behavior": { ... unchanged ... }
103
+ }
104
+ ```
105
+
106
+ **Detection rule for a provider P:**
107
+ > Installed if either (a) for some harness H, expanding any of `H.plugin_roots` yields a child directory named `P.plugin_shape.dir_name` AND every entry in `P.plugin_shape.required_subdirs` exists under it; OR (b) any entry in `P.detect.any_of` is found via the legacy 5-base-dir search.
108
+
109
+ (a) is the preferred path. (b) is back-compat for hand-curated literal sentinels.
110
+
111
+ ### 4.2 `cp doctor` output (sectioned)
112
+
113
+ ```
114
+ cp v0.5.0
115
+ Repo root: C:\src\github\stock-analyze-hub\core-service-py
116
+ .planning/: present
117
+ Config: .planning\config.json (schema v2)
118
+
119
+ Harnesses detected:
120
+ ✓ copilot ~/.copilot/installed-plugins/* (2 marketplaces, 4 plugins)
121
+ ✓ claude ~/.claude/plugins/* (1 plugin), ~/.claude/skills/ (0 plugins)
122
+ ✗ cursor (no plugins found at ~/.cursor/extensions/*)
123
+ — aider (file-based — no plugin slot)
124
+
125
+ Providers detected:
126
+ ✓ superpowers via copilot @ installed-plugins/superpowers-marketplace/superpowers
127
+ via claude @ plugins/superpowers
128
+ ✗ echo-provider (stub — install with `cp install echo-provider --local`)
129
+ ✓ manual (always available)
130
+
131
+ Configured workflow_provider: superpowers [`cp config set workflow_provider <name>` to switch]
132
+
133
+ Roles → resolved skill:
134
+ ✓ brainstorm → superpowers/brainstorming (via copilot)
135
+ ✓ plan → superpowers/writing-plans (via copilot)
136
+ ... (9 total)
137
+
138
+ GSD compatibility:
139
+ cp-aware config: ✓
140
+ shared files: .planning/PROJECT.md, .planning/ROADMAP.md, .planning/STATE.md, .planning/config.json
141
+ phase dirs: 6
142
+ ```
143
+
144
+ **Flags**:
145
+ - `--json` emits the full `detectAllInstalled()` payload as machine-readable JSON (consumers: statusline, external tooling, CI).
146
+ - `--quiet` emits only the `Configured` line + role table (terse mode for CI / tight terminals).
147
+
148
+ **Exit codes**: `0` if configured provider resolves OR `behavior.fall_back_to_manual_if_provider_missing=true`; `1` otherwise.
149
+
150
+ ### 4.3 `cp config refresh`
151
+
152
+ ```
153
+ $ cp config refresh [--dry-run]
154
+ cp: would add 3 sentinels to providers.superpowers.detect.any_of
155
+ cp: would add provider 'echo-provider'
156
+ cp: would migrate schema v1 → v2
157
+ (no changes written — use without --dry-run to apply)
158
+ ```
159
+
160
+ Idempotent. Second run is a no-op. Implementation: call `mergeCpDefaults` with `verbose: true`, print planned mutations, write back unless `--dry-run`.
161
+
162
+ ### 4.4 Auto-heal at `loadConfig` time
163
+
164
+ First v0.5 invocation in any project with a `.planning/config.json` that lacks new defaults (sentinels, harnesses, new providers, schema bump) **silently merges and writes back**, with one stderr line:
165
+
166
+ ```
167
+ cp: refreshed .planning/config.json with 3 new sentinels, 1 new provider (echo-provider), schema v1 → v2
168
+ ```
169
+
170
+ Subsequent invocations are no-ops (idempotent merge).
171
+
172
+ ## 5. Internal architecture
173
+
174
+ ### 5.1 New module: `lib/detect.js` (~250 LOC target)
175
+
176
+ ```
177
+ // Public API (also re-exported through lib/provider.js for back-compat)
178
+ detectAllInstalled(cfg) → DetectionReport
179
+ detectProviderAtAnyHarness(cfg, providerName) → ProviderHit | { installed: false }
180
+ expandRoot(rootSpec) → string[] // trailing-* glob expansion with tilde
181
+ ```
182
+
183
+ ```
184
+ type DetectionReport = {
185
+ harnesses: HarnessReport[],
186
+ providers: ProviderReport[],
187
+ }
188
+ type HarnessReport = {
189
+ name: string,
190
+ configured: boolean,
191
+ scannedRoots: { root: string, expanded: string[] }[],
192
+ pluginCount: number,
193
+ }
194
+ type ProviderReport = {
195
+ name: string,
196
+ installed: boolean,
197
+ hits: ProviderHit[], // empty if installed=false
198
+ }
199
+ type ProviderHit = {
200
+ via: string, // harness name, or '_anywhere' for legacy literal match
201
+ source: 'plugin_shape' | 'literal' | 'always',
202
+ evidence: string, // absolute path that satisfied detection
203
+ }
204
+ ```
205
+
206
+ **Pure functions** — no I/O caching, no module-level state. Filesystem reads happen at call time. Tests monkey-patch `os.homedir` (same pattern as v0.4.5).
207
+
208
+ **`expandRoot` algorithm**:
209
+ 1. Replace leading `~/` with `os.homedir() + '/'`. No env-var expansion.
210
+ 2. If no `*` in the path → return `[absPath]` if exists else `[]`.
211
+ 3. Else split on first `*`-segment, `readdirSync(parent)`, filter to dirs, append rest of path to each, recurse.
212
+ 4. Trailing slashes normalized to platform sep.
213
+
214
+ ### 5.2 Slimmed `lib/provider.js` (~100 LOC target, was 145)
215
+
216
+ Keeps:
217
+ - `loadConfig(root)`, `saveConfig(cfg, root)`, `configPath(root)`, `loadDefaults()`
218
+ - `cpGet(cfg, dotted, fallback)`, `cpSet(cfg, dotted, value)`
219
+ - `resolveSkill(role, root)` — now a thin wrapper that calls `detect.detectProviderAtAnyHarness` then maps to a skill name.
220
+ - `resolvePrompt(role, root)` — unchanged.
221
+
222
+ Removes (moves to `lib/detect.js`):
223
+ - `existsAnywhere(candidate)`
224
+ - `detectProvider(cfg, name)` — re-exported from detect.js as `detectProviderAtAnyHarness` for clarity; old export name kept as alias for one minor version.
225
+
226
+ ### 5.3 New module: `lib/merge.js` (~150 LOC target)
227
+
228
+ ```
229
+ mergeCpDefaults(raw, defaults, { verbose: false }) → { cfg, changed, summary, plannedChanges }
230
+ ```
231
+
232
+ Pure function. No I/O. Caller decides whether to write the result.
233
+
234
+ **Merge rules** (purely additive — never delete user data):
235
+
236
+ | Key | Rule |
237
+ |---|---|
238
+ | `cp.version` | `Math.max(raw, defaults)` |
239
+ | `cp.workflow_provider` | user wins (never overwrite) |
240
+ | `cp.harnesses[H]` | add missing H entirely; for existing H, deep-merge keys |
241
+ | `cp.harnesses[H].plugin_roots` | union (dedupe, preserve order) |
242
+ | `cp.providers[P]` | add missing P entirely |
243
+ | `cp.providers[P].detect.any_of` | union (dedupe) |
244
+ | `cp.providers[P].plugin_shape` | add if missing; never overwrite |
245
+ | `cp.providers[P].skills[role]` | add if missing; user wins on conflict |
246
+ | `cp.providers[P].prompts[role]` | add if missing; user wins on conflict |
247
+ | `cp.behavior[K]` | add if missing; user wins on conflict |
248
+
249
+ `summary` is a short human-readable string composed from `plannedChanges`. Example: `"3 new sentinels, 1 new provider (echo-provider), schema v1 → v2"`.
250
+
251
+ ### 5.4 `loadConfig` change
252
+
253
+ ```js
254
+ function loadConfig(root) {
255
+ const p = configPath(root);
256
+ const defaults = loadDefaults();
257
+ if (!fs.existsSync(p)) return defaults;
258
+
259
+ const raw = JSON.parse(fs.readFileSync(p, 'utf8'));
260
+
261
+ if (!raw.cp) { // existing pure-GSD path
262
+ raw.cp = defaults.cp;
263
+ fs.writeFileSync(p, JSON.stringify(raw, null, 2) + '\n');
264
+ return raw;
265
+ }
266
+
267
+ const merged = mergeCpDefaults(raw, defaults);
268
+ if (merged.changed) {
269
+ fs.writeFileSync(p, JSON.stringify(merged.cfg, null, 2) + '\n');
270
+ process.stderr.write(`cp: refreshed .planning/config.json with ${merged.summary}\n`);
271
+ }
272
+ return merged.cfg;
273
+ }
274
+ ```
275
+
276
+ Stderr write is intentional — `cp doctor`'s output is parsed in tests / status-line; mutating stdout would break them. Stderr is the right channel for advisory notices.
277
+
278
+ ### 5.5 `bin/cp.js` additions
279
+
280
+ - `cp doctor` rewritten to consume `detect.detectAllInstalled` and emit the sectioned format. New flags: `--json`, `--quiet`.
281
+ - `cp config refresh [--dry-run]` — new subcommand. Reads config, calls `mergeCpDefaults(verbose:true)`, prints planned changes, writes unless `--dry-run`.
282
+ - `cp install echo-provider --local` — new flag on existing `install` dispatcher. Plants `~/.cp/providers/echo-provider/skills/echo/SKILL.md` (single file, ~5 lines). Pure local install, no network.
283
+
284
+ ## 6. Error handling
285
+
286
+ | Scenario | Behavior |
287
+ |---|---|
288
+ | Malformed `~/.copilot/installed-plugins/<x>` (file instead of dir) | Skipped by `readdirSync` + dir filter; no error surfaced |
289
+ | `cp.harnesses[H].plugin_roots` entry has multiple `*` | Each `*` segment expanded recursively; works as expected |
290
+ | `~/` expansion when `os.homedir()` returns empty | Skip the root; log a warning to stderr via `cp doctor` (once per invocation) |
291
+ | User-edited `cp.providers[P].plugin_shape.required_subdirs` is not an array | `detectProviderAtAnyHarness` returns `{installed: false, reason: 'malformed plugin_shape'}` and `cp doctor` shows `✗ P (config error: ...)` instead of crashing |
292
+ | `mergeCpDefaults` encounters a value-type conflict (e.g., user set `plugin_roots: "string"` instead of array) | User value preserved; one stderr warning; merge continues for other keys |
293
+ | `cp config refresh` against a missing `.planning/config.json` | Exit 1 with `cp: no .planning/config.json found — run \`cp init\` first` |
294
+
295
+ No `try/catch` around `fs.readFileSync` for config — let the parse error propagate (matches v0.4.x behavior). The merge function is wrapped in try/catch only at the `loadConfig` boundary; on merge failure, log to stderr and return the unmerged config (don't block the command).
296
+
297
+ ## 7. Testing strategy
298
+
299
+ ### Test fixtures
300
+
301
+ | Fixture name | Purpose |
302
+ |---|---|
303
+ | `host-copilot-only` | `~/.copilot/installed-plugins/superpowers-marketplace/superpowers/` exists with required subdirs |
304
+ | `host-claude-only` | `~/.claude/plugins/superpowers/` exists |
305
+ | `host-both` | Both layouts present (multi-harness detection) |
306
+ | `host-neither` | Empty home — manual fallback only |
307
+ | `host-stub-installed` | `~/.cp/providers/echo-provider/skills/echo/` exists |
308
+ | `host-malformed` | `~/.copilot/installed-plugins/junk-marketplace/superpowers/` is a file, not dir |
309
+
310
+ ### Test files
311
+
312
+ | File | Coverage | Assertions |
313
+ |---|---|---|
314
+ | `test/unit-detect.js` (NEW) | `expandRoot` (10 cases incl. tilde, missing parent, mixed literals, multi-`*`); `detectProviderAtAnyHarness` against all 6 host fixtures; back-compat with legacy `any_of` literals; full `detectAllInstalled` shape check | ~40 |
315
+ | `test/unit-merge.js` (NEW) | Each merge rule (~11) × edge cases (user-empty / user-extra / user-conflict / schema v1→v2 / type mismatch); idempotency (apply twice == apply once); summary string format | ~50 |
316
+ | `test/dryrun-doctor.js` (NEW) | Spawn `bin/cp.js doctor` against temp roots, assert output sections; `--json` shape; `--quiet` shape; exit codes for missing-provider + fallback-disabled | ~25 |
317
+ | `test/dryrun-config-refresh.js` (NEW) | Brownfield fixtures (v0.4.4 config / v0.3.x config / pure-GSD / hand-rolled / empty-cp) go through refresh; assert diff; assert second run is no-op | ~25 |
318
+ | `test/unit-libs.js` (EXTEND) | Keep v0.4.5 sections; add smoke test that `provider.resolveSkill` still works through the new path | ~5 |
319
+
320
+ Total new: ~145. Baseline 751 → ~895.
321
+
322
+ All new tests follow the v0.4.5 isolation pattern: monkey-patch `os.homedir()` to a temp dir so the host machine state doesn't leak.
323
+
324
+ ### Manual / dogfood verification
325
+
326
+ - Run `cp doctor` in this repo (`context-planning`) → expect all 4 harnesses, both superpowers + echo-provider listed.
327
+ - Run `cp doctor` in `StockAnalyzer/core-service-py` → expect identical output (proves brownfield auto-heal worked).
328
+ - Remove the local `installed-plugins/...` override from StockAnalyzer's config, run `cp doctor` again → auto-heal restores it, detection still works.
329
+ - `cp config refresh --dry-run` in a freshly init'd v0.4.x project → expect non-empty diff; without `--dry-run` → first run mutates, second run is no-op.
330
+
331
+ ## 8. Rollout plan
332
+
333
+ Each phase ships as an internal pre-release; the final tag is `v0.5.0`.
334
+
335
+ | Phase | Tag | Scope | Ships when |
336
+ |---|---|---|---|
337
+ | 1. Schema + detection core | v0.5.0-alpha | Templates updated; `lib/detect.js` lands; `lib/provider.js` slimmed; existing tests still pass; `cp doctor` output unchanged externally (uses old code path) | `test/unit-detect.js` green |
338
+ | 2. `cp doctor` rewrite | v0.5.0-beta | Sectioned output; `--json`/`--quiet` flags; `test/dryrun-doctor.js` green | Manual eyeball OK |
339
+ | 3. Auto-heal + `cp config refresh` | v0.5.0-rc | `lib/merge.js` lands; `loadConfig` writes back on first encounter; new subcommand; `test/unit-merge.js` + `test/dryrun-config-refresh.js` green | Brownfield fixtures verified by hand |
340
+ | 4. Echo-provider stub + installer | v0.5.0 | `install/echo-provider.js` plants local files; `cp doctor` shows echo as detected after install; README example | End-to-end "switch workflow_provider to echo-provider" demo works |
341
+ | 5. Migration doc + CHANGELOG (no version) | — | `docs/MIGRATION-v0.5.md` (before/after configs); CHANGELOG; README update; GitHub release with summary | All above shipped |
342
+
343
+ ## 9. Risks
344
+
345
+ | Risk | Likelihood | Mitigation |
346
+ |---|---|---|
347
+ | First-run auto-heal mutates user's config without their knowing | Medium | Loud stderr message; `cp config refresh --dry-run` documented in v0.4.5 → v0.5 upgrade notes; auto-heal is purely additive so worst case is extra entries the user can prune |
348
+ | Trailing-`*` semantics confuse users expecting `**` | Low | Documented explicitly in `cp doctor --help` and migration doc; trailing-`*` covers all known real-world cases |
349
+ | `cp.harnesses` block under `cp.*` confuses GSD users | Very low | GSD ignores unknown keys under the `cp` block by design (existing convention) |
350
+ | Echo-provider sets a precedent users misuse as a real provider | Low | `description` says "Not for end users"; `cp doctor` doesn't recommend switching to it; README example labels it a schema test |
351
+ | `~/.cp/providers/` directory convention conflicts with future cp-managed user state | Low | Document the convention in v0.5; if v0.6 needs cp-managed user state, put it under `~/.cp/state/` or `~/.cp/cache/` |
352
+
353
+ ## 10. Out of scope (parked for v0.6+)
354
+
355
+ - Full minimatch globbing (`**`, character classes)
356
+ - A real second workflow provider (BMAD / GSD-as-provider integration)
357
+ - `cp install superpowers` (auto-install Superpowers via npm / curl)
358
+ - Phase-level workflow_provider override
359
+ - Telemetry / first-run survey
360
+ - Multi-OS testing matrix in CI (currently zero CI; that's a separate concern in `.planning/codebase/CONCERNS.md`)
361
+ - Refactoring `bin/cp.js` LOC growth (1100+ lines, hand-rolled argv per handler — open MEDIUM concern, not v0.5)
362
+
363
+ ## 11. Open questions
364
+
365
+ None at spec-write time. All scoping decisions captured in the Q1-Q5 interactive brainstorm:
366
+
367
+ | Q | Decision |
368
+ |---|---|
369
+ | Harness scope | All four (Copilot, Claude, Cursor, Aider) modeled in schema |
370
+ | Glob support | Trailing-`*` segment only (zero-deps) |
371
+ | `cp doctor` output | Sectioned (full enumeration) |
372
+ | Brownfield merge | Auto-write on first load + explicit `cp config refresh` |
373
+ | Second provider | Schema-test stub (`echo-provider`) only |
374
+
375
+ ## 12. References
376
+
377
+ - Triggering bug: `cp doctor` reports Superpowers missing under Copilot CLI marketplace install (fixed as a band-aid in v0.4.5, full structural fix is this milestone).
378
+ - Predecessor commit: `a168fbd` — `fix(v0.4.5): detect Superpowers via Copilot CLI marketplace install`
379
+ - Brownfield issue surfaced during v0.4.5 verification: `loadConfig` only merges defaults when `cp` block is entirely missing — root cause of the auto-heal requirement in this design.
380
+ - Architecture principle drawn from existing PROJECT.md: "Node-only runtime: keep zero-deps in `bin/`/`lib/`" — gates Q2 against minimatch.