@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
@@ -3,43 +3,67 @@ import { ensureConfig } from "./config.js";
3
3
  import { readArtifact, writeArtifact } from "./artifacts.js";
4
4
  import { discoverStandards } from "./standards.js";
5
5
  import { loadState, saveState } from "./store.js";
6
- import { parseAnswer, parseArtifactName, parseBackend, parseCwd, parseExecutionMode, parseFeedback, parseGate, parseHost, parseOptionalNote, parseRequest, parseRunId, parseWorkflowMode, } from "./validation.js";
6
+ import { parseAnswer, parseArtifactName, parseBackend, parseCwd, parseExecutionMode, parseExecutionPolicy, parseFeedback, parseGate, parseHost, parseOptionalNote, parseRequest, parseRunId, parseWaiverReason, parseWorkflowMode, } from "./validation.js";
7
+ import { GATES } from "./types.js";
7
8
  function now() {
8
9
  return new Date().toISOString();
9
10
  }
10
11
  function newRunId() {
11
12
  return `dc-${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`;
12
13
  }
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];
14
+ export function nextPhaseAfterGate(state, gate) {
15
+ switch (gate) {
16
+ case "requirements":
17
+ return "architecture";
18
+ case "architecture":
19
+ return "implementation";
20
+ case "implementation":
21
+ return state.executionMode === "apply" ? "execution" : "testing";
22
+ case "implementation-review":
23
+ return "testing";
24
+ case "testing":
25
+ return "acceptance";
26
+ }
21
27
  }
22
28
  export function artifactForPhase(phase) {
23
29
  const artifactByPhase = {
24
30
  requirements: "requirements",
25
31
  architecture: "architecture",
26
32
  implementation: "implementation-plan",
33
+ execution: "implementation-review",
34
+ review: "architecture-review",
27
35
  testing: "test-report",
28
36
  acceptance: "acceptance",
29
37
  complete: "acceptance",
30
38
  };
31
39
  return artifactByPhase[phase];
32
40
  }
33
- export function gateForPhase(phase) {
34
- if (phase === "requirements" || phase === "architecture" || phase === "implementation" || phase === "testing") {
35
- return phase;
36
- }
37
- return undefined;
41
+ export function gateForPhase(phase, enabledGates) {
42
+ const gate = phase === "review"
43
+ ? "implementation-review"
44
+ : (phase === "requirements" ||
45
+ phase === "architecture" ||
46
+ phase === "implementation" ||
47
+ phase === "testing"
48
+ ? phase
49
+ : undefined);
50
+ return gate && (!enabledGates || enabledGates.includes(gate)) ? gate : undefined;
51
+ }
52
+ function configuredGates(gates) {
53
+ const configured = new Set(gates.filter((gate) => GATES.includes(gate)));
54
+ configured.add("implementation-review");
55
+ configured.add("testing");
56
+ return [...configured];
38
57
  }
39
- function assertCurrentGate(state, gate) {
40
- const expected = gateForPhase(state.phase);
41
- if (expected && expected !== gate) {
42
- throw new Error(`Cannot act on ${gate} while current gate is ${expected}`);
58
+ function assertPendingCurrentGate(state, gate) {
59
+ const expected = gateForPhase(state.phase, state.enabledGates);
60
+ if (expected !== gate) {
61
+ throw new Error(expected
62
+ ? `Cannot act on ${gate} while current gate is ${expected}`
63
+ : `Cannot act on ${gate} while workflow phase is ${state.phase}`);
64
+ }
65
+ if (state.status !== "awaiting_approval" || state.gates[gate] !== "pending") {
66
+ throw new Error(`Gate ${gate} is not pending approval`);
43
67
  }
44
68
  }
45
69
  async function writeCurrentArtifact(state) {
@@ -56,6 +80,11 @@ export async function startWorkflow(input, options = {}) {
56
80
  const config = await ensureConfig(cwd);
57
81
  const backend = input.backend ? parseBackend(input.backend) : config.defaultBackend === "host-preferred" ? host : config.defaultBackend;
58
82
  const executionMode = input.executionMode ? parseExecutionMode(input.executionMode) : config.executionMode;
83
+ const executionPolicy = input.executionPolicy ? parseExecutionPolicy(input.executionPolicy) : "interactive-host";
84
+ const enabledGates = configuredGates(config.workflow.gates);
85
+ if (executionMode === "apply" && backend === "local") {
86
+ throw new Error("DevCrew apply mode requires a codex or claude backend; local is plan-only");
87
+ }
59
88
  const createdAt = now();
60
89
  const state = {
61
90
  version: 1,
@@ -64,20 +93,25 @@ export async function startWorkflow(input, options = {}) {
64
93
  host,
65
94
  mode,
66
95
  executionMode,
96
+ executionPolicy,
97
+ enabledGates,
98
+ artifactDirectory: config.workflow.artifactDirectory,
67
99
  backend,
68
100
  request,
69
101
  phase: "requirements",
70
- status: "awaiting_approval",
102
+ status: enabledGates.includes("requirements") ? "awaiting_approval" : "ready",
71
103
  createdAt,
72
104
  updatedAt: createdAt,
73
105
  gates: {
74
- requirements: "pending",
106
+ requirements: enabledGates.includes("requirements") ? "pending" : "not_started",
75
107
  architecture: "not_started",
76
108
  implementation: "not_started",
109
+ "implementation-review": "not_started",
77
110
  testing: "not_started",
78
111
  },
79
112
  artifacts: {},
80
113
  roles: [],
114
+ pendingQuestions: [],
81
115
  answers: [],
82
116
  approvals: [],
83
117
  feedback: [],
@@ -85,6 +119,7 @@ export async function startWorkflow(input, options = {}) {
85
119
  changedFiles: [],
86
120
  implementationDiff: "",
87
121
  verification: [],
122
+ verificationStatus: "not_run",
88
123
  lintResults: [],
89
124
  };
90
125
  if (!options.skipArtifactWrite) {
@@ -97,7 +132,10 @@ export async function getWorkflowStatus(input) {
97
132
  }
98
133
  export async function continueWorkflow(input) {
99
134
  const state = await getWorkflowStatus(input);
100
- if (state.status === "awaiting_approval" || state.status === "awaiting_input" || state.status === "complete") {
135
+ if (state.status === "awaiting_approval" ||
136
+ state.status === "awaiting_input" ||
137
+ state.status === "awaiting_execution" ||
138
+ state.status === "complete") {
101
139
  return state;
102
140
  }
103
141
  if (state.phase === "acceptance") {
@@ -106,8 +144,17 @@ export async function continueWorkflow(input) {
106
144
  state.status = "complete";
107
145
  return saveState(state);
108
146
  }
109
- const gate = gateForPhase(state.phase);
147
+ if (state.phase === "execution") {
148
+ throw new Error("DevCrew execution phase requires orchestrated continuation");
149
+ }
150
+ const configuredGate = gateForPhase(state.phase);
151
+ const gate = gateForPhase(state.phase, state.enabledGates);
110
152
  if (!gate) {
153
+ if (configuredGate) {
154
+ state.phase = nextPhaseAfterGate(state, configuredGate);
155
+ state.status = "ready";
156
+ return saveState(state);
157
+ }
111
158
  state.status = "complete";
112
159
  return saveState(state);
113
160
  }
@@ -116,17 +163,29 @@ export async function continueWorkflow(input) {
116
163
  await writeCurrentArtifact(state);
117
164
  return saveState(state);
118
165
  }
119
- export async function approveWorkflow(input) {
166
+ export async function validateWorkflowApproval(input) {
120
167
  const state = await getWorkflowStatus(input);
121
168
  const gate = parseGate(input.gate);
122
- assertCurrentGate(state, gate);
169
+ if (state.gates[gate] === "approved") {
170
+ return state;
171
+ }
172
+ assertPendingCurrentGate(state, gate);
173
+ parseOptionalNote(input.note);
174
+ return state;
175
+ }
176
+ export async function approveWorkflow(input) {
177
+ const state = await validateWorkflowApproval(input);
178
+ const gate = parseGate(input.gate);
179
+ if (state.gates[gate] === "approved") {
180
+ return state;
181
+ }
123
182
  state.gates[gate] = "approved";
124
183
  state.approvals.push({
125
184
  gate,
126
185
  note: parseOptionalNote(input.note),
127
186
  createdAt: now(),
128
187
  });
129
- const nextPhase = nextPhaseAfterGate(gate);
188
+ const nextPhase = nextPhaseAfterGate(state, gate);
130
189
  state.phase = nextPhase;
131
190
  state.status = "ready";
132
191
  return saveState(state);
@@ -134,7 +193,10 @@ export async function approveWorkflow(input) {
134
193
  export async function rejectWorkflow(input) {
135
194
  const state = await getWorkflowStatus(input);
136
195
  const gate = parseGate(input.gate);
137
- assertCurrentGate(state, gate);
196
+ if (state.gates[gate] === "rejected") {
197
+ return state;
198
+ }
199
+ assertPendingCurrentGate(state, gate);
138
200
  state.gates[gate] = "rejected";
139
201
  state.status = "awaiting_input";
140
202
  state.feedback.push({
@@ -146,20 +208,47 @@ export async function rejectWorkflow(input) {
146
208
  }
147
209
  export async function answerWorkflow(input, options = {}) {
148
210
  const state = await getWorkflowStatus(input);
211
+ const gate = gateForPhase(state.phase, state.enabledGates);
212
+ if (state.status === "awaiting_input" && state.pendingQuestions.length > 0 && state.phase === "requirements") {
213
+ state.answers.push({
214
+ answer: parseAnswer(input.answer),
215
+ createdAt: now(),
216
+ });
217
+ state.pendingQuestions = [];
218
+ state.status = "ready";
219
+ return saveState(state);
220
+ }
221
+ if (state.status !== "awaiting_input" || !gate || state.gates[gate] !== "rejected") {
222
+ throw new Error("Workflow must be awaiting_input at a rejected current gate before recording an answer");
223
+ }
149
224
  state.answers.push({
150
225
  answer: parseAnswer(input.answer),
151
226
  createdAt: now(),
152
227
  });
153
- const gate = gateForPhase(state.phase);
154
- if (gate) {
155
- state.gates[gate] = "pending";
156
- state.status = "awaiting_approval";
157
- }
228
+ state.gates[gate] = "pending";
229
+ state.status = "awaiting_approval";
158
230
  if (!options.skipArtifactWrite) {
159
231
  await writeCurrentArtifact(state);
160
232
  }
161
233
  return saveState(state);
162
234
  }
235
+ export async function waiveVerificationWorkflow(input) {
236
+ const state = await getWorkflowStatus(input);
237
+ if (state.executionMode !== "apply" ||
238
+ state.phase !== "testing" ||
239
+ state.status !== "awaiting_input" ||
240
+ state.gates.testing !== "rejected" ||
241
+ state.verificationStatus !== "failed") {
242
+ throw new Error("A verification waiver is only available after failed apply-mode testing");
243
+ }
244
+ state.verificationWaiver = {
245
+ reason: parseWaiverReason(input.reason),
246
+ createdAt: now(),
247
+ };
248
+ state.gates.testing = "pending";
249
+ state.status = "awaiting_approval";
250
+ return saveState(state);
251
+ }
163
252
  export async function getArtifact(input) {
164
253
  const state = await getWorkflowStatus(input);
165
254
  return readArtifact(state, parseArtifactName(input.name));