@crouton-kit/crouter 0.1.7 → 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/cli.js CHANGED
@@ -13,7 +13,7 @@ import { registerPlanCommand } from './commands/plan.js';
13
13
  import { registerSpecCommand } from './commands/spec.js';
14
14
  import { registerAgentCommand } from './commands/agent.js';
15
15
  import { maybeAutoUpdate } from './core/auto-update.js';
16
- import { ensureBootSkill, ensureOfficialMarketplace } from './core/bootstrap.js';
16
+ import { ensureBootSkill, ensureOfficialMarketplace, ensureProjectScope } from './core/bootstrap.js';
17
17
  function readPackageVersion() {
18
18
  const here = dirname(fileURLToPath(import.meta.url));
19
19
  const pkgPath = join(here, '..', 'package.json');
@@ -38,6 +38,7 @@ registerSpecCommand(program);
38
38
  registerAgentCommand(program);
39
39
  ensureOfficialMarketplace(process.argv);
40
40
  ensureBootSkill(process.argv);
41
+ ensureProjectScope(process.argv);
41
42
  maybeAutoUpdate(process.argv);
42
43
  program.parseAsync().catch((err) => {
43
44
  process.stderr.write(`crtr: ${err instanceof Error ? err.message : String(err)}\n`);
@@ -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();
@@ -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
- const result = spawnSync('termrender', ['--tmux', path], {
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 = [];
@@ -3,3 +3,4 @@ export declare const OFFICIAL_MARKETPLACE_URL = "https://github.com/crouton-labs
3
3
  export declare const OFFICIAL_MARKETPLACE_REF = "main";
4
4
  export declare function ensureBootSkill(argv: string[]): void;
5
5
  export declare function ensureOfficialMarketplace(argv: string[]): void;
6
+ export declare function ensureProjectScope(argv: string[]): void;
@@ -1,11 +1,12 @@
1
1
  import { writeFileSync } from 'node:fs';
2
2
  import { homedir } from 'node:os';
3
3
  import { join } from 'node:path';
4
- import { userScopeRoot } from './scope.js';
4
+ import { findProjectScopeRoot, resetScopeCache, userScopeRoot } from './scope.js';
5
5
  import { ensureDir, pathExists, readText, removePath, nowIso } from './fs-utils.js';
6
6
  import { readConfig, readState, updateConfig, updateState, ensureScopeInitialized } from './config.js';
7
7
  import { clone } from './git.js';
8
8
  import { readMarketplaceManifest } from './manifest.js';
9
+ import { CRTR_DIR_NAME } from '../types.js';
9
10
  export const OFFICIAL_MARKETPLACE_NAME = 'crouter-official-marketplace';
10
11
  export const OFFICIAL_MARKETPLACE_URL = 'https://github.com/crouton-labs/crouter-official-marketplace.git';
11
12
  export const OFFICIAL_MARKETPLACE_REF = 'main';
@@ -141,3 +142,29 @@ export function ensureOfficialMarketplace(argv) {
141
142
  }
142
143
  }
143
144
  }
145
+ export function ensureProjectScope(argv) {
146
+ try {
147
+ if (process.env.CRTR_NO_AUTO_INIT === '1')
148
+ return;
149
+ if (shouldSkipForArgv(argv))
150
+ return;
151
+ // Already inside a project scope (here or in an ancestor) — nothing to do.
152
+ if (findProjectScopeRoot() !== null)
153
+ return;
154
+ const cwd = process.cwd();
155
+ // Never auto-init at $HOME — that path is reserved for the user scope.
156
+ if (cwd === homedir())
157
+ return;
158
+ const projectRoot = join(cwd, CRTR_DIR_NAME);
159
+ if (projectRoot === userScopeRoot())
160
+ return;
161
+ ensureScopeInitialized('project', projectRoot);
162
+ resetScopeCache();
163
+ }
164
+ catch (e) {
165
+ if (process.env.CRTR_DEBUG === '1') {
166
+ const msg = e instanceof Error ? e.message : String(e);
167
+ process.stderr.write(`crtr: project-init error: ${msg}\n`);
168
+ }
169
+ }
170
+ }
@@ -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 declare function parseSkillQualifier(raw: string): {
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;
@@ -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 { plugin: pluginQualifier, name } = parseSkillQualifier(rawName);
143
- const plugins = opts.scope ? listInstalledPlugins(opts.scope) : listAllPlugins();
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 (!opts.pluginFilter &&
179
+ if (!pluginFilter &&
149
180
  (pluginQualifier === undefined || pluginQualifier === SCOPE_SKILL_PLUGIN)) {
150
- const scopes = opts.scope
151
- ? [opts.scope]
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 (opts.pluginFilter && plugin.name !== opts.pluginFilter)
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
- if (matches.length === 0) {
199
- throw notFound(pluginQualifier
200
- ? `skill not found: ${pluginQualifier}:${name}`
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
- // Resolution order picks the first; flag ambiguity only if user didn't qualify.
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 idx = raw.indexOf(':');
223
- if (idx === -1)
335
+ const colonIdx = raw.indexOf(':');
336
+ if (colonIdx === -1)
224
337
  return { name: raw };
225
- return { plugin: raw.slice(0, idx), name: raw.slice(idx + 1) };
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) => {
@@ -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 implementation agent. A plan has been approved upstream and your
29
- job is to execute it: write code, run tests, verify the change works
30
- end-to-end.
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
- ## Process
35
-
36
- 1. Read the plan end-to-end before touching code.
37
- 2. If the plan references a spec (\`--spec\` was passed when saving), read it
38
- too it has the contract you are realizing.
39
- 3. Read the files listed under "Files to modify / create" and "Existing
40
- utilities to reuse" to ground yourself in the current code.
41
- 4. Execute the plan step by step. Stay within scope — if the plan does not
42
- call for a change, do not make it.
43
- 5. After each meaningful change, run the verification described in the plan
44
- (tests, manual checks). Fix anything that fails before continuing.
45
- 6. When the plan is complete and verification passes, summarize what
46
- shipped in a single short message: files touched, tests run, what works.
47
-
48
- ## Guardrails
49
-
50
- - **Do not redesign.** If the plan is wrong, surface the issue and ask;
51
- do not silently substitute your own approach.
52
- - **Do not expand scope.** No drive-by refactors, no "while I'm here" cleanup.
53
- - **Honor existing conventions.** Match the file's style, naming, and
54
- patterns. Use the utilities the plan named.
55
- - Commit only if the user asks.
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
- When verification passes, your turn ends. The user may then ask for a code
58
- review via \`crtr agent review\`.
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
@@ -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. No extra step needed.
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
 
@@ -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 planning.
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
- ## What to Check
24
+ Read the spec end-to-end first.
24
25
 
25
- | Category | What to Look For |
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 — not covering multiple independent subsystems |
33
+ | Scope | Focused enough for a single plan |
31
34
  | YAGNI | Unrequested features, over-engineering |
32
35
 
33
- ## Calibration
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
- **Only flag issues that would cause real problems during implementation planning.**
36
- A missing section, a contradiction, or a requirement so ambiguous it could be
37
- interpreted two different ways those are issues. Minor wording improvements,
38
- stylistic preferences, and "sections less detailed than others" are not.
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 there are serious gaps that would lead to a flawed plan.
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 Format
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 X]: [specific issue] - [why it matters for planning]
62
+ - [Section]: [specific issue] [why it matters for planning]
50
63
 
51
64
  **Recommendations (advisory, do not block approval):**
52
- - [suggestions for improvement]
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 implementation.
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 Check
86
+ ## What to check
73
87
 
74
- | Category | What to Look For |
88
+ | Category | What to look for |
75
89
  |----------|------------------|
76
90
  | Completeness | TODOs, placeholders, incomplete tasks, missing steps |
77
- | Spec Alignment | Plan covers spec requirements, no major scope creep |
78
- | Task Decomposition | Tasks have clear boundaries, steps are actionable |
79
- | Buildability | Could an engineer follow this plan without getting stuck? |
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
- ## Calibration
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
- **Only flag issues that would cause real problems during implementation.**
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 there are serious gaps missing requirements from the spec,
88
- contradictory steps, placeholder content, or tasks so vague they can't be acted on.
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 Format
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 X, Step Y]: [specific issue] - [why it matters for implementation]
117
+ - [Task / Section]: [specific issue] [why it matters for implementation]
98
118
 
99
119
  **Recommendations (advisory, do not block approval):**
100
- - [suggestions for improvement]
120
+ - [suggestions]
101
121
 
102
122
  ${SUBMIT_INSTRUCTIONS}`;
103
123
  }
@@ -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>\`
@@ -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
- Prefer EARS-style phrasing where it fits (\`When <trigger>, the system shall
40
- <behavior>\`), but do not force it. Group requirements by capability.
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
- You may launch a Plan agent to draft requirements for a specific capability
43
- in parallel, but for small specs writing them yourself is usually faster.
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 the requirements against the shape — if a requirement reveals a
50
- gap in the design, refine the design before saving.
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
- - When <trigger>, the system shall <behavior>.
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. No extra step needed.
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crouton-kit/crouter",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "crtr — fast access to skills, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",