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.
Files changed (70) hide show
  1. package/API.md +11 -0
  2. package/docs/decisions/composition-machinery.md +180 -0
  3. package/docs/decisions/publish-path.md +6 -0
  4. package/docs/decisions/workflow-core.md +5 -4
  5. package/lib/AGENTS.md +2 -1
  6. package/lib/bugfix/phases/build.js +168 -0
  7. package/lib/bugfix/phases/capture.js +170 -0
  8. package/lib/bugfix/phases/integrate.js +165 -0
  9. package/lib/bugfix/phases/map.js +129 -0
  10. package/lib/bugfix/phases/publish.js +592 -0
  11. package/lib/bugfix/phases/qa.js +356 -0
  12. package/lib/bugfix/phases/reproduce.js +254 -0
  13. package/lib/bugfix/phases/review.js +292 -0
  14. package/lib/bugfix/phases/triage.js +69 -0
  15. package/lib/chore/CONTRACT.md +181 -0
  16. package/lib/chore/DISPOSITION.md +98 -0
  17. package/lib/chore/extract.js +204 -0
  18. package/lib/chore/phase-lib.js +885 -0
  19. package/lib/chore/phases/build.js +110 -0
  20. package/lib/chore/phases/capture.js +82 -0
  21. package/lib/chore/phases/integrate.js +109 -0
  22. package/lib/chore/phases/map.js +81 -0
  23. package/lib/chore/phases/publish.js +543 -0
  24. package/lib/chore/phases/review.js +255 -0
  25. package/lib/chore/phases/triage.js +57 -0
  26. package/lib/chore/prompts/evidence-gatherer.js +41 -0
  27. package/lib/chore/prompts/evidence-gatherer.schema.json +1 -0
  28. package/lib/chore/prompts/tool-check.js +15 -0
  29. package/lib/chore/prompts/trailers.js +56 -0
  30. package/lib/chore/prompts/verdict-reask.js +28 -0
  31. package/lib/chore/prompts/verdict-reask.schema.json +1 -0
  32. package/lib/chore/prompts/work-agent.js +52 -0
  33. package/lib/chore/prompts/work-agent.schema.json +1 -0
  34. package/lib/chore/spawn-keys.js +44 -0
  35. package/lib/chore/spawn-vocab.js +87 -0
  36. package/lib/chore-run.js +538 -0
  37. package/lib/chore-tick.js +289 -0
  38. package/lib/crew-api.js +273 -0
  39. package/lib/crew-dispatch-worker.js +27 -7
  40. package/lib/crew-release.sh +7 -2
  41. package/lib/extract.js +252 -0
  42. package/lib/prompts/tool-check.js +18 -0
  43. package/lib/prompts/trailers.js +59 -0
  44. package/lib/prompts/verdict-reask.js +31 -0
  45. package/lib/prompts/verdict-reask.schema.json +1 -0
  46. package/lib/prompts/work-agent.js +56 -0
  47. package/lib/prompts/work-agent.schema.json +1 -0
  48. package/lib/reap-spawns.js +407 -0
  49. package/lib/schema.sql +12 -1
  50. package/lib/spawn-keys.js +47 -0
  51. package/lib/spawn-step.js +572 -0
  52. package/lib/standard/phases/build.js +120 -0
  53. package/lib/standard/phases/capture.js +163 -0
  54. package/lib/standard/phases/integrate.js +172 -0
  55. package/lib/standard/phases/map.js +119 -0
  56. package/lib/standard/phases/publish.js +565 -0
  57. package/lib/standard/phases/qa.js +399 -0
  58. package/lib/standard/phases/review.js +281 -0
  59. package/lib/standard/phases/triage.js +64 -0
  60. package/lib/test-detached-integrate.sh +47 -0
  61. package/lib/workflow-driver.js +605 -0
  62. package/lib/workflow-lib.js +1012 -0
  63. package/lib/workflow-spec.js +187 -0
  64. package/lib/worktree-lifecycle.sh +55 -3
  65. package/package.json +1 -1
  66. package/seed/cron-body-template.md +61 -9
  67. package/workflows/bugfix.js +17 -17
  68. package/workflows/chore.js +16 -16
  69. package/workflows/docs.js +14 -11
  70. 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"}