@shenlee/devcrew 0.1.3 → 0.1.4

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 (39) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/packages/adapters/src/index.js +147 -6
  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/superpowers/plans/2026-07-14-p0-remediation.md +213 -0
  18. package/docs/superpowers/plans/2026-07-14-reliability-recovery.md +208 -0
  19. package/docs/superpowers/plans/2026-07-14-structured-role-results.md +231 -0
  20. package/docs/superpowers/specs/2026-07-14-p0-remediation-design.md +52 -0
  21. package/docs/superpowers/specs/2026-07-14-reliability-recovery-design.md +92 -0
  22. package/docs/superpowers/specs/2026-07-14-structured-role-results-design.md +96 -0
  23. package/docs/workflow.md +11 -2
  24. package/package.json +1 -1
  25. package/packages/adapters/src/index.ts +168 -6
  26. package/packages/core/src/active-run.ts +13 -1
  27. package/packages/core/src/config.ts +96 -17
  28. package/packages/core/src/index.ts +1 -0
  29. package/packages/core/src/lock.ts +104 -0
  30. package/packages/core/src/paths.ts +4 -0
  31. package/packages/core/src/store.ts +12 -1
  32. package/packages/core/src/types.ts +47 -1
  33. package/packages/core/src/version.ts +1 -1
  34. package/packages/core/src/workflow.ts +22 -3
  35. package/packages/orchestrator/src/index.ts +109 -17
  36. package/packages/service/src/tools.ts +86 -16
  37. package/plugins/devcrew-codex/.codex-plugin/plugin.json +1 -1
  38. package/plugins/devcrew-codex/.mcp.json +1 -1
  39. package/scripts/smoke-codex-plugin.mjs +1 -1
@@ -22,6 +22,7 @@ import {
22
22
  } from "./validation.js";
23
23
  import type {
24
24
  AnswerWorkflowInput,
25
+ AbortWorkflowInput,
25
26
  ApproveWorkflowInput,
26
27
  ArtifactName,
27
28
  ArtifactReadResult,
@@ -187,7 +188,8 @@ export async function continueWorkflow(input: RunRef): Promise<RunState> {
187
188
  state.status === "awaiting_approval" ||
188
189
  state.status === "awaiting_input" ||
189
190
  state.status === "awaiting_execution" ||
190
- state.status === "complete"
191
+ state.status === "complete" ||
192
+ state.status === "aborted"
191
193
  ) {
192
194
  return state;
193
195
  }
@@ -220,6 +222,23 @@ export async function continueWorkflow(input: RunRef): Promise<RunState> {
220
222
  return saveState(state);
221
223
  }
222
224
 
225
+ export async function abortWorkflow(input: AbortWorkflowInput): Promise<RunState> {
226
+ const state = await getWorkflowStatus(input);
227
+ if (state.status === "aborted") {
228
+ return state;
229
+ }
230
+ if (state.status === "complete") {
231
+ throw new Error("Completed DevCrew runs cannot be aborted");
232
+ }
233
+ state.abort = {
234
+ reason: parseWaiverReason(input.reason),
235
+ abortedAt: now(),
236
+ };
237
+ delete state.executionInstruction;
238
+ state.status = "aborted";
239
+ return saveState(state);
240
+ }
241
+
223
242
  export async function validateWorkflowApproval(input: ApproveWorkflowInput): Promise<RunState> {
224
243
  const state = await getWorkflowStatus(input);
225
244
  const gate = parseGate(input.gate);
@@ -301,9 +320,9 @@ export async function waiveVerificationWorkflow(input: WaiveVerificationInput):
301
320
  state.phase !== "testing" ||
302
321
  state.status !== "awaiting_input" ||
303
322
  state.gates.testing !== "rejected" ||
304
- state.verificationStatus !== "failed"
323
+ (state.verificationStatus !== "failed" && state.verificationStatus !== "not_run")
305
324
  ) {
306
- throw new Error("A verification waiver is only available after failed apply-mode testing");
325
+ throw new Error("A verification waiver is only available after failed or missing apply-mode verification");
307
326
  }
308
327
  state.verificationWaiver = {
309
328
  reason: parseWaiverReason(input.reason),
@@ -5,6 +5,7 @@ import { dirname } from "node:path";
5
5
  import { runRole } from "../../adapters/src/index.js";
6
6
  import type { RoleRunInput } from "../../adapters/src/index.js";
7
7
  import {
8
+ abortWorkflow,
8
9
  answerWorkflow,
9
10
  approveWorkflow,
10
11
  artifactForPhase,
@@ -24,6 +25,7 @@ import {
24
25
  validateWorkflowApproval,
25
26
  waiveVerificationWorkflow,
26
27
  type AnswerWorkflowInput,
28
+ type AbortWorkflowInput,
27
29
  type ApproveWorkflowInput,
28
30
  type ArtifactName,
29
31
  type CompleteExecutionInput,
@@ -49,9 +51,10 @@ import {
49
51
  const COMMAND_TIMEOUT_MS = 300_000;
50
52
 
51
53
  type RoleRunner = (input: RoleRunInput) => Promise<RoleResult>;
54
+ type ExecutingRole = Exclude<RoleResult["role"], "conductor">;
52
55
 
53
- function roleForPhase(phase: Phase): RoleResult["role"] | undefined {
54
- const roles: Partial<Record<Phase, RoleResult["role"]>> = {
56
+ function roleForPhase(phase: Phase): ExecutingRole | undefined {
57
+ const roles: Partial<Record<Phase, ExecutingRole>> = {
55
58
  requirements: "pm",
56
59
  architecture: "architect",
57
60
  implementation: "implementer",
@@ -70,6 +73,7 @@ function conductorDecision(state: RunState, role: RoleResult["role"], gate: stri
70
73
  summary,
71
74
  markdown: `# Conductor Decision\n\n${summary}\n`,
72
75
  usedFallback: false,
76
+ format: "legacy",
73
77
  };
74
78
  }
75
79
 
@@ -239,12 +243,15 @@ function verificationStatusFor(results: VerificationResult[]): VerificationStatu
239
243
  }
240
244
 
241
245
  function setTestingGateFromVerification(state: RunState): void {
242
- if (state.verificationStatus === "failed") {
246
+ if (state.verificationStatus !== "passed") {
243
247
  state.gates.testing = "rejected";
244
248
  state.status = "awaiting_input";
245
249
  state.feedback.push({
246
250
  gate: "testing",
247
- message: "Automated verification failed. Inspect the test report, revise the implementation, or record an explicit verification waiver with its reason.",
251
+ message:
252
+ state.verificationStatus === "failed"
253
+ ? "Automated verification failed. Inspect the test report, revise the implementation, or record an explicit verification waiver with its reason."
254
+ : "No verification evidence was recorded. Configure or run validation, revise the implementation, or record an explicit verification waiver with its reason.",
248
255
  createdAt: now(),
249
256
  });
250
257
  return;
@@ -269,6 +276,7 @@ function hostCompletionResult(state: RunState, summary: string): RoleResult {
269
276
  summary: `${role} completed through the native ${state.host} host: ${summary}`,
270
277
  markdown: `${renderArtifact(artifact, state).trim()}\n\n## Native Host Summary\n\n${summary}\n`,
271
278
  usedFallback: false,
279
+ format: "legacy",
272
280
  };
273
281
  }
274
282
 
@@ -309,16 +317,57 @@ function parseCompletionVerification(value: unknown): VerificationResult[] {
309
317
  });
310
318
  }
311
319
 
312
- function appendExecutionSections(artifact: ArtifactName, markdown: string, state: RunState): string {
320
+ function structuredRoleResultBlock(result: RoleResult): string {
321
+ if (result.format !== "structured" || !result.structured) {
322
+ return "";
323
+ }
324
+ const data = result.structured;
325
+ const sections = [
326
+ "## Structured Role Result",
327
+ "",
328
+ `Schema Version: ${data.schemaVersion}`,
329
+ `Summary: ${data.summary}`,
330
+ ];
331
+ if (data.questions?.length) {
332
+ sections.push("", "### Questions", "", ...data.questions.map((question) => `- ${question.id}: ${question.prompt}`));
333
+ }
334
+ if (data.decisions?.length) {
335
+ sections.push("", "### Decisions", "", ...data.decisions.map((decision) => `- ${decision}`));
336
+ }
337
+ if (data.changedFiles?.length) {
338
+ sections.push("", "### Changed Files", "", ...data.changedFiles.map((file) => `- ${file}`));
339
+ }
340
+ if (data.evidence.length) {
341
+ sections.push("", "### Command Evidence", "");
342
+ for (const evidence of data.evidence) {
343
+ sections.push(`- \`${evidence.command}\` — exit code ${evidence.exitCode}`);
344
+ if (evidence.output) {
345
+ sections.push("", "```text", evidence.output, "```");
346
+ }
347
+ }
348
+ }
349
+ if (data.testCases?.length) {
350
+ sections.push("", "### Test Cases", "", "| ID | Scenario | Type | Expected |", "| --- | --- | --- | --- |");
351
+ sections.push(...data.testCases.map((testCase) => `| ${testCase.id} | ${testCase.scenario} | ${testCase.type} | ${testCase.expected} |`));
352
+ }
353
+ if (data.risks.length) {
354
+ sections.push("", "### Risks", "", ...data.risks.map((risk) => `- ${risk}`));
355
+ }
356
+ return `\n\n${sections.join("\n")}`;
357
+ }
358
+
359
+ function appendExecutionSections(artifact: ArtifactName, markdown: string, state: RunState, result?: RoleResult): string {
360
+ let content = markdown.trim();
361
+ if (result?.format === "structured" && !content.includes("## Structured Role Result")) {
362
+ content += structuredRoleResultBlock(result);
363
+ }
313
364
  if (artifact === "architecture-review" && state.architectureReview) {
314
- let content = markdown.trim();
315
365
  if (!content.includes("## Review Decision")) {
316
366
  content += `\n\n## Review Decision\n\nDecision: ${state.architectureReview.decision}\n\nSummary: ${state.architectureReview.summary}`;
317
367
  }
318
368
  return `${content}\n`;
319
369
  }
320
370
  if (artifact === "test-report") {
321
- let content = markdown.trim();
322
371
  if (!content.includes("## Acceptance Evidence")) {
323
372
  content += `\n\n## Acceptance Evidence\n\n${verificationBlock(state.verification)}`;
324
373
  }
@@ -330,7 +379,7 @@ function appendExecutionSections(artifact: ArtifactName, markdown: string, state
330
379
  }
331
380
  return `${content}\n`;
332
381
  }
333
- return markdown;
382
+ return `${content}\n`;
334
383
  }
335
384
 
336
385
  async function writeImplementationReview(state: RunState): Promise<void> {
@@ -451,11 +500,12 @@ async function runCurrentPhaseRole(state: RunState, runner: RoleRunner = runRole
451
500
  }
452
501
 
453
502
  if (state.phase === "review") {
454
- if (!result.reviewDecision) {
503
+ const reviewDecision = result.format === "structured" ? result.structured?.reviewDecision : result.reviewDecision;
504
+ if (!reviewDecision) {
455
505
  throw new Error("DevCrew architecture review must return a structured review decision");
456
506
  }
457
507
  state.architectureReview = {
458
- decision: result.reviewDecision,
508
+ decision: reviewDecision,
459
509
  summary: result.summary,
460
510
  reviewedAt: now(),
461
511
  };
@@ -464,12 +514,15 @@ async function runCurrentPhaseRole(state: RunState, runner: RoleRunner = runRole
464
514
  // When the backend cannot run a real SDK we keep a single deterministic
465
515
  // artifact source by rendering the rich phase template from the core layer.
466
516
  const baseMarkdown = result.usedFallback ? `${fallbackNotice(result)}${renderArtifact(artifact, state)}` : result.markdown;
467
- const markdown = appendExecutionSections(artifact, baseMarkdown, state);
517
+ const markdown = appendExecutionSections(artifact, baseMarkdown, state, result);
468
518
  state.roles.push({ ...result, markdown });
469
519
  state.artifacts[artifact] = await writeMarkdownArtifact(state, artifact, markdown);
470
520
  state.executionInstruction = undefined;
471
- if (state.phase === "requirements" && result.questions && result.questions.length > 0) {
472
- state.pendingQuestions = result.questions;
521
+ const questions = result.format === "structured"
522
+ ? result.structured?.questions?.map((question) => question.prompt) ?? []
523
+ : result.questions ?? [];
524
+ if (state.phase === "requirements" && questions.length > 0) {
525
+ state.pendingQuestions = questions;
473
526
  state.gates.requirements = "not_started";
474
527
  state.status = "awaiting_input";
475
528
  return saveState(state);
@@ -512,7 +565,8 @@ export async function continueOrchestratedWorkflow(input: RunRef, runner: RoleRu
512
565
  state.status === "awaiting_approval" ||
513
566
  state.status === "awaiting_input" ||
514
567
  state.status === "awaiting_execution" ||
515
- state.status === "complete"
568
+ state.status === "complete" ||
569
+ state.status === "aborted"
516
570
  ) {
517
571
  return state;
518
572
  }
@@ -552,8 +606,8 @@ export async function approveOrchestratedWorkflow(input: ApproveWorkflowInput):
552
606
  return cleanupAfterApproval(before);
553
607
  }
554
608
  if (promotingTesting) {
555
- if (before.verificationStatus === "failed" && !before.verificationWaiver) {
556
- throw new Error("Failed verification cannot be promoted without an explicit verification waiver");
609
+ if (before.verificationStatus !== "passed" && !before.verificationWaiver) {
610
+ throw new Error("Verification must pass before promotion or have an explicit verification waiver");
557
611
  }
558
612
  await promoteExecutionChanges(before);
559
613
  let approved: RunState;
@@ -627,7 +681,7 @@ export async function completeOrchestratedExecution(input: CompleteExecutionInpu
627
681
  await writeImplementationReview(state);
628
682
  const result = hostCompletionResult(state, summary);
629
683
  const artifact = artifactForPhase(state.phase);
630
- const markdown = appendExecutionSections(artifact, result.markdown, state);
684
+ const markdown = appendExecutionSections(artifact, result.markdown, state, result);
631
685
  state.roles.push({ ...result, markdown });
632
686
  state.artifacts[artifact] = await writeMarkdownArtifact(state, artifact, markdown);
633
687
  state.executionInstruction = undefined;
@@ -639,6 +693,33 @@ export async function rejectOrchestratedWorkflow(input: RejectWorkflowInput): Pr
639
693
  return rejectWorkflow(input);
640
694
  }
641
695
 
696
+ export async function abortOrchestratedWorkflow(input: AbortWorkflowInput): Promise<RunState> {
697
+ const state = await abortWorkflow(input);
698
+ if (!state.executionWorkspace) {
699
+ return state;
700
+ }
701
+ try {
702
+ await cleanupExecutionWorkspace(state);
703
+ } catch {
704
+ return state;
705
+ }
706
+ state.executionWorkspace = undefined;
707
+ return saveState(state);
708
+ }
709
+
710
+ export async function recoverOrchestratedWorkflow(input: RunRef): Promise<RunState> {
711
+ const state = await getWorkflowStatus(input);
712
+ if (state.status !== "aborted" && state.status !== "complete") {
713
+ throw new Error("Only terminal DevCrew runs can be recovered");
714
+ }
715
+ if (!state.executionWorkspace) {
716
+ return state;
717
+ }
718
+ await cleanupExecutionWorkspace(state);
719
+ state.executionWorkspace = undefined;
720
+ return saveState(state);
721
+ }
722
+
642
723
  export async function answerOrchestratedWorkflow(input: AnswerWorkflowInput, runner: RoleRunner = runRole): Promise<RunState> {
643
724
  const before = await getWorkflowStatus(input);
644
725
  const state = await answerWorkflow(input, { skipArtifactWrite: true });
@@ -652,6 +733,17 @@ export async function answerOrchestratedWorkflow(input: AnswerWorkflowInput, run
652
733
  state.gates.testing = "not_started";
653
734
  return saveState(state);
654
735
  }
736
+ if (
737
+ before.executionMode === "apply" &&
738
+ before.phase === "review" &&
739
+ before.gates["implementation-review"] === "rejected" &&
740
+ before.architectureReview?.decision === "changes_required"
741
+ ) {
742
+ state.phase = "execution";
743
+ state.status = "ready";
744
+ state.gates["implementation-review"] = "not_started";
745
+ return saveState(state);
746
+ }
655
747
 
656
748
  const role = roleForPhase(state.phase);
657
749
  if (!role) {
@@ -1,17 +1,22 @@
1
1
  import {
2
+ clearActiveRunIfMatches,
2
3
  getArtifact,
3
4
  getActiveRunId,
4
5
  getWorkflowStatus,
6
+ recoverRepositoryLock,
5
7
  setActiveRun,
8
+ withRepositoryLock,
6
9
  type Host,
7
10
  type RunState,
8
11
  } from "../../core/src/index.js";
9
12
  import {
13
+ abortOrchestratedWorkflow,
10
14
  answerOrchestratedWorkflow,
11
15
  approveOrchestratedWorkflow,
12
16
  completeOrchestratedExecution,
13
17
  continueOrchestratedWorkflow,
14
18
  rejectOrchestratedWorkflow,
19
+ recoverOrchestratedWorkflow,
15
20
  startOrchestratedWorkflow,
16
21
  waiveOrchestratedVerification,
17
22
  } from "../../orchestrator/src/index.js";
@@ -58,8 +63,24 @@ function withInferredHost(args: Record<string, unknown>): Record<string, unknown
58
63
  return { ...args, host: inferHost() };
59
64
  }
60
65
 
66
+ async function withMutationLock<T>(args: Record<string, unknown>, action: () => Promise<T>): Promise<T> {
67
+ if (typeof args.cwd !== "string" || !args.cwd.trim()) {
68
+ return action();
69
+ }
70
+ return withRepositoryLock(args.cwd, action);
71
+ }
72
+
61
73
  export function listDevCrewTools(): DevCrewTool[] {
62
74
  return [
75
+ {
76
+ name: "devcrew_abort",
77
+ description: "Abort a nonterminal run, preserve its audit evidence, and clean its isolated worktree when possible.",
78
+ inputSchema: {
79
+ type: "object",
80
+ required: ["cwd", "reason"],
81
+ properties: { cwd: cwdProperty, runId: runIdProperty, reason: { type: "string" } },
82
+ },
83
+ },
63
84
  {
64
85
  name: "devcrew_start",
65
86
  description: "Create a new gated DevCrew workflow run.",
@@ -85,6 +106,15 @@ export function listDevCrewTools(): DevCrewTool[] {
85
106
  },
86
107
  },
87
108
  },
109
+ {
110
+ name: "devcrew_recover",
111
+ description: "Explicitly clear a confirmed stale lock and retry cleanup for a terminal run without executing an agent.",
112
+ inputSchema: {
113
+ type: "object",
114
+ required: ["cwd"],
115
+ properties: { cwd: cwdProperty, runId: runIdProperty },
116
+ },
117
+ },
88
118
  {
89
119
  name: "devcrew_status",
90
120
  description: "Read the status of a DevCrew workflow run.",
@@ -190,7 +220,8 @@ function summarizeState(state: RunState): string {
190
220
  const role = state.roles.at(-1);
191
221
  const roleFallback =
192
222
  role?.usedFallback === true ? (role.backend === "local" ? "local" : "sdk") : role ? "none" : "none";
193
- return `Run ${state.runId}: phase=${state.phase}, status=${state.status}, execution_mode=${state.executionMode}, pending_gate=${pendingGate}, role_fallback=${roleFallback}`;
223
+ const roleFormat = role?.format ?? "none";
224
+ return `Run ${state.runId}: phase=${state.phase}, status=${state.status}, execution_mode=${state.executionMode}, pending_gate=${pendingGate}, role_fallback=${roleFallback}, role_format=${roleFormat}`;
194
225
  }
195
226
 
196
227
  function success(text: string, structuredContent?: Record<string, unknown>): ToolResult {
@@ -213,37 +244,76 @@ function failure(error: unknown): ToolResult {
213
244
  export async function callDevCrewTool(name: string, args: Record<string, unknown>): Promise<ToolResult> {
214
245
  try {
215
246
  if (name === "devcrew_start") {
216
- const state = await startOrchestratedWorkflow(withInferredHost(args) as never);
217
- await setActiveRun(state.cwd, state.runId);
218
- return success(`${summarizeState(state)}. Review ${state.artifacts.requirements}`, { state });
247
+ return await withMutationLock(args, async () => {
248
+ const state = await startOrchestratedWorkflow(withInferredHost(args) as never);
249
+ await setActiveRun(state.cwd, state.runId);
250
+ return success(`${summarizeState(state)}. Review ${state.artifacts.requirements}`, { state });
251
+ });
252
+ }
253
+ if (name === "devcrew_abort") {
254
+ return await withMutationLock(args, async () => {
255
+ const runArgs = await withActiveRun(args);
256
+ const state = await abortOrchestratedWorkflow(runArgs as never);
257
+ await clearActiveRunIfMatches(state.cwd, state.runId);
258
+ return success(`${summarizeState(state)}. Run aborted.`, { state });
259
+ });
260
+ }
261
+ if (name === "devcrew_recover") {
262
+ if (typeof args.cwd !== "string" || !args.cwd.trim()) {
263
+ throw new Error("cwd must be a non-empty string");
264
+ }
265
+ const recoveredLock = await recoverRepositoryLock(args.cwd);
266
+ if (typeof args.runId !== "string" || !args.runId.trim()) {
267
+ return success(
268
+ recoveredLock ? "Repository lock recovery completed." : "No stale repository lock was present.",
269
+ { recoveredLock },
270
+ );
271
+ }
272
+ return await withMutationLock(args, async () => {
273
+ const runArgs = await withActiveRun(args);
274
+ const state = await recoverOrchestratedWorkflow(runArgs as never);
275
+ return success(`${summarizeState(state)}. Recovery cleanup completed.`, { state });
276
+ });
219
277
  }
220
278
  if (name === "devcrew_status") {
221
279
  const state = await getWorkflowStatus((await withActiveRun(args)) as never);
222
280
  return success(summarizeState(state), { state });
223
281
  }
224
282
  if (name === "devcrew_answer") {
225
- const state = await answerOrchestratedWorkflow((await withActiveRun(args)) as never);
226
- return success(`${summarizeState(state)}. Answer recorded.`, { state });
283
+ return await withMutationLock(args, async () => {
284
+ const state = await answerOrchestratedWorkflow((await withActiveRun(args)) as never);
285
+ return success(`${summarizeState(state)}. Answer recorded.`, { state });
286
+ });
227
287
  }
228
288
  if (name === "devcrew_approve") {
229
- const state = await approveOrchestratedWorkflow((await withActiveRun(args)) as never);
230
- return success(`${summarizeState(state)}. Gate approved.`, { state });
289
+ return await withMutationLock(args, async () => {
290
+ const state = await approveOrchestratedWorkflow((await withActiveRun(args)) as never);
291
+ return success(`${summarizeState(state)}. Gate approved.`, { state });
292
+ });
231
293
  }
232
294
  if (name === "devcrew_reject") {
233
- const state = await rejectOrchestratedWorkflow((await withActiveRun(args)) as never);
234
- return success(`${summarizeState(state)}. Gate rejected.`, { state });
295
+ return await withMutationLock(args, async () => {
296
+ const state = await rejectOrchestratedWorkflow((await withActiveRun(args)) as never);
297
+ return success(`${summarizeState(state)}. Gate rejected.`, { state });
298
+ });
235
299
  }
236
300
  if (name === "devcrew_continue") {
237
- const state = await continueOrchestratedWorkflow((await withActiveRun(args)) as never);
238
- return success(`${summarizeState(state)}.`, { state });
301
+ return await withMutationLock(args, async () => {
302
+ const state = await continueOrchestratedWorkflow((await withActiveRun(args)) as never);
303
+ return success(`${summarizeState(state)}.`, { state });
304
+ });
239
305
  }
240
306
  if (name === "devcrew_complete_execution") {
241
- const state = await completeOrchestratedExecution((await withActiveRun(args)) as never);
242
- return success(`${summarizeState(state)}. Native host completion recorded.`, { state });
307
+ return await withMutationLock(args, async () => {
308
+ const state = await completeOrchestratedExecution((await withActiveRun(args)) as never);
309
+ return success(`${summarizeState(state)}. Native host completion recorded.`, { state });
310
+ });
243
311
  }
244
312
  if (name === "devcrew_waive_verification") {
245
- const state = await waiveOrchestratedVerification((await withActiveRun(args)) as never);
246
- return success(`${summarizeState(state)}. Verification waiver recorded.`, { state });
313
+ return await withMutationLock(args, async () => {
314
+ const state = await waiveOrchestratedVerification((await withActiveRun(args)) as never);
315
+ return success(`${summarizeState(state)}. Verification waiver recorded.`, { state });
316
+ });
247
317
  }
248
318
  if (name === "devcrew_artifact") {
249
319
  const artifact = await getArtifact((await withActiveRun(args)) as never);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devcrew",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "DevCrew gated multi-role workflow service for Codex.",
5
5
  "author": {
6
6
  "name": "DevCrew Contributors",
@@ -6,7 +6,7 @@
6
6
  "exec",
7
7
  "--silent",
8
8
  "--yes",
9
- "--package=@shenlee/devcrew@0.1.3",
9
+ "--package=@shenlee/devcrew@0.1.4",
10
10
  "--",
11
11
  "node",
12
12
  "-e",
@@ -298,7 +298,7 @@ async function main() {
298
298
  await client.request("initialize", {
299
299
  protocolVersion: "2025-03-26",
300
300
  capabilities: {},
301
- clientInfo: { name: "devcrew-codex-plugin-smoke", version: "0.1.3" },
301
+ clientInfo: { name: "devcrew-codex-plugin-smoke", version: "0.1.4" },
302
302
  });
303
303
  client.notify("notifications/initialized", {});
304
304
  const tools = await client.request("tools/list", {});