@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
package/dist/core/jobs.js DELETED
@@ -1,462 +0,0 @@
1
- // Job / long-running-operation infrastructure.
2
- //
3
- // Files are the single source of truth. No in-memory registry. An agent picks
4
- // up a job by id across processes. Crashes recover by reading files.
5
- //
6
- // Layout: ${XDG_STATE_HOME or ~/.local/state}/crtr/jobs/<job_id>/
7
- // meta.json — written atomically on create; updated atomically on terminal transition.
8
- // log.jsonl — append-only event log.
9
- // result.md — agent submissions (markdown body + YAML frontmatter). Written atomically.
10
- // result.json — programmatic submissions (structured object). Written atomically.
11
- // Either result file's APPEARANCE is the completion signal. Exactly one is written per job.
12
- import { existsSync, mkdirSync, appendFileSync, readFileSync, writeFileSync, renameSync, readdirSync, statSync, } from 'node:fs';
13
- import { watch } from 'node:fs';
14
- import { spawnSync } from 'node:child_process';
15
- import { join } from 'node:path';
16
- import { homedir } from 'node:os';
17
- import { randomBytes } from 'node:crypto';
18
- import { notFound, general } from './errors.js';
19
- // ---------------------------------------------------------------------------
20
- // Paths
21
- // ---------------------------------------------------------------------------
22
- function jobsRoot() {
23
- const xdg = process.env['XDG_STATE_HOME'];
24
- const base = (xdg !== undefined && xdg !== '') ? xdg : join(homedir(), '.local', 'state');
25
- return join(base, 'crtr', 'jobs');
26
- }
27
- function jobDir(jobId) {
28
- return join(jobsRoot(), jobId);
29
- }
30
- function metaPath(jobId) {
31
- return join(jobDir(jobId), 'meta.json');
32
- }
33
- function logPath(jobId) {
34
- return join(jobDir(jobId), 'log.jsonl');
35
- }
36
- function resultJsonPath(jobId) {
37
- return join(jobDir(jobId), 'result.json');
38
- }
39
- function resultMdPath(jobId) {
40
- return join(jobDir(jobId), 'result.md');
41
- }
42
- /** Path of whichever result file currently exists, or null if neither does. */
43
- function existingResultPath(jobId) {
44
- const md = resultMdPath(jobId);
45
- if (existsSync(md))
46
- return md;
47
- const js = resultJsonPath(jobId);
48
- if (existsSync(js))
49
- return js;
50
- return null;
51
- }
52
- // ---------------------------------------------------------------------------
53
- // Internal helpers
54
- // ---------------------------------------------------------------------------
55
- function generateJobId() {
56
- const ts = Date.now().toString(36);
57
- const rnd = randomBytes(4).toString('hex');
58
- return `${ts}-${rnd}`;
59
- }
60
- function ensureJobsRoot() {
61
- mkdirSync(jobsRoot(), { recursive: true });
62
- }
63
- function readMeta(jobId) {
64
- const p = metaPath(jobId);
65
- if (!existsSync(p)) {
66
- throw notFound(`job not found: ${jobId}`, { job_id: jobId });
67
- }
68
- try {
69
- return JSON.parse(readFileSync(p, 'utf8'));
70
- }
71
- catch {
72
- throw general(`failed to parse meta.json for job ${jobId}`, { job_id: jobId });
73
- }
74
- }
75
- function writeMeta(jobId, meta) {
76
- const dir = jobDir(jobId);
77
- const tmp = join(dir, '.meta.tmp');
78
- writeFileSync(tmp, JSON.stringify(meta, null, 2), 'utf8');
79
- renameSync(tmp, metaPath(jobId));
80
- }
81
- function pidAlive(pid) {
82
- try {
83
- process.kill(pid, 0);
84
- return true;
85
- }
86
- catch {
87
- return false;
88
- }
89
- }
90
- const LEVEL_RANK = {
91
- debug: 0,
92
- info: 1,
93
- warn: 2,
94
- error: 3,
95
- };
96
- // ---------------------------------------------------------------------------
97
- // Exported API
98
- // ---------------------------------------------------------------------------
99
- /**
100
- * Allocate a new job directory and write meta.json atomically.
101
- * Returns the job_id and the absolute directory path.
102
- */
103
- export function createJob(kind, opts) {
104
- ensureJobsRoot();
105
- const jobId = generateJobId();
106
- const dir = jobDir(jobId);
107
- mkdirSync(dir, { recursive: true });
108
- const meta = {
109
- job_id: jobId,
110
- kind,
111
- created_at: new Date().toISOString(),
112
- cwd: opts.cwd,
113
- status: 'live',
114
- };
115
- if (opts.pid !== undefined) {
116
- meta.pid = opts.pid;
117
- }
118
- writeMeta(jobId, meta);
119
- return { jobId, dir };
120
- }
121
- /**
122
- * Record the tmux pane hosting a detached worker so `cancelJob` can kill it.
123
- */
124
- export function recordJobPane(jobId, paneId) {
125
- const meta = readMeta(jobId);
126
- meta.pane_id = paneId;
127
- writeMeta(jobId, meta);
128
- }
129
- /**
130
- * Append one event line to log.jsonl. Does NOT throw if jobId doesn't exist —
131
- * a crashed writer should not further corrupt state; use a guard at the call site.
132
- */
133
- export function appendEvent(jobId, event) {
134
- const p = logPath(jobId);
135
- const line = {
136
- ts: new Date().toISOString(),
137
- level: event.level,
138
- event: event.event,
139
- message: event.message,
140
- };
141
- if (event.data !== undefined) {
142
- line.data = event.data;
143
- }
144
- appendFileSync(p, JSON.stringify(line) + '\n', 'utf8');
145
- }
146
- /**
147
- * Atomically write result.json (structured object) and update meta.json status.
148
- * Used by programmatic callers (human, sys) that produce object results.
149
- * The result file's appearance is the completion signal — never inferred from log content.
150
- */
151
- export function writeResult(jobId, result, terminalStatus) {
152
- const dir = jobDir(jobId);
153
- if (!existsSync(dir)) {
154
- throw notFound(`job not found: ${jobId}`, { job_id: jobId });
155
- }
156
- const payload = {
157
- status: terminalStatus,
158
- result,
159
- written_at: new Date().toISOString(),
160
- };
161
- const tmp = join(dir, '.result.tmp');
162
- writeFileSync(tmp, JSON.stringify(payload, null, 2), 'utf8');
163
- renameSync(tmp, resultJsonPath(jobId));
164
- const meta = readMeta(jobId);
165
- meta.status = terminalStatus;
166
- writeMeta(jobId, meta);
167
- }
168
- /**
169
- * Atomically write result.md (YAML frontmatter + markdown body) and update meta.json status.
170
- * Used by `crtr job submit` for agent-driven markdown results.
171
- */
172
- export function writeMarkdownResult(jobId, body, terminalStatus, reason) {
173
- const dir = jobDir(jobId);
174
- if (!existsSync(dir)) {
175
- throw notFound(`job not found: ${jobId}`, { job_id: jobId });
176
- }
177
- const fm = {
178
- status: terminalStatus,
179
- written_at: new Date().toISOString(),
180
- };
181
- if (reason !== undefined && reason !== '') {
182
- fm.reason = reason;
183
- }
184
- const content = `${renderFrontmatter(fm)}${body}`;
185
- const tmp = join(dir, '.result.tmp');
186
- writeFileSync(tmp, content, 'utf8');
187
- renameSync(tmp, resultMdPath(jobId));
188
- const meta = readMeta(jobId);
189
- meta.status = terminalStatus;
190
- writeMeta(jobId, meta);
191
- }
192
- /**
193
- * Render a small fixed-shape frontmatter block. We control writer and reader,
194
- * so a 3-key hand-rolled emitter is plenty — no YAML dep, no escaping surprises.
195
- * Values are plain strings; we double-quote `reason` to survive newlines/colons.
196
- */
197
- function renderFrontmatter(fm) {
198
- const lines = ['---', `status: ${fm.status}`, `written_at: ${fm.written_at}`];
199
- if (fm.reason !== undefined) {
200
- const escaped = fm.reason.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
201
- lines.push(`reason: "${escaped}"`);
202
- }
203
- lines.push('---', '');
204
- return lines.join('\n');
205
- }
206
- /**
207
- * Parse the small fixed-shape frontmatter we emit. Tolerant of trailing
208
- * whitespace; returns `{ frontmatter, body }`. Throws if the document does not
209
- * start with `---\n` or no closing `---` is found.
210
- */
211
- function parseMarkdownResult(raw) {
212
- if (!raw.startsWith('---\n') && !raw.startsWith('---\r\n')) {
213
- throw new Error('result.md missing opening --- delimiter');
214
- }
215
- const afterOpen = raw.indexOf('\n') + 1;
216
- const closeIdx = raw.indexOf('\n---', afterOpen);
217
- if (closeIdx === -1) {
218
- throw new Error('result.md missing closing --- delimiter');
219
- }
220
- const fmBlock = raw.slice(afterOpen, closeIdx);
221
- // Body starts after the closing `---` line.
222
- const afterCloseLine = raw.indexOf('\n', closeIdx + 1);
223
- const body = afterCloseLine === -1 ? '' : raw.slice(afterCloseLine + 1);
224
- const fm = {};
225
- for (const line of fmBlock.split('\n')) {
226
- const m = line.match(/^([a-z_]+):\s*(.*)$/);
227
- if (m === null)
228
- continue;
229
- const key = m[1];
230
- if (m[2] === undefined)
231
- continue;
232
- let val = m[2];
233
- if (val.startsWith('"') && val.endsWith('"') && val.length >= 2) {
234
- val = val.slice(1, -1).replace(/\\n/g, '\n').replace(/\\"/g, '"').replace(/\\\\/g, '\\');
235
- }
236
- if (key === 'status') {
237
- fm.status = val;
238
- }
239
- else if (key === 'written_at') {
240
- fm.written_at = val;
241
- }
242
- else if (key === 'reason') {
243
- fm.reason = val;
244
- }
245
- }
246
- if (fm.status === undefined || fm.written_at === undefined) {
247
- throw new Error('result.md frontmatter missing status or written_at');
248
- }
249
- return { frontmatter: fm, body };
250
- }
251
- export function readResult(jobId, opts = {}) {
252
- const dir = jobDir(jobId);
253
- if (!existsSync(dir)) {
254
- throw notFound(`job not found: ${jobId}`, { job_id: jobId });
255
- }
256
- function parseAt(path) {
257
- const raw = readFileSync(path, 'utf8');
258
- if (path.endsWith('.md')) {
259
- const { frontmatter, body } = parseMarkdownResult(raw);
260
- const out = { status: frontmatter.status, result_md: body };
261
- if (frontmatter.reason !== undefined) {
262
- out.reason = frontmatter.reason;
263
- }
264
- return out;
265
- }
266
- const parsed = JSON.parse(raw);
267
- return { status: parsed.status, result: parsed.result };
268
- }
269
- const existing = existingResultPath(jobId);
270
- if (existing !== null) {
271
- return Promise.resolve(parseAt(existing));
272
- }
273
- if (opts.waitMs === undefined || opts.waitMs <= 0) {
274
- return Promise.resolve({ status: 'timeout' });
275
- }
276
- return new Promise((resolve) => {
277
- let settled = false;
278
- const finish = (response) => {
279
- if (settled)
280
- return;
281
- settled = true;
282
- clearTimeout(timer);
283
- try {
284
- watcher.close();
285
- }
286
- catch { /* noop */ }
287
- resolve(response);
288
- };
289
- const watcher = watch(dir, (_event, name) => {
290
- if (name !== 'result.md' && name !== 'result.json')
291
- return;
292
- const path = existingResultPath(jobId);
293
- if (path !== null) {
294
- finish(parseAt(path));
295
- }
296
- });
297
- const path = existingResultPath(jobId);
298
- if (path !== null) {
299
- finish(parseAt(path));
300
- return;
301
- }
302
- const timer = setTimeout(() => {
303
- finish({ status: 'timeout' });
304
- }, opts.waitMs);
305
- });
306
- }
307
- /**
308
- * Derive job state from meta.json, the result file, and the tail of log.jsonl.
309
- * If a pid is recorded, is not alive, and no result file exists → 'failed'.
310
- */
311
- export function jobStatus(jobId) {
312
- const meta = readMeta(jobId);
313
- const age_s = (Date.now() - new Date(meta.created_at).getTime()) / 1000;
314
- let state = meta.status;
315
- if (state === 'live') {
316
- const existing = existingResultPath(jobId);
317
- if (existing !== null) {
318
- // Result file present but meta not yet updated (rare); trust the file.
319
- try {
320
- if (existing.endsWith('.md')) {
321
- const { frontmatter } = parseMarkdownResult(readFileSync(existing, 'utf8'));
322
- state = frontmatter.status;
323
- }
324
- else {
325
- const r = JSON.parse(readFileSync(existing, 'utf8'));
326
- state = r.status;
327
- }
328
- }
329
- catch { /* leave as live */ }
330
- }
331
- else if (meta.pid !== undefined && !pidAlive(meta.pid)) {
332
- state = 'failed';
333
- }
334
- }
335
- // Tail of log for last_event.
336
- let last_event = null;
337
- const lp = logPath(jobId);
338
- if (existsSync(lp)) {
339
- const lines = readFileSync(lp, 'utf8').trimEnd().split('\n');
340
- for (let i = lines.length - 1; i >= 0; i--) {
341
- const line = lines[i];
342
- if (line === undefined || line.trim() === '')
343
- continue;
344
- try {
345
- const ev = JSON.parse(line);
346
- last_event = { event: ev.event, ts: ev.ts };
347
- break;
348
- }
349
- catch {
350
- continue;
351
- }
352
- }
353
- }
354
- return { state, age_s, last_event };
355
- }
356
- /**
357
- * List all jobs sorted by created_at ascending. Pagination is applied by the
358
- * caller, not here.
359
- */
360
- export function listJobs() {
361
- const root = jobsRoot();
362
- if (!existsSync(root))
363
- return [];
364
- const entries = readdirSync(root);
365
- const jobs = [];
366
- for (const entry of entries) {
367
- const dir = join(root, entry);
368
- try {
369
- if (!statSync(dir).isDirectory())
370
- continue;
371
- const mp = join(dir, 'meta.json');
372
- if (!existsSync(mp))
373
- continue;
374
- const meta = JSON.parse(readFileSync(mp, 'utf8'));
375
- // Derive effective state (result file beats meta.status for live jobs).
376
- let state = meta.status;
377
- if (state === 'live') {
378
- const mdP = join(dir, 'result.md');
379
- const jsP = join(dir, 'result.json');
380
- try {
381
- if (existsSync(mdP)) {
382
- const { frontmatter } = parseMarkdownResult(readFileSync(mdP, 'utf8'));
383
- state = frontmatter.status;
384
- }
385
- else if (existsSync(jsP)) {
386
- const r = JSON.parse(readFileSync(jsP, 'utf8'));
387
- state = r.status;
388
- }
389
- }
390
- catch { /* leave as live */ }
391
- }
392
- jobs.push({ job_id: meta.job_id, kind: meta.kind, state, created_at: meta.created_at });
393
- }
394
- catch {
395
- continue;
396
- }
397
- }
398
- jobs.sort((a, b) => a.created_at.localeCompare(b.created_at));
399
- return jobs;
400
- }
401
- /**
402
- * Read and filter log events. Ordering preserved. sinceTs/untilTs are ISO8601
403
- * strings; minLevel filters by severity rank (inclusive).
404
- */
405
- export function readLog(jobId, opts = {}) {
406
- const dir = jobDir(jobId);
407
- if (!existsSync(dir)) {
408
- throw notFound(`job not found: ${jobId}`, { job_id: jobId });
409
- }
410
- const lp = logPath(jobId);
411
- if (!existsSync(lp))
412
- return [];
413
- const raw = readFileSync(lp, 'utf8');
414
- const results = [];
415
- const minRank = opts.minLevel !== undefined ? LEVEL_RANK[opts.minLevel] : 0;
416
- for (const line of raw.split('\n')) {
417
- if (line.trim() === '')
418
- continue;
419
- let ev;
420
- try {
421
- ev = JSON.parse(line);
422
- }
423
- catch {
424
- continue;
425
- }
426
- if (opts.sinceTs !== undefined && ev.ts < opts.sinceTs)
427
- continue;
428
- if (opts.untilTs !== undefined && ev.ts >= opts.untilTs)
429
- continue;
430
- if (LEVEL_RANK[ev.level] < minRank)
431
- continue;
432
- results.push(ev);
433
- }
434
- return results;
435
- }
436
- /**
437
- * Best-effort cancel: send SIGTERM to the recorded pid (if any), mark meta
438
- * canceled. Success means the signal was delivered, not that execution stopped.
439
- */
440
- export function cancelJob(jobId) {
441
- const meta = readMeta(jobId);
442
- if (meta.status !== 'live') {
443
- // Already terminal — nothing to cancel.
444
- return { canceled: false };
445
- }
446
- let signaled = false;
447
- if (meta.pid !== undefined) {
448
- try {
449
- process.kill(meta.pid, 'SIGTERM');
450
- signaled = true;
451
- }
452
- catch { /* pid gone or unpermitted */ }
453
- }
454
- if (meta.pane_id !== undefined && meta.pane_id !== '') {
455
- const k = spawnSync('tmux', ['kill-pane', '-t', meta.pane_id], { encoding: 'utf8' });
456
- if (k.status === 0)
457
- signaled = true;
458
- }
459
- meta.status = 'canceled';
460
- writeMeta(jobId, meta);
461
- return { canceled: signaled };
462
- }
@@ -1,18 +0,0 @@
1
- /**
2
- * First user message for a spec → plan handoff.
3
- *
4
- * Thin prompt: the worker discovers the full planning workflow by running
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
- */
9
- export declare function planHandoffPrompt(specPath: string, jobId: string): string;
10
- /**
11
- * First user message for a plan → implementation handoff.
12
- */
13
- export declare function implementHandoffPrompt(planPath: string, jobId: string): string;
14
- /**
15
- * First user message for a reviewer agent.
16
- * The reviewer submits via `crtr job submit` rather than `crtr agent submit`.
17
- */
18
- export declare function reviewerHandoffPrompt(artifactPath: string, artifactKind: 'plan' | 'spec', specPath: string | null, jobId: string): string;
@@ -1,153 +0,0 @@
1
- import { planReviewPrompt, specReviewPrompt } from './review.js';
2
- /**
3
- * First user message for a spec → plan handoff.
4
- *
5
- * Thin prompt: the worker discovers the full planning workflow by running
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
- */
10
- export function planHandoffPrompt(specPath, jobId) {
11
- return `You were launched in a new tmux pane to turn an approved spec into a plan.
12
-
13
- **Spec:** ${specPath}
14
-
15
- 1. Run \`crtr agent plan new -h\` to load the planning workflow and output schema.
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 agent plan new\` on stdin.
18
- 4. When done, submit a short markdown report on stdin:
19
-
20
- \`\`\`bash
21
- crtr job submit ${jobId} <<'MD'
22
- Plan saved at <path>. <one-line summary of the plan's shape>.
23
- MD
24
- \`\`\`
25
-
26
- If you cannot complete the plan, submit a failure with a reason:
27
-
28
- \`\`\`bash
29
- crtr job submit ${jobId} --status failed --reason "<why>"
30
- \`\`\`
31
-
32
- Begin now.`;
33
- }
34
- /**
35
- * First user message for a plan → implementation handoff.
36
- */
37
- export function implementHandoffPrompt(planPath, jobId) {
38
- return `You are executing an approved plan. For small plans, implement directly.
39
- For plans with parallelizable scale, orchestrate parallel subagents and
40
- coordinate them — don't write all the code yourself when the plan is
41
- structured to fan out.
42
-
43
- **Plan to implement:** ${planPath}
44
-
45
- ## Phase 1: Read
46
-
47
- 1. Read the plan end-to-end. If it references a spec, read that too.
48
- 2. Read the files the plan names under "Files to modify / create" and
49
- "Existing utilities to reuse" to ground yourself in current code.
50
- 3. If the plan has task blocks with dependencies, extract the task list,
51
- dependency graph, and integration contracts.
52
-
53
- ## Phase 2: Scale
54
-
55
- Count the plan's **independent task groups** (tasks with no mutual
56
- dependencies that can run in parallel). Pick the strategy:
57
-
58
- | Independent groups | Files touched | Strategy |
59
- |-------------------|---------------|----------|
60
- | 1 | 1–3 | **Implement directly.** Skip Phases 3–5; just execute the plan and go to Phase 6. |
61
- | 1–2 | 3–5 | Implement directly, or single subagent if you want parallelism with verification |
62
- | 2–4 | 5–15 | **2 parallel subagents** |
63
- | 4–8 | 10–30 | **3 parallel subagents** |
64
- | 8+ | 25+ | **4 parallel subagents** (cap) |
65
-
66
- Use the higher column to pick the tier. Never spawn more subagents than
67
- there are independent groups. **Bump one tier** if: tight cross-group
68
- interface coordination, mixed languages/frameworks, or both infra +
69
- application layers change.
70
-
71
- ## Phase 3: Partition
72
-
73
- Group tasks into **disjoint sets** where:
74
-
75
- - Each group owns clear file boundaries — **no two groups edit the same files**.
76
- - Within a group, tasks are sequenced for one subagent to execute in order.
77
- - Across groups, dependencies become layers: dependent groups run *after*
78
- their predecessors complete.
79
-
80
- If two tasks must touch the same file, sequence them in the same group.
81
-
82
- ## Phase 4: Dispatch
83
-
84
- For each task group in the current dependency layer, dispatch a subagent
85
- in parallel via the Task tool. Use \`general-purpose\` by default; use
86
- \`devcore:programmer\` if the project has devcore installed.
87
-
88
- **Each subagent's prompt must include:**
89
- - The specific tasks from the plan it owns (paste verbatim)
90
- - The plan path: \`${planPath}\`
91
- - The spec path if one exists
92
- - The exact file ownership for this group
93
- - Integration contracts it produces or consumes (types, APIs, shapes)
94
- - **Constraint: do NOT run tests or typechecks** — other subagents may be
95
- mid-edit. The orchestrator runs verification at layer boundaries.
96
- - Instruction to return when its tasks are complete, surfacing blockers
97
-
98
- ## Phase 5: Coordinate
99
-
100
- Wait for all subagents in the current layer. Then:
101
-
102
- - If any reports a blocker, resolve it: fix yourself, adjust scope with
103
- the user, or re-dispatch a corrected task. Don't proceed past the blocker.
104
- - Run the plan's verification for the just-finished layer (tests, manual
105
- checks). Fix any failures before dispatching the next layer.
106
- - Dispatch the next layer.
107
-
108
- **Stay in the coordinator role.** Don't implement tasks yourself unless a
109
- subagent returns blocked work and the fix is small enough that re-dispatch
110
- would be slower.
111
-
112
- ## Phase 6: Report and submit
113
-
114
- When all tasks complete and verification passes, submit a markdown report on stdin:
115
-
116
- \`\`\`bash
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
122
- \`\`\`
123
-
124
- If implementation fails, submit a failure with a reason:
125
- \`\`\`bash
126
- crtr job submit ${jobId} --status failed --reason "<why>"
127
- \`\`\`
128
-
129
- ## Guardrails (apply to you AND your subagents)
130
-
131
- - **No redesign.** If the plan is wrong, surface the issue — do not
132
- silently substitute your own approach.
133
- - **No scope expansion.** No drive-by refactors, no "while I'm here"
134
- cleanup, no new abstractions the plan didn't request.
135
- - **Honor conventions.** Match each file's existing style, naming, and
136
- patterns. Use the utilities the plan named.
137
- - **Commit only if the user asks.**
138
-
139
- Begin by reading the plan.`;
140
- }
141
- /**
142
- * First user message for a reviewer agent.
143
- * The reviewer submits via `crtr job submit` rather than `crtr agent submit`.
144
- */
145
- export function reviewerHandoffPrompt(artifactPath, artifactKind, specPath, jobId) {
146
- const reviewBody = artifactKind === 'spec'
147
- ? specReviewPrompt(artifactPath)
148
- : planReviewPrompt(artifactPath, specPath);
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\`\`\``);
150
- return `${patched}
151
-
152
- After calling \`crtr job submit\`, your turn ends and the pane closes itself. Do NOT chat or summarize after submission.`;
153
- }
@@ -1,8 +0,0 @@
1
- /**
2
- * First user message for a reproduction-only debug handoff.
3
- *
4
- * The agent's sole job is ONE failing integration test that reproduces the
5
- * reported bug. It never fixes the bug. Symmetric with the agent.ts handoff
6
- * builders: thin prompt, exact submit contract, turn ends after submit.
7
- */
8
- export declare function reproHandoffPrompt(issue: string, jobId: string): string;
@@ -1,44 +0,0 @@
1
- /**
2
- * First user message for a reproduction-only debug handoff.
3
- *
4
- * The agent's sole job is ONE failing integration test that reproduces the
5
- * reported bug. It never fixes the bug. Symmetric with the agent.ts handoff
6
- * builders: thin prompt, exact submit contract, turn ends after submit.
7
- */
8
- export function reproHandoffPrompt(issue, jobId) {
9
- return `You were spawned solely to write ONE integration test that fails *because of this bug*:
10
-
11
- ${issue}
12
-
13
- Rules — follow exactly:
14
-
15
- - Do NOT fix the bug. Do NOT modify product code to make a test pass. Your only output is a test.
16
- - The test must fail against the current code, and the failure must BE the reported bug — not an import error, a typo, or an unrelated assertion. Run it; paste the real failing output; confirm the failure mode matches the issue.
17
- - Prefer an integration test against real dependencies. Mocking away the broken component is theater and does NOT count as reproduction.
18
- - If you cannot produce a faithful failing test, GIVE UP. Never weaken assertions, hardcode expected values, or fabricate a clean-looking run — a tautological or over-mocked test is worse than none.
19
-
20
- Submit exactly one of the following via \`crtr job submit\`, then your turn ends — do not chat:
21
-
22
- Reproduced (markdown body on stdin):
23
- \`\`\`bash
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
36
- \`\`\`
37
-
38
- Gave up (no faithful repro achievable):
39
- \`\`\`bash
40
- crtr job submit ${jobId} --status failed --reason "<why a faithful repro was not achievable>"
41
- \`\`\`
42
-
43
- Begin now.`;
44
- }