@crouton-kit/crouter 0.1.8 → 0.1.9
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/commands/skill.js +17 -0
- package/dist/core/artifact.js +14 -3
- package/dist/core/resolver.d.ts +4 -2
- package/dist/core/resolver.js +145 -18
- package/dist/prompts/agent.js +87 -27
- package/dist/prompts/plan.js +34 -1
- package/dist/prompts/review.js +48 -28
- package/dist/prompts/skill.js +16 -0
- package/dist/prompts/spec.js +24 -9
- package/package.json +1 -1
package/dist/commands/skill.js
CHANGED
|
@@ -31,11 +31,18 @@ function buildShowFooter(skillPath) {
|
|
|
31
31
|
function wrapSkill(name, path, content) {
|
|
32
32
|
return `<skill name="${name}" path="${path}">\n${content.endsWith('\n') ? content : content + '\n'}</skill>`;
|
|
33
33
|
}
|
|
34
|
+
const SKILL_IDENTIFIER_HELP = 'Skill identifier forms (accepted by show, path, where, enable, disable):\n' +
|
|
35
|
+
' <name> bare name — resolves scope-root first, then plugins\n' +
|
|
36
|
+
' <plugin>:<name> explicit plugin (canonical)\n' +
|
|
37
|
+
' <scope>:<name> scope-root skill in a specific scope (user|project)\n' +
|
|
38
|
+
' <scope>:<plugin>/<name> fully qualified — matches `skill list` / `skill search` output\n' +
|
|
39
|
+
' <plugin>/<name> shorthand for <plugin>:<name> when unambiguous';
|
|
34
40
|
export function registerSkillCommands(program) {
|
|
35
41
|
const skill = program
|
|
36
42
|
.command('skill [nameOrVerb] [rest...]')
|
|
37
43
|
.description('manage and inspect skills')
|
|
38
44
|
.option('--frontmatter', 'include YAML frontmatter in the printed body')
|
|
45
|
+
.addHelpText('after', '\n' + SKILL_IDENTIFIER_HELP)
|
|
39
46
|
.action(async (nameOrVerb, _rest, opts) => {
|
|
40
47
|
if (nameOrVerb === undefined) {
|
|
41
48
|
out(skillPrompt());
|
|
@@ -62,6 +69,8 @@ export function registerSkillCommands(program) {
|
|
|
62
69
|
.option('--plugin <name>', 'filter by plugin name')
|
|
63
70
|
.option('-a, --all', 'include disabled skills')
|
|
64
71
|
.option('--json', 'emit JSON')
|
|
72
|
+
.addHelpText('after', '\nOutput format: <scope>:<plugin>/<name> — paste this identifier into ' +
|
|
73
|
+
'`crtr skill show` to read the skill.')
|
|
65
74
|
.action(async (opts) => {
|
|
66
75
|
try {
|
|
67
76
|
const scopes = listScopes(opts.scope);
|
|
@@ -109,6 +118,12 @@ export function registerSkillCommands(program) {
|
|
|
109
118
|
.option('--plugin <name>', 'filter by plugin name')
|
|
110
119
|
.option('--frontmatter', 'include YAML frontmatter in the printed body')
|
|
111
120
|
.option('--json', 'emit JSON')
|
|
121
|
+
.addHelpText('after', '\nExamples:\n' +
|
|
122
|
+
' crtr skill show rules # bare name\n' +
|
|
123
|
+
' crtr skill show claude-authoring:rules # plugin:name (canonical)\n' +
|
|
124
|
+
' crtr skill show user:claude-authoring/rules # scope:plugin/name (matches search/list output)\n' +
|
|
125
|
+
' crtr skill show claude-authoring/rules # plugin/name shorthand\n\n' +
|
|
126
|
+
SKILL_IDENTIFIER_HELP)
|
|
112
127
|
.action(async (name, opts) => {
|
|
113
128
|
try {
|
|
114
129
|
const scopeArg = resolveScopeArg(opts.scope);
|
|
@@ -363,6 +378,8 @@ export function registerSkillCommands(program) {
|
|
|
363
378
|
.option('-a, --all', 'include disabled skills')
|
|
364
379
|
.option('--body', 'also search SKILL.md body')
|
|
365
380
|
.option('--json', 'emit JSON')
|
|
381
|
+
.addHelpText('after', '\nOutput columns (tab-separated): <scope>:<plugin>/<name> <matched-fields> <description>\n' +
|
|
382
|
+
'The identifier is pasteable into `crtr skill show`.')
|
|
366
383
|
.action(async (query, opts) => {
|
|
367
384
|
try {
|
|
368
385
|
const needle = query.toLowerCase();
|
package/dist/core/artifact.js
CHANGED
|
@@ -6,7 +6,8 @@ import { CRTR_DIR_NAME } from '../types.js';
|
|
|
6
6
|
import { ensureDir, pathExists, readText, walkFiles } from './fs-utils.js';
|
|
7
7
|
import { usage, notFound, general } from './errors.js';
|
|
8
8
|
import { out, hint, jsonOut, handleError, info } from './output.js';
|
|
9
|
-
import { spawnSidePaneReview, DEFAULT_PANE_OPTS, } from './spawn.js';
|
|
9
|
+
import { spawnSidePaneReview, countPanesInCurrentWindow, DEFAULT_PANE_OPTS, } from './spawn.js';
|
|
10
|
+
import { readConfig } from './config.js';
|
|
10
11
|
export function mangleCwd(cwd = process.cwd()) {
|
|
11
12
|
return cwd.replace(/\//g, '-');
|
|
12
13
|
}
|
|
@@ -32,7 +33,17 @@ export function inTmux() {
|
|
|
32
33
|
return Boolean(process.env.TMUX);
|
|
33
34
|
}
|
|
34
35
|
export function openInTmuxPane(path) {
|
|
35
|
-
|
|
36
|
+
// Always pass --watch so the pane live-updates when the agent edits the
|
|
37
|
+
// file. The watcher re-detects width on resize and survives parse errors.
|
|
38
|
+
const args = ['--tmux', '--watch'];
|
|
39
|
+
// If the current tmux window is already at the configured pane budget,
|
|
40
|
+
// open in a new window instead of cramping the existing split further.
|
|
41
|
+
const maxPanes = readConfig('user').max_panes_per_window;
|
|
42
|
+
if (countPanesInCurrentWindow() >= maxPanes) {
|
|
43
|
+
args.push('--tmux-new-window');
|
|
44
|
+
}
|
|
45
|
+
args.push(path);
|
|
46
|
+
const result = spawnSync('termrender', args, {
|
|
36
47
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
37
48
|
});
|
|
38
49
|
if (result.error) {
|
|
@@ -52,7 +63,7 @@ export function openInTmuxPane(path) {
|
|
|
52
63
|
}
|
|
53
64
|
const paneId = result.stdout.toString().trim();
|
|
54
65
|
if (paneId)
|
|
55
|
-
hint(`opened in tmux pane ${paneId}`);
|
|
66
|
+
hint(`opened in tmux pane ${paneId} (live — edits to the file refresh the view)`);
|
|
56
67
|
}
|
|
57
68
|
async function readStdin() {
|
|
58
69
|
const chunks = [];
|
package/dist/core/resolver.d.ts
CHANGED
|
@@ -18,10 +18,12 @@ export interface SkillResolutionOpts {
|
|
|
18
18
|
pluginFilter?: string;
|
|
19
19
|
}
|
|
20
20
|
export declare function resolveSkill(rawName: string, opts?: SkillResolutionOpts): Skill;
|
|
21
|
-
export
|
|
21
|
+
export interface ParsedSkillQualifier {
|
|
22
|
+
scope?: Scope;
|
|
22
23
|
plugin?: string;
|
|
23
24
|
name: string;
|
|
24
|
-
}
|
|
25
|
+
}
|
|
26
|
+
export declare function parseSkillQualifier(raw: string): ParsedSkillQualifier;
|
|
25
27
|
export declare function listInstalledMarketplaces(scope: Scope): InstalledMarketplace[];
|
|
26
28
|
export declare function listAllMarketplaces(): InstalledMarketplace[];
|
|
27
29
|
export declare function findMarketplaceByName(name: string, scope?: Scope): InstalledMarketplace | null;
|
package/dist/core/resolver.js
CHANGED
|
@@ -4,7 +4,7 @@ import { readConfig } from './config.js';
|
|
|
4
4
|
import { listDirs, pathExists, readText, walkFiles, } from './fs-utils.js';
|
|
5
5
|
import { readMarketplaceManifest, readPluginManifest } from './manifest.js';
|
|
6
6
|
import { parseFrontmatter } from './frontmatter.js';
|
|
7
|
-
import { ambiguous, notFound } from './errors.js';
|
|
7
|
+
import { ambiguous, notFound, usage } from './errors.js';
|
|
8
8
|
import { marketplacesDir, pluginsDir, projectScopeRoot, scopeSkillsDir, userScopeRoot, } from './scope.js';
|
|
9
9
|
export function listInstalledPlugins(scope) {
|
|
10
10
|
const dir = pluginsDir(scope);
|
|
@@ -139,16 +139,47 @@ export function listAllSkills(scopeFilter) {
|
|
|
139
139
|
];
|
|
140
140
|
}
|
|
141
141
|
export function resolveSkill(rawName, opts = {}) {
|
|
142
|
-
const
|
|
143
|
-
|
|
142
|
+
const parsed = parseSkillQualifier(rawName);
|
|
143
|
+
if (parsed.scope && opts.scope && parsed.scope !== opts.scope) {
|
|
144
|
+
throw usage(`scope conflict: identifier "${rawName}" uses scope "${parsed.scope}" but --scope is "${opts.scope}"`);
|
|
145
|
+
}
|
|
146
|
+
if (parsed.plugin && opts.pluginFilter && parsed.plugin !== opts.pluginFilter) {
|
|
147
|
+
throw usage(`plugin conflict: identifier "${rawName}" uses plugin "${parsed.plugin}" but --plugin is "${opts.pluginFilter}"`);
|
|
148
|
+
}
|
|
149
|
+
const effectiveScope = opts.scope ?? parsed.scope;
|
|
150
|
+
const effectivePluginFilter = opts.pluginFilter ?? parsed.plugin;
|
|
151
|
+
const direct = findSkillMatches(parsed.name, parsed.plugin, effectiveScope, effectivePluginFilter);
|
|
152
|
+
if (direct.length > 0)
|
|
153
|
+
return pickMatch(direct, parsed.name, parsed.plugin);
|
|
154
|
+
// Fallback: bare `plugin/name` (no colon) — try splitting on first `/`.
|
|
155
|
+
// Disambiguates "claude-authoring/rules" (which the search/list display also emits as
|
|
156
|
+
// "user:claude-authoring/rules") from a nested scope-root skill of the same shape.
|
|
157
|
+
if (!parsed.plugin && parsed.name.includes('/')) {
|
|
158
|
+
const slashIdx = parsed.name.indexOf('/');
|
|
159
|
+
const maybePlugin = parsed.name.slice(0, slashIdx);
|
|
160
|
+
const rest = parsed.name.slice(slashIdx + 1);
|
|
161
|
+
if (effectivePluginFilter === undefined || effectivePluginFilter === maybePlugin) {
|
|
162
|
+
const fallback = findSkillMatches(rest, maybePlugin, effectiveScope, maybePlugin);
|
|
163
|
+
if (fallback.length > 0)
|
|
164
|
+
return pickMatch(fallback, rest, maybePlugin);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
throw notFound(formatNotFoundMessage(rawName, parsed), {
|
|
168
|
+
skill: parsed.name,
|
|
169
|
+
plugin: parsed.plugin,
|
|
170
|
+
scope: parsed.scope,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
function findSkillMatches(name, pluginQualifier, scope, pluginFilter) {
|
|
174
|
+
const plugins = scope ? listInstalledPlugins(scope) : listAllPlugins();
|
|
144
175
|
const enabledPlugins = plugins.filter((p) => p.enabled);
|
|
145
176
|
const cfgs = loadScopeConfigs();
|
|
146
177
|
const matches = [];
|
|
147
178
|
// Scope-root skills first — they're the user's own captured knowledge.
|
|
148
|
-
if (!
|
|
179
|
+
if (!pluginFilter &&
|
|
149
180
|
(pluginQualifier === undefined || pluginQualifier === SCOPE_SKILL_PLUGIN)) {
|
|
150
|
-
const scopes =
|
|
151
|
-
? [
|
|
181
|
+
const scopes = scope
|
|
182
|
+
? [scope]
|
|
152
183
|
: [projectScopeRoot() ? 'project' : null, 'user'].filter(Boolean);
|
|
153
184
|
for (const s of scopes) {
|
|
154
185
|
const skillsRoot = scopeSkillsDir(s);
|
|
@@ -176,7 +207,7 @@ export function resolveSkill(rawName, opts = {}) {
|
|
|
176
207
|
for (const plugin of ordered) {
|
|
177
208
|
if (pluginQualifier && plugin.name !== pluginQualifier)
|
|
178
209
|
continue;
|
|
179
|
-
if (
|
|
210
|
+
if (pluginFilter && plugin.name !== pluginFilter)
|
|
180
211
|
continue;
|
|
181
212
|
const skillPath = join(plugin.root, SKILLS_DIR, ...name.split('/'), SKILL_ENTRY_FILE);
|
|
182
213
|
if (!pathExists(skillPath))
|
|
@@ -195,20 +226,16 @@ export function resolveSkill(rawName, opts = {}) {
|
|
|
195
226
|
disabledIn,
|
|
196
227
|
});
|
|
197
228
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
: `skill not found: ${name}`, { skill: name, plugin: pluginQualifier });
|
|
202
|
-
}
|
|
229
|
+
return matches;
|
|
230
|
+
}
|
|
231
|
+
function pickMatch(matches, name, pluginQualifier) {
|
|
203
232
|
if (matches.length === 1)
|
|
204
233
|
return matches[0];
|
|
205
234
|
const sameScopeAndPlugin = matches.every((m) => m.plugin === matches[0].plugin && m.scope === matches[0].scope);
|
|
206
235
|
if (sameScopeAndPlugin)
|
|
207
236
|
return matches[0];
|
|
208
|
-
|
|
209
|
-
if (!pluginQualifier) {
|
|
237
|
+
if (!pluginQualifier)
|
|
210
238
|
return matches[0];
|
|
211
|
-
}
|
|
212
239
|
throw ambiguous(`ambiguous skill: ${name}`, {
|
|
213
240
|
skill: name,
|
|
214
241
|
candidates: matches.map((m) => ({
|
|
@@ -218,11 +245,111 @@ export function resolveSkill(rawName, opts = {}) {
|
|
|
218
245
|
})),
|
|
219
246
|
});
|
|
220
247
|
}
|
|
248
|
+
function formatNotFoundMessage(rawName, parsed) {
|
|
249
|
+
const suggestions = suggestSkills(parsed.name, parsed.plugin);
|
|
250
|
+
const lines = [`skill not found: ${rawName}`];
|
|
251
|
+
lines.push(' expected forms: <name>, <plugin>:<name>, <scope>:<plugin>/<name>');
|
|
252
|
+
if (suggestions.length > 0) {
|
|
253
|
+
const formatted = suggestions
|
|
254
|
+
.map((s) => s.plugin === SCOPE_SKILL_PLUGIN ? s.name : `${s.plugin}:${s.name}`)
|
|
255
|
+
.slice(0, 3);
|
|
256
|
+
lines.push(` did you mean: ${formatted.join(', ')}`);
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
lines.push(' run `crtr skill list` or `crtr skill search <query>` to discover skills');
|
|
260
|
+
}
|
|
261
|
+
return lines.join('\n');
|
|
262
|
+
}
|
|
263
|
+
function suggestSkills(name, plugin) {
|
|
264
|
+
let all;
|
|
265
|
+
try {
|
|
266
|
+
all = listAllSkills();
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
return [];
|
|
270
|
+
}
|
|
271
|
+
const target = name.toLowerCase();
|
|
272
|
+
const targetBase = target.split('/').pop() ?? target;
|
|
273
|
+
const targetPluginGuess = target.includes('/') ? target.split('/')[0] : undefined;
|
|
274
|
+
const exactName = all.filter((s) => s.name.toLowerCase() === target);
|
|
275
|
+
if (exactName.length > 0)
|
|
276
|
+
return exactName;
|
|
277
|
+
const exactBase = all.filter((s) => {
|
|
278
|
+
const sBase = s.name.toLowerCase().split('/').pop() ?? s.name.toLowerCase();
|
|
279
|
+
return sBase === targetBase;
|
|
280
|
+
});
|
|
281
|
+
if (exactBase.length > 0)
|
|
282
|
+
return exactBase;
|
|
283
|
+
const scored = all
|
|
284
|
+
.map((s) => {
|
|
285
|
+
const sName = s.name.toLowerCase();
|
|
286
|
+
const sBase = sName.split('/').pop() ?? sName;
|
|
287
|
+
const sPlugin = s.plugin.toLowerCase();
|
|
288
|
+
let score = 0;
|
|
289
|
+
if (plugin !== undefined && sPlugin === plugin.toLowerCase())
|
|
290
|
+
score += 5;
|
|
291
|
+
if (targetPluginGuess !== undefined && sPlugin === targetPluginGuess)
|
|
292
|
+
score += 5;
|
|
293
|
+
if (sName.includes(target) || target.includes(sName))
|
|
294
|
+
score += 4;
|
|
295
|
+
if (sBase.includes(targetBase) || targetBase.includes(sBase))
|
|
296
|
+
score += 3;
|
|
297
|
+
if (editDistance(sBase, targetBase) <= 2)
|
|
298
|
+
score += 4;
|
|
299
|
+
return { skill: s, score };
|
|
300
|
+
})
|
|
301
|
+
.filter((x) => x.score > 0)
|
|
302
|
+
.sort((a, b) => b.score - a.score);
|
|
303
|
+
return scored.slice(0, 3).map((x) => x.skill);
|
|
304
|
+
}
|
|
305
|
+
function editDistance(a, b) {
|
|
306
|
+
if (a === b)
|
|
307
|
+
return 0;
|
|
308
|
+
if (a.length === 0)
|
|
309
|
+
return b.length;
|
|
310
|
+
if (b.length === 0)
|
|
311
|
+
return a.length;
|
|
312
|
+
const prev = new Array(b.length + 1);
|
|
313
|
+
const curr = new Array(b.length + 1);
|
|
314
|
+
for (let j = 0; j <= b.length; j++)
|
|
315
|
+
prev[j] = j;
|
|
316
|
+
for (let i = 1; i <= a.length; i++) {
|
|
317
|
+
curr[0] = i;
|
|
318
|
+
for (let j = 1; j <= b.length; j++) {
|
|
319
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
320
|
+
curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
321
|
+
}
|
|
322
|
+
for (let j = 0; j <= b.length; j++)
|
|
323
|
+
prev[j] = curr[j];
|
|
324
|
+
}
|
|
325
|
+
return prev[b.length];
|
|
326
|
+
}
|
|
327
|
+
const SCOPE_QUALIFIERS = new Set(['user', 'project']);
|
|
328
|
+
// Accepted identifier forms:
|
|
329
|
+
// <name> — bare name; scope-root first, then plugins
|
|
330
|
+
// <plugin>:<name> — explicit plugin
|
|
331
|
+
// <scope>:<name> — scope-root in a specific scope
|
|
332
|
+
// <scope>:<plugin>/<name> — fully qualified (matches `skill list` / `skill search` display)
|
|
333
|
+
// Bare `<plugin>/<name>` (no colon) is handled as a fallback inside resolveSkill.
|
|
221
334
|
export function parseSkillQualifier(raw) {
|
|
222
|
-
const
|
|
223
|
-
if (
|
|
335
|
+
const colonIdx = raw.indexOf(':');
|
|
336
|
+
if (colonIdx === -1)
|
|
224
337
|
return { name: raw };
|
|
225
|
-
|
|
338
|
+
const before = raw.slice(0, colonIdx);
|
|
339
|
+
const after = raw.slice(colonIdx + 1);
|
|
340
|
+
if (SCOPE_QUALIFIERS.has(before)) {
|
|
341
|
+
const scope = before;
|
|
342
|
+
const slashIdx = after.indexOf('/');
|
|
343
|
+
if (slashIdx !== -1) {
|
|
344
|
+
return {
|
|
345
|
+
scope,
|
|
346
|
+
plugin: after.slice(0, slashIdx),
|
|
347
|
+
name: after.slice(slashIdx + 1),
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
return { scope, name: after };
|
|
351
|
+
}
|
|
352
|
+
return { plugin: before, name: after };
|
|
226
353
|
}
|
|
227
354
|
function orderPluginsByResolution(plugins) {
|
|
228
355
|
const score = (p) => {
|
package/dist/prompts/agent.js
CHANGED
|
@@ -25,37 +25,97 @@ The originating pane has closed; the user is watching you here. Begin now.`;
|
|
|
25
25
|
* First user message for a plan → implementation handoff.
|
|
26
26
|
*/
|
|
27
27
|
export function implementHandoffPrompt(planPath) {
|
|
28
|
-
return `You are an
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
return `You are executing an approved plan. For small plans, implement directly.
|
|
29
|
+
For plans with parallelizable scale, orchestrate parallel subagents and
|
|
30
|
+
coordinate them — don't write all the code yourself when the plan is
|
|
31
|
+
structured to fan out.
|
|
31
32
|
|
|
32
33
|
**Plan to implement:** ${planPath}
|
|
33
34
|
|
|
34
|
-
##
|
|
35
|
-
|
|
36
|
-
1. Read the plan end-to-end
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
35
|
+
## Phase 1: Read
|
|
36
|
+
|
|
37
|
+
1. Read the plan end-to-end. If it references a spec (\`--spec\` was passed
|
|
38
|
+
at save time), read that too — it's the contract you are realizing.
|
|
39
|
+
2. Read the files the plan names under "Files to modify / create" (or the
|
|
40
|
+
per-task \`Files:\` lines) and "Existing utilities to reuse" to ground
|
|
41
|
+
yourself in current code.
|
|
42
|
+
3. If the plan has task blocks with dependencies, extract the task list,
|
|
43
|
+
dependency graph, and integration contracts.
|
|
44
|
+
|
|
45
|
+
## Phase 2: Scale
|
|
46
|
+
|
|
47
|
+
Count the plan's **independent task groups** (tasks with no mutual
|
|
48
|
+
dependencies that can run in parallel). Pick the strategy:
|
|
49
|
+
|
|
50
|
+
| Independent groups | Files touched | Strategy |
|
|
51
|
+
|-------------------|---------------|----------|
|
|
52
|
+
| 1 | 1–3 | **Implement directly.** Skip Phases 3–5; just execute the plan and go to Phase 6. |
|
|
53
|
+
| 1–2 | 3–5 | Implement directly, or single subagent if you want parallelism with verification |
|
|
54
|
+
| 2–4 | 5–15 | **2 parallel subagents** |
|
|
55
|
+
| 4–8 | 10–30 | **3 parallel subagents** |
|
|
56
|
+
| 8+ | 25+ | **4 parallel subagents** (cap) |
|
|
57
|
+
|
|
58
|
+
Use the higher column to pick the tier. Never spawn more subagents than
|
|
59
|
+
there are independent groups. **Bump one tier** if: tight cross-group
|
|
60
|
+
interface coordination, mixed languages/frameworks, or both infra +
|
|
61
|
+
application layers change.
|
|
62
|
+
|
|
63
|
+
## Phase 3: Partition
|
|
64
|
+
|
|
65
|
+
Group tasks into **disjoint sets** where:
|
|
66
|
+
|
|
67
|
+
- Each group owns clear file boundaries — **no two groups edit the same files**.
|
|
68
|
+
- Within a group, tasks are sequenced for one subagent to execute in order.
|
|
69
|
+
- Across groups, dependencies become layers: dependent groups run *after*
|
|
70
|
+
their predecessors complete.
|
|
71
|
+
|
|
72
|
+
If two tasks must touch the same file, sequence them in the same group.
|
|
73
|
+
|
|
74
|
+
## Phase 4: Dispatch
|
|
56
75
|
|
|
57
|
-
|
|
58
|
-
|
|
76
|
+
For each task group in the current dependency layer, dispatch a subagent
|
|
77
|
+
in parallel via the Task tool. Use \`general-purpose\` by default; use
|
|
78
|
+
\`devcore:programmer\` if the project has devcore installed.
|
|
79
|
+
|
|
80
|
+
**Each subagent's prompt must include:**
|
|
81
|
+
- The specific tasks from the plan it owns (paste verbatim)
|
|
82
|
+
- The plan path: \`${planPath}\`
|
|
83
|
+
- The spec path if one exists
|
|
84
|
+
- The exact file ownership for this group
|
|
85
|
+
- Integration contracts it produces or consumes (types, APIs, shapes)
|
|
86
|
+
- **Constraint: do NOT run tests or typechecks** — other subagents may be
|
|
87
|
+
mid-edit. The orchestrator runs verification at layer boundaries.
|
|
88
|
+
- Instruction to return when its tasks are complete, surfacing blockers
|
|
89
|
+
|
|
90
|
+
## Phase 5: Coordinate
|
|
91
|
+
|
|
92
|
+
Wait for all subagents in the current layer. Then:
|
|
93
|
+
|
|
94
|
+
- If any reports a blocker, resolve it: fix yourself, adjust scope with
|
|
95
|
+
the user, or re-dispatch a corrected task. Don't proceed past the blocker.
|
|
96
|
+
- Run the plan's verification for the just-finished layer (tests, manual
|
|
97
|
+
checks). Fix any failures before dispatching the next layer.
|
|
98
|
+
- Dispatch the next layer.
|
|
99
|
+
|
|
100
|
+
**Stay in the coordinator role.** Don't implement tasks yourself unless a
|
|
101
|
+
subagent returns blocked work and the fix is small enough that re-dispatch
|
|
102
|
+
would be slower.
|
|
103
|
+
|
|
104
|
+
## Phase 6: Report
|
|
105
|
+
|
|
106
|
+
When all tasks complete and verification passes, write one short message:
|
|
107
|
+
files touched per group, tests run, what works. The user may then ask for
|
|
108
|
+
a code review via \`crtr agent review\`.
|
|
109
|
+
|
|
110
|
+
## Guardrails (apply to you AND your subagents)
|
|
111
|
+
|
|
112
|
+
- **No redesign.** If the plan is wrong, surface the issue — do not
|
|
113
|
+
silently substitute your own approach.
|
|
114
|
+
- **No scope expansion.** No drive-by refactors, no "while I'm here"
|
|
115
|
+
cleanup, no new abstractions the plan didn't request.
|
|
116
|
+
- **Honor conventions.** Match each file's existing style, naming, and
|
|
117
|
+
patterns. Use the utilities the plan named.
|
|
118
|
+
- **Commit only if the user asks.**
|
|
59
119
|
|
|
60
120
|
You were launched in a new tmux pane via \`crtr agent implement\`. The
|
|
61
121
|
originating pane has closed; the user is watching you here. Begin by reading
|
package/dist/prompts/plan.js
CHANGED
|
@@ -58,6 +58,19 @@ between approaches. Never use it to ask the user "is this plan okay?" or
|
|
|
58
58
|
|
|
59
59
|
## Phase 4: Final Plan
|
|
60
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
|
+
|
|
61
74
|
Save the plan with \`crtr plan --name <kebab-case-name>\`. Pipe the markdown
|
|
62
75
|
body in via stdin (heredoc):
|
|
63
76
|
|
|
@@ -85,6 +98,22 @@ Be concise enough to scan, detailed enough to execute.>
|
|
|
85
98
|
EOF
|
|
86
99
|
\`\`\`
|
|
87
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
|
+
|
|
88
117
|
- Pick a short, descriptive kebab-case name. Names may be nested
|
|
89
118
|
(\`crtr plan --name auth/jwt-refresh\`) — they become subdirectories.
|
|
90
119
|
- If this plan implements a saved spec, pass \`--spec <spec-name>\` so the
|
|
@@ -92,7 +121,11 @@ EOF
|
|
|
92
121
|
\`crtr plan --name <name> --spec <spec-name> <<'EOF' ... EOF\`
|
|
93
122
|
- The file lands at \`${plansDir}/<name>.md\`.
|
|
94
123
|
- If you are running inside tmux, the saved plan auto-opens in a side pane
|
|
95
|
-
via termrender.
|
|
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.
|
|
96
129
|
|
|
97
130
|
## Phase 5: Review
|
|
98
131
|
|
package/dist/prompts/review.js
CHANGED
|
@@ -16,40 +16,53 @@ summarize or chat after submission — \`crtr agent submit\` IS the response.
|
|
|
16
16
|
If you cannot complete the review (file missing, totally malformed, etc.),
|
|
17
17
|
still call \`crtr agent submit\` with a brief explanation of why.`;
|
|
18
18
|
export function specReviewPrompt(specPath) {
|
|
19
|
-
return `You are reviewing a spec document. Verify it is complete and ready for
|
|
19
|
+
return `You are reviewing a spec document. Verify it is complete and ready for
|
|
20
|
+
planning.
|
|
20
21
|
|
|
21
22
|
**Spec to review:** ${specPath}
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
Read the spec end-to-end first.
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
## What to check
|
|
27
|
+
|
|
28
|
+
| Category | What to look for |
|
|
26
29
|
|----------|------------------|
|
|
27
30
|
| Completeness | TODOs, placeholders, "TBD", incomplete sections |
|
|
28
31
|
| Consistency | Internal contradictions, conflicting requirements |
|
|
29
32
|
| Clarity | Requirements ambiguous enough to cause someone to build the wrong thing |
|
|
30
|
-
| Scope | Focused enough for a single plan
|
|
33
|
+
| Scope | Focused enough for a single plan |
|
|
31
34
|
| YAGNI | Unrequested features, over-engineering |
|
|
32
35
|
|
|
33
|
-
|
|
36
|
+
For specs with non-trivial component interaction, also walk the primary
|
|
37
|
+
flow from trigger to final state and check whether preconditions, state
|
|
38
|
+
transitions, failure handling, and handoffs between components are
|
|
39
|
+
actually specified. This is the highest-signal check when there are
|
|
40
|
+
seams to fall between — skip it for self-contained specs.
|
|
34
41
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
For larger specs touching established patterns, optionally spawn a Task
|
|
43
|
+
agent (\`general-purpose\`, \`sonnet\`) to cross-check the spec's design
|
|
44
|
+
against \`CLAUDE.md\` / \`.claude/rules/*.md\` and the files it references,
|
|
45
|
+
looking for contradictions with project conventions. Skip for small specs.
|
|
46
|
+
|
|
47
|
+
## Calibration
|
|
39
48
|
|
|
40
|
-
Approve unless
|
|
49
|
+
Approve unless an implementer or planner would be led astray. Real issues:
|
|
50
|
+
missing requirements, contradictory design, unspecified failure modes on
|
|
51
|
+
critical paths, requirements ambiguous enough to be built two ways. Not
|
|
52
|
+
issues: wording preferences, "I'd have organized this differently",
|
|
53
|
+
sections less detailed than others.
|
|
41
54
|
|
|
42
|
-
## Output
|
|
55
|
+
## Output
|
|
43
56
|
|
|
44
57
|
## Spec Review
|
|
45
58
|
|
|
46
59
|
**Status:** Approved | Issues Found
|
|
47
60
|
|
|
48
61
|
**Issues (if any):**
|
|
49
|
-
- [Section
|
|
62
|
+
- [Section]: [specific issue] — [why it matters for planning]
|
|
50
63
|
|
|
51
64
|
**Recommendations (advisory, do not block approval):**
|
|
52
|
-
- [suggestions
|
|
65
|
+
- [suggestions]
|
|
53
66
|
|
|
54
67
|
${SUBMIT_INSTRUCTIONS}`;
|
|
55
68
|
}
|
|
@@ -65,39 +78,46 @@ merits (internal completeness, task decomposition, buildability). Skip the
|
|
|
65
78
|
|
|
66
79
|
Read the plan first, then the spec, then evaluate alignment and the other
|
|
67
80
|
criteria below.`;
|
|
68
|
-
return `You are reviewing a plan document. Verify it is complete and ready for
|
|
81
|
+
return `You are reviewing a plan document. Verify it is complete and ready for
|
|
82
|
+
implementation.
|
|
69
83
|
|
|
70
84
|
${inputs}
|
|
71
85
|
|
|
72
|
-
## What to
|
|
86
|
+
## What to check
|
|
73
87
|
|
|
74
|
-
| Category | What to
|
|
88
|
+
| Category | What to look for |
|
|
75
89
|
|----------|------------------|
|
|
76
90
|
| Completeness | TODOs, placeholders, incomplete tasks, missing steps |
|
|
77
|
-
| Spec
|
|
78
|
-
|
|
|
79
|
-
|
|
|
91
|
+
${specPath === null ? '' : '| Spec alignment | Plan covers spec requirements, no major scope creep, no unjustified divergence from the spec\'s design |\n'}| Resolved decisions | No "if X then Y" branches, no "investigate whether…", no deferred choices |
|
|
92
|
+
| Buildability | Could an engineer follow this without getting stuck or re-deciding things? |
|
|
93
|
+
| Quality smells | Timelines, "for now" shortcuts, magic values, fallbacks, missing type definitions where the plan creates a new contract |
|
|
80
94
|
|
|
81
|
-
|
|
95
|
+
For medium+ plans that claim parallelizable tasks, also check that tasks
|
|
96
|
+
own disjoint files (or call out unavoidable overlap) and that shared
|
|
97
|
+
types/APIs between tasks have their contracts specified.
|
|
98
|
+
|
|
99
|
+
For large plans${specPath === null ? '' : ', or when spec coverage feels uncertain'}, you may
|
|
100
|
+
optionally spawn one Task agent (\`general-purpose\`, \`sonnet\`) to
|
|
101
|
+
cross-check ${specPath === null ? 'coverage of the plan\'s own stated phases against its task list' : `spec requirements at \`${specPath}\` against plan tasks`}.
|
|
102
|
+
Skip for small plans.
|
|
82
103
|
|
|
83
|
-
|
|
84
|
-
An implementer building the wrong thing or getting stuck is an issue.
|
|
85
|
-
Minor wording, stylistic preferences, and "nice to have" suggestions are not.
|
|
104
|
+
## Calibration
|
|
86
105
|
|
|
87
|
-
Approve unless
|
|
88
|
-
|
|
106
|
+
Approve unless an implementer would build the wrong thing, get stuck, or
|
|
107
|
+
ship something that violates the plan's quality bar. Minor wording,
|
|
108
|
+
stylistic preferences, and "nice to have" reorganizations are NOT issues.
|
|
89
109
|
|
|
90
|
-
## Output
|
|
110
|
+
## Output
|
|
91
111
|
|
|
92
112
|
## Plan Review
|
|
93
113
|
|
|
94
114
|
**Status:** Approved | Issues Found
|
|
95
115
|
|
|
96
116
|
**Issues (if any):**
|
|
97
|
-
- [Task
|
|
117
|
+
- [Task / Section]: [specific issue] — [why it matters for implementation]
|
|
98
118
|
|
|
99
119
|
**Recommendations (advisory, do not block approval):**
|
|
100
|
-
- [suggestions
|
|
120
|
+
- [suggestions]
|
|
101
121
|
|
|
102
122
|
${SUBMIT_INSTRUCTIONS}`;
|
|
103
123
|
}
|
package/dist/prompts/skill.js
CHANGED
|
@@ -5,6 +5,22 @@ Skills are markdown the agent loads on demand. **Audience: future LLM agent
|
|
|
5
5
|
sessions, not humans.** Write for the model: terse, decision-first, dense.
|
|
6
6
|
The CLI is the index — \`crtr skill list/search/grep\` discovers what's saved.
|
|
7
7
|
|
|
8
|
+
## Route by intent
|
|
9
|
+
|
|
10
|
+
If a query follows this prompt, route based on it. Run the suggested command
|
|
11
|
+
first, then act on its output.
|
|
12
|
+
|
|
13
|
+
- **Capture** ("save", "remember", "build context for", "make a skill"):
|
|
14
|
+
\`crtr skill create [topic]\` — picks template, hints next command.
|
|
15
|
+
- **Find** ("what do we have on X", "skill for Y"):
|
|
16
|
+
\`crtr skill search <query>\` → \`crtr skill show <name>\` on best hit.
|
|
17
|
+
- **Load by name** ("show me X"): \`crtr skill show <name>\`.
|
|
18
|
+
- **List all**: \`crtr skill list\`.
|
|
19
|
+
- **No intent given / query empty**: ask the user what they want before running.
|
|
20
|
+
|
|
21
|
+
Don't load \`create\` and \`template\` outputs in the same turn — \`create\` decides
|
|
22
|
+
the type, then call \`template\`.
|
|
23
|
+
|
|
8
24
|
Locations (resolution order):
|
|
9
25
|
1. **Scope-direct** \`<scope-root>/skills/<name>/SKILL.md\` → \`user:<name>\` / \`project:<name>\`
|
|
10
26
|
2. **Plugin skills** \`<plugin>/skills/<name>/SKILL.md\` → \`<scope>:<plugin>/<name>\`
|
package/dist/prompts/spec.js
CHANGED
|
@@ -12,6 +12,10 @@ Specs for this directory live at:
|
|
|
12
12
|
If a relevant prior spec already exists there, read it first. Treat an
|
|
13
13
|
existing spec as the starting point — extend or revise rather than restart.
|
|
14
14
|
|
|
15
|
+
**Anti-pattern: do not fish for clarifications upfront.** Draft a concrete
|
|
16
|
+
spec first based on your investigation, *then* iterate. A specific draft the
|
|
17
|
+
user can react to converges faster than a list of questions in a vacuum.
|
|
18
|
+
|
|
15
19
|
## Phase 1: Shape
|
|
16
20
|
|
|
17
21
|
Build a comprehensive picture of the problem and the relevant code. Surface
|
|
@@ -36,18 +40,25 @@ should be:
|
|
|
36
40
|
- **Behavior-focused** — describes what the system does, not how.
|
|
37
41
|
- **Scoped** — covers one observable behavior.
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
Group requirements by capability. Plain English is fine for most things. If
|
|
44
|
+
a requirement is conditional or stateful enough that phrasing gets fuzzy,
|
|
45
|
+
EARS templates can sharpen it (\`When <trigger>, the system shall <behavior>\`
|
|
46
|
+
or \`If <condition>, then the system shall <response>\`) — reach for them
|
|
47
|
+
when they earn their keep, not by default.
|
|
41
48
|
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
For larger / multi-component designs, before moving on it's worth walking
|
|
50
|
+
the design end-to-end as a sanity check: at each step from trigger to
|
|
51
|
+
final state, ask whether preconditions, state, failure handling, and
|
|
52
|
+
handoffs between components are actually specified. Skip this for small
|
|
53
|
+
self-contained specs — it's the kind of thing that catches bugs in the
|
|
54
|
+
seams, so apply it where there are seams.
|
|
44
55
|
|
|
45
56
|
## Phase 3: Deepen
|
|
46
57
|
|
|
47
58
|
- Read the critical files identified during Phase 1 to deepen your
|
|
48
59
|
understanding before locking decisions.
|
|
49
|
-
- Reconcile
|
|
50
|
-
|
|
60
|
+
- Reconcile requirements against the shape — if a requirement reveals a gap
|
|
61
|
+
in the design, refine the design before saving.
|
|
51
62
|
- Use **AskUserQuestion** for any remaining ambiguities. Bias toward asking
|
|
52
63
|
when a decision is non-obvious or when the user's intent is genuinely
|
|
53
64
|
unclear.
|
|
@@ -74,10 +85,10 @@ outcome. Include relevant constraints — user goals, stakeholders, deadlines.>
|
|
|
74
85
|
they were chosen. Reference existing code with \`file_path:line_number\`.>
|
|
75
86
|
|
|
76
87
|
## Requirements
|
|
77
|
-
<grouped behavioral requirements. Each one testable.>
|
|
88
|
+
<grouped behavioral requirements. Each one testable. Plain English is fine.>
|
|
78
89
|
|
|
79
90
|
### <Capability A>
|
|
80
|
-
-
|
|
91
|
+
- <one observable behavior>
|
|
81
92
|
- ...
|
|
82
93
|
|
|
83
94
|
### <Capability B>
|
|
@@ -96,7 +107,11 @@ EOF
|
|
|
96
107
|
(\`crtr spec --name auth/refresh-tokens\`).
|
|
97
108
|
- The file lands at \`${specsDir}/<name>.md\`.
|
|
98
109
|
- If you are running inside tmux, the saved spec auto-opens in a side pane
|
|
99
|
-
via termrender.
|
|
110
|
+
(or a new window when the current one is full) via termrender. The pane
|
|
111
|
+
is **live** — it re-renders whenever the file changes on disk. For small
|
|
112
|
+
tweaks, **edit the file path directly with the Edit tool** instead of
|
|
113
|
+
re-running the heredoc save; the pane updates in place. Re-save via
|
|
114
|
+
heredoc only when you want to re-trigger the reviewer.
|
|
100
115
|
|
|
101
116
|
## Phase 5: Review
|
|
102
117
|
|