muse-crew 0.14.1 → 0.14.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/AGENTS.md +1 -1
- package/docs/decisions/AGENTS.md +3 -0
- package/docs/decisions/publish-path.md +195 -6
- package/docs/decisions/workflow-core.md +1 -1
- package/docs/guide.md +3 -3
- package/docs/publish-verification.md +5 -2
- package/docs/reviews/critic-0143-redo.md +94 -0
- package/docs/reviews/critic-0143.md +108 -0
- package/lib/AGENTS.md +5 -3
- package/lib/crew-api.js +8 -6
- package/lib/publish-npm.sh +39 -2
- package/lib/retry-publish.js +5 -2
- package/lib/test-detached-integrate.sh +416 -0
- package/lib/test-publish-preflight.sh +94 -5
- package/lib/verify-publish.js +3 -4
- package/lib/worktree-lifecycle.sh +380 -94
- package/package.json +1 -1
- package/workflows/bugfix.js +67 -70
- package/workflows/chore.js +65 -68
- package/workflows/standard.js +67 -70
package/workflows/standard.js
CHANGED
|
@@ -428,7 +428,10 @@ function hydrateReleaseDecision(rec) {
|
|
|
428
428
|
// it must match the artifact's real content.
|
|
429
429
|
|
|
430
430
|
// See docs/decisions/publish-path.md#publish-attempt-ledger: every trigger outcome is recorded in the durable ledger.
|
|
431
|
-
|
|
431
|
+
// keyTag (optional): same-outcome ledger writes in one pass need distinct
|
|
432
|
+
// agent-call keys — the runtime replays cached calls by key, so a shared key
|
|
433
|
+
// silently drops the second write (2026-09-19, submitted-on-issuance).
|
|
434
|
+
async function recordPublishLedger(entry, rework, keyTag) {
|
|
432
435
|
try {
|
|
433
436
|
var ledgerDir = crewHome + "/.publish-ledger";
|
|
434
437
|
var line = JSON.stringify({
|
|
@@ -450,7 +453,7 @@ async function recordPublishLedger(entry, rework) {
|
|
|
450
453
|
"Run: mkdir -p " + sq(ledgerDir) + " && printf '%s\n' " + sq(line) +
|
|
451
454
|
" | sed \"s/@LEDGER_TS@/$(date -u +%Y-%m-%dT%H:%M:%SZ)/\" >> " + sq(ledgerDir + "/" + PUBLISH_SLUG + ".jsonl") + " && echo LEDGER_OK\n" +
|
|
452
455
|
"Return JSON { \"result\": \"<verbatim output>\" } and nothing else.",
|
|
453
|
-
{ key: attemptKey("publish-ledger-" + taskId + "-" + entry.outcome, rework),
|
|
456
|
+
{ key: attemptKey("publish-ledger-" + taskId + "-" + entry.outcome + (keyTag ? "-" + keyTag : ""), rework),
|
|
454
457
|
label: "Recording publish attempt in ledger",
|
|
455
458
|
schema: { type: "object", properties: { result: { type: "string" } }, required: ["result"] } }
|
|
456
459
|
);
|
|
@@ -648,7 +651,7 @@ let releaseDecision = null; // { release: "yes"|"no", version_bump: "patch"|"min
|
|
|
648
651
|
// Already-merged idempotency: the verified sha from the builder's
|
|
649
652
|
// `repo_diff: none (already-merged: <sha>)` declaration (null when the
|
|
650
653
|
// builder made commits or declared a runtime-state deliverable). The
|
|
651
|
-
// workflow verifies the sha is an ancestor of
|
|
654
|
+
// workflow verifies the sha is an ancestor of the integration target at Build closeout;
|
|
652
655
|
// Review's no-diff branch reads this, never the builder's prose.
|
|
653
656
|
let alreadyMergedSha = null;
|
|
654
657
|
// Deterministic publish target — computed by the workflow (registry base +
|
|
@@ -1119,7 +1122,7 @@ while (i < STEPS.length) {
|
|
|
1119
1122
|
"cd " + WORKTREE_HINT + "\n" +
|
|
1120
1123
|
"git add -A\n" +
|
|
1121
1124
|
"git commit -m \"" + safeTitle + "\"\n\n" +
|
|
1122
|
-
"If the task's deliverable is runtime state (a cron definition, scheduler change, or dashboard/config state created outside the repo) and the repository genuinely needs no change, do NOT fabricate a commit: leave the branch with no commits ahead of
|
|
1125
|
+
"If the task's deliverable is runtime state (a cron definition, scheduler change, or dashboard/config state created outside the repo) and the repository genuinely needs no change, do NOT fabricate a commit: leave the branch with no commits ahead of the integration target and declare `repo_diff: none` in your report, naming the runtime-state deliverable. If you verified the deliverable is already on the integration target (a prior merge or hand-repair landed it — do NOT re-implement working code), make no commit and declare `repo_diff: none (already-merged: <sha>)` naming the integration-target commit that carries the work; the workflow verifies the sha is an ancestor of the integration target, and a false declaration fails the phase. Otherwise commit your changes normally.\n\n" +
|
|
1123
1126
|
(rejectionNotes ? "This is REWORK after rejection. Address these specific issues:\n" + rejectionNotes + "\n\n" : "") +
|
|
1124
1127
|
"Report back in plain prose: what you built and the outcome." +
|
|
1125
1128
|
(PUBLISH_TYPE === "npm" ? " End your report with the release: and version_bump: lines exactly as specified above — keep them on their own lines, lowercase, unrephrased — then a line `worktree: ` followed by the exact working directory path from above (copy it verbatim \u2014 it must match character-for-character), then a final line with exactly: VERDICT: PASS if the build is complete, VERDICT: FAIL if it is not." : " End your report with a line `worktree: ` followed by the exact working directory path from above (copy it verbatim \u2014 it must match character-for-character), then exactly one line: VERDICT: PASS if the build is complete, VERDICT: FAIL if it is not.");
|
|
@@ -1157,16 +1160,16 @@ while (i < STEPS.length) {
|
|
|
1157
1160
|
(mapperSpec ? "MAPPER'S SPEC (the builder was asked to implement exactly this):\n" + mapperSpec + "\n\n" : "Read the spec at exactly " + SPEC_PATH + " (fall back to the task description if the file is absent).\n\n") +
|
|
1158
1161
|
"Examine the code changes by running:\n" +
|
|
1159
1162
|
LIFECYCLE_ENV + LIFECYCLE + " inspect " + taskId + "\n\n" +
|
|
1160
|
-
"The inspect output is authoritative: it prints the task branch's actual tip commit (TIP) and every commit ahead of
|
|
1163
|
+
"The inspect output is authoritative: it prints the task branch's actual tip commit (TIP) and every commit ahead of the integration target (inspect prints the target name). Base your review ONLY on this output — do NOT run git log yourself to pick commits, and do NOT discuss commit hashes from any other source (they may come from stale rework rounds or a different repo).\n\n" +
|
|
1161
1164
|
"You can also read specific files in the worktree at:\n" +
|
|
1162
1165
|
WORKTREE_HINT + "/\n\n" +
|
|
1163
1166
|
"Check quality, correctness, and spec compliance.\n" +
|
|
1164
1167
|
(SURFACE_TERMINAL ? "TERMINAL UX REVIEW: judge the CLI surface against " + UX_DOCTRINE_PATH + " — help accuracy, error quality, exit codes, output clarity. Reject when the bar is not met.\n" : "") +
|
|
1165
1168
|
(SURFACE_ARTIFACT ? "ARTIFACT UX REVIEW: judge the rendered surface against " + UX_DOCTRINE_PATH + " — alignment, spacing, hierarchy, composition, balance, finish, correctness. Reject when the bar is not met.\n" : "") +
|
|
1166
1169
|
"Check that public-affecting changes have matching public doc updates (API.md or the published API contract). If the docs are missing or inaccurate, report what is stale, then end your report with exactly this line: VERDICT: FAIL.\n" +
|
|
1167
|
-
"If the branch has no commits ahead of
|
|
1170
|
+
"If the branch has no commits ahead of the integration target (inspect shows an empty commit log), approve ONLY if the Build summary declares `repo_diff: none` with (a) a plausible runtime-state deliverable (e.g. a cron created via the cron tool), or (b) an already-merged declaration `repo_diff: none (already-merged: <sha>)` AND the mechanical fact below confirms the sha verified. MECHANICAL FACT (computed by the workflow, never by the builder): already_merged sha = " + (alreadyMergedSha ? alreadyMergedSha + " (verified ancestor of the integration target: YES)" : "none declared") + ". Otherwise report 'no commits ahead of the integration target and no valid repo_diff: none declaration — the builder likely forgot to commit', then end your report with exactly this line: VERDICT: FAIL.\n" +
|
|
1168
1171
|
(PUBLISH_TYPE === "npm" ? "PACKAGE VERSION: this project publishes to the npm registry, and versions are assigned at publish time — never in branches. Two checks:\n" +
|
|
1169
|
-
"(a) The task branch must NOT have changed package.json's `version` field.
|
|
1172
|
+
"(a) The task branch must NOT have changed package.json's `version` field. First resolve the integration target: " + LIFECYCLE_ENV + LIFECYCLE + " integration-target. Then check: cd " + REPO_PATH + " && git diff <target>..." + TASK_BRANCH + " -- package.json (substitute the exact token integration-target printed — when it prints HEAD, use the literal word HEAD: `git diff HEAD...<branch>` diffs the detached checkout against the branch). If the branch touched `version` in any way, report 'versions are assigned at publish time, never in branches — remove the version change' in your notes, then end your report with exactly this line: VERDICT: FAIL.\n" +
|
|
1170
1173
|
"(b) The accepted Build report declares: " + releaseDecisionText() + ". " +
|
|
1171
1174
|
(releaseDecision
|
|
1172
1175
|
? "Validate this decision against the change: release must be 'yes' when the change is consumer-observable and 'no' when internal-only; the version_bump scope must fit the change (patch for fixes, minor for new behavior, major for breaking changes). If the decision is wrong or mis-scoped, report your notes, then end with exactly this line: VERDICT: FAIL."
|
|
@@ -1174,36 +1177,32 @@ while (i < STEPS.length) {
|
|
|
1174
1177
|
"Write your review as plain prose — findings, then decision. End your report with exactly one line: VERDICT: PASS if the work passes, VERDICT: FAIL if it fails.";
|
|
1175
1178
|
|
|
1176
1179
|
} else if (step.name === "Integrate") {
|
|
1177
|
-
instructions = "Merge the approved task branch into
|
|
1178
|
-
"
|
|
1179
|
-
"
|
|
1180
|
-
"
|
|
1181
|
-
"
|
|
1182
|
-
"
|
|
1183
|
-
"-
|
|
1180
|
+
instructions = "Merge the approved task branch into the integration target and push the result.\n" +
|
|
1181
|
+
"The integration target is the repo's current checkout — a branch name, or the literal word HEAD when detached (no branch name exists when detached). Print the exact token with: "+ LIFECYCLE_ENV + LIFECYCLE + " integration-target\n" +
|
|
1182
|
+
"NEVER run git checkout — the lifecycle merges into the current checkout; moving it abandons the line Publish verifies.\n\n" +
|
|
1183
|
+
"Read the staged publish base (return its stdout verbatim):\n" + crewCmd("get-provenance", { project_id: LAUNCH_PROJECT_ID }) + "\n" +
|
|
1184
|
+
"Then run: "+ LIFECYCLE_ENV + "WORKFLOW_RUN_ID=" + lockHolder + " CREW_STAGED_BASE=<provenance.source_commit, empty when null> integrate " + taskId + " \"merge: " + safeTitle + "\\\"\\n" +
|
|
1185
|
+
"The script merges, reconciles, records, and pushes under the merge lock. Informational only: RECONCILED, NO_REMOTE, NO_REMOTE_RECONCILE. Read the output:\n" +
|
|
1186
|
+
"- STAGED_BASE_MISMATCH: the line left the staged base (stray checkout). VERDICT: FAIL.\n" +
|
|
1187
|
+
"- MERGED_EMPTY — check FIRST (it contains the word MERGED): either a runtime-state deliverable or an already-merged sha the workflow verified (Build declared repo_diff: none). No lock taken, no new commit. Report 'merged empty: no repo changes'. VERDICT: PASS.\n" +
|
|
1188
|
+
"- MERGED: the merge landed; the script pushed inline. Read the push line: PUSHED — report the hash (detached prints PUSHED: origin/main (refspec HEAD:main)), VERDICT: PASS; NO_REMOTE_PUSH — no remote configured, VERDICT: PASS; ERROR after MERGED — a retry recovers the push; report it, VERDICT: FAIL.\n" +
|
|
1189
|
+
"- LOCK_HELD: 10-minute backoff exhausted. VERDICT: FAIL.\n" +
|
|
1190
|
+
"- CONFLICT: the plain merge failed — aborted, the target is clean, and your task still holds the merge lock. Do NOT fail yet. Resolve it:\n" +
|
|
1184
1191
|
"RESOLUTION:\n" +
|
|
1185
|
-
"R1. Refresh the merge lock FIRST (a long resolution must not silently lose the lock to the orphan sweep): "+ LIFECYCLE_ENV + " refresh-lock " + taskId + ". Create a scratch worktree WITH A NEW BRANCH (
|
|
1186
|
-
"R2. For each conflicted file, read the three sides: git show :1:<file> (base), git show :2:<file> (ours =
|
|
1187
|
-
"R3. Verify in the scratch worktree, in this order: (a) git diff --check is clean; (b) git grep -n '^<<<<<<<' -- . returns nothing; (c) node --check every changed .js file (conflicted files plus everything listed by git diff --name-only). If any check fails, refresh the merge lock ("+ LIFECYCLE_ENV + " refresh-lock " + taskId + "), then retry
|
|
1192
|
+
"R1. Refresh the merge lock FIRST (a long resolution must not silently lose the lock to the orphan sweep): "+ LIFECYCLE_ENV + " refresh-lock " + taskId + ". Create a scratch worktree WITH A NEW BRANCH (the integration target is checked out in the repo, so create the scratch worktree from HEAD — never check out another branch in the repo checkout): cd " + REPO_PATH + " && git worktree add -b resolve/" + taskId + " /tmp/crew-resolve-" + taskId + " HEAD. Reproduce the conflict in the scratch worktree: cd /tmp/crew-resolve-" + taskId + " && git merge " + TASK_BRANCH + ". This reproduces the exact conflict (the integration target has not moved — the lock was held throughout). The task branch " + TASK_BRANCH + " is never modified.\n" +
|
|
1193
|
+
"R2. For each conflicted file, read the three sides: git show :1:<file> (base), git show :2:<file> (ours = the integration target), git show :3:<file> (theirs = task branch). Resolve each hunk by keeping both sides' changes when they do not semantically overlap. Version-only hunks resolve to the higher semver (safety net). Never invent new behavior. Leave no markers.\n" +
|
|
1194
|
+
"R3. Verify in the scratch worktree, in this order: (a) git diff --check is clean; (b) git grep -n '^<<<<<<<' -- . returns nothing; (c) node --check every changed .js file (conflicted files plus everything listed by git diff --name-only). If any check fails, refresh the merge lock ("+ LIFECYCLE_ENV + " refresh-lock " + taskId + "), then retry using the failure output as context — max 3 attempts total.\n" +
|
|
1188
1195
|
"R4. Commit the resolution on resolve/" + taskId + ": git add -A && git commit -m \"resolve conflicts: " + taskId + "\".\n" +
|
|
1189
|
-
"R5. Back in " + REPO_PATH + ": git
|
|
1196
|
+
"R5. Back in " + REPO_PATH + ": git merge --ff-only resolve/" + taskId + " — merges the resolution into the current checkout (the integration target; no git checkout anywhere). It fast-forwards — the target has not moved under the lock. Report the merged hash.\n" +
|
|
1190
1197
|
"R6. Clean up: cd " + REPO_PATH + " && git worktree remove --force /tmp/crew-resolve-" + taskId + " && git branch -D resolve/" + taskId + ".\n" +
|
|
1191
|
-
"ESCALATE —
|
|
1192
|
-
"-
|
|
1193
|
-
"\n" +
|
|
1194
|
-
"
|
|
1195
|
-
"
|
|
1196
|
-
"
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
" NEVER force-push. Do not run any --force variant. The lifecycle's `integrate` already reconciled origin/main under the merge lock, so a rejection means the remote advanced after that reconcile — recover mechanically while you still hold the lock:\n" +
|
|
1200
|
-
" 1. Run: cd " + REPO_PATH + " && git fetch origin main && git merge --no-edit origin/main -m \"merge: push-time reconcile (" + taskId + ")\".\n" +
|
|
1201
|
-
" 2. If the merge conflicts, abort it (git merge --abort), report 'push-time reconcile conflicted — manual resolution required', then end your report with exactly this line: VERDICT: FAIL.\n" +
|
|
1202
|
-
" 3. If it merges cleanly, retry the push exactly once: git push origin main. If the second push still rejects, report 'push rejected twice as non-fast-forward', then end your report with exactly this line: VERDICT: FAIL.\n" +
|
|
1203
|
-
" 4. If the retry succeeds, report the merged commit hash.\n\n" +
|
|
1204
|
-
"Report back in plain prose — what happened at each step — and end your report with exactly one line: VERDICT: PASS or VERDICT: FAIL.";
|
|
1205
|
-
|
|
1206
|
-
} else if (step.name === "Publish") {
|
|
1198
|
+
"ESCALATE — 'conflict needs human resolution', VERDICT: FAIL — when: 3 attempts are exhausted; the conflict touches generated files, migrations, or public API contracts; or 'looks right + checks pass' is not sufficient for any other reason. On escalation, RELEASE THE LOCK so the task can be reworked later: run CREW_HOME=" + crewHome + " CREW_REPO=" + REPO_PATH + " " + MERGE_LOCK + " release " + taskId + " (release is keyed on task id; no PID needed). Do NOT run post-deploy on escalation — it would delete the untouched task branch the human still needs.\n" +
|
|
1199
|
+
"- ERROR: report it. VERDICT: FAIL.\n\n" +
|
|
1200
|
+
"R5-PUSH (manual R5 resolution only — the normal path pushed inline). Run: " + LIFECYCLE_ENV + LIFECYCLE + " push-target " + taskId + "\n" +
|
|
1201
|
+
"PUSHED — report the merged hash (detached prints PUSHED: origin/main (refspec HEAD:main)), VERDICT: PASS; PUSH_SKIPPED — no push attempted (no record + no lock); NO_REMOTE_PUSH — no remote; VERDICT: PASS; ERROR or CONFLICT — report it, VERDICT: FAIL.\n" +
|
|
1202
|
+
"NEVER force-push.\n\n" +
|
|
1203
|
+
"Report what happened at each step, ending with exactly one line: VERDICT: PASS or VERDICT: FAIL.";
|
|
1204
|
+
|
|
1205
|
+
} else if (step.name === "Publish") {
|
|
1207
1206
|
if (PUBLISH_TYPE === "npm") {
|
|
1208
1207
|
// Deterministic publish: the workflow computed the target version and the
|
|
1209
1208
|
// agent runs exactly one command — the pinned publish script. The script
|
|
@@ -1220,7 +1219,7 @@ while (i < STEPS.length) {
|
|
|
1220
1219
|
" bash " + PUBLISH_NPM + "\n\n" +
|
|
1221
1220
|
"Do NOT run npm, npm pack, npm publish, or the python publish script yourself. Do NOT compare local and registry versions. Do NOT decide whether to publish.\n\n" +
|
|
1222
1221
|
"If the command exits nonzero, put the script's PUBLISH_FAILED line in your report, then end your report with exactly this line: VERDICT: FAIL.\n" +
|
|
1223
|
-
"If the script's output contains PUBLISH_SKIPPED=no-lock-held, the publish was skipped gracefully: Integrate reported MERGED_EMPTY (no commits ahead of
|
|
1222
|
+
"If the script's output contains PUBLISH_SKIPPED=no-lock-held, the publish was skipped gracefully: Integrate reported MERGED_EMPTY (no commits ahead of the integration target), so no merge lock was taken and there is nothing to ship. Paste the marker block verbatim into your report, then end your report with exactly these three lines, in this order — lowercase, no trailing period, do not rephrase:\n" +
|
|
1224
1223
|
"TARGET_VERSION=" + publishTarget.target + " computed as " + publishTarget.base + " + " + publishTarget.scope + " → " + publishTarget.target + "\n" +
|
|
1225
1224
|
"skipped: no-lock-held (empty-diff Integrate — nothing merged, nothing to ship)\n" +
|
|
1226
1225
|
"VERDICT: PASS\n\n" +
|
|
@@ -1537,7 +1536,7 @@ while (i < STEPS.length) {
|
|
|
1537
1536
|
log("Publish pre-trigger baseline read failed for task " + taskId + " (" + (baselineErr && baselineErr.message ? baselineErr.message : baselineErr) + ") — receipt attribution skipped; durable audit-dir evidence is the only positive signal");
|
|
1538
1537
|
}
|
|
1539
1538
|
// See docs/decisions/publish-path.md#trigger-await: the artifact_edit call is awaited.
|
|
1540
|
-
var rebuildTrigger = null;
|
|
1539
|
+
var rebuildTrigger = null, triggerThrew = false;
|
|
1541
1540
|
try {
|
|
1542
1541
|
var triggerText = String(await agent(rebuildPrompt,
|
|
1543
1542
|
{ key: rebuildAttemptKey, label: "Triggering artifact rebuild" }) || "");
|
|
@@ -1557,7 +1556,18 @@ while (i < STEPS.length) {
|
|
|
1557
1556
|
}
|
|
1558
1557
|
} catch (triggerErr) {
|
|
1559
1558
|
log("Publish rebuild trigger for task " + taskId + " threw (" + (triggerErr && triggerErr.message ? triggerErr.message : triggerErr) + ") — outcome unknown until observation confirms it; the edit may have gone through");
|
|
1559
|
+
triggerThrew = true;
|
|
1560
1560
|
}
|
|
1561
|
+
// (2026-09-19, room #22) Submitted-on-issuance: see docs/decisions/publish-path.md#submitted-on-issuance.
|
|
1562
|
+
await recordPublishLedger({
|
|
1563
|
+
commit: mergeCommitForPublish,
|
|
1564
|
+
attempt: rebuildAttemptKey,
|
|
1565
|
+
agent_id: null,
|
|
1566
|
+
applied_report: publishAppliedObservation,
|
|
1567
|
+
manifest_before: preTriggerManifest,
|
|
1568
|
+
outcome: "submitted",
|
|
1569
|
+
detail: "trigger issuance: rebuild-trigger agent invoked; receipt/observation pending. Invocation only - not evidence of artifact_edit or build acceptance." + (triggerThrew ? "; trigger-threw" : "; trigger-returned")
|
|
1570
|
+
}, totalReworkCount, "issuance");
|
|
1561
1571
|
// Post-trigger observation (primary, not fallback): the workflow
|
|
1562
1572
|
// attributes the edit itself. First the in-flight build state — a
|
|
1563
1573
|
// build whose agent_id is new relative to the pre-trigger baseline
|
|
@@ -1677,8 +1687,8 @@ while (i < STEPS.length) {
|
|
|
1677
1687
|
applied_report: publishAppliedObservation,
|
|
1678
1688
|
manifest_before: preTriggerManifest,
|
|
1679
1689
|
outcome: "submitted",
|
|
1680
|
-
detail: "fire-and-forget trigger; build receipt captured by workflow-owned build-state observation (pre/post-trigger diff)"
|
|
1681
|
-
}, totalReworkCount);
|
|
1690
|
+
detail: "fire-and-forget trigger; build receipt captured by workflow-owned build-state observation (pre/post-trigger diff); observation, not re-issuance"
|
|
1691
|
+
}, totalReworkCount, "receipt");
|
|
1682
1692
|
} else {
|
|
1683
1693
|
var newAuditDirs = [];
|
|
1684
1694
|
try {
|
|
@@ -1809,10 +1819,6 @@ while (i < STEPS.length) {
|
|
|
1809
1819
|
// its outcome — submitted, rejected, or unknown (unknown is recorded
|
|
1810
1820
|
// at the park site above). A later run or human matches commit hash +
|
|
1811
1821
|
// attempt key against the builder's eventual completion.
|
|
1812
|
-
// (2026-09-16) The trigger is fire-and-forget: the observation above
|
|
1813
|
-
// already recorded the ledger's submitted line on both positive paths
|
|
1814
|
-
// and parked on unknown — there is no applied report to observe and
|
|
1815
|
-
// no rejection signal to record.
|
|
1816
1822
|
// (2026-09-18, H2 verdict-first) The verdict was decided exactly
|
|
1817
1823
|
// once above; dispatch on it. landed bypasses the receipt poll
|
|
1818
1824
|
// (the audit evidence already proved completion); an explicit
|
|
@@ -1946,21 +1952,12 @@ while (i < STEPS.length) {
|
|
|
1946
1952
|
log("Publish build landed for task " + taskId + " — provenance stamp deferred to parent content verification");
|
|
1947
1953
|
} else {
|
|
1948
1954
|
// STEP 1b durable audit-dir fallback (2026-09-15, task aadeccc3):
|
|
1949
|
-
// the poll
|
|
1950
|
-
//
|
|
1951
|
-
//
|
|
1952
|
-
//
|
|
1953
|
-
//
|
|
1954
|
-
//
|
|
1955
|
-
// that appeared during the attempt window is evidence a build
|
|
1956
|
-
// completed. Attribution is by window, not by build identity:
|
|
1957
|
-
// the poll's saw_stranger signal only catches stranger builds in
|
|
1958
|
-
// flight AT a check — a stranger that finished entirely inside
|
|
1959
|
-
// the window is indistinguishable, so any observed stranger
|
|
1960
|
-
// blocks attribution and the outcome stays unknown. This never
|
|
1961
|
-
// re-issues the edit and never stamps provenance — ok=true only
|
|
1962
|
-
// routes to the parent's independent content read-back, which
|
|
1963
|
-
// remains the real verification.
|
|
1955
|
+
// the poll observes only in-flight builds; a build that finished
|
|
1956
|
+
// inside the window leaves a durable audit dir. Diff the audit
|
|
1957
|
+
// listing against the pre-trigger snapshot: a new timestamped dir
|
|
1958
|
+
// is evidence a build completed. Attribution is by window, not by build identity: any
|
|
1959
|
+
// observed stranger blocks it. Never re-issues, never stamps;
|
|
1960
|
+
// ok=true routes to the parent's read-back (the verification).
|
|
1964
1961
|
//
|
|
1965
1962
|
// The poll end-state is read from the poll's own observations,
|
|
1966
1963
|
// not from build_done alone: a build in flight at the last check
|
|
@@ -2129,7 +2126,7 @@ while (i < STEPS.length) {
|
|
|
2129
2126
|
// artifact_edit would trigger a duplicate build.
|
|
2130
2127
|
if (publishSkippedNoLock) {
|
|
2131
2128
|
instructions = "Publish was skipped deterministically by the workflow before your step — do NOT call artifact_edit, artifact_status, setprovenance, or post-deploy yourself; doing so would disturb the finalized state.\n\n" +
|
|
2132
|
-
"Integrate reported MERGED_EMPTY (the task branch had no commits ahead of
|
|
2129
|
+
"Integrate reported MERGED_EMPTY (the task branch had no commits ahead of the integration target), so no merge lock was taken and there is nothing to ship. Post-deploy step finished per its return — do NOT run post-deploy yourself.\n\n" +
|
|
2133
2130
|
"Write plain prose describing the skip, then on its own line: VERDICT: PASS\n" +
|
|
2134
2131
|
"The VERDICT line must be the last line of your report.";
|
|
2135
2132
|
} else {
|
|
@@ -2158,7 +2155,7 @@ while (i < STEPS.length) {
|
|
|
2158
2155
|
// publish skip becomes a loud QA failure with evidence, not a pass.
|
|
2159
2156
|
var npmPublishCheck = (PUBLISH_TYPE === "npm" && releaseDecision && releaseDecision.release === "yes")
|
|
2160
2157
|
? "NPM PUBLISH CHECK: the accepted Build report declared release: yes, so this run's Publish phase must have published — UNLESS it was skipped deterministically on an empty-diff Integrate. Find this task's recorded Publish result: run in shell and return the stdout verbatim:\n" + crewCmd("get-state", { events_limit: 1 }) + "\n, then find the session for this task_id with step \"Publish\" (status completed) in the returned sessions array and read its session notes (the Publish agent's summary — event history does NOT carry it).\n" +
|
|
2161
|
-
"CHECK THE SKIP PATH FIRST: if the notes contain a line matching skipped: no-lock-held, Publish was skipped deterministically — Integrate reported MERGED_EMPTY (no commits ahead of
|
|
2158
|
+
"CHECK THE SKIP PATH FIRST: if the notes contain a line matching skipped: no-lock-held, Publish was skipped deterministically — Integrate reported MERGED_EMPTY (no commits ahead of the integration target), so no merge lock was taken and there was nothing to ship. Verify the notes contain that skip-marker line and do NOT contain a line matching published: muse-crew@. Do NOT run npm view and do NOT demand registry movement — nothing was supposed to ship. Report 'npm publish check: Publish skipped deterministically (empty-diff Integrate — nothing to ship)' and PASS this check.\n" +
|
|
2162
2159
|
"Only when the notes contain no skip marker must the publish have landed — run the full verification below.\n" +
|
|
2163
2160
|
"Extract the line matching TARGET_VERSION=<new-version> computed as <base> + <scope> → <new-version> (the workflow appends it to the Publish notes, so it is always present). If the line is missing, report 'npm publish verification failed: Publish notes did not carry the computed target version', then end your report with exactly this line: VERDICT: FAIL.\n" +
|
|
2164
2161
|
"Verify the bump SCOPE: the <scope> in that line MUST equal the accepted version_bump scope \"" + releaseDecision.version_bump + "\" — if it differs, report the mismatch, then end your report with exactly this line: VERDICT: FAIL. Verify the ARITHMETIC: <base> + <scope> must equal <new-version> (patch increments the last segment only, e.g. 0.3.0 + patch → 0.3.1; minor increments the middle and resets the last to 0; major increments the first and resets the rest to 0) — if the math is wrong, report it, then end your report with exactly this line: VERDICT: FAIL.\n" +
|
|
@@ -2209,7 +2206,7 @@ while (i < STEPS.length) {
|
|
|
2209
2206
|
"Public docs (API.md, README) are NOT source code — read them freely, exactly as a user would.\n" +
|
|
2210
2207
|
"Read the shared UX bar FIRST: " + UX_DOCTRINE_PATH + " — it is the bar the whole crew builds to, and your verdict judges against it point by point.\n\n" +
|
|
2211
2208
|
"STEP 1: Experiential terminal inspection — drive the CLI as a user would, one command at a time.\n" +
|
|
2212
|
-
"a. The CLI under test lives in " + REPO_PATH + " (the merged change is on
|
|
2209
|
+
"a. The CLI under test lives in " + REPO_PATH + " (the merged change is on the integration target there). Start like a new user: run --help. You may RUN the CLI; you may never READ its source.\n" +
|
|
2213
2210
|
"b. Terminal targets for this task: " + termTargetsLine + ".\n" +
|
|
2214
2211
|
"c. Bounded terminal loop — at most 8 commands. For each target: run it RIGHT (the happy path), then run it WRONG on purpose (bad flags, missing args, nonexistent files, empty input, contradictory flags). Error quality is half the grade: every failure must exit non-zero, say what went wrong in plain language, and tell the user the fix. A raw stack trace shown to a user is a defect — file it as one.\n" +
|
|
2215
2212
|
"d. Evidence: capture EVERY invocation as a transcript. Run: mkdir -p " + termEvidence + "\n" +
|
|
@@ -2432,19 +2429,19 @@ while (i < STEPS.length) {
|
|
|
2432
2429
|
var amCheck = await agent(
|
|
2433
2430
|
"Verify the builder's already-merged declaration.\n" +
|
|
2434
2431
|
"Run in shell and return the stdout verbatim:\n" +
|
|
2435
|
-
"cd " + REPO_PATH + " && git rev-parse --verify --quiet " + am.sha + " >/dev/null && git merge-base --is-ancestor " + am.sha + "
|
|
2432
|
+
"TARGET=$(" + LIFECYCLE_ENV + LIFECYCLE + " integration-target) && cd " + REPO_PATH + " && git rev-parse --verify --quiet " + am.sha + " >/dev/null && git merge-base --is-ancestor " + am.sha + " $TARGET && echo ALREADY_MERGED_YES || echo ALREADY_MERGED_NO",
|
|
2436
2433
|
{ key: "verify-already-merged" + (totalReworkCount > 0 ? "-r" + totalReworkCount : ""), label: "Verifying already-merged declaration" }
|
|
2437
2434
|
);
|
|
2438
2435
|
var amOut = (typeof amCheck === "string") ? amCheck : JSON.stringify(amCheck);
|
|
2439
2436
|
if (!/ALREADY_MERGED_YES/.test(amOut)) {
|
|
2440
|
-
log("Build already-merged declaration failed verification — " + am.sha + " is not an ancestor of
|
|
2437
|
+
log("Build already-merged declaration failed verification — " + am.sha + " is not an ancestor of the integration target — marking failed for retry");
|
|
2441
2438
|
await agent(
|
|
2442
2439
|
"Record already-merged verification failure.\n" +
|
|
2443
2440
|
"Run in shell and return the stdout verbatim:\n" + crewCmd("record-phase", {
|
|
2444
2441
|
task_id: taskId,
|
|
2445
2442
|
session: { id: activeSessionId, task_id: taskId, identity: step.identity, step: step.name, status: "failed",
|
|
2446
|
-
notes: "Build declared repo_diff: none (already-merged: " + am.sha + ") but " + am.sha + " is not an ancestor of
|
|
2447
|
-
event: { task_id: taskId, type: "failed", message: "Build already-merged declaration failed verification — " + am.sha + " not an ancestor of
|
|
2443
|
+
notes: "Build declared repo_diff: none (already-merged: " + am.sha + ") but " + am.sha + " is not an ancestor of the integration target in the configured repo. The declaration is fabricated or mistaken; the work is not on the integration target. Phase failed for retry" },
|
|
2444
|
+
event: { task_id: taskId, type: "failed", message: "Build already-merged declaration failed verification — " + am.sha + " not an ancestor of the integration target, phase failed, dispatcher will retry" }
|
|
2448
2445
|
}),
|
|
2449
2446
|
{ key: "record-already-merged-fail-" + step.name, label: "Recording already-merged verification failure" }
|
|
2450
2447
|
);
|
|
@@ -2452,13 +2449,13 @@ while (i < STEPS.length) {
|
|
|
2452
2449
|
__hatchWorkflowControl: "blocked",
|
|
2453
2450
|
result: {
|
|
2454
2451
|
blocked_reason: "Build already-merged declaration failed verification",
|
|
2455
|
-
message: "The builder declared repo_diff: none (already-merged: " + am.sha + ") but " + am.sha + " is not an ancestor of
|
|
2452
|
+
message: "The builder declared repo_diff: none (already-merged: " + am.sha + ") but " + am.sha + " is not an ancestor of the integration target. The work is not on the integration target; the phase is marked failed and the dispatcher will retry Build.",
|
|
2456
2453
|
task_id: taskId
|
|
2457
2454
|
}
|
|
2458
2455
|
};
|
|
2459
2456
|
}
|
|
2460
2457
|
alreadyMergedSha = am.sha;
|
|
2461
|
-
log("Build already-merged declaration verified: " + am.sha + " is an ancestor of
|
|
2458
|
+
log("Build already-merged declaration verified: " + am.sha + " is an ancestor of the integration target");
|
|
2462
2459
|
}
|
|
2463
2460
|
}
|
|
2464
2461
|
|
|
@@ -2529,7 +2526,7 @@ while (i < STEPS.length) {
|
|
|
2529
2526
|
integrateVerifyOut = "";
|
|
2530
2527
|
}
|
|
2531
2528
|
if (/^VERIFIED:/m.test(integrateVerifyOut)) {
|
|
2532
|
-
log("Integrate verified for task " + taskId + ": task branch tip is an ancestor of
|
|
2529
|
+
log("Integrate verified for task " + taskId + ": task branch tip is an ancestor of the integration target");
|
|
2533
2530
|
} else {
|
|
2534
2531
|
var integrateVerifyReason = integrateVerifyOut
|
|
2535
2532
|
? integrateVerifyOut.split("\n")[0].slice(0, 200)
|
|
@@ -2712,12 +2709,12 @@ while (i < STEPS.length) {
|
|
|
2712
2709
|
}
|
|
2713
2710
|
rejectionNotes = summary;
|
|
2714
2711
|
// Already-merged corrective (room #16 blocker 11): when Review rejected
|
|
2715
|
-
// an empty branch but the work is already on
|
|
2712
|
+
// an empty branch but the work is already on the integration target (the workflow verified
|
|
2716
2713
|
// the sha), Wren must declare it — not re-implement or re-commit
|
|
2717
2714
|
// already-landed work. Scoped to the empty-branch rejection; any other
|
|
2718
2715
|
// rejection already carries its own specific notes.
|
|
2719
|
-
if (step.name === "Review" && alreadyMergedSha && /no commits ahead of main/i.test(summary)) {
|
|
2720
|
-
rejectionNotes += "\n\nCORRECTIVE (from the workflow, not the reviewer): the deliverable is already on
|
|
2716
|
+
if (step.name === "Review" && alreadyMergedSha && /no commits ahead of (main|the integration target)/i.test(summary)) {
|
|
2717
|
+
rejectionNotes += "\n\nCORRECTIVE (from the workflow, not the reviewer): the deliverable is already on the integration target — the workflow mechanically verified that " + alreadyMergedSha + " is an ancestor of the integration target. Do NOT re-implement the work and do NOT create a new commit for it. In your Build report, declare exactly: repo_diff: none (already-merged: " + alreadyMergedSha + ") — then end with VERDICT: PASS.";
|
|
2721
2718
|
log("Rework corrective appended for task " + taskId + ": already-merged " + alreadyMergedSha + " — Wren must declare, not rebuild");
|
|
2722
2719
|
}
|
|
2723
2720
|
i = BUILD_INDEX;
|