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,116 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Installer for Claude Code.
5
+ *
6
+ * Claude Code looks for slash-commands in `<repo>/.claude/commands/<file>.md`
7
+ * (project-scoped) or `~/.claude/commands/<file>.md` (user-scoped). Each
8
+ * markdown file becomes a /command — the filename without the .md extension.
9
+ *
10
+ * Claude Code also reads `<repo>/.claude/CLAUDE.md` (and `~/.claude/CLAUDE.md`)
11
+ * as ambient instructions for the agent. We append a small cp section so the
12
+ * agent knows when to invoke /cp-* commands and how to talk to the workflow
13
+ * provider.
14
+ *
15
+ * The same command markdown files used by the Copilot installer are reused
16
+ * verbatim — they're harness-agnostic. Claude treats the YAML frontmatter as
17
+ * metadata (name / description) and the body as instructions.
18
+ */
19
+
20
+ const fs = require('fs');
21
+ const path = require('path');
22
+ const { listCommandFiles, writeFile, writeFileSafe, homeDir } = require('./common');
23
+
24
+ const CP_BLOCK_BEGIN = '<!-- context-planning (cp) — managed by cp installer -->';
25
+ const CP_BLOCK_END = '<!-- /context-planning -->';
26
+
27
+ function install({ pluginRoot, repoRoot, force = false }) {
28
+ const userScope = process.env.CP_INSTALL_SCOPE === 'user';
29
+ const base = userScope
30
+ ? path.join(homeDir(), '.claude')
31
+ : path.join(repoRoot, '.claude');
32
+ const cmdDir = path.join(base, 'commands');
33
+
34
+ console.log(`Installing cp commands as Claude Code slash-commands into:`);
35
+ console.log(` ${cmdDir}`);
36
+
37
+ let written = 0;
38
+ let identical = 0;
39
+ const userModified = [];
40
+ for (const { name, src } of listCommandFiles(pluginRoot)) {
41
+ // Claude expects the filename to match the command, so /cp-progress reads
42
+ // .claude/commands/cp-progress.md. Our source files are already named
43
+ // {name}.md (no cp- prefix), so we prefix on install.
44
+ const dest = path.join(cmdDir, `cp-${name}.md`);
45
+ const body = fs.readFileSync(src, 'utf8');
46
+ const r = writeFileSafe(dest, body, { force });
47
+ if (r.status === 'written') { console.log(` + /cp-${name}`); written++; }
48
+ else if (r.status === 'identical') { console.log(` = /cp-${name} (unchanged)`); identical++; }
49
+ else if (r.status === 'user-modified') { console.log(` ! /cp-${name} (LOCALLY MODIFIED — kept)`); userModified.push(`cp-${name}.md`); }
50
+ }
51
+
52
+ // Merge the cp instruction block into CLAUDE.md (project or user) —
53
+ // idempotent. Strip any prior cp block first, then append a fresh one.
54
+ // This is always safe: we only ever touch text *between* our markers,
55
+ // so any unrelated CLAUDE.md content (user instructions, other plugins'
56
+ // blocks) is preserved verbatim. No --force needed.
57
+ const claudeMdPath = path.join(base, 'CLAUDE.md');
58
+ const block = `${CP_BLOCK_BEGIN}
59
+ # Instructions for context-planning (cp)
60
+
61
+ - When the user invokes a \`/cp-*\` slash command, load the matching command
62
+ file from \`.claude/commands/cp-*.md\`.
63
+ - \`cp\` owns the *state layer* (PROJECT.md / ROADMAP.md / STATE.md / phase dirs
64
+ under \`.planning/\`). It delegates *workflow* to whatever provider is
65
+ configured in \`.planning/config.json\` under the top-level \`cp:\` block
66
+ (default provider: superpowers). The same \`config.json\` is shared with GSD
67
+ when GSD is also installed — they coexist via separate top-level keys.
68
+ - For each role (brainstorm, plan, execute, review, finish), invoke the
69
+ matching provider skill (e.g. \`brainstorming\`, \`writing-plans\`,
70
+ \`subagent-driven-development\`), then return to cp to record the state
71
+ change.
72
+ - Always update .planning/STATE.md after a phase/quick task completes; tick
73
+ the matching checkbox in .planning/ROADMAP.md; write SUMMARY.md.
74
+ - Only invoke cp commands when the user explicitly asks. Don't apply cp
75
+ workflows unbidden.
76
+ ${CP_BLOCK_END}
77
+ `;
78
+
79
+ let existing = fs.existsSync(claudeMdPath)
80
+ ? fs.readFileSync(claudeMdPath, 'utf8')
81
+ : '';
82
+ // Remove any previously installed block (idempotent re-install).
83
+ const blockRe = new RegExp(
84
+ escapeRegex(CP_BLOCK_BEGIN) + '[\\s\\S]*?' + escapeRegex(CP_BLOCK_END) + '\\n?',
85
+ 'g'
86
+ );
87
+ existing = existing.replace(blockRe, '');
88
+ // Trim trailing newlines so we get a clean join.
89
+ existing = existing.replace(/\s+$/, '');
90
+ const merged = existing
91
+ ? existing + '\n\n' + block
92
+ : block;
93
+
94
+ writeFile(claudeMdPath, merged);
95
+
96
+ console.log(`\nInstalled: ${written} written, ${identical} unchanged${userModified.length ? `, ${userModified.length} kept (locally modified)` : ''}.`);
97
+ console.log(`Merged cp block into: ${claudeMdPath}`);
98
+ if (userModified.length > 0) {
99
+ console.log(`\n⚠ The following slash-command files exist with local modifications and were NOT overwritten:`);
100
+ for (const f of userModified) console.log(` - ${f}`);
101
+ console.log(` Re-run with \`cp install claude --force\` to overwrite them, or delete the local copy first.`);
102
+ }
103
+ console.log(`\nNext steps:`);
104
+ console.log(` 1. Make sure your workflow provider (default: superpowers) is installed.`);
105
+ console.log(` - For Claude Code: /plugin install superpowers@superpowers-marketplace`);
106
+ console.log(` 2. In a project directory: cp init`);
107
+ console.log(` 3. Then in Claude Code: /cp-new-milestone "my first milestone"`);
108
+
109
+ return { written, identical, userModified };
110
+ }
111
+
112
+ function escapeRegex(s) {
113
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
114
+ }
115
+
116
+ module.exports = { install };
@@ -0,0 +1,65 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Shared helpers for harness installers.
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ function listCommandFiles(pluginRoot) {
11
+ const dir = path.join(pluginRoot, 'commands', 'cp');
12
+ return fs
13
+ .readdirSync(dir)
14
+ .filter((f) => f.endsWith('.md'))
15
+ .map((f) => ({ name: f.replace(/\.md$/, ''), src: path.join(dir, f) }));
16
+ }
17
+
18
+ function copyFile(src, dest) {
19
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
20
+ fs.copyFileSync(src, dest);
21
+ }
22
+
23
+ /**
24
+ * Plain write (mkdir + writeFileSync). Use when you DO want to clobber —
25
+ * e.g. an internal cp-only state file we own end-to-end.
26
+ */
27
+ function writeFile(dest, content) {
28
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
29
+ fs.writeFileSync(dest, content);
30
+ }
31
+
32
+ /**
33
+ * Collision-aware write for installer outputs. Returns one of:
34
+ * { status: 'written' } — file did not exist or matches the incoming bytes
35
+ * exactly; either way wrote it (no user edit lost).
36
+ * { status: 'identical' } — file already on disk has identical content. Skipped.
37
+ * { status: 'user-modified' } — file exists with DIFFERENT content and `force`
38
+ * is false. NOT written; existing file untouched.
39
+ *
40
+ * The "identical" check is what makes safe re-installs cheap: idempotent
41
+ * re-runs after `cp update` or version bumps will report `identical` for
42
+ * unchanged files and `written` only for the ones the new release actually
43
+ * changed.
44
+ *
45
+ * v0.3.4 — closes CONCERNS Medium "Installers overwrite existing cp-scoped
46
+ * command/skill files without a collision prompt or merge path."
47
+ */
48
+ function writeFileSafe(dest, content, { force = false } = {}) {
49
+ if (fs.existsSync(dest)) {
50
+ let current;
51
+ try { current = fs.readFileSync(dest, 'utf8'); }
52
+ catch { current = null; }
53
+ if (current === content) return { status: 'identical' };
54
+ if (!force) return { status: 'user-modified' };
55
+ }
56
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
57
+ fs.writeFileSync(dest, content);
58
+ return { status: 'written' };
59
+ }
60
+
61
+ function homeDir() {
62
+ return require('os').homedir();
63
+ }
64
+
65
+ module.exports = { listCommandFiles, copyFile, writeFile, writeFileSafe, homeDir };
@@ -0,0 +1,86 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Installer for GitHub Copilot CLI.
5
+ *
6
+ * Copilot CLI looks for skills in `<repo>/.github/skills/<name>/` (project-scoped)
7
+ * or `~/.copilot/skills/<name>/` (user-scoped). It also reads a custom instruction
8
+ * block injected into the system prompt.
9
+ *
10
+ * Each command becomes a skill named `cp-<name>`, with a SKILL.md that contains
11
+ * the command's instructions. The skill files are harness-agnostic (just markdown);
12
+ * Copilot picks them up by name.
13
+ */
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const { listCommandFiles, writeFile, writeFileSafe, homeDir } = require('./common');
18
+
19
+ function install({ pluginRoot, repoRoot, force = false }) {
20
+ const target = process.env.CP_INSTALL_SCOPE === 'user'
21
+ ? path.join(homeDir(), '.copilot', 'skills')
22
+ : path.join(repoRoot, '.github', 'skills');
23
+
24
+ console.log(`Installing cp commands as Copilot skills into:`);
25
+ console.log(` ${target}`);
26
+
27
+ let written = 0;
28
+ let identical = 0;
29
+ const userModified = [];
30
+ for (const { name, src } of listCommandFiles(pluginRoot)) {
31
+ const skillName = `cp-${name}`;
32
+ const skillDir = path.join(target, skillName);
33
+ const skillMd = path.join(skillDir, 'SKILL.md');
34
+ const body = fs.readFileSync(src, 'utf8');
35
+ const r = writeFileSafe(skillMd, body, { force });
36
+ if (r.status === 'written') { console.log(` + ${skillName}`); written++; }
37
+ else if (r.status === 'identical') { console.log(` = ${skillName} (unchanged)`); identical++; }
38
+ else if (r.status === 'user-modified') { console.log(` ! ${skillName} (LOCALLY MODIFIED — kept)`); userModified.push(skillName); }
39
+ }
40
+
41
+ // The ambient instruction file is cp-owned end-to-end (we rewrite the
42
+ // whole block every install). Apply the same collision rule though so a
43
+ // user who hand-edited it doesn't silently lose changes.
44
+ const ctxDest = process.env.CP_INSTALL_SCOPE === 'user'
45
+ ? path.join(homeDir(), '.copilot', 'context-planning.md')
46
+ : path.join(repoRoot, '.github', 'context-planning.md');
47
+
48
+ const ctxBody = `<!-- context-planning (cp) — managed by cp installer -->
49
+ # Instructions for context-planning (cp)
50
+
51
+ - When the user invokes a \`/cp-*\` slash command (or \`cp-*\` skill), load the
52
+ matching SKILL.md from \`.github/skills/cp-*\`.
53
+ - \`cp\` owns the *state layer* (PROJECT.md / ROADMAP.md / STATE.md / phase dirs).
54
+ It delegates *workflow* to whatever provider is configured in
55
+ \`.planning/config.json\` under the top-level \`cp:\` block (default
56
+ provider: superpowers). The same \`config.json\` is shared with GSD when
57
+ GSD is also installed — they coexist via separate top-level keys.
58
+ - For each role (brainstorm, plan, execute, review, finish), invoke the matching
59
+ provider skill via its own slash-command, then return to cp to record the
60
+ state change.
61
+ - Always update .planning/STATE.md after a phase/quick task is completed; tick
62
+ the matching checkbox in .planning/ROADMAP.md; write SUMMARY.md.
63
+ - Only invoke cp commands when the user explicitly asks. Don't apply cp
64
+ workflows unbidden.
65
+ <!-- /context-planning -->
66
+ `;
67
+ const ctxR = writeFileSafe(ctxDest, ctxBody, { force });
68
+ if (ctxR.status === 'user-modified') userModified.push(path.basename(ctxDest));
69
+
70
+ console.log(`\nInstalled: ${written} written, ${identical} unchanged${userModified.length ? `, ${userModified.length} kept (locally modified)` : ''}.`);
71
+ console.log(`Context file: ${ctxDest} (${ctxR.status})`);
72
+ if (userModified.length > 0) {
73
+ console.log(`\n⚠ The following files exist on disk with local modifications and were NOT overwritten:`);
74
+ for (const f of userModified) console.log(` - ${f}`);
75
+ console.log(` Re-run with \`cp install copilot --force\` to overwrite them, or delete the local copy first.`);
76
+ }
77
+ console.log(`\nNext steps:`);
78
+ console.log(` 1. Make sure your workflow provider (default: superpowers) is installed.`);
79
+ console.log(` copilot plugin install superpowers@superpowers-marketplace`);
80
+ console.log(` 2. In a project directory: cp init`);
81
+ console.log(` 3. Then in Copilot CLI: /cp-new-milestone "my first milestone"`);
82
+
83
+ return { written, identical, userModified };
84
+ }
85
+
86
+ module.exports = { install };
@@ -0,0 +1,120 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Installer for Cursor IDE.
5
+ *
6
+ * Cursor reads project rules from `.cursor/rules/*.mdc` (Markdown with YAML
7
+ * frontmatter). Unlike Claude / Copilot CLI, Cursor has no per-project
8
+ * slash-command extension mechanism — its slash commands are hard-coded.
9
+ * The closest analog is to install each cp command as an INVOKABLE RULE so
10
+ * the user can attach it to their chat with `@cp-<name>` or the rule picker.
11
+ *
12
+ * Layout:
13
+ * .cursor/rules/context-planning.mdc — alwaysApply: true, ambient
14
+ * routing instructions (mirrors the
15
+ * `.github/context-planning.md` we
16
+ * install for Copilot CLI).
17
+ * .cursor/rules/cp-<name>.mdc — alwaysApply: false, one per
18
+ * `commands/cp/*.md`. User pulls
19
+ * them in via `@cp-<name>` or the
20
+ * rule picker.
21
+ *
22
+ * v0.4.2.
23
+ */
24
+
25
+ const fs = require('fs');
26
+ const path = require('path');
27
+ const { listCommandFiles, writeFileSafe, homeDir } = require('./common');
28
+
29
+ // Cursor rule body wrapper: add frontmatter on top of the raw command body.
30
+ // If the source already has YAML frontmatter (most cp slash commands do),
31
+ // strip its `description:` to use as the rule description, and discard the
32
+ // rest — Cursor only understands its own keys.
33
+ function buildRule({ name, body, alwaysApply }) {
34
+ let description = `cp slash-command: ${name}`;
35
+ let bodyOnly = body;
36
+ const fm = body.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
37
+ if (fm) {
38
+ const m = fm[1].match(/^description:\s*(.+)$/m);
39
+ if (m) description = m[1].trim().replace(/^["']|["']$/g, '');
40
+ bodyOnly = fm[2];
41
+ }
42
+ const yaml = [
43
+ '---',
44
+ `description: ${JSON.stringify(description)}`,
45
+ `alwaysApply: ${alwaysApply ? 'true' : 'false'}`,
46
+ '---',
47
+ '',
48
+ ].join('\n');
49
+ return yaml + bodyOnly;
50
+ }
51
+
52
+ function install({ pluginRoot, repoRoot, force = false }) {
53
+ const target = process.env.CP_INSTALL_SCOPE === 'user'
54
+ ? path.join(homeDir(), '.cursor', 'rules')
55
+ : path.join(repoRoot, '.cursor', 'rules');
56
+
57
+ console.log(`Installing cp commands as Cursor rules into:`);
58
+ console.log(` ${target}`);
59
+
60
+ let written = 0;
61
+ let identical = 0;
62
+ const userModified = [];
63
+
64
+ for (const { name, src } of listCommandFiles(pluginRoot)) {
65
+ const ruleName = `cp-${name}`;
66
+ const ruleFile = path.join(target, `${ruleName}.mdc`);
67
+ const rawBody = fs.readFileSync(src, 'utf8');
68
+ const rule = buildRule({ name, body: rawBody, alwaysApply: false });
69
+ const r = writeFileSafe(ruleFile, rule, { force });
70
+ if (r.status === 'written') { console.log(` + ${ruleName}.mdc`); written++; }
71
+ else if (r.status === 'identical') { console.log(` = ${ruleName}.mdc (unchanged)`); identical++; }
72
+ else if (r.status === 'user-modified') { console.log(` ! ${ruleName}.mdc (LOCALLY MODIFIED — kept)`); userModified.push(`${ruleName}.mdc`); }
73
+ }
74
+
75
+ // Ambient routing rule — alwaysApply so cp routing happens without the
76
+ // user having to remember to @-attach anything.
77
+ const ctxFile = path.join(target, 'context-planning.mdc');
78
+ const ctxBody = `<!-- context-planning (cp) — managed by cp installer -->
79
+ # Instructions for context-planning (cp)
80
+
81
+ When the user invokes a \`/cp-*\` or \`cp-*\` workflow, route to the matching
82
+ \`cp-<name>\` Cursor rule (under \`.cursor/rules/\`). Attach it to context if
83
+ not already attached, and follow its body verbatim.
84
+
85
+ \`cp\` owns the **state layer** (\`.planning/PROJECT.md\`, \`ROADMAP.md\`,
86
+ \`STATE.md\`, phase dirs). It delegates **workflow** to whatever provider is
87
+ configured in \`.planning/config.json\` under the top-level \`cp:\` block
88
+ (default provider: superpowers).
89
+
90
+ For each role (brainstorm, plan, execute, review, finish, etc.) invoke the
91
+ provider's primitive (or do the work in-chat if no provider is configured),
92
+ then return to cp to record the state change. Always update
93
+ \`.planning/STATE.md\` after a phase / quick task is completed; tick the
94
+ matching checkbox in \`.planning/ROADMAP.md\`; write the \`SUMMARY.md\`.
95
+
96
+ Only invoke cp commands when the user explicitly asks. Don't apply cp
97
+ workflows unbidden.
98
+ <!-- /context-planning -->
99
+ `;
100
+ const ctxRule = buildRule({ name: 'context-planning', body: ctxBody, alwaysApply: true });
101
+ const ctxR = writeFileSafe(ctxFile, ctxRule, { force });
102
+ if (ctxR.status === 'user-modified') userModified.push('context-planning.mdc');
103
+
104
+ console.log(`\nInstalled: ${written} written, ${identical} unchanged${userModified.length ? `, ${userModified.length} kept (locally modified)` : ''}.`);
105
+ console.log(`Ambient rule: ${ctxFile} (${ctxR.status})`);
106
+ if (userModified.length > 0) {
107
+ console.log(`\n⚠ The following rule files exist on disk with local modifications and were NOT overwritten:`);
108
+ for (const f of userModified) console.log(` - ${f}`);
109
+ console.log(` Re-run with \`cp install cursor --force\` to overwrite them, or delete the local copy first.`);
110
+ }
111
+ console.log(`\nNext steps:`);
112
+ console.log(` 1. Reload Cursor (or run "Cursor: Reload Window") to pick up the new rules.`);
113
+ console.log(` 2. In a project directory: cp init`);
114
+ console.log(` 3. In Cursor chat: @cp-new-milestone "my first milestone"`);
115
+ console.log(` (or any other @cp-<name> rule)`);
116
+
117
+ return { written, identical, userModified };
118
+ }
119
+
120
+ module.exports = { install, buildRule };
@@ -0,0 +1,50 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Installer for the echo-provider schema-test stub.
5
+ *
6
+ * Plants a minimal SKILL.md at .planning/providers/echo-provider/skills/echo/SKILL.md
7
+ * so the echo-provider entry in config.json can be detected and resolved.
8
+ *
9
+ * Usage: cp install echo-provider [--local]
10
+ * (--local is implicit; echo-provider always installs to the project)
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const { planningDir, repoRoot } = require('../lib/paths');
16
+
17
+ const SKILL_CONTENT = `---
18
+ name: echo
19
+ description: Schema-test stub skill. Echoes the role name. Not for end users.
20
+ ---
21
+
22
+ # Echo skill
23
+
24
+ This is a schema-test stub installed by \`cp install echo-provider\`.
25
+ It exists solely to prove that cp's provider detection schema works
26
+ with more than one provider. It does nothing useful.
27
+
28
+ When invoked for any role (plan, execute, brainstorm, etc.), simply
29
+ acknowledge that the echo-provider was reached and the role name was
30
+ received. Do not perform any actual work.
31
+ `;
32
+
33
+ function install() {
34
+ const root = repoRoot();
35
+ const providerDir = path.join(planningDir(root), 'providers', 'echo-provider');
36
+ const skillDir = path.join(providerDir, 'skills', 'echo');
37
+ const skillPath = path.join(skillDir, 'SKILL.md');
38
+
39
+ fs.mkdirSync(skillDir, { recursive: true });
40
+ fs.writeFileSync(skillPath, SKILL_CONTENT);
41
+
42
+ const results = [
43
+ { file: path.relative(root, skillPath), status: 'created' },
44
+ { file: path.relative(root, providerDir), status: 'detectable' },
45
+ ];
46
+
47
+ return { results };
48
+ }
49
+
50
+ module.exports = { install };
@@ -0,0 +1,169 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * lib/codebase-mapper.js — state-layer support for `/cp-map-codebase`.
5
+ *
6
+ * The cp-native equivalent of GSD's `gsd-map-codebase`. Owns:
7
+ * - scaffolding the `.planning/codebase/` directory with 7 stub files
8
+ * - reporting which docs exist + their freshness for `/cp-map-codebase`
9
+ * verification & for `cp doctor`
10
+ *
11
+ * The *exploration* and *content writing* is delegated to whatever sub-agent
12
+ * mechanism the host harness exposes (Copilot CLI's `task` tool, Claude
13
+ * Code's Task tool, etc.). That dispatch lives in `commands/cp/map-codebase.md`.
14
+ * This module deliberately does NOT call any LLM — it's pure file I/O so it
15
+ * can be unit-tested without a model.
16
+ *
17
+ * Output layout (matches GSD exactly, so `cp gsd-import` stays clean):
18
+ * .planning/codebase/
19
+ * STACK.md (tech focus)
20
+ * INTEGRATIONS.md (tech focus)
21
+ * ARCHITECTURE.md (arch focus)
22
+ * STRUCTURE.md (arch focus)
23
+ * CONVENTIONS.md (quality focus)
24
+ * TESTING.md (quality focus)
25
+ * CONCERNS.md (concerns focus)
26
+ */
27
+
28
+ const fs = require('fs');
29
+ const path = require('path');
30
+
31
+ const paths = require('./paths');
32
+
33
+ const DOCS = [
34
+ { file: 'STACK.md', focus: 'tech', template: 'codebase/STACK.md' },
35
+ { file: 'INTEGRATIONS.md', focus: 'tech', template: 'codebase/INTEGRATIONS.md' },
36
+ { file: 'ARCHITECTURE.md', focus: 'arch', template: 'codebase/ARCHITECTURE.md' },
37
+ { file: 'STRUCTURE.md', focus: 'arch', template: 'codebase/STRUCTURE.md' },
38
+ { file: 'CONVENTIONS.md', focus: 'quality', template: 'codebase/CONVENTIONS.md' },
39
+ { file: 'TESTING.md', focus: 'quality', template: 'codebase/TESTING.md' },
40
+ { file: 'CONCERNS.md', focus: 'concerns', template: 'codebase/CONCERNS.md' },
41
+ ];
42
+
43
+ /**
44
+ * The four focus areas a /cp-map-codebase dispatch will use, with the
45
+ * documents each one owns. Consumed by the slash command to build the
46
+ * 4 parallel agent prompts.
47
+ */
48
+ const FOCUS_AREAS = [
49
+ { focus: 'tech', docs: ['STACK.md', 'INTEGRATIONS.md'] },
50
+ { focus: 'arch', docs: ['ARCHITECTURE.md', 'STRUCTURE.md'] },
51
+ { focus: 'quality', docs: ['CONVENTIONS.md', 'TESTING.md'] },
52
+ { focus: 'concerns', docs: ['CONCERNS.md'] },
53
+ ];
54
+
55
+ function codebaseDir(root) {
56
+ return path.join(paths.planningDir(root), 'codebase');
57
+ }
58
+
59
+ function renderTemplate(text, vars) {
60
+ return Object.entries(vars).reduce(
61
+ (acc, [k, v]) => acc.replace(new RegExp(`\\{\\{${k}\\}\\}`, 'g'), String(v)),
62
+ text
63
+ );
64
+ }
65
+
66
+ /**
67
+ * Create `.planning/codebase/` and seed 7 stub files from the templates.
68
+ *
69
+ * Returns { ok, actions, codebaseDir, created: [filenames], skipped: [filenames],
70
+ * dryRun? }.
71
+ *
72
+ * options:
73
+ * - dryRun: boolean — return actions without writing
74
+ * - force: boolean — overwrite existing stubs (default false)
75
+ * - today: ISO date string (for tests)
76
+ *
77
+ * Refuses to overwrite any existing file unless force=true. Does NOT refuse
78
+ * if the directory itself exists — only individual files.
79
+ */
80
+ function scaffoldCodebase(root, options = {}) {
81
+ const { dryRun = false, force = false, today: todayIso } = options;
82
+
83
+ const planning = paths.planningDir(root);
84
+ if (!fs.existsSync(planning)) {
85
+ throw new Error(`.planning/ not found at ${planning}. Run \`cp init\` first.`);
86
+ }
87
+
88
+ const cbDir = codebaseDir(root);
89
+ const dateStr = todayIso || new Date().toISOString().slice(0, 10);
90
+
91
+ const actions = [];
92
+ const created = [];
93
+ const skipped = [];
94
+
95
+ if (!fs.existsSync(cbDir)) {
96
+ actions.push({ kind: 'mkdir', path: cbDir });
97
+ }
98
+
99
+ for (const doc of DOCS) {
100
+ const dest = path.join(cbDir, doc.file);
101
+ if (fs.existsSync(dest) && !force) {
102
+ skipped.push(doc.file);
103
+ continue;
104
+ }
105
+ const tplText = paths.readTemplate(doc.template);
106
+ const rendered = renderTemplate(tplText, { DATE: dateStr });
107
+ actions.push({ kind: 'write', path: dest, content: rendered, focus: doc.focus });
108
+ created.push(doc.file);
109
+ }
110
+
111
+ if (!dryRun) {
112
+ for (const a of actions) {
113
+ if (a.kind === 'mkdir') fs.mkdirSync(a.path, { recursive: true });
114
+ else if (a.kind === 'write') {
115
+ fs.mkdirSync(path.dirname(a.path), { recursive: true });
116
+ fs.writeFileSync(a.path, a.content);
117
+ }
118
+ }
119
+ }
120
+
121
+ return {
122
+ ok: true,
123
+ codebaseDir: cbDir,
124
+ actions,
125
+ created,
126
+ skipped,
127
+ dryRun: dryRun || undefined,
128
+ };
129
+ }
130
+
131
+ /**
132
+ * Inventory `.planning/codebase/`. Returns one row per expected doc with
133
+ * existence, line count, byte size, and a "looks-stub" heuristic.
134
+ *
135
+ * A doc looks like a stub if it's <= 40 lines OR contains the marker
136
+ * "fill via `/cp-map-codebase`" — both true for freshly-scaffolded files.
137
+ */
138
+ function codebaseStatus(root) {
139
+ const cbDir = codebaseDir(root);
140
+ const dirExists = fs.existsSync(cbDir);
141
+
142
+ const rows = DOCS.map((doc) => {
143
+ const p = path.join(cbDir, doc.file);
144
+ const exists = fs.existsSync(p);
145
+ if (!exists) {
146
+ return { file: doc.file, focus: doc.focus, exists: false, lines: 0, bytes: 0, looksStub: null };
147
+ }
148
+ const txt = fs.readFileSync(p, 'utf8');
149
+ const lines = txt.split('\n').length;
150
+ const bytes = Buffer.byteLength(txt, 'utf8');
151
+ const looksStub =
152
+ lines <= 40 ||
153
+ txt.includes('fill via `/cp-map-codebase`');
154
+ return { file: doc.file, focus: doc.focus, exists: true, lines, bytes, looksStub };
155
+ });
156
+
157
+ const allExist = rows.every((r) => r.exists);
158
+ const allFilled = rows.every((r) => r.exists && r.looksStub === false);
159
+
160
+ return { dirExists, codebaseDir: cbDir, rows, allExist, allFilled };
161
+ }
162
+
163
+ module.exports = {
164
+ DOCS,
165
+ FOCUS_AREAS,
166
+ codebaseDir,
167
+ scaffoldCodebase,
168
+ codebaseStatus,
169
+ };