@crouton-kit/crouter 0.2.5 → 0.3.1
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/dist/builtin-skills/skills/crouter-development/marketplaces/SKILL.md +9 -9
- package/dist/builtin-skills/skills/crouter-development/plugins/SKILL.md +19 -19
- package/dist/cli.js +42 -37
- package/dist/commands/__tests__/human.test.d.ts +1 -0
- package/dist/commands/__tests__/human.test.js +214 -0
- package/dist/commands/__tests__/skill.test.d.ts +1 -0
- package/dist/commands/__tests__/skill.test.js +287 -0
- package/dist/commands/debug.d.ts +3 -0
- package/dist/commands/debug.js +179 -0
- package/dist/commands/flow.d.ts +2 -0
- package/dist/commands/flow.js +24 -0
- package/dist/commands/human.d.ts +2 -0
- package/dist/commands/human.js +480 -0
- package/dist/commands/job.d.ts +2 -0
- package/dist/commands/job.js +669 -0
- package/dist/commands/pkg.d.ts +2 -0
- package/dist/commands/pkg.js +1021 -0
- package/dist/commands/plan.d.ts +4 -2
- package/dist/commands/plan.js +306 -22
- package/dist/commands/skill.d.ts +2 -2
- package/dist/commands/skill.js +615 -459
- package/dist/commands/spec.d.ts +3 -2
- package/dist/commands/spec.js +283 -10
- package/dist/commands/sys.d.ts +2 -0
- package/dist/commands/sys.js +712 -0
- package/dist/core/__tests__/argv-parser.test.d.ts +1 -0
- package/dist/core/__tests__/argv-parser.test.js +199 -0
- package/dist/core/__tests__/flow-leaves.test.d.ts +1 -0
- package/dist/core/__tests__/flow-leaves.test.js +248 -0
- package/dist/core/__tests__/job.test.d.ts +1 -0
- package/dist/core/__tests__/job.test.js +346 -0
- package/dist/core/__tests__/pkg.test.d.ts +1 -0
- package/dist/core/__tests__/pkg.test.js +218 -0
- package/dist/core/__tests__/sys.test.d.ts +1 -0
- package/dist/core/__tests__/sys.test.js +208 -0
- package/dist/core/artifact.d.ts +29 -18
- package/dist/core/artifact.js +78 -221
- package/dist/core/auto-update.js +11 -3
- package/dist/core/command.d.ts +36 -0
- package/dist/core/command.js +287 -0
- package/dist/core/errors.d.ts +3 -0
- package/dist/core/errors.js +5 -0
- package/dist/core/fs-utils.d.ts +1 -0
- package/dist/core/fs-utils.js +4 -0
- package/dist/core/help.d.ts +98 -0
- package/dist/core/help.js +163 -0
- package/dist/core/io.d.ts +29 -0
- package/dist/core/io.js +83 -0
- package/dist/core/jobs.d.ts +87 -0
- package/dist/core/jobs.js +353 -0
- package/dist/core/pagination.d.ts +33 -0
- package/dist/core/pagination.js +89 -0
- package/dist/core/self-update.d.ts +21 -0
- package/dist/{commands/update.js → core/self-update.js} +28 -63
- package/dist/core/spawn.d.ts +47 -65
- package/dist/core/spawn.js +78 -228
- package/dist/prompts/agent.d.ts +10 -5
- package/dist/prompts/agent.js +51 -74
- package/dist/prompts/debug.d.ts +8 -0
- package/dist/prompts/debug.js +37 -0
- package/dist/prompts/review.js +4 -11
- package/dist/prompts/skill.d.ts +0 -1
- package/dist/prompts/skill.js +95 -149
- package/package.json +4 -2
- package/dist/commands/agent.d.ts +0 -2
- package/dist/commands/agent.js +0 -265
- package/dist/commands/config.d.ts +0 -2
- package/dist/commands/config.js +0 -146
- package/dist/commands/doctor.d.ts +0 -2
- package/dist/commands/doctor.js +0 -268
- package/dist/commands/marketplace.d.ts +0 -2
- package/dist/commands/marketplace.js +0 -365
- package/dist/commands/plugin.d.ts +0 -2
- package/dist/commands/plugin.js +0 -367
- package/dist/commands/update.d.ts +0 -4
- package/dist/prompts/plan.d.ts +0 -1
- package/dist/prompts/plan.js +0 -175
- package/dist/prompts/spec.d.ts +0 -1
- package/dist/prompts/spec.js +0 -153
package/dist/commands/plugin.js
DELETED
|
@@ -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
|
-
}
|
package/dist/prompts/plan.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function planPrompt(plansDir: string): string;
|
package/dist/prompts/plan.js
DELETED
|
@@ -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
|
-
}
|
package/dist/prompts/spec.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function specPrompt(specsDir: string): string;
|