muse-crew 0.4.3 → 0.4.4

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,79 @@
1
+ #!/bin/bash
2
+ # test-worktree-backend.sh — regression tests for lib/worktree-lifecycle.sh
3
+ # (git worktree backend).
4
+ #
5
+ # Lifecycle on a scratch repo:
6
+ # validate → prepare (CREATED) → prepare (REUSED) → inspect → status →
7
+ # cleanup → cleanup (idempotent) → dirty-main preflight (exit 4)
8
+ set -u
9
+
10
+ REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)"
11
+ LIFECYCLE="$REPO_DIR/lib/worktree-lifecycle.sh"
12
+
13
+ fail() { echo "FAIL: $1"; exit 1; }
14
+
15
+ T=/tmp/crew-git-backend-test
16
+ rm -rf "$T"
17
+ mkdir -p "$T"
18
+ cd "$T"
19
+ git init -q -b main .
20
+ git config user.email test@test.t
21
+ git config user.name test
22
+ echo x > f.txt
23
+ git add -A
24
+ git commit -qm init
25
+
26
+ export CREW_REPO="$T"
27
+ tid="backendtest1"
28
+
29
+ # validate: good id passes, bad id is rejected
30
+ bash "$LIFECYCLE" validate "$tid" >/dev/null || fail "validate rejected a good id"
31
+ bash "$LIFECYCLE" validate "bad id!" >/dev/null 2>&1 && fail "validate accepted a bad id"
32
+
33
+ # prepare: creates worktree + branch + registry entry
34
+ out=$(bash "$LIFECYCLE" prepare "$tid") || fail "prepare exited non-zero: $out"
35
+ echo "$out" | grep -q '^CREATED' || fail "prepare: expected CREATED, got: $out"
36
+ echo "$out" | grep -q '^BASE: ' || fail "prepare: no BASE: line"
37
+ [ -d "$T/.worktrees/$tid" ] || fail "prepare: .worktrees/$tid missing"
38
+ git -C "$T" rev-parse --verify "task/$tid" >/dev/null 2>&1 || fail "prepare: branch task/$tid missing"
39
+ [ -f "$T/.worktrees/.registry/$tid" ] || fail "prepare: registry entry missing"
40
+ grep -q '^branch=task/backendtest1$' "$T/.worktrees/.registry/$tid" || fail "prepare: registry branch wrong"
41
+ grep -q "^path=$T/.worktrees/$tid\$" "$T/.worktrees/.registry/$tid" || fail "prepare: registry path wrong"
42
+
43
+ # prepare again: reuses the existing worktree
44
+ out=$(bash "$LIFECYCLE" prepare "$tid") || fail "prepare (reuse) exited non-zero"
45
+ echo "$out" | grep -q '^REUSED' || fail "prepare: expected REUSED on second run: $out"
46
+
47
+ # inspect: diffs the task branch against main
48
+ echo change >> "$T/.worktrees/$tid/f.txt"
49
+ (cd "$T/.worktrees/$tid" && git commit -qam work)
50
+ out=$(bash "$LIFECYCLE" inspect "$tid") || fail "inspect exited non-zero"
51
+ echo "$out" | grep -q "=== Branch: task/$tid ===" || fail "inspect: wrong branch header"
52
+ echo "$out" | grep -q "change" || fail "inspect: diff missing the worktree change"
53
+
54
+ # status: reports registry, worktree, branch
55
+ out=$(bash "$LIFECYCLE" status "$tid") || fail "status exited non-zero"
56
+ echo "$out" | grep -q "=== Worktree: $tid ===" || fail "status: missing header"
57
+ echo "$out" | grep -q "task/$tid" || fail "status: missing branch info"
58
+
59
+ # cleanup: removes worktree + branch + registry entry
60
+ out=$(bash "$LIFECYCLE" cleanup "$tid") || fail "cleanup exited non-zero"
61
+ [ -d "$T/.worktrees/$tid" ] && fail "cleanup: worktree still exists"
62
+ git -C "$T" rev-parse --verify "task/$tid" >/dev/null 2>&1 && fail "cleanup: branch still exists"
63
+ [ -f "$T/.worktrees/.registry/$tid" ] && fail "cleanup: registry entry still exists"
64
+
65
+ # cleanup again: idempotent
66
+ out=$(bash "$LIFECYCLE" cleanup "$tid") || fail "cleanup (idempotent) exited non-zero"
67
+ echo "$out" | grep -q "already clean" || fail "cleanup: expected already-clean on second run"
68
+
69
+ # prepare fails closed when main is dirty (exit 4)
70
+ echo dirty >> "$T/f.txt"
71
+ if bash "$LIFECYCLE" prepare "backendtest2" >/dev/null 2>&1; then
72
+ fail "prepare succeeded with dirty main"
73
+ else
74
+ [ "$?" -eq 4 ] || fail "prepare with dirty main: expected exit 4"
75
+ fi
76
+ git -C "$T" checkout -q -- f.txt
77
+
78
+ rm -rf "$T"
79
+ echo "worktree-backend: OK"
@@ -1,5 +1,14 @@
1
1
  #!/usr/bin/env bash
2
- # worktree-lifecycle.sh — git worktree lifecycle manager for Muse Crew
2
+ # worktree-lifecycle.sh — worktree lifecycle manager for Muse Crew
3
+ #
4
+ # Git backend: task worktrees live in $REPO/.worktrees/<task_id>, on
5
+ # branches named task/<task_id>. This script is the single seam where the
6
+ # worktree strategy lives — workflows and agents never run raw git worktree
7
+ # commands; they call these lifecycle commands instead.
8
+ #
9
+ # The crew's registry ($REPO/.worktrees/.registry/<task_id>) is the source
10
+ # of truth for task→branch/path. Never reconstruct the mapping from git
11
+ # state alone.
3
12
  #
4
13
  # Commands:
5
14
  # validate <task_id> — check task ID is safe for branches/paths
@@ -30,6 +39,28 @@ ORPHAN_SWEEP="$LIB_DIR/orphan-sweep.sh"
30
39
 
31
40
  # --- helpers ---
32
41
 
42
+ # Task branch name: task/<id>. The registry wins when the task was
43
+ # prepared before.
44
+ resolve_branch() {
45
+ local task_id="$1" b
46
+ if [ -f "$REGISTRY_DIR/$task_id" ]; then
47
+ b=$(grep '^branch=' "$REGISTRY_DIR/$task_id" | cut -d= -f2-)
48
+ if [ -n "$b" ]; then printf '%s' "$b"; return 0; fi
49
+ fi
50
+ printf 'task/%s' "$task_id"
51
+ }
52
+
53
+ # Worktree path: $REPO/.worktrees/<id>. The registry wins when the task
54
+ # was prepared before.
55
+ resolve_path() {
56
+ local task_id="$1" p
57
+ if [ -f "$REGISTRY_DIR/$task_id" ]; then
58
+ p=$(grep '^path=' "$REGISTRY_DIR/$task_id" | cut -d= -f2-)
59
+ if [ -n "$p" ]; then printf '%s' "$p"; return 0; fi
60
+ fi
61
+ printf '%s/%s' "$WORKTREE_DIR" "$task_id"
62
+ }
63
+
33
64
  validate_task_id() {
34
65
  local id="$1"
35
66
  if [[ ! "$id" =~ ^[a-zA-Z0-9][a-zA-Z0-9_-]*$ ]]; then
@@ -57,12 +88,12 @@ require_clean_main() {
57
88
  }
58
89
 
59
90
  register() {
60
- local task_id="$1" base_commit="$2"
91
+ local task_id="$1" base_commit="$2" branch="$3" wt_path="$4"
61
92
  mkdir -p "$REGISTRY_DIR"
62
93
  cat > "$REGISTRY_DIR/$task_id" <<EOF
63
94
  task_id=$task_id
64
- branch=task/$task_id
65
- path=$WORKTREE_DIR/$task_id
95
+ branch=$branch
96
+ path=$wt_path
66
97
  base_commit=$base_commit
67
98
  created=$(date -u +%Y-%m-%dT%H:%M:%SZ)
68
99
  EOF
@@ -96,7 +127,7 @@ cmd_prepare() {
96
127
  return 0
97
128
  fi
98
129
 
99
- # Preflight: main must be clean
130
+ # Preflight: main must be clean — prepare fails closed.
100
131
  require_clean_main || exit 4
101
132
 
102
133
  local base_commit
@@ -107,7 +138,7 @@ cmd_prepare() {
107
138
  mkdir -p "$WORKTREE_DIR"
108
139
  git worktree add "$WORKTREE_DIR/$task_id" -b "task/$task_id"
109
140
 
110
- register "$task_id" "$base_commit"
141
+ register "$task_id" "$base_commit" "task/$task_id" "$WORKTREE_DIR/$task_id"
111
142
 
112
143
  echo "CREATED: .worktrees/$task_id on branch task/$task_id"
113
144
  echo "BASE: $base_commit"
@@ -117,26 +148,29 @@ cmd_inspect() {
117
148
  local task_id="$1"
118
149
  validate_task_id "$task_id"
119
150
 
151
+ local branch
152
+ branch=$(resolve_branch "$task_id")
153
+
120
154
  cd "$REPO"
121
155
 
122
156
  # Verify the branch exists
123
- if ! git rev-parse --verify "task/$task_id" >/dev/null 2>&1; then
124
- echo "ERROR: branch task/$task_id does not exist"
157
+ if ! git rev-parse --verify "$branch" >/dev/null 2>&1; then
158
+ echo "ERROR: branch $branch does not exist"
125
159
  return 1
126
160
  fi
127
161
 
128
- echo "=== Branch: task/$task_id ==="
162
+ echo "=== Branch: $branch ==="
129
163
  local tip
130
- tip=$(git rev-parse "task/$task_id")
164
+ tip=$(git rev-parse "$branch")
131
165
  echo "TIP: $tip"
132
166
  echo ""
133
167
  echo "=== Commits ahead of main ==="
134
- git log --oneline "main..task/$task_id"
168
+ git log --oneline "main..$branch"
135
169
  echo ""
136
- echo "=== Diff: main...task/$task_id ==="
137
- git diff --stat "main...task/$task_id"
170
+ echo "=== Diff: main...$branch ==="
171
+ git diff --stat "main...$branch"
138
172
  echo ""
139
- git diff "main...task/$task_id"
173
+ git diff "main...$branch"
140
174
  }
141
175
 
142
176
  cmd_integrate() {
@@ -144,24 +178,27 @@ cmd_integrate() {
144
178
  local commit_msg="${2:-merge: $task_id}"
145
179
  validate_task_id "$task_id"
146
180
 
181
+ local branch
182
+ branch=$(resolve_branch "$task_id")
183
+
147
184
  cd "$REPO"
148
185
 
149
186
  # Preflight: main must be clean
150
187
  require_clean_main || exit 4
151
188
 
152
189
  # Verify task branch exists and has commits ahead of main
153
- if ! git rev-parse --verify "task/$task_id" >/dev/null 2>&1; then
154
- echo "ERROR: branch task/$task_id does not exist"
190
+ if ! git rev-parse --verify "$branch" >/dev/null 2>&1; then
191
+ echo "ERROR: branch $branch does not exist"
155
192
  return 1
156
193
  fi
157
194
 
158
195
  local ahead
159
- ahead=$(git rev-list --count "main..task/$task_id")
196
+ ahead=$(git rev-list --count "main..$branch")
160
197
  if [ "$ahead" -eq 0 ]; then
161
198
  # Approved empty diff: the deliverable was runtime state (cron,
162
199
  # scheduler, dashboard config), not a repo change. Nothing to merge,
163
200
  # nothing to serialize — the merge lock is intentionally not taken.
164
- echo "MERGED_EMPTY: task/$task_id has no commits ahead of main — runtime-state deliverable, nothing to merge"
201
+ echo "MERGED_EMPTY: $branch has no commits ahead of main — runtime-state deliverable, nothing to merge"
165
202
  return 0
166
203
  fi
167
204
 
@@ -174,7 +211,7 @@ cmd_integrate() {
174
211
  echo "$lock_result"
175
212
 
176
213
  # Merge
177
- if ! git merge --no-ff "task/$task_id" -m "$commit_msg" 2>&1; then
214
+ if ! git merge --no-ff "$branch" -m "$commit_msg" 2>&1; then
178
215
  echo "CONFLICT: merge failed — aborting"
179
216
  git merge --abort 2>/dev/null || true
180
217
  exit 3
@@ -248,6 +285,10 @@ cmd_status() {
248
285
  local task_id="$1"
249
286
  validate_task_id "$task_id"
250
287
 
288
+ local branch wt_path
289
+ branch=$(resolve_branch "$task_id")
290
+ wt_path=$(resolve_path "$task_id")
291
+
251
292
  cd "$REPO"
252
293
 
253
294
  echo "=== Worktree: $task_id ==="
@@ -261,11 +302,11 @@ cmd_status() {
261
302
  fi
262
303
 
263
304
  # Worktree existence
264
- if [ -d "$WORKTREE_DIR/$task_id" ]; then
265
- echo "Worktree: exists at .worktrees/$task_id"
305
+ if [ -n "$wt_path" ] && [ -d "$wt_path" ]; then
306
+ echo "Worktree: exists at $wt_path"
266
307
  local head dirty
267
- head=$(cd "$WORKTREE_DIR/$task_id" && git rev-parse --short HEAD 2>/dev/null || echo "unknown")
268
- dirty=$(cd "$WORKTREE_DIR/$task_id" && git status --porcelain 2>/dev/null | wc -l)
308
+ head=$(cd "$wt_path" && git rev-parse --short HEAD 2>/dev/null || echo "unknown")
309
+ dirty=$(cd "$wt_path" && git status --porcelain 2>/dev/null | wc -l)
269
310
  echo "HEAD: $head"
270
311
  [ "$dirty" -gt 0 ] && echo "Dirty: $dirty uncommitted changes" || echo "Dirty: clean"
271
312
  else
@@ -273,13 +314,13 @@ cmd_status() {
273
314
  fi
274
315
 
275
316
  # Branch existence and merge status
276
- if git rev-parse --verify "task/$task_id" >/dev/null 2>&1; then
317
+ if git rev-parse --verify "$branch" >/dev/null 2>&1; then
277
318
  local ahead behind
278
- ahead=$(git rev-list --count "main..task/$task_id" 2>/dev/null || echo "?")
279
- behind=$(git rev-list --count "task/$task_id..main" 2>/dev/null || echo "?")
280
- echo "Branch: task/$task_id (${ahead} ahead, ${behind} behind main)"
319
+ ahead=$(git rev-list --count "main..$branch" 2>/dev/null || echo "?")
320
+ behind=$(git rev-list --count "$branch..main" 2>/dev/null || echo "?")
321
+ echo "Branch: $branch (${ahead} ahead, ${behind} behind main)"
281
322
  local merged
282
- merged=$(git branch --merged main 2>/dev/null | sed 's/^[* +]*//' | grep -Fx "task/$task_id" || true)
323
+ merged=$(git branch --merged main 2>/dev/null | sed 's/^[* +]*//' | grep -Fx "$branch" || true)
283
324
  [ -n "$merged" ] && echo "Merged: yes" || echo "Merged: no"
284
325
  else
285
326
  echo "Branch: not found"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muse-crew",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "Opinionated orchestration for Muse \u2014 workflows, identities, and tooling for autonomous software development.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -6,15 +6,17 @@ You are the dispatch trigger for Muse Crew. Run the authoritative dispatcher wor
6
6
 
7
7
  1. **Load tools:** Call tool_search_load_tool_namespace with paths ["workflow_launch"].
8
8
 
9
- 2. **Run the dispatcher:** Call workflow_launch with scriptPath "{crewHome}/workflows/crew-dispatch.js" and args {"dashboardSlug": "{dashboardSlug}", "crewHome": "{crewHome}"}.
9
+ 2. **Load the workflow registry:** Read the file "{crewHome}/workflows/registry.json" with the read tool and parse it as JSON. If the file does not exist (the live release predates the registry), proceed without it — omit the `registry` arg and the dispatcher will load the registry the slow way and log a warning.
10
+
11
+ 3. **Run the dispatcher:** Call workflow_launch with scriptPath "{crewHome}/workflows/crew-dispatch.js" and args {"dashboardSlug": "{dashboardSlug}", "crewHome": "{crewHome}", "registry": <parsed registry JSON, or omit the key when the file was missing>}.
10
12
 
11
13
  Wait for it to complete. It reads the board, determines eligibility, claims tasks, acknowledges the poll, and returns structured results.
12
14
 
13
- 3. **Report claims:** Extract the `claims` array from the dispatcher result. For each claim, emit one line:
15
+ 4. **Report claims:** Extract the `claims` array from the dispatcher result. For each claim, emit one line:
14
16
  LAUNCH: script={claim.scriptPath} args={JSON.stringify(claim.args)}
15
17
 
16
18
  If the dispatcher returned no claims or the claims array is empty, report: NO_DISPATCH
17
19
 
18
20
  Do NOT call workflow_launch_async for any task workflow. The main chat agent does that.
19
21
 
20
- 4. Exit silently.
22
+ 5. Exit silently.
@@ -6,7 +6,11 @@ The full software engineering workflow. Use for bug reports where reproduction m
6
6
 
7
7
  ### Triage
8
8
  **Identity:** Sage
9
- Validate, prioritize, connect dependencies, assign this workflow. If the task needs decomposition, break it into subtasks first.
9
+ Validate, prioritize, connect dependencies, assign this workflow. If the task needs decomposition, break it into subtasks first. Flags the task experiential when it changes anything rendered and visible in the artifact.
10
+
11
+ ### Capture
12
+ **Identity:** Hazel
13
+ Baseline evidence for experiential tasks only — skipped otherwise. The capture itself is parent-driven; the workflow records a baseline request and parks for the parent protocol (`docs/visual-verdict.md`). After two requests the task continues with `baseline: none`.
10
14
 
11
15
  ### Reproduce
12
16
  **Identity:** Hazel
@@ -15,7 +19,7 @@ Validate, prioritize, connect dependencies, assign this workflow. If the task ne
15
19
 
16
20
  ### Map
17
21
  **Identity:** Mara
18
- Update the task with a solution-oriented spec. Research options, pick the shortest path, write it clearly enough that the builder doesn't need to ask questions.
22
+ Update the task with a solution-oriented spec. Research options, pick the shortest path, write it clearly enough that the builder doesn't need to ask questions. For experiential tasks, the spec cannot be written without baseline evidence (or an explicit `baseline: none`) — without it Map bounces back to Capture.
19
23
 
20
24
  ### Build
21
25
  **Identity:** Wren
@@ -40,3 +44,4 @@ Optional. Ships the merged code to the project's publish target (`deploy_type` o
40
44
  **Personas:** Yes — Hazel wears a persona costume to test from that perspective.
41
45
  **Constraint:** No code context. Same tools and process as reproduction. Pass or fail. File follow-up tasks for related issues found during testing.
42
46
  Fail if public-affecting changes lack public docs. Public docs are not code — Hazel reads them as a user would.
47
+ **Visual verdict:** for experiential artifact tasks, the QA agent covers the mechanical checks only — the visual verdict is owned by the parent after the rendered post-change inspection results arrive (`docs/visual-verdict.md`). No experiential task completes without a recorded visual PASS.
@@ -1,16 +1,20 @@
1
1
  # Chore
2
2
 
3
- For routine maintenance and simple tasks. Like Standard but without QA — the change is low-risk enough to ship after review.
3
+ For routine maintenance and simple tasks. Like Standard but without QA — the change is low-risk enough to ship after review. Capture keeps its baseline pass for experiential tasks, but there is no visual verdict and no QA gate here.
4
4
 
5
5
  ## Steps
6
6
 
7
7
  ### Triage
8
8
  **Identity:** Sage
9
- Validate, prioritize, connect dependencies, assign this workflow.
9
+ Validate, prioritize, connect dependencies, assign this workflow. Flags the task experiential when it changes anything rendered and visible in the artifact.
10
+
11
+ ### Capture
12
+ **Identity:** Hazel
13
+ Baseline evidence for experiential tasks only — skipped otherwise. The capture itself is parent-driven; the workflow records a baseline request and parks for the parent protocol (`docs/visual-verdict.md`). After two requests the task continues with `baseline: none`.
10
14
 
11
15
  ### Map
12
16
  **Identity:** Mara
13
- Research options, pick the path, write the spec.
17
+ Research options, pick the path, write the spec. For experiential tasks, the spec cannot be written without baseline evidence (or an explicit `baseline: none`) — without it Map bounces back to Capture.
14
18
 
15
19
  ### Build
16
20
  **Identity:** Wren
@@ -6,11 +6,15 @@ The default workflow for feature work and improvements. Like Bugfix but without
6
6
 
7
7
  ### Triage
8
8
  **Identity:** Sage
9
- Validate, prioritize, connect dependencies, assign this workflow.
9
+ Validate, prioritize, connect dependencies, assign this workflow. Flags the task experiential when it changes anything rendered and visible in the artifact.
10
+
11
+ ### Capture
12
+ **Identity:** Hazel
13
+ Baseline evidence for experiential tasks only — skipped otherwise. The capture itself is parent-driven; the workflow records a baseline request and parks for the parent protocol (`docs/visual-verdict.md`). After two requests the task continues with `baseline: none` and final QA judges on the rubric alone.
10
14
 
11
15
  ### Map
12
16
  **Identity:** Mara
13
- Research options, pick the path, write the spec.
17
+ Research options, pick the path, write the spec. For experiential tasks, the spec cannot be written without baseline evidence (or an explicit `baseline: none`) — without it Map bounces back to Capture.
14
18
 
15
19
  ### Build
16
20
  **Identity:** Wren
@@ -35,3 +39,4 @@ Optional. Ships the merged code to the project's publish target (`deploy_type` o
35
39
  **Personas:** Yes — Hazel wears a persona costume to test from that perspective.
36
40
  **Constraint:** No code context. Pass or fail. File follow-up tasks for related issues found during testing.
37
41
  Fail if public-affecting changes lack public docs. Public docs are not code — Hazel reads them as a user would.
42
+ **Visual verdict:** for experiential artifact tasks, the QA agent covers the mechanical checks only — the visual verdict is owned by the parent after the rendered post-change inspection results arrive (`docs/visual-verdict.md`). No experiential task completes without a recorded visual PASS.
@@ -2,11 +2,11 @@
2
2
 
3
3
  Executable Muse workflow scripts (JavaScript). These are what the workflow runtime actually runs.
4
4
 
5
- - `crew-dispatch.js` — reads the board, claims eligible tasks, returns structured launch records
5
+ - `crew-dispatch.js` — reads the board, recommends eligible tasks, returns structured launch records (the launched workflow self-claims; the dispatcher never writes claims)
6
6
  - `crew-init.js` — sets up a new crew instance: orchestration folders, dashboard, cron, sample project
7
- - `standard.js` — default task workflow: Triage → Map → Build → Review → Integrate → Publish → QA
8
- - `bugfix.js` — adds Reproduce after Triage
9
- - `chore.js` — drops QA (low-risk)
7
+ - `standard.js` — default task workflow: Triage → Capture → Map → Build → Review → Integrate → Publish → QA
8
+ - `bugfix.js` — adds Capture after Triage, then Reproduce
9
+ - `chore.js` — adds Capture after Triage, drops QA (low-risk)
10
10
  - `docs.js` — Tate writes, Cass reviews
11
11
 
12
12
  The `.js` scripts here are distinct from the `.md` definitions in `seed/workflows/` and `.orchestration/workflows/`. The `.md` files describe phases and prompts; these `.js` files execute them.