@crouton-kit/crouter 0.2.6 → 0.3.2

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 (80) hide show
  1. package/dist/builtin-skills/skills/crouter-development/marketplaces/SKILL.md +9 -9
  2. package/dist/builtin-skills/skills/crouter-development/plugins/SKILL.md +19 -19
  3. package/dist/cli.js +42 -37
  4. package/dist/commands/__tests__/human.test.d.ts +1 -0
  5. package/dist/commands/__tests__/human.test.js +214 -0
  6. package/dist/commands/__tests__/skill.test.d.ts +1 -0
  7. package/dist/commands/__tests__/skill.test.js +294 -0
  8. package/dist/commands/debug.d.ts +3 -0
  9. package/dist/commands/debug.js +179 -0
  10. package/dist/commands/flow.d.ts +2 -0
  11. package/dist/commands/flow.js +24 -0
  12. package/dist/commands/human.d.ts +2 -0
  13. package/dist/commands/human.js +480 -0
  14. package/dist/commands/job.d.ts +2 -0
  15. package/dist/commands/job.js +669 -0
  16. package/dist/commands/pkg.d.ts +2 -0
  17. package/dist/commands/pkg.js +1021 -0
  18. package/dist/commands/plan.d.ts +4 -2
  19. package/dist/commands/plan.js +306 -22
  20. package/dist/commands/skill.d.ts +2 -2
  21. package/dist/commands/skill.js +613 -456
  22. package/dist/commands/spec.d.ts +3 -2
  23. package/dist/commands/spec.js +283 -10
  24. package/dist/commands/sys.d.ts +2 -0
  25. package/dist/commands/sys.js +712 -0
  26. package/dist/core/__tests__/argv-parser.test.d.ts +1 -0
  27. package/dist/core/__tests__/argv-parser.test.js +199 -0
  28. package/dist/core/__tests__/flow-leaves.test.d.ts +1 -0
  29. package/dist/core/__tests__/flow-leaves.test.js +248 -0
  30. package/dist/core/__tests__/job.test.d.ts +1 -0
  31. package/dist/core/__tests__/job.test.js +346 -0
  32. package/dist/core/__tests__/pkg.test.d.ts +1 -0
  33. package/dist/core/__tests__/pkg.test.js +218 -0
  34. package/dist/core/__tests__/sys.test.d.ts +1 -0
  35. package/dist/core/__tests__/sys.test.js +208 -0
  36. package/dist/core/artifact.d.ts +29 -18
  37. package/dist/core/artifact.js +78 -221
  38. package/dist/core/auto-update.js +11 -4
  39. package/dist/core/command.d.ts +36 -0
  40. package/dist/core/command.js +287 -0
  41. package/dist/core/errors.d.ts +3 -0
  42. package/dist/core/errors.js +5 -0
  43. package/dist/core/fs-utils.d.ts +1 -0
  44. package/dist/core/fs-utils.js +4 -0
  45. package/dist/core/help.d.ts +98 -0
  46. package/dist/core/help.js +163 -0
  47. package/dist/core/io.d.ts +29 -0
  48. package/dist/core/io.js +83 -0
  49. package/dist/core/jobs.d.ts +87 -0
  50. package/dist/core/jobs.js +353 -0
  51. package/dist/core/pagination.d.ts +33 -0
  52. package/dist/core/pagination.js +89 -0
  53. package/dist/core/self-update.d.ts +21 -0
  54. package/dist/core/self-update.js +105 -0
  55. package/dist/core/spawn.d.ts +47 -65
  56. package/dist/core/spawn.js +78 -228
  57. package/dist/prompts/agent.d.ts +10 -5
  58. package/dist/prompts/agent.js +51 -74
  59. package/dist/prompts/debug.d.ts +8 -0
  60. package/dist/prompts/debug.js +37 -0
  61. package/dist/prompts/review.js +4 -11
  62. package/dist/prompts/skill.d.ts +0 -1
  63. package/dist/prompts/skill.js +95 -149
  64. package/package.json +4 -2
  65. package/dist/commands/agent.d.ts +0 -2
  66. package/dist/commands/agent.js +0 -265
  67. package/dist/commands/config.d.ts +0 -2
  68. package/dist/commands/config.js +0 -146
  69. package/dist/commands/doctor.d.ts +0 -2
  70. package/dist/commands/doctor.js +0 -268
  71. package/dist/commands/marketplace.d.ts +0 -2
  72. package/dist/commands/marketplace.js +0 -365
  73. package/dist/commands/plugin.d.ts +0 -2
  74. package/dist/commands/plugin.js +0 -367
  75. package/dist/commands/update.d.ts +0 -4
  76. package/dist/commands/update.js +0 -140
  77. package/dist/prompts/plan.d.ts +0 -1
  78. package/dist/prompts/plan.js +0 -175
  79. package/dist/prompts/spec.d.ts +0 -1
  80. package/dist/prompts/spec.js +0 -153
@@ -1,367 +0,0 @@
1
- import { join } from 'node:path';
2
- import { renameSync } from 'node:fs';
3
- import { notFound, usage, general } from '../core/errors.js';
4
- import { out, hint, info, jsonOut, handleError, isTTY } from '../core/output.js';
5
- import { pluginsDir, ensureProjectScopeRoot, resolveScopeArg, listScopes, userScopeRoot, } from '../core/scope.js';
6
- import { updateConfig, updateState, ensureScopeInitialized } from '../core/config.js';
7
- import { listInstalledPlugins, listAllPlugins, findPluginByName, listSkillsInPlugin, } from '../core/resolver.js';
8
- import { pathExists, ensureDir, removePath, nowIso } from '../core/fs-utils.js';
9
- import { clone, pull, deriveNameFromUrl } from '../core/git.js';
10
- import { readPluginManifest } from '../core/manifest.js';
11
- const KNOWN_VERBS = new Set([
12
- 'list',
13
- 'show',
14
- 'install',
15
- 'uninstall',
16
- 'enable',
17
- 'disable',
18
- 'update',
19
- ]);
20
- const GIT_URL_RE = /^(https?:\/\/|git@|ssh:\/\/|file:\/\/)/;
21
- function isGitUrl(arg) {
22
- return GIT_URL_RE.test(arg) || arg.endsWith('.git');
23
- }
24
- export function registerPluginCommands(program) {
25
- const plugin = program
26
- .command('plugin [nameOrVerb] [rest...]')
27
- .description('manage plugins')
28
- .action(async (nameOrVerb, rest) => {
29
- if (nameOrVerb === undefined) {
30
- plugin.help();
31
- return;
32
- }
33
- if (!KNOWN_VERBS.has(nameOrVerb)) {
34
- try {
35
- await showPlugin(nameOrVerb, { json: false });
36
- }
37
- catch (e) {
38
- handleError(e);
39
- }
40
- return;
41
- }
42
- // Known verbs dispatched by commander subcommands; nothing to do here.
43
- void rest;
44
- });
45
- // list
46
- plugin
47
- .command('list')
48
- .description('list installed plugins')
49
- .option('--scope <scope>', 'user|project|all (default: all)')
50
- .option('--json', 'emit JSON')
51
- .action(async (opts) => {
52
- try {
53
- const scopes = listScopes(opts.scope);
54
- const plugins = scopes
55
- .flatMap((s) => listInstalledPlugins(s))
56
- .sort((a, b) => {
57
- if (a.scope === 'project' && b.scope !== 'project')
58
- return -1;
59
- if (a.scope !== 'project' && b.scope === 'project')
60
- return 1;
61
- return a.name.localeCompare(b.name);
62
- });
63
- if (opts.json) {
64
- jsonOut({
65
- plugins: plugins.map((p) => ({
66
- name: p.name,
67
- scope: p.scope,
68
- version: p.version,
69
- source_marketplace: p.sourceMarketplace,
70
- description: p.manifest.description,
71
- enabled: p.enabled,
72
- root: p.root,
73
- })),
74
- });
75
- return;
76
- }
77
- for (const p of plugins) {
78
- const version = p.version !== undefined ? `@${p.version}` : '';
79
- const mkt = p.sourceMarketplace !== undefined ? ` [${p.sourceMarketplace}]` : '';
80
- const desc = p.manifest.description !== undefined ? ` ${p.manifest.description}` : '';
81
- out(`${p.scope}:${p.name}${version}${mkt}${desc}`);
82
- }
83
- }
84
- catch (e) {
85
- handleError(e, { json: opts.json });
86
- }
87
- });
88
- // show
89
- plugin
90
- .command('show <name>')
91
- .description('print plugin.json and skill index (default verb)')
92
- .option('--json', 'emit JSON')
93
- .action(async (name, opts) => {
94
- try {
95
- await showPlugin(name, opts);
96
- }
97
- catch (e) {
98
- handleError(e, { json: opts.json });
99
- }
100
- });
101
- // install
102
- plugin
103
- .command('install <gitUrlOrName>')
104
- .description('install a plugin from a git URL or marketplace name')
105
- .option('--scope <scope>', 'user|project (default: user)')
106
- .option('--ref <branch>', 'git branch/tag/ref to clone')
107
- .action(async (gitUrlOrName, opts) => {
108
- try {
109
- if (!isGitUrl(gitUrlOrName)) {
110
- throw usage(`"${gitUrlOrName}" is not a git URL and no matching marketplace plugin was found.\n` +
111
- `Use \`crtr marketplace install <mkt>:<name>\` to install from a marketplace.`, { code: 'USAGE' });
112
- }
113
- const url = gitUrlOrName;
114
- let scope = 'user';
115
- if (opts.scope !== undefined) {
116
- const resolved = resolveScopeArg(opts.scope);
117
- if (resolved === 'all') {
118
- throw usage('--scope must be user or project, not all');
119
- }
120
- scope = resolved;
121
- }
122
- else {
123
- hint('No --scope provided; defaulting to user scope (~/.crouter/plugins/).' +
124
- ' Pass --scope project to install into the project scope.');
125
- }
126
- let scopeRootPath;
127
- if (scope === 'project') {
128
- scopeRootPath = ensureProjectScopeRoot();
129
- ensureScopeInitialized(scope, scopeRootPath);
130
- }
131
- else {
132
- scopeRootPath = userScopeRoot();
133
- ensureScopeInitialized(scope, scopeRootPath);
134
- }
135
- const pDir = join(scopeRootPath, 'plugins');
136
- ensureDir(pDir);
137
- const tempName = deriveNameFromUrl(url);
138
- const tempDir = join(pDir, tempName);
139
- if (pathExists(tempDir)) {
140
- throw general(`plugin directory already exists: ${tempDir}\n` +
141
- `Uninstall the existing plugin first with \`crtr plugin uninstall ${tempName}\`.`);
142
- }
143
- clone(url, tempDir, { ref: opts.ref, depth: 1 });
144
- const manifest = readPluginManifest(tempDir);
145
- if (manifest === null) {
146
- removePath(tempDir);
147
- throw general(`cloned repo does not contain a valid .crouter-plugin/plugin.json: ${url}`);
148
- }
149
- const finalName = manifest.name;
150
- let finalDir = tempDir;
151
- if (finalName !== tempName) {
152
- const candidateDir = join(pDir, finalName);
153
- if (pathExists(candidateDir)) {
154
- removePath(tempDir);
155
- throw general(`plugin "${finalName}" is already installed at ${candidateDir}`);
156
- }
157
- renameSync(tempDir, candidateDir);
158
- finalDir = candidateDir;
159
- }
160
- updateConfig(scope, (cfg) => {
161
- cfg.plugins[finalName] = {
162
- enabled: true,
163
- version: manifest.version,
164
- };
165
- });
166
- info(`installed plugin "${finalName}"${manifest.version !== undefined ? ` v${manifest.version}` : ''} (${scope} scope)`);
167
- out(finalDir);
168
- }
169
- catch (e) {
170
- handleError(e);
171
- }
172
- });
173
- // uninstall
174
- plugin
175
- .command('uninstall <name>')
176
- .description('remove a plugin and its config entry')
177
- .option('--scope <scope>', 'user|project|all (default: all)')
178
- .option('--yes', 'skip confirmation in non-TTY mode')
179
- .action(async (name, opts) => {
180
- try {
181
- if (name === 'crtr') {
182
- throw usage(`cannot uninstall builtin plugin "crtr" — it ships with the binary`);
183
- }
184
- if (!isTTY() && !opts.yes) {
185
- throw usage(`uninstall requires --yes in non-TTY mode: crtr plugin uninstall ${name} --yes`);
186
- }
187
- const scopes = listScopes(opts.scope).filter((s) => s !== 'builtin');
188
- let removed = false;
189
- for (const scope of scopes) {
190
- const pDir = pluginsDir(scope);
191
- if (pDir === null)
192
- continue;
193
- const pluginDir = join(pDir, name);
194
- if (!pathExists(pluginDir))
195
- continue;
196
- removePath(pluginDir);
197
- updateConfig(scope, (cfg) => {
198
- delete cfg.plugins[name];
199
- });
200
- info(`uninstalled plugin "${name}" from ${scope} scope`);
201
- removed = true;
202
- }
203
- if (!removed) {
204
- throw notFound(`plugin not found: ${name}`);
205
- }
206
- }
207
- catch (e) {
208
- handleError(e);
209
- }
210
- });
211
- // enable
212
- plugin
213
- .command('enable <name>')
214
- .description('enable a plugin')
215
- .option('--scope <scope>', 'user|project|all (default: all)')
216
- .action(async (name, opts) => {
217
- try {
218
- await setEnabled(name, true, opts.scope);
219
- }
220
- catch (e) {
221
- handleError(e);
222
- }
223
- });
224
- // disable
225
- plugin
226
- .command('disable <name>')
227
- .description('disable a plugin without removing it')
228
- .option('--scope <scope>', 'user|project|all (default: all)')
229
- .action(async (name, opts) => {
230
- try {
231
- await setEnabled(name, false, opts.scope);
232
- }
233
- catch (e) {
234
- handleError(e);
235
- }
236
- });
237
- // update
238
- plugin
239
- .command('update [name]')
240
- .description('git pull one or all enabled non-marketplace plugins')
241
- .action(async (name) => {
242
- try {
243
- let targets;
244
- if (name !== undefined) {
245
- const found = findPluginByName(name);
246
- if (found === null) {
247
- throw notFound(`plugin not found: ${name}`);
248
- }
249
- targets = [{ name: found.name, scope: found.scope, root: found.root }];
250
- }
251
- else {
252
- const all = listAllPlugins();
253
- targets = all
254
- .filter((p) => p.enabled && !p.sourceMarketplace)
255
- .map((p) => ({ name: p.name, scope: p.scope, root: p.root }));
256
- }
257
- if (targets.length === 0) {
258
- info('no plugins to update');
259
- return;
260
- }
261
- for (const target of targets) {
262
- const res = pull(target.root);
263
- if (res.status !== 0) {
264
- info(`failed to update "${target.name}": ${res.stderr.trim()}`);
265
- continue;
266
- }
267
- const manifest = readPluginManifest(target.root);
268
- if (manifest !== null) {
269
- updateConfig(target.scope, (cfg) => {
270
- const entry = cfg.plugins[target.name];
271
- if (entry !== undefined) {
272
- entry.version = manifest.version;
273
- }
274
- else {
275
- cfg.plugins[target.name] = {
276
- enabled: true,
277
- version: manifest.version,
278
- };
279
- }
280
- });
281
- updateState(target.scope, (s) => {
282
- if (s.plugins[target.name] === undefined) {
283
- s.plugins[target.name] = {};
284
- }
285
- s.plugins[target.name].last_updated = nowIso();
286
- });
287
- }
288
- const version = manifest !== null && manifest.version !== undefined
289
- ? ` → v${manifest.version}`
290
- : '';
291
- info(`updated "${target.name}"${version}`);
292
- }
293
- }
294
- catch (e) {
295
- handleError(e);
296
- }
297
- });
298
- }
299
- async function showPlugin(name, opts) {
300
- const found = findPluginByName(name);
301
- if (found === null) {
302
- throw notFound(`plugin not found: ${name}`);
303
- }
304
- const skills = listSkillsInPlugin(found);
305
- if (opts.json) {
306
- jsonOut({
307
- plugin: {
308
- name: found.manifest.name,
309
- version: found.manifest.version,
310
- description: found.manifest.description,
311
- source: found.manifest.source,
312
- owner: found.manifest.owner,
313
- scope: found.scope,
314
- root: found.root,
315
- enabled: found.enabled,
316
- },
317
- skills: skills.map((s) => ({
318
- name: s.name,
319
- description: s.frontmatter.description,
320
- path: s.path,
321
- })),
322
- });
323
- return;
324
- }
325
- const manifest = found.manifest;
326
- const version = manifest.version !== undefined ? ` v${manifest.version}` : '';
327
- const desc = manifest.description !== undefined ? `\n ${manifest.description}` : '';
328
- const source = manifest.source !== undefined ? `\n source: ${manifest.source}` : '';
329
- out(`${manifest.name}${version} (${found.scope})${desc}${source}`);
330
- if (skills.length > 0) {
331
- out('');
332
- out('Skills:');
333
- for (const s of skills) {
334
- const skillDesc = s.frontmatter.description !== undefined
335
- ? ` — ${s.frontmatter.description}`
336
- : '';
337
- out(` ${s.name}${skillDesc}`);
338
- }
339
- }
340
- hint(`crtr: update with \`crtr plugin update ${name}\``);
341
- }
342
- async function setEnabled(name, enabled, scopeOpt) {
343
- const scopes = listScopes(scopeOpt);
344
- let acted = false;
345
- for (const scope of scopes) {
346
- const pDir = pluginsDir(scope);
347
- if (pDir === null)
348
- continue;
349
- const pluginDir = join(pDir, name);
350
- if (!pathExists(pluginDir))
351
- continue;
352
- updateConfig(scope, (cfg) => {
353
- const entry = cfg.plugins[name];
354
- if (entry !== undefined) {
355
- entry.enabled = enabled;
356
- }
357
- else {
358
- cfg.plugins[name] = { enabled };
359
- }
360
- });
361
- info(`plugin "${name}" ${enabled ? 'enabled' : 'disabled'} in ${scope} scope`);
362
- acted = true;
363
- }
364
- if (!acted) {
365
- throw notFound(`plugin not found: ${name}`);
366
- }
367
- }
@@ -1,4 +0,0 @@
1
- import { Command } from 'commander';
2
- export declare function selfCheck(): void;
3
- export declare function contentCheck(): void;
4
- export declare function registerUpdateCommand(program: Command): void;
@@ -1,140 +0,0 @@
1
- import { spawnSync } from 'node:child_process';
2
- import { fileURLToPath } from 'node:url';
3
- import { join, dirname } from 'node:path';
4
- import { readFileSync } from 'node:fs';
5
- import { err, warn, handleError } from '../core/output.js';
6
- import { projectScopeRoot } from '../core/scope.js';
7
- import { updateState } from '../core/config.js';
8
- import { listAllPlugins, listAllMarketplaces } from '../core/resolver.js';
9
- import { pull, fetch, currentSha, remoteSha } from '../core/git.js';
10
- import { nowIso } from '../core/fs-utils.js';
11
- import { network, general } from '../core/errors.js';
12
- const __filename = fileURLToPath(import.meta.url);
13
- const __dirname = dirname(__filename);
14
- const PKG_ROOT = join(__dirname, '..', '..', '..');
15
- const PACKAGE_JSON_PATH = join(PKG_ROOT, 'package.json');
16
- function currentVersion() {
17
- const raw = readFileSync(PACKAGE_JSON_PATH, 'utf8');
18
- const parsed = JSON.parse(raw);
19
- return parsed.version;
20
- }
21
- function selfUpdate() {
22
- const res = spawnSync('npm', ['i', '-g', '@crouton-kit/crtr@latest'], { stdio: 'inherit' });
23
- if (res.status !== 0) {
24
- throw general('npm install failed');
25
- }
26
- }
27
- export function selfCheck() {
28
- const res = spawnSync('npm', ['view', '@crouton-kit/crtr', 'version'], { encoding: 'utf8' });
29
- if (res.status !== 0) {
30
- warn('could not check for crtr updates (network unavailable)');
31
- return;
32
- }
33
- const latest = res.stdout.trim();
34
- const current = currentVersion();
35
- if (latest !== current) {
36
- err(`crtr: v${latest} available (current ${current}) — run \`crtr update --self\``);
37
- }
38
- }
39
- function contentUpdate() {
40
- const marketplaces = listAllMarketplaces();
41
- for (const mkt of marketplaces) {
42
- const res = pull(mkt.root);
43
- if (res.status !== 0) {
44
- throw network(`git pull failed for marketplace ${mkt.name}: ${res.stderr.trim()}`);
45
- }
46
- updateState(mkt.scope, (s) => {
47
- if (!s.marketplaces[mkt.name])
48
- s.marketplaces[mkt.name] = {};
49
- s.marketplaces[mkt.name].last_updated = nowIso();
50
- });
51
- }
52
- const plugins = listAllPlugins();
53
- for (const plugin of plugins) {
54
- if (!plugin.enabled)
55
- continue;
56
- if (plugin.sourceMarketplace)
57
- continue;
58
- const res = pull(plugin.root);
59
- if (res.status !== 0) {
60
- throw network(`git pull failed for plugin ${plugin.name}: ${res.stderr.trim()}`);
61
- }
62
- updateState(plugin.scope, (s) => {
63
- if (!s.plugins[plugin.name])
64
- s.plugins[plugin.name] = {};
65
- s.plugins[plugin.name].last_updated = nowIso();
66
- });
67
- }
68
- }
69
- export function contentCheck() {
70
- const marketplaces = listAllMarketplaces();
71
- for (const mkt of marketplaces) {
72
- const fetchRes = fetch(mkt.root, mkt.ref);
73
- if (fetchRes.status !== 0) {
74
- warn(`could not fetch ${mkt.name} (network unavailable)`);
75
- continue;
76
- }
77
- const head = currentSha(mkt.root);
78
- const remote = remoteSha(mkt.root, mkt.ref);
79
- if (head !== null && remote !== null && head !== remote) {
80
- err(`crtr: marketplace ${mkt.name} has updates available — run \`crtr update --content\``);
81
- }
82
- }
83
- const plugins = listAllPlugins();
84
- for (const plugin of plugins) {
85
- if (!plugin.enabled)
86
- continue;
87
- if (plugin.sourceMarketplace)
88
- continue;
89
- const ref = 'main';
90
- const fetchRes = fetch(plugin.root, ref);
91
- if (fetchRes.status !== 0) {
92
- warn(`could not fetch ${plugin.name} (network unavailable)`);
93
- continue;
94
- }
95
- const head = currentSha(plugin.root);
96
- const remote = remoteSha(plugin.root, ref);
97
- if (head !== null && remote !== null && head !== remote) {
98
- err(`crtr: plugin ${plugin.name} has updates available — run \`crtr plugin update ${plugin.name}\``);
99
- }
100
- }
101
- }
102
- export function registerUpdateCommand(program) {
103
- program
104
- .command('update')
105
- .description('update crtr itself and/or installed plugins and marketplaces')
106
- .option('--self', 'update crtr binary via npm')
107
- .option('--content', 'pull updates for all installed plugins and marketplaces')
108
- .option('--check', 'check for updates without applying them')
109
- .action(async (opts) => {
110
- try {
111
- const runSelf = opts.self === true;
112
- const runContent = opts.content === true;
113
- const runBoth = !runSelf && !runContent;
114
- if (opts.check) {
115
- if (runSelf || runBoth)
116
- selfCheck();
117
- if (runContent || runBoth)
118
- contentCheck();
119
- return;
120
- }
121
- if (runSelf || runBoth) {
122
- selfUpdate();
123
- const scopes = ['user'];
124
- if (projectScopeRoot())
125
- scopes.unshift('project');
126
- for (const scope of scopes) {
127
- updateState(scope, (s) => {
128
- s.last_self_check = nowIso();
129
- });
130
- }
131
- }
132
- if (runContent || runBoth) {
133
- contentUpdate();
134
- }
135
- }
136
- catch (e) {
137
- handleError(e);
138
- }
139
- });
140
- }
@@ -1 +0,0 @@
1
- export declare function planPrompt(plansDir: string): string;
@@ -1,175 +0,0 @@
1
- export function planPrompt(plansDir) {
2
- return `# Planning workflow
3
-
4
- You are entering a focused planning session. The goal is to produce an
5
- implementation plan that another agent (or you, in a later turn) can execute
6
- without re-discovering everything. A plan is a map, not a tutorial.
7
-
8
- Plans for this directory live at:
9
- ${plansDir}
10
-
11
- If a relevant prior plan already exists there, read it first.
12
-
13
- ## Phase 1: Initial Understanding
14
-
15
- Build a comprehensive picture of the user's request and the code involved.
16
- Actively search for existing functions, utilities, and patterns that can be
17
- reused — do not propose new code when a suitable implementation already
18
- exists.
19
-
20
- - **Launch up to 3 Explore subagents IN PARALLEL** (single message, multiple
21
- tool calls) to cover the codebase efficiently.
22
- - Use 1 agent when the task is isolated to known files, the user provided
23
- specific paths, or the change is small and targeted.
24
- - Use multiple agents when scope is uncertain, multiple areas of the codebase
25
- are involved, or you need to understand existing patterns before planning.
26
- - Quality over quantity — 3 agents maximum; usually 1 is right.
27
- - When using multiple agents, give each a distinct focus (existing impls,
28
- related components, test patterns) so they do not duplicate work.
29
-
30
- ## Phase 2: Design
31
-
32
- Design the implementation approach based on Phase 1 findings.
33
-
34
- - **Default**: launch at least 1 Plan agent — it validates your understanding
35
- and surfaces alternatives.
36
- - **Skip agents** only for truly trivial tasks (typo fixes, single-line
37
- changes, simple renames).
38
- - **Multiple agents (up to 3)** for tasks that benefit from different
39
- perspectives — large refactors, architectural changes, many edge cases.
40
-
41
- In the Plan agent prompt:
42
- - Provide comprehensive background context from Phase 1, including filenames
43
- and code-path traces.
44
- - Describe requirements and constraints.
45
- - Request a detailed implementation plan.
46
-
47
- ## Phase 3: Review
48
-
49
- - Read the critical files identified by agents to deepen your understanding.
50
- - Ensure the plan aligns with the user's original request.
51
- - Use **AskUserQuestion** to clarify any remaining questions with the user.
52
- Bias toward asking when a decision is non-obvious — interrupting once is
53
- cheaper than building the wrong thing.
54
-
55
- **Important:** Use AskUserQuestion ONLY to clarify requirements or choose
56
- between approaches. Never use it to ask the user "is this plan okay?" or
57
- "should I proceed?" — the save step below is the approval moment.
58
-
59
- ## Phase 4: Final Plan
60
-
61
- ### Quality bar
62
-
63
- Hold the draft to these — they're cheap to satisfy and they save the
64
- implementer from re-deciding things:
65
-
66
- - Every decision pinned. No "if X then Y" branches, no "investigate
67
- whether…", no deferred choices. If you don't know, find out or ask now.
68
- - No timelines, no fallbacks, no magic values, no "for now" shortcuts.
69
- - Where the plan creates a new interface, schema, or contract, write the
70
- actual shape rather than "design a Foo type."
71
-
72
- ### Save
73
-
74
- Save the plan with \`crtr plan --name <kebab-case-name>\`. Pipe the markdown
75
- body in via stdin (heredoc):
76
-
77
- \`\`\`bash
78
- crtr plan --name <kebab-case-name> <<'EOF'
79
- # Plan: <one-line title>
80
-
81
- ## Context
82
- <why this change is being made — the problem it addresses, what prompted it,
83
- and the intended outcome>
84
-
85
- ## Recommended approach
86
- <your chosen approach. Include only the recommendation, not all alternatives.
87
- Be concise enough to scan, detailed enough to execute.>
88
-
89
- ## Files to modify / create
90
- - \`path/to/file.ts\` — <what changes>
91
- - ...
92
-
93
- ## Existing utilities to reuse
94
- - \`function-name\` from \`path/to/file.ts:LL\` — <why it fits>
95
-
96
- ## Verification
97
- <how to test the changes end-to-end — run the code, run tests, etc.>
98
- EOF
99
- \`\`\`
100
-
101
- For plans touching 4+ files across distinct concerns, the implementer can
102
- dispatch parallel subagents — but only if you structure tasks for it. In
103
- that case, replace "Files to modify / create" with task blocks like:
104
-
105
- \`\`\`
106
- ## Tasks
107
- - **Task 1**: <name>
108
- - Files: \`a.ts\`, \`b.ts\` (disjoint from other tasks)
109
- - Depends on: (none) | Task N
110
- - Integration: <shared types/APIs with exact shape>
111
- - Changes: <bullets>
112
- \`\`\`
113
-
114
- Skip this structure for small plans; it's noise when there's no
115
- parallelism to unlock.
116
-
117
- - Pick a short, descriptive kebab-case name. Names may be nested
118
- (\`crtr plan --name auth/jwt-refresh\`) — they become subdirectories.
119
- - If this plan implements a saved spec, pass \`--spec <spec-name>\` so the
120
- reviewer can check alignment:
121
- \`crtr plan --name <name> --spec <spec-name> <<'EOF' ... EOF\`
122
- - The file lands at \`${plansDir}/<name>.md\`.
123
- - If you are running inside tmux, the saved plan auto-opens in a side pane
124
- (or a new window when the current one is full) via termrender. The pane
125
- is **live** — it re-renders whenever the file changes on disk. For small
126
- tweaks, **edit the file path directly with the Edit tool** instead of
127
- re-running the heredoc save; the pane updates in place. Re-save via
128
- heredoc only when you want to re-trigger the reviewer.
129
-
130
- ## Phase 5: Review
131
-
132
- By default the save command **blocks** while a reviewer agent reads the plan
133
- (and the spec, if \`--spec\` was passed) in a side pane (10-min budget) and
134
- returns its findings on stdout under a \`--- review ---\` marker. **Read the
135
- review** when the command returns:
136
-
137
- - If \`Status: Approved\`, you are done.
138
- - If \`Status: Issues Found\`, address the listed issues by editing the plan
139
- (\`crtr plan edit <name>\` or rewriting via the save command), then save
140
- again to re-trigger review.
141
-
142
- Pass \`--no-review\` only when the plan is genuinely trivial (one-line fix,
143
- typo, single-file rename). For anything substantive, take the review.
144
-
145
- ## Phase 6: Oversize check
146
-
147
- If the save command emits a \`--- advisory ---\` warning that the plan is
148
- too long, do not ignore it. Split the plan into a short index plan plus
149
- one or more nested part plans, each under the threshold, and re-save. The
150
- implementer will execute parts one at a time; very long plans tend to be
151
- under-decomposed.
152
-
153
- ## Phase 7: Done
154
-
155
- After the review returns Approved (or you have addressed its issues), your
156
- turn ends. No need to summarize the plan in chat — the user can read the file.
157
-
158
- If the user is ready to start building, ask once whether they want to hand
159
- off now. If yes, run:
160
-
161
- \`\`\`bash
162
- crtr agent implement --plan <name>
163
- \`\`\`
164
-
165
- This fires up an implementer in a new tmux pane and closes the current
166
- pane a few seconds later. Do NOT run this without the user's go-ahead.
167
-
168
- ## See also
169
-
170
- - \`crtr plan list\` — list saved plans for the current directory
171
- - \`crtr plan show <name>\` — print the body of a saved plan
172
- - \`crtr plan edit <name>\` — open a saved plan in \$EDITOR
173
- - \`crtr plan path [name]\` — absolute path of a plan or the plans directory
174
- `;
175
- }
@@ -1 +0,0 @@
1
- export declare function specPrompt(specsDir: string): string;