@shenlee/devcrew 0.1.0

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 (64) hide show
  1. package/CONTRIBUTING.md +21 -0
  2. package/LICENSE +186 -0
  3. package/README.md +142 -0
  4. package/README.zh-CN.md +156 -0
  5. package/SECURITY.md +19 -0
  6. package/dist/packages/adapters/src/index.js +234 -0
  7. package/dist/packages/cli/src/index.js +76 -0
  8. package/dist/packages/core/src/active-run.js +24 -0
  9. package/dist/packages/core/src/artifacts.js +120 -0
  10. package/dist/packages/core/src/config.js +51 -0
  11. package/dist/packages/core/src/index.js +11 -0
  12. package/dist/packages/core/src/paths.js +51 -0
  13. package/dist/packages/core/src/standards.js +61 -0
  14. package/dist/packages/core/src/store.js +24 -0
  15. package/dist/packages/core/src/types.js +48 -0
  16. package/dist/packages/core/src/validation.js +52 -0
  17. package/dist/packages/core/src/verification.js +157 -0
  18. package/dist/packages/core/src/version.js +2 -0
  19. package/dist/packages/core/src/workflow.js +166 -0
  20. package/dist/packages/orchestrator/src/index.js +376 -0
  21. package/dist/packages/plugins/src/index.js +173 -0
  22. package/dist/packages/service/src/index.js +2 -0
  23. package/dist/packages/service/src/stdio.js +100 -0
  24. package/dist/packages/service/src/tools.js +177 -0
  25. package/docs/claude-code.md +37 -0
  26. package/docs/codex.md +80 -0
  27. package/docs/quickstart.md +64 -0
  28. package/docs/roles.md +43 -0
  29. package/docs/workflow.md +70 -0
  30. package/examples/README.md +11 -0
  31. package/examples/feature-request.md +13 -0
  32. package/examples/greenfield-request.md +14 -0
  33. package/package.json +60 -0
  34. package/packages/adapters/src/index.ts +404 -0
  35. package/packages/cli/src/index.ts +88 -0
  36. package/packages/core/src/active-run.ts +31 -0
  37. package/packages/core/src/artifacts.ts +148 -0
  38. package/packages/core/src/config.ts +56 -0
  39. package/packages/core/src/index.ts +11 -0
  40. package/packages/core/src/paths.ts +66 -0
  41. package/packages/core/src/standards.ts +70 -0
  42. package/packages/core/src/store.ts +28 -0
  43. package/packages/core/src/types.ts +182 -0
  44. package/packages/core/src/validation.ts +79 -0
  45. package/packages/core/src/verification.ts +163 -0
  46. package/packages/core/src/version.ts +2 -0
  47. package/packages/core/src/workflow.ts +214 -0
  48. package/packages/orchestrator/src/index.ts +469 -0
  49. package/packages/plugins/assets/composer-icon.png +0 -0
  50. package/packages/plugins/assets/logo.png +0 -0
  51. package/packages/plugins/src/index.ts +211 -0
  52. package/packages/service/src/index.ts +2 -0
  53. package/packages/service/src/stdio.ts +121 -0
  54. package/packages/service/src/tools.ts +215 -0
  55. package/plugins/devcrew-codex/.codex-plugin/plugin.json +41 -0
  56. package/plugins/devcrew-codex/.mcp.json +16 -0
  57. package/plugins/devcrew-codex/agents/architect.toml +6 -0
  58. package/plugins/devcrew-codex/agents/implementer.toml +6 -0
  59. package/plugins/devcrew-codex/agents/pm.toml +6 -0
  60. package/plugins/devcrew-codex/agents/tester.toml +6 -0
  61. package/plugins/devcrew-codex/assets/composer-icon.png +0 -0
  62. package/plugins/devcrew-codex/assets/logo.png +0 -0
  63. package/plugins/devcrew-codex/skills/devcrew/SKILL.md +17 -0
  64. package/scripts/smoke-codex-plugin.mjs +331 -0
@@ -0,0 +1,211 @@
1
+ import { constants } from "node:fs";
2
+ import { access, copyFile, mkdir, writeFile } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ import { DEFAULT_CONFIG, DEVCREW_NPM_PACKAGE, DEVCREW_VERSION, ROLE_SECTIONS } from "../../core/src/index.js";
7
+
8
+ export interface GeneratedPlugin {
9
+ name: "devcrew";
10
+ path: string;
11
+ }
12
+
13
+ export interface GeneratedMarketplace {
14
+ name: "devcrew";
15
+ path: string;
16
+ }
17
+
18
+ async function writeJson(path: string, value: unknown): Promise<void> {
19
+ await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
20
+ }
21
+
22
+ async function bundledAssetPath(name: string): Promise<string> {
23
+ const moduleDir = dirname(fileURLToPath(import.meta.url));
24
+ const candidates = [
25
+ join(moduleDir, "..", "assets", name),
26
+ join(moduleDir, "..", "..", "..", "..", "packages", "plugins", "assets", name),
27
+ ];
28
+
29
+ for (const candidate of candidates) {
30
+ try {
31
+ await access(candidate, constants.R_OK);
32
+ return candidate;
33
+ } catch {
34
+ // Try the next layout: source tree first, compiled package second.
35
+ }
36
+ }
37
+
38
+ throw new Error(`Missing bundled DevCrew plugin asset: ${name}`);
39
+ }
40
+
41
+ async function writeCodexAssets(pluginRoot: string): Promise<void> {
42
+ const assetDir = join(pluginRoot, "assets");
43
+ await mkdir(assetDir, { recursive: true });
44
+ await copyFile(await bundledAssetPath("logo.png"), join(assetDir, "logo.png"));
45
+ await copyFile(await bundledAssetPath("composer-icon.png"), join(assetDir, "composer-icon.png"));
46
+ }
47
+
48
+ function roleExpectations(name: string): string {
49
+ const sections = ROLE_SECTIONS[name as keyof typeof ROLE_SECTIONS];
50
+ if (!sections || sections.length === 0) {
51
+ return "";
52
+ }
53
+ return sections.map((s) => `- ${s.heading} (${s.description})`).join("\n");
54
+ }
55
+
56
+ async function writeRoleAgents(root: string, format: "codex" | "claude"): Promise<void> {
57
+ const roles = [
58
+ ["pm", "Product manager. Clarifies requirements, scope boundaries, success criteria, and requester approvals."],
59
+ ["architect", "technical architecture specialist. Designs implementation, deployment, interfaces, and review criteria."],
60
+ ["implementer", "Implementation engineer. Writes code according to approved architecture and discovered standards."],
61
+ ["tester", "Testing and acceptance specialist. Verifies functionality, regressions, and acceptance evidence."],
62
+ ] as const;
63
+
64
+ if (format === "claude") {
65
+ const agentDir = join(root, "agents");
66
+ await mkdir(agentDir, { recursive: true });
67
+ for (const [name, description] of roles) {
68
+ await writeFile(
69
+ join(agentDir, `${name}.md`),
70
+ `---\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`,
71
+ "utf8",
72
+ );
73
+ }
74
+ return;
75
+ }
76
+
77
+ const agentDir = join(root, "agents");
78
+ await mkdir(agentDir, { recursive: true });
79
+ for (const [name, description] of roles) {
80
+ await writeFile(
81
+ join(agentDir, `${name}.toml`),
82
+ `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`,
83
+ "utf8",
84
+ );
85
+ }
86
+ }
87
+
88
+ function entrySkill(): string {
89
+ 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`;
90
+ }
91
+
92
+ function npmPackageSpecifier(): string {
93
+ return `${DEVCREW_NPM_PACKAGE}@${DEVCREW_VERSION}`;
94
+ }
95
+
96
+ export async function generateCodexPlugin(root: string): Promise<GeneratedPlugin> {
97
+ const pluginRoot = join(root, "plugins", "devcrew-codex");
98
+ await mkdir(join(pluginRoot, ".codex-plugin"), { recursive: true });
99
+ await mkdir(join(pluginRoot, "skills", "devcrew"), { recursive: true });
100
+ await writeCodexAssets(pluginRoot);
101
+ await writeJson(join(pluginRoot, ".codex-plugin", "plugin.json"), {
102
+ name: "devcrew",
103
+ version: DEVCREW_VERSION,
104
+ description: "DevCrew gated multi-role workflow service for Codex.",
105
+ author: {
106
+ name: "DevCrew Contributors",
107
+ url: "https://github.com/lishen802/devcrew",
108
+ },
109
+ homepage: "https://github.com/lishen802/devcrew#readme",
110
+ repository: "https://github.com/lishen802/devcrew",
111
+ license: "Apache-2.0",
112
+ keywords: ["codex", "agents", "workflow", "mcp", "skills"],
113
+ skills: "./skills/",
114
+ mcpServers: "./.mcp.json",
115
+ interface: {
116
+ displayName: "DevCrew",
117
+ shortDescription: "Run gated PM, architecture, implementation, and testing workflows.",
118
+ longDescription:
119
+ "DevCrew helps Codex run feature and product development through explicit requirements, architecture, implementation planning, and testing gates.",
120
+ developerName: "DevCrew Contributors",
121
+ category: "Productivity",
122
+ capabilities: ["Interactive", "Write"],
123
+ websiteURL: "https://github.com/lishen802/devcrew",
124
+ defaultPrompt: [
125
+ "Use DevCrew to plan this feature.",
126
+ "Use DevCrew to build this product.",
127
+ "Use DevCrew to review this implementation plan.",
128
+ ],
129
+ brandColor: "#2563EB",
130
+ composerIcon: "./assets/composer-icon.png",
131
+ logo: "./assets/logo.png",
132
+ },
133
+ });
134
+ await writeFile(join(pluginRoot, "skills", "devcrew", "SKILL.md"), entrySkill(), "utf8");
135
+ await writeJson(join(pluginRoot, ".mcp.json"), {
136
+ mcpServers: {
137
+ devcrew: {
138
+ command: "npx",
139
+ args: ["-y", npmPackageSpecifier(), "serve", "--stdio"],
140
+ env: { DEVCREW_HOST: "codex" },
141
+ },
142
+ },
143
+ });
144
+ await writeRoleAgents(pluginRoot, "codex");
145
+ return { name: "devcrew", path: pluginRoot };
146
+ }
147
+
148
+ export async function generateCodexMarketplace(root: string): Promise<GeneratedMarketplace> {
149
+ const marketplacePath = join(root, ".agents", "plugins", "marketplace.json");
150
+ await mkdir(join(root, ".agents", "plugins"), { recursive: true });
151
+ await writeJson(marketplacePath, {
152
+ name: "devcrew",
153
+ interface: {
154
+ displayName: "DevCrew",
155
+ },
156
+ plugins: [
157
+ {
158
+ name: "devcrew",
159
+ source: {
160
+ source: "local",
161
+ path: "./plugins/devcrew-codex",
162
+ },
163
+ policy: {
164
+ installation: "AVAILABLE",
165
+ authentication: "ON_INSTALL",
166
+ },
167
+ category: "Productivity",
168
+ },
169
+ ],
170
+ });
171
+ return { name: "devcrew", path: marketplacePath };
172
+ }
173
+
174
+ export async function generateClaudePlugin(root: string): Promise<GeneratedPlugin> {
175
+ const pluginRoot = join(root, "plugins", "devcrew-claude");
176
+ await mkdir(join(pluginRoot, ".claude-plugin"), { recursive: true });
177
+ await mkdir(join(pluginRoot, "skills", "devcrew"), { recursive: true });
178
+ await writeJson(join(pluginRoot, ".claude-plugin", "plugin.json"), {
179
+ name: "devcrew",
180
+ description: "DevCrew gated multi-role workflow service for Claude Code.",
181
+ version: DEVCREW_VERSION,
182
+ author: { name: "DevCrew Contributors" },
183
+ });
184
+ await writeFile(join(pluginRoot, "skills", "devcrew", "SKILL.md"), entrySkill(), "utf8");
185
+ await writeJson(join(pluginRoot, ".mcp.json"), {
186
+ mcpServers: {
187
+ devcrew: {
188
+ command: "npx",
189
+ args: ["-y", npmPackageSpecifier(), "serve", "--stdio"],
190
+ env: { DEVCREW_HOST: "claude" },
191
+ },
192
+ },
193
+ });
194
+ await writeRoleAgents(pluginRoot, "claude");
195
+ return { name: "devcrew", path: pluginRoot };
196
+ }
197
+
198
+ export async function initProject(root: string): Promise<{ codex: GeneratedPlugin; claude: GeneratedPlugin }> {
199
+ await mkdir(join(root, ".devcrew"), { recursive: true });
200
+ await mkdir(join(root, "docs", "devcrew"), { recursive: true });
201
+ await writeJson(join(root, ".devcrew", "config.json"), DEFAULT_CONFIG);
202
+ await writeFile(
203
+ join(root, ".devcrew", "standards.md"),
204
+ "# DevCrew Standards\n\nAdd project-specific coding, testing, documentation, and deployment rules here.\n",
205
+ "utf8",
206
+ );
207
+ const codex = await generateCodexPlugin(root);
208
+ await generateCodexMarketplace(root);
209
+ const claude = await generateClaudePlugin(root);
210
+ return { codex, claude };
211
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./stdio.js";
2
+ export * from "./tools.js";
@@ -0,0 +1,121 @@
1
+ import { callDevCrewTool, listDevCrewTools } from "./tools.js";
2
+ import { DEVCREW_VERSION } from "../../core/src/index.js";
3
+
4
+ interface JsonRpcRequest {
5
+ id?: string | number | null;
6
+ method?: string;
7
+ params?: Record<string, unknown>;
8
+ }
9
+
10
+ type JsonWriter = (message: unknown) => void;
11
+ type RequestHandler = (request: JsonRpcRequest) => Promise<void>;
12
+
13
+ function writeJson(message: unknown): void {
14
+ process.stdout.write(`${JSON.stringify(message)}\n`);
15
+ }
16
+
17
+ async function handleRequest(request: JsonRpcRequest, write: JsonWriter): Promise<void> {
18
+ if (request.method === "notifications/initialized") {
19
+ return;
20
+ }
21
+
22
+ if (request.id === undefined || request.id === null) {
23
+ return;
24
+ }
25
+
26
+ try {
27
+ if (request.method === "initialize") {
28
+ write({
29
+ jsonrpc: "2.0",
30
+ id: request.id,
31
+ result: {
32
+ protocolVersion: "2025-03-26",
33
+ capabilities: { tools: {} },
34
+ serverInfo: { name: "devcrew", version: DEVCREW_VERSION },
35
+ },
36
+ });
37
+ return;
38
+ }
39
+
40
+ if (request.method === "tools/list") {
41
+ write({
42
+ jsonrpc: "2.0",
43
+ id: request.id,
44
+ result: { tools: listDevCrewTools() },
45
+ });
46
+ return;
47
+ }
48
+
49
+ if (request.method === "tools/call") {
50
+ const params = request.params ?? {};
51
+ const name = params.name;
52
+ if (typeof name !== "string") {
53
+ throw new Error("tools/call params.name must be a string");
54
+ }
55
+ const result = await callDevCrewTool(name, (params.arguments ?? {}) as Record<string, unknown>);
56
+ write({
57
+ jsonrpc: "2.0",
58
+ id: request.id,
59
+ result,
60
+ });
61
+ return;
62
+ }
63
+
64
+ throw new Error(`Unsupported JSON-RPC method: ${request.method ?? "unknown"}`);
65
+ } catch (error) {
66
+ write({
67
+ jsonrpc: "2.0",
68
+ id: request.id,
69
+ error: {
70
+ code: -32000,
71
+ message: error instanceof Error ? error.message : String(error),
72
+ },
73
+ });
74
+ }
75
+ }
76
+
77
+ export function createStdioLineProcessor(
78
+ write: JsonWriter = writeJson,
79
+ handler: RequestHandler = (request) => handleRequest(request, write),
80
+ ): (line: string) => Promise<void> {
81
+ let queue = Promise.resolve();
82
+ return async (line: string): Promise<void> => {
83
+ const trimmed = line.trim();
84
+ if (!trimmed) {
85
+ return queue;
86
+ }
87
+
88
+ let request: JsonRpcRequest;
89
+ try {
90
+ request = JSON.parse(trimmed) as JsonRpcRequest;
91
+ } catch {
92
+ write({
93
+ jsonrpc: "2.0",
94
+ id: null,
95
+ error: { code: -32700, message: "Parse error" },
96
+ });
97
+ return queue;
98
+ }
99
+
100
+ queue = queue.then(() => handler(request));
101
+ return queue;
102
+ };
103
+ }
104
+
105
+ export function runStdioServer(): void {
106
+ let buffer = "";
107
+ const processLine = createStdioLineProcessor();
108
+ process.stdin.setEncoding("utf8");
109
+ process.stdin.on("data", (chunk) => {
110
+ buffer += chunk;
111
+ const lines = buffer.split("\n");
112
+ buffer = lines.pop() ?? "";
113
+ for (const line of lines) {
114
+ const trimmed = line.trim();
115
+ if (!trimmed) {
116
+ continue;
117
+ }
118
+ void processLine(trimmed);
119
+ }
120
+ });
121
+ }
@@ -0,0 +1,215 @@
1
+ import {
2
+ approveWorkflow,
3
+ getArtifact,
4
+ getActiveRunId,
5
+ getWorkflowStatus,
6
+ setActiveRun,
7
+ type Host,
8
+ type RunState,
9
+ } from "../../core/src/index.js";
10
+ import {
11
+ answerOrchestratedWorkflow,
12
+ continueOrchestratedWorkflow,
13
+ rejectOrchestratedWorkflow,
14
+ startOrchestratedWorkflow,
15
+ } from "../../orchestrator/src/index.js";
16
+
17
+ export interface DevCrewTool {
18
+ name: string;
19
+ description: string;
20
+ inputSchema: {
21
+ type: "object";
22
+ properties: Record<string, unknown>;
23
+ required?: string[];
24
+ };
25
+ }
26
+
27
+ export interface ToolResult {
28
+ content: Array<{ type: "text"; text: string }>;
29
+ structuredContent?: Record<string, unknown>;
30
+ isError?: boolean;
31
+ }
32
+
33
+ const cwdProperty = { type: "string", description: "Repository working directory." };
34
+ const runIdProperty = { type: "string", description: "DevCrew run id." };
35
+ const hostValues = ["codex", "claude"] as const;
36
+
37
+ function inferHost(env: NodeJS.ProcessEnv = process.env): Host {
38
+ const host = env.DEVCREW_HOST;
39
+ return host === "claude" || host === "codex" ? host : "codex";
40
+ }
41
+
42
+ async function withActiveRun(args: Record<string, unknown>): Promise<Record<string, unknown>> {
43
+ if (typeof args.runId === "string" && args.runId.trim()) {
44
+ return args;
45
+ }
46
+ if (typeof args.cwd !== "string" || !args.cwd.trim()) {
47
+ return args;
48
+ }
49
+ return { ...args, runId: await getActiveRunId(args.cwd) };
50
+ }
51
+
52
+ function withInferredHost(args: Record<string, unknown>): Record<string, unknown> {
53
+ if (typeof args.host === "string" && hostValues.includes(args.host as Host)) {
54
+ return args;
55
+ }
56
+ return { ...args, host: inferHost() };
57
+ }
58
+
59
+ export function listDevCrewTools(): DevCrewTool[] {
60
+ return [
61
+ {
62
+ name: "devcrew_start",
63
+ description: "Create a new gated DevCrew workflow run.",
64
+ inputSchema: {
65
+ type: "object",
66
+ required: ["cwd", "mode", "request"],
67
+ properties: {
68
+ cwd: cwdProperty,
69
+ host: { type: "string", enum: ["codex", "claude"], description: "Optional host override. Defaults to DEVCREW_HOST or codex." },
70
+ mode: { type: "string", enum: ["feature", "greenfield"] },
71
+ executionMode: {
72
+ type: "string",
73
+ enum: ["plan", "apply"],
74
+ description: "Execution mode. Defaults to plan; apply must be explicit.",
75
+ },
76
+ request: { type: "string" },
77
+ backend: { type: "string", enum: ["codex", "claude", "local"] },
78
+ },
79
+ },
80
+ },
81
+ {
82
+ name: "devcrew_status",
83
+ description: "Read the status of a DevCrew workflow run.",
84
+ inputSchema: {
85
+ type: "object",
86
+ required: ["cwd"],
87
+ properties: { cwd: cwdProperty, runId: runIdProperty },
88
+ },
89
+ },
90
+ {
91
+ name: "devcrew_answer",
92
+ description: "Record requester clarification input for the current gate.",
93
+ inputSchema: {
94
+ type: "object",
95
+ required: ["cwd", "answer"],
96
+ properties: { cwd: cwdProperty, runId: runIdProperty, answer: { type: "string" } },
97
+ },
98
+ },
99
+ {
100
+ name: "devcrew_approve",
101
+ description: "Approve the current workflow gate and advance to the next phase.",
102
+ inputSchema: {
103
+ type: "object",
104
+ required: ["cwd", "gate"],
105
+ properties: {
106
+ cwd: cwdProperty,
107
+ runId: runIdProperty,
108
+ gate: { type: "string", enum: ["requirements", "architecture", "implementation", "testing"] },
109
+ note: { type: "string" },
110
+ },
111
+ },
112
+ },
113
+ {
114
+ name: "devcrew_reject",
115
+ description: "Reject the current workflow gate and record feedback.",
116
+ inputSchema: {
117
+ type: "object",
118
+ required: ["cwd", "gate", "feedback"],
119
+ properties: {
120
+ cwd: cwdProperty,
121
+ runId: runIdProperty,
122
+ gate: { type: "string", enum: ["requirements", "architecture", "implementation", "testing"] },
123
+ feedback: { type: "string" },
124
+ },
125
+ },
126
+ },
127
+ {
128
+ name: "devcrew_continue",
129
+ description: "Continue a run after the previous gate was approved.",
130
+ inputSchema: {
131
+ type: "object",
132
+ required: ["cwd"],
133
+ properties: { cwd: cwdProperty, runId: runIdProperty },
134
+ },
135
+ },
136
+ {
137
+ name: "devcrew_artifact",
138
+ description: "Read a generated workflow artifact.",
139
+ inputSchema: {
140
+ type: "object",
141
+ required: ["cwd", "name"],
142
+ properties: {
143
+ cwd: cwdProperty,
144
+ runId: runIdProperty,
145
+ name: {
146
+ type: "string",
147
+ enum: ["requirements", "architecture", "implementation-plan", "implementation-review", "test-report", "acceptance"],
148
+ },
149
+ },
150
+ },
151
+ },
152
+ ];
153
+ }
154
+
155
+ function summarizeState(state: RunState): string {
156
+ const pendingGate = Object.entries(state.gates).find(([, status]) => status === "pending")?.[0] ?? "none";
157
+ const role = state.roles.at(-1);
158
+ const roleFallback =
159
+ role?.usedFallback === true ? (role.backend === "local" ? "local" : "sdk") : role ? "none" : "none";
160
+ return `Run ${state.runId}: phase=${state.phase}, status=${state.status}, execution_mode=${state.executionMode}, pending_gate=${pendingGate}, role_fallback=${roleFallback}`;
161
+ }
162
+
163
+ function success(text: string, structuredContent?: Record<string, unknown>): ToolResult {
164
+ return {
165
+ isError: false,
166
+ content: [{ type: "text", text }],
167
+ structuredContent,
168
+ };
169
+ }
170
+
171
+ function failure(error: unknown): ToolResult {
172
+ const message = error instanceof Error ? error.message : String(error);
173
+ return {
174
+ isError: true,
175
+ content: [{ type: "text", text: message }],
176
+ structuredContent: { error: message },
177
+ };
178
+ }
179
+
180
+ export async function callDevCrewTool(name: string, args: Record<string, unknown>): Promise<ToolResult> {
181
+ try {
182
+ if (name === "devcrew_start") {
183
+ const state = await startOrchestratedWorkflow(withInferredHost(args) as never);
184
+ await setActiveRun(state.cwd, state.runId);
185
+ return success(`${summarizeState(state)}. Review ${state.artifacts.requirements}`, { state });
186
+ }
187
+ if (name === "devcrew_status") {
188
+ const state = await getWorkflowStatus((await withActiveRun(args)) as never);
189
+ return success(summarizeState(state), { state });
190
+ }
191
+ if (name === "devcrew_answer") {
192
+ const state = await answerOrchestratedWorkflow((await withActiveRun(args)) as never);
193
+ return success(`${summarizeState(state)}. Answer recorded.`, { state });
194
+ }
195
+ if (name === "devcrew_approve") {
196
+ const state = await approveWorkflow((await withActiveRun(args)) as never);
197
+ return success(`${summarizeState(state)}. Gate approved.`, { state });
198
+ }
199
+ if (name === "devcrew_reject") {
200
+ const state = await rejectOrchestratedWorkflow((await withActiveRun(args)) as never);
201
+ return success(`${summarizeState(state)}. Gate rejected.`, { state });
202
+ }
203
+ if (name === "devcrew_continue") {
204
+ const state = await continueOrchestratedWorkflow((await withActiveRun(args)) as never);
205
+ return success(`${summarizeState(state)}.`, { state });
206
+ }
207
+ if (name === "devcrew_artifact") {
208
+ const artifact = await getArtifact((await withActiveRun(args)) as never);
209
+ return success(artifact.content, { artifact });
210
+ }
211
+ throw new Error(`Unknown DevCrew tool: ${name}`);
212
+ } catch (error) {
213
+ return failure(error);
214
+ }
215
+ }
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "devcrew",
3
+ "version": "0.1.0",
4
+ "description": "DevCrew gated multi-role workflow service for Codex.",
5
+ "author": {
6
+ "name": "DevCrew Contributors",
7
+ "url": "https://github.com/lishen802/devcrew"
8
+ },
9
+ "homepage": "https://github.com/lishen802/devcrew#readme",
10
+ "repository": "https://github.com/lishen802/devcrew",
11
+ "license": "Apache-2.0",
12
+ "keywords": [
13
+ "codex",
14
+ "agents",
15
+ "workflow",
16
+ "mcp",
17
+ "skills"
18
+ ],
19
+ "skills": "./skills/",
20
+ "mcpServers": "./.mcp.json",
21
+ "interface": {
22
+ "displayName": "DevCrew",
23
+ "shortDescription": "Run gated PM, architecture, implementation, and testing workflows.",
24
+ "longDescription": "DevCrew helps Codex run feature and product development through explicit requirements, architecture, implementation planning, and testing gates.",
25
+ "developerName": "DevCrew Contributors",
26
+ "category": "Productivity",
27
+ "capabilities": [
28
+ "Interactive",
29
+ "Write"
30
+ ],
31
+ "websiteURL": "https://github.com/lishen802/devcrew",
32
+ "defaultPrompt": [
33
+ "Use DevCrew to plan this feature.",
34
+ "Use DevCrew to build this product.",
35
+ "Use DevCrew to review this implementation plan."
36
+ ],
37
+ "brandColor": "#2563EB",
38
+ "composerIcon": "./assets/composer-icon.png",
39
+ "logo": "./assets/logo.png"
40
+ }
41
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "mcpServers": {
3
+ "devcrew": {
4
+ "command": "npx",
5
+ "args": [
6
+ "-y",
7
+ "@shenlee/devcrew@0.1.0",
8
+ "serve",
9
+ "--stdio"
10
+ ],
11
+ "env": {
12
+ "DEVCREW_HOST": "codex"
13
+ }
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,6 @@
1
+ name = "architect"
2
+ description = "technical architecture specialist. Designs implementation, deployment, interfaces, and review criteria."
3
+ developer_instructions = """
4
+ You are the DevCrew architect role. technical architecture specialist. Designs implementation, deployment, interfaces, and review criteria.
5
+ Return concise Markdown and keep inherited host permissions.
6
+ """
@@ -0,0 +1,6 @@
1
+ name = "implementer"
2
+ description = "Implementation engineer. Writes code according to approved architecture and discovered standards."
3
+ developer_instructions = """
4
+ You are the DevCrew implementer role. Implementation engineer. Writes code according to approved architecture and discovered standards.
5
+ Return concise Markdown and keep inherited host permissions.
6
+ """
@@ -0,0 +1,6 @@
1
+ name = "pm"
2
+ description = "Product manager. Clarifies requirements, scope boundaries, success criteria, and requester approvals."
3
+ developer_instructions = """
4
+ You are the DevCrew pm role. Product manager. Clarifies requirements, scope boundaries, success criteria, and requester approvals.
5
+ Return concise Markdown and keep inherited host permissions.
6
+ """
@@ -0,0 +1,6 @@
1
+ name = "tester"
2
+ description = "Testing and acceptance specialist. Verifies functionality, regressions, and acceptance evidence."
3
+ developer_instructions = """
4
+ You are the DevCrew tester role. Testing and acceptance specialist. Verifies functionality, regressions, and acceptance evidence.
5
+ Return concise Markdown and keep inherited host permissions.
6
+ """
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: devcrew
3
+ description: 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 完整研发流程, 需求澄清, 产品经理, 架构师, 开发测试流程.
4
+ ---
5
+
6
+ Use the DevCrew MCP tools to manage the workflow:
7
+
8
+ 1. 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`.
9
+ 2. 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.
10
+ 3. 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.
11
+ 4. Use `devcrew_status` to show the current phase and pending gate.
12
+ 5. Use `devcrew_answer` when the requester gives clarification.
13
+ 6. Use `devcrew_approve` or `devcrew_reject` for each gate.
14
+ 7. 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.
15
+ 8. Use `devcrew_artifact` to read generated requirements, architecture, implementation-plan, implementation-review, test-report, or acceptance files.
16
+
17
+ Do not bypass host sandbox, approval, or tool permissions.