muse-crew 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +5 -3
- package/docs/guide.md +1 -1
- package/docs/publish-verification.md +13 -0
- package/docs/state-boundary-spec.md +78 -0
- package/lib/AGENTS.md +6 -2
- package/lib/advance-publish-base.js +250 -0
- package/lib/classify-surface.js +109 -0
- package/lib/commit-scaffold.js +121 -0
- package/lib/compose-evidence-caption.js +55 -7
- package/lib/compute-publish-diff.js +145 -0
- package/lib/crew-api.js +34 -3
- package/lib/setup-project-repo.js +57 -11
- package/package.json +1 -1
- package/seed/cron-body-template.md +5 -4
- package/workflows/bugfix.js +58 -65
- package/workflows/chore.js +58 -65
- package/workflows/crew-dispatch.js +21 -6
- package/workflows/crew-init.js +113 -58
- package/workflows/standard.js +58 -65
package/workflows/chore.js
CHANGED
|
@@ -138,9 +138,15 @@ const PUBLISH_NPM = RUN_LIB + "/publish-npm.sh";
|
|
|
138
138
|
const CREW_API_PINNED = RUN_LIB + "/crew-api.js";
|
|
139
139
|
const SCHEMA_SQL_SRC = crewHome + "/lib/schema.sql";
|
|
140
140
|
const SCHEMA_SQL_PINNED = RUN_LIB + "/schema.sql";
|
|
141
|
-
|
|
141
|
+
const COMPUTE_DIFF_SRC = crewHome + "/current/lib/compute-publish-diff.js";
|
|
142
|
+
const COMPUTE_DIFF = RUN_LIB + "/compute-publish-diff.js";
|
|
143
|
+
// The six basenames the pin step must materialize — asserted mechanically
|
|
142
144
|
// by workflow code from the verbatim listing, never from agent prose.
|
|
143
|
-
|
|
145
|
+
// COMPUTE_DIFF is the deterministic publish-diff computer (room #14,
|
|
146
|
+
// 2026-09-17): the diff is computed by this script, never ferried as an
|
|
147
|
+
// agent JSON string. Pinned like the other publish-critical modules so a
|
|
148
|
+
// mid-run release swap cannot change it under the workflow.
|
|
149
|
+
const PIN_BASENAMES = [LIFECYCLE, MERGE_LOCK, PUBLISH_NPM, CREW_API_PINNED, SCHEMA_SQL_PINNED, COMPUTE_DIFF].map(function (p) { return p.split("/").pop(); });
|
|
144
150
|
|
|
145
151
|
// Project config — passed by dispatcher, falls back to dashboard defaults
|
|
146
152
|
const projectConfig = inputs.project_config || {};
|
|
@@ -311,7 +317,7 @@ function attemptKey(base, reworkCount) {
|
|
|
311
317
|
return base + (reworkCount > 0 ? "-r" + reworkCount : "");
|
|
312
318
|
}
|
|
313
319
|
// pinLifecycle(key) — snapshot the lifecycle scripts into RUN_LIB and return
|
|
314
|
-
// the verbatim `ls -1` listing so WORKFLOW CODE asserts the
|
|
320
|
+
// the verbatim `ls -1` listing so WORKFLOW CODE asserts the six pinned
|
|
315
321
|
// basenames; the agent cannot self-certify. (The pin step was the one place
|
|
316
322
|
// the workflows trusted agent prose: task 24be1cd6 walked to Publish on an
|
|
317
323
|
// empty pin dir.) Byte-identical across standard/bugfix/chore — pinned by
|
|
@@ -319,7 +325,7 @@ function attemptKey(base, reworkCount) {
|
|
|
319
325
|
function pinLifecycle(key) {
|
|
320
326
|
return agent(
|
|
321
327
|
"Snapshot lifecycle scripts for version pinning.\n" +
|
|
322
|
-
"Run: mkdir -p " + RUN_LIB + " && cp " + LIFECYCLE_SRC + " " + LIFECYCLE + " && cp " + MERGE_LOCK_SRC + " " + MERGE_LOCK + " && cp " + PUBLISH_NPM_SRC + " " + PUBLISH_NPM + " && cp " + CREW_API_SRC + " " + CREW_API_PINNED + " && cp " + SCHEMA_SQL_SRC + " " + SCHEMA_SQL_PINNED + " && chmod +x " + LIFECYCLE + " " + MERGE_LOCK + " " + PUBLISH_NPM + " && ls -1 " + RUN_LIB + "\n" +
|
|
328
|
+
"Run: mkdir -p " + RUN_LIB + " && cp " + LIFECYCLE_SRC + " " + LIFECYCLE + " && cp " + MERGE_LOCK_SRC + " " + MERGE_LOCK + " && cp " + PUBLISH_NPM_SRC + " " + PUBLISH_NPM + " && cp " + CREW_API_SRC + " " + CREW_API_PINNED + " && cp " + SCHEMA_SQL_SRC + " " + SCHEMA_SQL_PINNED + " && cp " + COMPUTE_DIFF_SRC + " " + COMPUTE_DIFF + " && chmod +x " + LIFECYCLE + " " + MERGE_LOCK + " " + PUBLISH_NPM + " && ls -1 " + RUN_LIB + "\n" +
|
|
323
329
|
"Return the verbatim output of the ls -1 command as { \"listing\": \"<verbatim output>\" } and nothing else.",
|
|
324
330
|
{ key: key, label: "Pinning lifecycle scripts",
|
|
325
331
|
schema: { type: "object", properties: { listing: { type: "string" } }, required: ["listing"] } }
|
|
@@ -500,37 +506,6 @@ function hydrateReleaseDecision(rec) {
|
|
|
500
506
|
return null;
|
|
501
507
|
}
|
|
502
508
|
|
|
503
|
-
// Publish diff transport: parse a unified diff into per-file {path, added,
|
|
504
|
-
// removed} line lists. Pure function — no I/O, no clock. Used by Publish to
|
|
505
|
-
// verify the artifact builder applied the carried change (canary run 4,
|
|
506
|
-
// 2026-09-11: the builder's source tree was stale and disconnected from the
|
|
507
|
-
// crew's repo; "rebuild from current source" rebuilt stale code and the
|
|
508
|
-
// workflow stamped the new commit hash on it — provenance fiction).
|
|
509
|
-
function parseUnifiedDiff(diffText) {
|
|
510
|
-
var files = [];
|
|
511
|
-
var current = null;
|
|
512
|
-
var lines = (diffText || "").split("\n");
|
|
513
|
-
for (var i = 0; i < lines.length; i++) {
|
|
514
|
-
var line = lines[i];
|
|
515
|
-
var m = /^diff --git a\/(.*) b\/(.*)$/.exec(line);
|
|
516
|
-
if (m) {
|
|
517
|
-
current = { path: m[2], added: [], removed: [] };
|
|
518
|
-
files.push(current);
|
|
519
|
-
continue;
|
|
520
|
-
}
|
|
521
|
-
if (!current) continue;
|
|
522
|
-
if (/^--- /.test(line) || /^\+\+\+ /.test(line)) continue;
|
|
523
|
-
if (/^@@ /.test(line)) continue;
|
|
524
|
-
if (line.charAt(0) === "+") {
|
|
525
|
-
current.added.push(line.slice(1));
|
|
526
|
-
} else if (line.charAt(0) === "-") {
|
|
527
|
-
current.removed.push(line.slice(1));
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
return files;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
|
|
534
509
|
// Publish read-back request (currently unavailable): the verbatim_request
|
|
535
510
|
// the parent protocol (docs/publish-verification.md) would hand to an
|
|
536
511
|
// independent read-back tool after the artifact build lands. artifact_inspect
|
|
@@ -1473,59 +1448,77 @@ while (i < STEPS.length) {
|
|
|
1473
1448
|
} else if (!/^[0-9a-f]{40}$/.test(publishBase)) {
|
|
1474
1449
|
return await parkTask("Publish base '" + publishBase + "' is not a valid commit SHA — cannot compute the publish diff. Human attention needed.");
|
|
1475
1450
|
}
|
|
1476
|
-
//
|
|
1477
|
-
//
|
|
1478
|
-
//
|
|
1479
|
-
//
|
|
1480
|
-
//
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1451
|
+
// Publish diff transport (room #14, 2026-09-17): the diff used to be
|
|
1452
|
+
// ferried as a JSON string field in the agent's response — the agent
|
|
1453
|
+
// produced a valid 700-line diff on disk but the JSON ferry dropped
|
|
1454
|
+
// it, and the parse saw zero files ("Publish diff parsed to zero
|
|
1455
|
+
// files"). The diff now travels git -> file -> deterministic script
|
|
1456
|
+
// summary; the LLM never carries diff bytes. The agent is pure hands:
|
|
1457
|
+
// it runs exactly one command (the PINNED compute-publish-diff.js —
|
|
1458
|
+
// a mid-run release swap cannot change it under the workflow) and
|
|
1459
|
+
// returns the small JSON summary verbatim. All fail-closed parks
|
|
1460
|
+
// below are unchanged in meaning.
|
|
1461
|
+
var publishDiffFile = crewHome + "/.publish-diffs/" + taskId + (reworkCount > 0 ? "-r" + reworkCount : "") + ".diff";
|
|
1484
1462
|
var diffResult = await agent(
|
|
1485
|
-
"Run
|
|
1486
|
-
|
|
1487
|
-
"
|
|
1488
|
-
"if [ \"$ANCESTOR\" = yes ]; then echo '---DIFF---' && git diff \"$BASE\" \"$HEAD\" && echo '---NAMES---' && git diff-tree --no-commit-id --name-only -r \"$BASE\" \"$HEAD\"; fi\n" +
|
|
1489
|
-
"Return JSON { \"commit\": \"<HEAD trimmed>\", \"base\": \"<BASE trimmed>\", \"ancestor\": \"<yes|no>\", \"diff\": \"<raw unified diff, may be multi-line>\", \"files\": \"<newline-separated paths>\" } and nothing else.",
|
|
1463
|
+
"Run exactly one command and nothing else:\n" +
|
|
1464
|
+
"node " + COMPUTE_DIFF + " --repo-path \"" + REPO_PATH + "\" --base " + publishBase + " --out \"" + publishDiffFile + "\"\n" +
|
|
1465
|
+
"The script prints one small JSON object to stdout. Return the stdout verbatim as { \"output\": \"<verbatim stdout>\" } and nothing else. Do not interpret it.",
|
|
1490
1466
|
{ key: attemptKey("publish-artifact-diff-" + taskId, reworkCount), label: "Computing publish diff from stamped base",
|
|
1491
|
-
schema: { type: "object", properties: {
|
|
1467
|
+
schema: { type: "object", properties: { output: { type: "string" } }, required: ["output"] } }
|
|
1492
1468
|
);
|
|
1493
|
-
|
|
1494
|
-
|
|
1469
|
+
var diffSummary = null;
|
|
1470
|
+
try {
|
|
1471
|
+
diffSummary = JSON.parse((diffResult.output || "").trim());
|
|
1472
|
+
} catch (e) {
|
|
1473
|
+
return await parkTask("Publish diff computation returned unparseable output — cannot verify application. Human attention needed.");
|
|
1474
|
+
}
|
|
1475
|
+
if (!diffSummary || diffSummary.ok !== true) {
|
|
1476
|
+
if (diffSummary && diffSummary.error === "not-ancestor") {
|
|
1477
|
+
return await parkTask("Publish base " + publishBase.slice(0, 12) + " is not an ancestor of HEAD " + String(diffSummary.commit || "").slice(0, 12) + " — the stamped provenance does not lead to the integrated commit. Human attention needed.");
|
|
1478
|
+
}
|
|
1479
|
+
return await parkTask("Publish diff computation failed (" + ((diffSummary && diffSummary.error) || "unknown error") + ") — cannot verify application. Human attention needed.");
|
|
1495
1480
|
}
|
|
1496
|
-
if (
|
|
1497
|
-
return await parkTask("Publish diff base mismatch:
|
|
1481
|
+
if (diffSummary.base !== publishBase) {
|
|
1482
|
+
return await parkTask("Publish diff base mismatch: script reported '" + String(diffSummary.base || "").slice(0, 12) + "' but the stamped base is '" + publishBase.slice(0, 12) + "'. Human attention needed.");
|
|
1498
1483
|
}
|
|
1499
|
-
var mergeCommitForPublish = (
|
|
1500
|
-
var
|
|
1501
|
-
if (!
|
|
1484
|
+
var mergeCommitForPublish = String(diffSummary.commit || "").trim();
|
|
1485
|
+
var publishDiffSha256 = String(diffSummary.sha256 || "");
|
|
1486
|
+
if (!diffSummary.bytes) {
|
|
1502
1487
|
return await parkTask("Publish diff is empty for commit " + (mergeCommitForPublish || "unknown") + " — a merge lock was held but there is no change to carry. Human attention needed.");
|
|
1503
1488
|
}
|
|
1504
|
-
if (
|
|
1505
|
-
return await parkTask("Publish diff contains binary files — the
|
|
1489
|
+
if (diffSummary.has_binary) {
|
|
1490
|
+
return await parkTask("Publish diff contains binary files — the diff transport cannot carry them. Human attention needed.");
|
|
1506
1491
|
}
|
|
1507
|
-
if (
|
|
1492
|
+
if (diffSummary.has_rename) {
|
|
1508
1493
|
return await parkTask("Publish diff contains a rename — the diff transport cannot carry renames. Human attention needed.");
|
|
1509
1494
|
}
|
|
1510
|
-
var expectedChanges = parseUnifiedDiff(mergeDiff);
|
|
1511
1495
|
// Budget counts CHANGED lines (added + removed), not raw unified-diff
|
|
1512
1496
|
// output lines: context lines and file headers inflated the old
|
|
1513
1497
|
// split("\n").length count ~2x, parking a 95-line change against a
|
|
1514
|
-
// 200-line budget (Gate 1 Journey 3 attempt 3, 2026-09-13).
|
|
1515
|
-
|
|
1498
|
+
// 200-line budget (Gate 1 Journey 3 attempt 3, 2026-09-13). The count
|
|
1499
|
+
// now comes from the deterministic script summary, never from the LLM.
|
|
1500
|
+
var mergeDiffChangedLines = diffSummary.changed_lines || 0;
|
|
1516
1501
|
if (mergeDiffChangedLines > 200) {
|
|
1517
1502
|
return await parkTask("Publish diff changes " + mergeDiffChangedLines + " lines (budget 200) — too large for the diff transport. Human attention needed.");
|
|
1518
1503
|
}
|
|
1519
|
-
if (
|
|
1504
|
+
if (!diffSummary.file_count) {
|
|
1520
1505
|
return await parkTask("Publish diff parsed to zero files for commit " + (mergeCommitForPublish || "unknown") + " — cannot verify application. Human attention needed.");
|
|
1521
1506
|
}
|
|
1507
|
+
// The trigger agent reads the diff from the checksummed file — the
|
|
1508
|
+
// workflow never holds diff bytes (room #14, 2026-09-17). The file
|
|
1509
|
+
// is small by construction: the 200-line budget above gates the
|
|
1510
|
+
// trigger. The sha256 check is the agent's only verification step;
|
|
1511
|
+
// a mismatch stops the trigger before any artifact_edit call.
|
|
1522
1512
|
var rebuildPrompt =
|
|
1523
1513
|
ARTIFACT_LOAD_PREAMBLE +
|
|
1524
|
-
"
|
|
1514
|
+
"The change to apply is the unified diff in the file \"" + publishDiffFile + "\" (sha256 " + publishDiffSha256 + ").\n" +
|
|
1515
|
+
"1. Verify the file: run sha256sum on it. If the printed hash is not exactly " + publishDiffSha256 + ", STOP and end your turn — do not call artifact_edit.\n" +
|
|
1516
|
+
"2. Read the file's full content.\n" +
|
|
1517
|
+
"3. Call artifact_edit with slug \"" + PUBLISH_SLUG + "\" and verbatim_request:\n" +
|
|
1525
1518
|
"'Apply the following change to your source tree, then rebuild and deploy.\n" +
|
|
1526
1519
|
"\n" +
|
|
1527
1520
|
"UNIFIED DIFF (relative to your source tree):\n" +
|
|
1528
|
-
"```diff\n
|
|
1521
|
+
"```diff\n<the full content of the verified file, pasted verbatim>\n```\n" +
|
|
1529
1522
|
"\n" +
|
|
1530
1523
|
"Rules:\n" +
|
|
1531
1524
|
"- For each file in the diff, apply its hunks to the same path in your source tree (use git apply or equivalent).\n" +
|
|
@@ -941,11 +941,17 @@ await agent(
|
|
|
941
941
|
// not timestamps — no wall-clock calls (see tests/determinism.test.js).
|
|
942
942
|
// Fire-and-forget: the script swallows its own errors and exits 0, the
|
|
943
943
|
// agent call carries no schema, and the whole call is wrapped in
|
|
944
|
-
// try/catch — a failed write can never fail the tick.
|
|
945
|
-
//
|
|
946
|
-
//
|
|
947
|
-
//
|
|
948
|
-
//
|
|
944
|
+
// try/catch — a failed write can never fail the tick. The script echoes
|
|
945
|
+
// the appended line to stdout (2026-09-17, rooms #12–#14): the fire-and-forget
|
|
946
|
+
// agent() call carries no schema and its return is discarded, but the
|
|
947
|
+
// platform's JSON-candidate scan throws on an EMPTY schema-less result —
|
|
948
|
+
// which is what the writer returned before it echoed. Every other
|
|
949
|
+
// fire-and-forget call in this file returns the CLI's non-empty JSON
|
|
950
|
+
// stdout verbatim, so the writer now speaks the same proven shape.
|
|
951
|
+
// Placement: every run that completes the poll (dispatching, NO_DISPATCH,
|
|
952
|
+
// partial-cap) reaches this point. Concurrency edge: overlapping ticks
|
|
953
|
+
// could duplicate seq (count+append is not atomic) but appends are atomic,
|
|
954
|
+
// so lines never interleave — seq is advisory, file order is the proof.
|
|
949
955
|
var tickReleaseScript = [
|
|
950
956
|
"var crewHome=process.argv[1];",
|
|
951
957
|
"try{",
|
|
@@ -954,7 +960,16 @@ var tickReleaseScript = [
|
|
|
954
960
|
"var file=path.join(crewHome,\".tick-releases.jsonl\");",
|
|
955
961
|
"var count=0;",
|
|
956
962
|
"try{var lines=fs.readFileSync(file,\"utf8\").split(\"\\n\");for(var i=0;i<lines.length;i++){if(lines[i].trim()!==\"\"){count++;}}}catch(e){}",
|
|
957
|
-
"
|
|
963
|
+
"var line=JSON.stringify({seq:count+1,release:release});",
|
|
964
|
+
"fs.appendFileSync(file,line+\"\\n\");",
|
|
965
|
+
// Rooms #12–#14: echo the appended line to stdout. The fire-and-forget
|
|
966
|
+
// agent() call below carries no schema and its return is discarded — but
|
|
967
|
+
// the platform's JSON-candidate scan throws on an EMPTY schema-less
|
|
968
|
+
// result, which is exactly what this script returned before (exit 0, no
|
|
969
|
+
// output). Every other fire-and-forget call in this file returns the
|
|
970
|
+
// CLI's non-empty JSON stdout verbatim, so the writer now speaks the same
|
|
971
|
+
// proven shape: the echoed line is the evidence of the write.
|
|
972
|
+
"process.stdout.write(line+\"\\n\");",
|
|
958
973
|
"}catch(e){}",
|
|
959
974
|
"process.exit(0);"
|
|
960
975
|
].join("");
|
package/workflows/crew-init.js
CHANGED
|
@@ -337,47 +337,6 @@ var scaffoldCreated = scaffoldResult.created ? scaffoldResult.created.length : 0
|
|
|
337
337
|
var scaffoldSkipped = scaffoldResult.skipped ? scaffoldResult.skipped.length : 0;
|
|
338
338
|
log("Scaffold: " + scaffoldCreated + " created, " + scaffoldSkipped + " skipped");
|
|
339
339
|
|
|
340
|
-
// ── Gitignore consent execution ─────────────────────────────────────
|
|
341
|
-
// If the user approved during the setup conversation (gitignoreConsent=true)
|
|
342
|
-
// and we have a repo (dashboard mode), append crew-owned paths to .gitignore
|
|
343
|
-
// via the deterministic lib/gitignore.js (no agent judgment — the agent
|
|
344
|
-
// just runs the fixed command; all merge logic is in the tested JS module).
|
|
345
|
-
// This is the ONLY user file outside .orchestration/ that the crew modifies.
|
|
346
|
-
if (gitignoreConsent && dashboardRepoPath) {
|
|
347
|
-
var gitignoreResult;
|
|
348
|
-
try {
|
|
349
|
-
gitignoreResult = await agent(
|
|
350
|
-
"Run the deterministic gitignore updater. Do not decide anything — just run the command and return its JSON output.\n\n" +
|
|
351
|
-
"Command:\n" +
|
|
352
|
-
"node " + crewHome + "/current/lib/gitignore.js --repo \"" + gateFacts.dashboardRepoExpanded + "\"\n\n" +
|
|
353
|
-
"The script prints one JSON line: { created, appended[], skipped[] }.\n" +
|
|
354
|
-
"Return that JSON verbatim.",
|
|
355
|
-
{
|
|
356
|
-
key: "scaffold-gitignore",
|
|
357
|
-
label: "Update .gitignore for crew paths",
|
|
358
|
-
schema: {
|
|
359
|
-
type: "object",
|
|
360
|
-
properties: {
|
|
361
|
-
created: { type: "boolean" },
|
|
362
|
-
appended: { type: "array", items: { type: "string" } },
|
|
363
|
-
skipped: { type: "array", items: { type: "string" } }
|
|
364
|
-
},
|
|
365
|
-
required: ["created", "appended", "skipped"]
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
);
|
|
369
|
-
log("Gitignore: " + (gitignoreResult.created ? "created" : "updated") +
|
|
370
|
-
" — appended [" + (gitignoreResult.appended || []).join(", ") + "]" +
|
|
371
|
-
", already present [" + (gitignoreResult.skipped || []).join(", ") + "]");
|
|
372
|
-
} catch (e) {
|
|
373
|
-
log("Gitignore update failed (non-fatal): " + String(e.message || e));
|
|
374
|
-
}
|
|
375
|
-
} else if (dashboardRepoPath) {
|
|
376
|
-
log("Gitignore: user declined — add '.worktrees/' and '.orchestration/user/' to " + dashboardRepoPath + "/.gitignore manually.");
|
|
377
|
-
} else {
|
|
378
|
-
log("Gitignore: no repo (CLI-only mode) — skipping.");
|
|
379
|
-
}
|
|
380
|
-
|
|
381
340
|
// ── Phase 3: Project registration ─────────────────────────────────────
|
|
382
341
|
// Registers the first project in crew-state.db via the Crew API. In
|
|
383
342
|
// dashboard mode, the dashboard becomes the crew's first project (so the
|
|
@@ -455,37 +414,132 @@ try {
|
|
|
455
414
|
log("Project: " + dashboardSlug + " " + projectResult.action + " (repo_path=" + gateFacts.dashboardRepoExpanded + ")");
|
|
456
415
|
} // end dashboard-mode project registration
|
|
457
416
|
|
|
458
|
-
// ──
|
|
459
|
-
//
|
|
460
|
-
//
|
|
461
|
-
//
|
|
417
|
+
// ── Project repo setup (2026-09-18) ─────────────────────────────────
|
|
418
|
+
// One deterministic composer — lib/setup-project-repo.js — scaffolds
|
|
419
|
+
// $REPO/.orchestration/, applies the consented .gitignore update, and COMMITS
|
|
420
|
+
// both so the repo's main checkout is clean when setup finishes.
|
|
421
|
+
// Room #13 (2026-09-17): the old two-call shape wrote the scaffold but never
|
|
422
|
+
// committed it, and the first task's Integrate then refused to merge onto the
|
|
423
|
+
// dirty main — twice, deterministically. This replaces both calls; the agent
|
|
424
|
+
// still runs one fixed command and all logic stays in the tested lib modules.
|
|
425
|
+
// Best-effort as before (a failed setup never blocks init), but a failed
|
|
426
|
+
// COMMIT is logged loudly: the repo is dirty and the first task will fail
|
|
427
|
+
// Integrate until the scaffold is committed.
|
|
428
|
+
var setupRepoWarning = null;
|
|
429
|
+
var setupRepoResult = null;
|
|
462
430
|
if (dashboardRepoPath) {
|
|
463
|
-
var repoOrchResult;
|
|
464
431
|
try {
|
|
465
|
-
|
|
466
|
-
"
|
|
432
|
+
setupRepoResult = await agent(
|
|
433
|
+
"Run the deterministic project-repo setup composer. Do not decide anything — just run the command and return its JSON output.\n\n" +
|
|
434
|
+
"Command:\n" +
|
|
435
|
+
"node " + crewHome + "/current/lib/setup-project-repo.js --repo \"" + gateFacts.dashboardRepoExpanded + "\" --crew-repo \"" + crewRepoPath + "\"" + (gitignoreConsent ? " --consent" : "") + "\n\n" +
|
|
436
|
+
"The script prints one JSON object: { orchestration, gitignore, commit }.\n" +
|
|
437
|
+
"orchestration carries orchDir, dirsCreated, and per-area created/skipped arrays;\n" +
|
|
438
|
+
"gitignore carries created/appended/skipped (or declined:true with a wouldChange preview);\n" +
|
|
439
|
+
"commit carries { committed, sha, reason }.\n" +
|
|
440
|
+
"Return that JSON verbatim.",
|
|
441
|
+
{
|
|
442
|
+
key: "scaffold-repo-setup",
|
|
443
|
+
label: "Set up project repo (scaffold + gitignore + commit)",
|
|
444
|
+
schema: {
|
|
445
|
+
type: "object",
|
|
446
|
+
properties: {
|
|
447
|
+
orchestration: {
|
|
448
|
+
type: "object",
|
|
449
|
+
properties: {
|
|
450
|
+
orchDir: { type: "string" },
|
|
451
|
+
dirsCreated: { type: "array", items: { type: "string" } }
|
|
452
|
+
},
|
|
453
|
+
required: ["orchDir", "dirsCreated"]
|
|
454
|
+
},
|
|
455
|
+
gitignore: { type: "object" },
|
|
456
|
+
commit: {
|
|
457
|
+
type: "object",
|
|
458
|
+
properties: {
|
|
459
|
+
committed: { type: "boolean" },
|
|
460
|
+
sha: { type: ["string", "null"] },
|
|
461
|
+
reason: { type: ["string", "null"] }
|
|
462
|
+
},
|
|
463
|
+
required: ["committed"]
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
required: ["orchestration", "gitignore", "commit"]
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
);
|
|
470
|
+
var orchDirs = (setupRepoResult.orchestration.dirsCreated || []).join(", ");
|
|
471
|
+
var commitMsg = setupRepoResult.commit.committed
|
|
472
|
+
? "committed " + setupRepoResult.commit.sha
|
|
473
|
+
: "not committed (" + setupRepoResult.commit.reason + ")";
|
|
474
|
+
log("Repo setup: scaffolded " + setupRepoResult.orchestration.orchDir +
|
|
475
|
+
" (dirs: [" + orchDirs + "]); gitignore " +
|
|
476
|
+
(gitignoreConsent ? "update consented" : "declined by user — add '.worktrees/' and '.orchestration/user/' manually") +
|
|
477
|
+
"; scaffold " + commitMsg);
|
|
478
|
+
if (!setupRepoResult.commit.committed && setupRepoResult.commit.reason !== "nothing-to-commit") {
|
|
479
|
+
setupRepoWarning = "Project repo setup left the scaffold uncommitted (" +
|
|
480
|
+
setupRepoResult.commit.reason + ") — the first task's Integrate will refuse to merge until it is committed.";
|
|
481
|
+
log("WARNING: " + setupRepoWarning);
|
|
482
|
+
}
|
|
483
|
+
} catch (e) {
|
|
484
|
+
var setupErr = String(e.message || e);
|
|
485
|
+
setupRepoWarning = "Project repo setup failed: " + setupErr + " — continuing without the repo scaffold.";
|
|
486
|
+
if (/commit failed/i.test(setupErr)) {
|
|
487
|
+
setupRepoWarning += " The repo has uncommitted crew files; the first task's Integrate will refuse to merge until they are committed.";
|
|
488
|
+
}
|
|
489
|
+
log(setupRepoWarning + " (non-fatal)");
|
|
490
|
+
}
|
|
491
|
+
} else {
|
|
492
|
+
log("Repo setup: no repo (CLI-only mode) — skipping.");
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// ── Publish-base advance (2026-09-18) ─────────────────────────────────
|
|
496
|
+
// Room #14: create-project stamps provenance.source_commit at the
|
|
497
|
+
// pre-scaffold repo HEAD, then the scaffold commits .orchestration/ (and the
|
|
498
|
+
// consented .gitignore) on top. The first task's publish diff then carries
|
|
499
|
+
// the whole scaffold (517 insertions in room #14) and blows the 200-line
|
|
500
|
+
// diff-transport budget. The scaffold is crew-owned infrastructure the
|
|
501
|
+
// dashboard artifact never consumes, so the correct publish base is the
|
|
502
|
+
// scaffold commit itself. Advance it deterministically via
|
|
503
|
+
// lib/advance-publish-base.js — only when this init created the project
|
|
504
|
+
// (the stamp is ours, never a real publish's), the scaffold commit's parent
|
|
505
|
+
// is exactly the stamped base (nothing slipped between), and the scaffold
|
|
506
|
+
// touches only crew-owned paths. Best-effort: a failed advance never blocks
|
|
507
|
+
// init — the first task then publishes from the stamped base as before.
|
|
508
|
+
var publishBaseResult = null;
|
|
509
|
+
if (dashboardRepoPath && projectResult && projectResult.action === "created" &&
|
|
510
|
+
setupRepoResult && setupRepoResult.commit && setupRepoResult.commit.committed) {
|
|
511
|
+
try {
|
|
512
|
+
publishBaseResult = await agent(
|
|
513
|
+
"Run the deterministic publish-base advance script. Do not decide anything — just run the command and return its JSON output.\n\n" +
|
|
467
514
|
"Command:\n" +
|
|
468
|
-
"node " + crewHome + "/current/lib/
|
|
469
|
-
"The script prints JSON
|
|
515
|
+
"node " + crewHome + "/current/lib/advance-publish-base.js --crew-home \"" + crewHome + "\" --repo \"" + gateFacts.dashboardRepoExpanded + "\" --scaffold-sha " + setupRepoResult.commit.sha + " --project-created\n\n" +
|
|
516
|
+
"The script prints one JSON object: { ok, action, reason?, from?, to? }.\n" +
|
|
470
517
|
"Return that JSON verbatim.",
|
|
471
518
|
{
|
|
472
|
-
key: "
|
|
473
|
-
label: "
|
|
519
|
+
key: "advance-publish-base",
|
|
520
|
+
label: "Advance publish base past scaffold commit",
|
|
474
521
|
schema: {
|
|
475
522
|
type: "object",
|
|
476
523
|
properties: {
|
|
477
|
-
|
|
478
|
-
|
|
524
|
+
ok: { type: "boolean" },
|
|
525
|
+
action: { type: "string" },
|
|
526
|
+
reason: {},
|
|
527
|
+
from: {},
|
|
528
|
+
to: {}
|
|
479
529
|
},
|
|
480
|
-
required: ["
|
|
530
|
+
required: ["ok", "action"]
|
|
481
531
|
}
|
|
482
532
|
}
|
|
483
533
|
);
|
|
484
|
-
log("
|
|
485
|
-
" (
|
|
534
|
+
log("Publish base: " + publishBaseResult.action +
|
|
535
|
+
(publishBaseResult.reason ? " (" + publishBaseResult.reason + ")" : "") +
|
|
536
|
+
(publishBaseResult.from ? " " + publishBaseResult.from + " -> " + publishBaseResult.to : ""));
|
|
486
537
|
} catch (e) {
|
|
487
|
-
log("
|
|
538
|
+
log("Publish base: advance script failed (" + String(e.message || e) + ") — continuing with the stamped base (non-fatal).");
|
|
488
539
|
}
|
|
540
|
+
} else {
|
|
541
|
+
log("Publish base: no advance (project " + (projectResult ? projectResult.action : "unknown") +
|
|
542
|
+
", scaffold " + (setupRepoResult && setupRepoResult.commit && setupRepoResult.commit.committed ? "committed" : "not committed") + ").");
|
|
489
543
|
}
|
|
490
544
|
|
|
491
545
|
// ── Scheduler identity (2026-09-16) ──────────────────────────────────
|
|
@@ -746,6 +800,7 @@ return {
|
|
|
746
800
|
releaseHash: releaseResult.hash,
|
|
747
801
|
scaffold: { created: scaffoldCreated, skipped: scaffoldSkipped },
|
|
748
802
|
project: projectResult.action,
|
|
803
|
+
repoSetupWarning: setupRepoWarning,
|
|
749
804
|
crons: cronsResult.summary.crons,
|
|
750
805
|
registry: cronsResult.summary.registry,
|
|
751
806
|
autoUpdate: {
|