muse-crew 0.2.0 → 0.3.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 CHANGED
@@ -33,7 +33,7 @@ Update an existing task's fields. Only `id` is required; all other fields are op
33
33
  | `description` | string (≤ 3000) | no | |
34
34
  | `state` | `todo` · `in_progress` · `parked` · `done` | no | |
35
35
  | `priority` | `high` · `normal` · `low` | no | |
36
- | `project` | slug | no | |
36
+ | `project` | slug | no | **Project-move guard:** changing this to a different project throws while the task has an active run (an agent session with status `running` that started within the last hour), because the live run keeps the old project's repo context and moving it mid-phase would work on the wrong repo. Wait for the run to finish, or recover/park the task first, then move it. |
37
37
  | `workflow` | slug or null | no | |
38
38
  | `deps` | string[] | no | |
39
39
 
@@ -147,6 +147,8 @@ Register a new project.
147
147
 
148
148
  Update project fields. Only `id` is required; all others are optional patch fields. Same fields as `createproject`, except `simultaneity` minimum is 0 (for kill switch state).
149
149
 
150
+ **Context-change guard:** changing `repo_path`, `deploy_type`, or `deploy_slug` is blocked while any task in the project has an active run (an agent session with status `running` that started within the last hour): the call throws, because live runs keep the old project config and the change would split the project context mid-run. Wait for the runs to finish, or recover/park those tasks first, then retry.
151
+
150
152
  ### `deleteproject`
151
153
 
152
154
  Remove a project registration.
package/docs/guide.md CHANGED
@@ -213,7 +213,7 @@ Each phase has an assigned identity — a character with a defined personality:
213
213
  | Map | **Mara** | Designer |
214
214
  | Build | **Wren** | Quietest one, trusts the plan |
215
215
  | Review | **Cass** | Fair but exacting — holds the spec as the contract |
216
- | Integrate | **Wren** | Merges the work, pushes `main` to the repo |
216
+ | Integrate | **Wren** | Merges the work, pushes `main` to the repo (succeeds vacuously when the task branch is empty — runtime-state deliverable) |
217
217
  | Publish | **Wren** | Ships the merged code to the publish target (skipped when none) |
218
218
  | QA | **Hazel** | Code-blind, persistent, wears persona costumes |
219
219
  | Reproduce | **Hazel** | Reproduces bugs before fixing |
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -6,10 +6,13 @@
6
6
  #
7
7
  # Layout:
8
8
  # ~/.jarvis/
9
- # releases/<full-commit-hash>/ # immutable snapshot (workflows/ + lib/)
9
+ # releases/<full-commit-hash>/ # immutable snapshot (workflows/ + lib/ + seed/workflows/ docs)
10
10
  # current -> releases/<hash> # active release
11
11
  # workflows -> current/workflows # convenience (cron/dispatcher reads this)
12
12
  # lib -> current/lib # convenience
13
+ # .orchestration/workflows/ # live workflow documents the dashboard
14
+ # # reads; re-synced from the activated
15
+ # # release on every deploy/rollback
13
16
  #
14
17
  # The Muse workflow runtime snapshots .js scripts at launch.
15
18
  # /tmp/crew-lib-* pins lifecycle scripts per run.
@@ -91,6 +94,37 @@ _validate_workflows() {
91
94
  echo "VALIDATED: workflow scripts parse"
92
95
  }
93
96
 
97
+ # ── workflow doc sync ───────────────────────────────────────────────
98
+ # Decision (2026-09-09): the dashboard reads the workflow documents live
99
+ # from $CREW_HOME/.orchestration/workflows/, and `recovertask` validates
100
+ # target_phase against them — so those documents must always reflect the
101
+ # phases the dispatcher actually runs. Both the workflow scripts and the
102
+ # .md docs ship in the same repo commit, so we re-sync the docs from the
103
+ # activated release on every release activation (deploy AND rollback),
104
+ # automatically. A manual re-sync step is exactly what drifted here: the
105
+ # copy was seeded once by crew-init's `cp -n` and never refreshed, leaving
106
+ # the dashboard on pre-publish-architecture phases (no Integrate, no
107
+ # Publish) while the dispatcher ran the current phase lists.
108
+ _sync_workflow_docs() {
109
+ local home="${1:?usage: _sync_workflow_docs <jarvis-home>}"
110
+ local src="$home/current/seed/workflows"
111
+ local dest="$home/.orchestration/workflows"
112
+ if [ ! -d "$src" ]; then
113
+ echo "WORKFLOW-DOCS: no seed/workflows in activated release; skipping sync" >&2
114
+ return 0
115
+ fi
116
+ mkdir -p "$dest"
117
+ local f base copied=0
118
+ for f in "$src"/*.md; do
119
+ [ -f "$f" ] || continue
120
+ base="$(basename "$f")"
121
+ [ "$base" = "AGENTS.md" ] && continue # folder docs, not a workflow
122
+ cp -f "$f" "$dest/$base"
123
+ copied=$((copied + 1))
124
+ done
125
+ echo "WORKFLOW-DOCS: synced $copied workflow docs to $dest"
126
+ }
127
+
94
128
  # ── deploy ────────────────────────────────────────────────────────────
95
129
  # Build, validate, and atomically activate a release from repo HEAD.
96
130
  # Single command — no cross-step lock needed.
@@ -124,11 +158,18 @@ cmd_deploy() {
124
158
  mkdir -p "$staging_dir"
125
159
  if [ -d .git ]; then
126
160
  # Extract from committed tree (not working directory)
127
- git archive HEAD -- workflows lib | tar -xC "$staging_dir"
161
+ _seed=""
162
+ git cat-file -e "HEAD:seed/workflows" 2>/dev/null && _seed="seed/workflows" || true
163
+ # shellcheck disable=SC2086
164
+ git archive HEAD -- workflows lib $_seed | tar -xC "$staging_dir"
128
165
  else
129
166
  # Plain directory (npm install): copy directly
130
167
  [ -d workflows ] && cp -r workflows "$staging_dir/"
131
168
  [ -d lib ] && cp -r lib "$staging_dir/"
169
+ if [ -d seed/workflows ]; then
170
+ mkdir -p "$staging_dir/seed"
171
+ cp -r seed/workflows "$staging_dir/seed/"
172
+ fi
132
173
  fi
133
174
  # Gate: refuse to install a release whose workflow scripts don't parse.
134
175
  if ! _validate_workflows "$staging_dir"; then
@@ -147,6 +188,9 @@ cmd_deploy() {
147
188
 
148
189
  echo "ACTIVATED: $hash"
149
190
 
191
+ # Keep the dashboard's workflow documents in sync with the release.
192
+ _sync_workflow_docs "$CREW_HOME"
193
+
150
194
  # Prune old releases — keep the 5 most recent
151
195
  _prune_releases "$home" 5
152
196
  }
@@ -172,6 +216,9 @@ cmd_rollback() {
172
216
  mv -T "$tmp_link" "$CREW_HOME/current"
173
217
 
174
218
  echo "ROLLED_BACK: $current_hash -> $prev"
219
+
220
+ # Keep the dashboard's workflow documents in sync with the release.
221
+ _sync_workflow_docs "$CREW_HOME"
175
222
  }
176
223
 
177
224
  # ── current ───────────────────────────────────────────────────────────
@@ -158,8 +158,11 @@ cmd_integrate() {
158
158
  local ahead
159
159
  ahead=$(git rev-list --count "main..task/$task_id")
160
160
  if [ "$ahead" -eq 0 ]; then
161
- echo "ERROR: task/$task_id has no commits ahead of main — nothing to merge"
162
- return 1
161
+ # Approved empty diff: the deliverable was runtime state (cron,
162
+ # scheduler, dashboard config), not a repo change. Nothing to merge,
163
+ # 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"
165
+ return 0
163
166
  fi
164
167
 
165
168
  # Acquire merge lock
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muse-crew",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Opinionated orchestration for Muse — workflows, identities, and tooling for autonomous software development.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "files": [
12
12
  "workflows/",
13
- "identities/*.md",
13
+ "identities/",
14
14
  "personas/",
15
15
  "lib/",
16
16
  "seed/",
@@ -1,3 +1,7 @@
1
1
  # AGENTS.md
2
2
 
3
3
  Human-readable workflow definitions — the canonical source. `crew-init` copies these to `$CREW_HOME/.orchestration/workflows/` during setup. Each file describes one workflow type: its phases, identity assignments, and progression rules.
4
+
5
+ ## Sync policy
6
+
7
+ The workflow `.md` docs are synced from the activated release into `$CREW_HOME/.orchestration/workflows/` on every `crew-release.sh` release activation (deploy and rollback) — automatically, never as a manual step. The invariant is that the dashboard's workflow documents always reflect the phases the dispatcher actually runs: the dashboard reads those documents live from disk and `recovertask` validates target phases against them, so drift breaks recovery. Because the docs and the workflow scripts ship in the same repo commit, syncing at activation maintains the invariant by construction; the manual-step approach is what drifted (docs seeded once by `crew-init`'s `cp -n` went stale while the phases gained Integrate and Publish), so there is no manual re-sync step.
@@ -209,6 +209,7 @@ while (i < STEPS.length) {
209
209
  "cd " + REPO_PATH + "/.worktrees/" + taskId + "\n" +
210
210
  "git add -A\n" +
211
211
  "git commit -m \"fix: " + safeTitle + "\"\n\n" +
212
+ "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 summary, naming the runtime-state deliverable. Otherwise commit your changes normally.\n\n" +
212
213
  (rejectionNotes ? "This is REWORK after rejection. Address these specific issues:\n" + rejectionNotes + "\n\n" : "") +
213
214
  "Your final response MUST be valid JSON and nothing else: { \"summary\": \"what you built\", \"passed\": true }. No prose, no markdown, just the JSON object.";
214
215
 
@@ -222,6 +223,7 @@ while (i < STEPS.length) {
222
223
  REPO_PATH + "/.worktrees/" + taskId + "/\n\n" +
223
224
  "Check quality, correctness, and spec compliance.\n" +
224
225
  "Check that public-affecting changes have matching public doc updates (API.md or the published API contract). If the docs are missing or inaccurate, reject with notes on what is stale.\n" +
226
+ "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 plausible runtime-state deliverable (e.g. a cron created via the cron tool). Otherwise reject: 'no commits ahead of main and no repo_diff: none declaration — the builder likely forgot to commit'.\n" +
225
227
  (PUBLISH_TYPE === "npm" ? "PACKAGE VERSION: this project publishes to the npm registry. Validate the builder's version choice: package.json must hold valid semver; if the version was bumped it must be greater than the registry version (npm view muse-crew version), the bump scope (patch/minor/major) must fit the change, and exactly one version field may change. If the version is invalid, already published, or mis-scoped, reject with notes.\n" : "") +
226
228
  "If the work passes review, your final response MUST be valid JSON and nothing else: { \"passed\": true, \"summary\": \"approval notes\" }.\n" +
227
229
  "If the work fails review, your final response MUST be valid JSON and nothing else: { \"passed\": false, \"summary\": \"rejection notes explaining what needs to change\" }.\n" +
@@ -232,6 +234,7 @@ while (i < STEPS.length) {
232
234
  "Run: CREW_REPO=" + REPO_PATH + " " + LIFECYCLE + " integrate " + taskId + " \"merge: fix: " + safeTitle + "\"\n\n" +
233
235
  "Read the output:\n" +
234
236
  "- If it contains MERGED, integration succeeded. Report the merged commit hash.\n" +
237
+ "- If it contains MERGED_EMPTY, the branch had no commits ahead of main (a runtime-state deliverable, declared by Build as repo_diff: none). Integration succeeded vacuously: the merge lock was NOT taken and there is no new commit. Set passed to true with summary 'merged empty: no repo changes — deliverable was runtime state'. SKIP STEP 2 (push): there is no new commit to push.\n" +
235
238
  "- If it contains LOCK_HELD, another task holds the merge lock (mid Integrate/Publish). Set passed to false.\n" +
236
239
  "- If it contains CONFLICT, a merge conflict occurred. Set passed to false with details.\n" +
237
240
  "- If it contains ERROR, something else failed. Set passed to false.\n\n" +
@@ -191,6 +191,7 @@ while (i < STEPS.length) {
191
191
  "cd " + REPO_PATH + "/.worktrees/" + taskId + "\n" +
192
192
  "git add -A\n" +
193
193
  "git commit -m \"chore: " + safeTitle + "\"\n\n" +
194
+ "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 summary, naming the runtime-state deliverable. Otherwise commit your changes normally.\n\n" +
194
195
  (rejectionNotes ? "REWORK after rejection. Address:\n" + rejectionNotes + "\n\n" : "") +
195
196
  "Your final response MUST be valid JSON and nothing else: { \"summary\": \"what you built\", \"passed\": true }. No prose, no markdown, just the JSON object.";
196
197
 
@@ -204,6 +205,7 @@ while (i < STEPS.length) {
204
205
  REPO_PATH + "/.worktrees/" + taskId + "/\n\n" +
205
206
  "Check quality, correctness, spec compliance.\n" +
206
207
  "Check that public-affecting changes have matching public doc updates (API.md or the published API contract). If the docs are missing or inaccurate, reject with notes on what is stale.\n" +
208
+ "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 plausible runtime-state deliverable (e.g. a cron created via the cron tool). Otherwise reject: 'no commits ahead of main and no repo_diff: none declaration — the builder likely forgot to commit'.\n" +
207
209
  (PUBLISH_TYPE === "npm" ? "PACKAGE VERSION: this project publishes to the npm registry. Validate the builder's version choice: package.json must hold valid semver; if the version was bumped it must be greater than the registry version (npm view muse-crew version), the bump scope (patch/minor/major) must fit the change, and exactly one version field may change. If the version is invalid, already published, or mis-scoped, reject with notes.\n" : "") +
208
210
  "If it passes, your final response MUST be valid JSON and nothing else: { \"passed\": true, \"summary\": \"approval notes\" }.\n" +
209
211
  "If it fails, your final response MUST be valid JSON and nothing else: { \"passed\": false, \"summary\": \"rejection notes\" }.\n" +
@@ -214,6 +216,7 @@ while (i < STEPS.length) {
214
216
  "Run: CREW_REPO=" + REPO_PATH + " " + LIFECYCLE + " integrate " + taskId + " \"merge: chore: " + safeTitle + "\"\n\n" +
215
217
  "Read the output:\n" +
216
218
  "- If it contains MERGED, integration succeeded. Report the merged commit hash.\n" +
219
+ "- If it contains MERGED_EMPTY, the branch had no commits ahead of main (a runtime-state deliverable, declared by Build as repo_diff: none). Integration succeeded vacuously: the merge lock was NOT taken and there is no new commit. Set passed to true with summary 'merged empty: no repo changes — deliverable was runtime state'. SKIP STEP 2 (push): there is no new commit to push.\n" +
217
220
  "- If it contains LOCK_HELD, another task holds the merge lock (mid Integrate/Publish). Set passed to false.\n" +
218
221
  "- If it contains CONFLICT, a merge conflict occurred. Set passed to false with details.\n" +
219
222
  "- If it contains ERROR, something else failed. Set passed to false.\n\n" +
@@ -207,6 +207,7 @@ while (i < STEPS.length) {
207
207
  "cd " + REPO_PATH + "/.worktrees/" + taskId + "\n" +
208
208
  "git add -A\n" +
209
209
  "git commit -m \"" + safeTitle + "\"\n\n" +
210
+ "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 summary, naming the runtime-state deliverable. Otherwise commit your changes normally.\n\n" +
210
211
  (rejectionNotes ? "This is REWORK after rejection. Address these specific issues:\n" + rejectionNotes + "\n\n" : "") +
211
212
  "Your final response MUST be valid JSON and nothing else: { \"summary\": \"what you built\", \"passed\": true }. No prose, no markdown, just the JSON object.";
212
213
 
@@ -220,6 +221,7 @@ while (i < STEPS.length) {
220
221
  REPO_PATH + "/.worktrees/" + taskId + "/\n\n" +
221
222
  "Check quality, correctness, and spec compliance.\n" +
222
223
  "Check that public-affecting changes have matching public doc updates (API.md or the published API contract). If the docs are missing or inaccurate, reject with notes on what is stale.\n" +
224
+ "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 plausible runtime-state deliverable (e.g. a cron created via the cron tool). Otherwise reject: 'no commits ahead of main and no repo_diff: none declaration — the builder likely forgot to commit'.\n" +
223
225
  (PUBLISH_TYPE === "npm" ? "PACKAGE VERSION: this project publishes to the npm registry. Validate the builder's version choice: package.json must hold valid semver; if the version was bumped it must be greater than the registry version (npm view muse-crew version), the bump scope (patch/minor/major) must fit the change, and exactly one version field may change. If the version is invalid, already published, or mis-scoped, reject with notes.\n" : "") +
224
226
  "If the work passes review, your final response MUST be valid JSON and nothing else: { \"passed\": true, \"summary\": \"approval notes\" }.\n" +
225
227
  "If the work fails review, your final response MUST be valid JSON and nothing else: { \"passed\": false, \"summary\": \"rejection notes explaining what needs to change\" }.\n" +
@@ -230,6 +232,7 @@ while (i < STEPS.length) {
230
232
  "Run: CREW_REPO=" + REPO_PATH + " " + LIFECYCLE + " integrate " + taskId + " \"merge: " + safeTitle + "\"\n\n" +
231
233
  "Read the output:\n" +
232
234
  "- If it contains MERGED, integration succeeded. Report the merged commit hash.\n" +
235
+ "- If it contains MERGED_EMPTY, the branch had no commits ahead of main (a runtime-state deliverable, declared by Build as repo_diff: none). Integration succeeded vacuously: the merge lock was NOT taken and there is no new commit. Set passed to true with summary 'merged empty: no repo changes — deliverable was runtime state'. SKIP STEP 2 (push): there is no new commit to push.\n" +
233
236
  "- If it contains LOCK_HELD, another task holds the merge lock (mid Integrate/Publish). Set passed to false with summary 'merge lock held'.\n" +
234
237
  "- If it contains CONFLICT, a merge conflict occurred. Set passed to false with the conflict details.\n" +
235
238
  "- If it contains ERROR, something else failed. Set passed to false with the error.\n\n" +