@crouton-kit/crouter 0.3.3 → 0.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +2 -2
  2. package/dist/builtin-skills/skills/crouter-development/marketplaces/SKILL.md +1 -1
  3. package/dist/builtin-skills/skills/crouter-development/plugins/SKILL.md +3 -3
  4. package/dist/cli.js +16 -26
  5. package/dist/commands/__tests__/skill.test.js +24 -28
  6. package/dist/commands/agent.d.ts +6 -0
  7. package/dist/commands/agent.js +585 -0
  8. package/dist/commands/debug.d.ts +1 -1
  9. package/dist/commands/debug.js +20 -7
  10. package/dist/commands/human.js +51 -19
  11. package/dist/commands/job.d.ts +9 -0
  12. package/dist/commands/job.js +100 -385
  13. package/dist/commands/{flow.d.ts → mode.d.ts} +1 -1
  14. package/dist/commands/mode.js +231 -0
  15. package/dist/commands/pkg.js +5 -0
  16. package/dist/commands/plan.d.ts +1 -1
  17. package/dist/commands/plan.js +24 -11
  18. package/dist/commands/skill.js +130 -107
  19. package/dist/commands/spec.d.ts +1 -1
  20. package/dist/commands/spec.js +24 -11
  21. package/dist/commands/sys.js +5 -0
  22. package/dist/core/__tests__/job.test.js +38 -74
  23. package/dist/core/__tests__/jobs.test.d.ts +1 -0
  24. package/dist/core/__tests__/jobs.test.js +98 -0
  25. package/dist/core/__tests__/resolver.test.d.ts +1 -0
  26. package/dist/core/__tests__/resolver.test.js +181 -0
  27. package/dist/core/__tests__/spawn.test.d.ts +1 -0
  28. package/dist/core/__tests__/spawn.test.js +138 -0
  29. package/dist/core/__tests__/subagents.test.d.ts +1 -0
  30. package/dist/core/__tests__/subagents.test.js +75 -0
  31. package/dist/core/__tests__/unknown-path.test.d.ts +1 -0
  32. package/dist/core/__tests__/unknown-path.test.js +52 -0
  33. package/dist/core/bootstrap.d.ts +2 -0
  34. package/dist/core/bootstrap.js +66 -0
  35. package/dist/core/command.d.ts +58 -2
  36. package/dist/core/command.js +62 -14
  37. package/dist/core/config.js +20 -2
  38. package/dist/core/frontmatter.d.ts +10 -0
  39. package/dist/core/frontmatter.js +24 -9
  40. package/dist/core/help.d.ts +39 -8
  41. package/dist/core/help.js +64 -32
  42. package/dist/core/jobs.d.ts +33 -13
  43. package/dist/core/jobs.js +259 -47
  44. package/dist/core/resolver.d.ts +1 -2
  45. package/dist/core/resolver.js +111 -47
  46. package/dist/core/spawn.d.ts +150 -10
  47. package/dist/core/spawn.js +493 -41
  48. package/dist/core/subagents.d.ts +18 -0
  49. package/dist/core/subagents.js +163 -0
  50. package/dist/prompts/agent.d.ts +12 -3
  51. package/dist/prompts/agent.js +51 -18
  52. package/dist/prompts/debug.js +14 -7
  53. package/dist/prompts/skill.js +16 -16
  54. package/dist/types.d.ts +22 -1
  55. package/dist/types.js +5 -2
  56. package/package.json +2 -2
  57. package/dist/commands/flow.js +0 -24
@@ -0,0 +1,163 @@
1
+ // Subagent discovery + resolution.
2
+ //
3
+ // Subagents are markdown files with YAML frontmatter, modeled on the pi
4
+ // subagent extension but surfaced through the crtr `agent` CLI. Each file
5
+ // declares a name/description (+ optional tools/model) in frontmatter; its
6
+ // markdown body becomes the spawned agent's appended system prompt.
7
+ //
8
+ // Layout mirrors skills, one level shallower (flat files, not nested dirs):
9
+ // <scope-root>/agents/<name>.md — scope-root agents (user/project)
10
+ // <plugin-root>/agents/<name>.md — plugin-provided agents
11
+ //
12
+ // Resolution precedence matches skills: project before user before builtin;
13
+ // scope-root agents before plugin agents within a scope.
14
+ import { join, basename } from 'node:path';
15
+ import { readdirSync } from 'node:fs';
16
+ import { AGENTS_DIR, SCOPE_SKILL_PLUGIN } from '../types.js';
17
+ import { listAllPlugins, listInstalledPlugins } from './resolver.js';
18
+ import { parseFrontmatterGeneric } from './frontmatter.js';
19
+ import { pathExists, readText } from './fs-utils.js';
20
+ import { projectScopeRoot, scopeRoot } from './scope.js';
21
+ import { ambiguous, notFound } from './errors.js';
22
+ /** `<scope-root>/agents` for a given scope, or null when the scope has no root. */
23
+ export function scopeAgentsDir(scope) {
24
+ const root = scopeRoot(scope);
25
+ return root ? join(root, AGENTS_DIR) : null;
26
+ }
27
+ function coerceTools(value) {
28
+ if (Array.isArray(value)) {
29
+ const arr = value.map((v) => String(v).trim()).filter(Boolean);
30
+ return arr.length > 0 ? arr : undefined;
31
+ }
32
+ if (typeof value === 'string') {
33
+ const arr = value
34
+ .split(',')
35
+ .map((t) => t.trim())
36
+ .filter(Boolean);
37
+ return arr.length > 0 ? arr : undefined;
38
+ }
39
+ return undefined;
40
+ }
41
+ function parseSubagentFile(filePath, scope, plugin) {
42
+ let source;
43
+ try {
44
+ source = readText(filePath);
45
+ }
46
+ catch {
47
+ return null;
48
+ }
49
+ const { data, body } = parseFrontmatterGeneric(source);
50
+ if (data === null)
51
+ return null;
52
+ // Name defaults to the filename stem when frontmatter omits it. A description
53
+ // is required for the agent to be useful in listings; skip files without one.
54
+ const fileStem = basename(filePath).replace(/\.md$/i, '');
55
+ const name = typeof data.name === 'string' && data.name.trim() !== ''
56
+ ? data.name.trim()
57
+ : fileStem;
58
+ if (typeof data.description !== 'string' || data.description.trim() === '') {
59
+ return null;
60
+ }
61
+ const fm = {
62
+ name,
63
+ description: data.description.trim(),
64
+ tools: coerceTools(data.tools),
65
+ model: typeof data.model === 'string' && data.model.trim() !== '' ? data.model.trim() : undefined,
66
+ };
67
+ return {
68
+ name,
69
+ plugin,
70
+ scope,
71
+ path: filePath,
72
+ frontmatter: fm,
73
+ systemPrompt: body,
74
+ };
75
+ }
76
+ function listAgentsInDir(dir, scope, plugin) {
77
+ if (!pathExists(dir))
78
+ return [];
79
+ let entries;
80
+ try {
81
+ entries = readdirSync(dir, { withFileTypes: true });
82
+ }
83
+ catch {
84
+ return [];
85
+ }
86
+ const out = [];
87
+ for (const e of entries) {
88
+ if (!e.name.toLowerCase().endsWith('.md'))
89
+ continue;
90
+ if (!e.isFile() && !e.isSymbolicLink())
91
+ continue;
92
+ const parsed = parseSubagentFile(join(dir, e.name), scope, plugin);
93
+ if (parsed !== null)
94
+ out.push(parsed);
95
+ }
96
+ return out;
97
+ }
98
+ /** Scope-root agents under `<scope-root>/agents/*.md`. */
99
+ export function listScopeRootSubagents(scope) {
100
+ if (scope === 'builtin')
101
+ return [];
102
+ const dir = scopeAgentsDir(scope);
103
+ if (!dir)
104
+ return [];
105
+ return listAgentsInDir(dir, scope, SCOPE_SKILL_PLUGIN);
106
+ }
107
+ /** All subagents: scope-root agents (project, user) plus enabled plugins. */
108
+ export function listSubagents(scopeFilter) {
109
+ const scopes = scopeFilter
110
+ ? [scopeFilter]
111
+ : [projectScopeRoot() ? 'project' : null, 'user'].filter(Boolean);
112
+ const fromScopeRoots = scopes.flatMap((s) => listScopeRootSubagents(s));
113
+ const plugins = scopeFilter ? listInstalledPlugins(scopeFilter) : listAllPlugins();
114
+ const fromPlugins = plugins
115
+ .filter((p) => p.enabled)
116
+ .flatMap((p) => listAgentsInDir(join(p.root, AGENTS_DIR), p.scope, p.name));
117
+ return [...fromScopeRoots, ...fromPlugins].sort((a, b) => a.name.localeCompare(b.name));
118
+ }
119
+ /** Canonical, unambiguous identifier: `<plugin>/<name>`, or bare `<name>` for
120
+ * scope-root agents. */
121
+ export function subagentId(a) {
122
+ return a.plugin === SCOPE_SKILL_PLUGIN ? a.name : `${a.plugin}/${a.name}`;
123
+ }
124
+ /** Resolve a subagent by name. Accepts a bare `<name>` or a `<plugin>/<name>`
125
+ * qualifier. Project precedes user precedes builtin; scope-root precedes
126
+ * plugin. Throws notFound / ambiguous as the skill resolver does. */
127
+ export function resolveSubagent(rawName, opts = {}) {
128
+ const slash = rawName.indexOf('/');
129
+ const pluginQualifier = opts.plugin ?? (slash !== -1 ? rawName.slice(0, slash) : undefined);
130
+ const name = slash !== -1 && opts.plugin === undefined ? rawName.slice(slash + 1) : rawName;
131
+ const all = listSubagents(opts.scope);
132
+ let matches = all.filter((a) => a.name === name);
133
+ if (pluginQualifier !== undefined) {
134
+ matches = matches.filter((a) => a.plugin === pluginQualifier ||
135
+ (pluginQualifier === SCOPE_SKILL_PLUGIN && a.plugin === SCOPE_SKILL_PLUGIN));
136
+ }
137
+ if (matches.length === 1)
138
+ return matches[0];
139
+ if (matches.length === 0) {
140
+ const known = all.map(subagentId).slice(0, 8).join(', ');
141
+ throw notFound(`subagent not found: ${rawName}`, {
142
+ subagent: name,
143
+ plugin: pluginQualifier,
144
+ next: known !== ''
145
+ ? `Known subagents: ${known}. Run \`crtr agent subagent list\` for the full set.`
146
+ : 'No subagents defined. Run `crtr agent subagent author -h` to scaffold one.',
147
+ });
148
+ }
149
+ // Multiple matches: prefer the highest-precedence scope/source deterministically.
150
+ const score = (a) => {
151
+ const scopeScore = a.scope === 'project' ? 0 : a.scope === 'user' ? 1 : 2;
152
+ const sourceScore = a.plugin === SCOPE_SKILL_PLUGIN ? 0 : 1;
153
+ return scopeScore * 2 + sourceScore;
154
+ };
155
+ const sorted = [...matches].sort((a, b) => score(a) - score(b));
156
+ if (score(sorted[0]) !== score(sorted[1]))
157
+ return sorted[0];
158
+ throw ambiguous(`ambiguous subagent: ${rawName}`, {
159
+ subagent: name,
160
+ candidates: matches.map((m) => ({ id: subagentId(m), scope: m.scope, path: m.path })),
161
+ next: 'Qualify with `<plugin>/<name>` or pass --scope to disambiguate.',
162
+ });
163
+ }
@@ -2,9 +2,9 @@
2
2
  * First user message for a spec → plan handoff.
3
3
  *
4
4
  * Thin prompt: the worker discovers the full planning workflow by running
5
- * `crtr flow plan new -h`, then saves the plan via `crtr flow plan new`. This avoids
6
- * embedding the planPrompt() blob here and keeps the prompt in sync with the
7
- * live CLI without any coupling.
5
+ * `crtr mode plan new -h`, then saves the plan via `crtr mode plan new`. This
6
+ * avoids embedding the planPrompt() blob here and keeps the prompt in sync
7
+ * with the live CLI without any coupling.
8
8
  */
9
9
  export declare function planHandoffPrompt(specPath: string, jobId: string): string;
10
10
  /**
@@ -16,3 +16,12 @@ export declare function implementHandoffPrompt(planPath: string, jobId: string):
16
16
  * The reviewer submits via `crtr job submit` rather than `crtr agent submit`.
17
17
  */
18
18
  export declare function reviewerHandoffPrompt(artifactPath: string, artifactKind: 'plan' | 'spec', specPath: string | null, jobId: string): string;
19
+ /**
20
+ * First user message for a general `agent new` worker.
21
+ *
22
+ * The worker runs as an interactive agent in a tmux pane (not print mode), so
23
+ * its stdout is NOT captured as the result — it must deliver its answer via
24
+ * `crtr job submit`, exactly like the mode-handoff workers. The original task
25
+ * is sent verbatim; the submit contract is appended after a separator.
26
+ */
27
+ export declare function agentNewPrompt(task: string, jobId: string): string;
@@ -3,30 +3,30 @@ import { planReviewPrompt, specReviewPrompt } from './review.js';
3
3
  * First user message for a spec → plan handoff.
4
4
  *
5
5
  * Thin prompt: the worker discovers the full planning workflow by running
6
- * `crtr flow plan new -h`, then saves the plan via `crtr flow plan new`. This avoids
7
- * embedding the planPrompt() blob here and keeps the prompt in sync with the
8
- * live CLI without any coupling.
6
+ * `crtr mode plan new -h`, then saves the plan via `crtr mode plan new`. This
7
+ * avoids embedding the planPrompt() blob here and keeps the prompt in sync
8
+ * with the live CLI without any coupling.
9
9
  */
10
10
  export function planHandoffPrompt(specPath, jobId) {
11
11
  return `You were launched in a new tmux pane to turn an approved spec into a plan.
12
12
 
13
13
  **Spec:** ${specPath}
14
14
 
15
- 1. Run \`crtr flow plan new -h\` to load the planning workflow and output schema.
15
+ 1. Run \`crtr mode plan new -h\` to load the planning workflow and output schema.
16
16
  2. Read the spec end-to-end.
17
- 3. Follow the workflow from step 1 and save the plan by passing the plan markdown to \`crtr flow plan new\` on stdin.
18
- 4. When done, submit your result:
17
+ 3. Follow the workflow from step 1 and save the plan by passing the plan markdown to \`crtr mode plan new\` on stdin.
18
+ 4. When done, submit a short markdown report on stdin:
19
19
 
20
20
  \`\`\`bash
21
- echo '{"status":"done","plan_saved":true}' > /tmp/crtr-result-${jobId}.json
22
- crtr job submit ${jobId} --context-file /tmp/crtr-result-${jobId}.json
21
+ crtr job submit ${jobId} <<'MD'
22
+ Plan saved at <path>. <one-line summary of the plan's shape>.
23
+ MD
23
24
  \`\`\`
24
25
 
25
- If you cannot complete the plan, still submit:
26
+ If you cannot complete the plan, submit a failure with a reason:
26
27
 
27
28
  \`\`\`bash
28
- echo '{"status":"failed","reason":"<why>"}' > /tmp/crtr-result-${jobId}.json
29
- crtr job submit ${jobId} --context-file /tmp/crtr-result-${jobId}.json
29
+ crtr job submit ${jobId} --status failed --reason "<why>"
30
30
  \`\`\`
31
31
 
32
32
  Begin now.`;
@@ -111,17 +111,19 @@ would be slower.
111
111
 
112
112
  ## Phase 6: Report and submit
113
113
 
114
- When all tasks complete and verification passes, submit your result:
114
+ When all tasks complete and verification passes, submit a markdown report on stdin:
115
115
 
116
116
  \`\`\`bash
117
- echo '{"status":"done","summary":"<one-line summary of files touched>"}' > /tmp/crtr-result-${jobId}.json
118
- crtr job submit ${jobId} --context-file /tmp/crtr-result-${jobId}.json
117
+ crtr job submit ${jobId} <<'MD'
118
+ **Summary:** <one-line summary of files touched>
119
+
120
+ <optional further notes — verification output, surprises, callouts>
121
+ MD
119
122
  \`\`\`
120
123
 
121
- If implementation fails, still submit:
124
+ If implementation fails, submit a failure with a reason:
122
125
  \`\`\`bash
123
- echo '{"status":"failed","reason":"<why>"}' > /tmp/crtr-result-${jobId}.json
124
- crtr job submit ${jobId} --context-file /tmp/crtr-result-${jobId}.json
126
+ crtr job submit ${jobId} --status failed --reason "<why>"
125
127
  \`\`\`
126
128
 
127
129
  ## Guardrails (apply to you AND your subagents)
@@ -144,8 +146,39 @@ export function reviewerHandoffPrompt(artifactPath, artifactKind, specPath, jobI
144
146
  const reviewBody = artifactKind === 'spec'
145
147
  ? specReviewPrompt(artifactPath)
146
148
  : planReviewPrompt(artifactPath, specPath);
147
- const patched = reviewBody.replace('__CRTR_SUBMIT_INSTRUCTION__', `the submit command injected below. The \`--kill-pane\` flag closes this reviewer pane after submission — keep it, do not drop it.\n\n\`\`\`bash\ncat > /tmp/crtr-result-${jobId}.json <<'JSON'\n{"status":"done","review":"<your full review markdown>"}\nJSON\ncrtr job submit ${jobId} --context-file /tmp/crtr-result-${jobId}.json --kill-pane\n\`\`\``);
149
+ const patched = reviewBody.replace('__CRTR_SUBMIT_INSTRUCTION__', `the submit command injected below. Pipe your full review markdown on stdin. The \`--kill-pane\` flag closes this reviewer pane after submission — keep it, do not drop it.\n\n\`\`\`bash\ncrtr job submit ${jobId} --kill-pane <<'MD'\n<your full review markdown — the same Status/Issues/Recommendations block you composed above>\nMD\n\`\`\`\n\nIf the artifact is too malformed to review, submit a failure instead:\n\n\`\`\`bash\ncrtr job submit ${jobId} --status failed --reason "<why>" --kill-pane\n\`\`\``);
148
150
  return `${patched}
149
151
 
150
152
  After calling \`crtr job submit\`, your turn ends and the pane closes itself. Do NOT chat or summarize after submission.`;
151
153
  }
154
+ /**
155
+ * First user message for a general `agent new` worker.
156
+ *
157
+ * The worker runs as an interactive agent in a tmux pane (not print mode), so
158
+ * its stdout is NOT captured as the result — it must deliver its answer via
159
+ * `crtr job submit`, exactly like the mode-handoff workers. The original task
160
+ * is sent verbatim; the submit contract is appended after a separator.
161
+ */
162
+ export function agentNewPrompt(task, jobId) {
163
+ return `${task}
164
+
165
+ ---
166
+
167
+ When you have finished the task above, deliver your final answer by piping your
168
+ full markdown result to \`crtr job submit\` — this is how the result is returned
169
+ to whoever spawned you:
170
+
171
+ \`\`\`bash
172
+ crtr job submit ${jobId} <<'MD'
173
+ <your complete answer / findings>
174
+ MD
175
+ \`\`\`
176
+
177
+ If you cannot complete the task, submit a failure with a reason instead:
178
+
179
+ \`\`\`bash
180
+ crtr job submit ${jobId} --status failed --reason "<why>"
181
+ \`\`\`
182
+
183
+ Do your work first; submit exactly once when done. After submitting, your turn ends.`;
184
+ }
@@ -19,18 +19,25 @@ Rules — follow exactly:
19
19
 
20
20
  Submit exactly one of the following via \`crtr job submit\`, then your turn ends — do not chat:
21
21
 
22
- Reproduced:
22
+ Reproduced (markdown body on stdin):
23
23
  \`\`\`bash
24
- cat > /tmp/crtr-result-${jobId}.json <<'JSON'
25
- {"status":"done","reproduces":true,"test_path":"<path>","test_command":"<exact cmd>","failure_output":"<pasted failing output>"}
26
- JSON
27
- crtr job submit ${jobId} --context-file /tmp/crtr-result-${jobId}.json
24
+ crtr job submit ${jobId} <<'MD'
25
+ **Reproduces:** yes
26
+
27
+ **Test path:** <path>
28
+
29
+ **Test command:** \`<exact cmd>\`
30
+
31
+ **Failure output:**
32
+ \`\`\`
33
+ <pasted failing output>
34
+ \`\`\`
35
+ MD
28
36
  \`\`\`
29
37
 
30
38
  Gave up (no faithful repro achievable):
31
39
  \`\`\`bash
32
- echo '{"status":"failed","reproduces":false,"reason":"<why a faithful repro was not achievable>"}' > /tmp/crtr-result-${jobId}.json
33
- crtr job submit ${jobId} --context-file /tmp/crtr-result-${jobId}.json
40
+ crtr job submit ${jobId} --status failed --reason "<why a faithful repro was not achievable>"
34
41
  \`\`\`
35
42
 
36
43
  Begin now.`;
@@ -111,7 +111,7 @@ are for large, complicated, or unintuitive systems only.
111
111
  ## 2. Scope + name
112
112
 
113
113
  - **Scope**: \`project\` by default. \`user\` only if cross-repo.
114
- - **Name**: kebab-case. Confirm no collision: \`crtr skill read where <name>\` (returns \`.path\`, \`.scope\`, \`.plugin\`).
114
+ - **Name**: kebab-case. Confirm no collision: \`crtr skill read <name> --no-body\` (returns \`.path\`, \`.scope\`, \`.plugin\`).
115
115
 
116
116
  ## 3. Parallel exploration
117
117
 
@@ -178,7 +178,7 @@ Non-obvious coupling. Looks-broken-but-isn't. Past footguns.
178
178
  \`\`\`
179
179
 
180
180
  **No \`## Related\` for within-plugin siblings** — the CLI auto-appends a
181
- \`## Neighbors\` section on \`crtr skill read show <name>\`. Add a manual \`## Related\`
181
+ \`## Neighbors\` section on \`crtr skill read <name>\`. Add a manual \`## Related\`
182
182
  only for cross-plugin or distant refs.
183
183
 
184
184
  **Density rules:**
@@ -195,8 +195,8 @@ only for cross-plugin or distant refs.
195
195
  Output is JSON; \`.content\` has the body, \`.path\` has the location:
196
196
 
197
197
  \`\`\`
198
- crtr skill read where <name>
199
- crtr skill read show <name>
198
+ crtr skill read <name> --no-body
199
+ crtr skill read <name>
200
200
  crtr skill find search "<keyword>" # confirm description triggers discovery
201
201
  \`\`\`
202
202
 
@@ -246,7 +246,7 @@ PR over many small ones for refactors here, because review churn dominates"*
246
246
 
247
247
  - **Scope**: \`user\` for cross-project methodology. \`project\` for repo-specific.
248
248
  - **Name**: kebab-case, verb-or-noun-phrase. Not "guide-to-X".
249
- - Check \`crtr skill read where <name>\` (returns \`.path\`, \`.scope\`, \`.plugin\`).
249
+ - Check \`crtr skill read <name> --no-body\` (returns \`.path\`, \`.scope\`, \`.plugin\`).
250
250
 
251
251
  ## 3. Scaffold
252
252
 
@@ -297,7 +297,7 @@ to read the whole thing for value, you've buried the judgment.
297
297
  \`\`\`
298
298
 
299
299
  **No \`## Related\` for within-plugin siblings** — the CLI auto-appends a
300
- \`## Neighbors\` section on \`crtr skill read show <name>\`. Add a manual \`## Related\`
300
+ \`## Neighbors\` section on \`crtr skill read <name>\`. Add a manual \`## Related\`
301
301
  only for cross-plugin or distant refs.
302
302
 
303
303
  ## 6. Progressive disclosure
@@ -319,8 +319,8 @@ loads supporting files only when needed.
319
319
  Output is JSON; \`.content\` has the body, \`.path\` has the location:
320
320
 
321
321
  \`\`\`
322
- crtr skill read where <name>
323
- crtr skill read show <name>
322
+ crtr skill read <name> --no-body
323
+ crtr skill read <name>
324
324
  crtr skill find search "<keyword>"
325
325
  \`\`\`
326
326
 
@@ -410,8 +410,8 @@ Or invent your own. Stay tight — no padding.
410
410
  Output is JSON; \`.content\` has the body, \`.path\` has the location:
411
411
 
412
412
  \`\`\`
413
- crtr skill read where <name>
414
- crtr skill read show <name>
413
+ crtr skill read <name> --no-body
414
+ crtr skill read <name>
415
415
  crtr skill find search "<keyword>"
416
416
  \`\`\`
417
417
 
@@ -464,7 +464,7 @@ field/flag/code values** — pull verbatim from source.
464
464
 
465
465
  - **Scope**: \`user\` for cross-project facts. \`project\` for repo-specific.
466
466
  - **Name**: noun-phrase. \`http-status-codes\` not \`learn-http-status\`.
467
- - Check \`crtr skill read where <name>\` (returns \`.path\`, \`.scope\`, \`.plugin\`).
467
+ - Check \`crtr skill read <name> --no-body\` (returns \`.path\`, \`.scope\`, \`.plugin\`).
468
468
 
469
469
  ## 3. Scaffold
470
470
 
@@ -525,8 +525,8 @@ SKILL.md links to siblings (\`see [full-table.md](full-table.md)\`).
525
525
  Output is JSON; \`.content\` has the body, \`.path\` has the location:
526
526
 
527
527
  \`\`\`
528
- crtr skill read where <name>
529
- crtr skill read show <name>
528
+ crtr skill read <name> --no-body
529
+ crtr skill read <name>
530
530
  crtr skill find search "<keyword>"
531
531
  \`\`\`
532
532
 
@@ -577,7 +577,7 @@ push to \\\`main\\\`, wait for green CI, click promote"* is a runbook step.
577
577
 
578
578
  - **Scope**: \`project\` for repo-specific procedures. \`user\` for cross-project.
579
579
  - **Name**: verb-phrase. \`deploy-to-prod\` not \`production-deployment-guide\`.
580
- - Check \`crtr skill read where <name>\` (returns \`.path\`, \`.scope\`, \`.plugin\`).
580
+ - Check \`crtr skill read <name> --no-body\` (returns \`.path\`, \`.scope\`, \`.plugin\`).
581
581
 
582
582
  ## 3. Scaffold
583
583
 
@@ -632,8 +632,8 @@ crtr skill author scaffold <name> --type runbook --scope <user|project> --descri
632
632
  Output is JSON; \`.content\` has the body, \`.path\` has the location:
633
633
 
634
634
  \`\`\`
635
- crtr skill read where <name>
636
- crtr skill read show <name>
635
+ crtr skill read <name> --no-body
636
+ crtr skill read <name>
637
637
  crtr skill find search "<keyword>"
638
638
  \`\`\`
639
639
 
package/dist/types.d.ts CHANGED
@@ -8,7 +8,7 @@ export declare const ExitCode: {
8
8
  readonly NETWORK: 5;
9
9
  };
10
10
  export type ExitCodeValue = (typeof ExitCode)[keyof typeof ExitCode];
11
- export declare const SCHEMA_VERSION = 1;
11
+ export declare const SCHEMA_VERSION = 2;
12
12
  export interface OwnerRef {
13
13
  name?: string;
14
14
  email?: string;
@@ -90,6 +90,26 @@ export interface Skill {
90
90
  enabled: boolean;
91
91
  disabledIn?: Scope;
92
92
  }
93
+ export interface SubagentFrontmatter {
94
+ name: string;
95
+ description?: string;
96
+ /** Tool allow-list (pi tool names). Passed through to pi via `--tools`. */
97
+ tools?: string[];
98
+ /** Model pattern/id passed to the agent CLI via `--model`. */
99
+ model?: string;
100
+ }
101
+ export interface Subagent {
102
+ name: string;
103
+ /** Plugin the subagent belongs to, or SCOPE_SKILL_PLUGIN ('_') for a
104
+ * scope-root agent stored at `<scope-root>/agents/<name>.md`. */
105
+ plugin: string;
106
+ scope: Scope;
107
+ /** Absolute path to the agent's .md file. */
108
+ path: string;
109
+ frontmatter: SubagentFrontmatter;
110
+ /** Markdown body — used as the spawned agent's appended system prompt. */
111
+ systemPrompt: string;
112
+ }
93
113
  export interface InstalledPlugin {
94
114
  name: string;
95
115
  scope: Scope;
@@ -117,6 +137,7 @@ export declare const CONFIG_FILE = "config.json";
117
137
  export declare const STATE_FILE = "state.json";
118
138
  export declare const SKILL_ENTRY_FILE = "SKILL.md";
119
139
  export declare const SKILLS_DIR = "skills";
140
+ export declare const AGENTS_DIR = "agents";
120
141
  export declare const SCOPE_SKILL_PLUGIN = "_";
121
142
  export declare const DEFAULT_MAX_PANES_PER_WINDOW = 3;
122
143
  export declare function defaultScopeConfig(): ScopeConfig;
package/dist/types.js CHANGED
@@ -6,7 +6,7 @@ export const ExitCode = {
6
6
  AMBIGUOUS: 4,
7
7
  NETWORK: 5,
8
8
  };
9
- export const SCHEMA_VERSION = 1;
9
+ export const SCHEMA_VERSION = 2;
10
10
  export const SKILL_TYPES = ['playbook', 'primer', 'reference', 'runbook', 'freeform'];
11
11
  export function isSkillType(v) {
12
12
  return typeof v === 'string' && SKILL_TYPES.includes(v);
@@ -20,6 +20,9 @@ export const CONFIG_FILE = 'config.json';
20
20
  export const STATE_FILE = 'state.json';
21
21
  export const SKILL_ENTRY_FILE = 'SKILL.md';
22
22
  export const SKILLS_DIR = 'skills';
23
+ // Subagent definitions live as flat `<name>.md` files under `<root>/agents/`,
24
+ // for both scope roots and plugins. Mirrors SKILLS_DIR.
25
+ export const AGENTS_DIR = 'agents';
23
26
  // Sentinel plugin name for skills that live at a scope root (no plugin wrapper).
24
27
  // Stored as `<scope-root>/skills/<name>/SKILL.md`. Shown in listings without the
25
28
  // `_/` prefix.
@@ -36,7 +39,7 @@ export function defaultScopeConfig() {
36
39
  };
37
40
  }
38
41
  export function skillConfigKey(plugin, name) {
39
- return `${plugin}:${name}`;
42
+ return `${plugin}/${name}`;
40
43
  }
41
44
  export function defaultScopeState() {
42
45
  return { marketplaces: {}, plugins: {} };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crouton-kit/crouter",
3
- "version": "0.3.3",
3
+ "version": "0.3.11",
4
4
  "description": "crtr — fast access to skills, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "license": "MIT",
37
37
  "dependencies": {
38
- "@crouton-kit/humanloop": "^0.3.8",
38
+ "@crouton-kit/humanloop": "^0.3.12",
39
39
  "commander": "^13.0.0"
40
40
  },
41
41
  "devDependencies": {
@@ -1,24 +0,0 @@
1
- // `crtr flow` umbrella — groups the spec → plan → debug development process.
2
- // registerSpec/registerPlan are unchanged (each still defineBranch{name}); they
3
- // nest under `flow` here instead of registering at root. registerDebug is a
4
- // leaf — `crtr flow debug` spawns directly and `-h` prints FLOW_DEBUG_GUIDE.
5
- import { defineBranch } from '../core/command.js';
6
- import { registerSpec } from './spec.js';
7
- import { registerPlan } from './plan.js';
8
- import { registerDebug } from './debug.js';
9
- export function registerFlow() {
10
- return defineBranch({
11
- name: 'flow',
12
- help: {
13
- name: 'flow',
14
- summary: 'the spec → plan → debug development process',
15
- model: 'spec captures requirements; plan decomposes them; debug root-causes failures reproduce-first.',
16
- children: [
17
- { name: 'spec', desc: 'create, read, list specifications', useWhen: 'capturing requirements before planning' },
18
- { name: 'plan', desc: 'create, read, list plans', useWhen: 'shaping or inspecting work' },
19
- { name: 'debug', desc: 'reproduce-first root-cause workflow', useWhen: 'a bug, test failure, or unexpected behavior needs root-causing' },
20
- ],
21
- },
22
- children: [registerSpec(), registerPlan(), registerDebug()],
23
- });
24
- }