muse-crew 0.1.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.
Files changed (42) hide show
  1. package/AGENTS.md +19 -0
  2. package/API.md +213 -0
  3. package/README.md +68 -0
  4. package/docs/AGENTS.md +3 -0
  5. package/docs/guide.md +333 -0
  6. package/identities/AGENTS.md +7 -0
  7. package/identities/cass.md +36 -0
  8. package/identities/hazel.md +35 -0
  9. package/identities/mara.md +31 -0
  10. package/identities/personality-notes.md +81 -0
  11. package/identities/sage.md +31 -0
  12. package/identities/tate.md +35 -0
  13. package/identities/wren.md +36 -0
  14. package/lib/AGENTS.md +8 -0
  15. package/lib/crew-release.sh +188 -0
  16. package/lib/merge-lock.sh +89 -0
  17. package/lib/orphan-sweep.sh +95 -0
  18. package/lib/worktree-lifecycle.sh +313 -0
  19. package/package.json +29 -0
  20. package/personas/AGENTS.md +5 -0
  21. package/personas/beginner.md +24 -0
  22. package/personas/completionist.md +24 -0
  23. package/personas/designer.md +26 -0
  24. package/personas/financial-stakeholder.md +25 -0
  25. package/personas/power-user.md +26 -0
  26. package/seed/AGENTS.md +8 -0
  27. package/seed/cron-body-template.md +20 -0
  28. package/seed/feedback/AGENTS.md +3 -0
  29. package/seed/feedback/README.md +40 -0
  30. package/seed/posture.md +8 -0
  31. package/seed/workflows/AGENTS.md +3 -0
  32. package/seed/workflows/bugfix.md +39 -0
  33. package/seed/workflows/chore.md +29 -0
  34. package/seed/workflows/docs.md +17 -0
  35. package/seed/workflows/standard.md +34 -0
  36. package/workflows/AGENTS.md +12 -0
  37. package/workflows/bugfix.js +341 -0
  38. package/workflows/chore.js +294 -0
  39. package/workflows/crew-dispatch.js +315 -0
  40. package/workflows/crew-init.js +254 -0
  41. package/workflows/docs.js +141 -0
  42. package/workflows/standard.js +350 -0
@@ -0,0 +1,254 @@
1
+ export const meta = {
2
+ name: "crew-init",
3
+ description: "Initialize Muse Crew: bootstrap release, scaffold orchestration, register dashboard project, set up polling cron. Idempotent — safe to re-run.",
4
+ phases: [
5
+ { name: "release", title: "Bootstrap release system" },
6
+ { name: "scaffold", title: "Scaffold orchestration directory" },
7
+ { name: "project", title: "Register dashboard project" },
8
+ { name: "cron", title: "Create polling cron" },
9
+ { name: "sweep-cron", title: "Create sweep cron" }
10
+ ]
11
+ };
12
+
13
+ // ── Arguments ──────────────────────────────────────────────────────────
14
+ const inputs = args ?? {};
15
+ const crewRepoPath = inputs.crewRepoPath;
16
+ const crewHome = inputs.crewHome;
17
+ const dashboardSlug = inputs.dashboardSlug;
18
+ const dashboardName = inputs.dashboardName || "Muse Crew";
19
+ const cronId = inputs.cronId || "crew-poll";
20
+ const sweepCronId = inputs.sweepCronId || "crew-sweep";
21
+
22
+ if (!crewRepoPath) throw new Error("crewRepoPath is required — path to muse-crew (git checkout or npm install)");
23
+ if (!crewHome) throw new Error("crewHome is required — e.g. ~/.crew");
24
+ if (!dashboardSlug) throw new Error("dashboardSlug is required — create the dashboard artifact first, then pass its slug here");
25
+
26
+ const orchDir = crewHome + "/.orchestration";
27
+
28
+ // ── Phase 1: Release ──────────────────────────────────────────────────
29
+ phase("release");
30
+ var releaseResult;
31
+ try {
32
+ releaseResult = await agent(
33
+ "Bootstrap the Muse Crew release system.\n\n" +
34
+ "Crew repo: " + crewRepoPath + "\n" +
35
+ "Release home: " + crewHome + "\n" +
36
+ "Release script (in repo): " + crewRepoPath + "/lib/crew-release.sh\n\n" +
37
+ "Steps:\n" +
38
+ "1. Run: test -f " + crewHome + "/crew-release.sh && echo EXISTS || echo MISSING\n" +
39
+ "2. If EXISTS, run: " + crewHome + "/crew-release.sh current\n" +
40
+ " Return { existed: true, hash: <current hash> }\n" +
41
+ "3. If MISSING, bootstrap:\n" +
42
+ " a. Run: bash " + crewRepoPath + "/lib/crew-release.sh init " + crewHome + " " + crewRepoPath + "\n" +
43
+ " b. Verify: " + crewHome + "/crew-release.sh current\n" +
44
+ " c. Return { existed: false, hash: <hash from current> }\n\n" +
45
+ "Return JSON with existed (boolean) and hash (string).",
46
+ {
47
+ key: "release-1",
48
+ label: "Bootstrap release system",
49
+ schema: {
50
+ type: "object",
51
+ properties: {
52
+ existed: { type: "boolean" },
53
+ hash: { type: "string" }
54
+ },
55
+ required: ["existed", "hash"]
56
+ }
57
+ }
58
+ );
59
+ } catch (e) {
60
+ return { __hatchWorkflowControl: "blocked", result: { blocked_reason: "Release bootstrap failed", message: String(e.message || e) } };
61
+ }
62
+ log("Release: " + (releaseResult.existed ? "existing" : "bootstrapped") + " at " + releaseResult.hash);
63
+
64
+ // ── Phase 2: Scaffold ─────────────────────────────────────────────────
65
+ // Copies identities (with portraits), personas, workflow docs, and
66
+ // feedback convention from the crew repo into .orchestration/ under crewHome.
67
+ // Uses cp --no-clobber so existing files are never overwritten.
68
+ phase("scaffold");
69
+ var scaffoldResult;
70
+ try {
71
+ scaffoldResult = await agent(
72
+ "Scaffold the .orchestration directory from the crew repo.\n\n" +
73
+ "Source repo: " + crewRepoPath + "\n" +
74
+ "Destination: " + orchDir + "\n\n" +
75
+ "Steps:\n" +
76
+ "1. Create directories:\n" +
77
+ " mkdir -p " + orchDir + "/identities " + orchDir + "/personas " + orchDir + "/workflows " + orchDir + "/feedback\n" +
78
+ "2. Copy identities (.md AND .png — portraits are in the repo):\n" +
79
+ " cp -n " + crewRepoPath + "/identities/*.md " + crewRepoPath + "/identities/*.png " + orchDir + "/identities/\n" +
80
+ " Also copy personality-notes.md if it exists.\n" +
81
+ "3. Copy personas:\n" +
82
+ " cp -n " + crewRepoPath + "/personas/*.md " + orchDir + "/personas/\n" +
83
+ "4. Copy workflow documentation (markdown descriptions, not JS scripts):\n" +
84
+ " cp -n " + crewRepoPath + "/seed/workflows/*.md " + orchDir + "/workflows/\n" +
85
+ "5. Copy feedback convention:\n" +
86
+ " cp -n " + crewRepoPath + "/seed/feedback/README.md " + orchDir + "/feedback/README.md\n" +
87
+ "6. List what ended up in " + orchDir + " to confirm.\n\n" +
88
+ "Return JSON with created (array of relative paths that were newly copied) and skipped (array that already existed).",
89
+ {
90
+ key: "scaffold-1",
91
+ label: "Scaffold .orchestration directory",
92
+ schema: {
93
+ type: "object",
94
+ properties: {
95
+ created: { type: "array", items: { type: "string" } },
96
+ skipped: { type: "array", items: { type: "string" } }
97
+ },
98
+ required: ["created", "skipped"]
99
+ }
100
+ }
101
+ );
102
+ } catch (e) {
103
+ return { __hatchWorkflowControl: "blocked", result: { blocked_reason: "Scaffold failed", message: String(e.message || e) } };
104
+ }
105
+ var scaffoldCreated = scaffoldResult.created ? scaffoldResult.created.length : 0;
106
+ var scaffoldSkipped = scaffoldResult.skipped ? scaffoldResult.skipped.length : 0;
107
+ log("Scaffold: " + scaffoldCreated + " created, " + scaffoldSkipped + " skipped");
108
+
109
+ // ── Phase 3: Register dashboard as project ────────────────────────────
110
+ // The dashboard IS the project. Register it so the crew knows what to work on.
111
+ phase("project");
112
+ var projectResult;
113
+ try {
114
+ projectResult = await agent(
115
+ "Register the dashboard as a project in its own database.\n\n" +
116
+ "Dashboard slug: " + dashboardSlug + "\n\n" +
117
+ "Steps:\n" +
118
+ "1. Call artifact_invoke_action with:\n" +
119
+ " slug: '" + dashboardSlug + "'\n" +
120
+ " action_name: 'createproject'\n" +
121
+ " args: {\n" +
122
+ " id: '" + dashboardSlug + "',\n" +
123
+ " display_name: '" + dashboardName + "',\n" +
124
+ " repo_path: '" + crewRepoPath + "',\n" +
125
+ " deploy_type: 'artifact',\n" +
126
+ " deploy_slug: '" + dashboardSlug + "',\n" +
127
+ " description: 'The Muse Crew dashboard — task board, activity feed, and crew controls'\n" +
128
+ " }\n" +
129
+ "2. Return { registered: true } on success.\n\n" +
130
+ "Return JSON with registered (boolean).",
131
+ {
132
+ key: "project-1",
133
+ label: "Register dashboard project",
134
+ schema: {
135
+ type: "object",
136
+ properties: {
137
+ registered: { type: "boolean" }
138
+ },
139
+ required: ["registered"]
140
+ }
141
+ }
142
+ );
143
+ } catch (e) {
144
+ return { __hatchWorkflowControl: "blocked", result: { blocked_reason: "Project registration failed", message: String(e.message || e) } };
145
+ }
146
+ log("Project: " + (projectResult.registered ? "registered" : "failed"));
147
+
148
+ // ── Phase 4: Polling cron ─────────────────────────────────────────────
149
+ // The cron body template lives in the repo at seed/cron-body-template.md.
150
+ // Placeholders: {crewHome}, {dashboardSlug} are replaced with actual values.
151
+ // Owner is the dashboard artifact so deleting the dashboard kills the cron.
152
+ phase("cron");
153
+ var cronResult;
154
+ try {
155
+ cronResult = await agent(
156
+ "Check if the polling cron exists and create it if not.\n\n" +
157
+ "Cron id: " + cronId + "\n" +
158
+ "Template: " + crewRepoPath + "/seed/cron-body-template.md\n" +
159
+ "crewHome: " + crewHome + "\n" +
160
+ "Dashboard slug: " + dashboardSlug + "\n\n" +
161
+ "Steps:\n" +
162
+ "1. Call cron_list and look for a job with id '" + cronId + "'\n" +
163
+ "2. If it exists, return { existed: true }\n" +
164
+ "3. If not found:\n" +
165
+ " a. Read the template at " + crewRepoPath + "/seed/cron-body-template.md\n" +
166
+ " b. Replace all occurrences of {crewHome} with: " + crewHome + "\n" +
167
+ " Replace all occurrences of {dashboardSlug} with: " + dashboardSlug + "\n" +
168
+ " c. Call cron_add with:\n" +
169
+ " - id: '" + cronId + "'\n" +
170
+ " - title: 'Muse Crew polling loop'\n" +
171
+ " - enabled: true\n" +
172
+ " - mode: 'task'\n" +
173
+ " - schedule: { kind: 'interval', every: '3m' }\n" +
174
+ " - owner: 'space:" + dashboardSlug + "'\n" +
175
+ " - timeout_secs: 180\n" +
176
+ " - body: the processed template text\n" +
177
+ " d. Return { existed: false }\n\n" +
178
+ "Return JSON with existed (boolean).",
179
+ {
180
+ key: "cron-1",
181
+ label: "Create polling cron",
182
+ schema: {
183
+ type: "object",
184
+ properties: {
185
+ existed: { type: "boolean" }
186
+ },
187
+ required: ["existed"]
188
+ }
189
+ }
190
+ );
191
+ } catch (e) {
192
+ return { __hatchWorkflowControl: "blocked", result: { blocked_reason: "Cron creation failed", message: String(e.message || e) } };
193
+ }
194
+ log("Cron: " + (cronResult.existed ? "already existed" : "created as " + cronId));
195
+
196
+ // ── Phase 5: Sweep cron ──────────────────────────────────────────────
197
+ phase("sweep-cron");
198
+ var sweepResult;
199
+ try {
200
+ sweepResult = await agent(
201
+ "Check if the sweep cron exists and create it if not.\n\n" +
202
+ "Cron id: " + sweepCronId + "\n" +
203
+ "crewHome: " + crewHome + "\n" +
204
+ "Dashboard slug: " + dashboardSlug + "\n\n" +
205
+ "Steps:\n" +
206
+ "1. Call cron_list and look for a job with id '" + sweepCronId + "'\n" +
207
+ "2. If it exists, return { existed: true }\n" +
208
+ "3. If not found, call cron_add with:\n" +
209
+ " - id: '" + sweepCronId + "'\n" +
210
+ " - title: 'Muse Crew orphan sweep'\n" +
211
+ " - enabled: true\n" +
212
+ " - mode: 'task'\n" +
213
+ " - schedule: { kind: 'interval', every: '30m' }\n" +
214
+ " - owner: 'space:" + dashboardSlug + "'\n" +
215
+ " - timeout_secs: 120\n" +
216
+ " - body: the body text below\n" +
217
+ " Return { existed: false }\n\n" +
218
+ "Body text for the cron:\n" +
219
+ "---\n" +
220
+ "## Muse Crew Orphan Sweep\n\n" +
221
+ "Run the orphan sweep to clean merged worktrees and break stale merge locks.\n\n" +
222
+ "Run this command:\n" +
223
+ crewHome + "/lib/orphan-sweep.sh clean\n\n" +
224
+ "Report the output. If it says CLEAN, no action was needed.\n" +
225
+ "---\n\n" +
226
+ "Return JSON with existed (boolean).",
227
+ {
228
+ key: "sweep-cron-1",
229
+ label: "Create sweep cron",
230
+ schema: {
231
+ type: "object",
232
+ properties: { existed: { type: "boolean" } },
233
+ required: ["existed"]
234
+ }
235
+ }
236
+ );
237
+ } catch (e) {
238
+ return { __hatchWorkflowControl: "blocked", result: { blocked_reason: "Sweep cron creation failed", message: String(e.message || e) } };
239
+ }
240
+ log("Sweep cron: " + (sweepResult.existed ? "already existed" : "created as " + sweepCronId));
241
+
242
+ // ── Summary ───────────────────────────────────────────────────────────
243
+ return {
244
+ message: "Muse Crew initialized.",
245
+ crewHome: crewHome,
246
+ dashboardSlug: dashboardSlug,
247
+ dashboardName: dashboardName,
248
+ cronId: cronId,
249
+ releaseHash: releaseResult.hash,
250
+ scaffold: { created: scaffoldCreated, skipped: scaffoldSkipped },
251
+ project: projectResult.registered ? "registered" : "failed",
252
+ cron: cronResult.existed ? "existed" : "created",
253
+ sweepCron: sweepResult.existed ? "existed" : "created"
254
+ };
@@ -0,0 +1,141 @@
1
+ export const meta = {
2
+ name: "crew-docs",
3
+ description: "Docs workflow: Triage → Write → Review",
4
+ phases: ["Triage", "Write", "Review"],
5
+ steps: [
6
+ { name: "Triage", identity: "sage" },
7
+ { name: "Write", identity: "tate" },
8
+ { name: "Review", identity: "cass" }
9
+ ],
10
+ reworkTarget: "Write"
11
+ };
12
+
13
+ const inputs = args ?? {};
14
+ const taskId = inputs.task_id;
15
+ const taskTitle = inputs.task_title || "";
16
+ const taskDescription = inputs.task_description || "";
17
+ const firstSessionId = inputs.session_id || null;
18
+ const startStepIndex = inputs.start_step_index || 0;
19
+
20
+ // Config from args — backward-compatible fallbacks for manual launches
21
+ const DASHBOARD_SLUG = inputs.dashboardSlug || "orchestra-dashboard";
22
+ const crewHome = inputs.crewHome || "~/workspace/.jarvis";
23
+ const ORCH_PATH = crewHome + "/.orchestration";
24
+
25
+ if (!taskId) {
26
+ throw new Error("task_id is required in args");
27
+ }
28
+
29
+ // STEPS inline — export const meta is parsed as metadata, not a runtime binding
30
+ const STEPS = [
31
+ { name: "Triage", identity: "sage" },
32
+ { name: "Write", identity: "tate" },
33
+ { name: "Review", identity: "cass" }
34
+ ];
35
+ const WRITE_INDEX = STEPS.findIndex(s => s.name === 'Write');
36
+ if (WRITE_INDEX < 0) throw new Error("STEPS missing 'Write' step");
37
+ const MAX_REWORK = 2;
38
+ let reworkCount = 0;
39
+ let rejectionNotes = inputs.rejection_notes || "";
40
+ let i = startStepIndex;
41
+
42
+ while (i < STEPS.length) {
43
+ const step = STEPS[i];
44
+ const isFirstClaim = (i === startStepIndex && reworkCount === 0);
45
+
46
+ phase(step.name);
47
+ log(step.name + " step (" + step.identity + ") for task " + taskId);
48
+
49
+ let activeSessionId;
50
+ if (isFirstClaim && firstSessionId) {
51
+ activeSessionId = firstSessionId;
52
+ } else {
53
+ const claimResult = await agent(
54
+ "Claim a session for this task step.\n" +
55
+ "Call artifact_invoke_action on slug \"" + DASHBOARD_SLUG + "\", action \"claimtask\", args:\n" +
56
+ "{ \"task_id\": \"" + taskId + "\", \"identity\": \"" + step.identity + "\", \"step\": \"" + step.name + "\", \"notes\": \"" + step.name + " step started" + (reworkCount > 0 ? " (rework #" + reworkCount + ")" : "") + "\" }.\n" +
57
+ "Return the session_id from the response.",
58
+ {
59
+ key: "claim-" + step.name + (reworkCount > 0 ? "-r" + reworkCount : ""),
60
+ label: "Claiming " + step.name,
61
+ schema: {
62
+ type: "object",
63
+ properties: { session_id: { type: "string" } },
64
+ required: ["session_id"]
65
+ }
66
+ }
67
+ );
68
+ activeSessionId = claimResult.session_id;
69
+ }
70
+
71
+ var instructions = "";
72
+ if (step.name === "Triage") {
73
+ 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
+ } 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.\nAll doc files go under " + crewHome + "/." +
76
+ (rejectionNotes ? "\n\nREWORK after review rejection. Address:\n" + rejectionNotes : "") +
77
+ "\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
+ } else if (step.name === "Review") {
79
+ instructions = "Review the docs independently and cold — clarity, accuracy, completeness.\nNo prior context from the writer.\nIf it passes, your final response MUST be valid JSON and nothing else: { \"passed\": true, \"summary\": \"approval notes\" }.\nIf it fails, your final response MUST be valid JSON and nothing else: { \"passed\": false, \"summary\": \"rejection notes\" }.\nNo prose, no markdown, just the JSON object.";
80
+ }
81
+
82
+ const stepResult = await agent(
83
+ "Read the identity file at " + ORCH_PATH + "/identities/" + step.identity + ".md using the read tool, and embody that character fully.\n\n" +
84
+ "## Your Assignment\n\n" +
85
+ "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 " + crewHome + "/.",
87
+ {
88
+ key: "work-" + step.name + (reworkCount > 0 ? "-r" + reworkCount : ""),
89
+ label: step.identity + ": " + step.name + " on \"" + taskTitle + "\"",
90
+ timeoutMs: 3600000,
91
+ schema: {
92
+ type: "object",
93
+ properties: { passed: { type: "boolean" }, summary: { type: "string" } },
94
+ required: ["summary"]
95
+ }
96
+ }
97
+ );
98
+
99
+ const summary = (stepResult.summary || "Step completed").slice(0, 2000);
100
+ const passed = stepResult.passed !== false;
101
+ const status = passed ? "completed" : "rejected";
102
+
103
+ await agent(
104
+ "Update session and log event.\n" +
105
+ "Call artifact_invoke_action on slug \"" + DASHBOARD_SLUG + "\", action \"upsertagentsession\", args:\n" +
106
+ "{ \"id\": \"" + activeSessionId + "\", \"task_id\": \"" + taskId + "\", \"identity\": \"" + step.identity + "\", \"step\": \"" + step.name + "\", \"status\": \"" + status + "\", \"notes\": " + JSON.stringify(summary) + " }.\n" +
107
+ "Then call artifact_invoke_action on slug \"" + DASHBOARD_SLUG + "\", action \"logevent\", args:\n" +
108
+ "{ \"task_id\": \"" + taskId + "\", \"type\": \"" + status + "\", \"identity\": \"" + step.identity + "\", \"message\": \"" + step.name + " " + status + " by " + step.identity + "\" }.",
109
+ {
110
+ key: "record-" + step.name + (reworkCount > 0 ? "-r" + reworkCount : ""),
111
+ label: "Recording " + step.name + " result",
112
+ schema: { type: "object" }
113
+ }
114
+ );
115
+
116
+ // Review rejection bounces to Write (not Build)
117
+ if (!passed && step.name === "Review") {
118
+ reworkCount++;
119
+ if (reworkCount > MAX_REWORK) {
120
+ log("Max rework attempts reached for task " + taskId);
121
+ return { status: "blocked", task_id: taskId, reason: "Exceeded " + MAX_REWORK + " rework attempts after Review rejection" };
122
+ }
123
+ rejectionNotes = summary;
124
+ i = WRITE_INDEX;
125
+ log("Review rejected — bouncing to Write (rework #" + reworkCount + ")");
126
+ continue;
127
+ }
128
+
129
+ i++;
130
+ }
131
+
132
+ await agent(
133
+ "Mark this task as done.\n" +
134
+ "Call artifact_invoke_action on slug \"" + DASHBOARD_SLUG + "\", action \"updatetask\", args: { \"id\": \"" + taskId + "\", \"state\": \"done\" }.\n" +
135
+ "Then call artifact_invoke_action on slug \"" + DASHBOARD_SLUG + "\", action \"logevent\", args:\n" +
136
+ "{ \"task_id\": \"" + taskId + "\", \"type\": \"completed\", \"message\": \"All docs workflow steps complete.\" }.",
137
+ { key: "task-done", label: "Completing task: " + taskTitle, schema: { type: "object" } }
138
+ );
139
+
140
+ log("Docs workflow complete for task " + taskId);
141
+ return { status: "ok", task_id: taskId, message: "Docs workflow complete for " + taskTitle };