@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.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/packages/adapters/src/index.js +147 -6
- 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/superpowers/plans/2026-07-14-p0-remediation.md +213 -0
- package/docs/superpowers/plans/2026-07-14-reliability-recovery.md +208 -0
- package/docs/superpowers/plans/2026-07-14-structured-role-results.md +231 -0
- package/docs/superpowers/specs/2026-07-14-p0-remediation-design.md +52 -0
- package/docs/superpowers/specs/2026-07-14-reliability-recovery-design.md +92 -0
- package/docs/superpowers/specs/2026-07-14-structured-role-results-design.md +96 -0
- package/docs/workflow.md +11 -2
- package/package.json +1 -1
- package/packages/adapters/src/index.ts +168 -6
- 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
|
@@ -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.4` 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.4 -- 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.4`, 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.4` wrapper so the MCP service is locked to the published package version.
|
|
41
41
|
|
|
42
42
|
## 4. Run A Workflow
|
|
43
43
|
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# P0 Verification and Review Remediation Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Prevent unverified apply patches from being promoted and send architecture-review findings back through implementation.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Keep `verificationStatus` as the single source for testing-gate policy. Treat `failed` and `not_run` identically for blocking, but allow the existing persisted waiver to reopen either state. Reuse the existing testing-rejection recovery pattern for implementation-review findings, preserving the isolated worktree while returning the state machine to `execution/ready`.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript, Node.js test runner, Git worktrees, existing DevCrew MCP/orchestrator APIs.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
### Task 1: Block missing verification evidence
|
|
14
|
+
|
|
15
|
+
**Files:**
|
|
16
|
+
- Modify: `tests/orchestrator.test.ts:483-533`
|
|
17
|
+
- Modify: `packages/orchestrator/src/index.ts:241-253, 552-558`
|
|
18
|
+
- Modify: `packages/core/src/workflow.ts:297-314`
|
|
19
|
+
|
|
20
|
+
- [ ] **Step 1: Write failing no-verification tests**
|
|
21
|
+
|
|
22
|
+
Add a test after the failed-verification test that creates an apply fixture with
|
|
23
|
+
an empty `verifyCommands` list and no discoverable manifest. Advance it through
|
|
24
|
+
testing and assert the following:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
assert.equal(tested.verificationStatus, "not_run");
|
|
28
|
+
assert.equal(tested.gates.testing, "rejected");
|
|
29
|
+
assert.equal(tested.status, "awaiting_input");
|
|
30
|
+
await assert.rejects(
|
|
31
|
+
() => approveOrchestratedWorkflow({ cwd, runId: tested.runId, gate: "testing" }),
|
|
32
|
+
/not pending approval/i,
|
|
33
|
+
);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
In the same test, call `waiveOrchestratedVerification` with a non-empty reason,
|
|
37
|
+
assert `testing: pending` and `awaiting_approval`, then approve testing and
|
|
38
|
+
assert the reviewed file was promoted. Add a second assertion that directly
|
|
39
|
+
sets a persisted test fixture to `verificationStatus: "not_run"`, a pending
|
|
40
|
+
testing gate, and no waiver, then expects `approveOrchestratedWorkflow` to
|
|
41
|
+
throw `Verification must pass before promotion`.
|
|
42
|
+
|
|
43
|
+
- [ ] **Step 2: Run the focused test and verify RED**
|
|
44
|
+
|
|
45
|
+
Run:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
node --import tsx --test tests/orchestrator.test.ts
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Expected: the new test fails because `not_run` currently opens a pending gate
|
|
52
|
+
and the waiver operation rejects it.
|
|
53
|
+
|
|
54
|
+
- [ ] **Step 3: Implement the minimal verification policy**
|
|
55
|
+
|
|
56
|
+
In `setTestingGateFromVerification`, replace the `failed`-only branch with a
|
|
57
|
+
non-passed branch. Preserve the existing failed message and add a distinct
|
|
58
|
+
missing-evidence message:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
if (state.verificationStatus !== "passed") {
|
|
62
|
+
state.gates.testing = "rejected";
|
|
63
|
+
state.status = "awaiting_input";
|
|
64
|
+
state.feedback.push({
|
|
65
|
+
gate: "testing",
|
|
66
|
+
message: state.verificationStatus === "failed"
|
|
67
|
+
? "Automated verification failed. Inspect the test report, revise the implementation, or record an explicit verification waiver with its reason."
|
|
68
|
+
: "No verification evidence was recorded. Configure or run validation, revise the implementation, or record an explicit verification waiver with its reason.",
|
|
69
|
+
createdAt: now(),
|
|
70
|
+
});
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Update `waiveVerificationWorkflow` to accept either `failed` or `not_run` and
|
|
76
|
+
change its error message to mention failed or missing verification. In
|
|
77
|
+
`approveOrchestratedWorkflow`, replace the failed-only promotion check with:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
if (before.verificationStatus !== "passed" && !before.verificationWaiver) {
|
|
81
|
+
throw new Error("Verification must pass before promotion or have an explicit verification waiver");
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- [ ] **Step 4: Run focused tests and verify GREEN**
|
|
86
|
+
|
|
87
|
+
Run:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
node --import tsx --test tests/orchestrator.test.ts
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Expected: all orchestrator tests pass, including failed and missing
|
|
94
|
+
verification waiver paths.
|
|
95
|
+
|
|
96
|
+
- [ ] **Step 5: Commit the verification fix**
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
git add packages/core/src/workflow.ts packages/orchestrator/src/index.ts tests/orchestrator.test.ts
|
|
100
|
+
git commit -m "fix: block promotion without verification evidence"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Task 2: Return review findings to execution
|
|
104
|
+
|
|
105
|
+
**Files:**
|
|
106
|
+
- Modify: `tests/orchestrator.test.ts:365-421`
|
|
107
|
+
- Modify: `packages/orchestrator/src/index.ts:639-658`
|
|
108
|
+
- Modify: `docs/workflow.md:45-49`
|
|
109
|
+
|
|
110
|
+
- [ ] **Step 1: Write the failing review-remediation test**
|
|
111
|
+
|
|
112
|
+
Extend `architecture review blocks testing when it requires changes` after its
|
|
113
|
+
current rejection assertions. Call `answerOrchestratedWorkflow` with a
|
|
114
|
+
non-empty answer and assert:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
assert.equal(revised.phase, "execution");
|
|
118
|
+
assert.equal(revised.status, "ready");
|
|
119
|
+
assert.equal(revised.gates["implementation-review"], "not_started");
|
|
120
|
+
assert.equal(revised.executionWorkspace?.path, workspacePath);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Then call `continueOrchestratedWorkflow` with a runner that records its phase
|
|
124
|
+
and assert the next executed role has `phase === "execution"` and
|
|
125
|
+
`role === "implementer"`, not `review`/`architect`.
|
|
126
|
+
|
|
127
|
+
- [ ] **Step 2: Run the focused test and verify RED**
|
|
128
|
+
|
|
129
|
+
Run:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
node --import tsx --test tests/orchestrator.test.ts
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Expected: the new assertions fail because `devcrew_answer` currently re-runs
|
|
136
|
+
the review phase.
|
|
137
|
+
|
|
138
|
+
- [ ] **Step 3: Implement the remediation transition**
|
|
139
|
+
|
|
140
|
+
Add this branch in `answerOrchestratedWorkflow` immediately after the existing
|
|
141
|
+
testing-rejection branch:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
if (
|
|
145
|
+
before.executionMode === "apply" &&
|
|
146
|
+
before.phase === "review" &&
|
|
147
|
+
before.gates["implementation-review"] === "rejected" &&
|
|
148
|
+
before.architectureReview?.decision === "changes_required"
|
|
149
|
+
) {
|
|
150
|
+
state.phase = "execution";
|
|
151
|
+
state.status = "ready";
|
|
152
|
+
state.gates["implementation-review"] = "not_started";
|
|
153
|
+
return saveState(state);
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Do not clear `executionWorkspace`, `architectureReview`, feedback, or the
|
|
158
|
+
prior review artifact; they remain audit evidence and the next execution phase
|
|
159
|
+
will refresh the captured diff and create a new review.
|
|
160
|
+
|
|
161
|
+
- [ ] **Step 4: Update the workflow documentation**
|
|
162
|
+
|
|
163
|
+
Replace the current claim that feedback must be addressed before another review
|
|
164
|
+
with text stating that `devcrew_answer` returns the run to the isolated
|
|
165
|
+
`execution/ready` state and a later `devcrew_continue` performs the revised
|
|
166
|
+
implementation before a fresh architect review.
|
|
167
|
+
|
|
168
|
+
- [ ] **Step 5: Run focused tests and verify GREEN**
|
|
169
|
+
|
|
170
|
+
Run:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
node --import tsx --test tests/orchestrator.test.ts
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Expected: all orchestrator tests pass and the remediation test observes an
|
|
177
|
+
implementer execution before a subsequent review.
|
|
178
|
+
|
|
179
|
+
- [ ] **Step 6: Commit the remediation fix**
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
git add docs/workflow.md packages/orchestrator/src/index.ts tests/orchestrator.test.ts
|
|
183
|
+
git commit -m "fix: return architecture findings to execution"
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Task 3: Full verification
|
|
187
|
+
|
|
188
|
+
**Files:**
|
|
189
|
+
- Verify: `packages/core/src/workflow.ts`
|
|
190
|
+
- Verify: `packages/orchestrator/src/index.ts`
|
|
191
|
+
- Verify: `tests/orchestrator.test.ts`
|
|
192
|
+
|
|
193
|
+
- [ ] **Step 1: Run the complete repository validation**
|
|
194
|
+
|
|
195
|
+
Run:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
npm run validate
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Expected: TypeScript compilation succeeds and every test passes.
|
|
202
|
+
|
|
203
|
+
- [ ] **Step 2: Inspect the final diff**
|
|
204
|
+
|
|
205
|
+
Run:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
git diff main...HEAD --check
|
|
209
|
+
git status --short
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Expected: no whitespace errors; only the two implementation commits and this
|
|
213
|
+
plan/spec commits are present on the branch.
|