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,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
|
+
};
|