muse-crew 0.14.0 → 0.14.2

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.
@@ -132,7 +132,7 @@ if (!taskId) {
132
132
  // See docs/decisions/workflow-core.md#closeout-envelope: the work agent returns the runtime's native envelope; verdict extracted mechanically.
133
133
  const VERDICT_STEPS = ["Build", "Review", "QA", "Reproduce", "Integrate", "Publish"];
134
134
  function extractVerdict(workerText) {
135
- // The verdict is the LAST VERDICT: PASS/FAIL in the report (contract: end
135
+ // The verdict is the LAST VERDICT: PASS/FAIL in the report (contract: end
136
136
  // your report with the verdict). This ignores literal VERDICT strings echoed
137
137
  // from the worker's instructions (which contain quoted examples). Fail
138
138
  // closed if: no verdict found, the last verdict is not in the trailing 100
@@ -442,9 +442,7 @@ async function recordPublishLedger(entry, rework) {
442
442
  label: "Recording publish attempt in ledger",
443
443
  schema: { type: "object", properties: { result: { type: "string" } }, required: ["result"] } }
444
444
  );
445
- var ok = !!(res && res.result && res.result.indexOf("LEDGER_OK") !== -1);
446
- log("Publish ledger: outcome '" + entry.outcome + "' for task " + taskId +
447
- (ok ? " recorded." : " NOT confirmed (" + ((res && res.result) || "no output") + ")"));
445
+ log("Noted publish outcome '" + entry.outcome + "' for task " + taskId + " in ledger");
448
446
  } catch (e) {
449
447
  log("Publish ledger: write failed for task " + taskId + " (non-fatal, observability only): " + (e && e.message ? e.message : e));
450
448
  }
@@ -687,7 +685,7 @@ let releaseDecision = null; // { release: "yes"|"no", version_bump: "patch"|"min
687
685
  // Already-merged idempotency: the verified sha from the builder's
688
686
  // `repo_diff: none (already-merged: <sha>)` declaration (null when the
689
687
  // builder made commits or declared a runtime-state deliverable). The
690
- // workflow verifies the sha is an ancestor of main at Build closeout;
688
+ // workflow verifies the sha is an ancestor of the integration target at Build closeout;
691
689
  // Review's no-diff branch reads this, never the builder's prose.
692
690
  let alreadyMergedSha = null;
693
691
  // Deterministic publish target — computed by the workflow (registry base +
@@ -1228,7 +1226,7 @@ while (i < STEPS.length) {
1228
1226
  "cd " + WORKTREE_HINT + "\n" +
1229
1227
  "git add -A\n" +
1230
1228
  "git commit -m \"fix: " + safeTitle + "\"\n\n" +
1231
- "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 main and declare `repo_diff: none` in your report, naming the runtime-state deliverable. If you verified the deliverable is already on main (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 main commit that carries the work; the workflow verifies the sha is an ancestor of main, and a false declaration fails the phase. Otherwise commit your changes normally.\n\n" +
1229
+ "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" +
1232
1230
  (rejectionNotes ? "This is REWORK after rejection. Address these specific issues:\n" + rejectionNotes + "\n\n" : "") +
1233
1231
  "Report back in plain prose: what you built and the outcome." +
1234
1232
  (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.");
@@ -1266,16 +1264,16 @@ while (i < STEPS.length) {
1266
1264
  (mapperSpec ? "MAPPER'S SPEC (the builder was asked to implement exactly this):\n" + mapperSpec + "\n\n" : "Read the spec (from the task description or spec files under " + crewHome + "/).\n\n") +
1267
1265
  "Examine the code changes by running:\n" +
1268
1266
  LIFECYCLE_ENV + LIFECYCLE + " inspect " + taskId + "\n\n" +
1269
- "The inspect output is authoritative: it prints the task branch's actual tip commit (TIP) and every commit ahead of main. 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" +
1267
+ "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" +
1270
1268
  "You can also read specific files in the worktree at:\n" +
1271
1269
  WORKTREE_HINT + "/\n\n" +
1272
1270
  "Check quality, correctness, and spec compliance.\n" +
1273
1271
  (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" : "") +
1274
1272
  (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" : "") +
1275
1273
  "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" +
1276
- "If the branch has no commits ahead of main (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 main: YES)" : "none declared") + ". Otherwise report 'no commits ahead of main and no valid repo_diff: none declaration — the builder likely forgot to commit', then end your report with exactly this line: VERDICT: FAIL.\n" +
1274
+ "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" +
1277
1275
  (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" +
1278
- "(a) The task branch must NOT have changed package.json's `version` field. Check: cd " + REPO_PATH + " && git diff main..." + TASK_BRANCH + " -- package.json. 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" +
1276
+ "(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" +
1279
1277
  "(b) The accepted Build report declares: " + releaseDecisionText() + ". " +
1280
1278
  (releaseDecision
1281
1279
  ? "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."
@@ -1283,36 +1281,32 @@ while (i < STEPS.length) {
1283
1281
  "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.";
1284
1282
 
1285
1283
  } else if (step.name === "Integrate") {
1286
- instructions = "Merge the approved task branch into main.\n\n" +
1287
- "Run: "+ LIFECYCLE_ENV + "WORKFLOW_RUN_ID=" + lockHolder + " integrate " + taskId + " \"merge: fix: " + safeTitle + "\"\n\n" +
1288
- "Read the output:\n" +
1289
- "- If it contains MERGED, integration succeeded. Report the merged commit hash.\n" +
1290
- "- If it contains MERGED_EMPTY, the branch had no commits ahead of main (declared by Build as repo_diff: none — either a runtime-state deliverable or an already-merged sha the workflow verified). Integration succeeded vacuously: the merge lock was NOT taken and there is no new commit. Report 'merged empty: no repo changes — deliverable was runtime state or already on main', then end your report with exactly this line: VERDICT: PASS. SKIP STEP 2 (push): there is no new commit to push.\n" +
1291
- "- If it contains LOCK_HELD, another task holds the merge lock (mid Integrate/Publish) and the 10-minute bounded backoff is exhausted. Report 'merge lock held after bounded backoff', then end your report with exactly this line: VERDICT: FAIL.\n" +
1292
- "- If it contains CONFLICT, the plain merge failed — the merge was aborted, main is clean, and your task still holds the merge lock. Do NOT fail yet. Resolve it:\n" +
1284
+ instructions = "Merge the approved task branch into the integration target and push the result.\n" +
1285
+ "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" +
1286
+ "NEVER run git checkout — the lifecycle merges into the current checkout; moving it abandons the line Publish verifies.\n\n" +
1287
+ "Read the staged publish base (return its stdout verbatim):\n" + crewCmd("get-provenance", { project_id: LAUNCH_PROJECT_ID }) + "\n" +
1288
+ "Then run: "+ LIFECYCLE_ENV + "WORKFLOW_RUN_ID=" + lockHolder + " CREW_STAGED_BASE=<provenance.source_commit, empty when null> integrate " + taskId + " \"merge: fix: " + safeTitle + "\\\"\\n" +
1289
+ "The script merges, reconciles, records, and pushes under the merge lock. Informational only: RECONCILED, NO_REMOTE, NO_REMOTE_RECONCILE. Read the output:\n" +
1290
+ "- STAGED_BASE_MISMATCH: the line left the staged base (stray checkout). VERDICT: FAIL.\n" +
1291
+ "- 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" +
1292
+ "- MERGED: the merge landed; the script pushed inline. Read the push line: PUSHED — report the hash, VERDICT: PASS; PUSH_SKIPPED — the detached line is room-local (the merge commit + record are the evidence), VERDICT: PASS; NO_REMOTE_PUSH — no remote configured, VERDICT: PASS; ERROR after MERGED — a retry recovers the push; report it, VERDICT: FAIL.\n" +
1293
+ "- LOCK_HELD: 10-minute backoff exhausted. VERDICT: FAIL.\n" +
1294
+ "- 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" +
1293
1295
  "RESOLUTION:\n" +
1294
- "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 (main is already checked out in the repo checkout, so git forbids checking it out a second time): cd " + REPO_PATH + " && git worktree add -b resolve/" + taskId + " /tmp/crew-resolve-" + taskId + " main. Reproduce the conflict in the scratch worktree: cd /tmp/crew-resolve-" + taskId + " && git merge " + TASK_BRANCH + ". This reproduces the exact conflict (main has not moved — the lock was held throughout). The task branch " + TASK_BRANCH + " is never modified.\n" +
1295
- "R2. For each conflicted file, read the three sides: git show :1:<file> (base), git show :2:<file> (ours = main), 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" +
1296
- "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 the resolution using the failure output as context — max 3 attempts total.\n" +
1296
+ "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" +
1297
+ "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" +
1298
+ "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" +
1297
1299
  "R4. Commit the resolution on resolve/" + taskId + ": git add -A && git commit -m \"resolve conflicts: " + taskId + "\".\n" +
1298
- "R5. Back in " + REPO_PATH + ": git checkout main && git merge --ff-only resolve/" + taskId + ". This fast-forwards — main has not moved while the lock was held. Then STEP 2 applies: git push origin main. Report the merged commit hash.\n" +
1300
+ "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" +
1299
1301
  "R6. Clean up: cd " + REPO_PATH + " && git worktree remove --force /tmp/crew-resolve-" + taskId + " && git branch -D resolve/" + taskId + ".\n" +
1300
- "ESCALATE — report 'conflict needs human resolution', then end your report with exactly this line: 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" +
1301
- "- If it contains ERROR, something else failed. Report the error, then end your report with exactly this line: VERDICT: FAIL.\n\n" +
1302
- "\n" +
1303
- "STEP 2: Push the merged main to the remote repository.\n" +
1304
- "First, refresh the merge lock — a slow push must not let the 600s lease expire under you: " + LIFECYCLE_ENV + LIFECYCLE + " refresh-lock " + taskId + ". If the refresh fails, report 'merge lock lost before push' and end your report with exactly this line: VERDICT: FAIL. Do not push without the lock.\n" +
1305
- "Run: cd " + REPO_PATH + " && git push origin main\n" +
1306
- "- If the push succeeds, report the merged commit hash.\n" +
1307
- "- If the push is rejected as non-fast-forward (the remote has commits not present locally),\n" +
1308
- " 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" +
1309
- " 1. Run: cd " + REPO_PATH + " && git fetch origin main && git merge --no-edit origin/main -m \"merge: push-time reconcile (" + taskId + ")\".\n" +
1310
- " 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" +
1311
- " 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" +
1312
- " 4. If the retry succeeds, report the merged commit hash.\n\n" +
1313
- "Report back in plain prose — what happened at each step — and end your report with exactly one line: VERDICT: PASS or VERDICT: FAIL.";
1314
-
1315
- } else if (step.name === "Publish") {
1302
+ "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" +
1303
+ "- ERROR: report it. VERDICT: FAIL.\n\n" +
1304
+ "R5-PUSH (manual R5 resolution only — the normal path pushed inline). Run: " + LIFECYCLE_ENV + LIFECYCLE + " push-target " + taskId + "\n" +
1305
+ "PUSHED — report the merged hash, VERDICT: PASS; PUSH_SKIPPED or NO_REMOTE_PUSH — nothing to push, VERDICT: PASS; ERROR or CONFLICT — report it, VERDICT: FAIL.\n" +
1306
+ "NEVER force-push.\n\n" +
1307
+ "Report what happened at each step, ending with exactly one line: VERDICT: PASS or VERDICT: FAIL.";
1308
+
1309
+ } else if (step.name === "Publish") {
1316
1310
  if (PUBLISH_TYPE === "npm") {
1317
1311
  // Deterministic publish: the workflow computed the target version and the
1318
1312
  // agent runs exactly one command — the pinned publish script. The script
@@ -1329,7 +1323,7 @@ while (i < STEPS.length) {
1329
1323
  " bash " + PUBLISH_NPM + "\n\n" +
1330
1324
  "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" +
1331
1325
  "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" +
1332
- "If the script's output contains PUBLISH_SKIPPED=no-lock-held, the publish was skipped gracefully: Integrate reported MERGED_EMPTY (no commits ahead of main), 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" +
1326
+ "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" +
1333
1327
  "TARGET_VERSION=" + publishTarget.target + " computed as " + publishTarget.base + " + " + publishTarget.scope + " → " + publishTarget.target + "\n" +
1334
1328
  "skipped: no-lock-held (empty-diff Integrate — nothing merged, nothing to ship)\n" +
1335
1329
  "VERDICT: PASS\n\n" +
@@ -2192,11 +2186,11 @@ while (i < STEPS.length) {
2192
2186
  if (!postDeploy.deployed) {
2193
2187
  return await parkTask("Post-deploy failed after a skipped publish: " + (postDeploy.output || "no output") + ". Nothing was published; worktree cleanup and lock state unknown — human attention needed.");
2194
2188
  }
2195
- log("Publish skipped cleanly for task " + taskId + " (no lock held) — post-deploy finalized cleanup");
2189
+ log("Publish skipped cleanly for task " + taskId + " (no lock held) — post-deploy step finished");
2196
2190
  } else {
2197
2191
  if (publishFailure) {
2198
2192
  return await parkTask(publishFailure + (postDeploy.deployed
2199
- ? " Post-deploy finalized cleanup."
2193
+ ? " Post-deploy step finished (cleanup status unknown)."
2200
2194
  : " Post-deploy also failed (" + (postDeploy.output || "no output") + ") — worktree and lock state unknown."));
2201
2195
  }
2202
2196
  if (!publishBuildLanded) {
@@ -2209,7 +2203,7 @@ while (i < STEPS.length) {
2209
2203
  // commit (design §1.5: "the trigger was sent for commit <short-sha>").
2210
2204
  if (publishUnknownFields) publishUnknownFields.commitShortSha = mergeCommitShortForPublish;
2211
2205
  return await parkTask(composeUnattributedParkReason(publishUnknownFields) + (postDeploy.deployed
2212
- ? " Post-deploy finalized cleanup."
2206
+ ? " Post-deploy step finished (cleanup status unknown)."
2213
2207
  : " Post-deploy also failed (" + (postDeploy.output || "no output") + ") — worktree and lock state unknown."));
2214
2208
  }
2215
2209
  if (!postDeploy.deployed) {
@@ -2238,7 +2232,7 @@ while (i < STEPS.length) {
2238
2232
  // artifact_edit would trigger a duplicate build.
2239
2233
  if (publishSkippedNoLock) {
2240
2234
  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" +
2241
- "Integrate reported MERGED_EMPTY (the task branch had no commits ahead of main), so no merge lock was taken and there is nothing to ship. The workflow finalized cleanup via post-deploy.\n\n" +
2235
+ "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" +
2242
2236
  "Write plain prose describing the skip, then on its own line: VERDICT: PASS\n" +
2243
2237
  "The VERDICT line must be the last line of your report.";
2244
2238
  } else {
@@ -2267,7 +2261,7 @@ while (i < STEPS.length) {
2267
2261
  // publish skip becomes a loud QA failure with evidence, not a pass.
2268
2262
  var npmPublishCheck = (PUBLISH_TYPE === "npm" && releaseDecision && releaseDecision.release === "yes")
2269
2263
  ? "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" +
2270
- "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 main), 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" +
2264
+ "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" +
2271
2265
  "Only when the notes contain no skip marker must the publish have landed — run the full verification below.\n" +
2272
2266
  "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" +
2273
2267
  "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" +
@@ -2320,7 +2314,7 @@ while (i < STEPS.length) {
2320
2314
  "Public docs (API.md, README) are NOT source code — read them freely, exactly as a user would.\n" +
2321
2315
  "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" +
2322
2316
  "STEP 1: Experiential terminal inspection — drive the fixed CLI as a user would, one command at a time, and verify the reported bug is actually fixed.\n" +
2323
- "a. The CLI under test lives in " + REPO_PATH + " (the merged change is on main there). Start like a new user: run --help. You may RUN the CLI; you may never READ its source.\n" +
2317
+ "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" +
2324
2318
  "b. Terminal targets for this task: " + btermTargetsLine + ".\n" +
2325
2319
  "c. Bounded terminal loop — at most 8 commands. For each target: run it RIGHT (the happy path — the reported bug's scenario first), 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" +
2326
2320
  "d. Evidence: capture EVERY invocation as a transcript. Run: mkdir -p " + btermEvidence + "\n" +
@@ -2720,19 +2714,19 @@ while (i < STEPS.length) {
2720
2714
  var amCheck = await agent(
2721
2715
  "Verify the builder's already-merged declaration.\n" +
2722
2716
  "Run in shell and return the stdout verbatim:\n" +
2723
- "cd " + REPO_PATH + " && git rev-parse --verify --quiet " + am.sha + " >/dev/null && git merge-base --is-ancestor " + am.sha + " main && echo ALREADY_MERGED_YES || echo ALREADY_MERGED_NO",
2717
+ "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",
2724
2718
  { key: "verify-already-merged" + (totalReworkCount > 0 ? "-r" + totalReworkCount : ""), label: "Verifying already-merged declaration" }
2725
2719
  );
2726
2720
  var amOut = (typeof amCheck === "string") ? amCheck : JSON.stringify(amCheck);
2727
2721
  if (!/ALREADY_MERGED_YES/.test(amOut)) {
2728
- log("Build already-merged declaration failed verification — " + am.sha + " is not an ancestor of main — marking failed for retry");
2722
+ log("Build already-merged declaration failed verification — " + am.sha + " is not an ancestor of the integration target — marking failed for retry");
2729
2723
  await agent(
2730
2724
  "Record already-merged verification failure.\n" +
2731
2725
  "Run in shell and return the stdout verbatim:\n" + crewCmd("record-phase", {
2732
2726
  task_id: taskId,
2733
2727
  session: { id: activeSessionId, task_id: taskId, identity: step.identity, step: step.name, status: "failed",
2734
- notes: "Build declared repo_diff: none (already-merged: " + am.sha + ") but " + am.sha + " is not an ancestor of main in the configured repo. The declaration is fabricated or mistaken; the work is not on main. Phase failed for retry" },
2735
- event: { task_id: taskId, type: "failed", message: "Build already-merged declaration failed verification — " + am.sha + " not an ancestor of main, phase failed, dispatcher will retry" }
2728
+ 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" },
2729
+ 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" }
2736
2730
  }),
2737
2731
  { key: "record-already-merged-fail-" + step.name, label: "Recording already-merged verification failure" }
2738
2732
  );
@@ -2740,13 +2734,13 @@ while (i < STEPS.length) {
2740
2734
  __hatchWorkflowControl: "blocked",
2741
2735
  result: {
2742
2736
  blocked_reason: "Build already-merged declaration failed verification",
2743
- message: "The builder declared repo_diff: none (already-merged: " + am.sha + ") but " + am.sha + " is not an ancestor of main. The work is not on main; the phase is marked failed and the dispatcher will retry Build.",
2737
+ 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.",
2744
2738
  task_id: taskId
2745
2739
  }
2746
2740
  };
2747
2741
  }
2748
2742
  alreadyMergedSha = am.sha;
2749
- log("Build already-merged declaration verified: " + am.sha + " is an ancestor of main");
2743
+ log("Build already-merged declaration verified: " + am.sha + " is an ancestor of the integration target");
2750
2744
  }
2751
2745
  }
2752
2746
 
@@ -2775,7 +2769,7 @@ while (i < STEPS.length) {
2775
2769
  integrateVerifyOut = "";
2776
2770
  }
2777
2771
  if (/^VERIFIED:/m.test(integrateVerifyOut)) {
2778
- log("Integrate verified for task " + taskId + ": task branch tip is an ancestor of main");
2772
+ log("Integrate verified for task " + taskId + ": task branch tip is an ancestor of the integration target");
2779
2773
  } else {
2780
2774
  var integrateVerifyReason = integrateVerifyOut
2781
2775
  ? integrateVerifyOut.split("\n")[0].slice(0, 200)
@@ -2831,11 +2825,7 @@ while (i < STEPS.length) {
2831
2825
  { key: attemptKey("publish-provenance-refresh-" + taskId, totalReworkCount), label: "Refreshing dashboard provenance after crew release",
2832
2826
  schema: { type: "object", properties: { refreshed: { type: "boolean" }, reason: { type: "string" }, crew_release: { type: "string" }, published_at: { type: "string" } }, required: ["refreshed"] } }
2833
2827
  );
2834
- if (provRefresh.refreshed) {
2835
- log("Provenance refreshed for task " + taskId + ": crew_release " + provRefresh.crew_release);
2836
- } else if (provRefresh.reason === "no-record") {
2837
- log("Provenance refresh skipped for task " + taskId + ": no existing record to refresh (fresh instance — the dashboard's first artifact publish will create it)");
2838
- } else {
2828
+ if (!provRefresh.refreshed && provRefresh.reason !== "no-record") {
2839
2829
  return await parkTask("Provenance refresh failed after a verified npm publish (crew_release " + (provRefresh.crew_release || "unknown") + "). The release is live but the dashboard record is stale — fail-closed.");
2840
2830
  }
2841
2831
  } catch (e) {
@@ -2940,12 +2930,12 @@ while (i < STEPS.length) {
2940
2930
  }
2941
2931
  rejectionNotes = summary;
2942
2932
  // Already-merged corrective (room #16 blocker 11): when Review rejected
2943
- // an empty branch but the work is already on main (the workflow verified
2933
+ // an empty branch but the work is already on the integration target (the workflow verified
2944
2934
  // the sha), Wren must declare it — not re-implement or re-commit
2945
2935
  // already-landed work. Scoped to the empty-branch rejection; any other
2946
2936
  // rejection already carries its own specific notes.
2947
- if (step.name === "Review" && alreadyMergedSha && /no commits ahead of main/i.test(summary)) {
2948
- rejectionNotes += "\n\nCORRECTIVE (from the workflow, not the reviewer): the deliverable is already on main — the workflow mechanically verified that " + alreadyMergedSha + " is an ancestor of main. 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.";
2937
+ if (step.name === "Review" && alreadyMergedSha && /no commits ahead of (main|the integration target)/i.test(summary)) {
2938
+ 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.";
2949
2939
  log("Rework corrective appended for task " + taskId + ": already-merged " + alreadyMergedSha + " — Wren must declare, not rebuild");
2950
2940
  }
2951
2941
  i = BUILD_INDEX;
@@ -501,9 +501,7 @@ async function recordPublishLedger(entry, rework) {
501
501
  label: "Recording publish attempt in ledger",
502
502
  schema: { type: "object", properties: { result: { type: "string" } }, required: ["result"] } }
503
503
  );
504
- var ok = !!(res && res.result && res.result.indexOf("LEDGER_OK") !== -1);
505
- log("Publish ledger: outcome '" + entry.outcome + "' for task " + taskId +
506
- (ok ? " recorded." : " NOT confirmed (" + ((res && res.result) || "no output") + ")"));
504
+ log("Noted publish outcome '" + entry.outcome + "' for task " + taskId + " in ledger");
507
505
  } catch (e) {
508
506
  log("Publish ledger: write failed for task " + taskId + " (non-fatal, observability only): " + (e && e.message ? e.message : e));
509
507
  }
@@ -692,7 +690,7 @@ let releaseDecision = null; // { release: "yes"|"no", version_bump: "patch"|"min
692
690
  // Already-merged idempotency: the verified sha from the builder's
693
691
  // `repo_diff: none (already-merged: <sha>)` declaration (null when the
694
692
  // builder made commits or declared a runtime-state deliverable). The
695
- // workflow verifies the sha is an ancestor of main at Build closeout;
693
+ // workflow verifies the sha is an ancestor of the integration target at Build closeout;
696
694
  // Review's no-diff branch reads this, never the builder's prose.
697
695
  let alreadyMergedSha = null;
698
696
  // Deterministic publish target — computed by the workflow (registry base +
@@ -1134,7 +1132,7 @@ while (i < STEPS.length) {
1134
1132
  "cd " + WORKTREE_HINT + "\n" +
1135
1133
  "git add -A\n" +
1136
1134
  "git commit -m \"chore: " + safeTitle + "\"\n\n" +
1137
- "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 main and declare `repo_diff: none` in your report, naming the runtime-state deliverable. If you verified the deliverable is already on main (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 main commit that carries the work; the workflow verifies the sha is an ancestor of main, and a false declaration fails the phase. Otherwise commit your changes normally.\n\n" +
1135
+ "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" +
1138
1136
  (rejectionNotes ? "REWORK after rejection. Address:\n" + rejectionNotes + "\n\n" : "") +
1139
1137
  "Report back in plain prose: what you built and the outcome." +
1140
1138
  (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.");
@@ -1172,16 +1170,16 @@ while (i < STEPS.length) {
1172
1170
  (mapperSpec ? "MAPPER'S SPEC (the builder was asked to implement exactly this):\n" + mapperSpec + "\n\n" : "Read the spec from the task description.\n\n") +
1173
1171
  "Examine the code changes by running:\n" +
1174
1172
  LIFECYCLE_ENV + LIFECYCLE + " inspect " + taskId + "\n\n" +
1175
- "The inspect output is authoritative: it prints the task branch's actual tip commit (TIP) and every commit ahead of main. 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" +
1173
+ "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" +
1176
1174
  "You can also read specific files in the worktree at:\n" +
1177
1175
  WORKTREE_HINT + "/\n\n" +
1178
1176
  "Check quality, correctness, spec compliance.\n" +
1179
1177
  (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" : "") +
1180
1178
  (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" : "") +
1181
1179
  "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" +
1182
- "If the branch has no commits ahead of main (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 main: YES)" : "none declared") + ". Otherwise report 'no commits ahead of main and no valid repo_diff: none declaration — the builder likely forgot to commit', then end your report with exactly this line: VERDICT: FAIL.\n" +
1180
+ "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" +
1183
1181
  (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" +
1184
- "(a) The task branch must NOT have changed package.json's `version` field. Check: cd " + REPO_PATH + " && git diff main..." + TASK_BRANCH + " -- package.json. 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" +
1182
+ "(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" +
1185
1183
  "(b) The accepted Build report declares: " + releaseDecisionText() + ". " +
1186
1184
  (releaseDecision
1187
1185
  ? "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."
@@ -1189,36 +1187,32 @@ while (i < STEPS.length) {
1189
1187
  "Write your review as plain prose — findings, then decision. End your report with exactly one line: VERDICT: PASS if it passes, VERDICT: FAIL if it fails.";
1190
1188
 
1191
1189
  } else if (step.name === "Integrate") {
1192
- instructions = "Merge the approved task branch into main.\n\n" +
1193
- "Run: "+ LIFECYCLE_ENV + "WORKFLOW_RUN_ID=" + lockHolder + " integrate " + taskId + " \"merge: chore: " + safeTitle + "\"\n\n" +
1194
- "Read the output:\n" +
1195
- "- If it contains MERGED, integration succeeded. Report the merged commit hash.\n" +
1196
- "- If it contains MERGED_EMPTY, the branch had no commits ahead of main (declared by Build as repo_diff: none — either a runtime-state deliverable or an already-merged sha the workflow verified). Integration succeeded vacuously: the merge lock was NOT taken and there is no new commit. Report 'merged empty: no repo changes — deliverable was runtime state or already on main', then end your report with exactly this line: VERDICT: PASS. SKIP STEP 2 (push): there is no new commit to push.\n" +
1197
- "- If it contains LOCK_HELD, another task holds the merge lock (mid Integrate/Publish) and the 10-minute bounded backoff is exhausted. Report 'merge lock held after bounded backoff', then end your report with exactly this line: VERDICT: FAIL.\n" +
1198
- "- If it contains CONFLICT, the plain merge failed — the merge was aborted, main is clean, and your task still holds the merge lock. Do NOT fail yet. Resolve it:\n" +
1190
+ instructions = "Merge the approved task branch into the integration target and push the result.\n" +
1191
+ "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" +
1192
+ "NEVER run git checkout — the lifecycle merges into the current checkout; moving it abandons the line Publish verifies.\n\n" +
1193
+ "Read the staged publish base (return its stdout verbatim):\n" + crewCmd("get-provenance", { project_id: LAUNCH_PROJECT_ID }) + "\n" +
1194
+ "Then run: "+ LIFECYCLE_ENV + "WORKFLOW_RUN_ID=" + lockHolder + " CREW_STAGED_BASE=<provenance.source_commit, empty when null> integrate " + taskId + " \"merge: chore: " + safeTitle + "\\\"\\n" +
1195
+ "The script merges, reconciles, records, and pushes under the merge lock. Informational only: RECONCILED, NO_REMOTE, NO_REMOTE_RECONCILE. Read the output:\n" +
1196
+ "- STAGED_BASE_MISMATCH: the line left the staged base (stray checkout). VERDICT: FAIL.\n" +
1197
+ "- 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" +
1198
+ "- MERGED: the merge landed; the script pushed inline. Read the push line: PUSHED — report the hash, VERDICT: PASS; PUSH_SKIPPED — the detached line is room-local (the merge commit + record are the evidence), VERDICT: PASS; NO_REMOTE_PUSH — no remote configured, VERDICT: PASS; ERROR after MERGED — a retry recovers the push; report it, VERDICT: FAIL.\n" +
1199
+ "- LOCK_HELD: 10-minute backoff exhausted. VERDICT: FAIL.\n" +
1200
+ "- 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" +
1199
1201
  "RESOLUTION:\n" +
1200
- "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 (main is already checked out in the repo checkout, so git forbids checking it out a second time): cd " + REPO_PATH + " && git worktree add -b resolve/" + taskId + " /tmp/crew-resolve-" + taskId + " main. Reproduce the conflict in the scratch worktree: cd /tmp/crew-resolve-" + taskId + " && git merge " + TASK_BRANCH + ". This reproduces the exact conflict (main has not moved — the lock was held throughout). The task branch " + TASK_BRANCH + " is never modified.\n" +
1201
- "R2. For each conflicted file, read the three sides: git show :1:<file> (base), git show :2:<file> (ours = main), 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" +
1202
- "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 the resolution using the failure output as context — max 3 attempts total.\n" +
1202
+ "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" +
1203
+ "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" +
1204
+ "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" +
1203
1205
  "R4. Commit the resolution on resolve/" + taskId + ": git add -A && git commit -m \"resolve conflicts: " + taskId + "\".\n" +
1204
- "R5. Back in " + REPO_PATH + ": git checkout main && git merge --ff-only resolve/" + taskId + ". This fast-forwards — main has not moved while the lock was held. Then STEP 2 applies: git push origin main. Report the merged commit hash.\n" +
1206
+ "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" +
1205
1207
  "R6. Clean up: cd " + REPO_PATH + " && git worktree remove --force /tmp/crew-resolve-" + taskId + " && git branch -D resolve/" + taskId + ".\n" +
1206
- "ESCALATE — report 'conflict needs human resolution', then end your report with exactly this line: 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" +
1207
- "- If it contains ERROR, something else failed. Report the error, then end your report with exactly this line: VERDICT: FAIL.\n\n" +
1208
- "\n" +
1209
- "STEP 2: Push the merged main to the remote repository.\n" +
1210
- "First, refresh the merge lock — a slow push must not let the 600s lease expire under you: " + LIFECYCLE_ENV + LIFECYCLE + " refresh-lock " + taskId + ". If the refresh fails, report 'merge lock lost before push' and end your report with exactly this line: VERDICT: FAIL. Do not push without the lock.\n" +
1211
- "Run: cd " + REPO_PATH + " && git push origin main\n" +
1212
- "- If the push succeeds, report the merged commit hash.\n" +
1213
- "- If the push is rejected as non-fast-forward (the remote has commits not present locally),\n" +
1214
- " 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" +
1215
- " 1. Run: cd " + REPO_PATH + " && git fetch origin main && git merge --no-edit origin/main -m \"merge: push-time reconcile (" + taskId + ")\".\n" +
1216
- " 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" +
1217
- " 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" +
1218
- " 4. If the retry succeeds, report the merged commit hash.\n\n" +
1219
- "Report back in plain prose — what happened at each step — and end your report with exactly one line: VERDICT: PASS or VERDICT: FAIL.";
1220
-
1221
- } else if (step.name === "Publish") {
1208
+ "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" +
1209
+ "- ERROR: report it. VERDICT: FAIL.\n\n" +
1210
+ "R5-PUSH (manual R5 resolution only — the normal path pushed inline). Run: " + LIFECYCLE_ENV + LIFECYCLE + " push-target " + taskId + "\n" +
1211
+ "PUSHED — report the merged hash, VERDICT: PASS; PUSH_SKIPPED or NO_REMOTE_PUSH — nothing to push, VERDICT: PASS; ERROR or CONFLICT — report it, VERDICT: FAIL.\n" +
1212
+ "NEVER force-push.\n\n" +
1213
+ "Report what happened at each step, ending with exactly one line: VERDICT: PASS or VERDICT: FAIL.";
1214
+
1215
+ } else if (step.name === "Publish") {
1222
1216
  if (PUBLISH_TYPE === "npm") {
1223
1217
  // Deterministic publish: the workflow computed the target version and the
1224
1218
  // agent runs exactly one command — the pinned publish script. The script
@@ -1235,7 +1229,7 @@ while (i < STEPS.length) {
1235
1229
  " bash " + PUBLISH_NPM + "\n\n" +
1236
1230
  "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" +
1237
1231
  "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" +
1238
- "If the script's output contains PUBLISH_SKIPPED=no-lock-held, the publish was skipped gracefully: Integrate reported MERGED_EMPTY (no commits ahead of main), 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" +
1232
+ "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" +
1239
1233
  "TARGET_VERSION=" + publishTarget.target + " computed as " + publishTarget.base + " + " + publishTarget.scope + " → " + publishTarget.target + "\n" +
1240
1234
  "skipped: no-lock-held (empty-diff Integrate — nothing merged, nothing to ship)\n" +
1241
1235
  "VERDICT: PASS\n\n" +
@@ -2098,11 +2092,11 @@ while (i < STEPS.length) {
2098
2092
  if (!postDeploy.deployed) {
2099
2093
  return await parkTask("Post-deploy failed after a skipped publish: " + (postDeploy.output || "no output") + ". Nothing was published; worktree cleanup and lock state unknown — human attention needed.");
2100
2094
  }
2101
- log("Publish skipped cleanly for task " + taskId + " (no lock held) — post-deploy finalized cleanup");
2095
+ log("Publish skipped cleanly for task " + taskId + " (no lock held) — post-deploy step finished");
2102
2096
  } else {
2103
2097
  if (publishFailure) {
2104
2098
  return await parkTask(publishFailure + (postDeploy.deployed
2105
- ? " Post-deploy finalized cleanup."
2099
+ ? " Post-deploy step finished (cleanup status unknown)."
2106
2100
  : " Post-deploy also failed (" + (postDeploy.output || "no output") + ") — worktree and lock state unknown."));
2107
2101
  }
2108
2102
  if (!publishBuildLanded) {
@@ -2115,7 +2109,7 @@ while (i < STEPS.length) {
2115
2109
  // commit (design §1.5: "the trigger was sent for commit <short-sha>").
2116
2110
  if (publishUnknownFields) publishUnknownFields.commitShortSha = mergeCommitShortForPublish;
2117
2111
  return await parkTask(composeUnattributedParkReason(publishUnknownFields) + (postDeploy.deployed
2118
- ? " Post-deploy finalized cleanup."
2112
+ ? " Post-deploy step finished (cleanup status unknown)."
2119
2113
  : " Post-deploy also failed (" + (postDeploy.output || "no output") + ") — worktree and lock state unknown."));
2120
2114
  }
2121
2115
  if (!postDeploy.deployed) {
@@ -2144,7 +2138,7 @@ while (i < STEPS.length) {
2144
2138
  // artifact_edit would trigger a duplicate build.
2145
2139
  if (publishSkippedNoLock) {
2146
2140
  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" +
2147
- "Integrate reported MERGED_EMPTY (the task branch had no commits ahead of main), so no merge lock was taken and there is nothing to ship. The workflow finalized cleanup via post-deploy.\n\n" +
2141
+ "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" +
2148
2142
  "Write plain prose describing the skip, then on its own line: VERDICT: PASS\n" +
2149
2143
  "The VERDICT line must be the last line of your report.";
2150
2144
  } else {
@@ -2348,19 +2342,19 @@ while (i < STEPS.length) {
2348
2342
  var amCheck = await agent(
2349
2343
  "Verify the builder's already-merged declaration.\n" +
2350
2344
  "Run in shell and return the stdout verbatim:\n" +
2351
- "cd " + REPO_PATH + " && git rev-parse --verify --quiet " + am.sha + " >/dev/null && git merge-base --is-ancestor " + am.sha + " main && echo ALREADY_MERGED_YES || echo ALREADY_MERGED_NO",
2345
+ "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",
2352
2346
  { key: "verify-already-merged" + (reworkCount > 0 ? "-r" + reworkCount : ""), label: "Verifying already-merged declaration" }
2353
2347
  );
2354
2348
  var amOut = (typeof amCheck === "string") ? amCheck : JSON.stringify(amCheck);
2355
2349
  if (!/ALREADY_MERGED_YES/.test(amOut)) {
2356
- log("Build already-merged declaration failed verification — " + am.sha + " is not an ancestor of main — marking failed for retry");
2350
+ log("Build already-merged declaration failed verification — " + am.sha + " is not an ancestor of the integration target — marking failed for retry");
2357
2351
  await agent(
2358
2352
  "Record already-merged verification failure.\n" +
2359
2353
  "Run in shell and return the stdout verbatim:\n" + crewCmd("record-phase", {
2360
2354
  task_id: taskId,
2361
2355
  session: { id: activeSessionId, task_id: taskId, identity: step.identity, step: step.name, status: "failed",
2362
- notes: "Build declared repo_diff: none (already-merged: " + am.sha + ") but " + am.sha + " is not an ancestor of main in the configured repo. The declaration is fabricated or mistaken; the work is not on main. Phase failed for retry" },
2363
- event: { task_id: taskId, type: "failed", message: "Build already-merged declaration failed verification — " + am.sha + " not an ancestor of main, phase failed, dispatcher will retry" }
2356
+ 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" },
2357
+ 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" }
2364
2358
  }),
2365
2359
  { key: "record-already-merged-fail-" + step.name, label: "Recording already-merged verification failure" }
2366
2360
  );
@@ -2368,13 +2362,13 @@ while (i < STEPS.length) {
2368
2362
  __hatchWorkflowControl: "blocked",
2369
2363
  result: {
2370
2364
  blocked_reason: "Build already-merged declaration failed verification",
2371
- message: "The builder declared repo_diff: none (already-merged: " + am.sha + ") but " + am.sha + " is not an ancestor of main. The work is not on main; the phase is marked failed and the dispatcher will retry Build.",
2365
+ 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.",
2372
2366
  task_id: taskId
2373
2367
  }
2374
2368
  };
2375
2369
  }
2376
2370
  alreadyMergedSha = am.sha;
2377
- log("Build already-merged declaration verified: " + am.sha + " is an ancestor of main");
2371
+ log("Build already-merged declaration verified: " + am.sha + " is an ancestor of the integration target");
2378
2372
  }
2379
2373
  }
2380
2374
 
@@ -2403,7 +2397,7 @@ while (i < STEPS.length) {
2403
2397
  integrateVerifyOut = "";
2404
2398
  }
2405
2399
  if (/^VERIFIED:/m.test(integrateVerifyOut)) {
2406
- log("Integrate verified for task " + taskId + ": task branch tip is an ancestor of main");
2400
+ log("Integrate verified for task " + taskId + ": task branch tip is an ancestor of the integration target");
2407
2401
  } else {
2408
2402
  var integrateVerifyReason = integrateVerifyOut
2409
2403
  ? integrateVerifyOut.split("\n")[0].slice(0, 200)
@@ -2458,11 +2452,7 @@ while (i < STEPS.length) {
2458
2452
  { key: attemptKey("publish-provenance-refresh-" + taskId, reworkCount), label: "Refreshing dashboard provenance after crew release",
2459
2453
  schema: { type: "object", properties: { refreshed: { type: "boolean" }, reason: { type: "string" }, crew_release: { type: "string" }, published_at: { type: "string" } }, required: ["refreshed"] } }
2460
2454
  );
2461
- if (provRefresh.refreshed) {
2462
- log("Provenance refreshed for task " + taskId + ": crew_release " + provRefresh.crew_release);
2463
- } else if (provRefresh.reason === "no-record") {
2464
- log("Provenance refresh skipped for task " + taskId + ": no existing record to refresh (fresh instance — the dashboard's first artifact publish will create it)");
2465
- } else {
2455
+ if (!provRefresh.refreshed && provRefresh.reason !== "no-record") {
2466
2456
  return await parkTask("Provenance refresh failed after a verified npm publish (crew_release " + (provRefresh.crew_release || "unknown") + "). The release is live but the dashboard record is stale — fail-closed.");
2467
2457
  }
2468
2458
  } catch (e) {
@@ -2541,12 +2531,12 @@ while (i < STEPS.length) {
2541
2531
  }
2542
2532
  rejectionNotes = summary;
2543
2533
  // Already-merged corrective (room #16 blocker 11): when Review rejected
2544
- // an empty branch but the work is already on main (the workflow verified
2534
+ // an empty branch but the work is already on the integration target (the workflow verified
2545
2535
  // the sha), Wren must declare it — not re-implement or re-commit
2546
2536
  // already-landed work. Scoped to the empty-branch rejection; any other
2547
2537
  // rejection already carries its own specific notes.
2548
- if (step.name === "Review" && alreadyMergedSha && /no commits ahead of main/i.test(summary)) {
2549
- rejectionNotes += "\n\nCORRECTIVE (from the workflow, not the reviewer): the deliverable is already on main — the workflow mechanically verified that " + alreadyMergedSha + " is an ancestor of main. 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.";
2538
+ if (step.name === "Review" && alreadyMergedSha && /no commits ahead of (main|the integration target)/i.test(summary)) {
2539
+ 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.";
2550
2540
  log("Rework corrective appended for task " + taskId + ": already-merged " + alreadyMergedSha + " — Wren must declare, not rebuild");
2551
2541
  }
2552
2542
  i = BUILD_INDEX;