muse-crew 0.1.0 → 0.1.1
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/AGENTS.md +1 -1
- package/API.md +9 -2
- package/README.md +5 -5
- package/docs/guide.md +15 -18
- package/lib/orphan-sweep.sh +8 -9
- package/package.json +1 -1
- package/seed/workflows/bugfix.md +6 -3
- package/seed/workflows/chore.md +5 -3
- package/seed/workflows/standard.md +6 -3
- package/workflows/AGENTS.md +1 -1
- package/workflows/bugfix.js +101 -45
- package/workflows/chore.js +84 -31
- package/workflows/crew-dispatch.js +3 -2
- package/workflows/crew-init.js +1 -1
- package/workflows/docs.js +7 -2
- package/workflows/standard.js +108 -51
package/AGENTS.md
CHANGED
|
@@ -5,7 +5,7 @@ Muse Crew source repository. The repo is the product; the personal instance (`$C
|
|
|
5
5
|
## Structure
|
|
6
6
|
|
|
7
7
|
- `API.md` — the Crew API contract: every action a task service must implement
|
|
8
|
-
- `.orchestration/` —
|
|
8
|
+
- `.orchestration/` — crew configuration notes (deploy config lives in the dashboard's project record — the single source of truth)
|
|
9
9
|
- `identities/` — crew member character files and portraits
|
|
10
10
|
- `lib/` — shell scripts for release, merge, worktree, and cleanup
|
|
11
11
|
- `personas/` — QA perspective costumes for Hazel
|
package/API.md
CHANGED
|
@@ -48,6 +48,13 @@ Atomically claim a task for a workflow step. Used by the dispatcher and by workf
|
|
|
48
48
|
| `step` | string (1–120) | no | The workflow phase name |
|
|
49
49
|
| `notes` | string (≤ 3000) | no | Defaults to `""` |
|
|
50
50
|
|
|
51
|
+
Returns one of:
|
|
52
|
+
|
|
53
|
+
- `{ "ok": true, "claimed": true, "session_id": "<uuid>", "session": {...} }`
|
|
54
|
+
- `{ "ok": true, "claimed": false, "reason": "already_claimed", "existing_session_id": "<uuid>" }`
|
|
55
|
+
|
|
56
|
+
`claimed` is the single field callers branch on. `reason` is present only when `claimed` is false.
|
|
57
|
+
|
|
51
58
|
### `recovertask`
|
|
52
59
|
|
|
53
60
|
Send a stuck or failed task to a specific workflow phase for recovery.
|
|
@@ -102,6 +109,7 @@ Read the event timeline.
|
|
|
102
109
|
|
|
103
110
|
| Field | Type | Required | Notes |
|
|
104
111
|
|-------|------|----------|-------|
|
|
112
|
+
| `task_id` | uuid | no | Return only events for this task |
|
|
105
113
|
| `limit` | integer (1–100) | no | Defaults to 50 |
|
|
106
114
|
|
|
107
115
|
---
|
|
@@ -129,12 +137,11 @@ Register a new project.
|
|
|
129
137
|
| `id` | slug | yes | Unique project identifier |
|
|
130
138
|
| `display_name` | string (1–120) | yes | |
|
|
131
139
|
| `repo_path` | string (1–1000) | yes | Absolute path to the project's Git repo |
|
|
132
|
-
| `deploy_type` | `artifact` · `
|
|
140
|
+
| `deploy_type` | `npm` · `artifact` · `vercel` · empty string | yes | Publish target: `npm` publishes the package to the npm registry, `artifact` rebuilds the live artifact, `vercel` is not yet implemented (blocks the task), empty skips the Publish phase |
|
|
133
141
|
| `deploy_slug` | slug or null | no | Required when `deploy_type` is `artifact` |
|
|
134
142
|
| `description` | string (≤ 3000) | no | |
|
|
135
143
|
| `simultaneity` | integer (1–100) | no | Max concurrent tasks; defaults to 2 |
|
|
136
144
|
| `quiesced` | boolean | no | Start paused; defaults to false |
|
|
137
|
-
| `idle_hunting` | boolean | no | Defaults to false |
|
|
138
145
|
|
|
139
146
|
### `updateproject`
|
|
140
147
|
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Five things make it go.
|
|
|
14
14
|
|
|
15
15
|
**Identities** are the characters who do the work — values, taste, writing style. What makes the reviewer exacting and the QA agent persistent.
|
|
16
16
|
|
|
17
|
-
**Projects** connect a repository to a
|
|
17
|
+
**Projects** connect a repository to a publish target. Every task's merged work is pushed to the project's private Git repo; the optional Publish phase then ships it to the target. Tasks belong to projects; the dispatcher routes each task through its project's configuration.
|
|
18
18
|
|
|
19
19
|
**Scheduling** uses [Muse's](https://muse.ai) always-on cron. The dispatcher checks the board every few minutes, claims what's ready, and puts the right agent on the right phase. Review rejects bounce back automatically. The system keeps going until the work is done or stuck.
|
|
20
20
|
|
|
@@ -26,15 +26,15 @@ The repo ships workflows and identities for a software development crew — one
|
|
|
26
26
|
|----------|------|----------------|
|
|
27
27
|
| **Sage** | Triage | Fast routing — classifies and prioritizes before anything else runs |
|
|
28
28
|
| **Mara** | Designer | Makes tasks solution-oriented before building starts |
|
|
29
|
-
| **Wren** | Builder | Trusts the plan, lets commits speak. Also handles integration and
|
|
29
|
+
| **Wren** | Builder | Trusts the plan, lets commits speak. Also handles integration and publishing |
|
|
30
30
|
| **Cass** | Reviewer | Fair but exacting — holds the spec as the contract until the work is tight |
|
|
31
31
|
| **Hazel** | QA | Code-blind by design. Persistent. Wears persona costumes for multi-lens testing |
|
|
32
32
|
| **Tate** | Docs | Observational, jargon-hostile |
|
|
33
33
|
|
|
34
34
|
```
|
|
35
|
-
standard: Triage → Map → Build → Review → Integrate →
|
|
36
|
-
bugfix: Triage → Reproduce → Map → Build → Review → Integrate →
|
|
37
|
-
chore: Triage → Map → Build → Review → Integrate →
|
|
35
|
+
standard: Triage → Map → Build → Review → Integrate → Publish → QA
|
|
36
|
+
bugfix: Triage → Reproduce → Map → Build → Review → Integrate → Publish → QA
|
|
37
|
+
chore: Triage → Map → Build → Review → Integrate → Publish
|
|
38
38
|
docs: Triage → Write → Review
|
|
39
39
|
```
|
|
40
40
|
|
package/docs/guide.md
CHANGED
|
@@ -5,7 +5,7 @@ This is the full setup and operations reference. If you're new, start with the [
|
|
|
5
5
|
## Components
|
|
6
6
|
|
|
7
7
|
**In this repo:**
|
|
8
|
-
- **Workflow scripts** — walk each task through its phases (Triage → Map → Build → Review → Integrate →
|
|
8
|
+
- **Workflow scripts** — walk each task through its phases (Triage → Map → Build → Review → Integrate → Publish → QA). The defaults are built for software, but you can create your own workflows for any kind of work.
|
|
9
9
|
- A **dispatcher** — reads the board and puts the crew to work.
|
|
10
10
|
- A **release system** — immutable releases with atomic symlink swap. Works with Git checkouts or npm installs.
|
|
11
11
|
- An **`.orchestration/` scaffold** — identity files, persona files, workflow documentation, and feedback conventions.
|
|
@@ -118,20 +118,19 @@ Required fields:
|
|
|
118
118
|
- `id` — kebab-case identifier, unique across all projects.
|
|
119
119
|
- `display_name` — human-readable name.
|
|
120
120
|
- `repo_path` — absolute path to the project's Git repository.
|
|
121
|
-
- `deploy_type` — `"artifact"` (
|
|
121
|
+
- `deploy_type` — names the project's **publish target**: `"npm"` (installs the immutable release and publishes the package to the npm registry), `"artifact"` (rebuilds the live artifact from disk), `"vercel"` (not yet implemented — blocks the task), or empty (no publish target — the Publish phase is skipped, for prototyping projects). Every project pushes merged work to its private Git repo in Integrate regardless of target — never force-push; a non-fast-forward rejection blocks the task.
|
|
122
122
|
|
|
123
123
|
Optional fields:
|
|
124
124
|
- `deploy_slug` — the artifact slug, required when `deploy_type` is `"artifact"`.
|
|
125
125
|
- `description` — free text.
|
|
126
126
|
- `simultaneity` — max concurrent tasks for this project (default: 2, range: 1–100).
|
|
127
127
|
- `quiesced` — start paused (default: false).
|
|
128
|
-
- `idle_hunting` — not yet implemented (default: false).
|
|
129
128
|
|
|
130
129
|
### What a registered project needs
|
|
131
130
|
|
|
132
131
|
The project must have:
|
|
133
132
|
- A Git repository at the specified `repo_path`.
|
|
134
|
-
- For artifact
|
|
133
|
+
- For the `artifact` publish target: an existing web artifact at the specified `deploy_slug`.
|
|
135
134
|
|
|
136
135
|
The project does **not** need:
|
|
137
136
|
- A `.orchestration/` folder (that belongs to the crew home, not the project).
|
|
@@ -199,9 +198,9 @@ Every 3 minutes, the `crew-poll` cron fires:
|
|
|
199
198
|
|
|
200
199
|
| Workflow | Phases |
|
|
201
200
|
|----------|--------|
|
|
202
|
-
| standard | Triage → Map → Build → Review → Integrate →
|
|
203
|
-
| bugfix | Triage → Reproduce → Map → Build → Review → Integrate →
|
|
204
|
-
| chore | Triage → Map → Build → Review → Integrate →
|
|
201
|
+
| standard | Triage → Map → Build → Review → Integrate → Publish → QA |
|
|
202
|
+
| bugfix | Triage → Reproduce → Map → Build → Review → Integrate → Publish → QA |
|
|
203
|
+
| chore | Triage → Map → Build → Review → Integrate → Publish |
|
|
205
204
|
| docs | Triage → Write → Review |
|
|
206
205
|
|
|
207
206
|
## Identities
|
|
@@ -214,8 +213,8 @@ Each phase has an assigned identity — a character with a defined personality:
|
|
|
214
213
|
| Map | **Mara** | Designer |
|
|
215
214
|
| Build | **Wren** | Quietest one, trusts the plan |
|
|
216
215
|
| Review | **Cass** | Fair but exacting — holds the spec as the contract |
|
|
217
|
-
| Integrate | **Wren** | Merges the work |
|
|
218
|
-
|
|
|
216
|
+
| Integrate | **Wren** | Merges the work, pushes `main` to the repo |
|
|
217
|
+
| Publish | **Wren** | Ships the merged code to the publish target (skipped when none) |
|
|
219
218
|
| QA | **Hazel** | Code-blind, persistent, wears persona costumes |
|
|
220
219
|
| Reproduce | **Hazel** | Reproduces bugs before fixing |
|
|
221
220
|
| Write | **Tate** | Docs writer, observational voice |
|
|
@@ -281,7 +280,7 @@ The full pause/resume/dispatch cycle has been proven end-to-end.
|
|
|
281
280
|
|
|
282
281
|
Releases are immutable snapshots of the crew's runtime code.
|
|
283
282
|
|
|
284
|
-
###
|
|
283
|
+
### Installing a release
|
|
285
284
|
|
|
286
285
|
```bash
|
|
287
286
|
crew-release.sh deploy <source-path>
|
|
@@ -294,7 +293,7 @@ crew-release.sh deploy <source-path>
|
|
|
294
293
|
5. Atomically swaps the `current` symlink to point at the new release.
|
|
295
294
|
6. Prunes old releases, keeping the 5 most recent.
|
|
296
295
|
|
|
297
|
-
The `crew-release.sh` manager itself lives at `crewHome/crew-release.sh`, outside the managed releases, so it's never overwritten by a
|
|
296
|
+
The `crew-release.sh` manager itself lives at `crewHome/crew-release.sh`, outside the managed releases, so it's never overwritten by a release activation.
|
|
298
297
|
|
|
299
298
|
### Rollback
|
|
300
299
|
|
|
@@ -306,7 +305,7 @@ Activates the second-newest release by modification time.
|
|
|
306
305
|
|
|
307
306
|
### Safety
|
|
308
307
|
|
|
309
|
-
Running workflows are safe from mid-
|
|
308
|
+
Running workflows are safe from mid-publish changes. Muse snapshots workflow JavaScript at launch time, so a deploy that swaps the symlink does not affect workflows already in flight.
|
|
310
309
|
|
|
311
310
|
### Release identity
|
|
312
311
|
|
|
@@ -322,12 +321,10 @@ crew-release.sh current
|
|
|
322
321
|
|
|
323
322
|
2. **Stuck sessions.** If a workflow dies mid-run, its session stays `running` indefinitely and the cron skips it every tick. Manual cleanup is currently required.
|
|
324
323
|
|
|
325
|
-
3. **No
|
|
324
|
+
3. **No streaming.** The polling loop checks every 3 minutes. There is no webhook or event-driven dispatch.
|
|
326
325
|
|
|
327
|
-
4. **
|
|
326
|
+
4. **Single dispatcher.** Only one cron runs the dispatcher. Concurrent ticks are prevented by the session-claiming atomicity, but there is no explicit distributed lock.
|
|
328
327
|
|
|
329
|
-
5. **
|
|
328
|
+
5. **No automatic retry escalation.** Failed tasks retry the same phase on the next tick. There is no backoff, retry limit, or escalation path.
|
|
330
329
|
|
|
331
|
-
6. **
|
|
332
|
-
|
|
333
|
-
7. **QA is code-blind.** Hazel uses `artifact_inspect` for visual/functional testing. She cannot read source code — this is by design (context isolation), but it means QA catches only what's visible or inspectable through the artifact's public interface.
|
|
330
|
+
6. **QA is code-blind.** Hazel uses `artifact_inspect` for visual/functional testing. She cannot read source code — this is by design (context isolation), but it means QA catches only what's visible or inspectable through the artifact's public interface.
|
package/lib/orphan-sweep.sh
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
# orphan-sweep.sh — find and clean orphaned worktrees + stale merge locks
|
|
3
3
|
#
|
|
4
4
|
# Usage:
|
|
5
|
-
# orphan-sweep.sh report — list orphans
|
|
5
|
+
# orphan-sweep.sh report — list orphans (read-only for worktrees);
|
|
6
|
+
# force-releases stale locks with dead PIDs
|
|
6
7
|
# orphan-sweep.sh clean — remove safe-to-clean orphans (merged branches only)
|
|
7
|
-
# and
|
|
8
|
+
# and force-release stale merge locks (>30 min)
|
|
8
9
|
#
|
|
9
10
|
# "Safe to clean" means the task branch is fully merged into main.
|
|
10
11
|
# Dirty or unmerged worktrees are always preserved and reported.
|
|
@@ -40,13 +41,11 @@ if [ -f "$LOCK_FILE" ]; then
|
|
|
40
41
|
else
|
|
41
42
|
echo "STALE_LOCK: held ${age_min}m by $lock_holder (pid ${lock_pid:-none} dead) — threshold ${STALE_LOCK_MIN}m"
|
|
42
43
|
found=1
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
echo " → ERROR: lock file still exists after rm at $LOCK_FILE"
|
|
49
|
-
fi
|
|
44
|
+
rm -f "$LOCK_FILE"
|
|
45
|
+
if [ ! -f "$LOCK_FILE" ]; then
|
|
46
|
+
echo " → released (dead PID)"
|
|
47
|
+
else
|
|
48
|
+
echo " → ERROR: lock file still exists after rm at $LOCK_FILE"
|
|
50
49
|
fi
|
|
51
50
|
fi
|
|
52
51
|
fi
|
package/package.json
CHANGED
package/seed/workflows/bugfix.md
CHANGED
|
@@ -19,21 +19,24 @@ Update the task with a solution-oriented spec. Research options, pick the shorte
|
|
|
19
19
|
|
|
20
20
|
### Build
|
|
21
21
|
**Identity:** Wren
|
|
22
|
-
Execute the spec. Write the code, write the tests.
|
|
22
|
+
Execute the spec. Write the code, write the tests. Update public docs for public-affecting changes — docs ship with implementation.
|
|
23
23
|
|
|
24
24
|
### Review
|
|
25
25
|
**Identity:** Cass
|
|
26
26
|
**Constraint:** Independent context. Do not share the builder's context. Read the spec and review the changes cold.
|
|
27
|
+
Verify public docs are current for public-affecting changes; reject if stale.
|
|
27
28
|
|
|
28
29
|
### Integrate
|
|
29
30
|
**Identity:** Wren
|
|
30
31
|
Merge changes into the target branch. Create the commit with a clear message.
|
|
32
|
+
Then push `main` to the project's private remote. Never force-push: a non-fast-forward rejection blocks the task.
|
|
31
33
|
|
|
32
|
-
###
|
|
34
|
+
### Publish
|
|
33
35
|
**Identity:** Wren
|
|
34
|
-
|
|
36
|
+
Optional. Ships the merged code to the project's publish target (`deploy_type` on the project record): `npm` installs the immutable release and publishes the package to the registry; `artifact` rebuilds the live artifact from disk; `vercel` is not implemented and blocks the task; an empty target skips this phase. The repo push already happened in Integrate — Publish never touches git.
|
|
35
37
|
|
|
36
38
|
### QA
|
|
37
39
|
**Identity:** Hazel
|
|
38
40
|
**Personas:** Yes — Hazel wears a persona costume to test from that perspective.
|
|
39
41
|
**Constraint:** No code context. Same tools and process as reproduction. Pass or fail. File follow-up tasks for related issues found during testing.
|
|
42
|
+
Fail if public-affecting changes lack public docs. Public docs are not code — Hazel reads them as a user would.
|
package/seed/workflows/chore.md
CHANGED
|
@@ -14,16 +14,18 @@ Research options, pick the path, write the spec.
|
|
|
14
14
|
|
|
15
15
|
### Build
|
|
16
16
|
**Identity:** Wren
|
|
17
|
-
Execute the spec.
|
|
17
|
+
Execute the spec. Update public docs for public-affecting changes — docs ship with implementation.
|
|
18
18
|
|
|
19
19
|
### Review
|
|
20
20
|
**Identity:** Cass
|
|
21
21
|
**Constraint:** Independent context. Review cold.
|
|
22
|
+
Verify public docs are current for public-affecting changes; reject if stale.
|
|
22
23
|
|
|
23
24
|
### Integrate
|
|
24
25
|
**Identity:** Wren
|
|
25
26
|
Merge changes into the target branch. Create the commit with a clear message.
|
|
27
|
+
Then push `main` to the project's private remote. Never force-push: a non-fast-forward rejection blocks the task.
|
|
26
28
|
|
|
27
|
-
###
|
|
29
|
+
### Publish
|
|
28
30
|
**Identity:** Wren
|
|
29
|
-
|
|
31
|
+
Optional. Ships the merged code to the project's publish target (`deploy_type` on the project record): `npm` installs the immutable release and publishes the package to the registry; `artifact` rebuilds the live artifact from disk; `vercel` is not implemented and blocks the task; an empty target skips this phase. The repo push already happened in Integrate — Publish never touches git.
|
|
@@ -14,21 +14,24 @@ Research options, pick the path, write the spec.
|
|
|
14
14
|
|
|
15
15
|
### Build
|
|
16
16
|
**Identity:** Wren
|
|
17
|
-
Execute the spec.
|
|
17
|
+
Execute the spec. Update public docs for public-affecting changes — docs ship with implementation.
|
|
18
18
|
|
|
19
19
|
### Review
|
|
20
20
|
**Identity:** Cass
|
|
21
21
|
**Constraint:** Independent context. Review cold.
|
|
22
|
+
Verify public docs are current for public-affecting changes; reject if stale.
|
|
22
23
|
|
|
23
24
|
### Integrate
|
|
24
25
|
**Identity:** Wren
|
|
25
26
|
Merge changes into the target branch. Create the commit with a clear message.
|
|
27
|
+
Then push `main` to the project's private remote. Never force-push: a non-fast-forward rejection blocks the task.
|
|
26
28
|
|
|
27
|
-
###
|
|
29
|
+
### Publish
|
|
28
30
|
**Identity:** Wren
|
|
29
|
-
|
|
31
|
+
Optional. Ships the merged code to the project's publish target (`deploy_type` on the project record): `npm` installs the immutable release and publishes the package to the registry; `artifact` rebuilds the live artifact from disk; `vercel` is not implemented and blocks the task; an empty target skips this phase. The repo push already happened in Integrate — Publish never touches git.
|
|
30
32
|
|
|
31
33
|
### QA
|
|
32
34
|
**Identity:** Hazel
|
|
33
35
|
**Personas:** Yes — Hazel wears a persona costume to test from that perspective.
|
|
34
36
|
**Constraint:** No code context. Pass or fail. File follow-up tasks for related issues found during testing.
|
|
37
|
+
Fail if public-affecting changes lack public docs. Public docs are not code — Hazel reads them as a user would.
|
package/workflows/AGENTS.md
CHANGED
|
@@ -4,7 +4,7 @@ Executable Muse workflow scripts (JavaScript). These are what the workflow runti
|
|
|
4
4
|
|
|
5
5
|
- `crew-dispatch.js` — reads the board, claims eligible tasks, returns structured launch records
|
|
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 →
|
|
7
|
+
- `standard.js` — default task workflow: Triage → Map → Build → Review → Integrate → Publish → QA
|
|
8
8
|
- `bugfix.js` — adds Reproduce after Triage
|
|
9
9
|
- `chore.js` — drops QA (low-risk)
|
|
10
10
|
- `docs.js` — Tate writes, Cass reviews
|
package/workflows/bugfix.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const meta = {
|
|
2
2
|
name: "crew-bugfix",
|
|
3
|
-
description: "Bugfix workflow: Triage → Reproduce → Map → Build → Review → Integrate →
|
|
4
|
-
phases: ["Triage", "Reproduce", "Map", "Build", "Review", "Integrate", "
|
|
3
|
+
description: "Bugfix workflow: Triage → Reproduce → Map → Build → Review → Integrate → Publish → QA",
|
|
4
|
+
phases: ["Triage", "Reproduce", "Map", "Build", "Review", "Integrate", "Publish", "QA"],
|
|
5
5
|
steps: [
|
|
6
6
|
{ name: "Triage", identity: "sage" },
|
|
7
7
|
{ name: "Reproduce", identity: "hazel" },
|
|
@@ -9,7 +9,7 @@ export const meta = {
|
|
|
9
9
|
{ name: "Build", identity: "wren" },
|
|
10
10
|
{ name: "Review", identity: "cass" },
|
|
11
11
|
{ name: "Integrate", identity: "wren" },
|
|
12
|
-
{ name: "
|
|
12
|
+
{ name: "Publish", identity: "wren" },
|
|
13
13
|
{ name: "QA", identity: "hazel" }
|
|
14
14
|
],
|
|
15
15
|
reworkTarget: "Build"
|
|
@@ -32,12 +32,14 @@ const MERGE_LOCK_SRC = crewHome + "/lib/merge-lock.sh";
|
|
|
32
32
|
const RUN_LIB = "/tmp/crew-lib-" + taskId;
|
|
33
33
|
const LIFECYCLE = RUN_LIB + "/worktree-lifecycle.sh";
|
|
34
34
|
const MERGE_LOCK = RUN_LIB + "/merge-lock.sh";
|
|
35
|
+
const ORPHAN_SWEEP_SRC = crewHome + "/lib/orphan-sweep.sh";
|
|
36
|
+
const ORPHAN_SWEEP = RUN_LIB + "/orphan-sweep.sh";
|
|
35
37
|
|
|
36
38
|
// Project config — passed by dispatcher, falls back to dashboard defaults
|
|
37
39
|
const projectConfig = inputs.project_config || {};
|
|
38
40
|
const REPO_PATH = projectConfig.repo_path || "~/workspace/ts-spaces/orchestra-dashboard";
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
+
const PUBLISH_TYPE = projectConfig.deploy_type || "";
|
|
42
|
+
const PUBLISH_SLUG = projectConfig.deploy_slug || DASHBOARD_SLUG;
|
|
41
43
|
const PROJECT_DESC = projectConfig.description || "React + TypeScript web dashboard (client/src/, server/src/, drizzle/)";
|
|
42
44
|
const RELEASE_SCRIPT = crewHome + "/crew-release.sh";
|
|
43
45
|
|
|
@@ -61,13 +63,15 @@ const STEPS = [
|
|
|
61
63
|
{ name: "Build", identity: "wren" },
|
|
62
64
|
{ name: "Review", identity: "cass" },
|
|
63
65
|
{ name: "Integrate", identity: "wren" },
|
|
64
|
-
{ name: "
|
|
66
|
+
{ name: "Publish", identity: "wren" },
|
|
65
67
|
{ name: "QA", identity: "hazel" }
|
|
66
68
|
];
|
|
67
69
|
const BUILD_INDEX = STEPS.findIndex(s => s.name === 'Build');
|
|
68
70
|
if (BUILD_INDEX < 0) throw new Error("STEPS missing 'Build' step");
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
// Shared rework budget: Review and QA rejections draw from the SAME pool of 2.
|
|
72
|
+
// E.g. 2 Review bounces + 1 QA bounce = 3 total > budget -> task blocks.
|
|
73
|
+
const MAX_TOTAL_REWORK = 2;
|
|
74
|
+
let totalReworkCount = 0;
|
|
71
75
|
let rejectionNotes = inputs.rejection_notes || "";
|
|
72
76
|
let mapperSpec = "";
|
|
73
77
|
let i = startStepIndex;
|
|
@@ -75,17 +79,35 @@ let i = startStepIndex;
|
|
|
75
79
|
// Pin lifecycle scripts
|
|
76
80
|
await agent(
|
|
77
81
|
"Snapshot lifecycle scripts for version pinning.\n" +
|
|
78
|
-
"Run: mkdir -p " + RUN_LIB + " && cp " + LIFECYCLE_SRC + " " + LIFECYCLE + " && cp " + MERGE_LOCK_SRC + " " + MERGE_LOCK + " && chmod +x " + LIFECYCLE + " " + MERGE_LOCK,
|
|
82
|
+
"Run: mkdir -p " + RUN_LIB + " && cp " + LIFECYCLE_SRC + " " + LIFECYCLE + " && cp " + MERGE_LOCK_SRC + " " + MERGE_LOCK + " && cp " + ORPHAN_SWEEP_SRC + " " + ORPHAN_SWEEP + " && chmod +x " + LIFECYCLE + " " + MERGE_LOCK + " " + ORPHAN_SWEEP,
|
|
79
83
|
{ key: "pin-lifecycle", label: "Pinning lifecycle scripts", schema: { type: "object" } }
|
|
80
84
|
);
|
|
81
85
|
|
|
82
86
|
while (i < STEPS.length) {
|
|
83
87
|
const step = STEPS[i];
|
|
84
|
-
const isFirstClaim = (i === startStepIndex &&
|
|
88
|
+
const isFirstClaim = (i === startStepIndex && totalReworkCount === 0);
|
|
85
89
|
|
|
86
90
|
phase(step.name);
|
|
87
91
|
log(step.name + " step (" + step.identity + ") for task " + taskId);
|
|
88
92
|
|
|
93
|
+
// Publish is optional and target-based. Empty target = prototyping project: skip the phase.
|
|
94
|
+
// Unknown target (incl. legacy "repo") = config error: block.
|
|
95
|
+
if (step.name === "Publish" && !PUBLISH_TYPE) {
|
|
96
|
+
log("Publish skipped for task " + taskId + " — no publish target configured (deploy_type empty)");
|
|
97
|
+
await agent(
|
|
98
|
+
"Release the merge lock and clean up without publishing.\n" +
|
|
99
|
+
"Run: CREW_REPO=" + REPO_PATH + " " + LIFECYCLE + " post-deploy " + taskId + "\n" +
|
|
100
|
+
"If the output contains DEPLOYED, the lock is released and the worktree is cleaned up.",
|
|
101
|
+
{ key: "publish-skip-cleanup", label: "Skipping Publish (no target)", schema: { type: "object" } }
|
|
102
|
+
);
|
|
103
|
+
i++;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (step.name === "Publish" && PUBLISH_TYPE !== "npm" && PUBLISH_TYPE !== "artifact" && PUBLISH_TYPE !== "vercel") {
|
|
107
|
+
log("Unknown publish target for task " + taskId + ": " + PUBLISH_TYPE);
|
|
108
|
+
return { status: "blocked", task_id: taskId, reason: "Unknown publish target '" + PUBLISH_TYPE + "' — expected 'npm', 'artifact', 'vercel', or empty (skip publish)." };
|
|
109
|
+
}
|
|
110
|
+
|
|
89
111
|
// Claim session
|
|
90
112
|
let activeSessionId;
|
|
91
113
|
if (isFirstClaim && firstSessionId) {
|
|
@@ -94,10 +116,10 @@ while (i < STEPS.length) {
|
|
|
94
116
|
const claimResult = await agent(
|
|
95
117
|
"Claim a session for this task step.\n" +
|
|
96
118
|
"Call artifact_invoke_action on slug \"" + DASHBOARD_SLUG + "\", action \"claimtask\", args:\n" +
|
|
97
|
-
"{ \"task_id\": \"" + taskId + "\", \"identity\": \"" + step.identity + "\", \"step\": \"" + step.name + "\", \"notes\": \"" + step.name + " step started" + (
|
|
119
|
+
"{ \"task_id\": \"" + taskId + "\", \"identity\": \"" + step.identity + "\", \"step\": \"" + step.name + "\", \"notes\": \"" + step.name + " step started" + (totalReworkCount > 0 ? " (rework #" + totalReworkCount + ")" : "") + "\" }.\n" +
|
|
98
120
|
"Return the session_id from the response.",
|
|
99
121
|
{
|
|
100
|
-
key: "claim-" + step.name + (
|
|
122
|
+
key: "claim-" + step.name + (totalReworkCount > 0 ? "-r" + totalReworkCount : ""),
|
|
101
123
|
label: "Claiming " + step.name,
|
|
102
124
|
schema: {
|
|
103
125
|
type: "object",
|
|
@@ -137,8 +159,10 @@ while (i < STEPS.length) {
|
|
|
137
159
|
(mapperSpec ? "MAPPER'S SPEC (implement exactly this):\n" + mapperSpec + "\n\n" : "") +
|
|
138
160
|
"Your working directory: " + REPO_PATH + "/.worktrees/" + taskId + "/\n" +
|
|
139
161
|
"This is the project source: " + PROJECT_DESC + "\n" +
|
|
140
|
-
"Edit the TypeScript source files directly. Do NOT use artifact_edit — that happens in the
|
|
141
|
-
"Do not add unrequested features. Build exactly what the spec calls for.\n
|
|
162
|
+
"Edit the TypeScript source files directly. Do NOT use artifact_edit — that happens in the Publish phase.\n" +
|
|
163
|
+
"Do not add unrequested features. Build exactly what the spec calls for.\n" +
|
|
164
|
+
"PUBLIC DOCS: If your change is public-affecting (it alters anything a user or consumer can observe: API actions, parameters, behavior, or errors), update the public docs in the same commit — API.md for API changes. Documentation and implementation ship together.\n\n" +
|
|
165
|
+
(PUBLISH_TYPE === "npm" ? "PACKAGE VERSION: this project publishes to the npm registry, so you choose the package version. If this change warrants a published release (anything a consumer can observe: workflow behavior, phase lists, identities, published docs, API), bump the version in package.json with semver (patch for fixes, minor for new behavior, major for breaking changes) and state the chosen version and why in your summary. If the change is internal-only, leave the version unchanged and say so. Check the registry first — npm view muse-crew version — and never re-publish an existing version.\n\n" : "") +
|
|
142
166
|
"STEP 3: Commit your changes.\n" +
|
|
143
167
|
"cd " + REPO_PATH + "/.worktrees/" + taskId + "\n" +
|
|
144
168
|
"git add -A\n" +
|
|
@@ -154,6 +178,8 @@ while (i < STEPS.length) {
|
|
|
154
178
|
"You can also read specific files in the worktree at:\n" +
|
|
155
179
|
REPO_PATH + "/.worktrees/" + taskId + "/\n\n" +
|
|
156
180
|
"Check quality, correctness, and spec compliance.\n" +
|
|
181
|
+
"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" +
|
|
182
|
+
(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" : "") +
|
|
157
183
|
"If the work passes review, your final response MUST be valid JSON and nothing else: { \"passed\": true, \"summary\": \"approval notes\" }.\n" +
|
|
158
184
|
"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" +
|
|
159
185
|
"No prose, no markdown, just the JSON object.";
|
|
@@ -163,45 +189,75 @@ while (i < STEPS.length) {
|
|
|
163
189
|
"Run: CREW_REPO=" + REPO_PATH + " " + LIFECYCLE + " integrate " + taskId + " \"merge: fix: " + safeTitle + "\"\n\n" +
|
|
164
190
|
"Read the output:\n" +
|
|
165
191
|
"- If it contains MERGED, integration succeeded. Report the merged commit hash.\n" +
|
|
166
|
-
"- If it contains LOCK_HELD, another task
|
|
192
|
+
"- If it contains LOCK_HELD, another task holds the merge lock (mid Integrate/Publish). Set passed to false.\n" +
|
|
167
193
|
"- If it contains CONFLICT, a merge conflict occurred. Set passed to false with details.\n" +
|
|
168
194
|
"- If it contains ERROR, something else failed. Set passed to false.\n\n" +
|
|
195
|
+
"\n" +
|
|
196
|
+
"STEP 2: Push the merged main to the remote repository.\n" +
|
|
197
|
+
"Run: cd " + REPO_PATH + " && git push origin main\n" +
|
|
198
|
+
"- If the push succeeds, report the merged commit hash.\n" +
|
|
199
|
+
"- If the push is rejected as non-fast-forward (the remote has commits not present locally),\n" +
|
|
200
|
+
" NEVER force-push. Do not run any --force variant. Set passed to false with summary:\n" +
|
|
201
|
+
" 'git push origin main rejected as non-fast-forward — remote main has diverged; manual resolution required'.
|
|
202
|
+
\n\n" +
|
|
169
203
|
"Your final response MUST be valid JSON and nothing else: { \"summary\": \"result\", \"passed\": true/false }. No prose, no markdown, just the JSON object.";
|
|
170
204
|
|
|
171
|
-
} else if (step.name === "
|
|
172
|
-
if (
|
|
173
|
-
//
|
|
174
|
-
instructions = "
|
|
175
|
-
"
|
|
205
|
+
} else if (step.name === "Publish") {
|
|
206
|
+
if (PUBLISH_TYPE === "npm") {
|
|
207
|
+
// npm packages: immutable release + pack + publish to the registry (push is universal in Integrate)
|
|
208
|
+
instructions = "Publish the npm package to the registry.\n\n" +
|
|
209
|
+
"The repo push already happened in Integrate — do NOT push to git in this phase, and NEVER force-push.\n" +
|
|
210
|
+
"Version discipline: publish ships the exact version merged in Integrate (Build applied it, Review validated it). Do not bump the version here.\n\n" +
|
|
211
|
+
"STEP 0: Refresh the merge lock to prevent stale-lock breaking during publish.\n" +
|
|
176
212
|
"Run: CREW_REPO=" + REPO_PATH + " " + LIFECYCLE + " refresh-lock " + taskId + "\n\n" +
|
|
177
|
-
"STEP 1: Install and activate the
|
|
213
|
+
"STEP 1: Install and activate the immutable release.\n" +
|
|
178
214
|
"Run: " + RELEASE_SCRIPT + " deploy " + REPO_PATH + "\n" +
|
|
179
215
|
"Verify the output contains INSTALLED and ACTIVATED (or EXISTS and ACTIVATED if unchanged).\n\n" +
|
|
180
|
-
"STEP 2:
|
|
216
|
+
"STEP 2: Check whether the package version needs publishing.\n" +
|
|
217
|
+
"Read the version from: cd " + REPO_PATH + " && node -p \"require('./package.json').version\"\n" +
|
|
218
|
+
"Check the registry: npm view muse-crew version 2>/dev/null || echo NOT_FOUND\n" +
|
|
219
|
+
"If the local version matches the registry version, the version is already live — skip to STEP 5.\n\n" +
|
|
220
|
+
"STEP 3: Pack and publish.\n" +
|
|
221
|
+
"Run: cd " + REPO_PATH + " && npm pack\n" +
|
|
222
|
+
"Then publish: python3 ~/workspace/skills/npm/bin/npm-publish.py " + REPO_PATH + "/muse-crew-$(node -p \"require('" + REPO_PATH + "/package.json').version\").tgz\n" +
|
|
223
|
+
"If publish fails with 'You cannot publish over the previously published versions', the version is already live — continue to STEP 4.\n\n" +
|
|
224
|
+
"STEP 4: Verify.\n" +
|
|
225
|
+
"Run: npm view muse-crew version\n" +
|
|
226
|
+
"Confirm it matches the local package.json version.\n\n" +
|
|
227
|
+
"STEP 5: Finalize.\n" +
|
|
181
228
|
"Run: CREW_REPO=" + REPO_PATH + " " + LIFECYCLE + " post-deploy " + taskId + "\n" +
|
|
182
229
|
"If the output contains DEPLOYED, finalization is complete.\n\n" +
|
|
183
|
-
"Your final response MUST be valid JSON and nothing else: { \"summary\": \"
|
|
230
|
+
"Your final response MUST be valid JSON and nothing else: { \"summary\": \"result\", \"passed\": true }.\n" +
|
|
184
231
|
"No prose, no markdown, just the JSON object.";
|
|
185
|
-
} else {
|
|
186
|
-
|
|
187
|
-
|
|
232
|
+
} else if (PUBLISH_TYPE === "artifact") {
|
|
233
|
+
// Artifact projects: rebuild the live artifact via artifact_edit, then finalize
|
|
234
|
+
instructions = "Publish the merged code to the live artifact.\n\n" +
|
|
235
|
+
"The repo push already happened in Integrate — do NOT push to git in this phase.\n\n" +
|
|
236
|
+
"STEP 0: Refresh the merge lock to prevent stale-lock breaking during publish.\n" +
|
|
188
237
|
"Run: CREW_REPO=" + REPO_PATH + " " + LIFECYCLE + " refresh-lock " + taskId + "\n\n" +
|
|
189
|
-
"STEP 1:
|
|
238
|
+
"STEP 1: Publish to the live artifact.\n" +
|
|
190
239
|
"Get the change summary: cd " + REPO_PATH + " && git log -1 --stat\n" +
|
|
191
|
-
"Then call artifact_edit with slug \"" +
|
|
240
|
+
"Then call artifact_edit with slug \"" + PUBLISH_SLUG + "\" and verbatim_request:\n" +
|
|
192
241
|
"'Rebuild the application from current source. Do not modify any source files — just rebuild and deploy what is on disk.'\n" +
|
|
193
242
|
"Wait for the build to complete by polling artifact_status until it is no longer running.\n\n" +
|
|
194
243
|
"STEP 2: Finalize.\n" +
|
|
195
244
|
"Run: CREW_REPO=" + REPO_PATH + " " + LIFECYCLE + " post-deploy " + taskId + "\n" +
|
|
196
|
-
"If the output contains DEPLOYED,
|
|
245
|
+
"If the output contains DEPLOYED, publishing is complete.\n\n" +
|
|
197
246
|
"If artifact_edit failed, still run post-deploy to release the merge lock and clean up.\n" +
|
|
198
|
-
"Report the failure: { \"passed\": false, \"summary\": \"artifact
|
|
199
|
-
"Your final response MUST be valid JSON and nothing else: { \"summary\": \"
|
|
247
|
+
"Report the failure: { \"passed\": false, \"summary\": \"artifact publish failed: [details]\" }.\n\n" +
|
|
248
|
+
"Your final response MUST be valid JSON and nothing else: { \"summary\": \"published changes\", \"passed\": true }.\n" +
|
|
249
|
+
"No prose, no markdown, just the JSON object.";
|
|
250
|
+
} else if (PUBLISH_TYPE === "vercel") {
|
|
251
|
+
// vercel publish is not yet implemented — block without inventing behavior
|
|
252
|
+
instructions = "The project's publish target is \"vercel\", which is not yet implemented.\n" +
|
|
253
|
+
"Do NOT invent publish behavior — do not guess CLI commands, APIs, or deployment steps.\n" +
|
|
254
|
+
"Your final response MUST be valid JSON and nothing else: { \"passed\": false, \"summary\": \"vercel publish not yet implemented\" }.\n" +
|
|
200
255
|
"No prose, no markdown, just the JSON object.";
|
|
201
256
|
}
|
|
202
|
-
|
|
203
257
|
} else if (step.name === "QA") {
|
|
204
258
|
instructions = "Final QA testing. You are CODE-BLIND — do NOT read source code.\n" +
|
|
259
|
+
"Public docs (API.md, README, published action schemas) are NOT source code — read them freely, exactly as a user would.\n" +
|
|
260
|
+
"DOCS GATE: If the fix is public-affecting (it alters anything a user or consumer can observe: API actions, parameters, behavior, or errors), verify the public docs describe it. If public docs are missing or stale, FAIL with { \"passed\": false, \"summary\": \"public docs missing/stale for [the change]\" }. QA always fails when public-affecting changes lack public docs. Guide/tutorial gaps are lower priority — file a follow-up task for those instead of failing.\n" +
|
|
205
261
|
"To test, use artifact_invoke_action on slug \"" + DASHBOARD_SLUG + "\" with action \"getstate\" (args {}) to read current sessions, events, and tasks.\n" +
|
|
206
262
|
"Verify the fix by checking that session notes in the returned data now handle newlines correctly.\n" +
|
|
207
263
|
"You can also check specific data with the getevents action.\n" +
|
|
@@ -216,8 +272,8 @@ while (i < STEPS.length) {
|
|
|
216
272
|
var eventPreamble = "";
|
|
217
273
|
if (step.name !== "Review") {
|
|
218
274
|
eventPreamble = "CONTEXT: First, fetch this task's event history for background.\n" +
|
|
219
|
-
"Call artifact_invoke_action on slug \"" + DASHBOARD_SLUG + "\", action \"getevents\", args: { \"
|
|
220
|
-
"
|
|
275
|
+
"Call artifact_invoke_action on slug \"" + DASHBOARD_SLUG + "\", action \"getevents\", args: { \"task_id\": \"" + taskId + "\" }.\n" +
|
|
276
|
+
"The returned events are filtered to this task. They contain notes and decisions from prior phases.\n\n";
|
|
221
277
|
}
|
|
222
278
|
|
|
223
279
|
var stepResult;
|
|
@@ -234,7 +290,7 @@ while (i < STEPS.length) {
|
|
|
234
290
|
"CONSTRAINT: Do NOT call logevent or upsertagentsession — the workflow handles all phase tracking after your step completes.\n\n" +
|
|
235
291
|
"Stay in character. Do the work thoroughly.",
|
|
236
292
|
{
|
|
237
|
-
key: "work-" + step.name + (
|
|
293
|
+
key: "work-" + step.name + (totalReworkCount > 0 ? "-r" + totalReworkCount : ""),
|
|
238
294
|
label: step.identity + ": " + step.name + " on \"" + taskTitle + "\"",
|
|
239
295
|
timeoutMs: 3600000,
|
|
240
296
|
schema: WORK_SCHEMA
|
|
@@ -282,7 +338,7 @@ while (i < STEPS.length) {
|
|
|
282
338
|
"Then call artifact_invoke_action on slug \"" + DASHBOARD_SLUG + "\", action \"logevent\", args:\n" +
|
|
283
339
|
"{ \"task_id\": \"" + taskId + "\", \"type\": \"" + status + "\", \"identity\": \"" + step.identity + "\", \"message\": \"" + step.name + " " + status + " by " + step.identity + "\" }.",
|
|
284
340
|
{
|
|
285
|
-
key: "record-" + step.name + (
|
|
341
|
+
key: "record-" + step.name + (totalReworkCount > 0 ? "-r" + totalReworkCount : ""),
|
|
286
342
|
label: "Recording " + step.name + " result",
|
|
287
343
|
schema: { type: "object" }
|
|
288
344
|
}
|
|
@@ -302,14 +358,14 @@ while (i < STEPS.length) {
|
|
|
302
358
|
|
|
303
359
|
// Review/QA rejection bounces to Build
|
|
304
360
|
if (!passed && (step.name === "Review" || step.name === "QA")) {
|
|
305
|
-
|
|
306
|
-
if (
|
|
307
|
-
log("
|
|
308
|
-
return { status: "blocked", task_id: taskId, reason: "Exceeded " +
|
|
361
|
+
totalReworkCount++;
|
|
362
|
+
if (totalReworkCount > MAX_TOTAL_REWORK) {
|
|
363
|
+
log("Shared rework budget exhausted for task " + taskId + " — worktree preserved at .worktrees/" + taskId + " for manual inspection");
|
|
364
|
+
return { status: "blocked", task_id: taskId, reason: "Exceeded shared rework budget (" + MAX_TOTAL_REWORK + " total rework attempts across Review and QA) after " + step.name + " rejection. Worktree preserved." };
|
|
309
365
|
}
|
|
310
366
|
rejectionNotes = summary;
|
|
311
367
|
i = BUILD_INDEX;
|
|
312
|
-
log(step.name + " rejected — bouncing to Build (rework #" +
|
|
368
|
+
log(step.name + " rejected — bouncing to Build (rework #" + totalReworkCount + " of " + MAX_TOTAL_REWORK + ")");
|
|
313
369
|
continue;
|
|
314
370
|
}
|
|
315
371
|
|
|
@@ -319,10 +375,10 @@ while (i < STEPS.length) {
|
|
|
319
375
|
return { status: "blocked", task_id: taskId, reason: "Integration failed: " + summary };
|
|
320
376
|
}
|
|
321
377
|
|
|
322
|
-
//
|
|
323
|
-
if (!passed && step.name === "
|
|
324
|
-
log("
|
|
325
|
-
return { status: "blocked", task_id: taskId, reason: "
|
|
378
|
+
// Publish failure blocks the task
|
|
379
|
+
if (!passed && step.name === "Publish") {
|
|
380
|
+
log("Publish failed for task " + taskId + ": " + summary);
|
|
381
|
+
return { status: "blocked", task_id: taskId, reason: "Publish failed: " + summary };
|
|
326
382
|
}
|
|
327
383
|
|
|
328
384
|
i++;
|
package/workflows/chore.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
export const meta = {
|
|
2
2
|
name: "crew-chore",
|
|
3
|
-
description: "Chore workflow: Triage → Map → Build → Review → Integrate →
|
|
4
|
-
phases: ["Triage", "Map", "Build", "Review", "Integrate", "
|
|
3
|
+
description: "Chore workflow: Triage → Map → Build → Review → Integrate → Publish",
|
|
4
|
+
phases: ["Triage", "Map", "Build", "Review", "Integrate", "Publish"],
|
|
5
5
|
steps: [
|
|
6
6
|
{ name: "Triage", identity: "sage" },
|
|
7
7
|
{ name: "Map", identity: "mara" },
|
|
8
8
|
{ name: "Build", identity: "wren" },
|
|
9
9
|
{ name: "Review", identity: "cass" },
|
|
10
10
|
{ name: "Integrate", identity: "wren" },
|
|
11
|
-
{ name: "
|
|
11
|
+
{ name: "Publish", identity: "wren" }
|
|
12
12
|
],
|
|
13
13
|
reworkTarget: "Build"
|
|
14
14
|
};
|
|
@@ -30,12 +30,14 @@ const MERGE_LOCK_SRC = crewHome + "/lib/merge-lock.sh";
|
|
|
30
30
|
const RUN_LIB = "/tmp/crew-lib-" + taskId;
|
|
31
31
|
const LIFECYCLE = RUN_LIB + "/worktree-lifecycle.sh";
|
|
32
32
|
const MERGE_LOCK = RUN_LIB + "/merge-lock.sh";
|
|
33
|
+
const ORPHAN_SWEEP_SRC = crewHome + "/lib/orphan-sweep.sh";
|
|
34
|
+
const ORPHAN_SWEEP = RUN_LIB + "/orphan-sweep.sh";
|
|
33
35
|
|
|
34
36
|
// Project config — passed by dispatcher, falls back to dashboard defaults
|
|
35
37
|
const projectConfig = inputs.project_config || {};
|
|
36
38
|
const REPO_PATH = projectConfig.repo_path || "~/workspace/ts-spaces/orchestra-dashboard";
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
+
const PUBLISH_TYPE = projectConfig.deploy_type || "";
|
|
40
|
+
const PUBLISH_SLUG = projectConfig.deploy_slug || DASHBOARD_SLUG;
|
|
39
41
|
const PROJECT_DESC = projectConfig.description || "React + TypeScript web dashboard (client/src/, server/src/, drizzle/)";
|
|
40
42
|
const RELEASE_SCRIPT = crewHome + "/crew-release.sh";
|
|
41
43
|
|
|
@@ -58,7 +60,7 @@ const STEPS = [
|
|
|
58
60
|
{ name: "Build", identity: "wren" },
|
|
59
61
|
{ name: "Review", identity: "cass" },
|
|
60
62
|
{ name: "Integrate", identity: "wren" },
|
|
61
|
-
{ name: "
|
|
63
|
+
{ name: "Publish", identity: "wren" }
|
|
62
64
|
];
|
|
63
65
|
const BUILD_INDEX = STEPS.findIndex(s => s.name === 'Build');
|
|
64
66
|
if (BUILD_INDEX < 0) throw new Error("STEPS missing 'Build' step");
|
|
@@ -71,7 +73,7 @@ let i = startStepIndex;
|
|
|
71
73
|
// Pin lifecycle scripts
|
|
72
74
|
await agent(
|
|
73
75
|
"Snapshot lifecycle scripts for version pinning.\n" +
|
|
74
|
-
"Run: mkdir -p " + RUN_LIB + " && cp " + LIFECYCLE_SRC + " " + LIFECYCLE + " && cp " + MERGE_LOCK_SRC + " " + MERGE_LOCK + " && chmod +x " + LIFECYCLE + " " + MERGE_LOCK,
|
|
76
|
+
"Run: mkdir -p " + RUN_LIB + " && cp " + LIFECYCLE_SRC + " " + LIFECYCLE + " && cp " + MERGE_LOCK_SRC + " " + MERGE_LOCK + " && cp " + ORPHAN_SWEEP_SRC + " " + ORPHAN_SWEEP + " && chmod +x " + LIFECYCLE + " " + MERGE_LOCK + " " + ORPHAN_SWEEP,
|
|
75
77
|
{ key: "pin-lifecycle", label: "Pinning lifecycle scripts", schema: { type: "object" } }
|
|
76
78
|
);
|
|
77
79
|
|
|
@@ -82,6 +84,24 @@ while (i < STEPS.length) {
|
|
|
82
84
|
phase(step.name);
|
|
83
85
|
log(step.name + " step (" + step.identity + ") for task " + taskId);
|
|
84
86
|
|
|
87
|
+
// Publish is optional and target-based. Empty target = prototyping project: skip the phase.
|
|
88
|
+
// Unknown target (incl. legacy "repo") = config error: block.
|
|
89
|
+
if (step.name === "Publish" && !PUBLISH_TYPE) {
|
|
90
|
+
log("Publish skipped for task " + taskId + " — no publish target configured (deploy_type empty)");
|
|
91
|
+
await agent(
|
|
92
|
+
"Release the merge lock and clean up without publishing.\n" +
|
|
93
|
+
"Run: CREW_REPO=" + REPO_PATH + " " + LIFECYCLE + " post-deploy " + taskId + "\n" +
|
|
94
|
+
"If the output contains DEPLOYED, the lock is released and the worktree is cleaned up.",
|
|
95
|
+
{ key: "publish-skip-cleanup", label: "Skipping Publish (no target)", schema: { type: "object" } }
|
|
96
|
+
);
|
|
97
|
+
i++;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (step.name === "Publish" && PUBLISH_TYPE !== "npm" && PUBLISH_TYPE !== "artifact" && PUBLISH_TYPE !== "vercel") {
|
|
101
|
+
log("Unknown publish target for task " + taskId + ": " + PUBLISH_TYPE);
|
|
102
|
+
return { status: "blocked", task_id: taskId, reason: "Unknown publish target '" + PUBLISH_TYPE + "' — expected 'npm', 'artifact', 'vercel', or empty (skip publish)." };
|
|
103
|
+
}
|
|
104
|
+
|
|
85
105
|
let activeSessionId;
|
|
86
106
|
if (isFirstClaim && firstSessionId) {
|
|
87
107
|
activeSessionId = firstSessionId;
|
|
@@ -121,8 +141,10 @@ while (i < STEPS.length) {
|
|
|
121
141
|
(mapperSpec ? "MAPPER'S SPEC (implement exactly this):\n" + mapperSpec + "\n\n" : "") +
|
|
122
142
|
"Your working directory: " + REPO_PATH + "/.worktrees/" + taskId + "/\n" +
|
|
123
143
|
"This is the project source: " + PROJECT_DESC + "\n" +
|
|
124
|
-
"Edit the TypeScript source files directly. Do NOT use artifact_edit — that happens in the
|
|
125
|
-
"Do not add unrequested features.\n
|
|
144
|
+
"Edit the TypeScript source files directly. Do NOT use artifact_edit — that happens in the Publish phase.\n" +
|
|
145
|
+
"Do not add unrequested features.\n" +
|
|
146
|
+
"PUBLIC DOCS: If your change is public-affecting (it alters anything a user or consumer can observe: API actions, parameters, behavior, or errors), update the public docs in the same commit — API.md for API changes. Documentation and implementation ship together.\n\n" +
|
|
147
|
+
(PUBLISH_TYPE === "npm" ? "PACKAGE VERSION: this project publishes to the npm registry, so you choose the package version. If this change warrants a published release (anything a consumer can observe: workflow behavior, phase lists, identities, published docs, API), bump the version in package.json with semver (patch for fixes, minor for new behavior, major for breaking changes) and state the chosen version and why in your summary. If the change is internal-only, leave the version unchanged and say so. Check the registry first — npm view muse-crew version — and never re-publish an existing version.\n\n" : "") +
|
|
126
148
|
"STEP 3: Commit your changes.\n" +
|
|
127
149
|
"cd " + REPO_PATH + "/.worktrees/" + taskId + "\n" +
|
|
128
150
|
"git add -A\n" +
|
|
@@ -138,6 +160,8 @@ while (i < STEPS.length) {
|
|
|
138
160
|
"You can also read specific files in the worktree at:\n" +
|
|
139
161
|
REPO_PATH + "/.worktrees/" + taskId + "/\n\n" +
|
|
140
162
|
"Check quality, correctness, spec compliance.\n" +
|
|
163
|
+
"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" +
|
|
164
|
+
(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" : "") +
|
|
141
165
|
"If it passes, your final response MUST be valid JSON and nothing else: { \"passed\": true, \"summary\": \"approval notes\" }.\n" +
|
|
142
166
|
"If it fails, your final response MUST be valid JSON and nothing else: { \"passed\": false, \"summary\": \"rejection notes\" }.\n" +
|
|
143
167
|
"No prose, no markdown, just the JSON object.";
|
|
@@ -147,40 +171,69 @@ while (i < STEPS.length) {
|
|
|
147
171
|
"Run: CREW_REPO=" + REPO_PATH + " " + LIFECYCLE + " integrate " + taskId + " \"merge: chore: " + safeTitle + "\"\n\n" +
|
|
148
172
|
"Read the output:\n" +
|
|
149
173
|
"- If it contains MERGED, integration succeeded. Report the merged commit hash.\n" +
|
|
150
|
-
"- If it contains LOCK_HELD, another task
|
|
174
|
+
"- If it contains LOCK_HELD, another task holds the merge lock (mid Integrate/Publish). Set passed to false.\n" +
|
|
151
175
|
"- If it contains CONFLICT, a merge conflict occurred. Set passed to false with details.\n" +
|
|
152
176
|
"- If it contains ERROR, something else failed. Set passed to false.\n\n" +
|
|
177
|
+
"\n" +
|
|
178
|
+
"STEP 2: Push the merged main to the remote repository.\n" +
|
|
179
|
+
"Run: cd " + REPO_PATH + " && git push origin main\n" +
|
|
180
|
+
"- If the push succeeds, report the merged commit hash.\n" +
|
|
181
|
+
"- If the push is rejected as non-fast-forward (the remote has commits not present locally),\n" +
|
|
182
|
+
" NEVER force-push. Do not run any --force variant. Set passed to false with summary:\n" +
|
|
183
|
+
" 'git push origin main rejected as non-fast-forward — remote main has diverged; manual resolution required'.
|
|
184
|
+
\n\n" +
|
|
153
185
|
"Your final response MUST be valid JSON and nothing else: { \"summary\": \"result\", \"passed\": true/false }. No prose, no markdown, just the JSON object.";
|
|
154
186
|
|
|
155
|
-
} else if (step.name === "
|
|
156
|
-
if (
|
|
157
|
-
//
|
|
158
|
-
instructions = "
|
|
159
|
-
"
|
|
187
|
+
} else if (step.name === "Publish") {
|
|
188
|
+
if (PUBLISH_TYPE === "npm") {
|
|
189
|
+
// npm packages: immutable release + pack + publish to the registry (push is universal in Integrate)
|
|
190
|
+
instructions = "Publish the npm package to the registry.\n\n" +
|
|
191
|
+
"The repo push already happened in Integrate — do NOT push to git in this phase, and NEVER force-push.\n" +
|
|
192
|
+
"Version discipline: publish ships the exact version merged in Integrate (Build applied it, Review validated it). Do not bump the version here.\n\n" +
|
|
193
|
+
"STEP 0: Refresh the merge lock to prevent stale-lock breaking during publish.\n" +
|
|
160
194
|
"Run: CREW_REPO=" + REPO_PATH + " " + LIFECYCLE + " refresh-lock " + taskId + "\n\n" +
|
|
161
|
-
"STEP 1: Install and activate the
|
|
195
|
+
"STEP 1: Install and activate the immutable release.\n" +
|
|
162
196
|
"Run: " + RELEASE_SCRIPT + " deploy " + REPO_PATH + "\n" +
|
|
163
197
|
"Verify the output contains INSTALLED and ACTIVATED (or EXISTS and ACTIVATED if unchanged).\n\n" +
|
|
164
|
-
"STEP 2:
|
|
198
|
+
"STEP 2: Check whether the package version needs publishing.\n" +
|
|
199
|
+
"Read the version from: cd " + REPO_PATH + " && node -p \"require('./package.json').version\"\n" +
|
|
200
|
+
"Check the registry: npm view muse-crew version 2>/dev/null || echo NOT_FOUND\n" +
|
|
201
|
+
"If the local version matches the registry version, the version is already live — skip to STEP 5.\n\n" +
|
|
202
|
+
"STEP 3: Pack and publish.\n" +
|
|
203
|
+
"Run: cd " + REPO_PATH + " && npm pack\n" +
|
|
204
|
+
"Then publish: python3 ~/workspace/skills/npm/bin/npm-publish.py " + REPO_PATH + "/muse-crew-$(node -p \"require('" + REPO_PATH + "/package.json').version\").tgz\n" +
|
|
205
|
+
"If publish fails with 'You cannot publish over the previously published versions', the version is already live — continue to STEP 4.\n\n" +
|
|
206
|
+
"STEP 4: Verify.\n" +
|
|
207
|
+
"Run: npm view muse-crew version\n" +
|
|
208
|
+
"Confirm it matches the local package.json version.\n\n" +
|
|
209
|
+
"STEP 5: Finalize.\n" +
|
|
165
210
|
"Run: CREW_REPO=" + REPO_PATH + " " + LIFECYCLE + " post-deploy " + taskId + "\n" +
|
|
166
211
|
"If the output contains DEPLOYED, finalization is complete.\n\n" +
|
|
167
|
-
"Your final response MUST be valid JSON and nothing else: { \"summary\": \"
|
|
212
|
+
"Your final response MUST be valid JSON and nothing else: { \"summary\": \"result\", \"passed\": true }.\n" +
|
|
168
213
|
"No prose, no markdown, just the JSON object.";
|
|
169
|
-
} else {
|
|
170
|
-
|
|
171
|
-
|
|
214
|
+
} else if (PUBLISH_TYPE === "artifact") {
|
|
215
|
+
// Artifact projects: rebuild the live artifact via artifact_edit, then finalize
|
|
216
|
+
instructions = "Publish the merged code to the live artifact.\n\n" +
|
|
217
|
+
"The repo push already happened in Integrate — do NOT push to git in this phase.\n\n" +
|
|
218
|
+
"STEP 0: Refresh the merge lock to prevent stale-lock breaking during publish.\n" +
|
|
172
219
|
"Run: CREW_REPO=" + REPO_PATH + " " + LIFECYCLE + " refresh-lock " + taskId + "\n\n" +
|
|
173
|
-
"STEP 1:
|
|
220
|
+
"STEP 1: Publish to the live artifact.\n" +
|
|
174
221
|
"Get the change summary: cd " + REPO_PATH + " && git log -1 --stat\n" +
|
|
175
|
-
"Then call artifact_edit with slug \"" +
|
|
222
|
+
"Then call artifact_edit with slug \"" + PUBLISH_SLUG + "\" and verbatim_request:\n" +
|
|
176
223
|
"'Rebuild the application from current source. Do not modify any source files — just rebuild and deploy what is on disk.'\n" +
|
|
177
224
|
"Wait for the build to complete by polling artifact_status until it is no longer running.\n\n" +
|
|
178
225
|
"STEP 2: Finalize.\n" +
|
|
179
226
|
"Run: CREW_REPO=" + REPO_PATH + " " + LIFECYCLE + " post-deploy " + taskId + "\n" +
|
|
180
|
-
"If the output contains DEPLOYED,
|
|
227
|
+
"If the output contains DEPLOYED, publishing is complete.\n\n" +
|
|
181
228
|
"If artifact_edit failed, still run post-deploy to release the merge lock and clean up.\n" +
|
|
182
|
-
"Report the failure: { \"passed\": false, \"summary\": \"artifact
|
|
183
|
-
"Your final response MUST be valid JSON and nothing else: { \"summary\": \"
|
|
229
|
+
"Report the failure: { \"passed\": false, \"summary\": \"artifact publish failed: [details]\" }.\n\n" +
|
|
230
|
+
"Your final response MUST be valid JSON and nothing else: { \"summary\": \"published changes\", \"passed\": true }.\n" +
|
|
231
|
+
"No prose, no markdown, just the JSON object.";
|
|
232
|
+
} else if (PUBLISH_TYPE === "vercel") {
|
|
233
|
+
// vercel publish is not yet implemented — block without inventing behavior
|
|
234
|
+
instructions = "The project's publish target is \"vercel\", which is not yet implemented.\n" +
|
|
235
|
+
"Do NOT invent publish behavior — do not guess CLI commands, APIs, or deployment steps.\n" +
|
|
236
|
+
"Your final response MUST be valid JSON and nothing else: { \"passed\": false, \"summary\": \"vercel publish not yet implemented\" }.\n" +
|
|
184
237
|
"No prose, no markdown, just the JSON object.";
|
|
185
238
|
}
|
|
186
239
|
}
|
|
@@ -189,8 +242,8 @@ while (i < STEPS.length) {
|
|
|
189
242
|
var eventPreamble = "";
|
|
190
243
|
if (step.name !== "Review") {
|
|
191
244
|
eventPreamble = "CONTEXT: First, fetch this task's event history for background.\n" +
|
|
192
|
-
"Call artifact_invoke_action on slug \"" + DASHBOARD_SLUG + "\", action \"getevents\", args: { \"
|
|
193
|
-
"
|
|
245
|
+
"Call artifact_invoke_action on slug \"" + DASHBOARD_SLUG + "\", action \"getevents\", args: { \"task_id\": \"" + taskId + "\" }.\n" +
|
|
246
|
+
"The returned events are filtered to this task. They contain notes and decisions from prior phases.\n\n";
|
|
194
247
|
}
|
|
195
248
|
|
|
196
249
|
var stepResult;
|
|
@@ -273,9 +326,9 @@ while (i < STEPS.length) {
|
|
|
273
326
|
return { status: "blocked", task_id: taskId, reason: "Integration failed: " + summary };
|
|
274
327
|
}
|
|
275
328
|
|
|
276
|
-
if (!passed && step.name === "
|
|
277
|
-
log("
|
|
278
|
-
return { status: "blocked", task_id: taskId, reason: "
|
|
329
|
+
if (!passed && step.name === "Publish") {
|
|
330
|
+
log("Publish failed for task " + taskId + ": " + summary);
|
|
331
|
+
return { status: "blocked", task_id: taskId, reason: "Publish failed: " + summary };
|
|
279
332
|
}
|
|
280
333
|
|
|
281
334
|
i++;
|
|
@@ -250,8 +250,9 @@ for (var p = 0; p < toProcess.length; p++) {
|
|
|
250
250
|
"Claim the task for the " + nextStepName + " step.\n" +
|
|
251
251
|
"Call artifact_invoke_action on slug \"" + DASHBOARD_SLUG + "\", action \"claimtask\", args:\n" +
|
|
252
252
|
"{ \"task_id\": \"" + itask.id + "\", \"identity\": \"" + identity + "\", \"step\": \"" + nextStepName + "\", \"notes\": \"" + nextStepName + " step started\" }.\n" +
|
|
253
|
-
"
|
|
254
|
-
"
|
|
253
|
+
"Do not interpret the response. It already contains an explicit \"claimed\" field — copy it verbatim.\n" +
|
|
254
|
+
"Return { claimed: <the response's claimed field exactly>, session_id: \"<the response's session_id field>\" }.\n" +
|
|
255
|
+
"If claimed is false there is no session_id; return { claimed: false, session_id: \"\" }.",
|
|
255
256
|
{
|
|
256
257
|
key: "claim-" + itask.id,
|
|
257
258
|
label: "Claiming " + nextStepName + " for: " + itask.title,
|
package/workflows/crew-init.js
CHANGED
|
@@ -172,7 +172,7 @@ try {
|
|
|
172
172
|
" - mode: 'task'\n" +
|
|
173
173
|
" - schedule: { kind: 'interval', every: '3m' }\n" +
|
|
174
174
|
" - owner: 'space:" + dashboardSlug + "'\n" +
|
|
175
|
-
" - timeout_secs:
|
|
175
|
+
" - timeout_secs: 120\n" +
|
|
176
176
|
" - body: the processed template text\n" +
|
|
177
177
|
" d. Return { existed: false }\n\n" +
|
|
178
178
|
"Return JSON with existed (boolean).",
|
package/workflows/docs.js
CHANGED
|
@@ -22,6 +22,11 @@ const DASHBOARD_SLUG = inputs.dashboardSlug || "orchestra-dashboard";
|
|
|
22
22
|
const crewHome = inputs.crewHome || "~/workspace/.jarvis";
|
|
23
23
|
const ORCH_PATH = crewHome + "/.orchestration";
|
|
24
24
|
|
|
25
|
+
// Project repo — the docs workflow edits the project's own docs, not the crew home.
|
|
26
|
+
// Falls back to crew home only for manual launches without a project config.
|
|
27
|
+
const projectConfig = inputs.project_config || {};
|
|
28
|
+
const REPO_PATH = projectConfig.repo_path || crewHome;
|
|
29
|
+
|
|
25
30
|
if (!taskId) {
|
|
26
31
|
throw new Error("task_id is required in args");
|
|
27
32
|
}
|
|
@@ -72,7 +77,7 @@ while (i < STEPS.length) {
|
|
|
72
77
|
if (step.name === "Triage") {
|
|
73
78
|
instructions = "Validate the task, check clarity, confirm the docs workflow assignment.\nYour final response MUST be valid JSON and nothing else: { \"summary\": \"your assessment\", \"passed\": true }. No prose, no markdown, just the JSON object.";
|
|
74
79
|
} else if (step.name === "Write") {
|
|
75
|
-
instructions = "Write or revise the documentation the task asks for.\nFollow Tate's voice — clear, conversational, no jargon unless it earns its place.\
|
|
80
|
+
instructions = "Write or revise the documentation the task asks for.\nFollow Tate's voice — clear, conversational, no jargon unless it earns its place.\nDo your work in the project repository at " + REPO_PATH + " — all doc files go there, not under the crew home." +
|
|
76
81
|
(rejectionNotes ? "\n\nREWORK after review rejection. Address:\n" + rejectionNotes : "") +
|
|
77
82
|
"\nYour final response MUST be valid JSON and nothing else: { \"summary\": \"what you wrote and where\", \"passed\": true }. No prose, no markdown, just the JSON object.";
|
|
78
83
|
} else if (step.name === "Review") {
|
|
@@ -83,7 +88,7 @@ while (i < STEPS.length) {
|
|
|
83
88
|
"Read the identity file at " + ORCH_PATH + "/identities/" + step.identity + ".md using the read tool, and embody that character fully.\n\n" +
|
|
84
89
|
"## Your Assignment\n\n" +
|
|
85
90
|
"Task: " + taskTitle + "\nTask ID: " + taskId + "\nDescription: " + taskDescription + "\nStep: " + step.name + "\nDashboard slug: " + DASHBOARD_SLUG + "\n\n" +
|
|
86
|
-
"## Instructions\n\n" + instructions + "\n\nCONSTRAINT: Do NOT call logevent or upsertagentsession — the workflow handles all phase tracking after your step completes.\n\nStay in character. All file work under " +
|
|
91
|
+
"## Instructions\n\n" + instructions + "\n\nCONSTRAINT: Do NOT call logevent or upsertagentsession — the workflow handles all phase tracking after your step completes.\n\nStay in character. All file work under " + REPO_PATH + "/.",
|
|
87
92
|
{
|
|
88
93
|
key: "work-" + step.name + (reworkCount > 0 ? "-r" + reworkCount : ""),
|
|
89
94
|
label: step.identity + ": " + step.name + " on \"" + taskTitle + "\"",
|
package/workflows/standard.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
export const meta = {
|
|
2
2
|
name: "crew-standard",
|
|
3
|
-
description: "Standard workflow: Triage → Map → Build → Review → Integrate →
|
|
4
|
-
phases: ["Triage", "Map", "Build", "Review", "Integrate", "
|
|
3
|
+
description: "Standard workflow: Triage → Map → Build → Review → Integrate → Publish → QA",
|
|
4
|
+
phases: ["Triage", "Map", "Build", "Review", "Integrate", "Publish", "QA"],
|
|
5
5
|
steps: [
|
|
6
6
|
{ name: "Triage", identity: "sage" },
|
|
7
7
|
{ name: "Map", identity: "mara" },
|
|
8
8
|
{ name: "Build", identity: "wren" },
|
|
9
9
|
{ name: "Review", identity: "cass" },
|
|
10
10
|
{ name: "Integrate", identity: "wren" },
|
|
11
|
-
{ name: "
|
|
11
|
+
{ name: "Publish", identity: "wren" },
|
|
12
12
|
{ name: "QA", identity: "hazel" }
|
|
13
13
|
],
|
|
14
14
|
reworkTarget: "Build"
|
|
@@ -32,12 +32,14 @@ const MERGE_LOCK_SRC = crewHome + "/lib/merge-lock.sh";
|
|
|
32
32
|
const RUN_LIB = "/tmp/crew-lib-" + taskId;
|
|
33
33
|
const LIFECYCLE = RUN_LIB + "/worktree-lifecycle.sh";
|
|
34
34
|
const MERGE_LOCK = RUN_LIB + "/merge-lock.sh";
|
|
35
|
+
const ORPHAN_SWEEP_SRC = crewHome + "/lib/orphan-sweep.sh";
|
|
36
|
+
const ORPHAN_SWEEP = RUN_LIB + "/orphan-sweep.sh";
|
|
35
37
|
|
|
36
38
|
// Project config — passed by dispatcher, falls back to dashboard defaults
|
|
37
39
|
const projectConfig = inputs.project_config || {};
|
|
38
40
|
const REPO_PATH = projectConfig.repo_path || "~/workspace/ts-spaces/orchestra-dashboard";
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
+
const PUBLISH_TYPE = projectConfig.deploy_type || "";
|
|
42
|
+
const PUBLISH_SLUG = projectConfig.deploy_slug || DASHBOARD_SLUG;
|
|
41
43
|
const PROJECT_DESC = projectConfig.description || "React + TypeScript web dashboard (client/src/, server/src/, drizzle/)";
|
|
42
44
|
const RELEASE_SCRIPT = crewHome + "/crew-release.sh";
|
|
43
45
|
|
|
@@ -60,13 +62,15 @@ const STEPS = [
|
|
|
60
62
|
{ name: "Build", identity: "wren" },
|
|
61
63
|
{ name: "Review", identity: "cass" },
|
|
62
64
|
{ name: "Integrate", identity: "wren" },
|
|
63
|
-
{ name: "
|
|
65
|
+
{ name: "Publish", identity: "wren" },
|
|
64
66
|
{ name: "QA", identity: "hazel" }
|
|
65
67
|
];
|
|
66
68
|
const BUILD_INDEX = STEPS.findIndex(s => s.name === 'Build');
|
|
67
69
|
if (BUILD_INDEX < 0) throw new Error("STEPS missing 'Build' step");
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
// Shared rework budget: Review and QA rejections draw from the SAME pool of 2.
|
|
71
|
+
// E.g. 2 Review bounces + 1 QA bounce = 3 total > budget -> task blocks.
|
|
72
|
+
const MAX_TOTAL_REWORK = 2;
|
|
73
|
+
let totalReworkCount = 0;
|
|
70
74
|
let rejectionNotes = inputs.rejection_notes || "";
|
|
71
75
|
let mapperSpec = "";
|
|
72
76
|
let i = startStepIndex;
|
|
@@ -80,7 +84,8 @@ await agent(
|
|
|
80
84
|
" mkdir -p " + RUN_LIB + "\n" +
|
|
81
85
|
" cp " + LIFECYCLE_SRC + " " + LIFECYCLE + "\n" +
|
|
82
86
|
" cp " + MERGE_LOCK_SRC + " " + MERGE_LOCK + "\n" +
|
|
83
|
-
"
|
|
87
|
+
" cp " + ORPHAN_SWEEP_SRC + " " + ORPHAN_SWEEP + "\n" +
|
|
88
|
+
" chmod +x " + LIFECYCLE + " " + MERGE_LOCK + " " + ORPHAN_SWEEP + "\n" +
|
|
84
89
|
"Confirm the files exist by listing " + RUN_LIB + ".",
|
|
85
90
|
{ key: "pin-lifecycle", label: "Pinning lifecycle scripts", schema: { type: "object" } }
|
|
86
91
|
);
|
|
@@ -88,11 +93,29 @@ log("Lifecycle scripts pinned to " + RUN_LIB);
|
|
|
88
93
|
|
|
89
94
|
while (i < STEPS.length) {
|
|
90
95
|
const step = STEPS[i];
|
|
91
|
-
const isFirstClaim = (i === startStepIndex &&
|
|
96
|
+
const isFirstClaim = (i === startStepIndex && totalReworkCount === 0);
|
|
92
97
|
|
|
93
98
|
phase(step.name);
|
|
94
99
|
log(step.name + " step (" + step.identity + ") for task " + taskId);
|
|
95
100
|
|
|
101
|
+
// Publish is optional and target-based. Empty target = prototyping project: skip the phase.
|
|
102
|
+
// Unknown target (incl. legacy "repo") = config error: block.
|
|
103
|
+
if (step.name === "Publish" && !PUBLISH_TYPE) {
|
|
104
|
+
log("Publish skipped for task " + taskId + " — no publish target configured (deploy_type empty)");
|
|
105
|
+
await agent(
|
|
106
|
+
"Release the merge lock and clean up without publishing.\n" +
|
|
107
|
+
"Run: CREW_REPO=" + REPO_PATH + " " + LIFECYCLE + " post-deploy " + taskId + "\n" +
|
|
108
|
+
"If the output contains DEPLOYED, the lock is released and the worktree is cleaned up.",
|
|
109
|
+
{ key: "publish-skip-cleanup", label: "Skipping Publish (no target)", schema: { type: "object" } }
|
|
110
|
+
);
|
|
111
|
+
i++;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (step.name === "Publish" && PUBLISH_TYPE !== "npm" && PUBLISH_TYPE !== "artifact" && PUBLISH_TYPE !== "vercel") {
|
|
115
|
+
log("Unknown publish target for task " + taskId + ": " + PUBLISH_TYPE);
|
|
116
|
+
return { status: "blocked", task_id: taskId, reason: "Unknown publish target '" + PUBLISH_TYPE + "' — expected 'npm', 'artifact', 'vercel', or empty (skip publish)." };
|
|
117
|
+
}
|
|
118
|
+
|
|
96
119
|
// Claim session — reuse dispatcher's session for the very first step
|
|
97
120
|
let activeSessionId;
|
|
98
121
|
if (isFirstClaim && firstSessionId) {
|
|
@@ -101,10 +124,10 @@ while (i < STEPS.length) {
|
|
|
101
124
|
const claimResult = await agent(
|
|
102
125
|
"Claim a session for this task step.\n" +
|
|
103
126
|
"Call artifact_invoke_action on slug \"" + DASHBOARD_SLUG + "\", action \"claimtask\", args:\n" +
|
|
104
|
-
"{ \"task_id\": \"" + taskId + "\", \"identity\": \"" + step.identity + "\", \"step\": \"" + step.name + "\", \"notes\": \"" + step.name + " step started" + (
|
|
127
|
+
"{ \"task_id\": \"" + taskId + "\", \"identity\": \"" + step.identity + "\", \"step\": \"" + step.name + "\", \"notes\": \"" + step.name + " step started" + (totalReworkCount > 0 ? " (rework #" + totalReworkCount + ")" : "") + "\" }.\n" +
|
|
105
128
|
"Return the session_id from the response.",
|
|
106
129
|
{
|
|
107
|
-
key: "claim-" + step.name + (
|
|
130
|
+
key: "claim-" + step.name + (totalReworkCount > 0 ? "-r" + totalReworkCount : ""),
|
|
108
131
|
label: "Claiming " + step.name,
|
|
109
132
|
schema: {
|
|
110
133
|
type: "object",
|
|
@@ -134,8 +157,10 @@ while (i < STEPS.length) {
|
|
|
134
157
|
(mapperSpec ? "MAPPER'S SPEC (implement exactly this):\n" + mapperSpec + "\n\n" : "") +
|
|
135
158
|
"Your working directory: " + REPO_PATH + "/.worktrees/" + taskId + "/\n" +
|
|
136
159
|
"This is the project source: " + PROJECT_DESC + "\n" +
|
|
137
|
-
"Edit source files directly. Do NOT use artifact_edit — that happens in the
|
|
138
|
-
"Do not add unrequested features. Build exactly what the spec calls for.\n
|
|
160
|
+
"Edit source files directly. Do NOT use artifact_edit — that happens in the Publish phase.\n" +
|
|
161
|
+
"Do not add unrequested features. Build exactly what the spec calls for.\n" +
|
|
162
|
+
"PUBLIC DOCS: If your change is public-affecting (it alters anything a user or consumer can observe: API actions, parameters, behavior, or errors), update the public docs in the same commit — API.md for API changes. Documentation and implementation ship together.\n\n" +
|
|
163
|
+
(PUBLISH_TYPE === "npm" ? "PACKAGE VERSION: this project publishes to the npm registry, so you choose the package version. If this change warrants a published release (anything a consumer can observe: workflow behavior, phase lists, identities, published docs, API), bump the version in package.json with semver (patch for fixes, minor for new behavior, major for breaking changes) and state the chosen version and why in your summary. If the change is internal-only, leave the version unchanged and say so. Check the registry first — npm view muse-crew version — and never re-publish an existing version.\n\n" : "") +
|
|
139
164
|
"STEP 3: Commit your changes.\n" +
|
|
140
165
|
"cd " + REPO_PATH + "/.worktrees/" + taskId + "\n" +
|
|
141
166
|
"git add -A\n" +
|
|
@@ -151,6 +176,8 @@ while (i < STEPS.length) {
|
|
|
151
176
|
"You can also read specific files in the worktree at:\n" +
|
|
152
177
|
REPO_PATH + "/.worktrees/" + taskId + "/\n\n" +
|
|
153
178
|
"Check quality, correctness, and spec compliance.\n" +
|
|
179
|
+
"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" +
|
|
180
|
+
(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" : "") +
|
|
154
181
|
"If the work passes review, your final response MUST be valid JSON and nothing else: { \"passed\": true, \"summary\": \"approval notes\" }.\n" +
|
|
155
182
|
"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" +
|
|
156
183
|
"No prose, no markdown, just the JSON object.";
|
|
@@ -160,56 +187,84 @@ while (i < STEPS.length) {
|
|
|
160
187
|
"Run: CREW_REPO=" + REPO_PATH + " " + LIFECYCLE + " integrate " + taskId + " \"merge: " + safeTitle + "\"\n\n" +
|
|
161
188
|
"Read the output:\n" +
|
|
162
189
|
"- If it contains MERGED, integration succeeded. Report the merged commit hash.\n" +
|
|
163
|
-
"- If it contains LOCK_HELD, another task
|
|
190
|
+
"- If it contains LOCK_HELD, another task holds the merge lock (mid Integrate/Publish). Set passed to false with summary 'merge lock held'.\n" +
|
|
164
191
|
"- If it contains CONFLICT, a merge conflict occurred. Set passed to false with the conflict details.\n" +
|
|
165
192
|
"- If it contains ERROR, something else failed. Set passed to false with the error.\n\n" +
|
|
193
|
+
"\n" +
|
|
194
|
+
"STEP 2: Push the merged main to the remote repository.\n" +
|
|
195
|
+
"Run: cd " + REPO_PATH + " && git push origin main\n" +
|
|
196
|
+
"- If the push succeeds, report the merged commit hash.\n" +
|
|
197
|
+
"- If the push is rejected as non-fast-forward (the remote has commits not present locally),\n" +
|
|
198
|
+
" NEVER force-push. Do not run any --force variant. Set passed to false with summary:\n" +
|
|
199
|
+
" 'git push origin main rejected as non-fast-forward — remote main has diverged; manual resolution required'.\n\n" +
|
|
166
200
|
"Your final response MUST be valid JSON and nothing else: { \"summary\": \"result\", \"passed\": true/false }. No prose, no markdown, just the JSON object.";
|
|
167
201
|
|
|
168
|
-
} else if (step.name === "
|
|
169
|
-
if (
|
|
170
|
-
//
|
|
171
|
-
instructions = "
|
|
172
|
-
"
|
|
202
|
+
} else if (step.name === "Publish") {
|
|
203
|
+
if (PUBLISH_TYPE === "npm") {
|
|
204
|
+
// npm packages: immutable release + pack + publish to the registry (push is universal in Integrate)
|
|
205
|
+
instructions = "Publish the npm package to the registry.\n\n" +
|
|
206
|
+
"The repo push already happened in Integrate — do NOT push to git in this phase, and NEVER force-push.\n" +
|
|
207
|
+
"Version discipline: publish ships the exact version merged in Integrate (Build applied it, Review validated it). Do not bump the version here.\n\n" +
|
|
208
|
+
"STEP 0: Refresh the merge lock to prevent stale-lock breaking during publish.\n" +
|
|
173
209
|
"Run: CREW_REPO=" + REPO_PATH + " " + LIFECYCLE + " refresh-lock " + taskId + "\n\n" +
|
|
174
|
-
"STEP 1: Install and activate the
|
|
210
|
+
"STEP 1: Install and activate the immutable release.\n" +
|
|
175
211
|
"Run: " + RELEASE_SCRIPT + " deploy " + REPO_PATH + "\n" +
|
|
176
212
|
"Verify the output contains INSTALLED and ACTIVATED (or EXISTS and ACTIVATED if unchanged).\n\n" +
|
|
177
|
-
"STEP 2:
|
|
213
|
+
"STEP 2: Check whether the package version needs publishing.\n" +
|
|
214
|
+
"Read the version from: cd " + REPO_PATH + " && node -p \"require('./package.json').version\"\n" +
|
|
215
|
+
"Check the registry: npm view muse-crew version 2>/dev/null || echo NOT_FOUND\n" +
|
|
216
|
+
"If the local version matches the registry version, the version is already live — skip to STEP 5.\n\n" +
|
|
217
|
+
"STEP 3: Pack and publish.\n" +
|
|
218
|
+
"Run: cd " + REPO_PATH + " && npm pack\n" +
|
|
219
|
+
"Then publish: python3 ~/workspace/skills/npm/bin/npm-publish.py " + REPO_PATH + "/muse-crew-$(node -p \"require('" + REPO_PATH + "/package.json').version\").tgz\n" +
|
|
220
|
+
"If publish fails with 'You cannot publish over the previously published versions', the version is already live — continue to STEP 4.\n\n" +
|
|
221
|
+
"STEP 4: Verify.\n" +
|
|
222
|
+
"Run: npm view muse-crew version\n" +
|
|
223
|
+
"Confirm it matches the local package.json version.\n\n" +
|
|
224
|
+
"STEP 5: Finalize.\n" +
|
|
178
225
|
"Run: CREW_REPO=" + REPO_PATH + " " + LIFECYCLE + " post-deploy " + taskId + "\n" +
|
|
179
226
|
"If the output contains DEPLOYED, finalization is complete.\n\n" +
|
|
180
|
-
"Your final response MUST be valid JSON and nothing else: { \"summary\": \"
|
|
227
|
+
"Your final response MUST be valid JSON and nothing else: { \"summary\": \"result\", \"passed\": true }.\n" +
|
|
181
228
|
"No prose, no markdown, just the JSON object.";
|
|
182
|
-
} else {
|
|
183
|
-
// Artifact projects:
|
|
184
|
-
instructions = "
|
|
185
|
-
"
|
|
229
|
+
} else if (PUBLISH_TYPE === "artifact") {
|
|
230
|
+
// Artifact projects: rebuild the live artifact via artifact_edit, then finalize
|
|
231
|
+
instructions = "Publish the merged code to the live artifact.\n\n" +
|
|
232
|
+
"The repo push already happened in Integrate — do NOT push to git in this phase.\n\n" +
|
|
233
|
+
"STEP 0: Refresh the merge lock to prevent stale-lock breaking during publish.\n" +
|
|
186
234
|
"Run: CREW_REPO=" + REPO_PATH + " " + LIFECYCLE + " refresh-lock " + taskId + "\n\n" +
|
|
187
|
-
"STEP 1:
|
|
235
|
+
"STEP 1: Publish to the live artifact.\n" +
|
|
188
236
|
"Get the change summary: cd " + REPO_PATH + " && git log -1 --stat\n" +
|
|
189
|
-
"Then call artifact_edit with slug \"" +
|
|
237
|
+
"Then call artifact_edit with slug \"" + PUBLISH_SLUG + "\" and verbatim_request:\n" +
|
|
190
238
|
"'Rebuild the application from current source. Do not modify any source files — just rebuild and deploy what is on disk.'\n" +
|
|
191
239
|
"Wait for the build to complete by polling artifact_status until it is no longer running.\n\n" +
|
|
192
240
|
"STEP 2: Finalize.\n" +
|
|
193
241
|
"Run: CREW_REPO=" + REPO_PATH + " " + LIFECYCLE + " post-deploy " + taskId + "\n" +
|
|
194
|
-
"If the output contains DEPLOYED,
|
|
242
|
+
"If the output contains DEPLOYED, publishing is complete.\n\n" +
|
|
195
243
|
"If artifact_edit failed, still run post-deploy to release the merge lock and clean up.\n" +
|
|
196
|
-
"Report the failure: { \"passed\": false, \"summary\": \"artifact
|
|
197
|
-
"Your final response MUST be valid JSON and nothing else: { \"summary\": \"
|
|
244
|
+
"Report the failure: { \"passed\": false, \"summary\": \"artifact publish failed: [details]\" }.\n\n" +
|
|
245
|
+
"Your final response MUST be valid JSON and nothing else: { \"summary\": \"published changes\", \"passed\": true }.\n" +
|
|
246
|
+
"No prose, no markdown, just the JSON object.";
|
|
247
|
+
} else if (PUBLISH_TYPE === "vercel") {
|
|
248
|
+
// vercel publish is not yet implemented — block without inventing behavior
|
|
249
|
+
instructions = "The project's publish target is \"vercel\", which is not yet implemented.\n" +
|
|
250
|
+
"Do NOT invent publish behavior — do not guess CLI commands, APIs, or deployment steps.\n" +
|
|
251
|
+
"Your final response MUST be valid JSON and nothing else: { \"passed\": false, \"summary\": \"vercel publish not yet implemented\" }.\n" +
|
|
198
252
|
"No prose, no markdown, just the JSON object.";
|
|
199
253
|
}
|
|
200
|
-
|
|
201
254
|
} else if (step.name === "QA") {
|
|
202
|
-
if (
|
|
255
|
+
if (PUBLISH_TYPE === "artifact") {
|
|
203
256
|
var safeDesc = taskDescription.replace(/"/g, "'").replace(/\\/g, "\\\\").slice(0, 500);
|
|
204
|
-
instructions = "You are code-blind QA. You NEVER read source files.\n
|
|
205
|
-
"
|
|
257
|
+
instructions = "You are code-blind QA. You NEVER read source files.\n" +
|
|
258
|
+
"Public docs (API.md, README, published action schemas) are NOT source code — read them freely, exactly as a user would.\n\n" +
|
|
259
|
+
"STEP 1: Trigger a visual inspection of the published artifact.\n" +
|
|
206
260
|
"Call artifact_inspect with:\n" +
|
|
207
|
-
" slug: \"" +
|
|
261
|
+
" slug: \"" + PUBLISH_SLUG + "\"\n" +
|
|
208
262
|
" repair_authorized: false\n" +
|
|
209
263
|
" verbatim_request: \"Verify task: " + safeTitle + ". " + safeDesc + "\"\n\n" +
|
|
210
264
|
"This call is asynchronous — it fires the inspection but results arrive outside this workflow. That is expected and correct.\n\n" +
|
|
211
265
|
"STEP 2: Verify data integrity via the dashboard API.\n" +
|
|
212
|
-
"Use artifact_invoke_action on slug \"" +
|
|
266
|
+
"Use artifact_invoke_action on slug \"" + PUBLISH_SLUG + "\" with read-only actions (e.g. gettasks, getagentsessions) to check the task's data-level effects.\n" +
|
|
267
|
+
"DOCS GATE: If the change is public-affecting (it alters anything a user or consumer can observe: API actions, parameters, behavior, or errors), verify the public docs describe it. If public docs are missing or stale for a public-affecting change, FAIL with { \"passed\": false, \"summary\": \"public docs missing/stale for [the change]\" }. QA always fails when public-affecting changes lack public docs. Guide/tutorial gaps are lower priority — file a follow-up task for those instead of failing.\n\n" +
|
|
213
268
|
"STEP 3: File follow-up tasks for any related issues you discover.\n" +
|
|
214
269
|
"Use artifact_invoke_action createtask on slug \"" + DASHBOARD_SLUG + "\" for each issue.\n\n" +
|
|
215
270
|
"Your final response MUST be valid JSON and nothing else:\n" +
|
|
@@ -217,7 +272,9 @@ while (i < STEPS.length) {
|
|
|
217
272
|
"No prose, no markdown, just the JSON object.";
|
|
218
273
|
} else {
|
|
219
274
|
instructions = "Test from a user's perspective. You are CODE-BLIND — do NOT read source code.\n" +
|
|
275
|
+
"Public docs (API.md, README) are NOT source code — read them freely, exactly as a user would.\n" +
|
|
220
276
|
"Verify the change is working as described in the task.\n" +
|
|
277
|
+
"DOCS GATE: If the change is public-affecting (it alters anything a user or consumer can observe: API actions, parameters, behavior, or errors), verify the public docs describe it. If public docs are missing or stale, FAIL with { \"passed\": false, \"summary\": \"public docs missing/stale for [the change]\" }. QA always fails when public-affecting changes lack public docs. Guide/tutorial gaps are lower priority — file a follow-up task for those instead of failing.\n" +
|
|
221
278
|
"File follow-up tasks via artifact_invoke_action createtask on slug \"" + DASHBOARD_SLUG + "\" for related issues found.\n\n" +
|
|
222
279
|
"Your final response MUST be valid JSON and nothing else: { \"passed\": true/false, \"summary\": \"what you tested and found\" }.\n" +
|
|
223
280
|
"No prose, no markdown, just the JSON object.";
|
|
@@ -228,8 +285,8 @@ while (i < STEPS.length) {
|
|
|
228
285
|
var eventPreamble = "";
|
|
229
286
|
if (step.name !== "Review") {
|
|
230
287
|
eventPreamble = "CONTEXT: First, fetch this task's event history for background.\n" +
|
|
231
|
-
"Call artifact_invoke_action on slug \"" + DASHBOARD_SLUG + "\", action \"getevents\", args: { \"
|
|
232
|
-
"
|
|
288
|
+
"Call artifact_invoke_action on slug \"" + DASHBOARD_SLUG + "\", action \"getevents\", args: { \"task_id\": \"" + taskId + "\" }.\n" +
|
|
289
|
+
"The returned events are filtered to this task. They contain notes and decisions from prior phases.\n\n";
|
|
233
290
|
}
|
|
234
291
|
|
|
235
292
|
// Run work agent WITH schema — runtime retries on non-JSON via structured outputs
|
|
@@ -247,7 +304,7 @@ while (i < STEPS.length) {
|
|
|
247
304
|
"CONSTRAINT: Do NOT call logevent or upsertagentsession — the workflow handles all phase tracking after your step completes.\n\n" +
|
|
248
305
|
"Stay in character. Do the work thoroughly.",
|
|
249
306
|
{
|
|
250
|
-
key: "work-" + step.name + (
|
|
307
|
+
key: "work-" + step.name + (totalReworkCount > 0 ? "-r" + totalReworkCount : ""),
|
|
251
308
|
label: step.identity + ": " + step.name + " on \"" + taskTitle + "\"",
|
|
252
309
|
timeoutMs: 3600000,
|
|
253
310
|
schema: WORK_SCHEMA
|
|
@@ -296,7 +353,7 @@ while (i < STEPS.length) {
|
|
|
296
353
|
"Then call artifact_invoke_action on slug \"" + DASHBOARD_SLUG + "\", action \"logevent\", args:\n" +
|
|
297
354
|
"{ \"task_id\": \"" + taskId + "\", \"type\": \"" + status + "\", \"identity\": \"" + step.identity + "\", \"message\": \"" + step.name + " " + status + " by " + step.identity + "\" }.",
|
|
298
355
|
{
|
|
299
|
-
key: "record-" + step.name + (
|
|
356
|
+
key: "record-" + step.name + (totalReworkCount > 0 ? "-r" + totalReworkCount : ""),
|
|
300
357
|
label: "Recording " + step.name + " result",
|
|
301
358
|
schema: { type: "object" }
|
|
302
359
|
}
|
|
@@ -304,14 +361,14 @@ while (i < STEPS.length) {
|
|
|
304
361
|
|
|
305
362
|
// Handle rejection — bounce back to Build
|
|
306
363
|
if (!passed && (step.name === "Review" || step.name === "QA")) {
|
|
307
|
-
|
|
308
|
-
if (
|
|
309
|
-
log("
|
|
310
|
-
return { status: "blocked", task_id: taskId, reason: "Exceeded " +
|
|
364
|
+
totalReworkCount++;
|
|
365
|
+
if (totalReworkCount > MAX_TOTAL_REWORK) {
|
|
366
|
+
log("Shared rework budget exhausted for task " + taskId + " — worktree preserved at .worktrees/" + taskId + " for manual inspection");
|
|
367
|
+
return { status: "blocked", task_id: taskId, reason: "Exceeded shared rework budget (" + MAX_TOTAL_REWORK + " total rework attempts across Review and QA) after " + step.name + " rejection. Worktree preserved." };
|
|
311
368
|
}
|
|
312
369
|
rejectionNotes = summary;
|
|
313
370
|
i = BUILD_INDEX;
|
|
314
|
-
log(step.name + " rejected — bouncing to Build (rework #" +
|
|
371
|
+
log(step.name + " rejected — bouncing to Build (rework #" + totalReworkCount + " of " + MAX_TOTAL_REWORK + ")");
|
|
315
372
|
continue;
|
|
316
373
|
}
|
|
317
374
|
|
|
@@ -321,10 +378,10 @@ while (i < STEPS.length) {
|
|
|
321
378
|
return { status: "blocked", task_id: taskId, reason: "Integration failed: " + summary };
|
|
322
379
|
}
|
|
323
380
|
|
|
324
|
-
//
|
|
325
|
-
if (!passed && step.name === "
|
|
326
|
-
log("
|
|
327
|
-
return { status: "blocked", task_id: taskId, reason: "
|
|
381
|
+
// Publish failure blocks the task
|
|
382
|
+
if (!passed && step.name === "Publish") {
|
|
383
|
+
log("Publish failed for task " + taskId + ": " + summary);
|
|
384
|
+
return { status: "blocked", task_id: taskId, reason: "Publish failed: " + summary };
|
|
328
385
|
}
|
|
329
386
|
|
|
330
387
|
i++;
|