@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.
- package/bin/crtrd +2 -0
- package/dist/builtin-personas/design/base.md +9 -0
- package/dist/builtin-personas/design/orchestrator.md +10 -0
- package/dist/builtin-personas/developer/base.md +9 -0
- package/dist/builtin-personas/developer/orchestrator.md +12 -0
- package/dist/builtin-personas/explore/base.md +9 -0
- package/dist/builtin-personas/explore/orchestrator.md +9 -0
- package/dist/builtin-personas/general/base.md +5 -0
- package/dist/builtin-personas/general/orchestrator.md +7 -0
- package/dist/builtin-personas/orchestration-kernel.md +71 -0
- package/dist/builtin-personas/plan/base.md +7 -0
- package/dist/builtin-personas/plan/orchestrator.md +12 -0
- package/dist/builtin-personas/review/base.md +7 -0
- package/dist/builtin-personas/review/orchestrator.md +9 -0
- package/dist/builtin-personas/runtime-base.md +39 -0
- package/dist/builtin-personas/spec/base.md +7 -0
- package/dist/builtin-personas/spec/orchestrator.md +10 -0
- package/dist/builtin-skills/skills/design/SKILL.md +51 -0
- package/dist/builtin-skills/skills/development/SKILL.md +109 -0
- package/dist/builtin-skills/skills/planning/SKILL.md +59 -0
- package/dist/builtin-skills/skills/spec/SKILL.md +83 -0
- package/dist/cli.js +25 -27
- package/dist/commands/{job.d.ts → attention.d.ts} +1 -1
- package/dist/commands/attention.js +152 -0
- package/dist/commands/canvas.d.ts +2 -0
- package/dist/commands/canvas.js +35 -0
- package/dist/commands/{agent.d.ts → daemon.d.ts} +1 -1
- package/dist/commands/daemon.js +111 -0
- package/dist/commands/dashboard.d.ts +2 -0
- package/dist/commands/dashboard.js +65 -0
- package/dist/commands/human/prompts.d.ts +5 -0
- package/dist/commands/human/prompts.js +269 -0
- package/dist/commands/human/queue.d.ts +3 -0
- package/dist/commands/human/queue.js +133 -0
- package/dist/commands/human/shared.d.ts +43 -0
- package/dist/commands/human/shared.js +107 -0
- package/dist/commands/human.js +15 -427
- package/dist/commands/node.d.ts +2 -0
- package/dist/commands/node.js +354 -0
- package/dist/commands/pkg/market-inspect.d.ts +1 -0
- package/dist/commands/pkg/market-inspect.js +157 -0
- package/dist/commands/pkg/market-manage.d.ts +1 -0
- package/dist/commands/pkg/market-manage.js +316 -0
- package/dist/commands/pkg/market.d.ts +1 -0
- package/dist/commands/pkg/market.js +16 -0
- package/dist/commands/pkg/plugin-inspect.d.ts +1 -0
- package/dist/commands/pkg/plugin-inspect.js +142 -0
- package/dist/commands/pkg/plugin-manage.d.ts +1 -0
- package/dist/commands/pkg/plugin-manage.js +294 -0
- package/dist/commands/pkg/plugin.d.ts +1 -0
- package/dist/commands/pkg/plugin.js +16 -0
- package/dist/commands/pkg/shared.d.ts +5 -0
- package/dist/commands/pkg/shared.js +61 -0
- package/dist/commands/pkg.js +8 -1004
- package/dist/commands/push.d.ts +3 -0
- package/dist/commands/push.js +159 -0
- package/dist/commands/revive.d.ts +2 -0
- package/dist/commands/revive.js +64 -0
- package/dist/commands/skill/author.d.ts +3 -0
- package/dist/commands/skill/author.js +147 -0
- package/dist/commands/skill/find.d.ts +4 -0
- package/dist/commands/skill/find.js +254 -0
- package/dist/commands/skill/read.d.ts +1 -0
- package/dist/commands/skill/read.js +89 -0
- package/dist/commands/skill/shared.d.ts +19 -0
- package/dist/commands/skill/shared.js +207 -0
- package/dist/commands/skill/state.d.ts +3 -0
- package/dist/commands/skill/state.js +69 -0
- package/dist/commands/skill.js +12 -681
- package/dist/commands/sys/config.d.ts +1 -0
- package/dist/commands/sys/config.js +186 -0
- package/dist/commands/sys/doctor.d.ts +1 -0
- package/dist/commands/sys/doctor.js +369 -0
- package/dist/commands/sys/shared.d.ts +3 -0
- package/dist/commands/sys/shared.js +24 -0
- package/dist/commands/sys/update.d.ts +2 -0
- package/dist/commands/sys/update.js +114 -0
- package/dist/commands/sys.js +9 -694
- package/dist/core/__tests__/argv-parser.test.js +19 -1
- package/dist/core/__tests__/canvas-inbox-watcher.test.js +100 -0
- package/dist/core/__tests__/canvas.test.js +154 -0
- package/dist/core/__tests__/reset.test.js +105 -0
- package/dist/core/__tests__/resolver.test.js +69 -1
- package/dist/core/__tests__/unknown-path.test.d.ts +1 -0
- package/dist/core/__tests__/unknown-path.test.js +52 -0
- package/dist/core/bootstrap.d.ts +2 -0
- package/dist/core/bootstrap.js +66 -0
- package/dist/core/canvas/attention.d.ts +24 -0
- package/dist/core/canvas/attention.js +94 -0
- package/dist/core/canvas/canvas.d.ts +40 -0
- package/dist/core/canvas/canvas.js +210 -0
- package/dist/core/canvas/db.d.ts +7 -0
- package/dist/core/canvas/db.js +61 -0
- package/dist/core/canvas/index.d.ts +4 -0
- package/dist/core/canvas/index.js +6 -0
- package/dist/core/canvas/paths.d.ts +16 -0
- package/dist/core/canvas/paths.js +62 -0
- package/dist/core/canvas/render.d.ts +30 -0
- package/dist/core/canvas/render.js +186 -0
- package/dist/core/canvas/types.d.ts +87 -0
- package/dist/core/canvas/types.js +8 -0
- package/dist/core/command.d.ts +63 -2
- package/dist/core/command.js +97 -24
- package/dist/core/feed/feed.d.ts +43 -0
- package/dist/core/feed/feed.js +116 -0
- package/dist/core/feed/inbox.d.ts +50 -0
- package/dist/core/feed/inbox.js +124 -0
- package/dist/core/frontmatter.d.ts +10 -0
- package/dist/core/frontmatter.js +24 -9
- package/dist/core/help.d.ts +39 -8
- package/dist/core/help.js +69 -35
- package/dist/core/io.d.ts +15 -1
- package/dist/core/io.js +56 -6
- package/dist/core/personas/index.d.ts +12 -0
- package/dist/core/personas/index.js +10 -0
- package/dist/core/personas/loader.d.ts +44 -0
- package/dist/core/personas/loader.js +157 -0
- package/dist/core/personas/resolve.d.ts +36 -0
- package/dist/core/personas/resolve.js +110 -0
- package/dist/core/render.d.ts +11 -0
- package/dist/core/render.js +126 -0
- package/dist/core/resolver.d.ts +10 -0
- package/dist/core/resolver.js +160 -2
- package/dist/core/runtime/front-door.d.ts +10 -0
- package/dist/core/runtime/front-door.js +97 -0
- package/dist/core/runtime/kickoff.d.ts +23 -0
- package/dist/core/runtime/kickoff.js +134 -0
- package/dist/core/runtime/launch.d.ts +34 -0
- package/dist/core/runtime/launch.js +85 -0
- package/dist/core/runtime/nodes.d.ts +38 -0
- package/dist/core/runtime/nodes.js +95 -0
- package/dist/core/runtime/presence.d.ts +38 -0
- package/dist/core/runtime/presence.js +152 -0
- package/dist/core/runtime/promote.d.ts +30 -0
- package/dist/core/runtime/promote.js +105 -0
- package/dist/core/runtime/reset.d.ts +13 -0
- package/dist/core/runtime/reset.js +97 -0
- package/dist/core/runtime/revive.d.ts +26 -0
- package/dist/core/runtime/revive.js +89 -0
- package/dist/core/runtime/roadmap.d.ts +12 -0
- package/dist/core/runtime/roadmap.js +52 -0
- package/dist/core/runtime/spawn.d.ts +33 -0
- package/dist/core/runtime/spawn.js +118 -0
- package/dist/core/runtime/stop-guard.d.ts +18 -0
- package/dist/core/runtime/stop-guard.js +33 -0
- package/dist/core/runtime/tmux.d.ts +88 -0
- package/dist/core/runtime/tmux.js +198 -0
- package/dist/core/spawn.d.ts +17 -80
- package/dist/core/spawn.js +15 -219
- package/dist/daemon/crtrd-cli.d.ts +1 -0
- package/dist/daemon/crtrd-cli.js +4 -0
- package/dist/daemon/crtrd.d.ts +20 -0
- package/dist/daemon/crtrd.js +200 -0
- package/dist/daemon/manage.d.ts +17 -0
- package/dist/daemon/manage.js +57 -0
- package/dist/pi-extensions/canvas-inbox-watcher.d.ts +16 -0
- package/dist/pi-extensions/canvas-inbox-watcher.js +229 -0
- package/dist/pi-extensions/canvas-nav.d.ts +32 -0
- package/dist/pi-extensions/canvas-nav.js +536 -0
- package/dist/pi-extensions/canvas-stophook.d.ts +17 -0
- package/dist/pi-extensions/canvas-stophook.js +373 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.js +3 -0
- package/package.json +6 -5
- package/dist/commands/agent.js +0 -384
- package/dist/commands/debug.d.ts +0 -3
- package/dist/commands/debug.js +0 -179
- package/dist/commands/job.js +0 -344
- package/dist/commands/plan.d.ts +0 -4
- package/dist/commands/plan.js +0 -309
- package/dist/commands/spec.d.ts +0 -3
- package/dist/commands/spec.js +0 -286
- package/dist/core/__tests__/flow-leaves.test.js +0 -248
- package/dist/core/__tests__/job.test.js +0 -310
- package/dist/core/__tests__/jobs.test.js +0 -66
- package/dist/core/jobs.d.ts +0 -101
- package/dist/core/jobs.js +0 -462
- package/dist/prompts/agent.d.ts +0 -18
- package/dist/prompts/agent.js +0 -153
- package/dist/prompts/debug.d.ts +0 -8
- package/dist/prompts/debug.js +0 -44
- /package/dist/core/__tests__/{flow-leaves.test.d.ts → canvas-inbox-watcher.test.d.ts} +0 -0
- /package/dist/core/__tests__/{job.test.d.ts → canvas.test.d.ts} +0 -0
- /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
|
-
}
|
package/dist/prompts/agent.d.ts
DELETED
|
@@ -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;
|
package/dist/prompts/agent.js
DELETED
|
@@ -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
|
-
}
|
package/dist/prompts/debug.d.ts
DELETED
|
@@ -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;
|
package/dist/prompts/debug.js
DELETED
|
@@ -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
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|