@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.
Files changed (48) hide show
  1. package/README.md +30 -8
  2. package/README.zh-CN.md +27 -8
  3. package/dist/packages/adapters/src/index.js +62 -8
  4. package/dist/packages/core/src/artifacts.js +14 -4
  5. package/dist/packages/core/src/config.js +1 -1
  6. package/dist/packages/core/src/paths.js +10 -6
  7. package/dist/packages/core/src/store.js +36 -0
  8. package/dist/packages/core/src/types.js +5 -1
  9. package/dist/packages/core/src/validation.js +7 -1
  10. package/dist/packages/core/src/version.js +1 -1
  11. package/dist/packages/core/src/workflow.js +120 -31
  12. package/dist/packages/orchestrator/src/index.js +339 -160
  13. package/dist/packages/orchestrator/src/worktree.js +198 -0
  14. package/dist/packages/plugins/src/index.js +2 -32
  15. package/dist/packages/service/src/stdio.js +19 -3
  16. package/dist/packages/service/src/tools.js +45 -6
  17. package/docs/claude-code.md +5 -3
  18. package/docs/codex.md +12 -4
  19. package/docs/quickstart.md +1 -1
  20. package/docs/superpowers/plans/2026-07-10-p0-foundation-repair.md +939 -0
  21. package/docs/superpowers/plans/2026-07-13-safety-semantics.md +313 -0
  22. package/docs/superpowers/specs/2026-07-10-p0-foundation-repair-design.md +182 -0
  23. package/docs/superpowers/specs/2026-07-13-execution-boundaries-design.md +126 -0
  24. package/docs/workflow.md +36 -4
  25. package/package.json +1 -1
  26. package/packages/adapters/src/index.ts +87 -11
  27. package/packages/core/src/artifacts.ts +16 -4
  28. package/packages/core/src/config.ts +1 -1
  29. package/packages/core/src/paths.ts +11 -6
  30. package/packages/core/src/store.ts +41 -1
  31. package/packages/core/src/types.ts +53 -2
  32. package/packages/core/src/validation.ts +10 -0
  33. package/packages/core/src/version.ts +1 -1
  34. package/packages/core/src/workflow.ts +136 -30
  35. package/packages/orchestrator/src/index.ts +377 -182
  36. package/packages/orchestrator/src/worktree.ts +269 -0
  37. package/packages/plugins/src/index.ts +2 -44
  38. package/packages/service/src/stdio.ts +27 -6
  39. package/packages/service/src/tools.ts +46 -5
  40. package/plugins/devcrew-codex/.codex-plugin/plugin.json +1 -1
  41. package/plugins/devcrew-codex/.mcp.json +1 -1
  42. package/plugins/devcrew-codex/assets/logo.png +0 -0
  43. package/plugins/devcrew-codex/skills/devcrew/SKILL.md +8 -7
  44. package/scripts/smoke-codex-plugin.mjs +1 -1
  45. package/plugins/devcrew-codex/agents/architect.toml +0 -6
  46. package/plugins/devcrew-codex/agents/implementer.toml +0 -6
  47. package/plugins/devcrew-codex/agents/pm.toml +0 -6
  48. 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
- const nextByGate: Record<GateName, RunState["phase"]> = {
44
- requirements: "architecture",
45
- architecture: "implementation",
46
- implementation: "testing",
47
- testing: "acceptance",
48
- };
49
- return nextByGate[gate];
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
- if (phase === "requirements" || phase === "architecture" || phase === "implementation" || phase === "testing") {
66
- return phase;
67
- }
68
- return undefined;
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 assertCurrentGate(state: RunState, gate: GateName): void {
72
- const expected = gateForPhase(state.phase);
73
- if (expected && expected !== gate) {
74
- throw new Error(`Cannot act on ${gate} while current gate is ${expected}`);
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 (state.status === "awaiting_approval" || state.status === "awaiting_input" || state.status === "complete") {
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
- const gate = gateForPhase(state.phase);
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 approveWorkflow(input: ApproveWorkflowInput): Promise<RunState> {
223
+ export async function validateWorkflowApproval(input: ApproveWorkflowInput): Promise<RunState> {
164
224
  const state = await getWorkflowStatus(input);
165
225
  const gate = parseGate(input.gate);
166
- assertCurrentGate(state, gate);
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
- assertCurrentGate(state, gate);
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
- const gate = gateForPhase(state.phase);
201
- if (gate) {
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));