muse-crew 0.6.6 → 0.6.7
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/AGENTS.md +1 -1
- package/API.md +53 -9
- package/docs/guide.md +9 -6
- package/docs/publish-verification.md +205 -0
- package/lib/AGENTS.md +5 -5
- package/lib/crew-api.js +1101 -0
- package/lib/crew-release.sh +19 -0
- package/lib/merge-lock.sh +18 -5
- package/lib/publish-npm.sh +6 -5
- package/lib/schema.sql +101 -0
- package/lib/test-merge-lock.sh +13 -0
- package/lib/test-publish-skip.sh +2 -1
- package/lib/test-terminal-cleanup.sh +131 -0
- package/lib/test-verify-merge.sh +1 -0
- package/lib/test-worktree-backend.sh +74 -0
- package/lib/worktree-lifecycle.sh +164 -16
- package/package.json +2 -2
- package/seed/AGENTS.md +0 -1
- package/seed/crons.json +0 -13
- package/workflows/AGENTS.md +4 -2
- package/workflows/bugfix.js +987 -193
- package/workflows/chore.js +936 -177
- package/workflows/crew-dispatch.js +201 -37
- package/workflows/crew-init.js +82 -54
- package/workflows/docs.js +117 -48
- package/workflows/standard.js +970 -195
- package/workflows/tests/read-board-parse.test.mjs +162 -0
- package/lib/orphan-sweep.sh +0 -287
- package/lib/test-orphan-sweep.sh +0 -276
- package/seed/cron-sweep-body-template.md +0 -11
package/workflows/docs.js
CHANGED
|
@@ -22,11 +22,19 @@ const startStepIndex = inputs.start_step_index || 0;
|
|
|
22
22
|
// resolution back via updatetask in the self-claim below.
|
|
23
23
|
const RESOLVED_WORKFLOW = inputs.resolved_workflow || null;
|
|
24
24
|
const WORKFLOW_WAS_NULL = inputs.workflow_was_null === true;
|
|
25
|
-
const CLAIM_WORKFLOW_PERSIST = (WORKFLOW_WAS_NULL && RESOLVED_WORKFLOW) ? ", \"workflow\": \"" + RESOLVED_WORKFLOW + "\"" : "";
|
|
26
25
|
|
|
27
26
|
// Config from args — backward-compatible fallbacks for manual launches
|
|
28
|
-
const DASHBOARD_SLUG = inputs.dashboardSlug || "orchestra-dashboard";
|
|
29
27
|
const crewHome = inputs.crewHome || "~/workspace/.jarvis";
|
|
28
|
+
// Crew API: the workflow calls the crew-owned CLI, not the dashboard.
|
|
29
|
+
// The CLI implements the API.md contract against $CREW_HOME/crew-state.db.
|
|
30
|
+
const CREW_API = crewHome + "/current/lib/crew-api.js";
|
|
31
|
+
// Build a shell command invoking the CLI. Args are JSON-encoded and
|
|
32
|
+
// single-quote-wrapped for safe shell passing. The agent runs this and
|
|
33
|
+
// returns the stdout verbatim (the CLI emits JSON on stdout).
|
|
34
|
+
function crewCmd(command, args) {
|
|
35
|
+
var json = JSON.stringify(args || {}).replace(/'/g, "'\\''");
|
|
36
|
+
return "node " + CREW_API + " --crew-home " + crewHome + " " + command + " --json '" + json + "'";
|
|
37
|
+
}
|
|
30
38
|
const ORCH_PATH = crewHome + "/.orchestration";
|
|
31
39
|
|
|
32
40
|
// Project repo — the docs workflow edits the project's own docs, not the crew home.
|
|
@@ -35,7 +43,12 @@ const projectConfig = inputs.project_config || {};
|
|
|
35
43
|
// Project this run was dispatched for — the mid-run project-change guard
|
|
36
44
|
// compares the task's live project against this on every phase boundary.
|
|
37
45
|
const LAUNCH_PROJECT_ID = inputs.project_id || "";
|
|
38
|
-
|
|
46
|
+
// Fail closed on a missing repo_path — docs go in the project repo,
|
|
47
|
+
// never in the crew home.
|
|
48
|
+
const REPO_PATH = projectConfig.repo_path || "";
|
|
49
|
+
if (!REPO_PATH) {
|
|
50
|
+
throw new Error("Project '" + (inputs.project_id || "unknown") + "' has no repo_path configured — set it via updateproject before dispatching tasks.");
|
|
51
|
+
}
|
|
39
52
|
|
|
40
53
|
if (!taskId) {
|
|
41
54
|
throw new Error("task_id is required in args");
|
|
@@ -69,10 +82,9 @@ async function parkTask(reason) {
|
|
|
69
82
|
try {
|
|
70
83
|
await agent(
|
|
71
84
|
"Park this task for human attention.\n" +
|
|
72
|
-
"
|
|
73
|
-
JSON.stringify({ task_id: taskId, message: parkMessage }) + ".\n" +
|
|
85
|
+
"Run in shell and return the stdout verbatim:\n" + crewCmd("park-task", { task_id: taskId, message: parkMessage }) + "\n" +
|
|
74
86
|
"The parked state is the human-attention signal — the dispatcher skips parked tasks.",
|
|
75
|
-
{ key: "park-task", label: "Parking task for human attention"
|
|
87
|
+
{ key: "park-task", label: "Parking task for human attention" }
|
|
76
88
|
);
|
|
77
89
|
} catch (parkErr) {
|
|
78
90
|
log("PARK FAILED for task " + taskId + ": " + (parkErr && parkErr.message ? parkErr.message : parkErr) + " — park did not land, reporting failed so the next tick retries");
|
|
@@ -97,8 +109,8 @@ while (i < STEPS.length) {
|
|
|
97
109
|
// the step with the new project's config.
|
|
98
110
|
if (LAUNCH_PROJECT_ID) {
|
|
99
111
|
const projectCheck = await agent(
|
|
100
|
-
"Read this task's current project from the
|
|
101
|
-
"
|
|
112
|
+
"Read this task's current project from the crew API.\n" +
|
|
113
|
+
"Run in shell and return the stdout verbatim:\n" + crewCmd("get-state", { events_limit: 1 }) + "\n" +
|
|
102
114
|
"Find the task with id \"" + taskId + "\" in the returned tasks array.\n" +
|
|
103
115
|
"Return exactly { \"project\": \"<the task's project field, or empty string if absent>\" } and nothing else.",
|
|
104
116
|
{
|
|
@@ -113,12 +125,12 @@ while (i < STEPS.length) {
|
|
|
113
125
|
log(abortMessage);
|
|
114
126
|
await agent(
|
|
115
127
|
"Abort the stale run.\n" +
|
|
116
|
-
"
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
{ key: "abort-project-change", label: "Aborting stale run (project changed)"
|
|
128
|
+
"Run in shell and return the stdout verbatim:\n" + crewCmd("record-phase", {
|
|
129
|
+
task_id: taskId,
|
|
130
|
+
session: { task_id: taskId, identity: step.identity, step: REWORK_STEP, status: "failed", notes: abortMessage },
|
|
131
|
+
event: { task_id: taskId, type: "failed", identity: step.identity, message: abortMessage }
|
|
132
|
+
}) + "\n",
|
|
133
|
+
{ key: "abort-project-change", label: "Aborting stale run (project changed)" }
|
|
122
134
|
);
|
|
123
135
|
return { status: "failed", task_id: taskId, reason: abortMessage };
|
|
124
136
|
}
|
|
@@ -131,11 +143,12 @@ while (i < STEPS.length) {
|
|
|
131
143
|
// claimed:false and this run stands down as a duplicate.
|
|
132
144
|
let activeSessionId;
|
|
133
145
|
if (isFirstClaim) {
|
|
146
|
+
var firstClaimUpdateArgs = { id: taskId, state: "in_progress" };
|
|
147
|
+
if (WORKFLOW_WAS_NULL && RESOLVED_WORKFLOW) firstClaimUpdateArgs.workflow = RESOLVED_WORKFLOW;
|
|
134
148
|
const claimResult = await agent(
|
|
135
149
|
"Claim this task for the " + step.name + " step.\n" +
|
|
136
|
-
"
|
|
137
|
-
"Then
|
|
138
|
-
"{ \"task_id\": \"" + taskId + "\", \"identity\": \"" + step.identity + "\", \"step\": \"" + step.name + "\", \"notes\": \"" + step.name + " step started\" }.\n" +
|
|
150
|
+
"Run in shell and return the stdout verbatim:\n" + crewCmd("update-task", firstClaimUpdateArgs) + "\n" +
|
|
151
|
+
"Then run in shell and return the stdout verbatim:\n" + crewCmd("claim-task", { task_id: taskId, identity: step.identity, step: step.name, notes: step.name + " step started" }) + "\n" +
|
|
139
152
|
"Do not interpret the claim response. It already contains an explicit \"claimed\" field — copy it verbatim.\n" +
|
|
140
153
|
"Return { claimed: <verbatim>, session_id: \"<...>\" }. If claimed is false there is no session_id; return { claimed: false, session_id: \"\" }.",
|
|
141
154
|
{
|
|
@@ -156,8 +169,7 @@ while (i < STEPS.length) {
|
|
|
156
169
|
} else {
|
|
157
170
|
const claimResult = await agent(
|
|
158
171
|
"Claim a session for this task step.\n" +
|
|
159
|
-
"
|
|
160
|
-
"{ \"task_id\": \"" + taskId + "\", \"identity\": \"" + step.identity + "\", \"step\": \"" + step.name + "\", \"notes\": \"" + step.name + " step started" + (reworkCount > 0 ? " (rework #" + reworkCount + ")" : "") + "\" }.\n" +
|
|
172
|
+
"Run in shell and return the stdout verbatim:\n" + crewCmd("claim-task", { task_id: taskId, identity: step.identity, step: step.name, notes: step.name + " step started" + (reworkCount > 0 ? " (rework #" + reworkCount + ")" : "") }) + "\n" +
|
|
161
173
|
"Return the session_id from the response.",
|
|
162
174
|
{
|
|
163
175
|
key: "claim-" + step.name + (reworkCount > 0 ? "-r" + reworkCount : ""),
|
|
@@ -274,20 +286,58 @@ while (i < STEPS.length) {
|
|
|
274
286
|
function workRetryKey(stepName, reworkSuffix, attempt) {
|
|
275
287
|
return "work-" + stepName + reworkSuffix + "-t" + attempt;
|
|
276
288
|
}
|
|
289
|
+
// TOOL_CHECK_PREAMBLE - every work agent runs this first. The artifact tool
|
|
290
|
+
// namespace is deferred for workflow children: present but invisible until
|
|
291
|
+
// the child loads it via tool_search.load_tool_namespace (bug 3472bf36 root
|
|
292
|
+
// cause, verified 2026-09-11 by direct probe: 5/5 children self-loaded it;
|
|
293
|
+
// the "non-deterministic platform flake" was children never being told to
|
|
294
|
+
// load it). The two signal lines are the ONLY machine-read tool-availability
|
|
295
|
+
// evidence - the workflow never guesses from English prose.
|
|
296
|
+
// Byte-identical across standard/bugfix/chore/docs - pinned by
|
|
297
|
+
// tests/artifact-tools.test.js.
|
|
298
|
+
var TOOL_CHECK_PREAMBLE =
|
|
299
|
+
"TOOL CHECK (do this first, before any other work):\n" +
|
|
300
|
+
"1. Call tool_search.load_tool_namespace with paths [\"artifact\"].\n" +
|
|
301
|
+
"2. Write exactly one line: artifact_tools: ok - or artifact_tools: missing if the call failed or the tool does not exist.\n" +
|
|
302
|
+
"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" +
|
|
303
|
+
"Then do the assignment below.\n\n";
|
|
277
304
|
function buildTransportRetryTrailer(stepName, repoPath, taskId, attempt, reason) {
|
|
278
|
-
// reason: "discarded" (the runtime threw the output away
|
|
279
|
-
// machine-read)
|
|
280
|
-
// nothing usable)
|
|
281
|
-
//
|
|
305
|
+
// reason: "discarded" (the runtime threw the output away - it could not be
|
|
306
|
+
// machine-read), "empty" (agent() returned without throwing but produced
|
|
307
|
+
// nothing usable), "no-tools" (the worker's TOOL CHECK reported
|
|
308
|
+
// artifact_tools: missing), or "no-transport" (the worker's TOOL CHECK
|
|
309
|
+
// reported shell_transport: unavailable).
|
|
310
|
+
// The trailer tells the retry what to expect, not just to try again.
|
|
282
311
|
var why = reason === "empty"
|
|
283
312
|
? "your previous attempt returned no usable output"
|
|
313
|
+
: reason === "no-tools"
|
|
314
|
+
? "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)"
|
|
315
|
+
: reason === "no-transport"
|
|
316
|
+
? "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)"
|
|
284
317
|
: "your previous attempt's output could not be machine-read as JSON and was discarded";
|
|
285
318
|
return "\n\nTRANSPORT RETRY (attempt " + attempt + " of 2): " + why + ". " +
|
|
286
|
-
"First check existing state (worktree/branch at " + repoPath + "/.worktrees/" + taskId + ", the task branch, dashboard sessions for this task)
|
|
319
|
+
"First check existing state (worktree/branch at " + repoPath + "/.worktrees/" + taskId + ", the task branch, dashboard sessions for this task) - " +
|
|
287
320
|
"if the " + stepName + " work is already complete, report on what was done rather than duplicating side effects. " +
|
|
288
321
|
"Then return your report as JSON in exactly the shape specified above.";
|
|
289
322
|
}
|
|
290
|
-
|
|
323
|
+
// parseToolSignals - bug 3472bf36. The work agent's TOOL CHECK emits two
|
|
324
|
+
// exact signal lines: artifact_tools: ok|missing and
|
|
325
|
+
// shell_transport: ok|unavailable. The workflow reads ONLY these lines.
|
|
326
|
+
// It never guesses tool availability from English prose: the old regex
|
|
327
|
+
// misclassified ordinary inability-prose (e.g. a Map agent describing what
|
|
328
|
+
// it could not inspect) and burned all three attempts on useless retries.
|
|
329
|
+
// Byte-identical across standard/bugfix/chore/docs - pinned by
|
|
330
|
+
// tests/artifact-tools.test.js.
|
|
331
|
+
function parseToolSignals(text) {
|
|
332
|
+
var t = String(text || "");
|
|
333
|
+
var out = { artifactTools: "unknown", shellTransport: "unknown" };
|
|
334
|
+
var am = t.match(/^artifact_tools:\s*(ok|missing)\s*$/m);
|
|
335
|
+
if (am) out.artifactTools = am[1];
|
|
336
|
+
var sm = t.match(/^shell_transport:\s*(ok|unavailable)\s*$/m);
|
|
337
|
+
if (sm) out.shellTransport = sm[1];
|
|
338
|
+
return out;
|
|
339
|
+
}
|
|
340
|
+
function describeWorkAgentFailure(stepName, identity, attempts) {
|
|
291
341
|
// Honest classification of a work-agent call that yielded no usable
|
|
292
342
|
// report, with the per-attempt evidence preserved in the session notes.
|
|
293
343
|
// Two distinct cases:
|
|
@@ -359,10 +409,11 @@ while (i < STEPS.length) {
|
|
|
359
409
|
// string. The verdict is still extracted deterministically from the report
|
|
360
410
|
// text by extractVerdict below — never by an agent.
|
|
361
411
|
var workPromptBase =
|
|
412
|
+
TOOL_CHECK_PREAMBLE +
|
|
362
413
|
"Read the identity file at " + ORCH_PATH + "/identities/" + step.identity + ".md using the read tool, and embody that character fully.\n\n" +
|
|
363
414
|
"## Your Assignment\n\n" +
|
|
364
|
-
"Task: " + taskTitle + "\nTask ID: " + taskId + "\nDescription: " + taskDescription + "\nStep: " + step.name + "\
|
|
365
|
-
"## Instructions\n\n" + instructions + "\n\nCONSTRAINT: Do NOT call
|
|
415
|
+
"Task: " + taskTitle + "\nTask ID: " + taskId + "\nDescription: " + taskDescription + "\nStep: " + step.name + "\n\n" +
|
|
416
|
+
"## Instructions\n\n" + instructions + "\n\nCONSTRAINT: Do NOT call the crew API directly — the workflow handles all phase tracking after your step completes.\n\nStay in character. All file work under " + REPO_PATH + "/.\n\n" +
|
|
366
417
|
"Return your work as JSON in exactly this shape: {\"status\": \"ok\", \"result\": \"your report here\"}. " +
|
|
367
418
|
"The result is plain prose describing what you did and found. For verdict steps, end the report with exactly one line: VERDICT: PASS or VERDICT: FAIL.";
|
|
368
419
|
var workKeyBase = "work-" + step.name + (reworkCount > 0 ? "-r" + reworkCount : "");
|
|
@@ -370,7 +421,8 @@ while (i < STEPS.length) {
|
|
|
370
421
|
var workAttempts = [];
|
|
371
422
|
for (var workAttempt = 0; workAttempt <= 2; workAttempt++) {
|
|
372
423
|
var workKey = workAttempt === 0 ? workKeyBase : workRetryKey(step.name, (reworkCount > 0 ? "-r" + reworkCount : ""), workAttempt);
|
|
373
|
-
var
|
|
424
|
+
var prevAttempt = workAttempt === 0 ? null : workAttempts[workAttempt - 1];
|
|
425
|
+
var retryReason = workAttempt === 0 ? null : (prevAttempt.threw ? "discarded" : (prevAttempt.outcome === "missing-artifact-tools" ? "no-tools" : (prevAttempt.outcome === "unavailable-shell-transport" ? "no-transport" : "empty")));
|
|
374
426
|
try {
|
|
375
427
|
workerResult = await agent(
|
|
376
428
|
workPromptBase + (workAttempt === 0 ? "" : buildTransportRetryTrailer(step.name, REPO_PATH, taskId, workAttempt, retryReason)),
|
|
@@ -385,6 +437,22 @@ while (i < STEPS.length) {
|
|
|
385
437
|
// always fails (regression shipped in ecef136 when Date.now() was
|
|
386
438
|
// removed from this loop).
|
|
387
439
|
if (typeof workerResult === "string" && workerResult.trim()) {
|
|
440
|
+
// Bug 3472bf36: a worker whose TOOL CHECK reports artifact_tools: missing
|
|
441
|
+
// (or shell_transport: unavailable) gets a fresh launch - the load is
|
|
442
|
+
// per-launch - instead of a useless report.
|
|
443
|
+
var toolSignals = parseToolSignals(workerResult);
|
|
444
|
+
if (toolSignals.artifactTools === "missing") {
|
|
445
|
+
workAttempts.push({ threw: false, error: "", outcome: "missing-artifact-tools" });
|
|
446
|
+
log(step.name + " work agent attempt " + (workAttempt + 1) + " of 3 reported artifact_tools: missing — retrying with a fresh launch");
|
|
447
|
+
workerResult = null;
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
if (toolSignals.shellTransport === "unavailable") {
|
|
451
|
+
workAttempts.push({ threw: false, error: "", outcome: "unavailable-shell-transport" });
|
|
452
|
+
log(step.name + " work agent attempt " + (workAttempt + 1) + " of 3 reported shell_transport: unavailable — retrying with a fresh launch");
|
|
453
|
+
workerResult = null;
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
388
456
|
if (workAttempt > 0) log(step.name + " work agent transport retry " + workAttempt + " returned a machine-readable report");
|
|
389
457
|
break;
|
|
390
458
|
}
|
|
@@ -407,11 +475,12 @@ while (i < STEPS.length) {
|
|
|
407
475
|
log(step.name + " " + workFailure.notes + " — marking failed for retry");
|
|
408
476
|
await agent(
|
|
409
477
|
"Record work failure.\n" +
|
|
410
|
-
"
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
478
|
+
"Run in shell and return the stdout verbatim:\n" + crewCmd("record-phase", {
|
|
479
|
+
task_id: taskId,
|
|
480
|
+
session: { id: activeSessionId, task_id: taskId, identity: step.identity, step: step.name, status: "failed", notes: workFailure.notes },
|
|
481
|
+
event: { task_id: taskId, type: "failed", message: workFailure.eventMessage }
|
|
482
|
+
}) + "\n",
|
|
483
|
+
{ key: "record-block-" + step.name, label: "Recording work failure" }
|
|
415
484
|
);
|
|
416
485
|
return {
|
|
417
486
|
__hatchWorkflowControl: "blocked",
|
|
@@ -441,11 +510,12 @@ while (i < STEPS.length) {
|
|
|
441
510
|
log(step.name + " verdict re-ask exhausted — marking failed for retry");
|
|
442
511
|
await agent(
|
|
443
512
|
"Record verdict failure.\n" +
|
|
444
|
-
"
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
513
|
+
"Run in shell and return the stdout verbatim:\n" + crewCmd("record-phase", {
|
|
514
|
+
task_id: taskId,
|
|
515
|
+
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)" },
|
|
516
|
+
event: { task_id: taskId, type: "failed", message: step.name + " verdict line missing or ambiguous, re-ask exhausted — phase failed, dispatcher will retry" }
|
|
517
|
+
}) + "\n",
|
|
518
|
+
{ key: "record-block-" + step.name, label: "Recording verdict failure" }
|
|
449
519
|
);
|
|
450
520
|
return {
|
|
451
521
|
__hatchWorkflowControl: "blocked",
|
|
@@ -475,14 +545,14 @@ while (i < STEPS.length) {
|
|
|
475
545
|
|
|
476
546
|
await agent(
|
|
477
547
|
"Update session and log event.\n" +
|
|
478
|
-
"
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
548
|
+
"Run in shell and return the stdout verbatim:\n" + crewCmd("record-phase", {
|
|
549
|
+
task_id: taskId,
|
|
550
|
+
session: { id: activeSessionId, task_id: taskId, identity: step.identity, step: step.name, status: status, notes: summary },
|
|
551
|
+
event: { task_id: taskId, type: status, identity: step.identity, message: step.name + " " + status + " by " + step.identity }
|
|
552
|
+
}) + "\n",
|
|
482
553
|
{
|
|
483
554
|
key: "record-" + step.name + (reworkCount > 0 ? "-r" + reworkCount : ""),
|
|
484
|
-
label: "Recording " + step.name + " result"
|
|
485
|
-
schema: { type: "object" }
|
|
555
|
+
label: "Recording " + step.name + " result"
|
|
486
556
|
}
|
|
487
557
|
);
|
|
488
558
|
|
|
@@ -504,10 +574,9 @@ while (i < STEPS.length) {
|
|
|
504
574
|
|
|
505
575
|
await agent(
|
|
506
576
|
"Mark this task as done.\n" +
|
|
507
|
-
"
|
|
508
|
-
"Then
|
|
509
|
-
|
|
510
|
-
{ key: "task-done", label: "Completing task: " + taskTitle, schema: { type: "object" } }
|
|
577
|
+
"Run in shell and return the stdout verbatim:\n" + crewCmd("update-task", { id: taskId, state: "done" }) + "\n" +
|
|
578
|
+
"Then run in shell and return the stdout verbatim:\n" + crewCmd("log-event", { task_id: taskId, type: "completed", message: "All docs workflow steps complete." }),
|
|
579
|
+
{ key: "task-done", label: "Completing task: " + taskTitle }
|
|
511
580
|
);
|
|
512
581
|
|
|
513
582
|
log("Docs workflow complete for task " + taskId);
|