@shenlee/devcrew 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -7
- package/README.zh-CN.md +12 -7
- package/dist/packages/adapters/src/index.js +204 -10
- package/dist/packages/core/src/active-run.js +13 -1
- package/dist/packages/core/src/artifacts.js +14 -4
- package/dist/packages/core/src/config.js +88 -17
- package/dist/packages/core/src/index.js +1 -0
- package/dist/packages/core/src/lock.js +89 -0
- package/dist/packages/core/src/paths.js +10 -6
- package/dist/packages/core/src/store.js +40 -0
- package/dist/packages/core/src/types.js +4 -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 +87 -12
- package/dist/packages/orchestrator/src/index.js +317 -18
- package/dist/packages/plugins/src/index.js +2 -32
- package/dist/packages/service/src/tools.js +117 -17
- package/docs/claude-code.md +18 -3
- package/docs/codex.md +22 -5
- package/docs/quickstart.md +1 -1
- package/docs/superpowers/plans/2026-07-13-safety-semantics.md +313 -0
- package/docs/superpowers/plans/2026-07-14-p0-remediation.md +213 -0
- package/docs/superpowers/plans/2026-07-14-reliability-recovery.md +208 -0
- package/docs/superpowers/plans/2026-07-14-structured-role-results.md +231 -0
- package/docs/superpowers/specs/2026-07-13-execution-boundaries-design.md +126 -0
- package/docs/superpowers/specs/2026-07-14-p0-remediation-design.md +52 -0
- package/docs/superpowers/specs/2026-07-14-reliability-recovery-design.md +92 -0
- package/docs/superpowers/specs/2026-07-14-structured-role-results-design.md +96 -0
- package/docs/workflow.md +28 -4
- package/package.json +1 -1
- package/packages/adapters/src/index.ts +250 -13
- package/packages/core/src/active-run.ts +13 -1
- package/packages/core/src/artifacts.ts +16 -4
- package/packages/core/src/config.ts +97 -18
- package/packages/core/src/index.ts +1 -0
- package/packages/core/src/lock.ts +104 -0
- package/packages/core/src/paths.ts +11 -6
- package/packages/core/src/store.ts +45 -1
- package/packages/core/src/types.ts +92 -2
- package/packages/core/src/validation.ts +10 -0
- package/packages/core/src/version.ts +1 -1
- package/packages/core/src/workflow.ts +101 -11
- package/packages/orchestrator/src/index.ts +349 -19
- package/packages/plugins/src/index.ts +2 -44
- package/packages/service/src/tools.ts +126 -15
- package/plugins/devcrew-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/devcrew-codex/.mcp.json +1 -1
- 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 -12
- package/plugins/devcrew-codex/agents/implementer.toml +0 -12
- package/plugins/devcrew-codex/agents/pm.toml +0 -13
- package/plugins/devcrew-codex/agents/tester.toml +0 -12
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { getArtifact, getActiveRunId, getWorkflowStatus, setActiveRun, } from "../../core/src/index.js";
|
|
2
|
-
import { answerOrchestratedWorkflow, approveOrchestratedWorkflow, continueOrchestratedWorkflow, rejectOrchestratedWorkflow, startOrchestratedWorkflow, } from "../../orchestrator/src/index.js";
|
|
1
|
+
import { clearActiveRunIfMatches, getArtifact, getActiveRunId, getWorkflowStatus, recoverRepositoryLock, setActiveRun, withRepositoryLock, } from "../../core/src/index.js";
|
|
2
|
+
import { abortOrchestratedWorkflow, answerOrchestratedWorkflow, approveOrchestratedWorkflow, completeOrchestratedExecution, continueOrchestratedWorkflow, rejectOrchestratedWorkflow, recoverOrchestratedWorkflow, startOrchestratedWorkflow, waiveOrchestratedVerification, } from "../../orchestrator/src/index.js";
|
|
3
3
|
const cwdProperty = { type: "string", description: "Repository working directory." };
|
|
4
4
|
const runIdProperty = { type: "string", description: "DevCrew run id." };
|
|
5
5
|
const hostValues = ["codex", "claude"];
|
|
@@ -22,8 +22,23 @@ function withInferredHost(args) {
|
|
|
22
22
|
}
|
|
23
23
|
return { ...args, host: inferHost() };
|
|
24
24
|
}
|
|
25
|
+
async function withMutationLock(args, action) {
|
|
26
|
+
if (typeof args.cwd !== "string" || !args.cwd.trim()) {
|
|
27
|
+
return action();
|
|
28
|
+
}
|
|
29
|
+
return withRepositoryLock(args.cwd, action);
|
|
30
|
+
}
|
|
25
31
|
export function listDevCrewTools() {
|
|
26
32
|
return [
|
|
33
|
+
{
|
|
34
|
+
name: "devcrew_abort",
|
|
35
|
+
description: "Abort a nonterminal run, preserve its audit evidence, and clean its isolated worktree when possible.",
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: "object",
|
|
38
|
+
required: ["cwd", "reason"],
|
|
39
|
+
properties: { cwd: cwdProperty, runId: runIdProperty, reason: { type: "string" } },
|
|
40
|
+
},
|
|
41
|
+
},
|
|
27
42
|
{
|
|
28
43
|
name: "devcrew_start",
|
|
29
44
|
description: "Create a new gated DevCrew workflow run.",
|
|
@@ -39,11 +54,25 @@ export function listDevCrewTools() {
|
|
|
39
54
|
enum: ["plan", "apply"],
|
|
40
55
|
description: "Execution mode. Defaults to plan; apply must be explicit.",
|
|
41
56
|
},
|
|
57
|
+
executionPolicy: {
|
|
58
|
+
type: "string",
|
|
59
|
+
enum: ["interactive-host", "headless-restricted", "headless-unattended"],
|
|
60
|
+
description: "Apply execution policy. Defaults to interactive-host; headless policies are explicit DevCrew SDK policies.",
|
|
61
|
+
},
|
|
42
62
|
request: { type: "string" },
|
|
43
63
|
backend: { type: "string", enum: ["codex", "claude", "local"] },
|
|
44
64
|
},
|
|
45
65
|
},
|
|
46
66
|
},
|
|
67
|
+
{
|
|
68
|
+
name: "devcrew_recover",
|
|
69
|
+
description: "Explicitly clear a confirmed stale lock and retry cleanup for a terminal run without executing an agent.",
|
|
70
|
+
inputSchema: {
|
|
71
|
+
type: "object",
|
|
72
|
+
required: ["cwd"],
|
|
73
|
+
properties: { cwd: cwdProperty, runId: runIdProperty },
|
|
74
|
+
},
|
|
75
|
+
},
|
|
47
76
|
{
|
|
48
77
|
name: "devcrew_status",
|
|
49
78
|
description: "Read the status of a DevCrew workflow run.",
|
|
@@ -71,7 +100,7 @@ export function listDevCrewTools() {
|
|
|
71
100
|
properties: {
|
|
72
101
|
cwd: cwdProperty,
|
|
73
102
|
runId: runIdProperty,
|
|
74
|
-
gate: { type: "string", enum: ["requirements", "architecture", "implementation", "testing"] },
|
|
103
|
+
gate: { type: "string", enum: ["requirements", "architecture", "implementation", "implementation-review", "testing"] },
|
|
75
104
|
note: { type: "string" },
|
|
76
105
|
},
|
|
77
106
|
},
|
|
@@ -85,7 +114,7 @@ export function listDevCrewTools() {
|
|
|
85
114
|
properties: {
|
|
86
115
|
cwd: cwdProperty,
|
|
87
116
|
runId: runIdProperty,
|
|
88
|
-
gate: { type: "string", enum: ["requirements", "architecture", "implementation", "testing"] },
|
|
117
|
+
gate: { type: "string", enum: ["requirements", "architecture", "implementation", "implementation-review", "testing"] },
|
|
89
118
|
feedback: { type: "string" },
|
|
90
119
|
},
|
|
91
120
|
},
|
|
@@ -99,6 +128,32 @@ export function listDevCrewTools() {
|
|
|
99
128
|
properties: { cwd: cwdProperty, runId: runIdProperty },
|
|
100
129
|
},
|
|
101
130
|
},
|
|
131
|
+
{
|
|
132
|
+
name: "devcrew_complete_execution",
|
|
133
|
+
description: "Record completion by the native host for an interactive-host execution or testing step.",
|
|
134
|
+
inputSchema: {
|
|
135
|
+
type: "object",
|
|
136
|
+
required: ["cwd", "summary"],
|
|
137
|
+
properties: {
|
|
138
|
+
cwd: cwdProperty,
|
|
139
|
+
runId: runIdProperty,
|
|
140
|
+
summary: { type: "string" },
|
|
141
|
+
verification: {
|
|
142
|
+
type: "array",
|
|
143
|
+
description: "Required only when completing testing: command, exitCode, output, startedAt, and completedAt for each validation command.",
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: "devcrew_waive_verification",
|
|
150
|
+
description: "Record a reasoned risk waiver after failed apply-mode verification, then reopen the testing gate for approval.",
|
|
151
|
+
inputSchema: {
|
|
152
|
+
type: "object",
|
|
153
|
+
required: ["cwd", "reason"],
|
|
154
|
+
properties: { cwd: cwdProperty, runId: runIdProperty, reason: { type: "string" } },
|
|
155
|
+
},
|
|
156
|
+
},
|
|
102
157
|
{
|
|
103
158
|
name: "devcrew_artifact",
|
|
104
159
|
description: "Read a generated workflow artifact.",
|
|
@@ -110,7 +165,7 @@ export function listDevCrewTools() {
|
|
|
110
165
|
runId: runIdProperty,
|
|
111
166
|
name: {
|
|
112
167
|
type: "string",
|
|
113
|
-
enum: ["requirements", "architecture", "implementation-plan", "implementation-review", "test-report", "acceptance"],
|
|
168
|
+
enum: ["requirements", "architecture", "implementation-plan", "implementation-review", "architecture-review", "test-report", "acceptance"],
|
|
114
169
|
},
|
|
115
170
|
},
|
|
116
171
|
},
|
|
@@ -121,7 +176,8 @@ function summarizeState(state) {
|
|
|
121
176
|
const pendingGate = Object.entries(state.gates).find(([, status]) => status === "pending")?.[0] ?? "none";
|
|
122
177
|
const role = state.roles.at(-1);
|
|
123
178
|
const roleFallback = role?.usedFallback === true ? (role.backend === "local" ? "local" : "sdk") : role ? "none" : "none";
|
|
124
|
-
|
|
179
|
+
const roleFormat = role?.format ?? "none";
|
|
180
|
+
return `Run ${state.runId}: phase=${state.phase}, status=${state.status}, execution_mode=${state.executionMode}, pending_gate=${pendingGate}, role_fallback=${roleFallback}, role_format=${roleFormat}`;
|
|
125
181
|
}
|
|
126
182
|
function success(text, structuredContent) {
|
|
127
183
|
return {
|
|
@@ -141,29 +197,73 @@ function failure(error) {
|
|
|
141
197
|
export async function callDevCrewTool(name, args) {
|
|
142
198
|
try {
|
|
143
199
|
if (name === "devcrew_start") {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
200
|
+
return await withMutationLock(args, async () => {
|
|
201
|
+
const state = await startOrchestratedWorkflow(withInferredHost(args));
|
|
202
|
+
await setActiveRun(state.cwd, state.runId);
|
|
203
|
+
return success(`${summarizeState(state)}. Review ${state.artifacts.requirements}`, { state });
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
if (name === "devcrew_abort") {
|
|
207
|
+
return await withMutationLock(args, async () => {
|
|
208
|
+
const runArgs = await withActiveRun(args);
|
|
209
|
+
const state = await abortOrchestratedWorkflow(runArgs);
|
|
210
|
+
await clearActiveRunIfMatches(state.cwd, state.runId);
|
|
211
|
+
return success(`${summarizeState(state)}. Run aborted.`, { state });
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
if (name === "devcrew_recover") {
|
|
215
|
+
if (typeof args.cwd !== "string" || !args.cwd.trim()) {
|
|
216
|
+
throw new Error("cwd must be a non-empty string");
|
|
217
|
+
}
|
|
218
|
+
const recoveredLock = await recoverRepositoryLock(args.cwd);
|
|
219
|
+
if (typeof args.runId !== "string" || !args.runId.trim()) {
|
|
220
|
+
return success(recoveredLock ? "Repository lock recovery completed." : "No stale repository lock was present.", { recoveredLock });
|
|
221
|
+
}
|
|
222
|
+
return await withMutationLock(args, async () => {
|
|
223
|
+
const runArgs = await withActiveRun(args);
|
|
224
|
+
const state = await recoverOrchestratedWorkflow(runArgs);
|
|
225
|
+
return success(`${summarizeState(state)}. Recovery cleanup completed.`, { state });
|
|
226
|
+
});
|
|
147
227
|
}
|
|
148
228
|
if (name === "devcrew_status") {
|
|
149
229
|
const state = await getWorkflowStatus((await withActiveRun(args)));
|
|
150
230
|
return success(summarizeState(state), { state });
|
|
151
231
|
}
|
|
152
232
|
if (name === "devcrew_answer") {
|
|
153
|
-
|
|
154
|
-
|
|
233
|
+
return await withMutationLock(args, async () => {
|
|
234
|
+
const state = await answerOrchestratedWorkflow((await withActiveRun(args)));
|
|
235
|
+
return success(`${summarizeState(state)}. Answer recorded.`, { state });
|
|
236
|
+
});
|
|
155
237
|
}
|
|
156
238
|
if (name === "devcrew_approve") {
|
|
157
|
-
|
|
158
|
-
|
|
239
|
+
return await withMutationLock(args, async () => {
|
|
240
|
+
const state = await approveOrchestratedWorkflow((await withActiveRun(args)));
|
|
241
|
+
return success(`${summarizeState(state)}. Gate approved.`, { state });
|
|
242
|
+
});
|
|
159
243
|
}
|
|
160
244
|
if (name === "devcrew_reject") {
|
|
161
|
-
|
|
162
|
-
|
|
245
|
+
return await withMutationLock(args, async () => {
|
|
246
|
+
const state = await rejectOrchestratedWorkflow((await withActiveRun(args)));
|
|
247
|
+
return success(`${summarizeState(state)}. Gate rejected.`, { state });
|
|
248
|
+
});
|
|
163
249
|
}
|
|
164
250
|
if (name === "devcrew_continue") {
|
|
165
|
-
|
|
166
|
-
|
|
251
|
+
return await withMutationLock(args, async () => {
|
|
252
|
+
const state = await continueOrchestratedWorkflow((await withActiveRun(args)));
|
|
253
|
+
return success(`${summarizeState(state)}.`, { state });
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
if (name === "devcrew_complete_execution") {
|
|
257
|
+
return await withMutationLock(args, async () => {
|
|
258
|
+
const state = await completeOrchestratedExecution((await withActiveRun(args)));
|
|
259
|
+
return success(`${summarizeState(state)}. Native host completion recorded.`, { state });
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
if (name === "devcrew_waive_verification") {
|
|
263
|
+
return await withMutationLock(args, async () => {
|
|
264
|
+
const state = await waiveOrchestratedVerification((await withActiveRun(args)));
|
|
265
|
+
return success(`${summarizeState(state)}. Verification waiver recorded.`, { state });
|
|
266
|
+
});
|
|
167
267
|
}
|
|
168
268
|
if (name === "devcrew_artifact") {
|
|
169
269
|
const artifact = await getArtifact((await withActiveRun(args)));
|
package/docs/claude-code.md
CHANGED
|
@@ -17,18 +17,33 @@ 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
|
+
|
|
25
|
+
## Structured role results
|
|
26
|
+
|
|
27
|
+
SDK roles should return one `<!-- devcrew-role-result -->` marker followed by a
|
|
28
|
+
JSON fenced block with `schemaVersion: 1`, then the human-readable Markdown
|
|
29
|
+
artifact. The JSON carries the role summary, risks, command evidence, and
|
|
30
|
+
role-specific fields such as PM questions, architecture decisions, changed
|
|
31
|
+
files, or test cases. DevCrew removes the marked block before saving the
|
|
32
|
+
Markdown artifact and exposes the validated data through MCP state.
|
|
33
|
+
|
|
34
|
+
Markdown-only output remains supported as `role_format=legacy`. A missing marker
|
|
35
|
+
uses this compatibility path; a present marker with malformed, ambiguous, or
|
|
36
|
+
schema-invalid JSON is rejected and never silently downgraded.
|
|
37
|
+
|
|
23
38
|
For local plugin testing:
|
|
24
39
|
|
|
25
40
|
```bash
|
|
26
41
|
claude --plugin-dir plugins/devcrew-claude
|
|
27
42
|
```
|
|
28
43
|
|
|
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.
|
|
44
|
+
Then ask Claude Code to use DevCrew for a feature or product workflow. The generated `.mcp.json` starts the version-locked npm package with an `npm exec --package=@shenlee/devcrew@0.1.4` wrapper.
|
|
30
45
|
|
|
31
|
-
Claude Code
|
|
46
|
+
The default apply policy is `interactive-host`: Claude Code performs implementation and testing with its native controls. Explicit `headless-restricted` and `headless-unattended` policies are independent DevCrew SDK policies and do not inherit the current Claude Code approval session.
|
|
32
47
|
|
|
33
48
|
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
49
|
|
package/docs/codex.md
CHANGED
|
@@ -13,7 +13,7 @@ Restart Codex, open the plugin directory, select the DevCrew marketplace, and in
|
|
|
13
13
|
The plugin launches the MCP server with:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
npm exec --silent --yes --package=@shenlee/devcrew@0.1.
|
|
16
|
+
npm exec --silent --yes --package=@shenlee/devcrew@0.1.4 -- node -e "<DevCrew CLI wrapper>" -- serve --stdio
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
Use this path when you want to use DevCrew without cloning the repository first. The version is locked to the published npm package that matches the plugin manifest.
|
|
@@ -38,7 +38,22 @@ 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.
|
|
44
|
+
|
|
45
|
+
## Structured role results
|
|
46
|
+
|
|
47
|
+
SDK roles should return one `<!-- devcrew-role-result -->` marker followed by a
|
|
48
|
+
JSON fenced block with `schemaVersion: 1`, then the human-readable Markdown
|
|
49
|
+
artifact. The JSON carries the role summary, risks, command evidence, and
|
|
50
|
+
role-specific fields such as PM questions, architecture decisions, changed
|
|
51
|
+
files, or test cases. DevCrew removes the marked block before saving the
|
|
52
|
+
Markdown artifact and exposes the validated data through MCP state.
|
|
53
|
+
|
|
54
|
+
Markdown-only output remains supported as `role_format=legacy`. A missing marker
|
|
55
|
+
uses this compatibility path; a present marker with malformed, ambiguous, or
|
|
56
|
+
schema-invalid JSON is rejected and never silently downgraded.
|
|
42
57
|
|
|
43
58
|
For local development, point a Codex marketplace entry at the generated plugin folder, or copy the plugin into your existing local marketplace workflow.
|
|
44
59
|
|
|
@@ -50,11 +65,13 @@ The DevCrew skill tells Codex to use these MCP tools:
|
|
|
50
65
|
- `devcrew_approve`
|
|
51
66
|
- `devcrew_reject`
|
|
52
67
|
- `devcrew_continue`
|
|
68
|
+
- `devcrew_complete_execution`
|
|
69
|
+
- `devcrew_waive_verification`
|
|
53
70
|
- `devcrew_artifact`
|
|
54
71
|
|
|
55
|
-
Codex
|
|
72
|
+
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.
|
|
56
73
|
|
|
57
|
-
Apply mode keeps implementation planning read-only.
|
|
74
|
+
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.
|
|
58
75
|
|
|
59
76
|
Apply mode requires a Git repository, a clean requester worktree at execution and promotion, and a real Codex SDK backend.
|
|
60
77
|
|
|
@@ -66,7 +83,7 @@ npm install -g @shenlee/devcrew --include=optional
|
|
|
66
83
|
|
|
67
84
|
## Marketplace smoke test
|
|
68
85
|
|
|
69
|
-
After publishing `@shenlee/devcrew@0.1.
|
|
86
|
+
After publishing `@shenlee/devcrew@0.1.4`, run the real marketplace smoke test. Do not treat this as a pre-publication check because the plugin is locked to that npm version:
|
|
70
87
|
|
|
71
88
|
```bash
|
|
72
89
|
npm run smoke:codex-plugin
|
package/docs/quickstart.md
CHANGED
|
@@ -37,7 +37,7 @@ devcrew serve --stdio
|
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
Normally the generated plugin starts this command for the host agent.
|
|
40
|
-
Codex and Claude plugin bundles use a version-locked `npm exec --package=@shenlee/devcrew@0.1.
|
|
40
|
+
Codex and Claude plugin bundles use a version-locked `npm exec --package=@shenlee/devcrew@0.1.4` wrapper so the MCP service is locked to the published package version.
|
|
41
41
|
|
|
42
42
|
## 4. Run A Workflow
|
|
43
43
|
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# Safety Semantics Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Make apply-mode execution policy explicit and prevent failed or absent verification from silently promoting an isolated patch.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Add a persistent execution policy and verification outcome to the workflow state. Interactive apply runs pause with a host-execution handoff instead of launching a nested SDK; explicitly selected headless policies retain SDK execution with declared permissions. Testing only opens a normal promotion gate after successful verification; a separate waiver records the reason before an exception can be approved.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript, Node.js test runner, MCP JSON-RPC tools, Git worktrees.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## File Structure
|
|
14
|
+
|
|
15
|
+
- Modify: `packages/core/src/types.ts` — policy, verification, handoff, and waiver state contracts.
|
|
16
|
+
- Modify: `packages/core/src/validation.ts` — parse policy and waiver input.
|
|
17
|
+
- Modify: `packages/core/src/workflow.ts` — default/apply-policy invariants and waiver transition.
|
|
18
|
+
- Modify: `packages/core/src/store.ts` — migrate persisted legacy runs safely.
|
|
19
|
+
- Modify: `packages/adapters/src/index.ts` — explicit headless SDK options; no implicit approval inheritance.
|
|
20
|
+
- Modify: `packages/orchestrator/src/index.ts` — interactive execution pause/submission and verification gate blocking.
|
|
21
|
+
- Modify: `packages/service/src/tools.ts` — MCP schemas and handlers for execution completion and verification waiver.
|
|
22
|
+
- Modify: `packages/core/src/artifacts.ts` — render policy, verification status, and waiver evidence.
|
|
23
|
+
- Modify: `tests/core.test.ts`, `tests/adapters-sdk.test.ts`, `tests/orchestrator.test.ts`, `tests/service.test.ts` — red/green coverage for every new state transition.
|
|
24
|
+
- Modify: `README.md`, `docs/workflow.md`, `docs/codex.md`, `docs/claude-code.md`, `packages/plugins/src/index.ts` — correct public behavior and generated skill copy.
|
|
25
|
+
|
|
26
|
+
### Task 1: Persist explicit execution and verification semantics
|
|
27
|
+
|
|
28
|
+
**Files:**
|
|
29
|
+
- Modify: `packages/core/src/types.ts`
|
|
30
|
+
- Modify: `packages/core/src/validation.ts`
|
|
31
|
+
- Modify: `packages/core/src/store.ts`
|
|
32
|
+
- Test: `tests/core.test.ts`
|
|
33
|
+
|
|
34
|
+
- [ ] **Step 1: Write failing state-contract tests**
|
|
35
|
+
|
|
36
|
+
Add tests that start an apply workflow with `executionPolicy: "headless-restricted"`, assert the policy persists through `loadState`, and assert unknown policy values fail validation. Add a migration fixture without the new fields and assert it loads as an interactive-host apply run with `verificationStatus: "not_run"` and no waiver.
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
assert.equal(state.executionPolicy, "headless-restricted");
|
|
40
|
+
await assert.rejects(
|
|
41
|
+
() => startWorkflow({ ...input, executionMode: "apply", executionPolicy: "unsafe" }),
|
|
42
|
+
/executionPolicy/,
|
|
43
|
+
);
|
|
44
|
+
assert.equal(loaded.verificationStatus, "not_run");
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
- [ ] **Step 2: Run the focused core test and verify RED**
|
|
48
|
+
|
|
49
|
+
Run: `node --import tsx --test tests/core.test.ts`
|
|
50
|
+
|
|
51
|
+
Expected: FAIL because `executionPolicy` and `verificationStatus` are not yet represented in state or input validation.
|
|
52
|
+
|
|
53
|
+
- [ ] **Step 3: Add the minimal state and parsing contracts**
|
|
54
|
+
|
|
55
|
+
In `types.ts`, add:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
export const EXECUTION_POLICIES = ["interactive-host", "headless-restricted", "headless-unattended"] as const;
|
|
59
|
+
export type ExecutionPolicy = (typeof EXECUTION_POLICIES)[number];
|
|
60
|
+
export type VerificationStatus = "not_run" | "passed" | "failed";
|
|
61
|
+
|
|
62
|
+
export interface VerificationWaiver { reason: string; createdAt: string; }
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Add `executionPolicy` to `StartWorkflowInput` and `RunState`, plus
|
|
66
|
+
`verificationStatus` and optional `verificationWaiver` to `RunState`. Add
|
|
67
|
+
`parseExecutionPolicy()` and `parseWaiverReason()` in `validation.ts`. In
|
|
68
|
+
`loadState`, use `interactive-host` for legacy apply runs and `not_run` for
|
|
69
|
+
missing verification status.
|
|
70
|
+
|
|
71
|
+
- [ ] **Step 4: Run the focused core test and verify GREEN**
|
|
72
|
+
|
|
73
|
+
Run: `node --import tsx --test tests/core.test.ts`
|
|
74
|
+
|
|
75
|
+
Expected: PASS, including new persistence and invalid-policy cases.
|
|
76
|
+
|
|
77
|
+
- [ ] **Step 5: Commit the state contract**
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
git add packages/core/src/types.ts packages/core/src/validation.ts packages/core/src/store.ts tests/core.test.ts
|
|
81
|
+
git commit -m "feat: record execution and verification policy"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Task 2: Make SDK execution explicitly headless
|
|
85
|
+
|
|
86
|
+
**Files:**
|
|
87
|
+
- Modify: `packages/adapters/src/index.ts`
|
|
88
|
+
- Test: `tests/adapters-sdk.test.ts`
|
|
89
|
+
|
|
90
|
+
- [ ] **Step 1: Write failing adapter-policy tests**
|
|
91
|
+
|
|
92
|
+
Replace the assertion that Claude apply uses `acceptEdits` with tests proving
|
|
93
|
+
that an interactive-host execution is rejected before SDK invocation and that
|
|
94
|
+
headless policies select explicit SDK options. For Claude restricted mode, the
|
|
95
|
+
test must assert `permissionMode: "dontAsk"` and a narrow tool list. For Codex,
|
|
96
|
+
assert `approvalPolicy: "never"` only for `headless-unattended` and no implicit
|
|
97
|
+
approval-policy value for read-only planning.
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
await assert.rejects(
|
|
101
|
+
() => runRole({ ...baseInput, phase: "execution", executionMode: "apply", executionPolicy: "interactive-host", backend: "claude" }),
|
|
102
|
+
/interactive-host execution must be performed by the host/,
|
|
103
|
+
);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
- [ ] **Step 2: Run the focused adapter test and verify RED**
|
|
107
|
+
|
|
108
|
+
Run: `node --import tsx --test tests/adapters-sdk.test.ts`
|
|
109
|
+
|
|
110
|
+
Expected: FAIL because `RoleRunInput` lacks the policy and Claude currently
|
|
111
|
+
sets `acceptEdits` with bare `Bash` permission.
|
|
112
|
+
|
|
113
|
+
- [ ] **Step 3: Implement explicit headless profiles**
|
|
114
|
+
|
|
115
|
+
Add `executionPolicy` to `RoleRunInput`. Make `roleCanApply()` require a
|
|
116
|
+
headless policy. Reject SDK execution for `interactive-host`. Replace the
|
|
117
|
+
Claude restricted profile with `dontAsk`: implementers receive only `Read`,
|
|
118
|
+
`Grep`, `Glob`, `Edit`, and `Write`; testers receive only `Read`, `Grep`, and
|
|
119
|
+
`Glob`. Neither restricted role receives `Bash`; DevCrew's configured command
|
|
120
|
+
runner supplies verification evidence separately. For the explicitly selected
|
|
121
|
+
unattended policy, add the SDK's dangerous-skip opt-in and set Claude to
|
|
122
|
+
`bypassPermissions`; this is an auditable CI-only policy. Pass Codex
|
|
123
|
+
`approvalPolicy: "never"` only for that unattended policy. Document that both
|
|
124
|
+
headless profiles are DevCrew policies, not inherited host approval.
|
|
125
|
+
|
|
126
|
+
- [ ] **Step 4: Run the focused adapter test and verify GREEN**
|
|
127
|
+
|
|
128
|
+
Run: `node --import tsx --test tests/adapters-sdk.test.ts`
|
|
129
|
+
|
|
130
|
+
Expected: PASS with no `acceptEdits` assertion remaining.
|
|
131
|
+
|
|
132
|
+
- [ ] **Step 5: Commit the adapter boundary**
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
git add packages/adapters/src/index.ts tests/adapters-sdk.test.ts
|
|
136
|
+
git commit -m "fix: make SDK apply policies explicit"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Task 3: Add the interactive execution handoff
|
|
140
|
+
|
|
141
|
+
**Files:**
|
|
142
|
+
- Modify: `packages/orchestrator/src/index.ts`
|
|
143
|
+
- Modify: `packages/service/src/tools.ts`
|
|
144
|
+
- Test: `tests/orchestrator.test.ts`
|
|
145
|
+
- Test: `tests/service.test.ts`
|
|
146
|
+
|
|
147
|
+
- [ ] **Step 1: Write failing handoff tests**
|
|
148
|
+
|
|
149
|
+
Add an apply workflow with `executionPolicy: "interactive-host"`. After the
|
|
150
|
+
implementation gate is approved, `devcrew_continue` must create the isolated
|
|
151
|
+
worktree but not call the injected `RoleRunner`; it returns
|
|
152
|
+
`status: "awaiting_execution"` and structured instruction data. A new
|
|
153
|
+
`devcrew_complete_execution` call captures the worktree patch and advances to
|
|
154
|
+
testing without modifying the requester repository. Repeat the same handoff
|
|
155
|
+
for the tester: it receives the test-report instruction, returns exact command
|
|
156
|
+
result records, and DevCrew derives the verification status from those records.
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
assert.equal(paused.status, "awaiting_execution");
|
|
160
|
+
assert.equal(runnerCalls.filter((call) => call.phase === "execution").length, 0);
|
|
161
|
+
assert.equal(paused.executionInstruction?.cwd, paused.executionWorkspace?.path);
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
- [ ] **Step 2: Run focused orchestrator and service tests and verify RED**
|
|
165
|
+
|
|
166
|
+
Run: `node --import tsx --test tests/orchestrator.test.ts tests/service.test.ts`
|
|
167
|
+
|
|
168
|
+
Expected: FAIL because the current orchestrator immediately invokes the SDK
|
|
169
|
+
runner and no completion MCP tool exists.
|
|
170
|
+
|
|
171
|
+
- [ ] **Step 3: Implement pause and completion transitions**
|
|
172
|
+
|
|
173
|
+
Add `awaiting_execution` to `RunStatus` and an `ExecutionInstruction` state
|
|
174
|
+
object containing role, phase, worktree cwd, request, standards, approved
|
|
175
|
+
artifacts, and required result sections. In the execution and testing branches
|
|
176
|
+
of `runCurrentPhaseRole`, ensure the worktree, write the instruction, persist
|
|
177
|
+
the state, and return without calling `runner` or `runConfiguredVerification`
|
|
178
|
+
when policy is `interactive-host`.
|
|
179
|
+
|
|
180
|
+
Add `completeInteractiveExecution(input)` to orchestrator. It requires an
|
|
181
|
+
`awaiting_execution` state and a required Markdown summary supplied by the
|
|
182
|
+
host. For execution it captures changes, runs no nested SDK or shell command,
|
|
183
|
+
writes implementation-review evidence, and moves to `testing/ready`. For
|
|
184
|
+
testing it accepts a validated array of `{ command, exitCode, output,
|
|
185
|
+
startedAt, completedAt }`, writes the test report, derives the verification
|
|
186
|
+
status, and either opens the gate or enters the failure path. Expose it as
|
|
187
|
+
`devcrew_complete_execution` with an input schema that includes `summary` and
|
|
188
|
+
optional `verification` records.
|
|
189
|
+
|
|
190
|
+
- [ ] **Step 4: Run focused handoff tests and verify GREEN**
|
|
191
|
+
|
|
192
|
+
Run: `node --import tsx --test tests/orchestrator.test.ts tests/service.test.ts`
|
|
193
|
+
|
|
194
|
+
Expected: PASS; the requester repository remains unchanged until a later
|
|
195
|
+
testing approval.
|
|
196
|
+
|
|
197
|
+
- [ ] **Step 5: Commit the interactive handoff**
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
git add packages/orchestrator/src/index.ts packages/service/src/tools.ts tests/orchestrator.test.ts tests/service.test.ts
|
|
201
|
+
git commit -m "feat: hand interactive apply execution to host"
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Task 4: Block failed verification and require a recorded waiver
|
|
205
|
+
|
|
206
|
+
**Files:**
|
|
207
|
+
- Modify: `packages/core/src/workflow.ts`
|
|
208
|
+
- Modify: `packages/orchestrator/src/index.ts`
|
|
209
|
+
- Modify: `packages/service/src/tools.ts`
|
|
210
|
+
- Modify: `packages/core/src/artifacts.ts`
|
|
211
|
+
- Test: `tests/orchestrator.test.ts`
|
|
212
|
+
- Test: `tests/service.test.ts`
|
|
213
|
+
|
|
214
|
+
- [ ] **Step 1: Write failing verification tests**
|
|
215
|
+
|
|
216
|
+
Create an apply fixture with `verifyCommands: ["node -e \"process.exit(7)\""]`.
|
|
217
|
+
After testing, assert `verificationStatus === "failed"`, `status ===
|
|
218
|
+
"awaiting_input"`, and that `approveOrchestratedWorkflow({ gate: "testing" })`
|
|
219
|
+
rejects without promoting the patch. Add a second test showing that
|
|
220
|
+
`devcrew_waive_verification` rejects an empty reason, persists a non-empty
|
|
221
|
+
reason, reopens the testing gate, and permits promotion only after normal
|
|
222
|
+
testing approval.
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
await assert.rejects(
|
|
226
|
+
() => approveOrchestratedWorkflow({ cwd, runId, gate: "testing" }),
|
|
227
|
+
/verification failed/,
|
|
228
|
+
);
|
|
229
|
+
assert.equal(waived.verificationWaiver?.reason, "Known upstream test outage");
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
- [ ] **Step 2: Run focused workflow tests and verify RED**
|
|
233
|
+
|
|
234
|
+
Run: `node --import tsx --test tests/orchestrator.test.ts tests/service.test.ts`
|
|
235
|
+
|
|
236
|
+
Expected: FAIL because a nonzero command currently still opens the pending
|
|
237
|
+
testing gate and no waiver tool exists.
|
|
238
|
+
|
|
239
|
+
- [ ] **Step 3: Implement verification outcome and waiver flow**
|
|
240
|
+
|
|
241
|
+
After `runConfiguredVerification`, set `verificationStatus` from every result:
|
|
242
|
+
empty is `not_run`, all-zero is `passed`, otherwise `failed`. A failed status
|
|
243
|
+
sets `awaiting_input` and does not set the testing gate to pending. Add
|
|
244
|
+
`waiveVerification(input)` that requires failed verification and a non-empty
|
|
245
|
+
reason, records `VerificationWaiver`, changes the testing gate to pending, and
|
|
246
|
+
sets `awaiting_approval`. Add `devcrew_waive_verification` to the MCP schema
|
|
247
|
+
and handler.
|
|
248
|
+
|
|
249
|
+
Make `approveOrchestratedWorkflow` reject a testing promotion unless status is
|
|
250
|
+
passed or a waiver exists. Render the status, command exit evidence, and waiver
|
|
251
|
+
reason in both the test report and acceptance artifact.
|
|
252
|
+
|
|
253
|
+
- [ ] **Step 4: Run focused verification tests and verify GREEN**
|
|
254
|
+
|
|
255
|
+
Run: `node --import tsx --test tests/orchestrator.test.ts tests/service.test.ts`
|
|
256
|
+
|
|
257
|
+
Expected: PASS; failed verification cannot mutate the requester repository,
|
|
258
|
+
while a recorded waiver remains visible and permits intentional approval.
|
|
259
|
+
|
|
260
|
+
- [ ] **Step 5: Commit verification safety**
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
git add packages/core/src/workflow.ts packages/orchestrator/src/index.ts packages/service/src/tools.ts packages/core/src/artifacts.ts tests/orchestrator.test.ts tests/service.test.ts
|
|
264
|
+
git commit -m "fix: block failed verification promotion"
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Task 5: Correct generated instructions and user-facing documentation
|
|
268
|
+
|
|
269
|
+
**Files:**
|
|
270
|
+
- Modify: `README.md`
|
|
271
|
+
- Modify: `docs/workflow.md`
|
|
272
|
+
- Modify: `docs/codex.md`
|
|
273
|
+
- Modify: `docs/claude-code.md`
|
|
274
|
+
- Modify: `packages/plugins/src/index.ts`
|
|
275
|
+
- Test: `tests/plugins.test.ts`
|
|
276
|
+
|
|
277
|
+
- [ ] **Step 1: Write failing generated-skill/doc assertions**
|
|
278
|
+
|
|
279
|
+
Extend plugin tests to require the generated skill to distinguish
|
|
280
|
+
interactive-host execution from headless policy and to mention
|
|
281
|
+
`devcrew_complete_execution` and `devcrew_waive_verification`.
|
|
282
|
+
|
|
283
|
+
- [ ] **Step 2: Run plugin tests and verify RED**
|
|
284
|
+
|
|
285
|
+
Run: `node --import tsx --test tests/plugins.test.ts`
|
|
286
|
+
|
|
287
|
+
Expected: FAIL because the generated skill promises inherited host approvals
|
|
288
|
+
and lacks the new handoff/waiver tools.
|
|
289
|
+
|
|
290
|
+
- [ ] **Step 3: Update generated skill and docs**
|
|
291
|
+
|
|
292
|
+
Document that interactive host execution uses the host's native agent after
|
|
293
|
+
DevCrew supplies a worktree instruction; it does not run a nested SDK. Document
|
|
294
|
+
headless policies as explicit DevCrew policies. Replace all claims that DevCrew
|
|
295
|
+
inherits a current host approval boundary. Add the failed-verification and
|
|
296
|
+
recorded-waiver transition to workflow diagrams and tool lists.
|
|
297
|
+
|
|
298
|
+
- [ ] **Step 4: Run plugin tests and verify GREEN**
|
|
299
|
+
|
|
300
|
+
Run: `node --import tsx --test tests/plugins.test.ts`
|
|
301
|
+
|
|
302
|
+
Expected: PASS with the checked-in plugin matching regenerated text.
|
|
303
|
+
|
|
304
|
+
- [ ] **Step 5: Run full validation and commit documentation**
|
|
305
|
+
|
|
306
|
+
Run: `npm run validate`
|
|
307
|
+
|
|
308
|
+
Expected: exit code 0 with all tests passing.
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
git add README.md docs/workflow.md docs/codex.md docs/claude-code.md packages/plugins/src/index.ts plugins/devcrew-codex plugins/devcrew-claude tests/plugins.test.ts
|
|
312
|
+
git commit -m "docs: describe explicit apply execution policies"
|
|
313
|
+
```
|