@shenlee/devcrew 0.1.1 → 0.1.2

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 (37) hide show
  1. package/README.md +19 -5
  2. package/README.zh-CN.md +19 -5
  3. package/dist/packages/adapters/src/index.js +3 -2
  4. package/dist/packages/core/src/paths.js +3 -0
  5. package/dist/packages/core/src/store.js +6 -0
  6. package/dist/packages/core/src/types.js +1 -0
  7. package/dist/packages/core/src/version.js +1 -1
  8. package/dist/packages/core/src/workflow.js +51 -20
  9. package/dist/packages/orchestrator/src/index.js +118 -154
  10. package/dist/packages/orchestrator/src/worktree.js +198 -0
  11. package/dist/packages/service/src/stdio.js +19 -3
  12. package/dist/packages/service/src/tools.js +3 -3
  13. package/docs/claude-code.md +1 -1
  14. package/docs/codex.md +6 -2
  15. package/docs/quickstart.md +1 -1
  16. package/docs/superpowers/plans/2026-07-10-p0-foundation-repair.md +939 -0
  17. package/docs/superpowers/specs/2026-07-10-p0-foundation-repair-design.md +182 -0
  18. package/docs/workflow.md +21 -4
  19. package/package.json +1 -1
  20. package/packages/adapters/src/index.ts +4 -3
  21. package/packages/core/src/paths.ts +4 -0
  22. package/packages/core/src/store.ts +7 -0
  23. package/packages/core/src/types.ts +7 -0
  24. package/packages/core/src/version.ts +1 -1
  25. package/packages/core/src/workflow.ts +55 -20
  26. package/packages/orchestrator/src/index.ts +136 -179
  27. package/packages/orchestrator/src/worktree.ts +269 -0
  28. package/packages/service/src/stdio.ts +27 -6
  29. package/packages/service/src/tools.ts +2 -2
  30. package/plugins/devcrew-codex/.codex-plugin/plugin.json +1 -1
  31. package/plugins/devcrew-codex/.mcp.json +1 -1
  32. package/plugins/devcrew-codex/agents/architect.toml +6 -0
  33. package/plugins/devcrew-codex/agents/implementer.toml +6 -0
  34. package/plugins/devcrew-codex/agents/pm.toml +7 -0
  35. package/plugins/devcrew-codex/agents/tester.toml +6 -0
  36. package/plugins/devcrew-codex/assets/logo.png +0 -0
  37. package/scripts/smoke-codex-plugin.mjs +1 -1
package/README.md CHANGED
@@ -12,10 +12,10 @@ The first release is intentionally local-first. DevCrew stores workflow state an
12
12
 
13
13
  - Gated phases for requirements, architecture, implementation planning, and test reporting.
14
14
  - Two workflow modes: `feature` for existing repositories and `greenfield` for new products.
15
- - Safe execution modes: `plan` is the default; `apply` must be explicitly requested before implementer/tester roles can write files or run validation commands.
15
+ - Safe execution modes: `plan` is the default; `apply` must be explicit. Implementation planning stays read-only, while implementation and testing run in a DevCrew-owned Git worktree.
16
16
  - Host-preferred backend selection: Codex runs default to Codex, Claude Code runs default to Claude.
17
17
  - Orchestrated role execution: `devcrew_start` runs the PM role, and `devcrew_continue` runs the next phase role before opening the gate.
18
- - Implementation review artifact: implementation gates include changed files, captured diff, and architecture compliance review notes.
18
+ - Implementation review artifact: isolated execution records changed files, a binary-capable diff, lint evidence, and architecture compliance notes; testing refreshes the reviewed diff before promotion.
19
19
  - Repository artifacts in `.devcrew/runs/<run-id>/state.json` and `docs/devcrew/<run-id>/`.
20
20
  - Standards discovery from `.devcrew/standards.md`, `AGENTS.md`, `CLAUDE.md`, README, and common project manifests.
21
21
  - MCP tools: `devcrew_start`, `devcrew_status`, `devcrew_answer`, `devcrew_approve`, `devcrew_reject`, `devcrew_continue`, and `devcrew_artifact`.
@@ -34,7 +34,7 @@ Restart Codex, open the plugin directory, choose the DevCrew marketplace, and in
34
34
  The plugin starts the DevCrew MCP server with:
35
35
 
36
36
  ```bash
37
- npm exec --silent --yes --package=@shenlee/devcrew@0.1.1 -- node -e "<DevCrew CLI wrapper>" -- serve --stdio
37
+ npm exec --silent --yes --package=@shenlee/devcrew@0.1.2 -- node -e "<DevCrew CLI wrapper>" -- serve --stdio
38
38
  ```
39
39
 
40
40
  The plugin locks the MCP server to the published npm package version, so users do not need to clone the source or build TypeScript at install time. You only need Node.js and network access the first time Codex starts the MCP server.
@@ -81,12 +81,26 @@ The agent should call `devcrew_start`, show the PM-generated requirements artifa
81
81
 
82
82
  Plugins set `DEVCREW_HOST` for host detection, so `devcrew_start` can omit `host` unless you want to override it. DevCrew records the latest run as the repository's active run, so follow-up MCP calls can omit `runId`.
83
83
 
84
- By default DevCrew runs in `plan` mode. To allow the implementer/tester phases to make repository changes and run configured verification commands, explicitly request apply mode:
84
+ By default DevCrew runs in `plan` mode. To allow isolated execution to modify files and isolated testing to run configured verification commands, explicitly request apply mode:
85
85
 
86
86
  ```text
87
87
  Use DevCrew in apply mode to implement audit logging for the billing API.
88
88
  ```
89
89
 
90
+ Apply mode follows this sequence:
91
+
92
+ ```text
93
+ requirements approval
94
+ -> architecture approval
95
+ -> implementation plan approval
96
+ -> isolated execution
97
+ -> isolated testing
98
+ -> testing approval
99
+ -> patch promotion to requester repository
100
+ ```
101
+
102
+ Apply requires a Git repository, a real Codex or Claude SDK backend, and a clean requester worktree when isolated execution starts and when the patch is promoted. After approving the implementation plan, call `devcrew_continue` once for isolated execution and again for isolated testing. The requester repository is unchanged until the testing gate is approved. Rejecting testing and answering the feedback returns the same isolated workspace to `execution` without changing the requester repository.
103
+
90
104
  DevCrew auto-discovers verification commands from common project manifests. The current rules prefer `package.json` scripts (`validate`, then `test`, then `typecheck`/`lint`), then fall back to `go test ./...`, `cargo test`, or `python -m pytest` when matching manifests are present.
91
105
 
92
106
  You can override discovery with explicit commands in `.devcrew/config.json`:
@@ -119,7 +133,7 @@ For published installs, the host SDK packages are pinned optional dependencies s
119
133
 
120
134
  Public npm publishing is handled by the `npm publish` GitHub Actions workflow. It runs validation, checks the package with `npm pack --dry-run`, and publishes with npm provenance when a GitHub Release is published or the workflow is manually dispatched with `NPM_TOKEN` configured.
121
135
 
122
- After publishing the npm version referenced by the Codex plugin, run the real marketplace smoke test:
136
+ After publishing `@shenlee/devcrew@0.1.2`, run the real marketplace smoke test. This is a post-publication check because the plugin is version-locked to the npm package:
123
137
 
124
138
  ```bash
125
139
  npm run smoke:codex-plugin
package/README.zh-CN.md CHANGED
@@ -12,10 +12,10 @@ DevCrew 是一个面向 Codex、Claude Code 等编程 Agent 的本地工作流
12
12
 
13
13
  - 内置阶段门禁:需求确认、架构确认、实现计划确认、测试报告确认。
14
14
  - 支持两种工作流:`feature` 用于已有项目功能开发,`greenfield` 用于从零开始的新产品。
15
- - 支持安全执行模式:默认是 `plan`,只有显式请求 `apply` 时,implementer/tester 阶段才允许写文件或运行验证命令。
15
+ - 支持安全执行模式:默认是 `plan`,`apply` 必须显式开启。实现计划阶段始终只读,真正的实现和测试在 DevCrew 管理的 Git worktree 中隔离执行。
16
16
  - 默认按当前宿主选择后端:在 Codex 中优先使用 Codex,在 Claude Code 中优先使用 Claude。
17
17
  - 已接入角色编排:`devcrew_start` 会先运行 PM 角色,`devcrew_continue` 会运行下一阶段角色,然后再打开阶段门禁。
18
- - 实现评审产物:implementation gate 会附带 changed files、捕获的 diff 和架构符合性审查说明。
18
+ - 实现评审产物:隔离执行会记录 changed files、支持二进制的 diff、lint 证据和架构符合性说明;测试结束后会重新生成评审 diff,再等待晋升。
19
19
  - 运行状态写入 `.devcrew/runs/<run-id>/state.json`,评审产物写入 `docs/devcrew/<run-id>/`。
20
20
  - 自动发现项目规范:`.devcrew/standards.md`、`AGENTS.md`、`CLAUDE.md`、README 以及常见项目配置文件。
21
21
  - 提供 MCP 工具:`devcrew_start`、`devcrew_status`、`devcrew_answer`、`devcrew_approve`、`devcrew_reject`、`devcrew_continue`、`devcrew_artifact`。
@@ -34,7 +34,7 @@ codex plugin marketplace add lishen802/devcrew
34
34
  插件会用下面的命令启动 DevCrew MCP 服务:
35
35
 
36
36
  ```bash
37
- npm exec --silent --yes --package=@shenlee/devcrew@0.1.1 -- node -e "<DevCrew CLI wrapper>" -- serve --stdio
37
+ npm exec --silent --yes --package=@shenlee/devcrew@0.1.2 -- node -e "<DevCrew CLI wrapper>" -- serve --stdio
38
38
  ```
39
39
 
40
40
  插件会锁定到已发布的 npm 包版本,因此用户不需要克隆源码,也不需要在安装时编译 TypeScript;只需要本机有 Node.js,并且 Codex 第一次启动 MCP 服务时可以访问网络。
@@ -95,12 +95,26 @@ devcrew serve --stdio
95
95
 
96
96
  插件会设置 `DEVCREW_HOST` 用于宿主识别,因此 `devcrew_start` 可以省略 `host`,除非你需要显式覆盖。DevCrew 会把最新 run 记录为当前仓库的 active run,后续 MCP 调用可以省略 `runId`。
97
97
 
98
- 默认情况下 DevCrew 使用安全的 `plan` 模式。如果你希望 implementer/tester 阶段真正修改仓库并运行配置好的验证命令,需要明确要求 apply 模式:
98
+ 默认情况下 DevCrew 使用安全的 `plan` 模式。如果你希望隔离执行阶段真正修改文件,并在隔离测试阶段运行配置好的验证命令,需要明确要求 apply 模式:
99
99
 
100
100
  ```text
101
101
  使用 DevCrew apply 模式帮我实现 billing API 的审计日志功能。
102
102
  ```
103
103
 
104
+ apply 模式的固定顺序是:
105
+
106
+ ```text
107
+ 需求确认
108
+ -> 架构确认
109
+ -> 实现计划确认
110
+ -> 隔离执行
111
+ -> 隔离测试
112
+ -> 测试报告确认
113
+ -> 将补丁晋升到需求方仓库
114
+ ```
115
+
116
+ apply 模式要求项目是 Git 仓库、使用真实的 Codex 或 Claude SDK 后端,并且在开始隔离执行和最终晋升补丁时,需求方工作树都必须保持干净。实现计划审批后,需要额外调用一次 `devcrew_continue` 完成隔离执行,再调用一次进入隔离测试。测试门禁批准前,需求方仓库不会出现实现改动;如果驳回测试并提交反馈答案,流程会回到同一个隔离工作树的 `execution` 阶段,不会修改需求方仓库。
117
+
104
118
  DevCrew 会自动从常见项目清单中发现验证命令。当前规则会优先读取 `package.json` scripts(`validate`,然后是 `test`,再到 `typecheck`/`lint`),再按项目清单回退到 `go test ./...`、`cargo test` 或 `python -m pytest`。
105
119
 
106
120
  你也可以在 `.devcrew/config.json` 中显式覆盖:
@@ -133,7 +147,7 @@ npm pack --dry-run
133
147
 
134
148
  公开 npm 发布由 `npm publish` GitHub Actions 工作流处理。发布 GitHub Release 或手动触发 workflow 时,它会先运行验证,再执行 `npm pack --dry-run` 检查包内容,最后在配置 `NPM_TOKEN` 后使用 npm provenance 发布公开包。
135
149
 
136
- Codex 插件引用的 npm 版本发布完成后,运行真实 marketplace smoke test
150
+ 发布 `@shenlee/devcrew@0.1.2` 后,再运行真实 marketplace smoke test。由于插件锁定了 npm 版本,这是发布后的检查:
137
151
 
138
152
  ```bash
139
153
  npm run smoke:codex-plugin
@@ -70,7 +70,7 @@ export function renderRolePrompt(input) {
70
70
  lines.push("", `### ${name}`, content.trim());
71
71
  }
72
72
  }
73
- const canApply = executionMode === "apply" && (input.role === "implementer" || input.role === "tester");
73
+ const canApply = roleCanApply({ ...input, executionMode });
74
74
  const permissionInstruction = canApply
75
75
  ? input.role === "tester"
76
76
  ? "You may run validation commands needed for the approved scope and report exact evidence."
@@ -84,6 +84,7 @@ function titleForPhase(phase) {
84
84
  requirements: "Requirements",
85
85
  architecture: "Architecture",
86
86
  implementation: "Implementation Plan",
87
+ execution: "Implementation Review",
87
88
  testing: "Test Report",
88
89
  acceptance: "Acceptance",
89
90
  complete: "Acceptance",
@@ -155,7 +156,7 @@ export function extractClaudeResult(message) {
155
156
  return text;
156
157
  }
157
158
  function roleCanApply(input) {
158
- return input.executionMode === "apply" && (input.role === "implementer" || input.role === "tester");
159
+ return input.executionMode === "apply" && (input.phase === "execution" || input.phase === "testing");
159
160
  }
160
161
  function codexSandboxForRole(input) {
161
162
  return roleCanApply(input) ? "workspace-write" : "read-only";
@@ -9,6 +9,9 @@ export function runsDir(cwd) {
9
9
  export function runDir(cwd, runId) {
10
10
  return join(runsDir(cwd), runId);
11
11
  }
12
+ export function executionWorktreePath(cwd, runId) {
13
+ return join(devcrewDir(cwd), "worktrees", runId);
14
+ }
12
15
  export function statePath(cwd, runId) {
13
16
  return join(runDir(cwd, runId), "state.json");
14
17
  }
@@ -13,9 +13,15 @@ export async function saveState(state) {
13
13
  export async function loadState(cwd, runId) {
14
14
  const raw = await readFile(statePath(cwd, runId), "utf8");
15
15
  const parsed = JSON.parse(raw);
16
+ const executionWorkspace = parsed.executionWorkspace;
16
17
  return {
17
18
  ...parsed,
18
19
  executionMode: parsed.executionMode ?? "plan",
20
+ executionWorkspace: executionWorkspace &&
21
+ typeof executionWorkspace.path === "string" &&
22
+ typeof executionWorkspace.baseCommit === "string"
23
+ ? executionWorkspace
24
+ : undefined,
19
25
  changedFiles: Array.isArray(parsed.changedFiles) ? parsed.changedFiles : [],
20
26
  implementationDiff: typeof parsed.implementationDiff === "string" ? parsed.implementationDiff : "",
21
27
  verification: Array.isArray(parsed.verification) ? parsed.verification : [],
@@ -6,6 +6,7 @@ export const PHASES = [
6
6
  "requirements",
7
7
  "architecture",
8
8
  "implementation",
9
+ "execution",
9
10
  "testing",
10
11
  "acceptance",
11
12
  "complete",
@@ -1,2 +1,2 @@
1
- export const DEVCREW_VERSION = "0.1.1";
1
+ export const DEVCREW_VERSION = "0.1.2";
2
2
  export const DEVCREW_NPM_PACKAGE = "@shenlee/devcrew";
@@ -10,20 +10,24 @@ function now() {
10
10
  function newRunId() {
11
11
  return `dc-${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`;
12
12
  }
13
- export function nextPhaseAfterGate(gate) {
14
- const nextByGate = {
15
- requirements: "architecture",
16
- architecture: "implementation",
17
- implementation: "testing",
18
- testing: "acceptance",
19
- };
20
- return nextByGate[gate];
13
+ export function nextPhaseAfterGate(state, gate) {
14
+ switch (gate) {
15
+ case "requirements":
16
+ return "architecture";
17
+ case "architecture":
18
+ return "implementation";
19
+ case "implementation":
20
+ return state.executionMode === "apply" ? "execution" : "testing";
21
+ case "testing":
22
+ return "acceptance";
23
+ }
21
24
  }
22
25
  export function artifactForPhase(phase) {
23
26
  const artifactByPhase = {
24
27
  requirements: "requirements",
25
28
  architecture: "architecture",
26
29
  implementation: "implementation-plan",
30
+ execution: "implementation-review",
27
31
  testing: "test-report",
28
32
  acceptance: "acceptance",
29
33
  complete: "acceptance",
@@ -36,10 +40,15 @@ export function gateForPhase(phase) {
36
40
  }
37
41
  return undefined;
38
42
  }
39
- function assertCurrentGate(state, gate) {
43
+ function assertPendingCurrentGate(state, gate) {
40
44
  const expected = gateForPhase(state.phase);
41
- if (expected && expected !== gate) {
42
- throw new Error(`Cannot act on ${gate} while current gate is ${expected}`);
45
+ if (expected !== gate) {
46
+ throw new Error(expected
47
+ ? `Cannot act on ${gate} while current gate is ${expected}`
48
+ : `Cannot act on ${gate} while workflow phase is ${state.phase}`);
49
+ }
50
+ if (state.status !== "awaiting_approval" || state.gates[gate] !== "pending") {
51
+ throw new Error(`Gate ${gate} is not pending approval`);
43
52
  }
44
53
  }
45
54
  async function writeCurrentArtifact(state) {
@@ -56,6 +65,9 @@ export async function startWorkflow(input, options = {}) {
56
65
  const config = await ensureConfig(cwd);
57
66
  const backend = input.backend ? parseBackend(input.backend) : config.defaultBackend === "host-preferred" ? host : config.defaultBackend;
58
67
  const executionMode = input.executionMode ? parseExecutionMode(input.executionMode) : config.executionMode;
68
+ if (executionMode === "apply" && backend === "local") {
69
+ throw new Error("DevCrew apply mode requires a codex or claude backend; local is plan-only");
70
+ }
59
71
  const createdAt = now();
60
72
  const state = {
61
73
  version: 1,
@@ -106,6 +118,9 @@ export async function continueWorkflow(input) {
106
118
  state.status = "complete";
107
119
  return saveState(state);
108
120
  }
121
+ if (state.phase === "execution") {
122
+ throw new Error("DevCrew execution phase requires orchestrated continuation");
123
+ }
109
124
  const gate = gateForPhase(state.phase);
110
125
  if (!gate) {
111
126
  state.status = "complete";
@@ -116,17 +131,29 @@ export async function continueWorkflow(input) {
116
131
  await writeCurrentArtifact(state);
117
132
  return saveState(state);
118
133
  }
119
- export async function approveWorkflow(input) {
134
+ export async function validateWorkflowApproval(input) {
120
135
  const state = await getWorkflowStatus(input);
121
136
  const gate = parseGate(input.gate);
122
- assertCurrentGate(state, gate);
137
+ if (state.gates[gate] === "approved") {
138
+ return state;
139
+ }
140
+ assertPendingCurrentGate(state, gate);
141
+ parseOptionalNote(input.note);
142
+ return state;
143
+ }
144
+ export async function approveWorkflow(input) {
145
+ const state = await validateWorkflowApproval(input);
146
+ const gate = parseGate(input.gate);
147
+ if (state.gates[gate] === "approved") {
148
+ return state;
149
+ }
123
150
  state.gates[gate] = "approved";
124
151
  state.approvals.push({
125
152
  gate,
126
153
  note: parseOptionalNote(input.note),
127
154
  createdAt: now(),
128
155
  });
129
- const nextPhase = nextPhaseAfterGate(gate);
156
+ const nextPhase = nextPhaseAfterGate(state, gate);
130
157
  state.phase = nextPhase;
131
158
  state.status = "ready";
132
159
  return saveState(state);
@@ -134,7 +161,10 @@ export async function approveWorkflow(input) {
134
161
  export async function rejectWorkflow(input) {
135
162
  const state = await getWorkflowStatus(input);
136
163
  const gate = parseGate(input.gate);
137
- assertCurrentGate(state, gate);
164
+ if (state.gates[gate] === "rejected") {
165
+ return state;
166
+ }
167
+ assertPendingCurrentGate(state, gate);
138
168
  state.gates[gate] = "rejected";
139
169
  state.status = "awaiting_input";
140
170
  state.feedback.push({
@@ -146,15 +176,16 @@ export async function rejectWorkflow(input) {
146
176
  }
147
177
  export async function answerWorkflow(input, options = {}) {
148
178
  const state = await getWorkflowStatus(input);
179
+ const gate = gateForPhase(state.phase);
180
+ if (state.status !== "awaiting_input" || !gate || state.gates[gate] !== "rejected") {
181
+ throw new Error("Workflow must be awaiting_input at a rejected current gate before recording an answer");
182
+ }
149
183
  state.answers.push({
150
184
  answer: parseAnswer(input.answer),
151
185
  createdAt: now(),
152
186
  });
153
- const gate = gateForPhase(state.phase);
154
- if (gate) {
155
- state.gates[gate] = "pending";
156
- state.status = "awaiting_approval";
157
- }
187
+ state.gates[gate] = "pending";
188
+ state.status = "awaiting_approval";
158
189
  if (!options.skipArtifactWrite) {
159
190
  await writeCurrentArtifact(state);
160
191
  }