@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,7 +1,7 @@
1
1
  // Tmux pane spawning machinery for crtr job subtree.
2
2
  //
3
3
  // Kept: spawnAgent (fire-and-forget new pane), spawnAndDetach (detach + kill originating pane),
4
- // shellQuote, isInTmux, countPanesInCurrentWindow.
4
+ // shellQuote, isInTmux, countPanesInCurrentWindow, findWindowWithSpace.
5
5
  //
6
6
  // Removed: createSession, submitToSession, awaitSession, waitForResult,
7
7
  // sessionDirForId, writeSessionMeta, readSessionMeta — all superseded
@@ -28,6 +28,118 @@ export function countPanesInCurrentWindow() {
28
28
  return 0;
29
29
  return result.stdout.split('\n').filter((line) => line.trim() !== '').length;
30
30
  }
31
+ function listWindowsInCurrentSession() {
32
+ const result = spawnSync('tmux', ['list-windows', '-F', '#{window_id} #{window_panes} #{window_active}'], { encoding: 'utf8' });
33
+ if (result.status !== 0)
34
+ return [];
35
+ return result.stdout
36
+ .split('\n')
37
+ .filter((line) => line.trim() !== '')
38
+ .map((line) => {
39
+ const [id, count, active] = line.split(' ');
40
+ return {
41
+ windowId: id,
42
+ paneCount: Number.parseInt(count, 10),
43
+ isActive: active === '1',
44
+ };
45
+ });
46
+ }
47
+ /**
48
+ * Map of window_id → list of pane TTYs (basename, e.g. `ttys008`) for every
49
+ * pane in the current tmux session. Used as the bridge between tmux's pane
50
+ * model and the system process table for foreground-command lookup.
51
+ *
52
+ * tmux's `#{pane_current_command}` is unreliable on macOS because the Claude
53
+ * Code CLI sets `process.title` to its version (e.g. `2.1.143`), which is what
54
+ * tmux then reports. Going through the TTY + `ps` gives us the real binary
55
+ * name (`claude`) from the kernel.
56
+ */
57
+ function paneTtysByWindow() {
58
+ const result = spawnSync('tmux', ['list-panes', '-s', '-F', '#{window_id} #{pane_tty}'], { encoding: 'utf8' });
59
+ const out = new Map();
60
+ if (result.status !== 0)
61
+ return out;
62
+ for (const line of result.stdout.split('\n')) {
63
+ if (line.trim() === '')
64
+ continue;
65
+ const idx = line.indexOf(' ');
66
+ if (idx === -1)
67
+ continue;
68
+ const windowId = line.slice(0, idx);
69
+ const tty = line.slice(idx + 1);
70
+ const ttyBase = tty.startsWith('/dev/') ? tty.slice(5) : tty;
71
+ const existing = out.get(windowId);
72
+ if (existing === undefined) {
73
+ out.set(windowId, [ttyBase]);
74
+ }
75
+ else {
76
+ existing.push(ttyBase);
77
+ }
78
+ }
79
+ return out;
80
+ }
81
+ /**
82
+ * Map of tty basename → set of foreground process `comm` names on that tty.
83
+ * A process is "foreground" if its STAT field includes `+` (member of the
84
+ * terminal's foreground process group). Built from one `ps -axo ...` call.
85
+ */
86
+ function foregroundCommsByTty() {
87
+ const result = spawnSync('ps', ['-axo', 'stat=,comm=,tty='], { encoding: 'utf8' });
88
+ const out = new Map();
89
+ if (result.status !== 0)
90
+ return out;
91
+ for (const line of result.stdout.split('\n')) {
92
+ if (line.trim() === '')
93
+ continue;
94
+ const m = line.match(/^(\S+)\s+(.+?)\s+(\S+)\s*$/);
95
+ if (m === null)
96
+ continue;
97
+ const [, stat, comm, tty] = m;
98
+ if (!stat.includes('+'))
99
+ continue;
100
+ if (tty === '??' || tty === '?')
101
+ continue;
102
+ const existing = out.get(tty);
103
+ if (existing === undefined) {
104
+ out.set(tty, new Set([comm.trim()]));
105
+ }
106
+ else {
107
+ existing.add(comm.trim());
108
+ }
109
+ }
110
+ return out;
111
+ }
112
+ /**
113
+ * Find a window in the current tmux session with fewer than `maxPanesPerWindow`
114
+ * panes AND where every existing pane has `claude` as a foreground process.
115
+ * Prefers the active window so the spawned pane is visible to the user;
116
+ * otherwise falls back to the first other eligible window. Returns the tmux
117
+ * window id (e.g. `@5`) to pass via `-t`, or null if no window qualifies.
118
+ *
119
+ * Windows holding non-agent panes (dashboards, log tails, idle shells, editors,
120
+ * REPLs, etc.) are skipped so spawning never disrupts those workflows. A pane
121
+ * qualifies as long as `claude` is among its foreground commands — co-resident
122
+ * helpers like `caffeinate` don't disqualify it.
123
+ */
124
+ export function findWindowWithSpace(maxPanesPerWindow) {
125
+ const windows = listWindowsInCurrentSession();
126
+ const ttysByWindow = paneTtysByWindow();
127
+ const fgByTty = foregroundCommsByTty();
128
+ const isClaudeOnly = (windowId) => {
129
+ const ttys = ttysByWindow.get(windowId);
130
+ if (ttys === undefined || ttys.length === 0)
131
+ return false;
132
+ return ttys.every((tty) => fgByTty.get(tty)?.has('claude') === true);
133
+ };
134
+ const eligible = windows.filter((w) => w.paneCount < maxPanesPerWindow && isClaudeOnly(w.windowId));
135
+ const active = eligible.find((w) => w.isActive);
136
+ if (active !== undefined)
137
+ return active.windowId;
138
+ const first = eligible[0];
139
+ if (first === undefined)
140
+ return null;
141
+ return first.windowId;
142
+ }
31
143
  /**
32
144
  * Schedule a kill-pane on the *current* tmux pane after `delaySeconds`, detached
33
145
  * so the caller can return normally before the pane dies. No-op outside tmux
@@ -75,9 +187,15 @@ export function spawnAndDetach(opts) {
75
187
  message: 'handoff requires tmux (TMUX env var not set)',
76
188
  };
77
189
  }
78
- const inner = opts.command !== undefined
79
- ? opts.command
80
- : ['claude', '--dangerously-skip-permissions', shellQuote(opts.prompt)].join(' ');
190
+ const buildClaudeInner = () => {
191
+ const parts = ['claude'];
192
+ if (opts.name !== undefined && opts.name !== '') {
193
+ parts.push('-n', shellQuote(opts.name));
194
+ }
195
+ parts.push('--dangerously-skip-permissions', shellQuote(opts.prompt));
196
+ return parts.join(' ');
197
+ };
198
+ const inner = opts.command !== undefined ? opts.command : buildClaudeInner();
81
199
  const useFailGuard = opts.failGuard !== false;
82
200
  const fullCmd = useFailGuard ? wrapperCmd(inner, opts.jobId) : inner;
83
201
  const splitArgs = [];
@@ -117,9 +235,14 @@ export function spawnAndDetach(opts) {
117
235
  };
118
236
  }
119
237
  /**
120
- * Async sibling spawn. Launches a claude session in a new tmux pane or window
121
- * (depending on current pane count vs maxPanesPerWindow). Returns immediately
122
- * with the pane id; the parent stays alive.
238
+ * Async sibling spawn. Launches a claude session in a tmux pane, progressively
239
+ * filling existing windows up to `maxPanesPerWindow` before creating a new
240
+ * window. Returns immediately with the pane id; the parent stays alive.
241
+ *
242
+ * Placement order:
243
+ * 1. Current window, if it has space.
244
+ * 2. Any other window in the session with space.
245
+ * 3. New window (every existing window at capacity).
123
246
  *
124
247
  * If `fork` is set, uses `claude --resume <id> --fork-session`.
125
248
  */
@@ -131,17 +254,21 @@ export function spawnAgent(opts) {
131
254
  };
132
255
  }
133
256
  const claudeParts = ['claude'];
257
+ if (opts.name !== undefined && opts.name !== '') {
258
+ claudeParts.push('-n', shellQuote(opts.name));
259
+ }
134
260
  if (opts.fork !== undefined) {
135
261
  claudeParts.push('--resume', opts.fork.sessionId, '--fork-session');
136
262
  }
137
263
  claudeParts.push('--dangerously-skip-permissions', shellQuote(opts.prompt));
138
264
  const claudeCmd = claudeParts.join(' ');
139
265
  const fullCmd = wrapperCmd(claudeCmd, opts.jobId);
140
- const useNewWindow = countPanesInCurrentWindow() >= opts.maxPanesPerWindow;
141
- const placement = useNewWindow ? 'new-window' : 'split-window';
266
+ const targetWindow = findWindowWithSpace(opts.maxPanesPerWindow);
267
+ const placement = targetWindow === null ? 'new-window' : 'split-window';
142
268
  const tmuxArgs = [placement];
143
- if (!useNewWindow)
144
- tmuxArgs.push('-h');
269
+ if (placement === 'split-window') {
270
+ tmuxArgs.push('-h', '-t', targetWindow);
271
+ }
145
272
  tmuxArgs.push('-P', '-F', '#{pane_id}', '-c', opts.cwd, '-e', `CRTR_JOB_ID=${opts.jobId}`, fullCmd);
146
273
  const split = spawnSync('tmux', tmuxArgs, { encoding: 'utf8' });
147
274
  if (split.status !== 0) {
@@ -150,6 +277,12 @@ export function spawnAgent(opts) {
150
277
  return { status: 'spawn-failed', message: msg };
151
278
  }
152
279
  const paneId = split.stdout.trim();
280
+ // Re-balance the target window's panes evenly so the new pane doesn't end up
281
+ // half the size of its siblings. -t <pane_id> resolves to the window it lives
282
+ // in for both placements (split + new-window).
283
+ spawnSync('tmux', ['select-layout', '-t', paneId, 'even-horizontal'], {
284
+ encoding: 'utf8',
285
+ });
153
286
  return {
154
287
  status: 'spawned',
155
288
  paneId,
@@ -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 agent plan new -h`, then saves the plan via `crtr agent 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
  /**
@@ -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 agent plan new -h`, then saves the plan via `crtr agent 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 agent 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 agent 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,7 +146,7 @@ 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.`;
@@ -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;
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);
@@ -36,7 +36,7 @@ export function defaultScopeConfig() {
36
36
  };
37
37
  }
38
38
  export function skillConfigKey(plugin, name) {
39
- return `${plugin}:${name}`;
39
+ return `${plugin}/${name}`;
40
40
  }
41
41
  export function defaultScopeState() {
42
42
  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.8",
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.11",
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
- }