muse-crew 0.9.1 → 0.10.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.
@@ -0,0 +1,124 @@
1
+ # Spec: Crew-Owned State Boundaries
2
+
3
+ **Date:** 2026-09-17
4
+ **Status:** Approved by Eric — build and publish
5
+ **Context:** Gate 1 J1/J2 canaries exposed two instances of crew-owned state leaking into user git operations (0.9.1: `.worktrees/` broke cleanliness check; 0.9.2: `.merge-lock` committed via `git add -A`). The root problem: we can't enumerate all crew-owned files, so surgical exclusions are whack-a-mole.
6
+
7
+ ## The Rule
8
+
9
+ Crew state lives in exactly three places. Nothing crew-owned sits in the repo unclassified.
10
+
11
+ | Location | Version-controlled? | Contents |
12
+ |----------|---------------------|----------|
13
+ | `$REPO/.orchestration/` | **Yes** | Custom workflows, identities, phases — the user's crew configuration |
14
+ | `$REPO/.orchestration/user/` | **No** (gitignored) | Personal settings, local config, secrets — user-specific, not shared |
15
+ | `$CREW_HOME/` | N/A (outside repo) | Worktrees, merge lock, registry, pins — transient operational state |
16
+
17
+ ## Directory Structure
18
+
19
+ ```
20
+ $REPO/
21
+ .orchestration/
22
+ workflows/ # custom workflow definitions (versioned)
23
+ identities/ # custom identity definitions (versioned)
24
+ phases/ # custom phase definitions (versioned)
25
+ user/ # personal settings (GITIGNORED)
26
+ config.json # example: user preferences
27
+ secrets/ # example: local secrets (never versioned)
28
+ .gitignore # includes .orchestration/user/
29
+
30
+ $CREW_HOME/
31
+ worktrees/ # task worktrees (moved from $REPO/.worktrees/)
32
+ .merge-lock # merge lock file (moved from $REPO/.worktrees/.merge-lock)
33
+ .registry/ # task registry (moved from $REPO/.worktrees/.registry/)
34
+ .pins/ # release pins
35
+ ```
36
+
37
+ ## Changes
38
+
39
+ ### 1. Gitignore consent flow (THIS RELEASE)
40
+
41
+ **What:** The crew needs to add `.worktrees/` to the repo's `.gitignore`. This is the ONLY user file outside crew-owned directories that the crew modifies.
42
+
43
+ **When:** During the setup conversation (before `crew-init` is launched), as part of the "setup is a conversation" experience.
44
+
45
+ **UX (in the setup conversation):**
46
+
47
+ The agent (emojiman) says:
48
+
49
+ ```
50
+ The crew keeps its working files in .worktrees/ (task worktrees, merge lock).
51
+ These should never be committed to git.
52
+
53
+ I'll add one line to your .gitignore:
54
+
55
+ + .worktrees/
56
+
57
+ OK to apply? (yes/no)
58
+ ```
59
+
60
+ **If user says yes:** Launch `crew-init` with `gitignoreConsent: true`. The scaffold phase appends the entry.
61
+
62
+ **If user says no:**
63
+ ```
64
+ Got it — I'll leave your .gitignore alone. Add this line yourself when you get a chance:
65
+ .worktrees/
66
+ The crew will still work (it excludes .worktrees/ from its own git operations),
67
+ but you'll see it as untracked until you do.
68
+ ```
69
+ Launch `crew-init` with `gitignoreConsent: false`.
70
+
71
+ **`crew-init.js` inputs:**
72
+ - `gitignoreConsent` (boolean, default false): Whether the user approved `.gitignore` modification.
73
+
74
+ **Edge cases:**
75
+ - **No `.gitignore` exists:** Create it with the entry (if consent given).
76
+ - **Entry already present:** Skip (idempotent).
77
+ - **Non-interactive / not provided:** Default false (fail closed).
78
+
79
+ ### 2. `.orchestration/user/` structure (FUTURE)
80
+
81
+ When `$REPO/.orchestration/` is implemented (project-specific custom workflows, identities, phases), it will follow this structure:
82
+
83
+ ```
84
+ $REPO/.orchestration/
85
+ workflows/ # versioned — custom workflow definitions
86
+ identities/ # versioned — custom identity definitions
87
+ phases/ # versioned — custom phase definitions
88
+ user/ # GITIGNORED — personal settings, local config, secrets
89
+ ```
90
+
91
+ The gitignore consent flow will be extended to include `.orchestration/user/` at that time. The standing rule (crew modifies only `.gitignore`, with consent) already covers this.
92
+
93
+ ### 3. Worktrees relocation (deferred)
94
+
95
+ **Decision:** For this release, `.worktrees/` stays in `$REPO/.worktrees/` but is covered by the gitignore consent flow (add `.worktrees/` to `.gitignore` alongside `.orchestration/user/`).
96
+
97
+ **Rationale:** Relocating to `$CREW_HOME/worktrees/` is the cleaner long-term solution (eliminates the entire class of bug), but it's a larger change affecting `worktree-lifecycle.sh`, `merge-lock.sh`, and all workflow `WORKTREE_HINT` constants. The gitignore approach solves the immediate leak with minimal risk. Relocation is tracked as future work.
98
+
99
+ **Gitignore entries added (with consent):**
100
+ ```
101
+ .orchestration/user/
102
+ .worktrees/
103
+ ```
104
+
105
+ ### 4. Migration
106
+
107
+ Existing setups (e.g., Room #12) have `$REPO/.worktrees/` without gitignore coverage. On the next `crew-init` re-run (or manual upgrade), the consent flow will offer to add the entries. No automatic migration — the user must consent.
108
+
109
+ ## Standing Rule
110
+
111
+ If the crew ever needs to modify another user file outside `.orchestration/`, it goes through the same flow: heads-up, diff, Enter/skip. The `.gitignore` is currently the only such file.
112
+
113
+ ## Test Plan
114
+
115
+ 1. `crew-init` on a repo without `.gitignore` → creates file, adds entries, user confirms
116
+ 2. `crew-init` on a repo with existing `.gitignore` → appends entries, shows diff
117
+ 3. `crew-init` with entries already present → no-op, no prompt
118
+ 4. User types 'skip' → setup continues, warning logged
119
+ 5. Non-interactive → no modification, instruction logged
120
+ 6. Full suite passes
121
+
122
+ ## Release
123
+
124
+ Version 0.9.2 (includes the `.merge-lock` exclusion fix from d3e5790).
@@ -210,6 +210,35 @@ git -C "$T4/repo" branch -D "task/deadbeef" "task/deadbeef-1111" >/dev/null
210
210
  out=$(bash "$LIFECYCLE" resolve-branch "$fullid") || fail "prefix-test: resolve-branch failed with no branch present"
211
211
  [ "$out" = "task/$fullid" ] || fail "prefix-test: resolve-branch returned '$out', expected canonical fallback task/$fullid"
212
212
 
213
+ # post-deploy never commits the crew's .worktrees/ state into main
214
+ # (2026-09-17, Gate 1 J1 canary): cmd_post_deploy's `git add -A` swept up
215
+ # a leftover .worktrees/.merge-lock into main. The add must exclude the
216
+ # crew-owned .worktrees/ dir.
217
+ T5=/tmp/crew-git-postdeploy-test
218
+ rm -rf "$T5"
219
+ mkdir -p "$T5"
220
+ git init -q -b main "$T5"
221
+ git -C "$T5" config user.email test@test.t
222
+ git -C "$T5" config user.name test
223
+ echo base > "$T5/f.txt"
224
+ git -C "$T5" add -A
225
+ git -C "$T5" commit -qm base
226
+ export CREW_REPO="$T5"
227
+ export CREW_LIB="$REPO_DIR/lib"
228
+ out=$(bash "$LIFECYCLE" prepare "pd1") || fail "postdeploy-test: prepare failed: $out"
229
+ # Simulate the merge-lock left behind by a real integrate flow
230
+ printf 'task_id=pd1\nholder=test\n' > "$T5/.worktrees/.merge-lock"
231
+ # A genuine user change the post-deploy commit should pick up
232
+ echo userchange > "$T5/user.txt"
233
+ out=$(bash "$LIFECYCLE" post-deploy "pd1") || fail "postdeploy-test: post-deploy failed: $out"
234
+ # The user change must be committed...
235
+ git -C "$T5" log --name-only --format= -1 | grep -q '^user\.txt$' || fail "postdeploy-test: user change not committed"
236
+ # ...but the crew's merge-lock must NOT be in main's history
237
+ if git -C "$T5" log --name-only --format= | grep -q '^\.worktrees/'; then
238
+ fail "postdeploy-test: .worktrees/ content committed into main"
239
+ fi
240
+ rm -rf "$T5"
241
+
213
242
  rm -rf "$T2" "$T3" "$T4"
214
243
  export CREW_REPO="$T"
215
244
  rm -rf "$T"
@@ -414,8 +414,11 @@ cmd_post_deploy() {
414
414
  cd "$REPO"
415
415
  git checkout main 2>/dev/null
416
416
 
417
- # Commit any changes the artifact builder left behind
418
- git add -A
417
+ # Commit any changes the artifact builder left behind.
418
+ # The crew's own .worktrees/ dir (merge lock, registry) is crew-owned
419
+ # state, never user content — exclude it so a stray lock file can never
420
+ # be committed into main (J1 canary: .merge-lock landed in main via add -A).
421
+ git add -A -- ':!.worktrees/'
419
422
  if ! git diff --cached --quiet 2>/dev/null; then
420
423
  git commit -m "rebuild: $task_id"
421
424
  echo "COMMITTED: artifact builder changes"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muse-crew",
3
- "version": "0.9.1",
3
+ "version": "0.10.0",
4
4
  "description": "Opinionated orchestration for Muse \u2014 workflows, identities, and tooling for autonomous software development.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -20,6 +20,13 @@ const dashboardName = inputs.dashboardName || "Muse Crew";
20
20
  const crewName = inputs.crewName || null;
21
21
  const cronIds = inputs.cronIds || {};
22
22
 
23
+ // ── Gitignore consent input ─────────────────────────────────────────
24
+ // Set by the setup conversation before launch. If true, the scaffold phase
25
+ // appends crew-owned paths (.orchestration/user/, .worktrees/) to the repo's
26
+ // .gitignore. Defaults to false (fail closed) — the user must explicitly
27
+ // approve modifying their .gitignore during the setup conversation.
28
+ const gitignoreConsent = inputs.gitignoreConsent === true;
29
+
23
30
  // ── Automatic-update policy inputs ──────────────────────────────────
24
31
  // All optional. autoUpdateCrew/autoUpdateDashboard default to true (the
25
32
  // update watcher files upgrade tasks through the loop); updateChannel
@@ -330,6 +337,50 @@ var scaffoldCreated = scaffoldResult.created ? scaffoldResult.created.length : 0
330
337
  var scaffoldSkipped = scaffoldResult.skipped ? scaffoldResult.skipped.length : 0;
331
338
  log("Scaffold: " + scaffoldCreated + " created, " + scaffoldSkipped + " skipped");
332
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
+ // This is the ONLY user file outside .orchestration/ that the crew modifies.
344
+ if (gitignoreConsent && dashboardRepoPath) {
345
+ var gitignoreResult;
346
+ try {
347
+ gitignoreResult = await agent(
348
+ "Update the .gitignore file in the repository to exclude crew-owned paths.\n\n" +
349
+ "Repository: " + dashboardRepoPath + "\n\n" +
350
+ "Steps:\n" +
351
+ "1. Check if " + dashboardRepoPath + "/.gitignore exists.\n" +
352
+ "2. Ensure this entry is present (one per line):\n" +
353
+ " .worktrees/\n" +
354
+ "3. If the file doesn't exist, create it with that line.\n" +
355
+ "4. If it exists, append the entry if missing (do not duplicate).\n" +
356
+ "5. Report what was done.\n\n" +
357
+ "Return JSON with: { created: boolean (true if file was created), appended: array of entries that were added, skipped: array of entries already present }.",
358
+ {
359
+ key: "scaffold-gitignore",
360
+ label: "Update .gitignore for crew paths",
361
+ schema: {
362
+ type: "object",
363
+ properties: {
364
+ created: { type: "boolean" },
365
+ appended: { type: "array", items: { type: "string" } },
366
+ skipped: { type: "array", items: { type: "string" } }
367
+ },
368
+ required: ["created", "appended", "skipped"]
369
+ }
370
+ }
371
+ );
372
+ log("Gitignore: " + (gitignoreResult.created ? "created" : "updated") +
373
+ " — appended [" + (gitignoreResult.appended || []).join(", ") + "]" +
374
+ ", already present [" + (gitignoreResult.skipped || []).join(", ") + "]");
375
+ } catch (e) {
376
+ log("Gitignore update failed (non-fatal): " + String(e.message || e));
377
+ }
378
+ } else if (dashboardRepoPath) {
379
+ log("Gitignore: user declined — add '.worktrees/' to " + dashboardRepoPath + "/.gitignore manually.");
380
+ } else {
381
+ log("Gitignore: no repo (CLI-only mode) — skipping.");
382
+ }
383
+
333
384
  // ── Phase 3: Project registration ─────────────────────────────────────
334
385
  // Registers the first project in crew-state.db via the Crew API. In
335
386
  // dashboard mode, the dashboard becomes the crew's first project (so the