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,41 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { repoRoot } = require('../../lib/paths');
|
|
5
|
+
const codebaseMapper = require('../../lib/codebase-mapper');
|
|
6
|
+
|
|
7
|
+
function run(args = []) {
|
|
8
|
+
const root = repoRoot();
|
|
9
|
+
let json = false;
|
|
10
|
+
for (const a of args) {
|
|
11
|
+
if (a === '--json') json = true;
|
|
12
|
+
else { console.error(`unexpected arg: ${a}`); process.exit(2); }
|
|
13
|
+
}
|
|
14
|
+
const r = codebaseMapper.codebaseStatus(root);
|
|
15
|
+
if (json) {
|
|
16
|
+
console.log(JSON.stringify(r, null, 2));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (!r.dirExists) {
|
|
20
|
+
console.log(`.planning/codebase/ not present — run \`cp scaffold-codebase\` first.`);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
console.log(`Codebase dir: ${path.relative(root, r.codebaseDir)}`);
|
|
24
|
+
console.log('');
|
|
25
|
+
console.log(' status focus file lines bytes');
|
|
26
|
+
console.log(' ------ -------- ---------------- ------ ------');
|
|
27
|
+
for (const row of r.rows) {
|
|
28
|
+
let status;
|
|
29
|
+
if (!row.exists) status = 'missing';
|
|
30
|
+
else if (row.looksStub) status = 'stub ';
|
|
31
|
+
else status = 'filled ';
|
|
32
|
+
const lines = row.exists ? String(row.lines).padStart(5) : ' -';
|
|
33
|
+
const bytes = row.exists ? String(row.bytes).padStart(5) : ' -';
|
|
34
|
+
console.log(` ${status} ${row.focus.padEnd(8)} ${row.file.padEnd(16)} ${lines} ${bytes}`);
|
|
35
|
+
}
|
|
36
|
+
console.log('');
|
|
37
|
+
console.log(`All present: ${r.allExist ? '✓' : '✗'}`);
|
|
38
|
+
console.log(`All filled: ${r.allFilled ? '✓' : '✗ (run /cp-map-codebase)'}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = { name: 'codebase-status', run };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { repoRoot } = require('../../lib/paths');
|
|
5
|
+
const lifecycle = require('../../lib/lifecycle');
|
|
6
|
+
|
|
7
|
+
function run(args = []) {
|
|
8
|
+
const root = repoRoot();
|
|
9
|
+
let name = null;
|
|
10
|
+
let dryRun = false;
|
|
11
|
+
let noCommit = false;
|
|
12
|
+
let json = false;
|
|
13
|
+
for (let i = 0; i < args.length; i++) {
|
|
14
|
+
const a = args[i];
|
|
15
|
+
if (a === '--dry-run') dryRun = true;
|
|
16
|
+
else if (a === '--no-commit') noCommit = true;
|
|
17
|
+
else if (a === '--json') json = true;
|
|
18
|
+
else if (a.startsWith('-')) { console.error(`unknown option: ${a}`); process.exit(2); }
|
|
19
|
+
else if (!name) name = a;
|
|
20
|
+
else { console.error(`unexpected arg: ${a}`); process.exit(2); }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const r = lifecycle.completeMilestone(root, { name, dryRun, noCommit });
|
|
24
|
+
|
|
25
|
+
if (json) {
|
|
26
|
+
console.log(JSON.stringify(r, null, 2));
|
|
27
|
+
process.exit(r.ok ? 0 : 1);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!r.ok) {
|
|
32
|
+
console.error(`complete-milestone: ${r.reason}`);
|
|
33
|
+
if (r.reason === 'incomplete') {
|
|
34
|
+
console.error(`\nMilestone "${r.milestone}" still has work to do:`);
|
|
35
|
+
for (const rep of r.verify.reports.filter(x => !x.ok)) {
|
|
36
|
+
console.error(` Phase ${rep.phaseNum} ${rep.name}: plans ${rep.plansDone}/${rep.plansTotal} done; missing SUMMARY: ${rep.summariesMissing.join(', ') || '—'}`);
|
|
37
|
+
}
|
|
38
|
+
} else if (r.hint) {
|
|
39
|
+
console.error(r.hint);
|
|
40
|
+
}
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log(`Milestone: ${r.milestone}`);
|
|
45
|
+
console.log(`Phases: ${r.phases.join(', ')}`);
|
|
46
|
+
console.log(`Subsystems: ${r.agg.subsystems.join(', ') || '—'}`);
|
|
47
|
+
console.log(`Files: ${r.agg.filesCreated.length} created, ${r.agg.filesModified.length} modified`);
|
|
48
|
+
console.log(`\nActions${dryRun ? ' (dry-run)' : ''}:`);
|
|
49
|
+
for (const a of r.actions) {
|
|
50
|
+
const rel = path.relative(root, a.path);
|
|
51
|
+
const mark = a.kind === 'write' ? '✓' : a.kind === 'delete' ? '✗' : '·';
|
|
52
|
+
console.log(` ${mark} ${a.kind.padEnd(6)} ${rel}${a.reason ? ' (' + a.reason + ')' : ''}`);
|
|
53
|
+
}
|
|
54
|
+
if (!dryRun && r.commit) console.log(`\nCommitted: ${r.commit}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = { name: 'complete-milestone', run };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const { repoRoot } = require('../../lib/paths');
|
|
5
|
+
const provider = require('../../lib/provider');
|
|
6
|
+
|
|
7
|
+
function run(args = []) {
|
|
8
|
+
const root = repoRoot();
|
|
9
|
+
const sub = args[0];
|
|
10
|
+
|
|
11
|
+
// refresh reads raw config directly — must NOT go through loadConfig's auto-heal
|
|
12
|
+
if (sub === 'refresh') {
|
|
13
|
+
const dryRun = args.includes('--dry-run');
|
|
14
|
+
const p = provider.configPath(root);
|
|
15
|
+
if (!fs.existsSync(p)) {
|
|
16
|
+
console.error('cp: no .planning/config.json found — run `cp init` first');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
const raw = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
20
|
+
const defaults = provider.loadDefaults();
|
|
21
|
+
const { mergeCpDefaults } = require('../../lib/merge');
|
|
22
|
+
const merged = mergeCpDefaults(raw, defaults);
|
|
23
|
+
if (!merged.changed) {
|
|
24
|
+
console.log('cp: config is already up to date with upstream defaults.');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
for (const c of merged.plannedChanges) {
|
|
28
|
+
console.log(`cp: ${dryRun ? 'would' : 'will'} add: ${c}`);
|
|
29
|
+
}
|
|
30
|
+
if (dryRun) {
|
|
31
|
+
console.log('(no changes written — use without --dry-run to apply)');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
fs.writeFileSync(p, JSON.stringify(merged.cfg, null, 2) + '\n');
|
|
35
|
+
console.log(`cp: refreshed .planning/config.json with ${merged.summary}`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// All other subcommands need loaded config
|
|
40
|
+
const cfg = provider.loadConfig(root);
|
|
41
|
+
if (sub === 'get') {
|
|
42
|
+
const key = args[1];
|
|
43
|
+
if (!key) {
|
|
44
|
+
console.log(JSON.stringify(cfg.cp || {}, null, 2));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const v = provider.cpGet(cfg, key);
|
|
48
|
+
console.log(v === undefined ? '' : typeof v === 'object' ? JSON.stringify(v, null, 2) : v);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (sub === 'set') {
|
|
52
|
+
const key = args[1];
|
|
53
|
+
let val = args[2];
|
|
54
|
+
if (!key) {
|
|
55
|
+
console.error('Usage: cp config set <key> <value>');
|
|
56
|
+
process.exit(2);
|
|
57
|
+
}
|
|
58
|
+
if (val === 'true') val = true;
|
|
59
|
+
else if (val === 'false') val = false;
|
|
60
|
+
else if (val !== '' && !isNaN(Number(val))) val = Number(val);
|
|
61
|
+
provider.cpSet(cfg, key, val);
|
|
62
|
+
provider.saveConfig(cfg, root);
|
|
63
|
+
console.log(`set cp.${key} = ${JSON.stringify(val)}`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
console.error('Usage: cp config get [<key>] | set <key> <value> | refresh [--dry-run]');
|
|
67
|
+
process.exit(2);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = { name: 'config', run };
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const pkg = require('../../package.json');
|
|
6
|
+
const { repoRoot, planningDir } = require('../../lib/paths');
|
|
7
|
+
const provider = require('../../lib/provider');
|
|
8
|
+
const compat = require('../../lib/gsd-compat');
|
|
9
|
+
|
|
10
|
+
function run(args = []) {
|
|
11
|
+
const jsonMode = args.includes('--json');
|
|
12
|
+
const quiet = args.includes('--quiet');
|
|
13
|
+
const root = repoRoot();
|
|
14
|
+
const cfg = provider.loadConfig(root);
|
|
15
|
+
const cpBlock = cfg.cp || {};
|
|
16
|
+
const configured = cpBlock.workflow_provider || 'superpowers';
|
|
17
|
+
const detect = require('../../lib/detect');
|
|
18
|
+
const report = detect.detectAllInstalled(cfg);
|
|
19
|
+
|
|
20
|
+
// --json: machine-parsable full report
|
|
21
|
+
if (jsonMode) {
|
|
22
|
+
const roles = [
|
|
23
|
+
'brainstorm', 'plan', 'execute', 'review',
|
|
24
|
+
'finish', 'worktree', 'tdd', 'debug', 'verify',
|
|
25
|
+
];
|
|
26
|
+
const resolvedRoles = {};
|
|
27
|
+
for (const role of roles) {
|
|
28
|
+
const rr = provider.resolveSkill(role, root);
|
|
29
|
+
resolvedRoles[role] = {
|
|
30
|
+
provider: rr.name,
|
|
31
|
+
skill: rr.skill,
|
|
32
|
+
installed: rr.installed,
|
|
33
|
+
fallback: !!rr.fallback,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const json = {
|
|
37
|
+
version: pkg.version,
|
|
38
|
+
root,
|
|
39
|
+
configured,
|
|
40
|
+
harnesses: report.harnesses,
|
|
41
|
+
providers: report.providers,
|
|
42
|
+
roles: resolvedRoles,
|
|
43
|
+
};
|
|
44
|
+
console.log(JSON.stringify(json, null, 2));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// --quiet: minimal output (configured + roles only)
|
|
49
|
+
if (quiet) {
|
|
50
|
+
console.log(`Configured: ${configured}`);
|
|
51
|
+
const roles = [
|
|
52
|
+
'brainstorm', 'plan', 'execute', 'review',
|
|
53
|
+
'finish', 'worktree', 'tdd', 'debug', 'verify',
|
|
54
|
+
];
|
|
55
|
+
for (const role of roles) {
|
|
56
|
+
const rr = provider.resolveSkill(role, root);
|
|
57
|
+
const tag = rr.fallback ? ` [fallback]` : '';
|
|
58
|
+
const skill = rr.skill || '(none)';
|
|
59
|
+
const mark = rr.installed ? '✓' : '✗';
|
|
60
|
+
console.log(` ${mark} ${role.padEnd(10)} -> ${rr.name}/${skill}${tag}`);
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Full sectioned output
|
|
66
|
+
const planningPresent = fs.existsSync(planningDir(root));
|
|
67
|
+
const schemaVersion = cpBlock.version || 1;
|
|
68
|
+
|
|
69
|
+
console.log(`cp v${pkg.version} (invocable as \`cplan\` or \`cp\`)`);
|
|
70
|
+
console.log(`Repo root: ${root}`);
|
|
71
|
+
console.log(
|
|
72
|
+
`.planning/: ${planningPresent ? 'present' : 'missing (run `cp init`)'}`
|
|
73
|
+
);
|
|
74
|
+
console.log(`Config: ${provider.configPath(root)} (schema v${schemaVersion})`);
|
|
75
|
+
|
|
76
|
+
// Section 1: Harnesses
|
|
77
|
+
console.log(`\nHarnesses detected:`);
|
|
78
|
+
for (const h of report.harnesses) {
|
|
79
|
+
if (!h.scannedRoots.length || h.scannedRoots.every((r) => !r.root)) {
|
|
80
|
+
console.log(` — ${h.name.padEnd(10)} (file-based — no plugin slot)`);
|
|
81
|
+
} else {
|
|
82
|
+
const rootSummaries = h.scannedRoots.map((r) => {
|
|
83
|
+
const shortRoot = r.root.replace(/^~\//, '~/');
|
|
84
|
+
return `${shortRoot} (${r.expanded.length} plugins)`;
|
|
85
|
+
}).join(', ');
|
|
86
|
+
const mark = h.pluginCount > 0 ? '✓' : '✗';
|
|
87
|
+
if (h.pluginCount > 0) {
|
|
88
|
+
console.log(` ${mark} ${h.name.padEnd(10)} ${rootSummaries}`);
|
|
89
|
+
} else {
|
|
90
|
+
console.log(` ${mark} ${h.name.padEnd(10)} (no plugins found at ${h.scannedRoots.map((r) => r.root).join(', ')})`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Section 2: Providers
|
|
96
|
+
console.log(`\nProviders detected:`);
|
|
97
|
+
for (const p of report.providers) {
|
|
98
|
+
if (p.installed) {
|
|
99
|
+
const hitDescs = p.hits.map((hit) => {
|
|
100
|
+
if (hit.source === 'always') return '(always available)';
|
|
101
|
+
return `via ${hit.via} @ ${hit.evidence ? path.basename(path.dirname(hit.evidence)) + '/' + path.basename(hit.evidence) : '?'}`;
|
|
102
|
+
});
|
|
103
|
+
console.log(` ✓ ${p.name.padEnd(18)} ${hitDescs.join(', ')}`);
|
|
104
|
+
} else {
|
|
105
|
+
console.log(` ✗ ${p.name.padEnd(18)} (not detected)`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Section 3: Configured
|
|
110
|
+
console.log(`\nConfigured workflow_provider: ${configured} [\`cp config set workflow_provider <name>\` to switch]`);
|
|
111
|
+
|
|
112
|
+
// Section 4: Roles
|
|
113
|
+
const roles = [
|
|
114
|
+
'brainstorm', 'plan', 'execute', 'review',
|
|
115
|
+
'finish', 'worktree', 'tdd', 'debug', 'verify',
|
|
116
|
+
];
|
|
117
|
+
console.log(`\nRoles → resolved skill:`);
|
|
118
|
+
for (const role of roles) {
|
|
119
|
+
const rr = provider.resolveSkill(role, root);
|
|
120
|
+
const tag = rr.fallback ? ` [fallback from missing ${rr.primaryMissing}]` : '';
|
|
121
|
+
const skill = rr.skill || '(no mapping)';
|
|
122
|
+
const mark = rr.installed ? '✓' : '✗';
|
|
123
|
+
console.log(` ${mark} ${role.padEnd(10)} -> ${rr.name}/${skill}${tag}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Section 5: GSD compat
|
|
127
|
+
const r = compat.report(root);
|
|
128
|
+
console.log(`\nGSD compatibility:`);
|
|
129
|
+
console.log(` cp-aware config: ${r.cpAware ? '✓' : '✗'}`);
|
|
130
|
+
console.log(` GSD sentinels: ${r.gsdProject ? '✓ detected' : '— none'}`);
|
|
131
|
+
console.log(` shared files: ${r.sharedFiles.length ? r.sharedFiles.join(', ') : '— none'}`);
|
|
132
|
+
console.log(` phase dirs: ${r.phases.length}`);
|
|
133
|
+
if (r.warnings.length) {
|
|
134
|
+
console.log(` warnings:`);
|
|
135
|
+
for (const w of r.warnings) console.log(` - ${w}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = { name: 'doctor', run };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { repoRoot } = require('../../lib/paths');
|
|
6
|
+
const importer = require('../../lib/import');
|
|
7
|
+
const init = require('./init');
|
|
8
|
+
|
|
9
|
+
function resolveAuditRoot(rootArg) {
|
|
10
|
+
if (!rootArg) return repoRoot();
|
|
11
|
+
const abs = path.resolve(rootArg);
|
|
12
|
+
if (!fs.existsSync(abs)) {
|
|
13
|
+
console.error(`--root path does not exist: ${abs}`);
|
|
14
|
+
process.exit(2);
|
|
15
|
+
}
|
|
16
|
+
// Walk up from abs looking for .git or .planning, like repoRoot() does.
|
|
17
|
+
let dir = abs;
|
|
18
|
+
for (let i = 0; i < 12; i++) {
|
|
19
|
+
if (fs.existsSync(path.join(dir, '.git')) || fs.existsSync(path.join(dir, '.planning'))) {
|
|
20
|
+
return dir;
|
|
21
|
+
}
|
|
22
|
+
const parent = path.dirname(dir);
|
|
23
|
+
if (parent === dir) break;
|
|
24
|
+
dir = parent;
|
|
25
|
+
}
|
|
26
|
+
return abs;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function run(args = []) {
|
|
30
|
+
let rootArg = null;
|
|
31
|
+
let json = false;
|
|
32
|
+
let apply = false;
|
|
33
|
+
for (let i = 0; i < args.length; i++) {
|
|
34
|
+
const a = args[i];
|
|
35
|
+
if (a === '--root') rootArg = args[++i];
|
|
36
|
+
else if (a === '--json') json = true;
|
|
37
|
+
else if (a === '--apply') apply = true;
|
|
38
|
+
else if (a === '-h' || a === '--help') {
|
|
39
|
+
console.log(`Usage: cp gsd-import [--root <dir>] [--json] [--apply]
|
|
40
|
+
|
|
41
|
+
Read-only audit of a planning project (defaults to the current repo). Reports
|
|
42
|
+
GSD/cp compatibility, sentinel files, phase inventory, frontmatter health, and
|
|
43
|
+
what \`cp init\` would change. Never modifies anything unless --apply.
|
|
44
|
+
|
|
45
|
+
Options:
|
|
46
|
+
--root <dir> Audit <dir> instead of the current repo (still searches up
|
|
47
|
+
from <dir> for a .git or .planning marker)
|
|
48
|
+
--json Emit the raw report as JSON instead of human-readable text
|
|
49
|
+
--apply After printing the audit, run \`cp init\` against the target
|
|
50
|
+
root (additive only — GSD files are never rewritten)
|
|
51
|
+
|
|
52
|
+
Exit codes:
|
|
53
|
+
0 clean / nothing to do
|
|
54
|
+
1 errors found (parse failures or required files missing)
|
|
55
|
+
2 changes pending (run with --apply or \`cp init\` to apply)
|
|
56
|
+
`);
|
|
57
|
+
return;
|
|
58
|
+
} else {
|
|
59
|
+
console.error(`unknown gsd-import option: ${a}`);
|
|
60
|
+
process.exit(2);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const target = resolveAuditRoot(rootArg);
|
|
65
|
+
const report = importer.audit(target);
|
|
66
|
+
|
|
67
|
+
if (json) {
|
|
68
|
+
console.log(JSON.stringify(report, null, 2));
|
|
69
|
+
} else {
|
|
70
|
+
process.stdout.write(importer.render(report));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (apply) {
|
|
74
|
+
console.log('');
|
|
75
|
+
console.log(`--apply: running \`cp init\` against ${target} ...`);
|
|
76
|
+
console.log('');
|
|
77
|
+
const prevCwd = process.cwd();
|
|
78
|
+
process.chdir(target);
|
|
79
|
+
try {
|
|
80
|
+
init.run();
|
|
81
|
+
} finally {
|
|
82
|
+
process.chdir(prevCwd);
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
process.exit(importer.exitCode(report));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = { name: 'gsd-import', run };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { repoRoot } = require('../../lib/paths');
|
|
5
|
+
const lifecycle = require('../../lib/lifecycle');
|
|
6
|
+
const inbox = require('../../lib/inbox');
|
|
7
|
+
|
|
8
|
+
function run(args = []) {
|
|
9
|
+
let json = false;
|
|
10
|
+
let showAll = false;
|
|
11
|
+
let tickIdx = null;
|
|
12
|
+
let note = null;
|
|
13
|
+
let noCommit = false;
|
|
14
|
+
for (let i = 0; i < args.length; i++) {
|
|
15
|
+
const a = args[i];
|
|
16
|
+
if (a === '--json') json = true;
|
|
17
|
+
else if (a === '--all') showAll = true;
|
|
18
|
+
else if (a === '--no-commit') noCommit = true;
|
|
19
|
+
else if (a === '--tick') {
|
|
20
|
+
tickIdx = args[++i];
|
|
21
|
+
if (tickIdx == null) { console.error('--tick requires <N>'); process.exit(2); }
|
|
22
|
+
} else if (a === '--note') {
|
|
23
|
+
note = args[++i];
|
|
24
|
+
if (note == null) { console.error('--note requires <destination>'); process.exit(2); }
|
|
25
|
+
} else { console.error(`unknown option: ${a}`); process.exit(2); }
|
|
26
|
+
}
|
|
27
|
+
const root = repoRoot();
|
|
28
|
+
|
|
29
|
+
if (tickIdx !== null) {
|
|
30
|
+
let r;
|
|
31
|
+
try { r = inbox.markTriaged(root, tickIdx, note); }
|
|
32
|
+
catch (e) { console.error(`inbox: ${e.message}`); process.exit(1); }
|
|
33
|
+
lifecycle.writeBatch(r.actions);
|
|
34
|
+
const destPart = r.item.destination ? ` → ${r.item.destination}` : '';
|
|
35
|
+
console.log(`✓ triaged${destPart} [${r.item.ts}] ${r.item.text}`);
|
|
36
|
+
if (!noCommit) {
|
|
37
|
+
const commit = lifecycle.gitCommit(root, `cp: triage inbox item${destPart}`, {
|
|
38
|
+
paths: lifecycle.pathsFromActions(r.actions),
|
|
39
|
+
});
|
|
40
|
+
if (commit) console.log(`committed ${commit}`);
|
|
41
|
+
}
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const state = inbox.listInbox(root);
|
|
46
|
+
if (json) {
|
|
47
|
+
console.log(JSON.stringify(state, null, 2));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!state.exists) {
|
|
52
|
+
console.log(`Inbox is empty (no ${path.relative(root, state.path)} yet).`);
|
|
53
|
+
console.log(`Add an item: cp capture "your idea here"`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
console.log(`Inbox: ${path.relative(root, state.path)}`);
|
|
58
|
+
console.log(`Open: ${state.open.length} Triaged: ${state.triaged.length}`);
|
|
59
|
+
console.log('');
|
|
60
|
+
if (state.open.length === 0) {
|
|
61
|
+
console.log(' (no open items — capture a new one with `cp capture "..."`)');
|
|
62
|
+
} else {
|
|
63
|
+
console.log('## Open');
|
|
64
|
+
for (const it of state.open) {
|
|
65
|
+
console.log(` ${String(it.idx).padStart(3)} [${it.ts}] ${it.text}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (showAll && state.triaged.length > 0) {
|
|
69
|
+
console.log('');
|
|
70
|
+
console.log('## Triaged');
|
|
71
|
+
for (const it of state.triaged) {
|
|
72
|
+
const dest = it.destination ? `→ ${it.destination}` : '→';
|
|
73
|
+
console.log(` ${String(it.idx).padStart(3)} [${it.ts}] ${dest} ${it.text}`);
|
|
74
|
+
}
|
|
75
|
+
} else if (!showAll && state.triaged.length > 0) {
|
|
76
|
+
console.log('');
|
|
77
|
+
console.log(`(${state.triaged.length} triaged item(s) hidden — use \`cp inbox --all\`)`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = { name: 'inbox', run };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Command registry — single source of truth for `cp <name>` lookup.
|
|
5
|
+
*
|
|
6
|
+
* Each module exports `{ name, run(args) }`.
|
|
7
|
+
*
|
|
8
|
+
* To add a new command:
|
|
9
|
+
* 1. Drop a file in this directory exporting `{ name, run }`.
|
|
10
|
+
* 2. Add an entry below.
|
|
11
|
+
* 3. Update `bin/commands/_usage.js` if it should appear in `cp help`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
module.exports = {
|
|
15
|
+
version: require('./version'),
|
|
16
|
+
init: require('./init'),
|
|
17
|
+
doctor: require('./doctor'),
|
|
18
|
+
status: require('./status'),
|
|
19
|
+
tick: require('./tick'),
|
|
20
|
+
config: require('./config'),
|
|
21
|
+
'gsd-import': require('./gsd-import'),
|
|
22
|
+
install: require('./install'),
|
|
23
|
+
'write-summary': require('./write-summary'),
|
|
24
|
+
'scaffold-milestone': require('./scaffold-milestone'),
|
|
25
|
+
'scaffold-phase': require('./scaffold-phase'),
|
|
26
|
+
'scaffold-codebase': require('./scaffold-codebase'),
|
|
27
|
+
'codebase-status': require('./codebase-status'),
|
|
28
|
+
'complete-milestone': require('./complete-milestone'),
|
|
29
|
+
capture: require('./capture'),
|
|
30
|
+
inbox: require('./inbox'),
|
|
31
|
+
statusline: require('./statusline'),
|
|
32
|
+
worktree: require('./worktree'),
|
|
33
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { repoRoot, planningDir, ensureDir, readTemplate } = require('../../lib/paths');
|
|
6
|
+
const provider = require('../../lib/provider');
|
|
7
|
+
const compat = require('../../lib/gsd-compat');
|
|
8
|
+
const { today, renderTemplate } = require('./_helpers');
|
|
9
|
+
|
|
10
|
+
function run() {
|
|
11
|
+
const root = repoRoot();
|
|
12
|
+
const dir = planningDir(root);
|
|
13
|
+
ensureDir(dir);
|
|
14
|
+
ensureDir(path.join(dir, 'phases'));
|
|
15
|
+
ensureDir(path.join(dir, 'quick'));
|
|
16
|
+
|
|
17
|
+
const wasGsdProject = compat.isGsdProject(root);
|
|
18
|
+
const sharedPresent = compat.presentSharedFiles(root);
|
|
19
|
+
|
|
20
|
+
const files = [
|
|
21
|
+
['PROJECT.md', 'PROJECT.md'],
|
|
22
|
+
['ROADMAP.md', 'ROADMAP.md'],
|
|
23
|
+
['STATE.md', 'STATE.md'],
|
|
24
|
+
['MILESTONES.md', 'MILESTONES.md'],
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
let created = 0;
|
|
28
|
+
for (const [target, tpl] of files) {
|
|
29
|
+
const dest = path.join(dir, target);
|
|
30
|
+
if (!fs.existsSync(dest)) {
|
|
31
|
+
const content = renderTemplate(readTemplate(tpl), {
|
|
32
|
+
PROJECT_NAME: path.basename(root),
|
|
33
|
+
DATE: today(),
|
|
34
|
+
TRIGGER: 'cp init',
|
|
35
|
+
CORE_VALUE: '(not set yet — fill PROJECT.md)',
|
|
36
|
+
CURRENT_PHASE_NAME: 'pre-planning',
|
|
37
|
+
PHASE_NUM: '0',
|
|
38
|
+
TOTAL_PHASES: '0',
|
|
39
|
+
PLAN_NUM: '0',
|
|
40
|
+
TOTAL_PLANS_IN_PHASE: '0',
|
|
41
|
+
STATUS: 'Ready to plan',
|
|
42
|
+
LAST_ACTIVITY: 'init',
|
|
43
|
+
CONTINUE_HERE_PATH_OR_NONE: 'None',
|
|
44
|
+
MILESTONE_NAME: 'v0.1 — first milestone',
|
|
45
|
+
PHASE_RANGE: '1',
|
|
46
|
+
});
|
|
47
|
+
fs.writeFileSync(dest, content);
|
|
48
|
+
created++;
|
|
49
|
+
console.log(` + ${path.relative(root, dest)}`);
|
|
50
|
+
} else {
|
|
51
|
+
console.log(` = ${path.relative(root, dest)} (exists, kept)`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// config.json: merge-or-create.
|
|
56
|
+
const cfgPath = provider.configPath(root);
|
|
57
|
+
if (!fs.existsSync(cfgPath)) {
|
|
58
|
+
provider.saveConfig(provider.loadDefaults(), root);
|
|
59
|
+
console.log(` + ${path.relative(root, cfgPath)}`);
|
|
60
|
+
created++;
|
|
61
|
+
} else {
|
|
62
|
+
// Merge cp.* if missing — loadConfig() does this automatically.
|
|
63
|
+
provider.loadConfig(root);
|
|
64
|
+
console.log(` = ${path.relative(root, cfgPath)} (kept; cp block ensured)`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log(`\n${created} new file(s).`);
|
|
68
|
+
if (wasGsdProject) {
|
|
69
|
+
console.log(
|
|
70
|
+
`\nDetected a GSD project (research/ / todos/ / seeds/ / REQUIREMENTS.md).`
|
|
71
|
+
);
|
|
72
|
+
console.log(
|
|
73
|
+
`cp wrote a 'cp' block into config.json but did not modify any GSD files.`
|
|
74
|
+
);
|
|
75
|
+
console.log(
|
|
76
|
+
`You can switch back to GSD any time; cp is additive only.`
|
|
77
|
+
);
|
|
78
|
+
} else if (sharedPresent.length > 0 && !wasGsdProject) {
|
|
79
|
+
console.log(
|
|
80
|
+
`\n${sharedPresent.length} shared file(s) detected — cp will treat this`
|
|
81
|
+
);
|
|
82
|
+
console.log(`as a GSD-compatible project.`);
|
|
83
|
+
}
|
|
84
|
+
console.log(`\nNext: edit .planning/PROJECT.md, then run /cp-new-milestone or /cp-plan-phase.`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = { name: 'init', run };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { repoRoot, pluginRoot } = require('../../lib/paths');
|
|
5
|
+
const { available } = require('./_helpers');
|
|
6
|
+
|
|
7
|
+
function run(args = []) {
|
|
8
|
+
const harness = args[0];
|
|
9
|
+
if (!harness) {
|
|
10
|
+
console.error('Usage: cp install <copilot|claude|cursor|aider|echo-provider> [--force]');
|
|
11
|
+
process.exit(2);
|
|
12
|
+
}
|
|
13
|
+
const force = args.includes('--force');
|
|
14
|
+
|
|
15
|
+
// Special case: echo-provider installs to .planning/providers/
|
|
16
|
+
if (harness === 'echo-provider') {
|
|
17
|
+
const echoInstaller = require(path.join(pluginRoot(), 'install', 'echo-provider.js'));
|
|
18
|
+
const result = echoInstaller.install();
|
|
19
|
+
for (const r of result.results) {
|
|
20
|
+
console.log(`✓ ${r.file} (${r.status})`);
|
|
21
|
+
}
|
|
22
|
+
console.log('\necho-provider installed. Switch with:');
|
|
23
|
+
console.log(' cp config set workflow_provider echo-provider');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let installer;
|
|
28
|
+
try {
|
|
29
|
+
installer = require(path.join(pluginRoot(), 'install', `${harness}.js`));
|
|
30
|
+
} catch (e) {
|
|
31
|
+
console.error(`Unknown harness: ${harness}`);
|
|
32
|
+
console.error(`Available: copilot${available('claude') ? ', claude' : ''}${available('cursor') ? ', cursor' : ''}${available('aider') ? ', aider' : ''}`);
|
|
33
|
+
process.exit(2);
|
|
34
|
+
}
|
|
35
|
+
const result = installer.install({ pluginRoot: pluginRoot(), repoRoot: repoRoot(), force });
|
|
36
|
+
// Non-zero exit when there are user-modified files we refused to overwrite
|
|
37
|
+
// (signals the caller — e.g. CI — that the install was incomplete).
|
|
38
|
+
if (result && Array.isArray(result.userModified) && result.userModified.length > 0 && !force) {
|
|
39
|
+
process.exitCode = 3;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = { name: 'install', run };
|