@shenlee/devcrew 0.1.3 → 0.1.5
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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/packages/adapters/src/index.js +188 -7
- package/dist/packages/core/src/active-run.js +13 -1
- package/dist/packages/core/src/config.js +87 -16
- package/dist/packages/core/src/index.js +1 -0
- package/dist/packages/core/src/lock.js +89 -0
- package/dist/packages/core/src/paths.js +3 -0
- package/dist/packages/core/src/store.js +10 -0
- package/dist/packages/core/src/version.js +1 -1
- package/dist/packages/core/src/workflow.js +20 -3
- package/dist/packages/orchestrator/src/index.js +100 -16
- package/dist/packages/service/src/tools.js +79 -18
- package/docs/claude-code.md +14 -1
- package/docs/codex.md +15 -2
- package/docs/quickstart.md +1 -1
- package/docs/workflow.md +11 -2
- package/package.json +2 -2
- package/packages/adapters/src/index.ts +220 -9
- package/packages/core/src/active-run.ts +13 -1
- package/packages/core/src/config.ts +96 -17
- package/packages/core/src/index.ts +1 -0
- package/packages/core/src/lock.ts +104 -0
- package/packages/core/src/paths.ts +4 -0
- package/packages/core/src/store.ts +12 -1
- package/packages/core/src/types.ts +47 -1
- package/packages/core/src/version.ts +1 -1
- package/packages/core/src/workflow.ts +22 -3
- package/packages/orchestrator/src/index.ts +109 -17
- package/packages/service/src/tools.ts +86 -16
- package/plugins/devcrew-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/devcrew-codex/.mcp.json +1 -1
- package/scripts/smoke-codex-plugin.mjs +1 -1
- package/docs/superpowers/plans/2026-07-10-p0-foundation-repair.md +0 -939
- package/docs/superpowers/plans/2026-07-13-safety-semantics.md +0 -313
- package/docs/superpowers/specs/2026-07-10-p0-foundation-repair-design.md +0 -182
- package/docs/superpowers/specs/2026-07-13-execution-boundaries-design.md +0 -126
|
@@ -2,7 +2,7 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
4
|
import { runRole } from "../../adapters/src/index.js";
|
|
5
|
-
import { answerWorkflow, approveWorkflow, artifactForPhase, artifactPath, ARTIFACTS, discoverCoverageCommands, discoverLintCommands, discoverVerifyCommands, gateForPhase, getWorkflowStatus, nextPhaseAfterGate, readConfig, rejectWorkflow, renderArtifact, saveState, startWorkflow, validateWorkflowApproval, waiveVerificationWorkflow, } from "../../core/src/index.js";
|
|
5
|
+
import { abortWorkflow, answerWorkflow, approveWorkflow, artifactForPhase, artifactPath, ARTIFACTS, discoverCoverageCommands, discoverLintCommands, discoverVerifyCommands, gateForPhase, getWorkflowStatus, nextPhaseAfterGate, readConfig, rejectWorkflow, renderArtifact, saveState, startWorkflow, validateWorkflowApproval, waiveVerificationWorkflow, } from "../../core/src/index.js";
|
|
6
6
|
import { captureExecutionChanges, cleanupExecutionWorkspace, ensureExecutionWorkspace, promoteExecutionChanges, rollbackPromotedExecutionChanges, } from "./worktree.js";
|
|
7
7
|
// Hard cap so a hung apply/verify command cannot block the serialized MCP loop.
|
|
8
8
|
const COMMAND_TIMEOUT_MS = 300_000;
|
|
@@ -25,6 +25,7 @@ function conductorDecision(state, role, gate) {
|
|
|
25
25
|
summary,
|
|
26
26
|
markdown: `# Conductor Decision\n\n${summary}\n`,
|
|
27
27
|
usedFallback: false,
|
|
28
|
+
format: "legacy",
|
|
28
29
|
};
|
|
29
30
|
}
|
|
30
31
|
function priorArtifactNamesForPhase(phase) {
|
|
@@ -170,12 +171,14 @@ function verificationStatusFor(results) {
|
|
|
170
171
|
return results.every((result) => result.exitCode === 0) ? "passed" : "failed";
|
|
171
172
|
}
|
|
172
173
|
function setTestingGateFromVerification(state) {
|
|
173
|
-
if (state.verificationStatus
|
|
174
|
+
if (state.verificationStatus !== "passed") {
|
|
174
175
|
state.gates.testing = "rejected";
|
|
175
176
|
state.status = "awaiting_input";
|
|
176
177
|
state.feedback.push({
|
|
177
178
|
gate: "testing",
|
|
178
|
-
message:
|
|
179
|
+
message: state.verificationStatus === "failed"
|
|
180
|
+
? "Automated verification failed. Inspect the test report, revise the implementation, or record an explicit verification waiver with its reason."
|
|
181
|
+
: "No verification evidence was recorded. Configure or run validation, revise the implementation, or record an explicit verification waiver with its reason.",
|
|
179
182
|
createdAt: now(),
|
|
180
183
|
});
|
|
181
184
|
return;
|
|
@@ -198,6 +201,7 @@ function hostCompletionResult(state, summary) {
|
|
|
198
201
|
summary: `${role} completed through the native ${state.host} host: ${summary}`,
|
|
199
202
|
markdown: `${renderArtifact(artifact, state).trim()}\n\n## Native Host Summary\n\n${summary}\n`,
|
|
200
203
|
usedFallback: false,
|
|
204
|
+
format: "legacy",
|
|
201
205
|
};
|
|
202
206
|
}
|
|
203
207
|
function parseCompletionSummary(value) {
|
|
@@ -233,16 +237,56 @@ function parseCompletionVerification(value) {
|
|
|
233
237
|
};
|
|
234
238
|
});
|
|
235
239
|
}
|
|
236
|
-
function
|
|
240
|
+
function structuredRoleResultBlock(result) {
|
|
241
|
+
if (result.format !== "structured" || !result.structured) {
|
|
242
|
+
return "";
|
|
243
|
+
}
|
|
244
|
+
const data = result.structured;
|
|
245
|
+
const sections = [
|
|
246
|
+
"## Structured Role Result",
|
|
247
|
+
"",
|
|
248
|
+
`Schema Version: ${data.schemaVersion}`,
|
|
249
|
+
`Summary: ${data.summary}`,
|
|
250
|
+
];
|
|
251
|
+
if (data.questions?.length) {
|
|
252
|
+
sections.push("", "### Questions", "", ...data.questions.map((question) => `- ${question.id}: ${question.prompt}`));
|
|
253
|
+
}
|
|
254
|
+
if (data.decisions?.length) {
|
|
255
|
+
sections.push("", "### Decisions", "", ...data.decisions.map((decision) => `- ${decision}`));
|
|
256
|
+
}
|
|
257
|
+
if (data.changedFiles?.length) {
|
|
258
|
+
sections.push("", "### Changed Files", "", ...data.changedFiles.map((file) => `- ${file}`));
|
|
259
|
+
}
|
|
260
|
+
if (data.evidence.length) {
|
|
261
|
+
sections.push("", "### Command Evidence", "");
|
|
262
|
+
for (const evidence of data.evidence) {
|
|
263
|
+
sections.push(`- \`${evidence.command}\` — exit code ${evidence.exitCode}`);
|
|
264
|
+
if (evidence.output) {
|
|
265
|
+
sections.push("", "```text", evidence.output, "```");
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (data.testCases?.length) {
|
|
270
|
+
sections.push("", "### Test Cases", "", "| ID | Scenario | Type | Expected |", "| --- | --- | --- | --- |");
|
|
271
|
+
sections.push(...data.testCases.map((testCase) => `| ${testCase.id} | ${testCase.scenario} | ${testCase.type} | ${testCase.expected} |`));
|
|
272
|
+
}
|
|
273
|
+
if (data.risks.length) {
|
|
274
|
+
sections.push("", "### Risks", "", ...data.risks.map((risk) => `- ${risk}`));
|
|
275
|
+
}
|
|
276
|
+
return `\n\n${sections.join("\n")}`;
|
|
277
|
+
}
|
|
278
|
+
function appendExecutionSections(artifact, markdown, state, result) {
|
|
279
|
+
let content = markdown.trim();
|
|
280
|
+
if (result?.format === "structured" && !content.includes("## Structured Role Result")) {
|
|
281
|
+
content += structuredRoleResultBlock(result);
|
|
282
|
+
}
|
|
237
283
|
if (artifact === "architecture-review" && state.architectureReview) {
|
|
238
|
-
let content = markdown.trim();
|
|
239
284
|
if (!content.includes("## Review Decision")) {
|
|
240
285
|
content += `\n\n## Review Decision\n\nDecision: ${state.architectureReview.decision}\n\nSummary: ${state.architectureReview.summary}`;
|
|
241
286
|
}
|
|
242
287
|
return `${content}\n`;
|
|
243
288
|
}
|
|
244
289
|
if (artifact === "test-report") {
|
|
245
|
-
let content = markdown.trim();
|
|
246
290
|
if (!content.includes("## Acceptance Evidence")) {
|
|
247
291
|
content += `\n\n## Acceptance Evidence\n\n${verificationBlock(state.verification)}`;
|
|
248
292
|
}
|
|
@@ -254,7 +298,7 @@ function appendExecutionSections(artifact, markdown, state) {
|
|
|
254
298
|
}
|
|
255
299
|
return `${content}\n`;
|
|
256
300
|
}
|
|
257
|
-
return
|
|
301
|
+
return `${content}\n`;
|
|
258
302
|
}
|
|
259
303
|
async function writeImplementationReview(state) {
|
|
260
304
|
state.artifacts["implementation-review"] = await writeMarkdownArtifact(state, "implementation-review", renderArtifact("implementation-review", state));
|
|
@@ -361,11 +405,12 @@ async function runCurrentPhaseRole(state, runner = runRole) {
|
|
|
361
405
|
await writeImplementationReview(state);
|
|
362
406
|
}
|
|
363
407
|
if (state.phase === "review") {
|
|
364
|
-
|
|
408
|
+
const reviewDecision = result.format === "structured" ? result.structured?.reviewDecision : result.reviewDecision;
|
|
409
|
+
if (!reviewDecision) {
|
|
365
410
|
throw new Error("DevCrew architecture review must return a structured review decision");
|
|
366
411
|
}
|
|
367
412
|
state.architectureReview = {
|
|
368
|
-
decision:
|
|
413
|
+
decision: reviewDecision,
|
|
369
414
|
summary: result.summary,
|
|
370
415
|
reviewedAt: now(),
|
|
371
416
|
};
|
|
@@ -373,12 +418,15 @@ async function runCurrentPhaseRole(state, runner = runRole) {
|
|
|
373
418
|
// When the backend cannot run a real SDK we keep a single deterministic
|
|
374
419
|
// artifact source by rendering the rich phase template from the core layer.
|
|
375
420
|
const baseMarkdown = result.usedFallback ? `${fallbackNotice(result)}${renderArtifact(artifact, state)}` : result.markdown;
|
|
376
|
-
const markdown = appendExecutionSections(artifact, baseMarkdown, state);
|
|
421
|
+
const markdown = appendExecutionSections(artifact, baseMarkdown, state, result);
|
|
377
422
|
state.roles.push({ ...result, markdown });
|
|
378
423
|
state.artifacts[artifact] = await writeMarkdownArtifact(state, artifact, markdown);
|
|
379
424
|
state.executionInstruction = undefined;
|
|
380
|
-
|
|
381
|
-
|
|
425
|
+
const questions = result.format === "structured"
|
|
426
|
+
? result.structured?.questions?.map((question) => question.prompt) ?? []
|
|
427
|
+
: result.questions ?? [];
|
|
428
|
+
if (state.phase === "requirements" && questions.length > 0) {
|
|
429
|
+
state.pendingQuestions = questions;
|
|
382
430
|
state.gates.requirements = "not_started";
|
|
383
431
|
state.status = "awaiting_input";
|
|
384
432
|
return saveState(state);
|
|
@@ -419,7 +467,8 @@ export async function continueOrchestratedWorkflow(input, runner = runRole) {
|
|
|
419
467
|
if (state.status === "awaiting_approval" ||
|
|
420
468
|
state.status === "awaiting_input" ||
|
|
421
469
|
state.status === "awaiting_execution" ||
|
|
422
|
-
state.status === "complete"
|
|
470
|
+
state.status === "complete" ||
|
|
471
|
+
state.status === "aborted") {
|
|
423
472
|
return state;
|
|
424
473
|
}
|
|
425
474
|
return runCurrentPhaseRole(state, runner);
|
|
@@ -454,8 +503,8 @@ export async function approveOrchestratedWorkflow(input) {
|
|
|
454
503
|
return cleanupAfterApproval(before);
|
|
455
504
|
}
|
|
456
505
|
if (promotingTesting) {
|
|
457
|
-
if (before.verificationStatus
|
|
458
|
-
throw new Error("
|
|
506
|
+
if (before.verificationStatus !== "passed" && !before.verificationWaiver) {
|
|
507
|
+
throw new Error("Verification must pass before promotion or have an explicit verification waiver");
|
|
459
508
|
}
|
|
460
509
|
await promoteExecutionChanges(before);
|
|
461
510
|
let approved;
|
|
@@ -524,7 +573,7 @@ export async function completeOrchestratedExecution(input) {
|
|
|
524
573
|
await writeImplementationReview(state);
|
|
525
574
|
const result = hostCompletionResult(state, summary);
|
|
526
575
|
const artifact = artifactForPhase(state.phase);
|
|
527
|
-
const markdown = appendExecutionSections(artifact, result.markdown, state);
|
|
576
|
+
const markdown = appendExecutionSections(artifact, result.markdown, state, result);
|
|
528
577
|
state.roles.push({ ...result, markdown });
|
|
529
578
|
state.artifacts[artifact] = await writeMarkdownArtifact(state, artifact, markdown);
|
|
530
579
|
state.executionInstruction = undefined;
|
|
@@ -534,6 +583,32 @@ export async function completeOrchestratedExecution(input) {
|
|
|
534
583
|
export async function rejectOrchestratedWorkflow(input) {
|
|
535
584
|
return rejectWorkflow(input);
|
|
536
585
|
}
|
|
586
|
+
export async function abortOrchestratedWorkflow(input) {
|
|
587
|
+
const state = await abortWorkflow(input);
|
|
588
|
+
if (!state.executionWorkspace) {
|
|
589
|
+
return state;
|
|
590
|
+
}
|
|
591
|
+
try {
|
|
592
|
+
await cleanupExecutionWorkspace(state);
|
|
593
|
+
}
|
|
594
|
+
catch {
|
|
595
|
+
return state;
|
|
596
|
+
}
|
|
597
|
+
state.executionWorkspace = undefined;
|
|
598
|
+
return saveState(state);
|
|
599
|
+
}
|
|
600
|
+
export async function recoverOrchestratedWorkflow(input) {
|
|
601
|
+
const state = await getWorkflowStatus(input);
|
|
602
|
+
if (state.status !== "aborted" && state.status !== "complete") {
|
|
603
|
+
throw new Error("Only terminal DevCrew runs can be recovered");
|
|
604
|
+
}
|
|
605
|
+
if (!state.executionWorkspace) {
|
|
606
|
+
return state;
|
|
607
|
+
}
|
|
608
|
+
await cleanupExecutionWorkspace(state);
|
|
609
|
+
state.executionWorkspace = undefined;
|
|
610
|
+
return saveState(state);
|
|
611
|
+
}
|
|
537
612
|
export async function answerOrchestratedWorkflow(input, runner = runRole) {
|
|
538
613
|
const before = await getWorkflowStatus(input);
|
|
539
614
|
const state = await answerWorkflow(input, { skipArtifactWrite: true });
|
|
@@ -545,6 +620,15 @@ export async function answerOrchestratedWorkflow(input, runner = runRole) {
|
|
|
545
620
|
state.gates.testing = "not_started";
|
|
546
621
|
return saveState(state);
|
|
547
622
|
}
|
|
623
|
+
if (before.executionMode === "apply" &&
|
|
624
|
+
before.phase === "review" &&
|
|
625
|
+
before.gates["implementation-review"] === "rejected" &&
|
|
626
|
+
before.architectureReview?.decision === "changes_required") {
|
|
627
|
+
state.phase = "execution";
|
|
628
|
+
state.status = "ready";
|
|
629
|
+
state.gates["implementation-review"] = "not_started";
|
|
630
|
+
return saveState(state);
|
|
631
|
+
}
|
|
548
632
|
const role = roleForPhase(state.phase);
|
|
549
633
|
if (!role) {
|
|
550
634
|
return state;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { getArtifact, getActiveRunId, getWorkflowStatus, setActiveRun, } from "../../core/src/index.js";
|
|
2
|
-
import { answerOrchestratedWorkflow, approveOrchestratedWorkflow, completeOrchestratedExecution, continueOrchestratedWorkflow, rejectOrchestratedWorkflow, startOrchestratedWorkflow, waiveOrchestratedVerification, } from "../../orchestrator/src/index.js";
|
|
1
|
+
import { clearActiveRunIfMatches, getArtifact, getActiveRunId, getWorkflowStatus, recoverRepositoryLock, setActiveRun, withRepositoryLock, } from "../../core/src/index.js";
|
|
2
|
+
import { abortOrchestratedWorkflow, answerOrchestratedWorkflow, approveOrchestratedWorkflow, completeOrchestratedExecution, continueOrchestratedWorkflow, rejectOrchestratedWorkflow, recoverOrchestratedWorkflow, startOrchestratedWorkflow, waiveOrchestratedVerification, } from "../../orchestrator/src/index.js";
|
|
3
3
|
const cwdProperty = { type: "string", description: "Repository working directory." };
|
|
4
4
|
const runIdProperty = { type: "string", description: "DevCrew run id." };
|
|
5
5
|
const hostValues = ["codex", "claude"];
|
|
@@ -22,8 +22,23 @@ function withInferredHost(args) {
|
|
|
22
22
|
}
|
|
23
23
|
return { ...args, host: inferHost() };
|
|
24
24
|
}
|
|
25
|
+
async function withMutationLock(args, action) {
|
|
26
|
+
if (typeof args.cwd !== "string" || !args.cwd.trim()) {
|
|
27
|
+
return action();
|
|
28
|
+
}
|
|
29
|
+
return withRepositoryLock(args.cwd, action);
|
|
30
|
+
}
|
|
25
31
|
export function listDevCrewTools() {
|
|
26
32
|
return [
|
|
33
|
+
{
|
|
34
|
+
name: "devcrew_abort",
|
|
35
|
+
description: "Abort a nonterminal run, preserve its audit evidence, and clean its isolated worktree when possible.",
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: "object",
|
|
38
|
+
required: ["cwd", "reason"],
|
|
39
|
+
properties: { cwd: cwdProperty, runId: runIdProperty, reason: { type: "string" } },
|
|
40
|
+
},
|
|
41
|
+
},
|
|
27
42
|
{
|
|
28
43
|
name: "devcrew_start",
|
|
29
44
|
description: "Create a new gated DevCrew workflow run.",
|
|
@@ -49,6 +64,15 @@ export function listDevCrewTools() {
|
|
|
49
64
|
},
|
|
50
65
|
},
|
|
51
66
|
},
|
|
67
|
+
{
|
|
68
|
+
name: "devcrew_recover",
|
|
69
|
+
description: "Explicitly clear a confirmed stale lock and retry cleanup for a terminal run without executing an agent.",
|
|
70
|
+
inputSchema: {
|
|
71
|
+
type: "object",
|
|
72
|
+
required: ["cwd"],
|
|
73
|
+
properties: { cwd: cwdProperty, runId: runIdProperty },
|
|
74
|
+
},
|
|
75
|
+
},
|
|
52
76
|
{
|
|
53
77
|
name: "devcrew_status",
|
|
54
78
|
description: "Read the status of a DevCrew workflow run.",
|
|
@@ -152,7 +176,8 @@ function summarizeState(state) {
|
|
|
152
176
|
const pendingGate = Object.entries(state.gates).find(([, status]) => status === "pending")?.[0] ?? "none";
|
|
153
177
|
const role = state.roles.at(-1);
|
|
154
178
|
const roleFallback = role?.usedFallback === true ? (role.backend === "local" ? "local" : "sdk") : role ? "none" : "none";
|
|
155
|
-
|
|
179
|
+
const roleFormat = role?.format ?? "none";
|
|
180
|
+
return `Run ${state.runId}: phase=${state.phase}, status=${state.status}, execution_mode=${state.executionMode}, pending_gate=${pendingGate}, role_fallback=${roleFallback}, role_format=${roleFormat}`;
|
|
156
181
|
}
|
|
157
182
|
function success(text, structuredContent) {
|
|
158
183
|
return {
|
|
@@ -172,37 +197,73 @@ function failure(error) {
|
|
|
172
197
|
export async function callDevCrewTool(name, args) {
|
|
173
198
|
try {
|
|
174
199
|
if (name === "devcrew_start") {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
200
|
+
return await withMutationLock(args, async () => {
|
|
201
|
+
const state = await startOrchestratedWorkflow(withInferredHost(args));
|
|
202
|
+
await setActiveRun(state.cwd, state.runId);
|
|
203
|
+
return success(`${summarizeState(state)}. Review ${state.artifacts.requirements}`, { state });
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
if (name === "devcrew_abort") {
|
|
207
|
+
return await withMutationLock(args, async () => {
|
|
208
|
+
const runArgs = await withActiveRun(args);
|
|
209
|
+
const state = await abortOrchestratedWorkflow(runArgs);
|
|
210
|
+
await clearActiveRunIfMatches(state.cwd, state.runId);
|
|
211
|
+
return success(`${summarizeState(state)}. Run aborted.`, { state });
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
if (name === "devcrew_recover") {
|
|
215
|
+
if (typeof args.cwd !== "string" || !args.cwd.trim()) {
|
|
216
|
+
throw new Error("cwd must be a non-empty string");
|
|
217
|
+
}
|
|
218
|
+
const recoveredLock = await recoverRepositoryLock(args.cwd);
|
|
219
|
+
if (typeof args.runId !== "string" || !args.runId.trim()) {
|
|
220
|
+
return success(recoveredLock ? "Repository lock recovery completed." : "No stale repository lock was present.", { recoveredLock });
|
|
221
|
+
}
|
|
222
|
+
return await withMutationLock(args, async () => {
|
|
223
|
+
const runArgs = await withActiveRun(args);
|
|
224
|
+
const state = await recoverOrchestratedWorkflow(runArgs);
|
|
225
|
+
return success(`${summarizeState(state)}. Recovery cleanup completed.`, { state });
|
|
226
|
+
});
|
|
178
227
|
}
|
|
179
228
|
if (name === "devcrew_status") {
|
|
180
229
|
const state = await getWorkflowStatus((await withActiveRun(args)));
|
|
181
230
|
return success(summarizeState(state), { state });
|
|
182
231
|
}
|
|
183
232
|
if (name === "devcrew_answer") {
|
|
184
|
-
|
|
185
|
-
|
|
233
|
+
return await withMutationLock(args, async () => {
|
|
234
|
+
const state = await answerOrchestratedWorkflow((await withActiveRun(args)));
|
|
235
|
+
return success(`${summarizeState(state)}. Answer recorded.`, { state });
|
|
236
|
+
});
|
|
186
237
|
}
|
|
187
238
|
if (name === "devcrew_approve") {
|
|
188
|
-
|
|
189
|
-
|
|
239
|
+
return await withMutationLock(args, async () => {
|
|
240
|
+
const state = await approveOrchestratedWorkflow((await withActiveRun(args)));
|
|
241
|
+
return success(`${summarizeState(state)}. Gate approved.`, { state });
|
|
242
|
+
});
|
|
190
243
|
}
|
|
191
244
|
if (name === "devcrew_reject") {
|
|
192
|
-
|
|
193
|
-
|
|
245
|
+
return await withMutationLock(args, async () => {
|
|
246
|
+
const state = await rejectOrchestratedWorkflow((await withActiveRun(args)));
|
|
247
|
+
return success(`${summarizeState(state)}. Gate rejected.`, { state });
|
|
248
|
+
});
|
|
194
249
|
}
|
|
195
250
|
if (name === "devcrew_continue") {
|
|
196
|
-
|
|
197
|
-
|
|
251
|
+
return await withMutationLock(args, async () => {
|
|
252
|
+
const state = await continueOrchestratedWorkflow((await withActiveRun(args)));
|
|
253
|
+
return success(`${summarizeState(state)}.`, { state });
|
|
254
|
+
});
|
|
198
255
|
}
|
|
199
256
|
if (name === "devcrew_complete_execution") {
|
|
200
|
-
|
|
201
|
-
|
|
257
|
+
return await withMutationLock(args, async () => {
|
|
258
|
+
const state = await completeOrchestratedExecution((await withActiveRun(args)));
|
|
259
|
+
return success(`${summarizeState(state)}. Native host completion recorded.`, { state });
|
|
260
|
+
});
|
|
202
261
|
}
|
|
203
262
|
if (name === "devcrew_waive_verification") {
|
|
204
|
-
|
|
205
|
-
|
|
263
|
+
return await withMutationLock(args, async () => {
|
|
264
|
+
const state = await waiveOrchestratedVerification((await withActiveRun(args)));
|
|
265
|
+
return success(`${summarizeState(state)}. Verification waiver recorded.`, { state });
|
|
266
|
+
});
|
|
206
267
|
}
|
|
207
268
|
if (name === "devcrew_artifact") {
|
|
208
269
|
const artifact = await getArtifact((await withActiveRun(args)));
|
package/docs/claude-code.md
CHANGED
|
@@ -22,13 +22,26 @@ It contains:
|
|
|
22
22
|
Role behavior is defined by the DevCrew MCP service and its shared runtime role
|
|
23
23
|
schema. The plugin intentionally does not bundle inactive subagent templates.
|
|
24
24
|
|
|
25
|
+
## Structured role results
|
|
26
|
+
|
|
27
|
+
SDK roles should return one `<!-- devcrew-role-result -->` marker followed by a
|
|
28
|
+
JSON fenced block with `schemaVersion: 1`, then the human-readable Markdown
|
|
29
|
+
artifact. The JSON carries the role summary, risks, command evidence, and
|
|
30
|
+
role-specific fields such as PM questions, architecture decisions, changed
|
|
31
|
+
files, or test cases. DevCrew removes the marked block before saving the
|
|
32
|
+
Markdown artifact and exposes the validated data through MCP state.
|
|
33
|
+
|
|
34
|
+
Markdown-only output remains supported as `role_format=legacy`. A missing marker
|
|
35
|
+
uses this compatibility path; a present marker with malformed, ambiguous, or
|
|
36
|
+
schema-invalid JSON is rejected and never silently downgraded.
|
|
37
|
+
|
|
25
38
|
For local plugin testing:
|
|
26
39
|
|
|
27
40
|
```bash
|
|
28
41
|
claude --plugin-dir plugins/devcrew-claude
|
|
29
42
|
```
|
|
30
43
|
|
|
31
|
-
Then ask Claude Code to use DevCrew for a feature or product workflow. The generated `.mcp.json` starts the version-locked npm package with an `npm exec --package=@shenlee/devcrew@0.1.
|
|
44
|
+
Then ask Claude Code to use DevCrew for a feature or product workflow. The generated `.mcp.json` starts the version-locked npm package with an `npm exec --package=@shenlee/devcrew@0.1.5` wrapper.
|
|
32
45
|
|
|
33
46
|
The default apply policy is `interactive-host`: Claude Code performs implementation and testing with its native controls. Explicit `headless-restricted` and `headless-unattended` policies are independent DevCrew SDK policies and do not inherit the current Claude Code approval session.
|
|
34
47
|
|
package/docs/codex.md
CHANGED
|
@@ -13,7 +13,7 @@ Restart Codex, open the plugin directory, select the DevCrew marketplace, and in
|
|
|
13
13
|
The plugin launches the MCP server with:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
npm exec --silent --yes --package=@shenlee/devcrew@0.1.
|
|
16
|
+
npm exec --silent --yes --package=@shenlee/devcrew@0.1.5 -- node -e "<DevCrew CLI wrapper>" -- serve --stdio
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
Use this path when you want to use DevCrew without cloning the repository first. The version is locked to the published npm package that matches the plugin manifest.
|
|
@@ -42,6 +42,19 @@ It contains:
|
|
|
42
42
|
Role behavior is defined by the DevCrew MCP service and its shared runtime role
|
|
43
43
|
schema. The plugin intentionally does not bundle inactive subagent templates.
|
|
44
44
|
|
|
45
|
+
## Structured role results
|
|
46
|
+
|
|
47
|
+
SDK roles should return one `<!-- devcrew-role-result -->` marker followed by a
|
|
48
|
+
JSON fenced block with `schemaVersion: 1`, then the human-readable Markdown
|
|
49
|
+
artifact. The JSON carries the role summary, risks, command evidence, and
|
|
50
|
+
role-specific fields such as PM questions, architecture decisions, changed
|
|
51
|
+
files, or test cases. DevCrew removes the marked block before saving the
|
|
52
|
+
Markdown artifact and exposes the validated data through MCP state.
|
|
53
|
+
|
|
54
|
+
Markdown-only output remains supported as `role_format=legacy`. A missing marker
|
|
55
|
+
uses this compatibility path; a present marker with malformed, ambiguous, or
|
|
56
|
+
schema-invalid JSON is rejected and never silently downgraded.
|
|
57
|
+
|
|
45
58
|
For local development, point a Codex marketplace entry at the generated plugin folder, or copy the plugin into your existing local marketplace workflow.
|
|
46
59
|
|
|
47
60
|
The DevCrew skill tells Codex to use these MCP tools:
|
|
@@ -70,7 +83,7 @@ npm install -g @shenlee/devcrew --include=optional
|
|
|
70
83
|
|
|
71
84
|
## Marketplace smoke test
|
|
72
85
|
|
|
73
|
-
After publishing `@shenlee/devcrew@0.1.
|
|
86
|
+
After publishing `@shenlee/devcrew@0.1.5`, run the real marketplace smoke test. Do not treat this as a pre-publication check because the plugin is locked to that npm version:
|
|
74
87
|
|
|
75
88
|
```bash
|
|
76
89
|
npm run smoke:codex-plugin
|
package/docs/quickstart.md
CHANGED
|
@@ -37,7 +37,7 @@ devcrew serve --stdio
|
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
Normally the generated plugin starts this command for the host agent.
|
|
40
|
-
Codex and Claude plugin bundles use a version-locked `npm exec --package=@shenlee/devcrew@0.1.
|
|
40
|
+
Codex and Claude plugin bundles use a version-locked `npm exec --package=@shenlee/devcrew@0.1.5` wrapper so the MCP service is locked to the published package version.
|
|
41
41
|
|
|
42
42
|
## 4. Run A Workflow
|
|
43
43
|
|
package/docs/workflow.md
CHANGED
|
@@ -42,11 +42,20 @@ The implementation-plan approval advances the run to `execution`. With the defau
|
|
|
42
42
|
|
|
43
43
|
Apply requires Git and a clean requester worktree both when execution starts and when the reviewed patch is promoted. The requester repository remains unchanged until testing approval. A failed verification enters `awaiting_input`, rather than a promotable testing gate; it can be revised with `devcrew_answer` or deliberately reopened only through `devcrew_waive_verification` with a non-empty risk reason. If testing is rejected, `devcrew_answer` records the response and returns the run to `execution/ready` with the isolated worktree intact.
|
|
44
44
|
|
|
45
|
+
`devcrew_abort` stops a nonterminal run with a required reason. It preserves the
|
|
46
|
+
run state and artifacts for audit, removes its isolated worktree when possible,
|
|
47
|
+
and clears the active-run pointer only when it refers to that run. An aborted
|
|
48
|
+
run cannot continue. `devcrew_recover` never runs an agent: it explicitly
|
|
49
|
+
clears only a confirmed stale repository lock and retries cleanup for a
|
|
50
|
+
terminal run that retained an isolated worktree.
|
|
51
|
+
|
|
45
52
|
The execution review is also structured: the architect must return either
|
|
46
53
|
`Decision: approved` or `Decision: changes_required` in its
|
|
47
54
|
`architecture-review` artifact. A `changes_required` decision rejects the
|
|
48
|
-
implementation-review gate and keeps the run at `awaiting_input
|
|
49
|
-
|
|
55
|
+
implementation-review gate and keeps the run at `awaiting_input`. Submitting
|
|
56
|
+
`devcrew_answer` then returns the run to `execution/ready` in the same
|
|
57
|
+
isolated worktree, resets that review gate, and requires a new implementation
|
|
58
|
+
pass followed by a later approving review before testing can start.
|
|
50
59
|
|
|
51
60
|
`devcrew_start` records the created run as the active run for the repository.
|
|
52
61
|
Subsequent MCP calls can omit `runId`; DevCrew resolves it from
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shenlee/devcrew",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Multi-host agent workflow service for Codex and Claude Code.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -55,6 +55,6 @@
|
|
|
55
55
|
},
|
|
56
56
|
"optionalDependencies": {
|
|
57
57
|
"@anthropic-ai/claude-agent-sdk": "0.3.163",
|
|
58
|
-
"@openai/codex-sdk": "0.
|
|
58
|
+
"@openai/codex-sdk": "0.144.5"
|
|
59
59
|
}
|
|
60
60
|
}
|