@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.
Files changed (37) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/packages/adapters/src/index.js +188 -7
  4. package/dist/packages/core/src/active-run.js +13 -1
  5. package/dist/packages/core/src/config.js +87 -16
  6. package/dist/packages/core/src/index.js +1 -0
  7. package/dist/packages/core/src/lock.js +89 -0
  8. package/dist/packages/core/src/paths.js +3 -0
  9. package/dist/packages/core/src/store.js +10 -0
  10. package/dist/packages/core/src/version.js +1 -1
  11. package/dist/packages/core/src/workflow.js +20 -3
  12. package/dist/packages/orchestrator/src/index.js +100 -16
  13. package/dist/packages/service/src/tools.js +79 -18
  14. package/docs/claude-code.md +14 -1
  15. package/docs/codex.md +15 -2
  16. package/docs/quickstart.md +1 -1
  17. package/docs/workflow.md +11 -2
  18. package/package.json +2 -2
  19. package/packages/adapters/src/index.ts +220 -9
  20. package/packages/core/src/active-run.ts +13 -1
  21. package/packages/core/src/config.ts +96 -17
  22. package/packages/core/src/index.ts +1 -0
  23. package/packages/core/src/lock.ts +104 -0
  24. package/packages/core/src/paths.ts +4 -0
  25. package/packages/core/src/store.ts +12 -1
  26. package/packages/core/src/types.ts +47 -1
  27. package/packages/core/src/version.ts +1 -1
  28. package/packages/core/src/workflow.ts +22 -3
  29. package/packages/orchestrator/src/index.ts +109 -17
  30. package/packages/service/src/tools.ts +86 -16
  31. package/plugins/devcrew-codex/.codex-plugin/plugin.json +1 -1
  32. package/plugins/devcrew-codex/.mcp.json +1 -1
  33. package/scripts/smoke-codex-plugin.mjs +1 -1
  34. package/docs/superpowers/plans/2026-07-10-p0-foundation-repair.md +0 -939
  35. package/docs/superpowers/plans/2026-07-13-safety-semantics.md +0 -313
  36. package/docs/superpowers/specs/2026-07-10-p0-foundation-repair-design.md +0 -182
  37. 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 === "failed") {
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: "Automated verification failed. Inspect the test report, revise the implementation, or record an explicit verification waiver with its reason.",
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 appendExecutionSections(artifact, markdown, state) {
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 markdown;
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
- if (!result.reviewDecision) {
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: result.reviewDecision,
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
- if (state.phase === "requirements" && result.questions && result.questions.length > 0) {
381
- state.pendingQuestions = result.questions;
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 === "failed" && !before.verificationWaiver) {
458
- throw new Error("Failed verification cannot be promoted without an explicit verification waiver");
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
- return `Run ${state.runId}: phase=${state.phase}, status=${state.status}, execution_mode=${state.executionMode}, pending_gate=${pendingGate}, role_fallback=${roleFallback}`;
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
- const state = await startOrchestratedWorkflow(withInferredHost(args));
176
- await setActiveRun(state.cwd, state.runId);
177
- return success(`${summarizeState(state)}. Review ${state.artifacts.requirements}`, { state });
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
- const state = await answerOrchestratedWorkflow((await withActiveRun(args)));
185
- return success(`${summarizeState(state)}. Answer recorded.`, { state });
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
- const state = await approveOrchestratedWorkflow((await withActiveRun(args)));
189
- return success(`${summarizeState(state)}. Gate approved.`, { state });
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
- const state = await rejectOrchestratedWorkflow((await withActiveRun(args)));
193
- return success(`${summarizeState(state)}. Gate rejected.`, { state });
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
- const state = await continueOrchestratedWorkflow((await withActiveRun(args)));
197
- return success(`${summarizeState(state)}.`, { state });
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
- const state = await completeOrchestratedExecution((await withActiveRun(args)));
201
- return success(`${summarizeState(state)}. Native host completion recorded.`, { state });
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
- const state = await waiveOrchestratedVerification((await withActiveRun(args)));
205
- return success(`${summarizeState(state)}. Verification waiver recorded.`, { state });
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)));
@@ -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.3` wrapper.
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.3 -- node -e "<DevCrew CLI wrapper>" -- serve --stdio
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.3`, run the real marketplace smoke test. Do not treat this as a pre-publication check because the plugin is locked to that npm version:
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
@@ -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.3` wrapper so the MCP service is locked to the published package version.
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`; it cannot
49
- enter testing until the feedback is addressed and a later review approves it.
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",
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.137.0"
58
+ "@openai/codex-sdk": "0.144.5"
59
59
  }
60
60
  }