@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.
- package/README.md +30 -8
- package/README.zh-CN.md +27 -8
- package/dist/packages/adapters/src/index.js +62 -8
- package/dist/packages/core/src/artifacts.js +14 -4
- package/dist/packages/core/src/config.js +1 -1
- package/dist/packages/core/src/paths.js +10 -6
- package/dist/packages/core/src/store.js +36 -0
- package/dist/packages/core/src/types.js +5 -1
- package/dist/packages/core/src/validation.js +7 -1
- package/dist/packages/core/src/version.js +1 -1
- package/dist/packages/core/src/workflow.js +120 -31
- package/dist/packages/orchestrator/src/index.js +339 -160
- package/dist/packages/orchestrator/src/worktree.js +198 -0
- package/dist/packages/plugins/src/index.js +2 -32
- package/dist/packages/service/src/stdio.js +19 -3
- package/dist/packages/service/src/tools.js +45 -6
- package/docs/claude-code.md +5 -3
- package/docs/codex.md +12 -4
- package/docs/quickstart.md +1 -1
- package/docs/superpowers/plans/2026-07-10-p0-foundation-repair.md +939 -0
- package/docs/superpowers/plans/2026-07-13-safety-semantics.md +313 -0
- package/docs/superpowers/specs/2026-07-10-p0-foundation-repair-design.md +182 -0
- package/docs/superpowers/specs/2026-07-13-execution-boundaries-design.md +126 -0
- package/docs/workflow.md +36 -4
- package/package.json +1 -1
- package/packages/adapters/src/index.ts +87 -11
- package/packages/core/src/artifacts.ts +16 -4
- package/packages/core/src/config.ts +1 -1
- package/packages/core/src/paths.ts +11 -6
- package/packages/core/src/store.ts +41 -1
- package/packages/core/src/types.ts +53 -2
- package/packages/core/src/validation.ts +10 -0
- package/packages/core/src/version.ts +1 -1
- package/packages/core/src/workflow.ts +136 -30
- package/packages/orchestrator/src/index.ts +377 -182
- package/packages/orchestrator/src/worktree.ts +269 -0
- package/packages/plugins/src/index.ts +2 -44
- package/packages/service/src/stdio.ts +27 -6
- package/packages/service/src/tools.ts +46 -5
- package/plugins/devcrew-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/devcrew-codex/.mcp.json +1 -1
- package/plugins/devcrew-codex/assets/logo.png +0 -0
- package/plugins/devcrew-codex/skills/devcrew/SKILL.md +8 -7
- package/scripts/smoke-codex-plugin.mjs +1 -1
- package/plugins/devcrew-codex/agents/architect.toml +0 -6
- package/plugins/devcrew-codex/agents/implementer.toml +0 -6
- package/plugins/devcrew-codex/agents/pm.toml +0 -6
- 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
|
|
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
|
|
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
|
|
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
|
-
|
|
81
|
-
|
|
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 {
|
|
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
|
|
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 });
|
package/docs/claude-code.md
CHANGED
|
@@ -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.
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
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.3` wrapper so the MCP service is locked to the published package version.
|
|
41
41
|
|
|
42
42
|
## 4. Run A Workflow
|
|
43
43
|
|