muse-crew 0.14.6 → 0.14.8
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 +45 -5
- package/docs/decisions/AGENTS.md +2 -0
- package/docs/decisions/publish-path.md +56 -8
- package/docs/decisions/qa-reproduce.md +30 -0
- package/lib/AGENTS.md +6 -5
- package/lib/crew-api.js +403 -7
- package/lib/merge-lock.sh +153 -41
- package/lib/qa-db.js +132 -0
- package/lib/schema.sql +50 -0
- package/lib/serve-artifact.js +46 -2
- package/lib/test-detached-integrate.sh +122 -0
- package/lib/test-merge-lock.sh +30 -1
- package/lib/worktree-lifecycle.sh +284 -34
- package/package.json +1 -1
- package/seed/cron-body-template.md +6 -6
- package/workflows/AGENTS.md +1 -1
- package/workflows/bugfix.js +657 -104
- package/workflows/chore.js +461 -106
- package/workflows/crew-dispatch.js +57 -4
- package/workflows/docs.js +24 -2
- package/workflows/standard.js +662 -109
- package/workflows/upgrade.js +13 -1
|
@@ -46,10 +46,43 @@ const MAX_TASKS_PER_POLL = inputs.maxTasksPerPoll || 6;
|
|
|
46
46
|
|
|
47
47
|
// Workflow step registry: static per release. The poll cron body reads
|
|
48
48
|
// <crewHome>/workflows/registry.json (generated at release time from the
|
|
49
|
-
// workflow files, which stay the single source of truth) and
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
|
|
49
|
+
// workflow files, which stay the single source of truth) and ferries the
|
|
50
|
+
// RAW FILE TEXT as inputs.registryText. The dispatcher parses it
|
|
51
|
+
// deterministically — the poller LLM never parses-and-re-emits a nested
|
|
52
|
+
// object into the tool call. (Room #26: 15+ "schema validation" launch
|
|
53
|
+
// rejections 09-19→09-21 from the LLM garbling the nested object, plus one
|
|
54
|
+
// hallucinated-but-shape-valid registry that silently drove dispatch with
|
|
55
|
+
// wrong step tables.) A string arg is trivially well-formed at the
|
|
56
|
+
// tool-call boundary — the same ferry pattern the board already uses.
|
|
57
|
+
// Fall back to the agent() load only when the caller predates the registry
|
|
58
|
+
// — slow, but never broken.
|
|
59
|
+
function parseRegistryText(text) {
|
|
60
|
+
if (text === undefined || text === null) {
|
|
61
|
+
return { ok: false, reason: "registryText is missing" };
|
|
62
|
+
}
|
|
63
|
+
if (typeof text !== "string") {
|
|
64
|
+
return { ok: false, reason: "registryText is not a string" };
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
return { ok: true, registry: JSON.parse(text) };
|
|
68
|
+
} catch (e) {
|
|
69
|
+
return { ok: false, reason: "registryText is not valid JSON: " + e.message };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
var registryResult = null;
|
|
73
|
+
var registryTextResult = parseRegistryText(inputs.registryText);
|
|
74
|
+
if (registryTextResult.ok) {
|
|
75
|
+
registryResult = registryTextResult.registry;
|
|
76
|
+
} else if (inputs.registryText !== undefined && inputs.registryText !== null) {
|
|
77
|
+
// Present but unusable: name the defect, then fall through to the slow load.
|
|
78
|
+
log("WARNING: " + registryTextResult.reason + " — falling back to slow registry load");
|
|
79
|
+
} else if (inputs.registry !== undefined && inputs.registry !== null) {
|
|
80
|
+
// Legacy: pre-string-ferry poller prompts passed the parsed object.
|
|
81
|
+
// Same validation applies; a garbled object falls through to the slow
|
|
82
|
+
// load exactly like a missing one.
|
|
83
|
+
log("WARNING: legacy inputs.registry object received — prefer the registryText string ferry");
|
|
84
|
+
registryResult = inputs.registry;
|
|
85
|
+
}
|
|
53
86
|
// Nested-registry seam (2026-09-20): a malformed nested registry object
|
|
54
87
|
// (e.g. a workflow key mapping to null/undefined, or a steps entry without
|
|
55
88
|
// a name) used to die deep in the lookup tables with a cryptic TypeError
|
|
@@ -78,6 +111,18 @@ function validateRegistry(reg) {
|
|
|
78
111
|
if (typeof wf.reworkTarget !== "string") {
|
|
79
112
|
return { ok: false, reason: "workflow '" + wfNames[i] + "' has no reworkTarget string" };
|
|
80
113
|
}
|
|
114
|
+
// Content pin (room #26): a shape-valid registry can still be
|
|
115
|
+
// hallucinated — 2026-09-21 a poller ferried docs.reworkTarget="Build"
|
|
116
|
+
// (docs.js has no Build step) and it passed shape validation, silently
|
|
117
|
+
// driving dispatch with wrong step tables. The rework target must name
|
|
118
|
+
// a real step of its own workflow; this invariant is release-independent.
|
|
119
|
+
var stepNames = {};
|
|
120
|
+
for (var j = 0; j < wf.steps.length; j++) {
|
|
121
|
+
stepNames[wf.steps[j].name] = true;
|
|
122
|
+
}
|
|
123
|
+
if (!stepNames[wf.reworkTarget]) {
|
|
124
|
+
return { ok: false, reason: "workflow '" + wfNames[i] + "' reworkTarget '" + wf.reworkTarget + "' names no step" };
|
|
125
|
+
}
|
|
81
126
|
}
|
|
82
127
|
return { ok: true };
|
|
83
128
|
}
|
|
@@ -113,6 +158,14 @@ if (!registryResult) {
|
|
|
113
158
|
}
|
|
114
159
|
}
|
|
115
160
|
);
|
|
161
|
+
// The slow agent load is the same untrusted ferry as the args path — a
|
|
162
|
+
// hallucinated registry would silently drive dispatch with wrong step
|
|
163
|
+
// tables. Re-run the existing validation on the slow-loaded result and
|
|
164
|
+
// fail closed with the same reason; there is no second fallback.
|
|
165
|
+
var slowCheck = validateRegistry(registryResult);
|
|
166
|
+
if (!slowCheck.ok) {
|
|
167
|
+
throw new Error("registry slow load failed validation (" + slowCheck.reason + ") — cannot derive step tables; aborting dispatch");
|
|
168
|
+
}
|
|
116
169
|
}
|
|
117
170
|
|
|
118
171
|
// Derive lookup tables from canonical definitions
|
package/workflows/docs.js
CHANGED
|
@@ -543,7 +543,17 @@ function describeWorkAgentFailure(stepName, identity, attempts) {
|
|
|
543
543
|
"Run in shell and return the stdout verbatim:\n" + crewCmd("record-phase", {
|
|
544
544
|
task_id: taskId,
|
|
545
545
|
session: { id: activeSessionId, task_id: taskId, identity: step.identity, step: step.name, status: "failed", notes: "Worker report had no single unambiguous VERDICT: PASS/FAIL line (bounded re-ask exhausted)" },
|
|
546
|
-
event: { task_id: taskId, type: "failed", message: step.name + " verdict line missing or ambiguous, re-ask exhausted — phase failed, dispatcher will retry" }
|
|
546
|
+
event: { task_id: taskId, type: "failed", message: step.name + " verdict line missing or ambiguous, re-ask exhausted — phase failed, dispatcher will retry" },
|
|
547
|
+
// Room #26 blocker 33: INDETERMINATE verdict record — the report
|
|
548
|
+
// could not be read at all, but its full text is still preserved
|
|
549
|
+
// as grounds. (docs has Review as its only verdict step.)
|
|
550
|
+
verdict: {
|
|
551
|
+
step: "Review",
|
|
552
|
+
attempt: reworkCount,
|
|
553
|
+
reviewer: step.identity,
|
|
554
|
+
verdict: "INDETERMINATE",
|
|
555
|
+
grounds: workerText
|
|
556
|
+
}
|
|
547
557
|
}) + "\n",
|
|
548
558
|
{ key: "record-block-" + step.name, label: "Recording verdict failure" }
|
|
549
559
|
);
|
|
@@ -578,7 +588,19 @@ function describeWorkAgentFailure(stepName, identity, attempts) {
|
|
|
578
588
|
"Run in shell and return the stdout verbatim:\n" + crewCmd("record-phase", {
|
|
579
589
|
task_id: taskId,
|
|
580
590
|
session: { id: activeSessionId, task_id: taskId, identity: step.identity, step: step.name, status: status, notes: summary },
|
|
581
|
-
event: { task_id: taskId, type: status, identity: step.identity, message: step.name + " " + status + " by " + step.identity }
|
|
591
|
+
event: { task_id: taskId, type: status, identity: step.identity, message: step.name + " " + status + " by " + step.identity },
|
|
592
|
+
// Room #26 blocker 33: structured, non-lossy Review verdict record.
|
|
593
|
+
// The session note above is truncated; the verdict row carries the
|
|
594
|
+
// full worker report as grounds, written in the same transaction.
|
|
595
|
+
// attempt is the rework round (0 = first Review). (docs has Review
|
|
596
|
+
// as its only verdict step, so verdictPassed is always set here.)
|
|
597
|
+
verdict: (verdictPassed !== null ? {
|
|
598
|
+
step: "Review",
|
|
599
|
+
attempt: reworkCount,
|
|
600
|
+
reviewer: step.identity,
|
|
601
|
+
verdict: verdictPassed ? "PASS" : "FAIL",
|
|
602
|
+
grounds: workerText
|
|
603
|
+
} : null)
|
|
582
604
|
}) + "\n",
|
|
583
605
|
{
|
|
584
606
|
key: "record-" + step.name + (reworkCount > 0 ? "-r" + reworkCount : ""),
|