muse-crew 0.7.14 → 0.7.15

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/lib/crew-api.js CHANGED
@@ -25,6 +25,7 @@
25
25
  import { readFileSync, existsSync, statSync, readlinkSync, readdirSync, appendFileSync } from "node:fs";
26
26
  import { join, resolve, sep, basename } from "node:path";
27
27
  import { randomUUID } from "node:crypto";
28
+ import { execFileSync } from "node:child_process";
28
29
  import { DatabaseSync } from "node:sqlite";
29
30
  import { homedir } from "node:os";
30
31
 
@@ -1540,7 +1541,50 @@ commands["record-phase"] = (db, args) => {
1540
1541
  return { session: mapSession(sessionRow), event: mapEvent(eventRow) };
1541
1542
  };
1542
1543
 
1543
- commands["create-project"] = (db, args) => {
1544
+ // Initial provenance stamp (2026-09-16, clean-room task ada95b71): the first
1545
+ // publish of an artifact project fell back to the empty-tree SHA as its diff
1546
+ // base because no provenance was ever stamped, turning the publish diff into
1547
+ // the whole repo tree (52 files / 4768 lines / 228KB) — unparseable through
1548
+ // the JSON transport and over the 200-line budget. The artifact is built from
1549
+ // the repo checkout at registration time, so create-project stamps
1550
+ // source_commit = repo HEAD for artifact-deploy projects; the first publish
1551
+ // then diffs only the task's own changes. The stamp never clobbers an
1552
+ // existing one (provenance storage is crew-home-global, so a second artifact
1553
+ // project keeps the first stamp — per-project provenance is a known gap this
1554
+ // does not introduce). A skipped stamp is reported, never silent, and never
1555
+ // fails project creation.
1556
+ function initialProvenancePlan({ deployType, provenanceExists, headSha, releaseName }) {
1557
+ if (deployType !== "artifact") return { stamped: false, reason: "deploy_type is not artifact" };
1558
+ if (provenanceExists) return { stamped: false, reason: "provenance already stamped" };
1559
+ if (!headSha) return { stamped: false, reason: "repo HEAD is unresolvable (unborn repo?)" };
1560
+ if (!releaseName) return { stamped: false, reason: "active crew release is unresolvable" };
1561
+ return { stamped: true, source_commit: headSha, crew_release: releaseName };
1562
+ }
1563
+
1564
+ function tryStampInitialProvenance(db, crewHome, repoPath, deployType) {
1565
+ const keys = db.prepare(
1566
+ "SELECT key FROM config WHERE key IN ('provenance.source_commit','provenance.crew_release','provenance.published_at')"
1567
+ ).all().map((r) => r.key);
1568
+ const provenanceExists = keys.length === 3;
1569
+ let headSha = null;
1570
+ try {
1571
+ headSha = execFileSync("git", ["rev-parse", "HEAD"],
1572
+ { cwd: repoPath, encoding: "utf8", timeout: 10000 }).trim();
1573
+ if (!/^[0-9a-f]{40}$/.test(headSha)) headSha = null;
1574
+ } catch { headSha = null; }
1575
+ let releaseName = null;
1576
+ try { releaseName = resolveActiveRelease(crewHome); } catch { releaseName = null; }
1577
+ const plan = initialProvenancePlan({ deployType, provenanceExists, headSha, releaseName });
1578
+ if (!plan.stamped) return plan;
1579
+ const upsert = db.prepare(
1580
+ "INSERT INTO config (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value");
1581
+ upsert.run("provenance.source_commit", plan.source_commit);
1582
+ upsert.run("provenance.crew_release", plan.crew_release);
1583
+ upsert.run("provenance.published_at", now());
1584
+ return plan;
1585
+ }
1586
+
1587
+ commands["create-project"] = (db, args, ctx) => {
1544
1588
  const id = (args.id ?? "").trim();
1545
1589
  if (!/^[a-z0-9-]+$/.test(id)) throw usageError("id must be a slug.");
1546
1590
  const displayName = (args.display_name ?? "").trim();
@@ -1572,7 +1616,8 @@ commands["create-project"] = (db, args) => {
1572
1616
  description, simultaneity, quiesced, visual_protocol, created_at, updated_at)
1573
1617
  VALUES (@id, @display_name, @repo_path, @deploy_type, @deploy_slug,
1574
1618
  @description, @simultaneity, @quiesced, @visual_protocol, @created_at, @updated_at)`).run(row);
1575
- return { project: mapProject(row) };
1619
+ const initialProvenance = tryStampInitialProvenance(db, ctx.crewHome, repoPath, deployType);
1620
+ return { project: mapProject(row), initial_provenance: initialProvenance };
1576
1621
  };
1577
1622
 
1578
1623
  commands["get-project"] = (db, args) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muse-crew",
3
- "version": "0.7.14",
3
+ "version": "0.7.15",
4
4
  "description": "Opinionated orchestration for Muse — workflows, identities, and tooling for autonomous software development.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -50,8 +50,12 @@ const CLAIM_WORKFLOW_PERSIST = (WORKFLOW_WAS_NULL && RESOLVED_WORKFLOW) ? ", \"w
50
50
  // Manual launches without the arg default to off (previous behavior).
51
51
  var VISUAL_PROTOCOL_AVAILABLE = inputs.visual_protocol === true;
52
52
 
53
- // Config from argsbackward-compatible fallbacks for manual launches
54
- const crewHome = inputs.crewHome || "~/workspace/.jarvis";
53
+ // crewHome is requiredthe dispatcher always passes it (crew-dispatch.js
54
+ // throws without it). Fail closed instead of silently defaulting to the dev
55
+ // home: a missing home is a loud error, a wrong home is silent corruption
56
+ // (2026-09-16: the silent default let a run resolve against the dev home).
57
+ if (!inputs.crewHome) throw new Error("crewHome is required — pass the crew home explicitly; no default");
58
+ const crewHome = inputs.crewHome;
55
59
  // Crew API: the workflow calls the crew-owned CLI, not the dashboard.
56
60
  // The CLI implements the API.md contract against $CREW_HOME/crew-state.db.
57
61
  const CREW_API = crewHome + "/current/lib/crew-api.js";
@@ -47,8 +47,12 @@ const NEXT_PHASE_ROUTED = (typeof inputs.next_phase === "string" && inputs.next_
47
47
  // Manual launches without the arg default to off (previous behavior).
48
48
  var VISUAL_PROTOCOL_AVAILABLE = inputs.visual_protocol === true;
49
49
 
50
- // Config from argsbackward-compatible fallbacks for manual launches
51
- const crewHome = inputs.crewHome || "~/workspace/.jarvis";
50
+ // crewHome is requiredthe dispatcher always passes it (crew-dispatch.js
51
+ // throws without it). Fail closed instead of silently defaulting to the dev
52
+ // home: a missing home is a loud error, a wrong home is silent corruption
53
+ // (2026-09-16: the silent default let a run resolve against the dev home).
54
+ if (!inputs.crewHome) throw new Error("crewHome is required — pass the crew home explicitly; no default");
55
+ const crewHome = inputs.crewHome;
52
56
  // Crew API: the workflow calls the crew-owned CLI, not the dashboard.
53
57
  // The CLI implements the API.md contract against $CREW_HOME/crew-state.db.
54
58
  const CREW_API = crewHome + "/current/lib/crew-api.js";
package/workflows/docs.js CHANGED
@@ -32,8 +32,12 @@ const WORKFLOW_WAS_NULL = inputs.workflow_was_null === true;
32
32
  // what the dispatcher routed on.
33
33
  const NEXT_PHASE_ROUTED = (typeof inputs.next_phase === "string" && inputs.next_phase.length > 0) ? inputs.next_phase : null;
34
34
 
35
- // Config from argsbackward-compatible fallbacks for manual launches
36
- const crewHome = inputs.crewHome || "~/workspace/.jarvis";
35
+ // crewHome is requiredthe dispatcher always passes it (crew-dispatch.js
36
+ // throws without it). Fail closed instead of silently defaulting to the dev
37
+ // home: a missing home is a loud error, a wrong home is silent corruption
38
+ // (2026-09-16: the silent default let a run resolve against the dev home).
39
+ if (!inputs.crewHome) throw new Error("crewHome is required — pass the crew home explicitly; no default");
40
+ const crewHome = inputs.crewHome;
37
41
  // Crew API: the workflow calls the crew-owned CLI, not the dashboard.
38
42
  // The CLI implements the API.md contract against $CREW_HOME/crew-state.db.
39
43
  const CREW_API = crewHome + "/current/lib/crew-api.js";
@@ -49,8 +49,12 @@ const CLAIM_WORKFLOW_PERSIST = (WORKFLOW_WAS_NULL && RESOLVED_WORKFLOW) ? ", \"w
49
49
  // Manual launches without the arg default to off (previous behavior).
50
50
  var VISUAL_PROTOCOL_AVAILABLE = inputs.visual_protocol === true;
51
51
 
52
- // Config from argsbackward-compatible fallbacks for manual launches
53
- const crewHome = inputs.crewHome || "~/workspace/.jarvis";
52
+ // crewHome is requiredthe dispatcher always passes it (crew-dispatch.js
53
+ // throws without it). Fail closed instead of silently defaulting to the dev
54
+ // home: a missing home is a loud error, a wrong home is silent corruption
55
+ // (2026-09-16: the silent default let a run resolve against the dev home).
56
+ if (!inputs.crewHome) throw new Error("crewHome is required — pass the crew home explicitly; no default");
57
+ const crewHome = inputs.crewHome;
54
58
  // Crew API: the workflow calls the crew-owned CLI, not the dashboard.
55
59
  // The CLI implements the API.md contract against $CREW_HOME/crew-state.db.
56
60
  const CREW_API = crewHome + "/current/lib/crew-api.js";
@@ -67,6 +71,14 @@ const ORCH_PATH = crewHome + "/.orchestration";
67
71
  const LIFECYCLE_SRC = crewHome + "/lib/worktree-lifecycle.sh";
68
72
  const MERGE_LOCK_SRC = crewHome + "/lib/merge-lock.sh";
69
73
  const RUN_LIB = crewHome + "/.pins/" + taskId; // persistent disk, NOT /tmp (tmpfs wiped by cell reboots — canary b5efd1b1)
74
+ // Deterministic spec path — computed by the workflow, never by the agent.
75
+ // Map writes the spec to exactly SPEC_PATH; the path is handed to the agent
76
+ // as a fact and verified mechanically after the step. Agents must never
77
+ // compose crew-home paths themselves (2026-09-16: a Map agent saved the spec
78
+ // under the platform's ~/workspace/.jarvis/workflow-runs/ run dir because the
79
+ // prompt named a directory and the agent invented the rest of the path).
80
+ const SPEC_DIR = crewHome + "/task-evidence/" + taskId + "/map";
81
+ const SPEC_PATH = SPEC_DIR + "/spec.md";
70
82
  const LIFECYCLE = RUN_LIB + "/worktree-lifecycle.sh";
71
83
  const MERGE_LOCK = RUN_LIB + "/merge-lock.sh";
72
84
  const PUBLISH_NPM_SRC = crewHome + "/lib/publish-npm.sh";
@@ -1213,7 +1225,7 @@ while (i < STEPS.length) {
1213
1225
  " If the baseline evidence is missing with no baseline:none recorded, do not write the spec — report 'baseline evidence missing — Map gate bounce required' and stop.\n" +
1214
1226
  "Declare capture targets for the post-change visual capture: end your report with a line `capture_targets: <comma-separated views/controls this change affects>` (optional; falls back to the task description).";
1215
1227
  }
1216
- instructions = "Research the problem space, evaluate options, pick the shortest path.\nWrite a clear spec that a builder can execute without asking questions.\nThe builder will edit source files in a git worktree.\nProject: " + PROJECT_DESC + "\nSave the spec to a file under " + crewHome + "/ if needed.\nReport back in plain prose — what you specified." + mapGatePara;
1228
+ instructions = "Research the problem space, evaluate options, pick the shortest path.\nWrite a clear spec that a builder can execute without asking questions.\nThe builder will edit source files in a git worktree.\nProject: " + PROJECT_DESC + "\nSave the spec to exactly this file: " + SPEC_PATH + " — run mkdir -p \"" + SPEC_DIR + "\" first. Do not save the spec anywhere else; this exact path is fixed and will be checked mechanically after your step.\nReport back in plain prose — what you specified." + mapGatePara;
1217
1229
 
1218
1230
  } else if (step.name === "Build") {
1219
1231
  instructions = "STEP 1: Prepare your worktree.\n" +
@@ -1265,7 +1277,7 @@ while (i < STEPS.length) {
1265
1277
  }
1266
1278
  }
1267
1279
  instructions = "Review independently and cold. You have NOT seen any reasoning from the builder.\nDo NOT access the task dashboard, event log, or any comments. Your review is based solely on the spec and the code.\n\n" +
1268
- (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") +
1280
+ (mapperSpec ? "MAPPER'S SPEC (the builder was asked to implement exactly this):\n" + mapperSpec + "\n\n" : "Read the spec at exactly " + SPEC_PATH + " (fall back to the task description if the file is absent).\n\n") +
1269
1281
  "Examine the code changes by running:\n" +
1270
1282
  LIFECYCLE_ENV + LIFECYCLE + " inspect " + taskId + "\n\n" +
1271
1283
  "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" +
@@ -2427,11 +2439,40 @@ while (i < STEPS.length) {
2427
2439
  }
2428
2440
  }
2429
2441
 
2442
+ // Deterministic spec-path verification: the agent cannot self-certify where
2443
+ // it saved the spec. After Map passes, the workflow confirms mechanically
2444
+ // that the spec file exists at the workflow-computed SPEC_PATH (2026-09-16:
2445
+ // a Map agent saved the spec under the platform's
2446
+ // ~/workspace/.jarvis/workflow-runs/ run dir instead of the crew home,
2447
+ // because the prompt named a directory and the agent composed the path).
2448
+ // A missing spec file is an operational step failure, not a park.
2449
+ if (step.name === "Map" && passed) {
2450
+ var specVerifyOut = "";
2451
+ try {
2452
+ var specVerifyResult = await agent(
2453
+ "Run: test -f \"" + SPEC_PATH + "\" && echo SPEC_PRESENT || echo SPEC_MISSING\n" +
2454
+ "Return JSON { \"output\": \"<the command's full stdout, trimmed>\" } and nothing else.",
2455
+ { key: attemptKey("verify-spec-" + taskId, totalReworkCount), label: "Verifying spec file landed",
2456
+ schema: { type: "object", properties: { output: { type: "string" } }, required: ["output"] } }
2457
+ );
2458
+ specVerifyOut = (specVerifyResult.output || "").trim();
2459
+ } catch (e) {
2460
+ specVerifyOut = "";
2461
+ }
2462
+ if (/^SPEC_PRESENT/m.test(specVerifyOut)) {
2463
+ log("Spec verified for task " + taskId + " at " + SPEC_PATH);
2464
+ } else {
2465
+ log("Spec verification failed for task " + taskId + ": no spec file at " + SPEC_PATH + " — marking failed for retry");
2466
+ stepResult.summary = (stepResult.summary || "") + "\nspec_verify: FAILED — no spec file at " + SPEC_PATH;
2467
+ passed = false;
2468
+ }
2469
+ }
2470
+
2430
2471
  // "rejected" is an explicit phase verdict routed through rework (Review/QA,
2431
2472
  // and the Build/Reproduce reports that feed them). Integrate/Publish work
2432
2473
  // that did not finish is operational — "failed", retryable under the
2433
2474
  // dispatcher's consecutive-failure cap.
2434
- const status = passed ? "completed" : (step.name === "Integrate" || step.name === "Publish" ? "failed" : "rejected");
2475
+ const status = passed ? "completed" : (step.name === "Integrate" || step.name === "Publish" || step.name === "Map" ? "failed" : "rejected");
2435
2476
 
2436
2477
  // Deterministic publish verification: the agent cannot self-certify a publish.
2437
2478
  // Skip-aware (park 2026-09-11): when the deterministic publish script found
@@ -2616,6 +2657,16 @@ while (i < STEPS.length) {
2616
2657
  return { status: "failed", task_id: taskId, reason: "Publish failed: " + summary };
2617
2658
  }
2618
2659
 
2660
+ // Map spec failure is operational (the spec file did not land at the
2661
+ // workflow-computed SPEC_PATH), not a verdict: the session above is
2662
+ // recorded "failed" and the dispatcher retries at Map under its
2663
+ // consecutive-failure cap, parking after the cap. The retry re-runs Map
2664
+ // with the same exact-path instructions against the same SPEC_PATH.
2665
+ if (!passed && step.name === "Map") {
2666
+ log("Map spec verification failed for task " + taskId + " — returning failed for dispatcher retry");
2667
+ return { status: "failed", task_id: taskId, reason: "Map spec verification failed: no spec file at " + SPEC_PATH };
2668
+ }
2669
+
2619
2670
  // Publish verification park: the build landed and post-deploy finalized,
2620
2671
  // but provenance is UNSTAMPED until the parent's independent read-back
2621
2672
  // (docs/publish-verification.md) confirms the artifact's actual content