muse-crew 0.17.2 → 0.17.3
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/API.md +11 -0
- package/docs/decisions/composition-machinery.md +180 -0
- package/docs/decisions/publish-path.md +6 -0
- package/docs/decisions/workflow-core.md +5 -4
- package/lib/AGENTS.md +2 -1
- package/lib/bugfix/phases/build.js +168 -0
- package/lib/bugfix/phases/capture.js +170 -0
- package/lib/bugfix/phases/integrate.js +165 -0
- package/lib/bugfix/phases/map.js +129 -0
- package/lib/bugfix/phases/publish.js +592 -0
- package/lib/bugfix/phases/qa.js +356 -0
- package/lib/bugfix/phases/reproduce.js +254 -0
- package/lib/bugfix/phases/review.js +292 -0
- package/lib/bugfix/phases/triage.js +69 -0
- package/lib/chore/CONTRACT.md +181 -0
- package/lib/chore/DISPOSITION.md +98 -0
- package/lib/chore/extract.js +204 -0
- package/lib/chore/phase-lib.js +885 -0
- package/lib/chore/phases/build.js +110 -0
- package/lib/chore/phases/capture.js +82 -0
- package/lib/chore/phases/integrate.js +109 -0
- package/lib/chore/phases/map.js +81 -0
- package/lib/chore/phases/publish.js +543 -0
- package/lib/chore/phases/review.js +255 -0
- package/lib/chore/phases/triage.js +57 -0
- package/lib/chore/prompts/evidence-gatherer.js +41 -0
- package/lib/chore/prompts/evidence-gatherer.schema.json +1 -0
- package/lib/chore/prompts/tool-check.js +15 -0
- package/lib/chore/prompts/trailers.js +56 -0
- package/lib/chore/prompts/verdict-reask.js +28 -0
- package/lib/chore/prompts/verdict-reask.schema.json +1 -0
- package/lib/chore/prompts/work-agent.js +52 -0
- package/lib/chore/prompts/work-agent.schema.json +1 -0
- package/lib/chore/spawn-keys.js +44 -0
- package/lib/chore/spawn-vocab.js +87 -0
- package/lib/chore-run.js +538 -0
- package/lib/chore-tick.js +289 -0
- package/lib/crew-api.js +273 -0
- package/lib/crew-dispatch-worker.js +27 -7
- package/lib/crew-release.sh +7 -2
- package/lib/extract.js +252 -0
- package/lib/prompts/tool-check.js +18 -0
- package/lib/prompts/trailers.js +59 -0
- package/lib/prompts/verdict-reask.js +31 -0
- package/lib/prompts/verdict-reask.schema.json +1 -0
- package/lib/prompts/work-agent.js +56 -0
- package/lib/prompts/work-agent.schema.json +1 -0
- package/lib/reap-spawns.js +407 -0
- package/lib/schema.sql +12 -1
- package/lib/spawn-keys.js +47 -0
- package/lib/spawn-step.js +572 -0
- package/lib/standard/phases/build.js +120 -0
- package/lib/standard/phases/capture.js +163 -0
- package/lib/standard/phases/integrate.js +172 -0
- package/lib/standard/phases/map.js +119 -0
- package/lib/standard/phases/publish.js +565 -0
- package/lib/standard/phases/qa.js +399 -0
- package/lib/standard/phases/review.js +281 -0
- package/lib/standard/phases/triage.js +64 -0
- package/lib/test-detached-integrate.sh +47 -0
- package/lib/workflow-driver.js +605 -0
- package/lib/workflow-lib.js +1012 -0
- package/lib/workflow-spec.js +187 -0
- package/lib/worktree-lifecycle.sh +55 -3
- package/package.json +1 -1
- package/seed/cron-body-template.md +61 -9
- package/workflows/bugfix.js +17 -17
- package/workflows/chore.js +16 -16
- package/workflows/docs.js +14 -11
- package/workflows/standard.js +16 -16
package/lib/extract.js
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
// lib/extract.js — deterministic report extractors for the worker-layer
|
|
2
|
+
// phase modules (sandbox exit, Phase C Piece 2).
|
|
3
|
+
//
|
|
4
|
+
// Import-safe: pure functions, no I/O, no clock, no randomness, no
|
|
5
|
+
// top-level await (require(esm)-safe).
|
|
6
|
+
//
|
|
7
|
+
// Generalized from lib/chore/extract.js (which stays live and untouched):
|
|
8
|
+
// every function here is workflow-neutral. The four dead-in-chore helpers
|
|
9
|
+
// (parseMergeRecord, decidePublishLockPath, decideIntegrateRetry,
|
|
10
|
+
// hydrateReleaseDecision) are NOT carried — they are dead in chore and in
|
|
11
|
+
// standard (defined but never called in workflows/standard.js).
|
|
12
|
+
// extractContentFindings and classifyContentFinding are ported from
|
|
13
|
+
// workflows/standard.js (QA content-attribution machinery).
|
|
14
|
+
|
|
15
|
+
export function extractVerdict(workerText) {
|
|
16
|
+
// The verdict is the LAST VERDICT: PASS/FAIL in the report (contract: end
|
|
17
|
+
// your report with the verdict). This ignores literal VERDICT strings echoed
|
|
18
|
+
// from the worker's instructions (which contain quoted examples). Fail
|
|
19
|
+
// closed if: no verdict found, the last verdict is not in the trailing 100
|
|
20
|
+
// chars (verdict must be at the end), or conflicting verdicts appear in the
|
|
21
|
+
// trailing 200 chars. Word boundary prevents "PASSING" matching as PASS.
|
|
22
|
+
var text = workerText || "";
|
|
23
|
+
var regex = /VERDICT:\s*(PASS|FAIL)\b/gi;
|
|
24
|
+
var matches = [];
|
|
25
|
+
var m;
|
|
26
|
+
while ((m = regex.exec(text)) !== null) {
|
|
27
|
+
matches.push({ value: /FAIL/i.test(m[0]) ? "FAIL" : "PASS", index: m.index });
|
|
28
|
+
}
|
|
29
|
+
if (matches.length === 0) return { ok: false, count: 0 };
|
|
30
|
+
var last = matches[matches.length - 1];
|
|
31
|
+
if (last.index < text.length - 100) return { ok: false, count: matches.length };
|
|
32
|
+
var trailing = matches.filter(function (x) { return x.index >= text.length - 200; });
|
|
33
|
+
var uniq = trailing.map(function (x) { return x.value; }).filter(function (v, i, a) { return a.indexOf(v) === i; });
|
|
34
|
+
if (uniq.length !== 1) return { ok: false, count: matches.length };
|
|
35
|
+
return { ok: true, passed: last.value === "PASS" };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function extractMarkerLines(workerText) {
|
|
39
|
+
var lines = (workerText || "").split("\n");
|
|
40
|
+
var markers = [];
|
|
41
|
+
for (var i = 0; i < lines.length; i++) {
|
|
42
|
+
var line = lines[i].trim();
|
|
43
|
+
if (/^(repo_diff:|release:|version_bump:|VERDICT:|TARGET_VERSION=|published:|experiential:|layer:|capture_targets:|terminal_targets:|worktree:)/i.test(line)) {
|
|
44
|
+
markers.push(line);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return markers.join("\n");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function parseToolSignals(text) {
|
|
51
|
+
var t = String(text || "");
|
|
52
|
+
var out = { artifactTools: "unknown", shellTransport: "unknown" };
|
|
53
|
+
var am = t.match(/^artifact_tools:\s*(ok|missing)\s*$/m);
|
|
54
|
+
if (am) out.artifactTools = am[1];
|
|
55
|
+
var sm = t.match(/^shell_transport:\s*(ok|unavailable)\s*$/m);
|
|
56
|
+
if (sm) out.shellTransport = sm[1];
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function parseClassifyFerry(bsResult) {
|
|
61
|
+
var bsStdout = (bsResult && typeof bsResult.stdout === "string") ? bsResult.stdout : null;
|
|
62
|
+
var bsStderr = (bsResult && typeof bsResult.stderr === "string") ? bsResult.stderr : null;
|
|
63
|
+
if (bsStdout === null || bsStderr === null) {
|
|
64
|
+
return { state: null, diagField: null, diagText: "",
|
|
65
|
+
failReason: "classifier return fields missing or non-string (stdout and stderr must both be strings)" };
|
|
66
|
+
}
|
|
67
|
+
// State channel: stdout ONLY. A BRANCH_STATE match on stderr is never
|
|
68
|
+
// trusted — workers merge streams constantly, and trusting a channel
|
|
69
|
+
// swap as truth is the wrong shape. Search, never anchor (blocker 11):
|
|
70
|
+
// the ferry labels output ("stdout: BRANCH_STATE: has-work"). Collect
|
|
71
|
+
// ALL matches and dedupe normalized values: exactly one distinct value
|
|
72
|
+
// is truth; zero or two-or-more is UNKNOWN — alternation order never
|
|
73
|
+
// adjudicates.
|
|
74
|
+
var bsRe = /\bBRANCH_STATE:\s*(has-work|already-merged:[0-9a-f]{40}|empty-no-work)(?![\w-])/gi;
|
|
75
|
+
var bsSeen = {};
|
|
76
|
+
var bsM;
|
|
77
|
+
while ((bsM = bsRe.exec(bsStdout)) !== null) {
|
|
78
|
+
bsSeen[bsM[1].toLowerCase()] = true;
|
|
79
|
+
}
|
|
80
|
+
var bsStates = Object.keys(bsSeen);
|
|
81
|
+
if (bsStates.length !== 1) {
|
|
82
|
+
return { state: null, diagField: null, diagText: "",
|
|
83
|
+
failReason: bsStates.length === 0
|
|
84
|
+
? "no BRANCH_STATE match in the stdout field"
|
|
85
|
+
: "ambiguous BRANCH_STATE values in stdout (" + bsStates.length + " distinct)" };
|
|
86
|
+
}
|
|
87
|
+
var bsState = bsStates[0];
|
|
88
|
+
// DIAG pin: search both fields, stderr first. Keep the DIAG: prefix — a
|
|
89
|
+
// prose mention without it must not satisfy the pin.
|
|
90
|
+
var bsDiagRe = /DIAG:\s*classify-branch\s+records=\d+\s+resolvable=\d+/;
|
|
91
|
+
var bsDiagField = bsDiagRe.test(bsStderr) ? "stderr" : (bsDiagRe.test(bsStdout) ? "stdout" : null);
|
|
92
|
+
var bsDiagText = "";
|
|
93
|
+
if (bsDiagField !== null) {
|
|
94
|
+
bsDiagText = bsDiagRe.exec(bsDiagField === "stderr" ? bsStderr : bsStdout)[0];
|
|
95
|
+
}
|
|
96
|
+
if (bsState.indexOf("already-merged:") === 0 && bsDiagField === null) {
|
|
97
|
+
// already-merged's sha flows into Cass's `git diff <sha>^1 <sha>` — a
|
|
98
|
+
// hallucinated-but-well-formed sha is a false-PASS vector. The script
|
|
99
|
+
// emits exactly one DIAG on every path that emits a BRANCH_STATE, so a
|
|
100
|
+
// missing pin means stderr wasn't faithfully ferried: UNKNOWN.
|
|
101
|
+
return { state: null, diagField: null, diagText: "",
|
|
102
|
+
failReason: "already-merged without the DIAG pin — stderr not faithfully ferried" };
|
|
103
|
+
}
|
|
104
|
+
return { state: bsState, diagField: bsDiagField, diagText: bsDiagText, failReason: "" };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function parseVersionFerry(vtResult) {
|
|
108
|
+
var vtStdout = (vtResult && typeof vtResult.stdout === "string") ? vtResult.stdout : null;
|
|
109
|
+
if (vtStdout === null) {
|
|
110
|
+
return { versionState: null,
|
|
111
|
+
failReason: "version-check return field missing or non-string (stdout must be a string)" };
|
|
112
|
+
}
|
|
113
|
+
// Search, never anchor (blocker 11): the ferry labels output
|
|
114
|
+
// ("stdout: VERSION_CLEAN"). Collect ALL matches and dedupe normalized
|
|
115
|
+
// values: exactly one distinct value is truth; zero or two-or-more is
|
|
116
|
+
// UNKNOWN — alternation order never adjudicates.
|
|
117
|
+
var vtRe = /\bVERSION_(TOUCHED|CLEAN)(?![\w-])/gi;
|
|
118
|
+
var vtSeen = {};
|
|
119
|
+
var vtM;
|
|
120
|
+
while ((vtM = vtRe.exec(vtStdout)) !== null) {
|
|
121
|
+
vtSeen[vtM[1].toUpperCase()] = true;
|
|
122
|
+
}
|
|
123
|
+
var vtStates = Object.keys(vtSeen);
|
|
124
|
+
if (vtStates.length !== 1) {
|
|
125
|
+
return { versionState: null,
|
|
126
|
+
failReason: vtStates.length === 0
|
|
127
|
+
? "no VERSION_(TOUCHED|CLEAN) match in the stdout field"
|
|
128
|
+
: "ambiguous VERSION values in stdout (" + vtStates.length + " distinct)" };
|
|
129
|
+
}
|
|
130
|
+
return { versionState: vtStates[0] === "CLEAN" ? "clean" : "touched", failReason: "" };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function extractReleaseDecision(workerText) {
|
|
134
|
+
var t = workerText || "";
|
|
135
|
+
var r = /release:\s*(yes|no)\b/i.exec(t);
|
|
136
|
+
if (!r) return null;
|
|
137
|
+
var b = /version_bump:\s*(patch|minor|major)\b/i.exec(t);
|
|
138
|
+
var rel = r[1].toLowerCase();
|
|
139
|
+
if (rel === "yes" && !b) return null;
|
|
140
|
+
return { release: rel, version_bump: b ? b[1].toLowerCase() : null };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function extractWorktree(workerText) {
|
|
144
|
+
var lines = (workerText || "").split("\n");
|
|
145
|
+
var found = null;
|
|
146
|
+
for (var i = 0; i < lines.length; i++) {
|
|
147
|
+
var line = lines[i].trim();
|
|
148
|
+
var m = /^worktree:\s*(\S.*)$/i.exec(line);
|
|
149
|
+
if (m) found = m[1].trim();
|
|
150
|
+
}
|
|
151
|
+
if (!found) return { ok: false };
|
|
152
|
+
// Normalize trailing slashes: ".../<task_id>/" and ".../<task_id>"
|
|
153
|
+
// name the same directory. Compare locations, not spellings — a
|
|
154
|
+
// builder that emits the trailing slash still worked in the right
|
|
155
|
+
// place. (Canary 2026-09-11: an exact comparison rejected a correct
|
|
156
|
+
// declaration over one trailing slash.)
|
|
157
|
+
return { ok: true, path: found.replace(/\/+$/, "") };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function extractExperiential(workerText) {
|
|
161
|
+
// Reads Sage's experiential: yes|no marker line. Missing or malformed
|
|
162
|
+
// fails closed to null — the flag is opt-in; null is treated as
|
|
163
|
+
// non-experiential (never silently parks a task on a garbled line).
|
|
164
|
+
var t = workerText || "";
|
|
165
|
+
var r = /experiential:\s*(yes|no)\b/i.exec(t);
|
|
166
|
+
if (!r) return null;
|
|
167
|
+
return r[1].toLowerCase() === "yes";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function extractLayer(workerText) {
|
|
171
|
+
// Reads Sage's layer: artifact|engine|docs marker line (bugfix Triage).
|
|
172
|
+
// Missing or malformed degrades to null (unknown) — callers degrade to
|
|
173
|
+
// "artifact" (today's single-strategy behavior), never park a task on a
|
|
174
|
+
// garbled line.
|
|
175
|
+
var t = workerText || "";
|
|
176
|
+
var r = /layer:\s*(artifact|engine|docs)\b/i.exec(t);
|
|
177
|
+
if (!r) return null;
|
|
178
|
+
return r[1].toLowerCase();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function bumpVersion(base, scope) {
|
|
182
|
+
var p = String(base).trim().split(".").map(function (x) { return parseInt(x, 10) || 0; });
|
|
183
|
+
if (scope === "major") return (p[0] + 1) + ".0.0";
|
|
184
|
+
if (scope === "minor") return p[0] + "." + (p[1] + 1) + ".0";
|
|
185
|
+
return p[0] + "." + p[1] + "." + (p[2] + 1); // patch (default)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function releaseDecisionText(releaseDecision) {
|
|
189
|
+
// releaseDecision is an explicit parameter (import-safe). Logic
|
|
190
|
+
// otherwise verbatim from the workflow sources.
|
|
191
|
+
if (!releaseDecision) return "no machine-readable release decision from the Build report";
|
|
192
|
+
return "release: " + releaseDecision.release + (releaseDecision.version_bump ? ", version_bump: " + releaseDecision.version_bump : " (no version_bump line)");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// extractRepoDiffNone — the Build report's plain `repo_diff: none` (no repo
|
|
196
|
+
// change) claim. Pure, no I/O.
|
|
197
|
+
export function extractRepoDiffNone(workerText) {
|
|
198
|
+
return /repo_diff:\s*none/im.test(workerText || "");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// extractPublishSkipped — the PUBLISH_SKIPPED=<reason> marker the npm publish
|
|
202
|
+
// script emits and the Publish agent pastes verbatim into its report.
|
|
203
|
+
// Returns the reason string or null.
|
|
204
|
+
export function extractPublishSkipped(workerText) {
|
|
205
|
+
var m = /^PUBLISH_SKIPPED=(no-lock-held|no-npm-publish)$/m.exec(workerText || "");
|
|
206
|
+
return m ? m[1] : null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// extractContentFindings — ported from workflows/standard.js (QA
|
|
210
|
+
// content-attribution). The block is the LAST "CONTENT-FINDINGS:" line;
|
|
211
|
+
// the JSON array follows on the same line. Fail closed (ok:false) when
|
|
212
|
+
// missing or malformed — the caller blocks the phase for retry; unknown
|
|
213
|
+
// attribution must never degrade to "no findings" (the prose grounds stay
|
|
214
|
+
// preserved in the session notes).
|
|
215
|
+
export function extractContentFindings(workerText) {
|
|
216
|
+
var text = workerText || "";
|
|
217
|
+
var regex = /^CONTENT-FINDINGS:\s*(\[.*\])\s*$/gim;
|
|
218
|
+
var matches = [];
|
|
219
|
+
var m;
|
|
220
|
+
while ((m = regex.exec(text)) !== null) {
|
|
221
|
+
matches.push(m[1]);
|
|
222
|
+
}
|
|
223
|
+
if (matches.length === 0) return { ok: false, reason: "no CONTENT-FINDINGS block" };
|
|
224
|
+
var findings;
|
|
225
|
+
try {
|
|
226
|
+
findings = JSON.parse(matches[matches.length - 1]);
|
|
227
|
+
} catch (e) {
|
|
228
|
+
return { ok: false, reason: "CONTENT-FINDINGS block is not valid JSON" };
|
|
229
|
+
}
|
|
230
|
+
if (!Array.isArray(findings)) return { ok: false, reason: "CONTENT-FINDINGS is not an array" };
|
|
231
|
+
for (var i = 0; i < findings.length; i++) {
|
|
232
|
+
var f = findings[i];
|
|
233
|
+
if (!f || typeof f !== "object" || typeof f.subject !== "string" || typeof f.observation !== "string") {
|
|
234
|
+
return { ok: false, reason: "finding " + i + " needs string subject and observation" };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return { ok: true, findings: findings };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// classifyContentFinding — ported from workflows/standard.js (QA
|
|
241
|
+
// content-attribution). diffFiles: the task's publish-diff file set
|
|
242
|
+
// (repo-relative paths). Pure set membership — no prose, no judgment.
|
|
243
|
+
export function classifyContentFinding(finding, diffFiles) {
|
|
244
|
+
var subject = finding.subject;
|
|
245
|
+
if (subject === "live-data") {
|
|
246
|
+
return { attribution: "environment-attributable", reason: "live-data is never in a git publish diff — the crew has no live-DB write path (blocker 32)" };
|
|
247
|
+
}
|
|
248
|
+
if (diffFiles.indexOf(subject) !== -1) {
|
|
249
|
+
return { attribution: "task-change", reason: "subject is in the task's publish diff" };
|
|
250
|
+
}
|
|
251
|
+
return { attribution: "environment-attributable", reason: "subject is not in the task's publish diff" };
|
|
252
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// lib/prompts/tool-check.js — the tool-check preamble for creative
|
|
2
|
+
// spawn prompts (sandbox exit, Phase C Piece 2).
|
|
3
|
+
//
|
|
4
|
+
// Generalized from lib/chore/prompts/tool-check.js (which stays live and
|
|
5
|
+
// untouched): byte-identical across standard/bugfix/chore/docs.
|
|
6
|
+
//
|
|
7
|
+
// Import-safe: pure constant, no I/O. Verbatim from workflows/chore.js
|
|
8
|
+
// (byte-identical across standard/bugfix/chore/docs — pinned by
|
|
9
|
+
// tests/artifact-tools.test.js). The two signal lines are the ONLY
|
|
10
|
+
// machine-read tool-availability evidence — the extractor never guesses
|
|
11
|
+
// from English prose.
|
|
12
|
+
|
|
13
|
+
export const TOOL_CHECK_PREAMBLE =
|
|
14
|
+
"TOOL CHECK (do this first, before any other work):\n" +
|
|
15
|
+
"1. Call tool_search.load_tool_namespace with paths [\"artifact\"].\n" +
|
|
16
|
+
"2. Write exactly one line: artifact_tools: ok - or artifact_tools: missing if the call failed or the tool does not exist.\n" +
|
|
17
|
+
"3. Run the shell command: echo tool-probe-ok - then write exactly one line: shell_transport: ok - or shell_transport: unavailable if you cannot run shell commands.\n" +
|
|
18
|
+
"Then do the assignment below.\n\n";
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// lib/prompts/trailers.js — retry trailers for creative spawn prompts
|
|
2
|
+
// (sandbox exit, Phase C Piece 2).
|
|
3
|
+
//
|
|
4
|
+
// Generalized from lib/chore/prompts/trailers.js (which stays live and
|
|
5
|
+
// untouched): every function here is workflow-neutral.
|
|
6
|
+
//
|
|
7
|
+
// Import-safe: pure functions, no I/O, no clock, no randomness. Logic-
|
|
8
|
+
// verbatim from workflows/chore.js with the exact source signatures:
|
|
9
|
+
// buildTransportRetryTrailer(stepName, repoPath, taskId, attempt, reason)
|
|
10
|
+
// classifyRetryTrailer(reason, failedReturn)
|
|
11
|
+
// versionRetryTrailer(reason, failedReturn)
|
|
12
|
+
// buildTransportRetryTrailer is byte-identical across standard/bugfix/chore/docs
|
|
13
|
+
// (pinned by tests/transport-retry.test.js); the classify/version trailers are
|
|
14
|
+
// chore-local.
|
|
15
|
+
//
|
|
16
|
+
// Note: the classify/version trailers are retained for contract fidelity but
|
|
17
|
+
// have no consumer in the worker layer — the mechanical gates they re-prompted
|
|
18
|
+
// (classify-branch, version-check) now run via direct execFile, so there is no
|
|
19
|
+
// agent ferry to re-prompt. Only buildTransportRetryTrailer is used, by the
|
|
20
|
+
// work-agent transport-retry path.
|
|
21
|
+
|
|
22
|
+
export function buildTransportRetryTrailer(stepName, repoPath, taskId, attempt, reason) {
|
|
23
|
+
// reason: "discarded" (the runtime threw the output away — the JSON-candidate
|
|
24
|
+
// scan found {...}-shaped fragments it could not parse), "empty" (the worker returned without throwing but produced
|
|
25
|
+
// nothing usable), "no-tools" (the worker's TOOL CHECK reported
|
|
26
|
+
// artifact_tools: missing), or "no-transport" (the worker's TOOL CHECK
|
|
27
|
+
// reported shell_transport: unavailable).
|
|
28
|
+
// The trailer tells the retry what to expect, not just to try again.
|
|
29
|
+
var why = reason === "empty"
|
|
30
|
+
? "your previous attempt returned no usable output"
|
|
31
|
+
: reason === "no-tools"
|
|
32
|
+
? "your previous attempt's TOOL CHECK reported artifact_tools: missing (this is a fresh launch, so run the TOOL CHECK's load step again before the work)"
|
|
33
|
+
: reason === "no-transport"
|
|
34
|
+
? "your previous attempt's TOOL CHECK reported shell_transport: unavailable (this is a fresh launch, so run the TOOL CHECK's shell probe again before the work)"
|
|
35
|
+
: "your previous attempt's output was discarded by the transport because it contained {...}-shaped fragments the transport could not parse; write plain prose with no JSON-shaped fragments";
|
|
36
|
+
return "\n\nTRANSPORT RETRY (attempt " + attempt + " of 2): " + why + ". " +
|
|
37
|
+
"First check existing state (worktree/branch at " + repoPath + "/.worktrees/" + taskId + ", the task branch, dashboard sessions for this task) - " +
|
|
38
|
+
"if the " + stepName + " work is already complete, report on what was done rather than duplicating side effects. " +
|
|
39
|
+
"Then return your report in exactly the shape specified above.";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function classifyRetryTrailer(reason, failedReturn) {
|
|
43
|
+
// Pass-2 simplification: the reason string already carries the truth
|
|
44
|
+
// (throw vs parse failure), so one neutral sentence replaces the
|
|
45
|
+
// throw/parse branch — the stringly-typed prefix contract between the
|
|
46
|
+
// call site and this function is deleted, not moved. Prompt accuracy,
|
|
47
|
+
// not prompt hardening.
|
|
48
|
+
return "\n\nCLASSIFY RETRY: the previous classifier attempt failed (" + reason + "). " +
|
|
49
|
+
"Return ONLY the two named fields: { \"stdout\": \"<the command's exact stdout>\", \"stderr\": \"<the command's exact stderr>\" }. " +
|
|
50
|
+
"Put each stream in its named field — do not add labels like \"stdout:\" in front of the content. " +
|
|
51
|
+
"Previous attempt detail (do not repeat this shape): " + String(failedReturn).slice(0, 200);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function versionRetryTrailer(reason, failedReturn) {
|
|
55
|
+
return "\n\nVERSION-CHECK RETRY: the previous version-check attempt failed (" + reason + "). " +
|
|
56
|
+
"Return ONLY the named field: { \"stdout\": \"<the command's exact stdout>\" }. " +
|
|
57
|
+
"Put the command's stdout in the named field — do not add labels like \"stdout:\" in front of the content. " +
|
|
58
|
+
"Previous attempt detail (do not repeat this shape): " + String(failedReturn).slice(0, 200);
|
|
59
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// lib/prompts/verdict-reask.js — the verdict re-ask prompt builder
|
|
2
|
+
// (sandbox exit, Phase C Piece 2).
|
|
3
|
+
//
|
|
4
|
+
// Generalized from lib/chore/prompts/verdict-reask.js (which stays live and
|
|
5
|
+
// untouched): a pure function of declared params, workflow-neutral.
|
|
6
|
+
//
|
|
7
|
+
// A pure function of declared params (P1). Logic-verbatim from
|
|
8
|
+
// workflows/chore.js buildVerdictReaskPrompt: the re-ask is a fresh content
|
|
9
|
+
// judgment — it must NOT copy any VERDICT line from the report, it decides
|
|
10
|
+
// from the content. Bounded to 2 attempts by the spawn bridge
|
|
11
|
+
// (MAX_ATTEMPTS), 180s each.
|
|
12
|
+
|
|
13
|
+
export function buildVerdictReaskPrompt(params) {
|
|
14
|
+
var p = params || {};
|
|
15
|
+
var stepName = p.step_name;
|
|
16
|
+
var workerText = p.worker_text;
|
|
17
|
+
if (typeof stepName !== "string" || stepName.length === 0) {
|
|
18
|
+
throw new Error("buildVerdictReaskPrompt: missing required param 'step_name'");
|
|
19
|
+
}
|
|
20
|
+
if (typeof workerText !== "string") {
|
|
21
|
+
throw new Error("buildVerdictReaskPrompt: missing required param 'worker_text'");
|
|
22
|
+
}
|
|
23
|
+
return (
|
|
24
|
+
"Mechanical transcription task. Read the work report below and emit its verdict.\n\n" +
|
|
25
|
+
"WORK REPORT (verbatim):\n" + workerText + "\n\n" +
|
|
26
|
+
"Decide from the report's own content whether the " + stepName + " step clearly describes successful completion: " +
|
|
27
|
+
"if it does, the verdict is PASS; otherwise — failure, error, unfinished work, or unclear — the verdict is FAIL. " +
|
|
28
|
+
"Do NOT copy any VERDICT line from the report — decide from the content.\n\n" +
|
|
29
|
+
"The result must be exactly one line and nothing else: VERDICT: PASS or VERDICT: FAIL."
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"_note":"Extractor contract for the verdict-reask spawn kind — the exact line grammar lib/chore/extract.js requires. This is NOT JSON Schema; 'schema' here means the machine-read report contract the agent's prose must satisfy.","contract":{"bounds":"2 attempts max, 180s each (MAX_ATTEMPTS['verdict-reask']); attempts are separate ledger rows kind='verdict-reask'. Terminal reask failures count toward the per-task transport budget.","verdict":{"function":"extractVerdict","grammar":"Exactly one line and nothing else: 'VERDICT: PASS' or 'VERDICT: FAIL'. Read with extractVerdict (same grammar as work-agent); anything else → missing (null) and the re-ask attempt is spent."}},"extractor_module":"../extract.js","kind":"verdict-reask","prompt_builder":"./verdict-reask.js"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// lib/prompts/work-agent.js — the work-agent prompt builder for the
|
|
2
|
+
// worker-layer phase modules (sandbox exit, Phase C Piece 2).
|
|
3
|
+
//
|
|
4
|
+
// Generalized from lib/chore/prompts/work-agent.js (which stays live and
|
|
5
|
+
// untouched): the frame is workflow-neutral; per-step instructions arrive
|
|
6
|
+
// as a param (built by the phase modules).
|
|
7
|
+
//
|
|
8
|
+
// A pure function of declared params (P1, 2026-09-26): prompts are NOT
|
|
9
|
+
// verbatim-lifted (only 1 of 44 chore prompts was liftable). The frame below
|
|
10
|
+
// reproduces the workPromptBase assembly from workflows/chore.js exactly;
|
|
11
|
+
// the per-step instructions arrive as a param (built by the phase modules),
|
|
12
|
+
// as do the conditional trailers.
|
|
13
|
+
|
|
14
|
+
import { TOOL_CHECK_PREAMBLE } from "./tool-check.js";
|
|
15
|
+
|
|
16
|
+
function requiredString(params, name) {
|
|
17
|
+
var v = params[name];
|
|
18
|
+
if (typeof v !== "string" || v.length === 0) {
|
|
19
|
+
throw new Error("buildWorkPrompt: missing required param '" + name + "'");
|
|
20
|
+
}
|
|
21
|
+
return v;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function buildWorkPrompt(params) {
|
|
25
|
+
var p = params || {};
|
|
26
|
+
var identity = requiredString(p, "identity");
|
|
27
|
+
var orchPath = requiredString(p, "orch_path");
|
|
28
|
+
var taskId = requiredString(p, "task_id");
|
|
29
|
+
var stepName = requiredString(p, "step_name");
|
|
30
|
+
var instructions = requiredString(p, "instructions");
|
|
31
|
+
// Source equivalence (workflows/chore.js): task_title and task_description
|
|
32
|
+
// flow through `inputs.task_title || ""` — empty values are valid and must
|
|
33
|
+
// NOT be rejected. Only identity/orch_path/task_id/step_name/instructions
|
|
34
|
+
// are required.
|
|
35
|
+
var taskTitle = p.task_title || "";
|
|
36
|
+
var taskDescription = p.task_description || "";
|
|
37
|
+
var crewApi = typeof p.crew_api === "string" ? p.crew_api : "";
|
|
38
|
+
// Source equivalence: the "Crew API:" line is emitted for every step
|
|
39
|
+
// EXCEPT Review (Cass must not call the Crew API directly — the frame is
|
|
40
|
+
// `(step.name !== "Review" ? "Crew API: " + CREW_API + "\n" : "")`). The
|
|
41
|
+
// phase module passes the pinned crew-api path, or "" for Review.
|
|
42
|
+
var eventPreamble = typeof p.event_preamble === "string" ? p.event_preamble : "";
|
|
43
|
+
var retryTrailer = typeof p.transport_retry_trailer === "string" ? p.transport_retry_trailer : "";
|
|
44
|
+
var includeToolCheck = p.include_tool_check !== false;
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
(includeToolCheck ? TOOL_CHECK_PREAMBLE : "") +
|
|
48
|
+
"Read the identity file at " + orchPath + "/identities/" + identity + ".md using the read tool, and embody that character fully.\n\n" +
|
|
49
|
+
"## Your Assignment\n\n" +
|
|
50
|
+
"Task: " + taskTitle + "\nTask ID: " + taskId + "\nDescription: " + taskDescription + "\nStep: " + stepName + "\n" +
|
|
51
|
+
(crewApi ? "Crew API: " + crewApi + "\n" : "") +
|
|
52
|
+
"\n## Instructions\n\n" + eventPreamble + instructions + "\n\nCONSTRAINT: Do NOT call logevent or upsertagentsession — the workflow handles all phase tracking after your step completes.\n\nStay in character. Do the work thoroughly.\n\n" +
|
|
53
|
+
"Your report is plain prose describing what you did and found. For verdict steps, end the report with exactly one line: VERDICT: PASS or VERDICT: FAIL." +
|
|
54
|
+
retryTrailer
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"_note":"Extractor contract for the work-agent spawn kind — the exact line grammars lib/chore/extract.js requires. This is NOT JSON Schema; 'schema' here means the machine-read report contract the agent's prose must satisfy.","contract":{"bump_version":{"function":"bumpVersion","grammar":"Deterministic semver bump (patch default); computed by the workflow, never by the agent."},"experiential":{"function":"extractExperiential","grammar":"'experiential: yes|no' → boolean; missing or malformed → null (opt-in flag, never parks on a garbled line)."},"marker_lines":{"function":"extractMarkerLines","grammar":"Known prefixes (repo_diff:, release:, version_bump:, VERDICT:, TARGET_VERSION=, published:, experiential:, layer:, capture_targets:, terminal_targets:, worktree:); identical lines deduped. Long reports cannot amputate them — they are re-appended after the summary slice."},"release_decision":{"function":"extractReleaseDecision","grammar":"'release: yes|no' → {release, version_bump}; missing release line → null; 'yes' without version_bump → null (fail closed)."},"repo_diff_none":{"function":"extractRepoDiffNone","grammar":"/repo_diff:\\s*none/im — the builder declared no repo change."},"tool_signals":{"function":"parseToolSignals","grammar":"Lines 'artifact_tools: ok|missing' and 'shell_transport: ok|unavailable' (anchored, first match each). Missing lines → 'unknown', never null."},"verdict":{"function":"extractVerdict","grammar":"The LAST 'VERDICT: PASS|FAIL' in the report; must be in the trailing 100 chars; conflicting verdicts in the trailing 200 chars fail closed. Returns {ok, passed} or {ok:false, count}. Word boundary prevents 'PASSING' matching."},"worktree":{"function":"extractWorktree","grammar":"Line 'worktree: <path>' (last wins); trailing slashes normalized. Missing → {ok:false}."}},"extractor_module":"../extract.js","kind":"work-agent","prompt_builder":"./work-agent.js"}
|