@shenlee/devcrew 0.1.1 → 0.1.3

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 (48) hide show
  1. package/README.md +30 -8
  2. package/README.zh-CN.md +27 -8
  3. package/dist/packages/adapters/src/index.js +62 -8
  4. package/dist/packages/core/src/artifacts.js +14 -4
  5. package/dist/packages/core/src/config.js +1 -1
  6. package/dist/packages/core/src/paths.js +10 -6
  7. package/dist/packages/core/src/store.js +36 -0
  8. package/dist/packages/core/src/types.js +5 -1
  9. package/dist/packages/core/src/validation.js +7 -1
  10. package/dist/packages/core/src/version.js +1 -1
  11. package/dist/packages/core/src/workflow.js +120 -31
  12. package/dist/packages/orchestrator/src/index.js +339 -160
  13. package/dist/packages/orchestrator/src/worktree.js +198 -0
  14. package/dist/packages/plugins/src/index.js +2 -32
  15. package/dist/packages/service/src/stdio.js +19 -3
  16. package/dist/packages/service/src/tools.js +45 -6
  17. package/docs/claude-code.md +5 -3
  18. package/docs/codex.md +12 -4
  19. package/docs/quickstart.md +1 -1
  20. package/docs/superpowers/plans/2026-07-10-p0-foundation-repair.md +939 -0
  21. package/docs/superpowers/plans/2026-07-13-safety-semantics.md +313 -0
  22. package/docs/superpowers/specs/2026-07-10-p0-foundation-repair-design.md +182 -0
  23. package/docs/superpowers/specs/2026-07-13-execution-boundaries-design.md +126 -0
  24. package/docs/workflow.md +36 -4
  25. package/package.json +1 -1
  26. package/packages/adapters/src/index.ts +87 -11
  27. package/packages/core/src/artifacts.ts +16 -4
  28. package/packages/core/src/config.ts +1 -1
  29. package/packages/core/src/paths.ts +11 -6
  30. package/packages/core/src/store.ts +41 -1
  31. package/packages/core/src/types.ts +53 -2
  32. package/packages/core/src/validation.ts +10 -0
  33. package/packages/core/src/version.ts +1 -1
  34. package/packages/core/src/workflow.ts +136 -30
  35. package/packages/orchestrator/src/index.ts +377 -182
  36. package/packages/orchestrator/src/worktree.ts +269 -0
  37. package/packages/plugins/src/index.ts +2 -44
  38. package/packages/service/src/stdio.ts +27 -6
  39. package/packages/service/src/tools.ts +46 -5
  40. package/plugins/devcrew-codex/.codex-plugin/plugin.json +1 -1
  41. package/plugins/devcrew-codex/.mcp.json +1 -1
  42. package/plugins/devcrew-codex/assets/logo.png +0 -0
  43. package/plugins/devcrew-codex/skills/devcrew/SKILL.md +8 -7
  44. package/scripts/smoke-codex-plugin.mjs +1 -1
  45. package/plugins/devcrew-codex/agents/architect.toml +0 -6
  46. package/plugins/devcrew-codex/agents/implementer.toml +0 -6
  47. package/plugins/devcrew-codex/agents/pm.toml +0 -6
  48. package/plugins/devcrew-codex/agents/tester.toml +0 -6
@@ -0,0 +1,198 @@
1
+ import { spawn } from "node:child_process";
2
+ import { access, mkdir, rm, writeFile } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+ import { executionWorktreePath, runDir, } from "../../core/src/index.js";
5
+ const REPOSITORY_PATHS = [
6
+ ".",
7
+ ":(exclude).devcrew",
8
+ ":(exclude).devcrew/**",
9
+ ":(exclude)docs/devcrew",
10
+ ":(exclude)docs/devcrew/**",
11
+ ];
12
+ async function runGit(args, cwd, stdin, env) {
13
+ return new Promise((resolveResult, rejectResult) => {
14
+ const child = spawn("git", args, { cwd, env, stdio: ["pipe", "pipe", "pipe"] });
15
+ let stdout = "";
16
+ let stderr = "";
17
+ let settled = false;
18
+ const rejectOnce = (error) => {
19
+ if (settled) {
20
+ return;
21
+ }
22
+ settled = true;
23
+ rejectResult(error);
24
+ };
25
+ child.stdout.on("data", (chunk) => {
26
+ stdout += chunk.toString("utf8");
27
+ });
28
+ child.stderr.on("data", (chunk) => {
29
+ stderr += chunk.toString("utf8");
30
+ });
31
+ child.on("error", rejectOnce);
32
+ child.on("close", (code) => {
33
+ if (settled) {
34
+ return;
35
+ }
36
+ settled = true;
37
+ if (code === 0) {
38
+ resolveResult(stdout);
39
+ return;
40
+ }
41
+ rejectResult(new Error(`git ${args[0]} failed: ${(stderr || stdout).trim()}`));
42
+ });
43
+ child.stdin.end(stdin);
44
+ });
45
+ }
46
+ async function pathExists(path) {
47
+ try {
48
+ await access(path);
49
+ return true;
50
+ }
51
+ catch {
52
+ return false;
53
+ }
54
+ }
55
+ async function assertCleanRequester(cwd) {
56
+ const status = await runGit([
57
+ "status",
58
+ "--porcelain",
59
+ "-uall",
60
+ "--",
61
+ ...REPOSITORY_PATHS,
62
+ ], cwd);
63
+ if (status.trim()) {
64
+ throw new Error("DevCrew apply promotion requires a clean working tree");
65
+ }
66
+ }
67
+ function nulPaths(output) {
68
+ return output.split("\0").filter(Boolean);
69
+ }
70
+ function parseChangedFiles(output) {
71
+ const fields = nulPaths(output);
72
+ const paths = [];
73
+ for (let index = 0; index < fields.length;) {
74
+ const status = fields[index++];
75
+ const firstPath = fields[index++];
76
+ if (!status || !firstPath) {
77
+ throw new Error("DevCrew could not parse git diff name status output");
78
+ }
79
+ paths.push(firstPath);
80
+ if (status.startsWith("R") || status.startsWith("C")) {
81
+ const secondPath = fields[index++];
82
+ if (!secondPath) {
83
+ throw new Error("DevCrew could not parse renamed git diff path");
84
+ }
85
+ paths.push(secondPath);
86
+ }
87
+ }
88
+ return [...new Set(paths)];
89
+ }
90
+ async function markUntrackedIntentToAdd(cwd, env) {
91
+ const untracked = nulPaths(await runGit(["ls-files", "--others", "--exclude-standard", "-z", "--", ...REPOSITORY_PATHS], cwd, undefined, env));
92
+ if (untracked.length > 0) {
93
+ await runGit(["add", "-N", "--", ...untracked], cwd, undefined, env);
94
+ }
95
+ }
96
+ async function capturePatch(workspace, options = {}) {
97
+ await markUntrackedIntentToAdd(workspace.path, options.env);
98
+ const changedFiles = parseChangedFiles(await runGit(["diff", "--name-status", "-z", workspace.baseCommit, "--", ...REPOSITORY_PATHS], workspace.path, undefined, options.env));
99
+ const patch = await runGit(["diff", "--binary", "--no-ext-diff", workspace.baseCommit, "--", ...REPOSITORY_PATHS], workspace.path, undefined, options.env);
100
+ if (!options.allowEmpty && !patch.trim()) {
101
+ throw new Error("DevCrew apply implementer produced no repository changes");
102
+ }
103
+ return { changedFiles, patch };
104
+ }
105
+ async function captureRequesterChanges(state, workspace) {
106
+ const indexPath = join(runDir(state.cwd, state.runId), "promotion.index");
107
+ await mkdir(dirname(indexPath), { recursive: true });
108
+ await rm(indexPath, { force: true });
109
+ const env = { ...process.env, GIT_INDEX_FILE: indexPath };
110
+ try {
111
+ await runGit(["read-tree", workspace.baseCommit], state.cwd, undefined, env);
112
+ return await capturePatch({ path: state.cwd, baseCommit: workspace.baseCommit }, { allowEmpty: true, env });
113
+ }
114
+ finally {
115
+ await rm(indexPath, { force: true });
116
+ }
117
+ }
118
+ export async function ensureExecutionWorkspace(state) {
119
+ if (state.executionWorkspace && (await pathExists(state.executionWorkspace.path))) {
120
+ return state.executionWorkspace;
121
+ }
122
+ await assertCleanRequester(state.cwd);
123
+ const baseCommit = (await runGit(["rev-parse", "HEAD"], state.cwd)).trim();
124
+ const path = executionWorktreePath(state.cwd, state.runId);
125
+ await mkdir(dirname(path), { recursive: true });
126
+ await runGit(["worktree", "add", "--detach", path, baseCommit], state.cwd);
127
+ return { path, baseCommit };
128
+ }
129
+ export async function captureExecutionChanges(workspace) {
130
+ const head = (await runGit(["rev-parse", "HEAD"], workspace.path)).trim();
131
+ if (head !== workspace.baseCommit) {
132
+ await runGit(["reset", "--mixed", workspace.baseCommit], workspace.path);
133
+ }
134
+ return capturePatch(workspace);
135
+ }
136
+ export async function promoteExecutionChanges(state) {
137
+ const workspace = state.executionWorkspace;
138
+ if (!workspace || !state.implementationDiff.trim()) {
139
+ throw new Error("DevCrew apply promotion requires reviewed execution changes");
140
+ }
141
+ const requesterHead = (await runGit(["rev-parse", "HEAD"], state.cwd)).trim();
142
+ if (requesterHead !== workspace.baseCommit) {
143
+ throw new Error("DevCrew apply promotion refused because requester HEAD changed");
144
+ }
145
+ const patchPath = join(runDir(state.cwd, state.runId), "implementation.patch");
146
+ await mkdir(dirname(patchPath), { recursive: true });
147
+ await writeFile(patchPath, state.implementationDiff);
148
+ const current = await captureRequesterChanges(state, workspace);
149
+ if (current.patch === state.implementationDiff) {
150
+ return;
151
+ }
152
+ if (current.patch.trim()) {
153
+ throw new Error("DevCrew apply promotion requires a clean working tree");
154
+ }
155
+ await assertCleanRequester(state.cwd);
156
+ await runGit(["apply", "--check", "--binary", patchPath], state.cwd);
157
+ await runGit(["apply", "--binary", patchPath], state.cwd);
158
+ const promoted = await captureRequesterChanges(state, workspace);
159
+ if (promoted.patch !== state.implementationDiff) {
160
+ try {
161
+ await runGit(["apply", "--reverse", "--binary", patchPath], state.cwd);
162
+ }
163
+ catch (error) {
164
+ const detail = error instanceof Error ? `: ${error.message}` : "";
165
+ throw new Error(`DevCrew promoted patch verification failed and rollback failed${detail}`);
166
+ }
167
+ throw new Error("DevCrew promoted patch differs from the reviewed implementation diff");
168
+ }
169
+ }
170
+ export async function rollbackPromotedExecutionChanges(state) {
171
+ const workspace = state.executionWorkspace;
172
+ if (!workspace || !state.implementationDiff.trim()) {
173
+ throw new Error("DevCrew apply rollback requires reviewed execution changes");
174
+ }
175
+ const patchPath = join(runDir(state.cwd, state.runId), "implementation.patch");
176
+ await runGit(["apply", "--reverse", "--check", "--binary", patchPath], state.cwd);
177
+ await runGit(["apply", "--reverse", "--binary", patchPath], state.cwd);
178
+ const remaining = await captureRequesterChanges(state, workspace);
179
+ if (remaining.patch.trim()) {
180
+ throw new Error("DevCrew apply rollback left requester repository changes");
181
+ }
182
+ }
183
+ export async function cleanupExecutionWorkspace(state) {
184
+ const workspace = state.executionWorkspace;
185
+ if (!workspace) {
186
+ return;
187
+ }
188
+ if (await pathExists(workspace.path)) {
189
+ await runGit(["worktree", "remove", "--force", workspace.path], state.cwd);
190
+ }
191
+ await runGit(["worktree", "prune"], state.cwd);
192
+ }
193
+ export async function executionCwd(state) {
194
+ if (state.executionMode !== "apply") {
195
+ return state.cwd;
196
+ }
197
+ return (await ensureExecutionWorkspace(state)).path;
198
+ }
@@ -2,7 +2,7 @@ import { constants } from "node:fs";
2
2
  import { access, copyFile, mkdir, writeFile } from "node:fs/promises";
3
3
  import { dirname, join } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { DEFAULT_CONFIG, DEVCREW_NPM_PACKAGE, DEVCREW_VERSION, ROLE_SECTIONS } from "../../core/src/index.js";
5
+ import { DEFAULT_CONFIG, DEVCREW_NPM_PACKAGE, DEVCREW_VERSION } from "../../core/src/index.js";
6
6
  async function writeJson(path, value) {
7
7
  await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
8
8
  }
@@ -29,36 +29,8 @@ async function writeCodexAssets(pluginRoot) {
29
29
  await copyFile(await bundledAssetPath("logo.png"), join(assetDir, "logo.png"));
30
30
  await copyFile(await bundledAssetPath("composer-icon.png"), join(assetDir, "composer-icon.png"));
31
31
  }
32
- function roleExpectations(name) {
33
- const sections = ROLE_SECTIONS[name];
34
- if (!sections || sections.length === 0) {
35
- return "";
36
- }
37
- return sections.map((s) => `- ${s.heading} (${s.description})`).join("\n");
38
- }
39
- async function writeRoleAgents(root, format) {
40
- const roles = [
41
- ["pm", "Product manager. Clarifies requirements, scope boundaries, success criteria, and requester approvals."],
42
- ["architect", "technical architecture specialist. Designs implementation, deployment, interfaces, and review criteria."],
43
- ["implementer", "Implementation engineer. Writes code according to approved architecture and discovered standards."],
44
- ["tester", "Testing and acceptance specialist. Verifies functionality, regressions, and acceptance evidence."],
45
- ];
46
- if (format === "claude") {
47
- const agentDir = join(root, "agents");
48
- await mkdir(agentDir, { recursive: true });
49
- for (const [name, description] of roles) {
50
- await writeFile(join(agentDir, `${name}.md`), `---\nname: ${name}\ndescription: ${description}\ntools: Read, Grep, Glob, Bash\n---\n\nYou are the DevCrew ${name} role. ${description} Return concise Markdown and keep inherited host permissions.\n\nProduce these required sections:\n\n${roleExpectations(name)}\n`, "utf8");
51
- }
52
- return;
53
- }
54
- const agentDir = join(root, "agents");
55
- await mkdir(agentDir, { recursive: true });
56
- for (const [name, description] of roles) {
57
- await writeFile(join(agentDir, `${name}.toml`), `name = "${name}"\ndescription = "${description}"\ndeveloper_instructions = """\nYou are the DevCrew ${name} role. ${description}\nReturn concise Markdown and keep inherited host permissions.\n\nProduce these required sections:\n${roleExpectations(name)}\n"""\n`, "utf8");
58
- }
59
- }
60
32
  function entrySkill() {
61
- return `---\nname: devcrew\ndescription: Run the DevCrew PM -> architecture -> implementation -> testing workflow. Use when the user asks for structured feature or product development, requirements clarification, architecture review, implementation planning, testing acceptance, or Chinese requests such as 完整研发流程, 需求澄清, 产品经理, 架构师, 开发测试流程.\n---\n\nUse the DevCrew MCP tools to manage the workflow:\n\n1. Start with \`devcrew_start\` using the current repository cwd, mode, request, and optional executionMode. Host is inferred from the plugin's \`DEVCREW_HOST\`; pass host only for an explicit override. Omit executionMode unless the requester explicitly asks DevCrew to apply changes; the default safe mode is \`plan\`.\n2. After start, DevCrew records the active run for this repository. For follow-up tools, omit runId unless you need to target a different run explicitly.\n3. Use \`executionMode: "apply"\` only when the requester explicitly wants DevCrew to write code or run validation commands. This still inherits host sandbox, approval, and tool permissions.\n4. Use \`devcrew_status\` to show the current phase and pending gate.\n5. Use \`devcrew_answer\` when the requester gives clarification.\n6. Use \`devcrew_approve\` or \`devcrew_reject\` for each gate.\n7. Use \`devcrew_continue\` after approvals. This executes the next phase role, writes the phase artifact, and opens the next gate. The implementation phase also writes \`implementation-review\` for diff and architecture compliance review.\n8. Use \`devcrew_artifact\` to read generated requirements, architecture, implementation-plan, implementation-review, test-report, or acceptance files.\n\nDo not bypass host sandbox, approval, or tool permissions.\n`;
33
+ return `---\nname: devcrew\ndescription: Run the DevCrew PM -> architecture -> implementation -> testing workflow. Use when the user asks for structured feature or product development, requirements clarification, architecture review, implementation planning, testing acceptance, or Chinese requests such as 完整研发流程, 需求澄清, 产品经理, 架构师, 开发测试流程.\n---\n\nUse the DevCrew MCP tools to manage the workflow:\n\n1. Start with \`devcrew_start\` using the current repository cwd, mode, request, and optional \`executionMode\`. Host is inferred from the plugin's \`DEVCREW_HOST\`; pass host only for an explicit override. Omit \`executionMode\` unless the requester explicitly asks DevCrew to apply changes; the default safe mode is \`plan\`.\n2. After start, DevCrew records the active run for this repository. For follow-up tools, omit \`runId\` unless you need to target a different run explicitly.\n3. For \`executionMode: "apply"\`, choose an explicit \`executionPolicy\`. The default \`interactive-host\` pauses at implementation and testing for the host's native agent to work in DevCrew's isolated worktree. \`headless-restricted\` and \`headless-unattended\` are DevCrew SDK policies; they do not inherit the current host approval session.\n4. Use \`devcrew_status\` to show the current phase, pending gate, and any execution instruction.\n5. Use \`devcrew_answer\` when the requester gives clarification.\n6. Use \`devcrew_approve\` or \`devcrew_reject\` for each gate.\n7. Use \`devcrew_continue\` after approvals. Apply runs enter an \`implementation-review\` gate after execution: review the architect's \`architecture-review\` artifact before testing. For \`interactive-host\`, if status becomes \`awaiting_execution\`, perform the native-host work in the indicated worktree then call \`devcrew_complete_execution\`. For testing, include command, exit code, output, startedAt, and completedAt evidence.\n8. Failed verification is not approvable. Revise through \`devcrew_answer\`, or use \`devcrew_waive_verification\` only when the requester explicitly accepts the recorded risk and provides a reason.\n9. Use \`devcrew_artifact\` to read generated requirements, architecture, implementation-plan, implementation-review, architecture-review, test-report, or acceptance files.\n\nNever describe a nested SDK session as inheriting the current host's approval decisions.\n`;
62
34
  }
63
35
  function npmPackageSpecifier() {
64
36
  return `${DEVCREW_NPM_PACKAGE}@${DEVCREW_VERSION}`;
@@ -136,7 +108,6 @@ export async function generateCodexPlugin(root) {
136
108
  devcrew: mcpServerConfig("codex"),
137
109
  },
138
110
  });
139
- await writeRoleAgents(pluginRoot, "codex");
140
111
  return { name: "devcrew", path: pluginRoot };
141
112
  }
142
113
  export async function generateCodexMarketplace(root) {
@@ -180,7 +151,6 @@ export async function generateClaudePlugin(root) {
180
151
  devcrew: mcpServerConfig("claude"),
181
152
  },
182
153
  });
183
- await writeRoleAgents(pluginRoot, "claude");
184
154
  return { name: "devcrew", path: pluginRoot };
185
155
  }
186
156
  export async function initProject(root) {
@@ -1,5 +1,12 @@
1
1
  import { callDevCrewTool, listDevCrewTools } from "./tools.js";
2
2
  import { DEVCREW_VERSION } from "../../core/src/index.js";
3
+ function isJsonRpcRequest(value) {
4
+ return (typeof value === "object" &&
5
+ value !== null &&
6
+ !Array.isArray(value) &&
7
+ value.jsonrpc === "2.0" &&
8
+ typeof value.method === "string");
9
+ }
3
10
  function writeJson(message) {
4
11
  process.stdout.write(`${JSON.stringify(message)}\n`);
5
12
  }
@@ -45,7 +52,7 @@ async function handleRequest(request, write) {
45
52
  });
46
53
  return;
47
54
  }
48
- throw new Error(`Unsupported JSON-RPC method: ${request.method ?? "unknown"}`);
55
+ throw new Error(`Unsupported JSON-RPC method: ${request.method}`);
49
56
  }
50
57
  catch (error) {
51
58
  write({
@@ -77,8 +84,17 @@ export function createStdioLineProcessor(write = writeJson, handler = (request)
77
84
  });
78
85
  return queue;
79
86
  }
80
- queue = queue.then(() => handler(request));
81
- return queue;
87
+ if (!isJsonRpcRequest(request)) {
88
+ write({
89
+ jsonrpc: "2.0",
90
+ id: null,
91
+ error: { code: -32600, message: "Invalid Request" },
92
+ });
93
+ return queue;
94
+ }
95
+ const task = queue.then(() => handler(request));
96
+ queue = task.catch(() => undefined);
97
+ return task;
82
98
  };
83
99
  }
84
100
  export function runStdioServer() {
@@ -1,5 +1,5 @@
1
- import { approveWorkflow, getArtifact, getActiveRunId, getWorkflowStatus, setActiveRun, } from "../../core/src/index.js";
2
- import { answerOrchestratedWorkflow, continueOrchestratedWorkflow, rejectOrchestratedWorkflow, startOrchestratedWorkflow, } from "../../orchestrator/src/index.js";
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";
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"];
@@ -39,6 +39,11 @@ export function listDevCrewTools() {
39
39
  enum: ["plan", "apply"],
40
40
  description: "Execution mode. Defaults to plan; apply must be explicit.",
41
41
  },
42
+ executionPolicy: {
43
+ type: "string",
44
+ enum: ["interactive-host", "headless-restricted", "headless-unattended"],
45
+ description: "Apply execution policy. Defaults to interactive-host; headless policies are explicit DevCrew SDK policies.",
46
+ },
42
47
  request: { type: "string" },
43
48
  backend: { type: "string", enum: ["codex", "claude", "local"] },
44
49
  },
@@ -71,7 +76,7 @@ export function listDevCrewTools() {
71
76
  properties: {
72
77
  cwd: cwdProperty,
73
78
  runId: runIdProperty,
74
- gate: { type: "string", enum: ["requirements", "architecture", "implementation", "testing"] },
79
+ gate: { type: "string", enum: ["requirements", "architecture", "implementation", "implementation-review", "testing"] },
75
80
  note: { type: "string" },
76
81
  },
77
82
  },
@@ -85,7 +90,7 @@ export function listDevCrewTools() {
85
90
  properties: {
86
91
  cwd: cwdProperty,
87
92
  runId: runIdProperty,
88
- gate: { type: "string", enum: ["requirements", "architecture", "implementation", "testing"] },
93
+ gate: { type: "string", enum: ["requirements", "architecture", "implementation", "implementation-review", "testing"] },
89
94
  feedback: { type: "string" },
90
95
  },
91
96
  },
@@ -99,6 +104,32 @@ export function listDevCrewTools() {
99
104
  properties: { cwd: cwdProperty, runId: runIdProperty },
100
105
  },
101
106
  },
107
+ {
108
+ name: "devcrew_complete_execution",
109
+ description: "Record completion by the native host for an interactive-host execution or testing step.",
110
+ inputSchema: {
111
+ type: "object",
112
+ required: ["cwd", "summary"],
113
+ properties: {
114
+ cwd: cwdProperty,
115
+ runId: runIdProperty,
116
+ summary: { type: "string" },
117
+ verification: {
118
+ type: "array",
119
+ description: "Required only when completing testing: command, exitCode, output, startedAt, and completedAt for each validation command.",
120
+ },
121
+ },
122
+ },
123
+ },
124
+ {
125
+ name: "devcrew_waive_verification",
126
+ description: "Record a reasoned risk waiver after failed apply-mode verification, then reopen the testing gate for approval.",
127
+ inputSchema: {
128
+ type: "object",
129
+ required: ["cwd", "reason"],
130
+ properties: { cwd: cwdProperty, runId: runIdProperty, reason: { type: "string" } },
131
+ },
132
+ },
102
133
  {
103
134
  name: "devcrew_artifact",
104
135
  description: "Read a generated workflow artifact.",
@@ -110,7 +141,7 @@ export function listDevCrewTools() {
110
141
  runId: runIdProperty,
111
142
  name: {
112
143
  type: "string",
113
- enum: ["requirements", "architecture", "implementation-plan", "implementation-review", "test-report", "acceptance"],
144
+ enum: ["requirements", "architecture", "implementation-plan", "implementation-review", "architecture-review", "test-report", "acceptance"],
114
145
  },
115
146
  },
116
147
  },
@@ -154,7 +185,7 @@ export async function callDevCrewTool(name, args) {
154
185
  return success(`${summarizeState(state)}. Answer recorded.`, { state });
155
186
  }
156
187
  if (name === "devcrew_approve") {
157
- const state = await approveWorkflow((await withActiveRun(args)));
188
+ const state = await approveOrchestratedWorkflow((await withActiveRun(args)));
158
189
  return success(`${summarizeState(state)}. Gate approved.`, { state });
159
190
  }
160
191
  if (name === "devcrew_reject") {
@@ -165,6 +196,14 @@ export async function callDevCrewTool(name, args) {
165
196
  const state = await continueOrchestratedWorkflow((await withActiveRun(args)));
166
197
  return success(`${summarizeState(state)}.`, { state });
167
198
  }
199
+ if (name === "devcrew_complete_execution") {
200
+ const state = await completeOrchestratedExecution((await withActiveRun(args)));
201
+ return success(`${summarizeState(state)}. Native host completion recorded.`, { state });
202
+ }
203
+ if (name === "devcrew_waive_verification") {
204
+ const state = await waiveOrchestratedVerification((await withActiveRun(args)));
205
+ return success(`${summarizeState(state)}. Verification waiver recorded.`, { state });
206
+ }
168
207
  if (name === "devcrew_artifact") {
169
208
  const artifact = await getArtifact((await withActiveRun(args)));
170
209
  return success(artifact.content, { artifact });
@@ -17,18 +17,20 @@ It contains:
17
17
 
18
18
  - `.claude-plugin/plugin.json`
19
19
  - `skills/devcrew/SKILL.md`
20
- - `agents/*.md`
21
20
  - `.mcp.json`
22
21
 
22
+ Role behavior is defined by the DevCrew MCP service and its shared runtime role
23
+ schema. The plugin intentionally does not bundle inactive subagent templates.
24
+
23
25
  For local plugin testing:
24
26
 
25
27
  ```bash
26
28
  claude --plugin-dir plugins/devcrew-claude
27
29
  ```
28
30
 
29
- 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.1` wrapper.
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.
30
32
 
31
- Claude Code permissions, hooks, and approval settings remain authoritative. DevCrew inherits the host boundary.
33
+ 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.
32
34
 
33
35
  For apply mode, `@anthropic-ai/claude-agent-sdk` must be resolvable from the installed DevCrew package. Published DevCrew packages declare it as an optional dependency, which npm installs by default. If `devcrew doctor` reports it as missing, reinstall DevCrew with optional dependencies enabled:
34
36
 
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.1 -- node -e "<DevCrew CLI wrapper>" -- serve --stdio
16
+ npm exec --silent --yes --package=@shenlee/devcrew@0.1.3 -- 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.
@@ -38,7 +38,9 @@ It contains:
38
38
  - `.codex-plugin/plugin.json`
39
39
  - `skills/devcrew/SKILL.md`
40
40
  - `.mcp.json`
41
- - role agent TOML templates under `agents/`
41
+
42
+ Role behavior is defined by the DevCrew MCP service and its shared runtime role
43
+ schema. The plugin intentionally does not bundle inactive subagent templates.
42
44
 
43
45
  For local development, point a Codex marketplace entry at the generated plugin folder, or copy the plugin into your existing local marketplace workflow.
44
46
 
@@ -50,9 +52,15 @@ The DevCrew skill tells Codex to use these MCP tools:
50
52
  - `devcrew_approve`
51
53
  - `devcrew_reject`
52
54
  - `devcrew_continue`
55
+ - `devcrew_complete_execution`
56
+ - `devcrew_waive_verification`
53
57
  - `devcrew_artifact`
54
58
 
55
- Codex sandbox and approval settings remain authoritative. DevCrew does not bypass them.
59
+ The default apply policy is `interactive-host`: Codex performs the implementation and testing with its native controls. A DevCrew nested SDK is not used in this path. Explicit `headless-restricted` and `headless-unattended` policies are separate DevCrew policies and do not inherit the current Codex approval session.
60
+
61
+ Apply mode keeps implementation planning read-only. With `interactive-host`, after the implementation gate is approved, call `devcrew_continue`; DevCrew creates `.devcrew/worktrees/<run-id>` and waits at `awaiting_execution`. Use Codex natively in that worktree, then call `devcrew_complete_execution`. Repeat for testing and include command, exit-code, and output evidence. A failed verification cannot open the testing gate; revise it or record an explicit reason through `devcrew_waive_verification`. Testing approval promotes the exact reviewed patch to the requester repository.
62
+
63
+ Apply mode requires a Git repository, a clean requester worktree at execution and promotion, and a real Codex SDK backend.
56
64
 
57
65
  For apply mode, `@openai/codex-sdk` must be resolvable from the installed DevCrew package. Published DevCrew packages declare it as an optional dependency, which npm installs by default. If `devcrew doctor` reports it as missing, reinstall DevCrew with optional dependencies enabled:
58
66
 
@@ -62,7 +70,7 @@ npm install -g @shenlee/devcrew --include=optional
62
70
 
63
71
  ## Marketplace smoke test
64
72
 
65
- After publishing the npm package version referenced by the plugin, run the real marketplace smoke test:
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:
66
74
 
67
75
  ```bash
68
76
  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.1` 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.3` wrapper so the MCP service is locked to the published package version.
41
41
 
42
42
  ## 4. Run A Workflow
43
43