@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,344 +0,0 @@
1
- // `crtr job` subtree — universal monitoring registry for any ongoing task.
2
- //
3
- // Producers (agent spawns, future task systems) register jobs and write
4
- // results; this subtree is the read/cancel/submit surface shared across all
5
- // producers. Sub-branches: read {list, status, logs, result}, submit, _fail,
6
- // cancel.
7
- //
8
- // Terminal-write contract:
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
- //
14
- // `job read logs` is the only JSONL leaf.
15
- import { defineBranch, defineLeaf } from '../core/command.js';
16
- import { emitLine } from '../core/io.js';
17
- import { InputError } from '../core/io.js';
18
- import { writeMarkdownResult, readResult as jobsReadResult, jobStatus, listJobs, readLog, cancelJob, } from '../core/jobs.js';
19
- import { scheduleKillCurrentPane } from '../core/spawn.js';
20
- import { paginate } from '../core/pagination.js';
21
- const WAIT_BUDGET_MS = 10 * 60 * 1000;
22
- const FOLLOW_POLL_MS = 1000;
23
- const DEFAULT_KILL_SECS = 2;
24
- // ---------------------------------------------------------------------------
25
- // read sub-branch
26
- // ---------------------------------------------------------------------------
27
- const readList = defineLeaf({
28
- name: 'list',
29
- help: {
30
- name: 'job read list',
31
- summary: 'paginated list of jobs, sorted by created_at ascending',
32
- params: [
33
- { kind: 'flag', name: 'limit', type: 'int', required: false, default: 20, constraint: 'Default 20, max 100.' },
34
- { kind: 'flag', name: 'cursor', type: 'string', required: false, constraint: 'Opaque token from next_cursor. Omit on first call.' },
35
- ],
36
- output: [
37
- { name: 'items', type: 'object[]', required: true, constraint: 'Each: {job_id, kind, state, created_at}. Sorted by created_at ascending.' },
38
- { name: 'next_cursor', type: 'string | null', required: true, constraint: 'null means no more items.' },
39
- { name: 'total', type: 'integer | null', required: true, constraint: 'Total count of all jobs.' },
40
- ],
41
- outputKind: 'object',
42
- effects: ['None. Read-only.'],
43
- },
44
- run: async (input) => {
45
- const limit = typeof input['limit'] === 'number' ? input['limit'] : 20;
46
- const cursor = typeof input['cursor'] === 'string' ? input['cursor'] : undefined;
47
- const all = listJobs();
48
- const page = paginate(all, { limit, cursor }, {
49
- defaultLimit: 20,
50
- maxLimit: 100,
51
- keyOf: (item) => item.created_at,
52
- total: 'count',
53
- });
54
- return {
55
- items: page.items,
56
- next_cursor: page.next_cursor,
57
- total: page.total,
58
- };
59
- },
60
- });
61
- const readStatus = defineLeaf({
62
- name: 'status',
63
- help: {
64
- name: 'job read status',
65
- summary: 'read the current status of a job',
66
- params: [
67
- { kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: 'Job id from a producer (e.g. `crtr agent new *`).' },
68
- ],
69
- output: [
70
- { name: 'job_id', type: 'string', required: true, constraint: 'Echo of input.' },
71
- { name: 'state', type: 'string', required: true, constraint: 'One of: live, done, failed, canceled.' },
72
- { name: 'age_s', type: 'number', required: true, constraint: 'Seconds since job creation.' },
73
- { name: 'last_event', type: 'object | null', required: true, constraint: 'Most recent log event {event, ts}, or null if no events yet.' },
74
- ],
75
- outputKind: 'object',
76
- effects: ['None. Read-only.'],
77
- },
78
- run: async (input) => {
79
- const jobId = input['job_id'];
80
- const status = jobStatus(jobId);
81
- return {
82
- job_id: jobId,
83
- state: status.state,
84
- age_s: status.age_s,
85
- last_event: status.last_event,
86
- };
87
- },
88
- });
89
- const readLogs = defineLeaf({
90
- name: 'logs',
91
- help: {
92
- name: 'job read logs',
93
- summary: 'read log events from a job; emits JSONL — one event object per line',
94
- params: [
95
- { kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: 'Job id from a producer (e.g. `crtr agent new *`).' },
96
- { kind: 'flag', name: 'since', type: 'string', required: false, constraint: 'ISO 8601 timestamp. Only emit events at or after this time.' },
97
- { kind: 'flag', name: 'until', type: 'string', required: false, constraint: 'ISO 8601 timestamp. Only emit events before this time.' },
98
- { kind: 'flag', name: 'level', type: 'enum', choices: ['debug', 'info', 'warn', 'error'], required: false, default: 'info', constraint: 'Minimum severity. Default: info.' },
99
- { kind: 'flag', name: 'follow', type: 'bool', required: false, constraint: 'When present, stream new events until the job reaches a terminal state, then stop.' },
100
- ],
101
- output: [
102
- {
103
- name: '<event line>',
104
- type: 'object',
105
- required: true,
106
- constraint: 'Each JSONL line is: {ts:string, level:"debug"|"info"|"warn"|"error", event:string, message:string, data?:object}. Emitted one per line.',
107
- },
108
- ],
109
- outputKind: 'jsonl',
110
- effects: ['None. Read-only.'],
111
- },
112
- run: async (input) => {
113
- const jobId = input['job_id'];
114
- const since = typeof input['since'] === 'string' ? input['since'] : undefined;
115
- const until = typeof input['until'] === 'string' ? input['until'] : undefined;
116
- const level = (typeof input['level'] === 'string' ? input['level'] : 'info');
117
- const follow = input['follow'] === true;
118
- const minLevel = level;
119
- // Emit all existing events.
120
- const events = readLog(jobId, { sinceTs: since, untilTs: until, minLevel });
121
- for (const ev of events) {
122
- emitLine(ev);
123
- }
124
- if (!follow)
125
- return;
126
- // Follow: poll for new events until the job reaches a terminal state.
127
- // Track the latest emitted timestamp to avoid re-emitting.
128
- let lastTs = until !== undefined ? until : new Date(0).toISOString();
129
- // Update lastTs from emitted events.
130
- for (const ev of events) {
131
- const e = ev;
132
- if (typeof e['ts'] === 'string' && e['ts'] > lastTs) {
133
- lastTs = e['ts'];
134
- }
135
- }
136
- const terminalStates = new Set(['done', 'failed', 'canceled']);
137
- await new Promise((resolve) => {
138
- const poll = () => {
139
- const status = jobStatus(jobId);
140
- const newEvents = readLog(jobId, { sinceTs: lastTs !== new Date(0).toISOString() ? lastTs : undefined, minLevel });
141
- for (const ev of newEvents) {
142
- const e = ev;
143
- if (typeof e['ts'] === 'string' && e['ts'] > lastTs) {
144
- emitLine(e);
145
- lastTs = e['ts'];
146
- }
147
- }
148
- if (terminalStates.has(status.state)) {
149
- resolve();
150
- return;
151
- }
152
- setTimeout(poll, FOLLOW_POLL_MS);
153
- };
154
- setTimeout(poll, FOLLOW_POLL_MS);
155
- });
156
- },
157
- });
158
- const readResult = defineLeaf({
159
- name: 'result',
160
- help: {
161
- name: 'job read result',
162
- summary: 'read the final result of a completed job',
163
- params: [
164
- { kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: 'Job id from a producer (e.g. `crtr agent new *`).' },
165
- { kind: 'flag', name: 'wait', type: 'bool', required: false, constraint: 'When present, blocks until a result file appears (up to 10 min).' },
166
- ],
167
- output: [
168
- { name: 'job_id', type: 'string', required: true, constraint: 'Echo of input.' },
169
- { name: 'status', type: 'string', required: true, constraint: 'One of: done, failed, canceled, timeout.' },
170
- { name: 'result_md', type: 'string', required: false, constraint: 'Markdown body submitted by an agent via `crtr job submit`. Present when the job used the agent submit path.' },
171
- { name: 'result', type: 'object', required: false, constraint: 'Structured object submitted by a programmatic caller (human/sys). Present when the job used the programmatic submit path.' },
172
- { name: 'reason', type: 'string', required: false, constraint: 'Failure reason from frontmatter when status is failed and the agent submit path was used.' },
173
- ],
174
- outputKind: 'object',
175
- effects: ['None. Read-only.'],
176
- },
177
- run: async (input) => {
178
- const jobId = input['job_id'];
179
- const wait = input['wait'] === true;
180
- const r = await jobsReadResult(jobId, { waitMs: wait ? WAIT_BUDGET_MS : 0 });
181
- const out = { job_id: jobId, status: r.status };
182
- if (r.result !== undefined) {
183
- out['result'] = r.result;
184
- }
185
- if (r.result_md !== undefined) {
186
- out['result_md'] = r.result_md;
187
- }
188
- if (r.reason !== undefined) {
189
- out['reason'] = r.reason;
190
- }
191
- return out;
192
- },
193
- });
194
- const readBranch = defineBranch({
195
- name: 'read',
196
- help: {
197
- name: 'job read',
198
- summary: 'read job status, logs, or results',
199
- children: [
200
- { name: 'list', desc: 'paginated job list', useWhen: 'enumerating jobs' },
201
- { name: 'status', desc: 'current state and age', useWhen: 'checking if a job is still live' },
202
- { name: 'logs', desc: 'stream JSONL log events', useWhen: 'monitoring progress or debugging a job' },
203
- { name: 'result', desc: 'read final result', useWhen: 'collecting the output of a completed job' },
204
- ],
205
- },
206
- children: [readList, readStatus, readLogs, readResult],
207
- });
208
- // ---------------------------------------------------------------------------
209
- // submit — called by the worker inside its pane (or by any producer that
210
- // writes a result programmatically)
211
- // ---------------------------------------------------------------------------
212
- const jobSubmit = defineLeaf({
213
- name: 'submit',
214
- help: {
215
- name: 'job submit',
216
- summary: 'deliver a markdown result back to a job record (called by workers, or any producer writing the terminal value)',
217
- params: [
218
- { kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: 'Job id injected as $CRTR_JOB_ID in the spawned pane.' },
219
- { kind: 'stdin', name: 'body', required: false, constraint: 'Markdown body of the result, piped on stdin. Required when --status is done (the default). When --status failed, stdin is optional; --reason carries the explanation.' },
220
- { kind: 'flag', name: 'status', type: 'enum', choices: ['done', 'failed'], required: false, default: 'done', constraint: 'Terminal status to record. Default: done.' },
221
- { kind: 'flag', name: 'reason', type: 'string', required: false, constraint: 'Short failure reason. Required when --status failed; ignored otherwise.' },
222
- { kind: 'flag', name: 'kill-pane', type: 'bool', required: false, constraint: `When present, schedule the current tmux pane to close ${DEFAULT_KILL_SECS}s after submission so the spawned worker does not linger. Reviewer agents should pass this; planner/implementer handoffs already self-kill on spawn.` },
223
- ],
224
- output: [
225
- { name: 'submitted', type: 'boolean', required: true, constraint: 'Always true on success.' },
226
- { name: 'pane_kill_scheduled', type: 'boolean', required: true, constraint: 'True when --kill-pane is set and a tmux pane kill was scheduled. False otherwise (--kill-pane not set, not in tmux, or TMUX_PANE unset).' },
227
- ],
228
- outputKind: 'object',
229
- effects: [
230
- 'Writes <jobdir>/result.md atomically (YAML frontmatter + body), marking the job done or failed.',
231
- 'Updates meta.json status to match.',
232
- `When --kill-pane is set, schedules \`tmux kill-pane\` on $TMUX_PANE after ${DEFAULT_KILL_SECS}s (detached; submit still returns cleanly).`,
233
- ],
234
- },
235
- run: async (input) => {
236
- const jobId = input['job_id'];
237
- const status = (typeof input['status'] === 'string' ? input['status'] : 'done');
238
- const body = typeof input['body'] === 'string' ? input['body'] : '';
239
- const reason = typeof input['reason'] === 'string' ? input['reason'] : '';
240
- const killPane = input['killPane'] === true;
241
- if (status === 'done' && body.trim() === '') {
242
- throw new InputError({
243
- error: 'invalid_field',
244
- message: '--status done requires a markdown body on stdin.',
245
- field: 'body',
246
- next: `Pipe the markdown result on stdin, e.g. \`crtr job submit ${jobId} <<'MD' ... MD\`. For failures, use \`--status failed --reason "<why>"\`.`,
247
- });
248
- }
249
- if (status === 'failed' && reason.trim() === '') {
250
- throw new InputError({
251
- error: 'invalid_field',
252
- message: '--status failed requires --reason "<text>".',
253
- field: 'reason',
254
- next: 'Pass --reason explaining why the task could not complete.',
255
- });
256
- }
257
- writeMarkdownResult(jobId, body, status, status === 'failed' ? reason : undefined);
258
- const paneKillScheduled = killPane ? scheduleKillCurrentPane(DEFAULT_KILL_SECS) : false;
259
- return { submitted: true, pane_kill_scheduled: paneKillScheduled };
260
- },
261
- });
262
- // ---------------------------------------------------------------------------
263
- // _fail — called by the wrapper shell if claude exits without submitting
264
- // ---------------------------------------------------------------------------
265
- const jobFail = defineLeaf({
266
- name: '_fail',
267
- help: {
268
- name: 'job _fail',
269
- summary: 'internal: mark a job failed if it has not already been submitted (called by wrapper shell)',
270
- params: [
271
- { kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: 'Job id. If a result file already exists, this is a no-op.' },
272
- ],
273
- output: [
274
- { name: 'recorded', type: 'boolean', required: true, constraint: 'True if failure was recorded; false if a result file already existed (no-op).' },
275
- ],
276
- outputKind: 'object',
277
- effects: [
278
- 'Writes result.md with status "failed" and a reason if no result file is present.',
279
- 'Updates meta.json status to failed.',
280
- ],
281
- },
282
- run: async (input) => {
283
- const jobId = input['job_id'];
284
- try {
285
- const existing = await jobsReadResult(jobId, { waitMs: 0 });
286
- if (existing.status !== 'timeout') {
287
- return { recorded: false };
288
- }
289
- }
290
- catch {
291
- // job dir not found — still try to write to surface the failure
292
- }
293
- try {
294
- writeMarkdownResult(jobId, '', 'failed', 'worker exited without submitting');
295
- return { recorded: true };
296
- }
297
- catch {
298
- return { recorded: false };
299
- }
300
- },
301
- });
302
- // ---------------------------------------------------------------------------
303
- // cancel
304
- // ---------------------------------------------------------------------------
305
- const jobCancel = defineLeaf({
306
- name: 'cancel',
307
- help: {
308
- name: 'job cancel',
309
- summary: 'send a best-effort cancellation signal to a running job',
310
- params: [
311
- { kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: 'Job id from a producer (e.g. `crtr agent new *`).' },
312
- ],
313
- output: [
314
- { name: 'canceled', type: 'boolean', required: true, constraint: 'True if a signal was delivered or the job was already terminal; false if the job was not live.' },
315
- ],
316
- outputKind: 'object',
317
- effects: ['Best-effort: delivers SIGTERM to the worker process and marks meta.json canceled.'],
318
- },
319
- run: async (input) => {
320
- const jobId = input['job_id'];
321
- const result = cancelJob(jobId);
322
- return { canceled: result.canceled };
323
- },
324
- });
325
- // ---------------------------------------------------------------------------
326
- // root branch
327
- // ---------------------------------------------------------------------------
328
- export function registerJob() {
329
- return defineBranch({
330
- name: 'job',
331
- help: {
332
- name: 'job',
333
- summary: 'monitor and collect results from any ongoing task',
334
- model: 'A job is a producer-agnostic record of an ongoing task: state, logs, terminal result. Producers (`crtr agent new *`, future task systems) create jobs; this subtree is the shared read/cancel/submit surface. States: live | done | failed | canceled.',
335
- children: [
336
- { name: 'read', desc: 'read status, logs, or results', useWhen: 'monitoring or collecting from a running or completed job' },
337
- { name: 'submit', desc: 'deliver result from inside a worker pane or any producer', useWhen: 'a worker is ready to return its output' },
338
- { name: '_fail', desc: 'internal: mark job failed on unsubmitted exit', useWhen: 'called by the wrapper shell, not manually' },
339
- { name: 'cancel', desc: 'best-effort cancel a live job', useWhen: 'stopping a job that is no longer needed' },
340
- ],
341
- },
342
- children: [readBranch, jobSubmit, jobFail, jobCancel],
343
- });
344
- }
@@ -1,4 +0,0 @@
1
- export declare const PLAN_NEW_GUIDE = "## Planning workflow\n\nBuild and save an implementation plan: a map another agent can execute without\nre-discovering context. Work through these phases before saving.\n\n### Phase 1: Understand\n\nBuild a full picture of the request and the code. Search for reusable\nfunctions, patterns, and existing implementations before proposing new ones.\n\nLaunch up to 3 Explore subagents IN PARALLEL (single message, multiple tool\ncalls). Use 1 agent for isolated, small-scope tasks; use more when scope is\nuncertain or multiple subsystems are involved. Give each a distinct focus so\nthey don't duplicate work.\n\n### Phase 2: Design\n\nDesign the implementation from Phase 1 findings. Default to launching at least\n1 Plan agent to validate your understanding and surface alternatives. Skip only\nfor trivially small tasks (typo fixes, single-line renames). Use up to 3 agents\nfor large refactors or architectural changes.\n\nIn the Plan agent prompt: include file paths, code-path traces, requirements,\nand constraints from Phase 1. Request a detailed implementation plan.\n\n### Phase 3: Review findings\n\nRead the critical files identified by agents. Confirm the plan aligns with the\nuser's request. Use AskUserQuestion ONLY to clarify requirements or choose\nbetween approaches \u2014 never to ask \"is this okay?\" or \"should I proceed?\".\n\n### Phase 4: Compose the plan body\n\nQuality bar \u2014 every item below is cheap to satisfy and saves the implementer\nfrom re-deciding:\n\n- Every decision pinned. No \"if X then Y\" branches, no \"investigate whether\u2026\",\n no deferred choices. If you don't know, find out or ask now.\n- No timelines, no fallbacks, no magic values, no \"for now\" shortcuts.\n- Where the plan creates a new interface, schema, or contract, write the actual\n shape, not \"design a Foo type.\"\n\nRequired sections:\n\n # Plan: <one-line title>\n\n ## Context\n <why this change is being made \u2014 the problem, what prompted it, intended outcome>\n\n ## Recommended approach\n <your chosen approach only. Concise enough to scan, detailed enough to execute.>\n\n ## Files to modify / create\n - `path/to/file.ts` \u2014 <what changes>\n\n ## Existing utilities to reuse\n - `functionName` from `path/to/file.ts:LL` \u2014 <why it fits>\n\n ## Verification\n <how to test end-to-end \u2014 run the code, run tests, etc.>\n\nFor plans touching 4+ files across distinct concerns, structure parallel tasks:\n\n ## Tasks\n - **Task 1**: <name>\n - Files: `a.ts`, `b.ts` (disjoint from other tasks)\n - Depends on: (none) | Task N\n - Integration: <shared types/APIs with exact shape>\n - Changes: <bullets>\n\nSkip the Tasks structure for small plans; it's noise when there's no\nparallelism to unlock.\n\n### Phase 5: Save\n\nRun `crtr agent plan new`:\n\n echo '<plan markdown>' | crtr agent plan new <kebab-case-name> [--spec <spec-name>]\n\n- NAME: short kebab-case slug. Nested names become subdirectories\n (e.g. `auth/jwt-refresh`).\n- Pipe the full plan markdown composed in Phase 4 on stdin.\n- `--spec` (optional): name of the spec this plan implements. Enables alignment\n check by the reviewer.\n\nOutput: `{path, follow_up}`. The `follow_up` field names the exact next call\n\u2014 run it.\n\n### Phase 6: Oversize check\n\nIf `follow_up` contains an oversize advisory (plan exceeds 200 lines), split\ninto a short index plan plus nested part plans, each under the threshold.\nRe-save. The implementer executes parts one at a time; long monolithic plans\nare under-decomposed.\n\n### Phase 7: Done\n\nAfter the reviewer approves the plan, your turn ends. Do not summarize in chat.\nFor a human gate, optionally put the plan in front of a person with `crtr\nhuman review` (anchored comments) and gate the handoff with `crtr human\napprove`. This complements \u2014 it does not replace \u2014 `crtr agent new reviewer`.\nIf the user is ready to build, ask once whether to hand off; if yes, run:\n`crtr agent new implementer` with the plan path.";
2
- export declare const PLAN_SHOW_GUIDE = "";
3
- import type { BranchDef } from '../core/command.js';
4
- export declare function registerPlan(): BranchDef;
@@ -1,309 +0,0 @@
1
- // `crtr agent plan` subtree — plan new / show / list handlers.
2
- export const PLAN_NEW_GUIDE = `## Planning workflow
3
-
4
- Build and save an implementation plan: a map another agent can execute without
5
- re-discovering context. Work through these phases before saving.
6
-
7
- ### Phase 1: Understand
8
-
9
- Build a full picture of the request and the code. Search for reusable
10
- functions, patterns, and existing implementations before proposing new ones.
11
-
12
- Launch up to 3 Explore subagents IN PARALLEL (single message, multiple tool
13
- calls). Use 1 agent for isolated, small-scope tasks; use more when scope is
14
- uncertain or multiple subsystems are involved. Give each a distinct focus so
15
- they don't duplicate work.
16
-
17
- ### Phase 2: Design
18
-
19
- Design the implementation from Phase 1 findings. Default to launching at least
20
- 1 Plan agent to validate your understanding and surface alternatives. Skip only
21
- for trivially small tasks (typo fixes, single-line renames). Use up to 3 agents
22
- for large refactors or architectural changes.
23
-
24
- In the Plan agent prompt: include file paths, code-path traces, requirements,
25
- and constraints from Phase 1. Request a detailed implementation plan.
26
-
27
- ### Phase 3: Review findings
28
-
29
- Read the critical files identified by agents. Confirm the plan aligns with the
30
- user's request. Use AskUserQuestion ONLY to clarify requirements or choose
31
- between approaches — never to ask "is this okay?" or "should I proceed?".
32
-
33
- ### Phase 4: Compose the plan body
34
-
35
- Quality bar — every item below is cheap to satisfy and saves the implementer
36
- from re-deciding:
37
-
38
- - Every decision pinned. No "if X then Y" branches, no "investigate whether…",
39
- no deferred choices. If you don't know, find out or ask now.
40
- - No timelines, no fallbacks, no magic values, no "for now" shortcuts.
41
- - Where the plan creates a new interface, schema, or contract, write the actual
42
- shape, not "design a Foo type."
43
-
44
- Required sections:
45
-
46
- # Plan: <one-line title>
47
-
48
- ## Context
49
- <why this change is being made — the problem, what prompted it, intended outcome>
50
-
51
- ## Recommended approach
52
- <your chosen approach only. Concise enough to scan, detailed enough to execute.>
53
-
54
- ## Files to modify / create
55
- - \`path/to/file.ts\` — <what changes>
56
-
57
- ## Existing utilities to reuse
58
- - \`functionName\` from \`path/to/file.ts:LL\` — <why it fits>
59
-
60
- ## Verification
61
- <how to test end-to-end — run the code, run tests, etc.>
62
-
63
- For plans touching 4+ files across distinct concerns, structure parallel tasks:
64
-
65
- ## Tasks
66
- - **Task 1**: <name>
67
- - Files: \`a.ts\`, \`b.ts\` (disjoint from other tasks)
68
- - Depends on: (none) | Task N
69
- - Integration: <shared types/APIs with exact shape>
70
- - Changes: <bullets>
71
-
72
- Skip the Tasks structure for small plans; it's noise when there's no
73
- parallelism to unlock.
74
-
75
- ### Phase 5: Save
76
-
77
- Run \`crtr agent plan new\`:
78
-
79
- echo '<plan markdown>' | crtr agent plan new <kebab-case-name> [--spec <spec-name>]
80
-
81
- - NAME: short kebab-case slug. Nested names become subdirectories
82
- (e.g. \`auth/jwt-refresh\`).
83
- - Pipe the full plan markdown composed in Phase 4 on stdin.
84
- - \`--spec\` (optional): name of the spec this plan implements. Enables alignment
85
- check by the reviewer.
86
-
87
- Output: \`{path, follow_up}\`. The \`follow_up\` field names the exact next call
88
- — run it.
89
-
90
- ### Phase 6: Oversize check
91
-
92
- If \`follow_up\` contains an oversize advisory (plan exceeds 200 lines), split
93
- into a short index plan plus nested part plans, each under the threshold.
94
- Re-save. The implementer executes parts one at a time; long monolithic plans
95
- are under-decomposed.
96
-
97
- ### Phase 7: Done
98
-
99
- After the reviewer approves the plan, your turn ends. Do not summarize in chat.
100
- For a human gate, optionally put the plan in front of a person with \`crtr
101
- human review\` (anchored comments) and gate the handoff with \`crtr human
102
- approve\`. This complements — it does not replace — \`crtr agent new reviewer\`.
103
- If the user is ready to build, ask once whether to hand off; if yes, run:
104
- \`crtr agent new implementer\` with the plan path.`;
105
- export const PLAN_SHOW_GUIDE = '';
106
- import { defineBranch, defineLeaf } from '../core/command.js';
107
- import { saveArtifact, readArtifact, listArtifacts, OVERSIZE_WARN_LINES } from '../core/artifact.js';
108
- import { paginate } from '../core/pagination.js';
109
- export function registerPlan() {
110
- const planNew = defineLeaf({
111
- name: 'new',
112
- help: {
113
- name: 'agent plan new',
114
- summary: 'draft a plan from intent and optional spec alignment',
115
- guide: PLAN_NEW_GUIDE,
116
- params: [
117
- {
118
- kind: 'positional',
119
- name: 'name',
120
- type: 'string',
121
- required: true,
122
- constraint: 'Kebab-case slug used as the artifact filename. No spaces; use hyphens.',
123
- },
124
- {
125
- kind: 'stdin',
126
- name: 'body',
127
- required: true,
128
- constraint: "Full planning prose. Treated as the planner's north star; not parsed further.",
129
- },
130
- {
131
- kind: 'flag',
132
- name: 'spec',
133
- type: 'string',
134
- required: false,
135
- constraint: 'Name of the spec this plan implements. Enables alignment check on write. Must reference an existing spec artifact.',
136
- },
137
- ],
138
- output: [
139
- {
140
- name: 'path',
141
- type: 'string',
142
- required: true,
143
- constraint: 'Absolute path to the written plan artifact.',
144
- },
145
- {
146
- name: 'follow_up',
147
- type: 'string',
148
- required: true,
149
- constraint: 'Recommended next call (reviewer spawn via `crtr agent new reviewer`).',
150
- },
151
- ],
152
- outputKind: 'object',
153
- effects: [
154
- 'Writes a plan artifact to the plans artifact directory.',
155
- 'If `--spec` is provided, records the spec alignment reference in the artifact frontmatter.',
156
- ],
157
- },
158
- run: async (input) => {
159
- const name = input['name'];
160
- const body = input['body'];
161
- const spec = input['spec'];
162
- const meta = {};
163
- if (spec !== undefined)
164
- meta['spec'] = spec;
165
- const { path, oversize, lineCount } = saveArtifact('plans', name, body, meta);
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
- follow_up +=
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
- if (oversize) {
170
- follow_up +=
171
- ` OVERSIZE ADVISORY: this plan is ${lineCount} lines (> ${OVERSIZE_WARN_LINES}). Split into a short index plan plus nested part plans before reviewing.`;
172
- }
173
- return { path, follow_up };
174
- },
175
- });
176
- const planShow = defineLeaf({
177
- name: 'show',
178
- help: {
179
- name: 'agent plan show',
180
- summary: 'read a plan artifact by name',
181
- params: [
182
- {
183
- kind: 'positional',
184
- name: 'name',
185
- type: 'string',
186
- required: true,
187
- constraint: 'Exact artifact name (no path extension). Use `plan list` to enumerate.',
188
- },
189
- ],
190
- output: [
191
- {
192
- name: 'name',
193
- type: 'string',
194
- required: true,
195
- constraint: 'Artifact name as stored.',
196
- },
197
- {
198
- name: 'path',
199
- type: 'string',
200
- required: true,
201
- constraint: 'Absolute path to the artifact file.',
202
- },
203
- {
204
- name: 'body',
205
- type: 'string',
206
- required: true,
207
- constraint: 'Full plan body text.',
208
- },
209
- {
210
- name: 'spec',
211
- type: 'string | null',
212
- required: true,
213
- constraint: 'Associated spec name, or null if none.',
214
- },
215
- ],
216
- outputKind: 'object',
217
- effects: ['None. Read-only.'],
218
- },
219
- run: async (input) => {
220
- const name = input['name'];
221
- const record = readArtifact('plans', name);
222
- return { name: record.name, path: record.path, body: record.body, spec: record.spec };
223
- },
224
- });
225
- const planList = defineLeaf({
226
- name: 'list',
227
- help: {
228
- name: 'agent plan list',
229
- summary: 'paginated list of plan artifacts, sorted ascending by name',
230
- params: [
231
- {
232
- kind: 'flag',
233
- name: 'scope',
234
- type: 'enum',
235
- choices: ['user', 'project', 'all'],
236
- required: false,
237
- constraint: 'Filter by scope. Omit to list all.',
238
- },
239
- {
240
- kind: 'flag',
241
- name: 'limit',
242
- type: 'int',
243
- required: false,
244
- default: 20,
245
- constraint: 'Default 20, max 100.',
246
- },
247
- {
248
- kind: 'flag',
249
- name: 'cursor',
250
- type: 'string',
251
- required: false,
252
- constraint: "Opaque token from a previous response's next_cursor. Omit on first call.",
253
- },
254
- ],
255
- output: [
256
- {
257
- name: 'items',
258
- type: 'object[]',
259
- required: true,
260
- constraint: 'Each: {name, path, updated_at}. Sorted ascending by name.',
261
- },
262
- {
263
- name: 'next_cursor',
264
- type: 'string | null',
265
- required: true,
266
- constraint: 'Pass on the next call to continue. null means no more items.',
267
- },
268
- {
269
- name: 'total',
270
- type: 'integer | null',
271
- required: true,
272
- constraint: 'Total plans matching the query. Exact when cheap; null on large/filtered sets — do not retry to force it.',
273
- },
274
- ],
275
- outputKind: 'object',
276
- effects: ['None. Read-only.'],
277
- },
278
- run: async (input) => {
279
- const limit = input['limit'] ?? 20;
280
- const cursor = input['cursor'];
281
- const all = listArtifacts('plans');
282
- const result = paginate(all, { limit, cursor }, {
283
- defaultLimit: 20,
284
- maxLimit: 100,
285
- keyOf: (item) => item.name,
286
- total: 'count',
287
- });
288
- return {
289
- items: result.items,
290
- next_cursor: result.next_cursor,
291
- total: result.total,
292
- };
293
- },
294
- });
295
- return defineBranch({
296
- name: 'plan',
297
- help: {
298
- name: 'agent plan',
299
- summary: 'create and read plan artifacts',
300
- model: 'Lifecycle: draft -> active -> handed-off.',
301
- children: [
302
- { name: 'new', desc: 'draft a plan from intent', useWhen: 'starting fresh work or decomposing a spec' },
303
- { name: 'show', desc: 'read a plan by name', useWhen: 'reasoning about an existing plan' },
304
- { name: 'list', desc: 'enumerate plans', useWhen: 'discovering what plans exist' },
305
- ],
306
- },
307
- children: [planNew, planShow, planList],
308
- });
309
- }