@crouton-kit/crouter 0.3.3 → 0.3.8

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.
@@ -1,4 +1,4 @@
1
- // `crtr flow plan` subtree — plan new / show / list handlers.
1
+ // `crtr agent plan` subtree — plan new / show / list handlers.
2
2
  export const PLAN_NEW_GUIDE = `## Planning workflow
3
3
 
4
4
  Build and save an implementation plan: a map another agent can execute without
@@ -74,9 +74,9 @@ parallelism to unlock.
74
74
 
75
75
  ### Phase 5: Save
76
76
 
77
- Run \`crtr flow plan new\`:
77
+ Run \`crtr agent plan new\`:
78
78
 
79
- echo '<plan markdown>' | crtr flow plan new <kebab-case-name> [--spec <spec-name>]
79
+ echo '<plan markdown>' | crtr agent plan new <kebab-case-name> [--spec <spec-name>]
80
80
 
81
81
  - NAME: short kebab-case slug. Nested names become subdirectories
82
82
  (e.g. \`auth/jwt-refresh\`).
@@ -99,9 +99,9 @@ are under-decomposed.
99
99
  After the reviewer approves the plan, your turn ends. Do not summarize in chat.
100
100
  For a human gate, optionally put the plan in front of a person with \`crtr
101
101
  human review\` (anchored comments) and gate the handoff with \`crtr human
102
- approve\`. This complements — it does not replace — \`crtr job start reviewer\`.
102
+ approve\`. This complements — it does not replace — \`crtr agent new reviewer\`.
103
103
  If the user is ready to build, ask once whether to hand off; if yes, run:
104
- \`crtr job start implementer\` with the plan path.`;
104
+ \`crtr agent new implementer\` with the plan path.`;
105
105
  export const PLAN_SHOW_GUIDE = '';
106
106
  import { defineBranch, defineLeaf } from '../core/command.js';
107
107
  import { saveArtifact, readArtifact, listArtifacts, OVERSIZE_WARN_LINES } from '../core/artifact.js';
@@ -110,7 +110,7 @@ export function registerPlan() {
110
110
  const planNew = defineLeaf({
111
111
  name: 'new',
112
112
  help: {
113
- name: 'plan new',
113
+ name: 'agent plan new',
114
114
  summary: 'draft a plan from intent and optional spec alignment',
115
115
  guide: PLAN_NEW_GUIDE,
116
116
  params: [
@@ -146,7 +146,7 @@ export function registerPlan() {
146
146
  name: 'follow_up',
147
147
  type: 'string',
148
148
  required: true,
149
- constraint: 'Recommended next call (reviewer job start).',
149
+ constraint: 'Recommended next call (reviewer spawn via `crtr agent new reviewer`).',
150
150
  },
151
151
  ],
152
152
  outputKind: 'object',
@@ -163,7 +163,7 @@ export function registerPlan() {
163
163
  if (spec !== undefined)
164
164
  meta['spec'] = spec;
165
165
  const { path, oversize, lineCount } = saveArtifact('plans', name, body, meta);
166
- let follow_up = `Review it: crtr job start reviewer --artifact-path ${path} --artifact-kind plan (returns {job_id}), then crtr job read result <job_id> --wait.`;
166
+ let follow_up = `Review it: crtr agent new reviewer ${path} --kind plan (returns {job_id}), then crtr job read result <job_id> --wait.`;
167
167
  follow_up +=
168
168
  ` Optional human gate (complements, does not replace the agent reviewer): crtr human review --file ${path} for anchored comments, then gate handoff with crtr human approve --title "Approve this plan?".`;
169
169
  if (oversize) {
@@ -176,7 +176,7 @@ export function registerPlan() {
176
176
  const planShow = defineLeaf({
177
177
  name: 'show',
178
178
  help: {
179
- name: 'plan show',
179
+ name: 'agent plan show',
180
180
  summary: 'read a plan artifact by name',
181
181
  params: [
182
182
  {
@@ -225,7 +225,7 @@ export function registerPlan() {
225
225
  const planList = defineLeaf({
226
226
  name: 'list',
227
227
  help: {
228
- name: 'plan list',
228
+ name: 'agent plan list',
229
229
  summary: 'paginated list of plan artifacts, sorted ascending by name',
230
230
  params: [
231
231
  {
@@ -295,7 +295,7 @@ export function registerPlan() {
295
295
  return defineBranch({
296
296
  name: 'plan',
297
297
  help: {
298
- name: 'plan',
298
+ name: 'agent plan',
299
299
  summary: 'create and read plan artifacts',
300
300
  model: 'Lifecycle: draft -> active -> handed-off.',
301
301
  children: [
@@ -1,5 +1,6 @@
1
1
  // `crtr skill` subtree handlers — P3 implementation.
2
- // Sub-branches: find {list, search, grep}, read {show, where}, author {guide, scaffold}, state {enable, disable}.
2
+ // Sub-branches: find {list, search, grep}, author {guide, scaffold}, state {enable, disable}.
3
+ // Leaf children of skill: read.
3
4
  import { join } from 'node:path';
4
5
  import { writeFileSync } from 'node:fs';
5
6
  import { defineBranch, defineLeaf } from '../core/command.js';
@@ -17,7 +18,7 @@ import { skillCreatePrompt, skillTemplatePrompt } from '../prompts/skill.js';
17
18
  // ---------------------------------------------------------------------------
18
19
  function formatNeighborQualifier(s) {
19
20
  return s.plugin === SCOPE_SKILL_PLUGIN
20
- ? `${s.scope}:${s.name}`
21
+ ? `${s.scope}/${s.name}`
21
22
  : `${s.plugin}/${s.name}`;
22
23
  }
23
24
  function formatNeighborKeywords(s) {
@@ -33,7 +34,7 @@ function buildNeighborsSection(skill) {
33
34
  return null;
34
35
  const lines = [
35
36
  '## Neighbors',
36
- '*Auto-discovered from filesystem. Run `crtr skill read show <name>` for full description + body.*',
37
+ '*Auto-discovered from filesystem. Run `crtr skill read <name>` for full description + body.*',
37
38
  '',
38
39
  ];
39
40
  if (siblings.length > 0) {
@@ -127,7 +128,7 @@ const findList = defineLeaf({
127
128
  return po;
128
129
  return a.name.localeCompare(b.name);
129
130
  });
130
- const keyOf = (sk) => `${sk.scope}:${sk.plugin}/${sk.name}`;
131
+ const keyOf = (sk) => `${sk.scope}/${sk.plugin}/${sk.name}`;
131
132
  const params = {};
132
133
  if (limit !== undefined)
133
134
  params.limit = limit;
@@ -155,7 +156,7 @@ const findList = defineLeaf({
155
156
  }),
156
157
  next_cursor: result.next_cursor,
157
158
  total: result.total,
158
- follow_up: 'Use `crtr skill read show <name>` for the full SKILL.md body. Run `crtr skill find list -h` for filters and verbosity.',
159
+ follow_up: 'Use `crtr skill read <name>` for the full SKILL.md body. Run `crtr skill find list -h` for filters and verbosity.',
159
160
  };
160
161
  },
161
162
  });
@@ -240,7 +241,7 @@ const findSearch = defineLeaf({
240
241
  score: h.score,
241
242
  description: h.skill.frontmatter.description !== undefined ? h.skill.frontmatter.description : null,
242
243
  })),
243
- follow_up: 'Use `crtr skill read show <name>` for the full SKILL.md body. Run `crtr skill find search -h` for filters.',
244
+ follow_up: 'Use `crtr skill read <name>` for the full SKILL.md body. Run `crtr skill find search -h` for filters.',
244
245
  };
245
246
  },
246
247
  });
@@ -322,25 +323,27 @@ const findBranch = defineBranch({
322
323
  children: [findList, findSearch, findGrep],
323
324
  });
324
325
  // ---------------------------------------------------------------------------
325
- // read sub-branch
326
+ // read leaf
326
327
  // ---------------------------------------------------------------------------
327
- const readShow = defineLeaf({
328
- name: 'show',
328
+ const readLeaf = defineLeaf({
329
+ name: 'read',
329
330
  help: {
330
- name: 'skill read show',
331
- summary: 'print SKILL.md body for a named skill',
331
+ name: 'skill read',
332
+ summary: 'load SKILL.md body and resolution metadata for a named skill',
332
333
  params: [
333
- { kind: 'positional', name: 'name', required: true, constraint: 'Skill identifier. Forms: <name>, <plugin>:<name>, <scope>:<name>, <scope>:<plugin>/<name>.' },
334
+ { kind: 'positional', name: 'name', required: true, constraint: 'Skill identifier. Forms: <name>, <plugin>/<name>, <scope>/<name>, <scope>/<plugin>/<name>.' },
334
335
  { kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: 'Narrows resolution when name is ambiguous.' },
335
336
  { kind: 'flag', name: 'plugin', type: 'string', required: false, constraint: 'Narrows resolution to a specific plugin.' },
336
- { kind: 'flag', name: 'frontmatter', type: 'bool', required: false, constraint: 'When present, includes YAML frontmatter in the output.' },
337
+ { kind: 'flag', name: 'frontmatter', type: 'bool', required: false, constraint: 'When present, includes YAML frontmatter in the output content.' },
338
+ { kind: 'flag', name: 'no-body', type: 'bool', required: false, constraint: 'When present, omits the body — returns resolution metadata only. Use to confirm a skill exists or locate it without loading SKILL.md.' },
337
339
  ],
338
340
  output: [
339
341
  { name: 'name', type: 'string', required: true, constraint: 'Resolved skill name.' },
340
342
  { name: 'plugin', type: 'string', required: true, constraint: 'Plugin the skill belongs to.' },
341
343
  { name: 'scope', type: 'string', required: true, constraint: 'Scope the skill was resolved from.' },
342
344
  { name: 'path', type: 'string', required: true, constraint: 'Absolute path to SKILL.md.' },
343
- { name: 'content', type: 'string', required: true, constraint: 'SKILL.md body (with or without frontmatter per the --frontmatter flag).' },
345
+ { name: 'content', type: 'string', required: false, constraint: 'SKILL.md body (with or without frontmatter per --frontmatter). Omitted when --no-body is set.' },
346
+ { name: 'follow_up', type: 'string', required: false, constraint: 'Hints at variant flags. Present on default reads; omitted when --no-body is set.' },
344
347
  ],
345
348
  outputKind: 'object',
346
349
  effects: ['None. Read-only.'],
@@ -350,6 +353,7 @@ const readShow = defineLeaf({
350
353
  const scopeStr = input['scope'];
351
354
  const pluginFilter = input['plugin'];
352
355
  const includeFrontmatter = input['frontmatter'];
356
+ const noBody = input['noBody'];
353
357
  const resolveOpts = {};
354
358
  if (scopeStr !== undefined) {
355
359
  const resolved = resolveScopeArg(scopeStr);
@@ -359,70 +363,21 @@ const readShow = defineLeaf({
359
363
  if (pluginFilter !== undefined)
360
364
  resolveOpts.pluginFilter = pluginFilter;
361
365
  const skillObj = resolveSkill(nameRaw, resolveOpts);
362
- const rawContent = readText(skillObj.path);
363
- const rawBody = includeFrontmatter ? rawContent : parseFrontmatter(rawContent).body;
364
- const content = appendNeighbors(skillObj, rawBody);
365
- return {
366
+ const out = {
366
367
  name: skillObj.name,
367
368
  plugin: skillObj.plugin,
368
369
  scope: skillObj.scope,
369
370
  path: skillObj.path,
370
- content,
371
371
  };
372
+ if (noBody)
373
+ return out;
374
+ const rawContent = readText(skillObj.path);
375
+ const rawBody = includeFrontmatter ? rawContent : parseFrontmatter(rawContent).body;
376
+ out['content'] = appendNeighbors(skillObj, rawBody);
377
+ out['follow_up'] = 'Add --no-body to skip the body and return path/scope/plugin only. Add --frontmatter to include YAML frontmatter in content.';
378
+ return out;
372
379
  },
373
380
  });
374
- const readWhere = defineLeaf({
375
- name: 'where',
376
- help: {
377
- name: 'skill read where',
378
- summary: 'show resolution metadata for a named skill without reading its body',
379
- params: [
380
- { kind: 'positional', name: 'name', required: true, constraint: 'Skill identifier. Same forms as skill read show.' },
381
- { kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: 'Narrows resolution.' },
382
- { kind: 'flag', name: 'plugin', type: 'string', required: false, constraint: 'Narrows resolution to a specific plugin.' },
383
- ],
384
- output: [
385
- { name: 'name', type: 'string', required: true, constraint: 'Resolved skill name.' },
386
- { name: 'plugin', type: 'string', required: true, constraint: 'Plugin the skill belongs to.' },
387
- { name: 'scope', type: 'string', required: true, constraint: 'Scope the skill was resolved from.' },
388
- { name: 'path', type: 'string', required: true, constraint: 'Absolute path to SKILL.md.' },
389
- ],
390
- outputKind: 'object',
391
- effects: ['None. Read-only.'],
392
- },
393
- run: async (input) => {
394
- const nameRaw = input['name'];
395
- const scopeStr = input['scope'];
396
- const pluginFilter = input['plugin'];
397
- const resolveOpts = {};
398
- if (scopeStr !== undefined) {
399
- const resolved = resolveScopeArg(scopeStr);
400
- if (resolved !== 'all')
401
- resolveOpts.scope = resolved;
402
- }
403
- if (pluginFilter !== undefined)
404
- resolveOpts.pluginFilter = pluginFilter;
405
- const skillObj = resolveSkill(nameRaw, resolveOpts);
406
- return {
407
- name: skillObj.name,
408
- plugin: skillObj.plugin,
409
- scope: skillObj.scope,
410
- path: skillObj.path,
411
- };
412
- },
413
- });
414
- const readBranch = defineBranch({
415
- name: 'read',
416
- help: {
417
- name: 'skill read',
418
- summary: 'read skill content or resolve its location',
419
- children: [
420
- { name: 'show', desc: 'print SKILL.md body', useWhen: 'loading skill content to act on it' },
421
- { name: 'where', desc: 'show resolution metadata only', useWhen: 'verifying which skill resolves and from where, without loading its body' },
422
- ],
423
- },
424
- children: [readShow, readWhere],
425
- });
426
381
  // ---------------------------------------------------------------------------
427
382
  // author sub-branch
428
383
  // ---------------------------------------------------------------------------
@@ -461,7 +416,7 @@ const authorScaffold = defineLeaf({
461
416
  name: 'skill author scaffold',
462
417
  summary: 'create an empty SKILL.md stub at the given qualifier',
463
418
  params: [
464
- { kind: 'positional', name: 'qualifier', required: true, constraint: 'Skill identifier in <plugin>:<skill> form.' },
419
+ { kind: 'positional', name: 'qualifier', required: true, constraint: 'Skill identifier in <plugin>/<skill> form.' },
465
420
  { kind: 'flag', name: 'type', type: 'enum', choices: [...VALID_TYPES], required: false, constraint: 'One of: playbook, primer, reference, runbook, freeform.' },
466
421
  { kind: 'flag', name: 'description', type: 'string', required: false, constraint: 'Short description written to frontmatter.' },
467
422
  { kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: 'Default: project if available, else user.' },
@@ -481,16 +436,22 @@ const authorScaffold = defineLeaf({
481
436
  const typeStr = input['type'];
482
437
  const description = input['description'];
483
438
  const scopeStr = input['scope'];
484
- const { plugin: pluginName, name: skillName } = parseSkillQualifier(qualifier);
485
- if (!skillName) {
439
+ const parsed = parseSkillQualifier(qualifier);
440
+ if (parsed.segments.length === 0) {
486
441
  throw usage('skill name required in qualifier');
487
442
  }
443
+ // For scaffold, the qualifier is always <plugin>/<skill>. If it's a single segment,
444
+ // treat it as a scope-direct skill name. Otherwise first segment is the plugin.
445
+ const pluginName = parsed.segments.length > 1 ? parsed.segments[0] : undefined;
446
+ const skillName = parsed.segments.length > 1
447
+ ? parsed.segments.slice(1).join('/')
448
+ : parsed.segments[0];
488
449
  if (typeStr !== undefined && !isSkillType(typeStr)) {
489
450
  throw usage(`unknown skill type: ${typeStr} / valid: ${SKILL_TYPES.join(' | ')}`);
490
451
  }
491
452
  const skillType = typeStr !== undefined && isSkillType(typeStr) ? typeStr : undefined;
492
453
  let skillFile;
493
- // Scope-direct: no plugin qualifier, or explicit `_:` sentinel
454
+ // Scope-direct: no plugin qualifier, or explicit `_/` sentinel (internal only)
494
455
  if (pluginName === undefined || pluginName === SCOPE_SKILL_PLUGIN) {
495
456
  const scope = resolveWriteScope(scopeStr);
496
457
  const scopeRootPath = requireScopeRoot(scope);
@@ -578,7 +539,7 @@ const stateEnable = defineLeaf({
578
539
  name: 'skill state enable',
579
540
  summary: 'enable a skill in the given scope',
580
541
  params: [
581
- { kind: 'positional', name: 'name', required: true, constraint: 'Skill identifier. Same forms as skill read show.' },
542
+ { kind: 'positional', name: 'name', required: true, constraint: 'Skill identifier. Same forms as skill read.' },
582
543
  { kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: 'Default: project if available, else user.' },
583
544
  ],
584
545
  output: [
@@ -597,7 +558,7 @@ const stateDisable = defineLeaf({
597
558
  name: 'skill state disable',
598
559
  summary: 'disable a skill in the given scope, hiding it from list and agent discovery',
599
560
  params: [
600
- { kind: 'positional', name: 'name', required: true, constraint: 'Skill identifier. Same forms as skill read show.' },
561
+ { kind: 'positional', name: 'name', required: true, constraint: 'Skill identifier. Same forms as skill read.' },
601
562
  { kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: 'Default: project if available, else user.' },
602
563
  ],
603
564
  output: [
@@ -622,12 +583,6 @@ const stateBranch = defineBranch({
622
583
  },
623
584
  children: [stateEnable, stateDisable],
624
585
  });
625
- // ---------------------------------------------------------------------------
626
- // Loaded-skills catalog (dynamicState for `skill -h`)
627
- // ---------------------------------------------------------------------------
628
- // A skill is a forest root within its source (scope+plugin) when no other
629
- // skill in that same source is its ancestor (`name` prefix + '/'). Nested
630
- // children stay discoverable via `skill find list` and the Neighbors section.
631
586
  function buildSkillCatalog() {
632
587
  let skills;
633
588
  try {
@@ -640,38 +595,90 @@ function buildSkillCatalog() {
640
595
  return null;
641
596
  const bySource = new Map();
642
597
  for (const s of skills) {
643
- const key = `${s.scope}${s.plugin}`;
598
+ const key = `${s.scope}\t${s.plugin}`;
644
599
  const arr = bySource.get(key);
645
600
  if (arr)
646
601
  arr.push(s);
647
602
  else
648
603
  bySource.set(key, [s]);
649
604
  }
650
- const byPrefix = new Map();
651
- for (const group of bySource.values()) {
605
+ const projectSources = [];
606
+ const userSources = [];
607
+ for (const [key, group] of bySource) {
608
+ const [scope, plugin] = key.split('\t');
652
609
  const names = group.map((g) => g.name);
653
- for (const s of group) {
654
- const isChild = names.some((n) => n !== s.name && s.name.startsWith(n + '/'));
655
- if (isChild)
656
- continue;
657
- const prefix = s.plugin === SCOPE_SKILL_PLUGIN ? `${s.scope}:` : `${s.plugin}/`;
658
- let set = byPrefix.get(prefix);
659
- if (!set) {
660
- set = new Set();
661
- byPrefix.set(prefix, set);
662
- }
663
- set.add(s.name);
664
- }
610
+ const roots = names
611
+ .filter((n) => !names.some((m) => m !== n && n.startsWith(m + '/')))
612
+ .sort();
613
+ if (roots.length === 0)
614
+ continue;
615
+ (scope === 'project' ? projectSources : userSources).push({ plugin, roots });
665
616
  }
666
- const prefixes = [...byPrefix.keys()].sort();
667
- const prefixW = prefixes.reduce((m, p) => (p.length > m ? p.length : m), 0);
668
617
  const lines = [`Loaded skills (${skills.length})`];
669
- for (const prefix of prefixes) {
670
- const names = [...byPrefix.get(prefix)].sort();
671
- lines.push(` ${prefix.padEnd(prefixW)} ${names.join(', ')}`);
672
- }
618
+ renderCatalogSection('Project', projectSources, lines);
619
+ renderCatalogSection('User', userSources, lines);
673
620
  return lines.join('\n');
674
621
  }
622
+ function renderCatalogSection(label, sources, out) {
623
+ if (sources.length === 0)
624
+ return;
625
+ const count = sources.reduce((n, s) => n + s.roots.length, 0);
626
+ out.push('');
627
+ out.push(`${label} (${count})`);
628
+ const sentinel = sources.filter((s) => s.plugin === SCOPE_SKILL_PLUGIN);
629
+ const named = sources
630
+ .filter((s) => s.plugin !== SCOPE_SKILL_PLUGIN)
631
+ .sort((a, b) => a.plugin.localeCompare(b.plugin));
632
+ for (const s of sentinel) {
633
+ for (const n of s.roots)
634
+ out.push(` ${n}`);
635
+ }
636
+ if (named.length === 0)
637
+ return;
638
+ const classified = named.map((s) => {
639
+ const subcats = new Map();
640
+ const bare = [];
641
+ for (const n of s.roots) {
642
+ const slash = n.indexOf('/');
643
+ if (slash === -1) {
644
+ bare.push(n);
645
+ }
646
+ else {
647
+ const sub = n.slice(0, slash);
648
+ const rest = n.slice(slash + 1);
649
+ const arr = subcats.get(sub);
650
+ if (arr)
651
+ arr.push(rest);
652
+ else
653
+ subcats.set(sub, [rest]);
654
+ }
655
+ }
656
+ return { plugin: s.plugin, roots: s.roots, subcats, bare };
657
+ });
658
+ // Smart-nest: a plugin nests iff it has 2+ distinct subcategories.
659
+ const nests = (p) => p.subcats.size >= 2;
660
+ const inlineLabels = classified.filter((p) => !nests(p)).map((p) => `${p.plugin}/`);
661
+ const inlineW = inlineLabels.reduce((m, p) => (p.length > m ? p.length : m), 0);
662
+ for (const p of classified) {
663
+ if (nests(p)) {
664
+ out.push(` ${p.plugin}/`);
665
+ if (p.bare.length > 0) {
666
+ out.push(` ${[...p.bare].sort().join(', ')}`);
667
+ }
668
+ const subKeys = [...p.subcats.keys()].sort();
669
+ const subLabels = subKeys.map((k) => `${k}/`);
670
+ const subW = subLabels.reduce((m, k) => (k.length > m ? k.length : m), 0);
671
+ for (let i = 0; i < subKeys.length; i++) {
672
+ const children = p.subcats.get(subKeys[i]).sort();
673
+ out.push(` ${subLabels[i].padEnd(subW)} ${children.join(', ')}`);
674
+ }
675
+ }
676
+ else {
677
+ const pluginLabel = `${p.plugin}/`;
678
+ out.push(` ${pluginLabel.padEnd(inlineW)} ${[...p.roots].sort().join(', ')}`);
679
+ }
680
+ }
681
+ }
675
682
  // ---------------------------------------------------------------------------
676
683
  // Root export
677
684
  // ---------------------------------------------------------------------------
@@ -681,15 +688,15 @@ export function registerSkill() {
681
688
  help: {
682
689
  name: 'skill',
683
690
  summary: 'discover, read, author, and manage skill state',
684
- model: '`find` when you do not yet know which skill applies — it locates candidates by topic, keyword, or body text. `read` when you have a name and need the SKILL.md content or its on-disk location. `author` when you are writing a new skill — it carries the template workflow and the scaffolder. `state` when a skill should be hidden from discovery without being removed. Append `-h` at any branch or leaf for its full schema.',
691
+ model: '`find` when you do not yet know which skill applies — it locates candidates by topic, keyword, or body text. `read` (leaf) loads SKILL.md by name; takes the name as a positional, returns body + metadata, accepts --no-body to skip the body. `author` when you are writing a new skill — it carries the template workflow and the scaffolder. `state` when a skill should be hidden from discovery without being removed. Append `-h` at any branch or leaf for its full schema.',
685
692
  dynamicState: buildSkillCatalog,
686
693
  children: [
687
694
  { name: 'find', desc: 'list, search, or grep skills', useWhen: 'discovering what skills are available' },
688
- { name: 'read', desc: 'read skill content or resolve location', useWhen: 'loading a skill to act on it' },
695
+ { name: 'read', desc: 'load SKILL.md body + metadata for a named skill', useWhen: 'loading a skill to act on it' },
689
696
  { name: 'author', desc: 'create and scaffold skills', useWhen: 'writing a new skill' },
690
697
  { name: 'state', desc: 'enable or disable skills', useWhen: 'toggling skill visibility' },
691
698
  ],
692
699
  },
693
- children: [findBranch, readBranch, authorBranch, stateBranch],
700
+ children: [findBranch, readLeaf, authorBranch, stateBranch],
694
701
  });
695
702
  }
@@ -1,3 +1,3 @@
1
- export declare const SPEC_NEW_GUIDE = "## Spec workflow\n\nBuild and save a design + requirements spec: a document describing what to\nbuild, the shape of the solution, and the behaviors it must satisfy. A spec is\nupstream of a plan \u2014 it captures decisions, not implementation steps.\n\nAnti-pattern: do not fish for clarifications upfront. Draft a concrete spec\nfirst based on your investigation, then iterate. A specific draft the user can\nreact to converges faster than a list of questions in a vacuum.\n\n### Phase 1: Shape\n\nBuild a comprehensive picture of the problem and the relevant code. Surface\nexisting patterns, constraints, and prior decisions.\n\nLaunch up to 3 Explore subagents IN PARALLEL (single message, multiple tool\ncalls). Use 1 agent for narrow, well-scoped problems; use more when the spec\ntouches several subsystems or you need to compare existing implementations.\nQuality over quantity \u2014 3 agents maximum.\n\nAfter exploration, draft a high-level design: the shape of the solution, new or\nchanged pieces, the boundaries.\n\n### Phase 2: Requirements\n\nTranslate the shape into concrete behavioral requirements. Each requirement\nmust be:\n\n- Testable \u2014 has a clear pass/fail condition.\n- Behavior-focused \u2014 describes what the system does, not how.\n- Scoped \u2014 covers one observable behavior.\n\nGroup requirements by capability. Plain English is fine. For conditional or\nstateful behaviors, EARS templates sharpen phrasing:\n`When <trigger>, the system shall <behavior>` or\n`If <condition>, then the system shall <response>`.\n\nFor larger / multi-component designs, walk the design end-to-end: at each step\nfrom trigger to final state, verify preconditions, state, failure handling, and\nhandoffs between components are specified. Skip this for small self-contained\nspecs.\n\n### Phase 3: Deepen\n\nRead the critical files identified during Phase 1. Reconcile requirements\nagainst the shape \u2014 if a requirement reveals a gap in the design, refine the\ndesign before saving.\n\nUse AskUserQuestion ONLY to clarify requirements or choose between approaches.\nNever use it to ask \"is this spec okay?\" or \"should I save?\".\n\n### Phase 4: Compose the spec body\n\nRequired sections:\n\n # Spec: <one-line title>\n\n ## Context\n <the problem this spec addresses, what motivates it, and the intended outcome.\n Include relevant constraints \u2014 user goals, stakeholders, deadlines.>\n\n ## Design\n <the shape of the solution. Components, data flow, key decisions and why they\n were chosen. Reference existing code with `file_path:line_number`.>\n\n ## Requirements\n <grouped behavioral requirements. Each one testable. Plain English is fine.>\n\n ### <Capability A>\n - <one observable behavior>\n\n ### <Capability B>\n - ...\n\n ## Out of scope\n <things explicitly NOT covered, so the next reader knows where the edges are.>\n\n ## Open questions\n <anything you could not resolve. Empty if all decisions are pinned.>\n\n### Phase 5: Save\n\nRun `crtr flow spec new`:\n\n echo '<spec markdown>' | crtr flow spec new <kebab-case-name>\n\n- NAME: short kebab-case slug. Nested names become subdirectories\n (e.g. `auth/refresh-tokens`).\n- Pipe the full spec markdown composed in Phase 4 on stdin.\n\nOutput: `{path, follow_up}`. The `follow_up` field names the exact next call\n\u2014 run it.\n\n### Phase 6: Done\n\nAfter the reviewer approves the spec, your turn ends. Do not summarize in chat.\nFor a human gate, optionally run `crtr human review` on the spec for anchored\ncomments and `crtr human approve` to gate the handoff \u2014 this complements, not\nreplaces, `crtr job start reviewer`.\nIf the user is ready to plan, ask once whether to hand off; if yes, follow the\n`follow_up` instructions from the save output.";
1
+ export declare const SPEC_NEW_GUIDE = "## Spec workflow\n\nBuild and save a design + requirements spec: a document describing what to\nbuild, the shape of the solution, and the behaviors it must satisfy. A spec is\nupstream of a plan \u2014 it captures decisions, not implementation steps.\n\nAnti-pattern: do not fish for clarifications upfront. Draft a concrete spec\nfirst based on your investigation, then iterate. A specific draft the user can\nreact to converges faster than a list of questions in a vacuum.\n\n### Phase 1: Shape\n\nBuild a comprehensive picture of the problem and the relevant code. Surface\nexisting patterns, constraints, and prior decisions.\n\nLaunch up to 3 Explore subagents IN PARALLEL (single message, multiple tool\ncalls). Use 1 agent for narrow, well-scoped problems; use more when the spec\ntouches several subsystems or you need to compare existing implementations.\nQuality over quantity \u2014 3 agents maximum.\n\nAfter exploration, draft a high-level design: the shape of the solution, new or\nchanged pieces, the boundaries.\n\n### Phase 2: Requirements\n\nTranslate the shape into concrete behavioral requirements. Each requirement\nmust be:\n\n- Testable \u2014 has a clear pass/fail condition.\n- Behavior-focused \u2014 describes what the system does, not how.\n- Scoped \u2014 covers one observable behavior.\n\nGroup requirements by capability. Plain English is fine. For conditional or\nstateful behaviors, EARS templates sharpen phrasing:\n`When <trigger>, the system shall <behavior>` or\n`If <condition>, then the system shall <response>`.\n\nFor larger / multi-component designs, walk the design end-to-end: at each step\nfrom trigger to final state, verify preconditions, state, failure handling, and\nhandoffs between components are specified. Skip this for small self-contained\nspecs.\n\n### Phase 3: Deepen\n\nRead the critical files identified during Phase 1. Reconcile requirements\nagainst the shape \u2014 if a requirement reveals a gap in the design, refine the\ndesign before saving.\n\nUse AskUserQuestion ONLY to clarify requirements or choose between approaches.\nNever use it to ask \"is this spec okay?\" or \"should I save?\".\n\n### Phase 4: Compose the spec body\n\nRequired sections:\n\n # Spec: <one-line title>\n\n ## Context\n <the problem this spec addresses, what motivates it, and the intended outcome.\n Include relevant constraints \u2014 user goals, stakeholders, deadlines.>\n\n ## Design\n <the shape of the solution. Components, data flow, key decisions and why they\n were chosen. Reference existing code with `file_path:line_number`.>\n\n ## Requirements\n <grouped behavioral requirements. Each one testable. Plain English is fine.>\n\n ### <Capability A>\n - <one observable behavior>\n\n ### <Capability B>\n - ...\n\n ## Out of scope\n <things explicitly NOT covered, so the next reader knows where the edges are.>\n\n ## Open questions\n <anything you could not resolve. Empty if all decisions are pinned.>\n\n### Phase 5: Save\n\nRun `crtr agent spec new`:\n\n echo '<spec markdown>' | crtr agent spec new <kebab-case-name>\n\n- NAME: short kebab-case slug. Nested names become subdirectories\n (e.g. `auth/refresh-tokens`).\n- Pipe the full spec markdown composed in Phase 4 on stdin.\n\nOutput: `{path, follow_up}`. The `follow_up` field names the exact next call\n\u2014 run it.\n\n### Phase 6: Done\n\nAfter the reviewer approves the spec, your turn ends. Do not summarize in chat.\nFor a human gate, optionally run `crtr human review` on the spec for anchored\ncomments and `crtr human approve` to gate the handoff \u2014 this complements, not\nreplaces, `crtr agent new reviewer`.\nIf the user is ready to plan, ask once whether to hand off; if yes, follow the\n`follow_up` instructions from the save output.";
2
2
  import type { BranchDef } from '../core/command.js';
3
3
  export declare function registerSpec(): BranchDef;
@@ -1,4 +1,4 @@
1
- // `crtr flow spec` subtree — spec new / show / list handlers.
1
+ // `crtr agent spec` subtree — spec new / show / list handlers.
2
2
  export const SPEC_NEW_GUIDE = `## Spec workflow
3
3
 
4
4
  Build and save a design + requirements spec: a document describing what to
@@ -81,9 +81,9 @@ Required sections:
81
81
 
82
82
  ### Phase 5: Save
83
83
 
84
- Run \`crtr flow spec new\`:
84
+ Run \`crtr agent spec new\`:
85
85
 
86
- echo '<spec markdown>' | crtr flow spec new <kebab-case-name>
86
+ echo '<spec markdown>' | crtr agent spec new <kebab-case-name>
87
87
 
88
88
  - NAME: short kebab-case slug. Nested names become subdirectories
89
89
  (e.g. \`auth/refresh-tokens\`).
@@ -97,7 +97,7 @@ Output: \`{path, follow_up}\`. The \`follow_up\` field names the exact next call
97
97
  After the reviewer approves the spec, your turn ends. Do not summarize in chat.
98
98
  For a human gate, optionally run \`crtr human review\` on the spec for anchored
99
99
  comments and \`crtr human approve\` to gate the handoff — this complements, not
100
- replaces, \`crtr job start reviewer\`.
100
+ replaces, \`crtr agent new reviewer\`.
101
101
  If the user is ready to plan, ask once whether to hand off; if yes, follow the
102
102
  \`follow_up\` instructions from the save output.`;
103
103
  import { defineBranch, defineLeaf } from '../core/command.js';
@@ -107,7 +107,7 @@ export function registerSpec() {
107
107
  const specNew = defineLeaf({
108
108
  name: 'new',
109
109
  help: {
110
- name: 'spec new',
110
+ name: 'agent spec new',
111
111
  summary: 'draft a specification artifact from intent',
112
112
  guide: SPEC_NEW_GUIDE,
113
113
  params: [
@@ -136,7 +136,7 @@ export function registerSpec() {
136
136
  name: 'follow_up',
137
137
  type: 'string',
138
138
  required: true,
139
- constraint: 'Recommended next call (planner job start).',
139
+ constraint: 'Recommended next call (planner spawn via `crtr agent new planner`).',
140
140
  },
141
141
  ],
142
142
  outputKind: 'object',
@@ -146,9 +146,9 @@ export function registerSpec() {
146
146
  const name = input['name'];
147
147
  const body = input['body'];
148
148
  const { path, oversize, lineCount } = saveArtifact('specs', name, body);
149
- let follow_up = `Plan it: crtr job start planner --artifact-path ${path} (returns {job_id}), then crtr job read result <job_id> --wait.`;
149
+ let follow_up = `Plan it: crtr agent new planner ${path} (returns {job_id}), then crtr job read result <job_id> --wait.`;
150
150
  follow_up +=
151
- ` Optional human gate before planning (complements, does not replace \`crtr job start reviewer\`): crtr human review --file ${path} for anchored comments, then gate with crtr human approve --title "Approve this spec?".`;
151
+ ` Optional human gate before planning (complements, does not replace \`crtr agent new reviewer\`): crtr human review --file ${path} for anchored comments, then gate with crtr human approve --title "Approve this spec?".`;
152
152
  if (oversize) {
153
153
  follow_up +=
154
154
  ` OVERSIZE ADVISORY: this spec is ${lineCount} lines (> ${OVERSIZE_WARN_LINES}). Split into focused sub-specs before planning.`;
@@ -159,7 +159,7 @@ export function registerSpec() {
159
159
  const specShow = defineLeaf({
160
160
  name: 'show',
161
161
  help: {
162
- name: 'spec show',
162
+ name: 'agent spec show',
163
163
  summary: 'read a spec artifact by name',
164
164
  params: [
165
165
  {
@@ -202,7 +202,7 @@ export function registerSpec() {
202
202
  const specList = defineLeaf({
203
203
  name: 'list',
204
204
  help: {
205
- name: 'spec list',
205
+ name: 'agent spec list',
206
206
  summary: 'paginated list of spec artifacts, sorted ascending by name',
207
207
  params: [
208
208
  {
@@ -272,7 +272,7 @@ export function registerSpec() {
272
272
  return defineBranch({
273
273
  name: 'spec',
274
274
  help: {
275
- name: 'spec',
275
+ name: 'agent spec',
276
276
  summary: 'create and read specification artifacts',
277
277
  model: 'Lifecycle: draft -> approved. Approved specs drive plan creation.',
278
278
  children: [