@crouton-kit/crouter 0.3.8 → 0.3.12

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 (184) hide show
  1. package/bin/crtrd +2 -0
  2. package/dist/builtin-personas/design/base.md +9 -0
  3. package/dist/builtin-personas/design/orchestrator.md +10 -0
  4. package/dist/builtin-personas/developer/base.md +9 -0
  5. package/dist/builtin-personas/developer/orchestrator.md +12 -0
  6. package/dist/builtin-personas/explore/base.md +9 -0
  7. package/dist/builtin-personas/explore/orchestrator.md +9 -0
  8. package/dist/builtin-personas/general/base.md +5 -0
  9. package/dist/builtin-personas/general/orchestrator.md +7 -0
  10. package/dist/builtin-personas/orchestration-kernel.md +71 -0
  11. package/dist/builtin-personas/plan/base.md +7 -0
  12. package/dist/builtin-personas/plan/orchestrator.md +12 -0
  13. package/dist/builtin-personas/review/base.md +7 -0
  14. package/dist/builtin-personas/review/orchestrator.md +9 -0
  15. package/dist/builtin-personas/runtime-base.md +39 -0
  16. package/dist/builtin-personas/spec/base.md +7 -0
  17. package/dist/builtin-personas/spec/orchestrator.md +10 -0
  18. package/dist/builtin-skills/skills/design/SKILL.md +51 -0
  19. package/dist/builtin-skills/skills/development/SKILL.md +109 -0
  20. package/dist/builtin-skills/skills/planning/SKILL.md +59 -0
  21. package/dist/builtin-skills/skills/spec/SKILL.md +83 -0
  22. package/dist/cli.js +25 -27
  23. package/dist/commands/{job.d.ts → attention.d.ts} +1 -1
  24. package/dist/commands/attention.js +152 -0
  25. package/dist/commands/canvas.d.ts +2 -0
  26. package/dist/commands/canvas.js +35 -0
  27. package/dist/commands/{agent.d.ts → daemon.d.ts} +1 -1
  28. package/dist/commands/daemon.js +111 -0
  29. package/dist/commands/dashboard.d.ts +2 -0
  30. package/dist/commands/dashboard.js +65 -0
  31. package/dist/commands/human/prompts.d.ts +5 -0
  32. package/dist/commands/human/prompts.js +269 -0
  33. package/dist/commands/human/queue.d.ts +3 -0
  34. package/dist/commands/human/queue.js +133 -0
  35. package/dist/commands/human/shared.d.ts +43 -0
  36. package/dist/commands/human/shared.js +107 -0
  37. package/dist/commands/human.js +15 -427
  38. package/dist/commands/node.d.ts +2 -0
  39. package/dist/commands/node.js +354 -0
  40. package/dist/commands/pkg/market-inspect.d.ts +1 -0
  41. package/dist/commands/pkg/market-inspect.js +157 -0
  42. package/dist/commands/pkg/market-manage.d.ts +1 -0
  43. package/dist/commands/pkg/market-manage.js +316 -0
  44. package/dist/commands/pkg/market.d.ts +1 -0
  45. package/dist/commands/pkg/market.js +16 -0
  46. package/dist/commands/pkg/plugin-inspect.d.ts +1 -0
  47. package/dist/commands/pkg/plugin-inspect.js +142 -0
  48. package/dist/commands/pkg/plugin-manage.d.ts +1 -0
  49. package/dist/commands/pkg/plugin-manage.js +294 -0
  50. package/dist/commands/pkg/plugin.d.ts +1 -0
  51. package/dist/commands/pkg/plugin.js +16 -0
  52. package/dist/commands/pkg/shared.d.ts +5 -0
  53. package/dist/commands/pkg/shared.js +61 -0
  54. package/dist/commands/pkg.js +8 -1004
  55. package/dist/commands/push.d.ts +3 -0
  56. package/dist/commands/push.js +159 -0
  57. package/dist/commands/revive.d.ts +2 -0
  58. package/dist/commands/revive.js +64 -0
  59. package/dist/commands/skill/author.d.ts +3 -0
  60. package/dist/commands/skill/author.js +147 -0
  61. package/dist/commands/skill/find.d.ts +4 -0
  62. package/dist/commands/skill/find.js +254 -0
  63. package/dist/commands/skill/read.d.ts +1 -0
  64. package/dist/commands/skill/read.js +89 -0
  65. package/dist/commands/skill/shared.d.ts +19 -0
  66. package/dist/commands/skill/shared.js +207 -0
  67. package/dist/commands/skill/state.d.ts +3 -0
  68. package/dist/commands/skill/state.js +69 -0
  69. package/dist/commands/skill.js +12 -681
  70. package/dist/commands/sys/config.d.ts +1 -0
  71. package/dist/commands/sys/config.js +186 -0
  72. package/dist/commands/sys/doctor.d.ts +1 -0
  73. package/dist/commands/sys/doctor.js +369 -0
  74. package/dist/commands/sys/shared.d.ts +3 -0
  75. package/dist/commands/sys/shared.js +24 -0
  76. package/dist/commands/sys/update.d.ts +2 -0
  77. package/dist/commands/sys/update.js +114 -0
  78. package/dist/commands/sys.js +9 -694
  79. package/dist/core/__tests__/argv-parser.test.js +19 -1
  80. package/dist/core/__tests__/canvas-inbox-watcher.test.js +100 -0
  81. package/dist/core/__tests__/canvas.test.js +154 -0
  82. package/dist/core/__tests__/reset.test.js +105 -0
  83. package/dist/core/__tests__/resolver.test.js +69 -1
  84. package/dist/core/__tests__/unknown-path.test.d.ts +1 -0
  85. package/dist/core/__tests__/unknown-path.test.js +52 -0
  86. package/dist/core/bootstrap.d.ts +2 -0
  87. package/dist/core/bootstrap.js +66 -0
  88. package/dist/core/canvas/attention.d.ts +24 -0
  89. package/dist/core/canvas/attention.js +94 -0
  90. package/dist/core/canvas/canvas.d.ts +40 -0
  91. package/dist/core/canvas/canvas.js +210 -0
  92. package/dist/core/canvas/db.d.ts +7 -0
  93. package/dist/core/canvas/db.js +61 -0
  94. package/dist/core/canvas/index.d.ts +4 -0
  95. package/dist/core/canvas/index.js +6 -0
  96. package/dist/core/canvas/paths.d.ts +16 -0
  97. package/dist/core/canvas/paths.js +62 -0
  98. package/dist/core/canvas/render.d.ts +30 -0
  99. package/dist/core/canvas/render.js +186 -0
  100. package/dist/core/canvas/types.d.ts +87 -0
  101. package/dist/core/canvas/types.js +8 -0
  102. package/dist/core/command.d.ts +63 -2
  103. package/dist/core/command.js +97 -24
  104. package/dist/core/feed/feed.d.ts +43 -0
  105. package/dist/core/feed/feed.js +116 -0
  106. package/dist/core/feed/inbox.d.ts +50 -0
  107. package/dist/core/feed/inbox.js +124 -0
  108. package/dist/core/frontmatter.d.ts +10 -0
  109. package/dist/core/frontmatter.js +24 -9
  110. package/dist/core/help.d.ts +39 -8
  111. package/dist/core/help.js +69 -35
  112. package/dist/core/io.d.ts +15 -1
  113. package/dist/core/io.js +56 -6
  114. package/dist/core/personas/index.d.ts +12 -0
  115. package/dist/core/personas/index.js +10 -0
  116. package/dist/core/personas/loader.d.ts +44 -0
  117. package/dist/core/personas/loader.js +157 -0
  118. package/dist/core/personas/resolve.d.ts +36 -0
  119. package/dist/core/personas/resolve.js +110 -0
  120. package/dist/core/render.d.ts +11 -0
  121. package/dist/core/render.js +126 -0
  122. package/dist/core/resolver.d.ts +10 -0
  123. package/dist/core/resolver.js +160 -2
  124. package/dist/core/runtime/front-door.d.ts +10 -0
  125. package/dist/core/runtime/front-door.js +97 -0
  126. package/dist/core/runtime/kickoff.d.ts +23 -0
  127. package/dist/core/runtime/kickoff.js +134 -0
  128. package/dist/core/runtime/launch.d.ts +34 -0
  129. package/dist/core/runtime/launch.js +85 -0
  130. package/dist/core/runtime/nodes.d.ts +38 -0
  131. package/dist/core/runtime/nodes.js +95 -0
  132. package/dist/core/runtime/presence.d.ts +38 -0
  133. package/dist/core/runtime/presence.js +152 -0
  134. package/dist/core/runtime/promote.d.ts +30 -0
  135. package/dist/core/runtime/promote.js +105 -0
  136. package/dist/core/runtime/reset.d.ts +13 -0
  137. package/dist/core/runtime/reset.js +97 -0
  138. package/dist/core/runtime/revive.d.ts +26 -0
  139. package/dist/core/runtime/revive.js +89 -0
  140. package/dist/core/runtime/roadmap.d.ts +12 -0
  141. package/dist/core/runtime/roadmap.js +52 -0
  142. package/dist/core/runtime/spawn.d.ts +33 -0
  143. package/dist/core/runtime/spawn.js +118 -0
  144. package/dist/core/runtime/stop-guard.d.ts +18 -0
  145. package/dist/core/runtime/stop-guard.js +33 -0
  146. package/dist/core/runtime/tmux.d.ts +88 -0
  147. package/dist/core/runtime/tmux.js +198 -0
  148. package/dist/core/spawn.d.ts +17 -80
  149. package/dist/core/spawn.js +15 -219
  150. package/dist/daemon/crtrd-cli.d.ts +1 -0
  151. package/dist/daemon/crtrd-cli.js +4 -0
  152. package/dist/daemon/crtrd.d.ts +20 -0
  153. package/dist/daemon/crtrd.js +200 -0
  154. package/dist/daemon/manage.d.ts +17 -0
  155. package/dist/daemon/manage.js +57 -0
  156. package/dist/pi-extensions/canvas-inbox-watcher.d.ts +16 -0
  157. package/dist/pi-extensions/canvas-inbox-watcher.js +229 -0
  158. package/dist/pi-extensions/canvas-nav.d.ts +32 -0
  159. package/dist/pi-extensions/canvas-nav.js +536 -0
  160. package/dist/pi-extensions/canvas-stophook.d.ts +17 -0
  161. package/dist/pi-extensions/canvas-stophook.js +373 -0
  162. package/dist/types.d.ts +21 -0
  163. package/dist/types.js +3 -0
  164. package/package.json +6 -5
  165. package/dist/commands/agent.js +0 -384
  166. package/dist/commands/debug.d.ts +0 -3
  167. package/dist/commands/debug.js +0 -179
  168. package/dist/commands/job.js +0 -344
  169. package/dist/commands/plan.d.ts +0 -4
  170. package/dist/commands/plan.js +0 -309
  171. package/dist/commands/spec.d.ts +0 -3
  172. package/dist/commands/spec.js +0 -286
  173. package/dist/core/__tests__/flow-leaves.test.js +0 -248
  174. package/dist/core/__tests__/job.test.js +0 -310
  175. package/dist/core/__tests__/jobs.test.js +0 -66
  176. package/dist/core/jobs.d.ts +0 -101
  177. package/dist/core/jobs.js +0 -462
  178. package/dist/prompts/agent.d.ts +0 -18
  179. package/dist/prompts/agent.js +0 -153
  180. package/dist/prompts/debug.d.ts +0 -8
  181. package/dist/prompts/debug.js +0 -44
  182. /package/dist/core/__tests__/{flow-leaves.test.d.ts → canvas-inbox-watcher.test.d.ts} +0 -0
  183. /package/dist/core/__tests__/{job.test.d.ts → canvas.test.d.ts} +0 -0
  184. /package/dist/core/__tests__/{jobs.test.d.ts → reset.test.d.ts} +0 -0
@@ -1,384 +0,0 @@
1
- // `crtr agent` umbrella — agentic workflows: spec/plan/debug + spawn primitives.
2
- //
3
- // `agent new {prompt,fork,planner,implementer,reviewer}` are the spawn leaves
4
- // (formerly `job start *`). Spawning creates a job record; monitoring lives at
5
- // `crtr job`. This split keeps the job registry agnostic of producer — agents
6
- // are one producer, future producers compose under their own subtree.
7
- //
8
- // Terminal-write contract for spawned workers:
9
- // Worker calls `crtr job submit` → jobs.writeResult(jobId, result, 'done').
10
- // If claude exits without submitting, the wrapper shell calls `crtr job _fail`
11
- // → jobs.writeResult(jobId, {}, 'failed') IF result.json does not yet exist.
12
- // `job read result` watches result.json appearance as the sole completion signal.
13
- import { defineBranch, defineLeaf } from '../core/command.js';
14
- import { InputError } from '../core/io.js';
15
- import { createJob, appendEvent } from '../core/jobs.js';
16
- import { spawnAgent, spawnAndDetach, isInTmux } from '../core/spawn.js';
17
- import { readConfig } from '../core/config.js';
18
- import { planHandoffPrompt, implementHandoffPrompt, reviewerHandoffPrompt } from '../prompts/agent.js';
19
- import { existsSync } from 'node:fs';
20
- import { registerSpec } from './spec.js';
21
- import { registerPlan } from './plan.js';
22
- import { registerDebug } from './debug.js';
23
- const DEFAULT_KILL_SECS = 2;
24
- function followUpResult(jobId) {
25
- return `crtr job read result ${jobId} --wait`;
26
- }
27
- function resolveMaxPanes() {
28
- const cfg = readConfig('user');
29
- return cfg.max_panes_per_window;
30
- }
31
- function assertTmux() {
32
- if (!isInTmux()) {
33
- throw new InputError({
34
- error: 'not_in_tmux',
35
- message: 'crtr agent new requires tmux (TMUX env var not set).',
36
- next: 'Run inside a tmux session.',
37
- });
38
- }
39
- }
40
- // ---------------------------------------------------------------------------
41
- // agent new prompt
42
- // ---------------------------------------------------------------------------
43
- const newPrompt = defineLeaf({
44
- name: 'prompt',
45
- help: {
46
- name: 'agent new prompt',
47
- summary: 'spawn a fresh Claude agent with a prompt; returns a job handle immediately',
48
- params: [
49
- { kind: 'stdin', name: 'prompt', required: true, constraint: 'Prompt text sent to the spawned agent.' },
50
- { kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: 'Working directory for the spawned agent. Defaults to process.cwd().' },
51
- { kind: 'flag', name: 'name', type: 'string', required: true, constraint: 'Display name passed to `claude -n`; surfaces in pane title and /resume picker.' },
52
- ],
53
- output: [
54
- { name: 'job_id', type: 'string', required: true, constraint: 'Use with `crtr job read status|logs|result` and `crtr job cancel`.' },
55
- { name: 'follow_up', type: 'string', required: true, constraint: 'Recommended next call.' },
56
- ],
57
- outputKind: 'object',
58
- effects: [
59
- 'Spawns a Claude agent in a sibling tmux pane.',
60
- 'Creates a job entry at $XDG_STATE_HOME/crtr/jobs/<job_id>/.',
61
- 'On completion, result writes atomically to result.json.',
62
- ],
63
- },
64
- run: async (input) => {
65
- assertTmux();
66
- const prompt = input['prompt'];
67
- const cwd = typeof input['cwd'] === 'string' ? input['cwd'] : process.cwd();
68
- const name = input['name'];
69
- const { jobId } = createJob('prompt', { cwd });
70
- const promptWithSubmit = `${prompt}
71
-
72
- ---
73
- When your task is complete, submit your result (markdown body piped on stdin):
74
- \`\`\`bash
75
- crtr job submit ${jobId} <<'MD'
76
- <your result as markdown>
77
- MD
78
- \`\`\`
79
- If you cannot complete the task, submit a failure with a reason (no stdin needed):
80
- \`\`\`bash
81
- crtr job submit ${jobId} --status failed --reason "<why>"
82
- \`\`\``;
83
- const result = spawnAgent({
84
- prompt: promptWithSubmit,
85
- cwd,
86
- jobId,
87
- maxPanesPerWindow: resolveMaxPanes(),
88
- name,
89
- });
90
- if (result.status === 'not-in-tmux') {
91
- throw new InputError({ error: 'not_in_tmux', message: result.message, next: 'Run inside a tmux session.' });
92
- }
93
- if (result.status === 'spawn-failed') {
94
- throw new InputError({ error: 'spawn_failed', message: result.message, next: 'Check tmux is running and try again.' });
95
- }
96
- const paneLabel = result.paneId !== undefined ? result.paneId : 'unknown';
97
- appendEvent(jobId, { level: 'info', event: 'worker_started', message: `pane ${paneLabel} spawned` });
98
- return { job_id: jobId, follow_up: followUpResult(jobId) };
99
- },
100
- });
101
- // ---------------------------------------------------------------------------
102
- // agent new fork
103
- // ---------------------------------------------------------------------------
104
- const newFork = defineLeaf({
105
- name: 'fork',
106
- help: {
107
- name: 'agent new fork',
108
- summary: 'fork the current Claude session into a sibling pane; returns a job handle immediately',
109
- params: [
110
- { kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: 'Working directory. Defaults to process.cwd().' },
111
- { kind: 'flag', name: 'name', type: 'string', required: true, constraint: 'Display name passed to `claude -n`; surfaces in pane title and /resume picker.' },
112
- ],
113
- output: [
114
- { name: 'job_id', type: 'string', required: true, constraint: 'Use with `crtr job read *` and `crtr job cancel`.' },
115
- { name: 'follow_up', type: 'string', required: true, constraint: 'Recommended next call.' },
116
- ],
117
- outputKind: 'object',
118
- effects: [
119
- 'Requires $CLAUDE_CODE_SESSION_ID — must run inside Claude Code.',
120
- 'Spawns a forked Claude session in a sibling tmux pane.',
121
- 'Creates a job entry and result sidecar as with `agent new prompt`.',
122
- ],
123
- },
124
- run: async (input) => {
125
- assertTmux();
126
- const parentSessionId = process.env['CLAUDE_CODE_SESSION_ID'];
127
- if (parentSessionId === undefined || parentSessionId === '') {
128
- throw new InputError({
129
- error: 'missing_session_id',
130
- message: 'crtr agent new fork requires $CLAUDE_CODE_SESSION_ID — must run inside Claude Code.',
131
- next: 'Run this command from within a Claude Code session.',
132
- });
133
- }
134
- const cwd = typeof input['cwd'] === 'string' ? input['cwd'] : process.cwd();
135
- const name = input['name'];
136
- const { jobId } = createJob('fork', { cwd });
137
- const promptWithSubmit = `Fork of session ${parentSessionId}
138
-
139
- ---
140
- When your task is complete, submit your result (markdown body piped on stdin):
141
- \`\`\`bash
142
- crtr job submit ${jobId} <<'MD'
143
- <your result as markdown>
144
- MD
145
- \`\`\`
146
- If you cannot complete the task, submit a failure with a reason (no stdin needed):
147
- \`\`\`bash
148
- crtr job submit ${jobId} --status failed --reason "<why>"
149
- \`\`\``;
150
- const result = spawnAgent({
151
- prompt: promptWithSubmit,
152
- cwd,
153
- jobId,
154
- fork: { sessionId: parentSessionId },
155
- maxPanesPerWindow: resolveMaxPanes(),
156
- name,
157
- });
158
- if (result.status === 'not-in-tmux') {
159
- throw new InputError({ error: 'not_in_tmux', message: result.message, next: 'Run inside a tmux session.' });
160
- }
161
- if (result.status === 'spawn-failed') {
162
- throw new InputError({ error: 'spawn_failed', message: result.message, next: 'Check tmux is running and try again.' });
163
- }
164
- const forkPaneLabel = result.paneId !== undefined ? result.paneId : 'unknown';
165
- appendEvent(jobId, { level: 'info', event: 'worker_started', message: `forked pane ${forkPaneLabel} spawned` });
166
- return { job_id: jobId, follow_up: followUpResult(jobId) };
167
- },
168
- });
169
- // ---------------------------------------------------------------------------
170
- // agent new planner
171
- // ---------------------------------------------------------------------------
172
- const newPlanner = defineLeaf({
173
- name: 'planner',
174
- help: {
175
- name: 'agent new planner',
176
- summary: 'launch a planning agent for an approved spec; closes the originating pane after handoff',
177
- params: [
178
- { kind: 'positional', name: 'spec_path', type: 'path', required: true, constraint: 'Absolute path to the spec file.' },
179
- { kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: 'Working directory. Defaults to process.cwd().' },
180
- { kind: 'flag', name: 'name', type: 'string', required: true, constraint: 'Display name passed to `claude -n`; surfaces in pane title and /resume picker.' },
181
- ],
182
- output: [
183
- { name: 'job_id', type: 'string', required: true, constraint: 'Use with `crtr job read *` and `crtr job cancel`.' },
184
- { name: 'follow_up', type: 'string', required: true, constraint: 'Recommended next call.' },
185
- ],
186
- outputKind: 'object',
187
- effects: [
188
- 'Spawns a planner agent in a sibling tmux pane.',
189
- 'Closes the originating pane after a short delay.',
190
- 'Creates a job entry and result sidecar.',
191
- ],
192
- },
193
- run: async (input) => {
194
- assertTmux();
195
- const specPath = input['spec_path'];
196
- const cwd = typeof input['cwd'] === 'string' ? input['cwd'] : process.cwd();
197
- const name = input['name'];
198
- if (!existsSync(specPath)) {
199
- throw new InputError({
200
- error: 'not_found',
201
- message: `spec not found: ${specPath}`,
202
- field: 'spec_path',
203
- next: 'Provide an absolute path to an existing spec file.',
204
- });
205
- }
206
- const { jobId } = createJob('planner', { cwd });
207
- const result = spawnAndDetach({
208
- prompt: planHandoffPrompt(specPath, jobId),
209
- cwd,
210
- jobId,
211
- placement: 'split-h',
212
- killAfterSeconds: DEFAULT_KILL_SECS,
213
- name,
214
- });
215
- if (result.status === 'not-in-tmux') {
216
- throw new InputError({ error: 'not_in_tmux', message: result.message, next: 'Run inside a tmux session.' });
217
- }
218
- if (result.status === 'spawn-failed') {
219
- throw new InputError({ error: 'spawn_failed', message: result.message, next: 'Check tmux is running and try again.' });
220
- }
221
- const plannerPaneLabel = result.paneId !== undefined ? result.paneId : 'unknown';
222
- appendEvent(jobId, { level: 'info', event: 'worker_started', message: `planner pane ${plannerPaneLabel} spawned` });
223
- return { job_id: jobId, follow_up: followUpResult(jobId) };
224
- },
225
- });
226
- // ---------------------------------------------------------------------------
227
- // agent new implementer
228
- // ---------------------------------------------------------------------------
229
- const newImplementer = defineLeaf({
230
- name: 'implementer',
231
- help: {
232
- name: 'agent new implementer',
233
- summary: 'launch an implementation agent for an approved plan; closes the originating pane after handoff',
234
- params: [
235
- { kind: 'positional', name: 'plan_path', type: 'path', required: true, constraint: 'Absolute path to the plan file.' },
236
- { kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: 'Working directory. Defaults to process.cwd().' },
237
- { kind: 'flag', name: 'name', type: 'string', required: true, constraint: 'Display name passed to `claude -n`; surfaces in pane title and /resume picker.' },
238
- ],
239
- output: [
240
- { name: 'job_id', type: 'string', required: true, constraint: 'Use with `crtr job read *` and `crtr job cancel`.' },
241
- { name: 'follow_up', type: 'string', required: true, constraint: 'Recommended next call.' },
242
- ],
243
- outputKind: 'object',
244
- effects: [
245
- 'Spawns an implementer agent in a sibling tmux pane.',
246
- 'Closes the originating pane after a short delay.',
247
- 'Creates a job entry and result sidecar.',
248
- ],
249
- },
250
- run: async (input) => {
251
- assertTmux();
252
- const planPath = input['plan_path'];
253
- const cwd = typeof input['cwd'] === 'string' ? input['cwd'] : process.cwd();
254
- const name = input['name'];
255
- if (!existsSync(planPath)) {
256
- throw new InputError({
257
- error: 'not_found',
258
- message: `plan not found: ${planPath}`,
259
- field: 'plan_path',
260
- next: 'Provide an absolute path to an existing plan file.',
261
- });
262
- }
263
- const { jobId } = createJob('implementer', { cwd });
264
- const result = spawnAndDetach({
265
- prompt: implementHandoffPrompt(planPath, jobId),
266
- cwd,
267
- jobId,
268
- placement: 'split-h',
269
- killAfterSeconds: DEFAULT_KILL_SECS,
270
- name,
271
- });
272
- if (result.status === 'not-in-tmux') {
273
- throw new InputError({ error: 'not_in_tmux', message: result.message, next: 'Check tmux is running and try again.' });
274
- }
275
- if (result.status === 'spawn-failed') {
276
- throw new InputError({ error: 'spawn_failed', message: result.message, next: 'Check tmux is running and try again.' });
277
- }
278
- const implPaneLabel = result.paneId !== undefined ? result.paneId : 'unknown';
279
- appendEvent(jobId, { level: 'info', event: 'worker_started', message: `implementer pane ${implPaneLabel} spawned` });
280
- return { job_id: jobId, follow_up: followUpResult(jobId) };
281
- },
282
- });
283
- // ---------------------------------------------------------------------------
284
- // agent new reviewer
285
- // ---------------------------------------------------------------------------
286
- const newReviewer = defineLeaf({
287
- name: 'reviewer',
288
- help: {
289
- name: 'agent new reviewer',
290
- summary: 'launch a reviewer agent for a plan or spec artifact; the originating pane stays alive to collect the verdict',
291
- params: [
292
- { kind: 'positional', name: 'artifact_path', type: 'path', required: true, constraint: 'Absolute path to the artifact to review.' },
293
- { kind: 'flag', name: 'kind', type: 'enum', choices: ['plan', 'spec'], required: true, constraint: 'Artifact kind to review.' },
294
- { kind: 'flag', name: 'spec-path', type: 'path', required: false, constraint: 'Absolute path to the spec, for plan reviews. Omit for spec reviews.' },
295
- { kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: 'Working directory. Defaults to process.cwd().' },
296
- { kind: 'flag', name: 'name', type: 'string', required: true, constraint: 'Display name passed to `claude -n`; surfaces in pane title and /resume picker.' },
297
- ],
298
- output: [
299
- { name: 'job_id', type: 'string', required: true, constraint: 'Use with `crtr job read *` and `crtr job cancel`.' },
300
- { name: 'follow_up', type: 'string', required: true, constraint: 'Recommended next call.' },
301
- ],
302
- outputKind: 'object',
303
- effects: [
304
- 'Spawns a reviewer agent in a sibling tmux pane.',
305
- 'The originating pane stays alive — wait on the result and act on the verdict.',
306
- 'Creates a job entry and result sidecar.',
307
- ],
308
- },
309
- run: async (input) => {
310
- assertTmux();
311
- const artifactPath = input['artifact_path'];
312
- const artifactKind = input['kind'];
313
- const specPath = typeof input['specPath'] === 'string' ? input['specPath'] : undefined;
314
- const cwd = typeof input['cwd'] === 'string' ? input['cwd'] : process.cwd();
315
- const name = input['name'];
316
- if (!existsSync(artifactPath)) {
317
- throw new InputError({
318
- error: 'not_found',
319
- message: `artifact not found: ${artifactPath}`,
320
- field: 'artifact_path',
321
- next: 'Provide an absolute path to an existing artifact file.',
322
- });
323
- }
324
- const { jobId } = createJob('reviewer', { cwd });
325
- // The reviewer is a subordinate the caller waits on (verdict → revise or
326
- // hand off), NOT a handoff successor. Use spawnAgent so the originating
327
- // pane (planner/orchestrator) stays alive to collect the result; do not
328
- // self-kill the caller the way planner/implementer handoffs do.
329
- const result = spawnAgent({
330
- prompt: reviewerHandoffPrompt(artifactPath, artifactKind, specPath !== undefined ? specPath : null, jobId),
331
- cwd,
332
- jobId,
333
- maxPanesPerWindow: resolveMaxPanes(),
334
- name,
335
- });
336
- if (result.status === 'not-in-tmux') {
337
- throw new InputError({ error: 'not_in_tmux', message: result.message, next: 'Run inside a tmux session.' });
338
- }
339
- if (result.status === 'spawn-failed') {
340
- throw new InputError({ error: 'spawn_failed', message: result.message, next: 'Check tmux is running and try again.' });
341
- }
342
- const reviewerPaneLabel = result.paneId !== undefined ? result.paneId : 'unknown';
343
- appendEvent(jobId, { level: 'info', event: 'worker_started', message: `reviewer pane ${reviewerPaneLabel} spawned` });
344
- return { job_id: jobId, follow_up: followUpResult(jobId) };
345
- },
346
- });
347
- // ---------------------------------------------------------------------------
348
- // agent new (branch)
349
- // ---------------------------------------------------------------------------
350
- const newBranch = defineBranch({
351
- name: 'new',
352
- help: {
353
- name: 'agent new',
354
- summary: 'spawn agent workers; all return a job handle immediately',
355
- children: [
356
- { name: 'prompt', desc: 'fresh agent with a prompt', useWhen: 'spawning a general-purpose agent' },
357
- { name: 'fork', desc: 'fork current session into a sibling pane', useWhen: 'branching the current session\'s context into a new agent' },
358
- { name: 'planner', desc: 'planning agent for a spec', useWhen: 'handing off spec → plan decomposition' },
359
- { name: 'implementer', desc: 'implementation agent for a plan', useWhen: 'handing off plan → code implementation' },
360
- { name: 'reviewer', desc: 'review agent for a plan or spec', useWhen: 'launching a review of a plan or spec artifact' },
361
- ],
362
- },
363
- children: [newPrompt, newFork, newPlanner, newImplementer, newReviewer],
364
- });
365
- // ---------------------------------------------------------------------------
366
- // agent (root umbrella)
367
- // ---------------------------------------------------------------------------
368
- export function registerAgent() {
369
- return defineBranch({
370
- name: 'agent',
371
- help: {
372
- name: 'agent',
373
- summary: 'agentic workflows: spec, plan, debug, and spawning agent workers',
374
- model: 'spec captures requirements; plan decomposes them; debug root-causes failures reproduce-first; new spawns the worker that executes the next phase. Spawned workers register as jobs — monitor and collect at `crtr job`.',
375
- children: [
376
- { name: 'spec', desc: 'create, read, list specifications', useWhen: 'capturing requirements before planning' },
377
- { name: 'plan', desc: 'create, read, list plans', useWhen: 'shaping or inspecting work' },
378
- { name: 'debug', desc: 'reproduce-first root-cause workflow', useWhen: 'a bug, test failure, or unexpected behavior needs root-causing' },
379
- { name: 'new', desc: 'spawn agent workers (prompt, fork, planner, implementer, reviewer)', useWhen: 'launching a new agent worker' },
380
- ],
381
- },
382
- children: [registerSpec(), registerPlan(), registerDebug(), newBranch],
383
- });
384
- }
@@ -1,3 +0,0 @@
1
- export declare const FLOW_DEBUG_GUIDE = "## Debug workflow \u2014 reproduce first\n\nAudience: the agent that ran `crtr agent debug`. A reproduction agent is\nalready spawned in a sibling pane. It writes ONE failing integration test and\nnever fixes anything. You do everything after: gate on the repro, root-cause,\nfix, verify against that same test.\n\n### Phase 0: Await the repro agent\n\nRun `crtr job read result <job_id> --wait` (10-min budget).\nOn status:\"timeout\": re-issue the wait, or run `crtr job read logs <job_id> --follow`\nuntil the job is terminal.\n\n### Phase 1: Gate on reproduction\n\n`reproduces:true`: read `test_path`, run `test_command` YOURSELF, confirm\nit fails for the stated reason. Do not trust the agent's claim \u2014 if it passes\nor fails differently, treat repro as NOT achieved. This test is the regression\ngate; it stays in the suite after the fix.\n`status:\"failed\"` / `reproduces:false` / your run disproves it: no repro\nharness. Continue, but record \"no reproduction \u2014 fix unverified; do not claim\nverified-fixed.\"\n\n### Phase 2: Reconnaissance\n\nRead the key files yourself \u2014 entry point, failure point, the data flow\nbetween. `git log` / `git blame` near the failure: recent changes are\nhigh-signal.\n\n### Phase 3: Assess difficulty, scale investigators\n\nSimple \u2192 solo (Explore subagents for tracing if the area is large).\nMedium \u2192 2\u20133 parallel `devcore:senior-advisor`: data-flow tracer, assumption\nauditor, change investigator.\nHard (intermittent, races, \"been stuck\", many modules) \u2192 3\u20135 parallel:\nend-to-end tracer, assumption breaker, git archaeologist, boundary inspector.\nGive investigators file paths, observed behavior, and concrete tasks \u2014 never\nyour hypotheses. Challenge theories against each other; the one that survives\ndisconfirmation wins.\n\n### Phase 4: Fix\n\nMinimal root-cause fix. No scope expansion, no drive-by refactor.\n\n### Phase 5: Verify\n\nRe-run `test_command`: it MUST now pass. Run the broader suite for\nregressions. If there was no repro test, state the fix is unverified by\nreproduction and recommend explicit manual verification.\n\n### Phase 6: Report\n\nRoot cause (exact line + why), evidence, the now-passing repro test path,\nconfidence (High/Medium/Low; if not High, name what is uncertain).\n\n### Constraints\n\nThe repro test is the regression guard \u2014 it stays; a fix-agent must never\nweaken it. Investigators run in forked contexts; they return summaries, not\nraw output. No code changes during Phases 2\u20133 except the repro test.";
2
- import type { LeafDef } from '../core/command.js';
3
- export declare function registerDebug(): LeafDef;
@@ -1,179 +0,0 @@
1
- // `crtr agent debug` leaf — reproduce-first root-cause workflow.
2
- //
3
- // Running it spawns a reproduction-only agent in a sibling tmux pane (the same
4
- // spawn + job-handle shape as `crtr agent new prompt`) and returns a job handle
5
- // plus a follow_up. The orchestrator-side methodology lives in FLOW_DEBUG_GUIDE
6
- // (the leaf's help.guide), loaded via `crtr agent debug -h` after the repro
7
- // agent returns. Methodology stays in the CLI guide field, like PLAN_NEW_GUIDE;
8
- // no builtin skill.
9
- export const FLOW_DEBUG_GUIDE = `## Debug workflow — reproduce first
10
-
11
- Audience: the agent that ran \`crtr agent debug\`. A reproduction agent is
12
- already spawned in a sibling pane. It writes ONE failing integration test and
13
- never fixes anything. You do everything after: gate on the repro, root-cause,
14
- fix, verify against that same test.
15
-
16
- ### Phase 0: Await the repro agent
17
-
18
- Run \`crtr job read result <job_id> --wait\` (10-min budget).
19
- On status:"timeout": re-issue the wait, or run \`crtr job read logs <job_id> --follow\`
20
- until the job is terminal.
21
-
22
- ### Phase 1: Gate on reproduction
23
-
24
- \`reproduces:true\`: read \`test_path\`, run \`test_command\` YOURSELF, confirm
25
- it fails for the stated reason. Do not trust the agent's claim — if it passes
26
- or fails differently, treat repro as NOT achieved. This test is the regression
27
- gate; it stays in the suite after the fix.
28
- \`status:"failed"\` / \`reproduces:false\` / your run disproves it: no repro
29
- harness. Continue, but record "no reproduction — fix unverified; do not claim
30
- verified-fixed."
31
-
32
- ### Phase 2: Reconnaissance
33
-
34
- Read the key files yourself — entry point, failure point, the data flow
35
- between. \`git log\` / \`git blame\` near the failure: recent changes are
36
- high-signal.
37
-
38
- ### Phase 3: Assess difficulty, scale investigators
39
-
40
- Simple → solo (Explore subagents for tracing if the area is large).
41
- Medium → 2–3 parallel \`devcore:senior-advisor\`: data-flow tracer, assumption
42
- auditor, change investigator.
43
- Hard (intermittent, races, "been stuck", many modules) → 3–5 parallel:
44
- end-to-end tracer, assumption breaker, git archaeologist, boundary inspector.
45
- Give investigators file paths, observed behavior, and concrete tasks — never
46
- your hypotheses. Challenge theories against each other; the one that survives
47
- disconfirmation wins.
48
-
49
- ### Phase 4: Fix
50
-
51
- Minimal root-cause fix. No scope expansion, no drive-by refactor.
52
-
53
- ### Phase 5: Verify
54
-
55
- Re-run \`test_command\`: it MUST now pass. Run the broader suite for
56
- regressions. If there was no repro test, state the fix is unverified by
57
- reproduction and recommend explicit manual verification.
58
-
59
- ### Phase 6: Report
60
-
61
- Root cause (exact line + why), evidence, the now-passing repro test path,
62
- confidence (High/Medium/Low; if not High, name what is uncertain).
63
-
64
- ### Constraints
65
-
66
- The repro test is the regression guard — it stays; a fix-agent must never
67
- weaken it. Investigators run in forked contexts; they return summaries, not
68
- raw output. No code changes during Phases 2–3 except the repro test.`;
69
- import { defineLeaf } from '../core/command.js';
70
- import { InputError } from '../core/io.js';
71
- import { createJob, appendEvent } from '../core/jobs.js';
72
- import { spawnAgent, isInTmux } from '../core/spawn.js';
73
- import { readConfig } from '../core/config.js';
74
- import { reproHandoffPrompt } from '../prompts/debug.js';
75
- // Inlined from job.ts (module-private there; not exported, per the no-shim
76
- // convention). Same forms.
77
- function resolveMaxPanes() {
78
- const cfg = readConfig('user');
79
- return cfg.max_panes_per_window;
80
- }
81
- function assertTmux() {
82
- if (!isInTmux()) {
83
- throw new InputError({
84
- error: 'not_in_tmux',
85
- message: 'crtr agent debug requires tmux (TMUX env var not set).',
86
- next: 'Run inside a tmux session.',
87
- });
88
- }
89
- }
90
- export function registerDebug() {
91
- return defineLeaf({
92
- name: 'debug',
93
- help: {
94
- name: 'agent debug',
95
- summary: 'reproduce-first root-cause workflow: spawns a reproduction agent, then you root-cause and fix',
96
- guide: FLOW_DEBUG_GUIDE,
97
- params: [
98
- {
99
- kind: 'stdin',
100
- name: 'steps_to_reproduce',
101
- required: true,
102
- constraint: 'Prose describing how to reproduce the failure. Pipe on stdin.',
103
- },
104
- {
105
- kind: 'flag',
106
- name: 'summary',
107
- type: 'string',
108
- required: true,
109
- constraint: 'One paragraph summary of the failure: symptom, where observed, expected vs actual.',
110
- },
111
- {
112
- kind: 'flag',
113
- name: 'cwd',
114
- type: 'path',
115
- required: false,
116
- constraint: 'Working directory for the spawned agent. Defaults to process.cwd().',
117
- },
118
- ],
119
- output: [
120
- {
121
- name: 'job_id',
122
- type: 'string',
123
- required: true,
124
- constraint: 'Use with `job read status`, `job read logs`, `job read result`, `job cancel`.',
125
- },
126
- {
127
- name: 'follow_up',
128
- type: 'string',
129
- required: true,
130
- constraint: 'Recommended next call.',
131
- },
132
- ],
133
- outputKind: 'object',
134
- effects: [
135
- 'Spawns a reproduction agent in a sibling tmux pane.',
136
- 'Creates a job entry at $XDG_STATE_HOME/crtr/jobs/<job_id>/.',
137
- 'On completion, result writes atomically to result.json.',
138
- ],
139
- },
140
- run: async (input) => {
141
- assertTmux();
142
- const stepsToReproduce = input['steps_to_reproduce'];
143
- const summary = input['summary'];
144
- const cwd = input['cwd'] ?? process.cwd();
145
- const issue = `${summary}\n\n${stepsToReproduce}`;
146
- const { jobId } = createJob('debug-repro', { cwd });
147
- const result = spawnAgent({
148
- prompt: reproHandoffPrompt(issue, jobId),
149
- cwd,
150
- jobId,
151
- maxPanesPerWindow: resolveMaxPanes(),
152
- });
153
- if (result.status === 'not-in-tmux') {
154
- throw new InputError({
155
- error: 'not_in_tmux',
156
- message: result.message,
157
- next: 'Run inside a tmux session.',
158
- });
159
- }
160
- if (result.status === 'spawn-failed') {
161
- throw new InputError({
162
- error: 'spawn_failed',
163
- message: result.message,
164
- next: 'Check tmux is running and try again.',
165
- });
166
- }
167
- const paneLabel = result.paneId !== undefined ? result.paneId : 'unknown';
168
- appendEvent(jobId, {
169
- level: 'info',
170
- event: 'worker_started',
171
- message: `repro pane ${paneLabel} spawned`,
172
- });
173
- return {
174
- job_id: jobId,
175
- follow_up: `Await the reproduction agent: crtr job read result ${jobId} --wait. Then run \`crtr agent debug -h\` and follow the workflow from Phase 1.`,
176
- };
177
- },
178
- });
179
- }