@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
|
@@ -10,12 +10,14 @@ import {
|
|
|
10
10
|
parseBackend,
|
|
11
11
|
parseCwd,
|
|
12
12
|
parseExecutionMode,
|
|
13
|
+
parseExecutionPolicy,
|
|
13
14
|
parseFeedback,
|
|
14
15
|
parseGate,
|
|
15
16
|
parseHost,
|
|
16
17
|
parseOptionalNote,
|
|
17
18
|
parseRequest,
|
|
18
19
|
parseRunId,
|
|
20
|
+
parseWaiverReason,
|
|
19
21
|
parseWorkflowMode,
|
|
20
22
|
} from "./validation.js";
|
|
21
23
|
import type {
|
|
@@ -29,7 +31,9 @@ import type {
|
|
|
29
31
|
RunRef,
|
|
30
32
|
RunState,
|
|
31
33
|
StartWorkflowInput,
|
|
34
|
+
WaiveVerificationInput,
|
|
32
35
|
} from "./types.js";
|
|
36
|
+
import { GATES } from "./types.js";
|
|
33
37
|
|
|
34
38
|
function now(): string {
|
|
35
39
|
return new Date().toISOString();
|
|
@@ -39,14 +43,19 @@ function newRunId(): string {
|
|
|
39
43
|
return `dc-${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`;
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
export function nextPhaseAfterGate(gate: GateName): RunState["phase"] {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
export function nextPhaseAfterGate(state: RunState, gate: GateName): RunState["phase"] {
|
|
47
|
+
switch (gate) {
|
|
48
|
+
case "requirements":
|
|
49
|
+
return "architecture";
|
|
50
|
+
case "architecture":
|
|
51
|
+
return "implementation";
|
|
52
|
+
case "implementation":
|
|
53
|
+
return state.executionMode === "apply" ? "execution" : "testing";
|
|
54
|
+
case "implementation-review":
|
|
55
|
+
return "testing";
|
|
56
|
+
case "testing":
|
|
57
|
+
return "acceptance";
|
|
58
|
+
}
|
|
50
59
|
}
|
|
51
60
|
|
|
52
61
|
export function artifactForPhase(phase: RunState["phase"]): ArtifactName {
|
|
@@ -54,6 +63,8 @@ export function artifactForPhase(phase: RunState["phase"]): ArtifactName {
|
|
|
54
63
|
requirements: "requirements",
|
|
55
64
|
architecture: "architecture",
|
|
56
65
|
implementation: "implementation-plan",
|
|
66
|
+
execution: "implementation-review",
|
|
67
|
+
review: "architecture-review",
|
|
57
68
|
testing: "test-report",
|
|
58
69
|
acceptance: "acceptance",
|
|
59
70
|
complete: "acceptance",
|
|
@@ -61,17 +72,40 @@ export function artifactForPhase(phase: RunState["phase"]): ArtifactName {
|
|
|
61
72
|
return artifactByPhase[phase];
|
|
62
73
|
}
|
|
63
74
|
|
|
64
|
-
export function gateForPhase(phase: RunState["phase"]): GateName | undefined {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
75
|
+
export function gateForPhase(phase: RunState["phase"], enabledGates?: readonly GateName[]): GateName | undefined {
|
|
76
|
+
const gate = phase === "review"
|
|
77
|
+
? "implementation-review"
|
|
78
|
+
: (
|
|
79
|
+
phase === "requirements" ||
|
|
80
|
+
phase === "architecture" ||
|
|
81
|
+
phase === "implementation" ||
|
|
82
|
+
phase === "testing"
|
|
83
|
+
? phase
|
|
84
|
+
: undefined
|
|
85
|
+
);
|
|
86
|
+
return gate && (!enabledGates || enabledGates.includes(gate)) ? gate : undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function configuredGates(gates: readonly GateName[]): GateName[] {
|
|
90
|
+
const configured = new Set(
|
|
91
|
+
gates.filter((gate): gate is GateName => GATES.includes(gate)),
|
|
92
|
+
);
|
|
93
|
+
configured.add("implementation-review");
|
|
94
|
+
configured.add("testing");
|
|
95
|
+
return [...configured];
|
|
69
96
|
}
|
|
70
97
|
|
|
71
|
-
function
|
|
72
|
-
const expected = gateForPhase(state.phase);
|
|
73
|
-
if (expected
|
|
74
|
-
throw new Error(
|
|
98
|
+
function assertPendingCurrentGate(state: RunState, gate: GateName): void {
|
|
99
|
+
const expected = gateForPhase(state.phase, state.enabledGates);
|
|
100
|
+
if (expected !== gate) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
expected
|
|
103
|
+
? `Cannot act on ${gate} while current gate is ${expected}`
|
|
104
|
+
: `Cannot act on ${gate} while workflow phase is ${state.phase}`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
if (state.status !== "awaiting_approval" || state.gates[gate] !== "pending") {
|
|
108
|
+
throw new Error(`Gate ${gate} is not pending approval`);
|
|
75
109
|
}
|
|
76
110
|
}
|
|
77
111
|
|
|
@@ -94,6 +128,11 @@ export async function startWorkflow(input: StartWorkflowInput, options: Workflow
|
|
|
94
128
|
const config = await ensureConfig(cwd);
|
|
95
129
|
const backend = input.backend ? parseBackend(input.backend) : config.defaultBackend === "host-preferred" ? host : config.defaultBackend;
|
|
96
130
|
const executionMode = input.executionMode ? parseExecutionMode(input.executionMode) : config.executionMode;
|
|
131
|
+
const executionPolicy = input.executionPolicy ? parseExecutionPolicy(input.executionPolicy) : "interactive-host";
|
|
132
|
+
const enabledGates = configuredGates(config.workflow.gates);
|
|
133
|
+
if (executionMode === "apply" && backend === "local") {
|
|
134
|
+
throw new Error("DevCrew apply mode requires a codex or claude backend; local is plan-only");
|
|
135
|
+
}
|
|
97
136
|
const createdAt = now();
|
|
98
137
|
const state: RunState = {
|
|
99
138
|
version: 1,
|
|
@@ -102,20 +141,25 @@ export async function startWorkflow(input: StartWorkflowInput, options: Workflow
|
|
|
102
141
|
host,
|
|
103
142
|
mode,
|
|
104
143
|
executionMode,
|
|
144
|
+
executionPolicy,
|
|
145
|
+
enabledGates,
|
|
146
|
+
artifactDirectory: config.workflow.artifactDirectory,
|
|
105
147
|
backend,
|
|
106
148
|
request,
|
|
107
149
|
phase: "requirements",
|
|
108
|
-
status: "awaiting_approval",
|
|
150
|
+
status: enabledGates.includes("requirements") ? "awaiting_approval" : "ready",
|
|
109
151
|
createdAt,
|
|
110
152
|
updatedAt: createdAt,
|
|
111
153
|
gates: {
|
|
112
|
-
requirements: "pending",
|
|
154
|
+
requirements: enabledGates.includes("requirements") ? "pending" : "not_started",
|
|
113
155
|
architecture: "not_started",
|
|
114
156
|
implementation: "not_started",
|
|
157
|
+
"implementation-review": "not_started",
|
|
115
158
|
testing: "not_started",
|
|
116
159
|
},
|
|
117
160
|
artifacts: {},
|
|
118
161
|
roles: [],
|
|
162
|
+
pendingQuestions: [],
|
|
119
163
|
answers: [],
|
|
120
164
|
approvals: [],
|
|
121
165
|
feedback: [],
|
|
@@ -123,6 +167,7 @@ export async function startWorkflow(input: StartWorkflowInput, options: Workflow
|
|
|
123
167
|
changedFiles: [],
|
|
124
168
|
implementationDiff: "",
|
|
125
169
|
verification: [],
|
|
170
|
+
verificationStatus: "not_run",
|
|
126
171
|
lintResults: [],
|
|
127
172
|
};
|
|
128
173
|
|
|
@@ -138,7 +183,12 @@ export async function getWorkflowStatus(input: RunRef): Promise<RunState> {
|
|
|
138
183
|
|
|
139
184
|
export async function continueWorkflow(input: RunRef): Promise<RunState> {
|
|
140
185
|
const state = await getWorkflowStatus(input);
|
|
141
|
-
if (
|
|
186
|
+
if (
|
|
187
|
+
state.status === "awaiting_approval" ||
|
|
188
|
+
state.status === "awaiting_input" ||
|
|
189
|
+
state.status === "awaiting_execution" ||
|
|
190
|
+
state.status === "complete"
|
|
191
|
+
) {
|
|
142
192
|
return state;
|
|
143
193
|
}
|
|
144
194
|
|
|
@@ -149,8 +199,18 @@ export async function continueWorkflow(input: RunRef): Promise<RunState> {
|
|
|
149
199
|
return saveState(state);
|
|
150
200
|
}
|
|
151
201
|
|
|
152
|
-
|
|
202
|
+
if (state.phase === "execution") {
|
|
203
|
+
throw new Error("DevCrew execution phase requires orchestrated continuation");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const configuredGate = gateForPhase(state.phase);
|
|
207
|
+
const gate = gateForPhase(state.phase, state.enabledGates);
|
|
153
208
|
if (!gate) {
|
|
209
|
+
if (configuredGate) {
|
|
210
|
+
state.phase = nextPhaseAfterGate(state, configuredGate);
|
|
211
|
+
state.status = "ready";
|
|
212
|
+
return saveState(state);
|
|
213
|
+
}
|
|
154
214
|
state.status = "complete";
|
|
155
215
|
return saveState(state);
|
|
156
216
|
}
|
|
@@ -160,10 +220,23 @@ export async function continueWorkflow(input: RunRef): Promise<RunState> {
|
|
|
160
220
|
return saveState(state);
|
|
161
221
|
}
|
|
162
222
|
|
|
163
|
-
export async function
|
|
223
|
+
export async function validateWorkflowApproval(input: ApproveWorkflowInput): Promise<RunState> {
|
|
164
224
|
const state = await getWorkflowStatus(input);
|
|
165
225
|
const gate = parseGate(input.gate);
|
|
166
|
-
|
|
226
|
+
if (state.gates[gate] === "approved") {
|
|
227
|
+
return state;
|
|
228
|
+
}
|
|
229
|
+
assertPendingCurrentGate(state, gate);
|
|
230
|
+
parseOptionalNote(input.note);
|
|
231
|
+
return state;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export async function approveWorkflow(input: ApproveWorkflowInput): Promise<RunState> {
|
|
235
|
+
const state = await validateWorkflowApproval(input);
|
|
236
|
+
const gate = parseGate(input.gate);
|
|
237
|
+
if (state.gates[gate] === "approved") {
|
|
238
|
+
return state;
|
|
239
|
+
}
|
|
167
240
|
state.gates[gate] = "approved";
|
|
168
241
|
state.approvals.push({
|
|
169
242
|
gate,
|
|
@@ -171,7 +244,7 @@ export async function approveWorkflow(input: ApproveWorkflowInput): Promise<RunS
|
|
|
171
244
|
createdAt: now(),
|
|
172
245
|
});
|
|
173
246
|
|
|
174
|
-
const nextPhase = nextPhaseAfterGate(gate);
|
|
247
|
+
const nextPhase = nextPhaseAfterGate(state, gate);
|
|
175
248
|
state.phase = nextPhase;
|
|
176
249
|
state.status = "ready";
|
|
177
250
|
return saveState(state);
|
|
@@ -180,7 +253,10 @@ export async function approveWorkflow(input: ApproveWorkflowInput): Promise<RunS
|
|
|
180
253
|
export async function rejectWorkflow(input: RejectWorkflowInput): Promise<RunState> {
|
|
181
254
|
const state = await getWorkflowStatus(input);
|
|
182
255
|
const gate = parseGate(input.gate);
|
|
183
|
-
|
|
256
|
+
if (state.gates[gate] === "rejected") {
|
|
257
|
+
return state;
|
|
258
|
+
}
|
|
259
|
+
assertPendingCurrentGate(state, gate);
|
|
184
260
|
state.gates[gate] = "rejected";
|
|
185
261
|
state.status = "awaiting_input";
|
|
186
262
|
state.feedback.push({
|
|
@@ -193,21 +269,51 @@ export async function rejectWorkflow(input: RejectWorkflowInput): Promise<RunSta
|
|
|
193
269
|
|
|
194
270
|
export async function answerWorkflow(input: AnswerWorkflowInput, options: WorkflowMutationOptions = {}): Promise<RunState> {
|
|
195
271
|
const state = await getWorkflowStatus(input);
|
|
272
|
+
const gate = gateForPhase(state.phase, state.enabledGates);
|
|
273
|
+
if (state.status === "awaiting_input" && state.pendingQuestions.length > 0 && state.phase === "requirements") {
|
|
274
|
+
state.answers.push({
|
|
275
|
+
answer: parseAnswer(input.answer),
|
|
276
|
+
createdAt: now(),
|
|
277
|
+
});
|
|
278
|
+
state.pendingQuestions = [];
|
|
279
|
+
state.status = "ready";
|
|
280
|
+
return saveState(state);
|
|
281
|
+
}
|
|
282
|
+
if (state.status !== "awaiting_input" || !gate || state.gates[gate] !== "rejected") {
|
|
283
|
+
throw new Error("Workflow must be awaiting_input at a rejected current gate before recording an answer");
|
|
284
|
+
}
|
|
196
285
|
state.answers.push({
|
|
197
286
|
answer: parseAnswer(input.answer),
|
|
198
287
|
createdAt: now(),
|
|
199
288
|
});
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
state.gates[gate] = "pending";
|
|
203
|
-
state.status = "awaiting_approval";
|
|
204
|
-
}
|
|
289
|
+
state.gates[gate] = "pending";
|
|
290
|
+
state.status = "awaiting_approval";
|
|
205
291
|
if (!options.skipArtifactWrite) {
|
|
206
292
|
await writeCurrentArtifact(state);
|
|
207
293
|
}
|
|
208
294
|
return saveState(state);
|
|
209
295
|
}
|
|
210
296
|
|
|
297
|
+
export async function waiveVerificationWorkflow(input: WaiveVerificationInput): Promise<RunState> {
|
|
298
|
+
const state = await getWorkflowStatus(input);
|
|
299
|
+
if (
|
|
300
|
+
state.executionMode !== "apply" ||
|
|
301
|
+
state.phase !== "testing" ||
|
|
302
|
+
state.status !== "awaiting_input" ||
|
|
303
|
+
state.gates.testing !== "rejected" ||
|
|
304
|
+
state.verificationStatus !== "failed"
|
|
305
|
+
) {
|
|
306
|
+
throw new Error("A verification waiver is only available after failed apply-mode testing");
|
|
307
|
+
}
|
|
308
|
+
state.verificationWaiver = {
|
|
309
|
+
reason: parseWaiverReason(input.reason),
|
|
310
|
+
createdAt: now(),
|
|
311
|
+
};
|
|
312
|
+
state.gates.testing = "pending";
|
|
313
|
+
state.status = "awaiting_approval";
|
|
314
|
+
return saveState(state);
|
|
315
|
+
}
|
|
316
|
+
|
|
211
317
|
export async function getArtifact(input: ArtifactRef): Promise<ArtifactReadResult> {
|
|
212
318
|
const state = await getWorkflowStatus(input);
|
|
213
319
|
return readArtifact(state, parseArtifactName(input.name));
|