@quinteroac/agents-coding-toolkit 0.1.0-preview → 0.1.1-preview.0

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 (50) hide show
  1. package/README.md +1 -1
  2. package/package.json +13 -4
  3. package/scaffold/.agents/skills/execute-refactor-item/tmpl_SKILL.md +59 -0
  4. package/scaffold/.agents/skills/plan-refactor/tmpl_SKILL.md +89 -9
  5. package/scaffold/.agents/skills/refine-refactor-plan/tmpl_SKILL.md +30 -0
  6. package/scaffold/.agents/tmpl_state_rules.md +0 -1
  7. package/scaffold/schemas/tmpl_refactor-execution-progress.ts +16 -0
  8. package/scaffold/schemas/tmpl_refactor-prd.ts +14 -0
  9. package/scaffold/schemas/tmpl_state.ts +1 -0
  10. package/schemas/refactor-execution-progress.ts +16 -0
  11. package/schemas/refactor-prd.ts +14 -0
  12. package/schemas/state.test.ts +58 -0
  13. package/schemas/state.ts +1 -0
  14. package/schemas/test-plan.test.ts +1 -1
  15. package/src/cli.test.ts +57 -0
  16. package/src/cli.ts +180 -56
  17. package/src/commands/approve-project-context.ts +13 -6
  18. package/src/commands/approve-refactor-plan.test.ts +254 -0
  19. package/src/commands/approve-refactor-plan.ts +200 -0
  20. package/src/commands/approve-requirement.test.ts +224 -0
  21. package/src/commands/approve-requirement.ts +75 -16
  22. package/src/commands/approve-test-plan.test.ts +2 -2
  23. package/src/commands/approve-test-plan.ts +21 -7
  24. package/src/commands/create-issue.test.ts +2 -2
  25. package/src/commands/create-project-context.ts +31 -25
  26. package/src/commands/create-prototype.test.ts +31 -13
  27. package/src/commands/create-prototype.ts +17 -7
  28. package/src/commands/create-test-plan.ts +8 -6
  29. package/src/commands/define-refactor-plan.test.ts +208 -0
  30. package/src/commands/define-refactor-plan.ts +96 -0
  31. package/src/commands/define-requirement.ts +15 -9
  32. package/src/commands/execute-refactor.test.ts +954 -0
  33. package/src/commands/execute-refactor.ts +336 -0
  34. package/src/commands/execute-test-plan.test.ts +9 -2
  35. package/src/commands/execute-test-plan.ts +13 -6
  36. package/src/commands/refine-project-context.ts +9 -7
  37. package/src/commands/refine-refactor-plan.test.ts +210 -0
  38. package/src/commands/refine-refactor-plan.ts +95 -0
  39. package/src/commands/refine-requirement.ts +9 -6
  40. package/src/commands/refine-test-plan.test.ts +2 -2
  41. package/src/commands/refine-test-plan.ts +9 -6
  42. package/src/commands/write-json.ts +102 -97
  43. package/src/force-flag.test.ts +144 -0
  44. package/src/guardrail.test.ts +411 -0
  45. package/src/guardrail.ts +104 -0
  46. package/src/install.test.ts +7 -5
  47. package/src/pack.test.ts +2 -1
  48. package/scaffold/.agents/flow/tmpl_README.md +0 -7
  49. package/scaffold/.agents/flow/tmpl_iteration_close_checklist.example.md +0 -11
  50. package/schemas/test-plan.ts +0 -20
@@ -3,6 +3,7 @@ import { join } from "node:path";
3
3
  import { $ } from "bun";
4
4
 
5
5
  import { CLI_PATH } from "../cli-path";
6
+ import { assertGuardrail } from "../guardrail";
6
7
  import type { Prd } from "../../scaffold/schemas/tmpl_prd";
7
8
  import { exists, readState, writeState, FLOW_REL_DIR } from "../state";
8
9
 
@@ -154,17 +155,75 @@ function parsePrd(markdown: string): Prd {
154
155
  // Main command
155
156
  // ---------------------------------------------------------------------------
156
157
 
157
- export async function runApproveRequirement(): Promise<void> {
158
+ interface WriteJsonResult {
159
+ exitCode: number;
160
+ stderr: string;
161
+ }
162
+
163
+ interface ApproveRequirementDeps {
164
+ existsFn: (path: string) => Promise<boolean>;
165
+ invokeWriteJsonFn: (
166
+ projectRoot: string,
167
+ schemaName: string,
168
+ outPath: string,
169
+ data: string,
170
+ ) => Promise<WriteJsonResult>;
171
+ nowFn: () => Date;
172
+ readFileFn: typeof readFile;
173
+ }
174
+
175
+ async function runWriteJsonCommand(
176
+ projectRoot: string,
177
+ schemaName: string,
178
+ outPath: string,
179
+ data: string,
180
+ ): Promise<WriteJsonResult> {
181
+ const result =
182
+ await $`bun ${CLI_PATH} write-json --schema ${schemaName} --out ${outPath} --data ${data}`
183
+ .cwd(projectRoot)
184
+ .nothrow()
185
+ .quiet();
186
+
187
+ return {
188
+ exitCode: result.exitCode,
189
+ stderr: result.stderr.toString().trim(),
190
+ };
191
+ }
192
+
193
+ const defaultDeps: ApproveRequirementDeps = {
194
+ existsFn: exists,
195
+ invokeWriteJsonFn: runWriteJsonCommand,
196
+ nowFn: () => new Date(),
197
+ readFileFn: readFile,
198
+ };
199
+
200
+ export async function runApproveRequirement(
201
+ optsOrDeps: { force?: boolean } | Partial<ApproveRequirementDeps> = {},
202
+ maybeDeps: Partial<ApproveRequirementDeps> = {},
203
+ ): Promise<void> {
204
+ const isDepsArg =
205
+ typeof optsOrDeps === "object"
206
+ && optsOrDeps !== null
207
+ && (
208
+ "existsFn" in optsOrDeps
209
+ || "invokeWriteJsonFn" in optsOrDeps
210
+ || "nowFn" in optsOrDeps
211
+ || "readFileFn" in optsOrDeps
212
+ );
213
+ const force = isDepsArg ? false : ((optsOrDeps as { force?: boolean }).force ?? false);
158
214
  const projectRoot = process.cwd();
159
215
  const state = await readState(projectRoot);
216
+ const deps = isDepsArg ? optsOrDeps : maybeDeps;
217
+ const mergedDeps: ApproveRequirementDeps = { ...defaultDeps, ...deps };
160
218
 
161
219
  // --- US-001: Validate status ---
162
220
  const requirementDefinition = state.phases.define.requirement_definition;
163
- if (requirementDefinition.status !== "in_progress") {
164
- throw new Error(
165
- `Cannot approve requirement from status '${requirementDefinition.status}'. Expected in_progress.`,
166
- );
167
- }
221
+ await assertGuardrail(
222
+ state,
223
+ requirementDefinition.status !== "in_progress",
224
+ `Cannot approve requirement from status '${requirementDefinition.status}'. Expected in_progress.`,
225
+ { force },
226
+ );
168
227
 
169
228
  const requirementFile = requirementDefinition.file;
170
229
  if (!requirementFile) {
@@ -172,26 +231,26 @@ export async function runApproveRequirement(): Promise<void> {
172
231
  }
173
232
 
174
233
  const requirementPath = join(projectRoot, FLOW_REL_DIR, requirementFile);
175
- if (!(await exists(requirementPath))) {
234
+ if (!(await mergedDeps.existsFn(requirementPath))) {
176
235
  throw new Error(`Cannot approve requirement: file not found at ${requirementPath}`);
177
236
  }
178
237
 
179
238
  // --- US-002: Parse PRD markdown and generate JSON ---
180
- const markdown = await readFile(requirementPath, "utf-8");
239
+ const markdown = await mergedDeps.readFileFn(requirementPath, "utf-8");
181
240
  const prdData = parsePrd(markdown);
182
241
  const prdJsonFileName = `it_${state.current_iteration}_PRD.json`;
183
242
  const prdJsonRelPath = join(FLOW_REL_DIR, prdJsonFileName);
184
243
 
185
244
  // Invoke write-json CLI to validate and write the PRD JSON
186
- const prdJsonString = JSON.stringify(prdData);
187
- const result =
188
- await $`bun ${CLI_PATH} write-json --schema prd --out ${prdJsonRelPath} --data ${prdJsonString}`
189
- .cwd(projectRoot)
190
- .nothrow()
191
- .quiet();
245
+ const result = await mergedDeps.invokeWriteJsonFn(
246
+ projectRoot,
247
+ "prd",
248
+ prdJsonRelPath,
249
+ JSON.stringify(prdData),
250
+ );
192
251
 
193
252
  if (result.exitCode !== 0) {
194
- const stderr = result.stderr.toString().trim();
253
+ const stderr = result.stderr.trim();
195
254
  console.error("PRD JSON generation failed. Requirement remains in_progress.");
196
255
  if (stderr) {
197
256
  console.error(stderr);
@@ -207,7 +266,7 @@ export async function runApproveRequirement(): Promise<void> {
207
266
  state.phases.define.prd_generation.status = "completed";
208
267
  state.phases.define.prd_generation.file = prdJsonFileName;
209
268
 
210
- state.last_updated = new Date().toISOString();
269
+ state.last_updated = mergedDeps.nowFn().toISOString();
211
270
  state.updated_by = "nvst:approve-requirement";
212
271
 
213
272
  await writeState(projectRoot, state);
@@ -3,7 +3,7 @@ import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
 
6
- import { TestPlanSchema } from "../../schemas/test-plan";
6
+ import { TestPlanSchema } from "../../scaffold/schemas/tmpl_test-plan";
7
7
  import { readState, writeState } from "../state";
8
8
  import { parseTestPlan, runApproveTestPlan } from "./approve-test-plan";
9
9
 
@@ -68,7 +68,7 @@ describe("approve test-plan command", () => {
68
68
 
69
69
  expect(source).toContain('import { runApproveTestPlan } from "./commands/approve-test-plan";');
70
70
  expect(source).toContain('if (subcommand === "test-plan") {');
71
- expect(source).toContain("await runApproveTestPlan();");
71
+ expect(source).toContain("await runApproveTestPlan({ force });");
72
72
  });
73
73
 
74
74
  test("requires test_plan.status to be pending_approval", async () => {
@@ -3,7 +3,8 @@ import { join } from "node:path";
3
3
  import { $ } from "bun";
4
4
 
5
5
  import { CLI_PATH } from "../cli-path";
6
- import type { TestPlan } from "../../schemas/test-plan";
6
+ import { assertGuardrail } from "../guardrail";
7
+ import type { TestPlan } from "../../scaffold/schemas/tmpl_test-plan";
7
8
  import { exists, FLOW_REL_DIR, readState, writeState } from "../state";
8
9
 
9
10
  interface WriteJsonResult {
@@ -145,18 +146,31 @@ async function runWriteJsonCommand(
145
146
  }
146
147
 
147
148
  export async function runApproveTestPlan(
148
- deps: Partial<ApproveTestPlanDeps> = {},
149
+ optsOrDeps: { force?: boolean } | Partial<ApproveTestPlanDeps> = {},
150
+ maybeDeps: Partial<ApproveTestPlanDeps> = {},
149
151
  ): Promise<void> {
152
+ const isDepsArg =
153
+ typeof optsOrDeps === "object"
154
+ && optsOrDeps !== null
155
+ && (
156
+ "existsFn" in optsOrDeps
157
+ || "invokeWriteJsonFn" in optsOrDeps
158
+ || "nowFn" in optsOrDeps
159
+ || "readFileFn" in optsOrDeps
160
+ );
161
+ const force = isDepsArg ? false : ((optsOrDeps as { force?: boolean }).force ?? false);
150
162
  const projectRoot = process.cwd();
151
163
  const state = await readState(projectRoot);
164
+ const deps = isDepsArg ? optsOrDeps : maybeDeps;
152
165
  const mergedDeps: ApproveTestPlanDeps = { ...defaultDeps, ...deps };
153
166
 
154
167
  const testPlan = state.phases.prototype.test_plan;
155
- if (testPlan.status !== "pending_approval") {
156
- throw new Error(
157
- `Cannot approve test plan from status '${testPlan.status}'. Expected pending_approval.`,
158
- );
159
- }
168
+ await assertGuardrail(
169
+ state,
170
+ testPlan.status !== "pending_approval",
171
+ `Cannot approve test plan from status '${testPlan.status}'. Expected pending_approval.`,
172
+ { force },
173
+ );
160
174
 
161
175
  const testPlanFile = testPlan.file;
162
176
  if (!testPlanFile) {
@@ -420,8 +420,8 @@ describe("create issue --test-execution-report CLI integration", () => {
420
420
  const exitCode = await proc.exited;
421
421
  const stderr = await new Response(proc.stderr).text();
422
422
  expect(stderr).not.toContain("Missing --agent");
423
- if (exitCode !== 0) {
424
- expect(stderr).toContain("test-execution-results");
423
+ if (exitCode !== 0 && stderr) {
424
+ expect(stderr).toMatch(/test-execution-results|not found|Test execution results/);
425
425
  }
426
426
  });
427
427
 
@@ -7,44 +7,50 @@ import {
7
7
  loadSkill,
8
8
  type AgentProvider,
9
9
  } from "../agent";
10
+ import { assertGuardrail } from "../guardrail";
10
11
  import { exists, readState, writeState } from "../state";
11
12
 
12
13
  export interface CreateProjectContextOptions {
13
14
  provider: AgentProvider;
14
15
  mode: "strict" | "yolo";
16
+ force?: boolean;
15
17
  }
16
18
 
17
19
  export async function runCreateProjectContext(opts: CreateProjectContextOptions): Promise<void> {
18
- const { provider, mode } = opts;
20
+ const { provider, mode, force = false } = opts;
19
21
  const projectRoot = process.cwd();
20
22
  const state = await readState(projectRoot);
21
23
 
22
- if (state.phases.define.prd_generation.status !== "completed") {
23
- throw new Error(
24
- "Cannot create project context: define.prd_generation must be completed first.",
25
- );
26
- }
24
+ await assertGuardrail(
25
+ state,
26
+ state.phases.define.prd_generation.status !== "completed",
27
+ "Cannot create project context: define.prd_generation must be completed first.",
28
+ { force },
29
+ );
27
30
 
28
31
  const projectContext = state.phases.prototype.project_context;
29
- if (projectContext.status === "pending_approval") {
30
- throw new Error(
31
- "Cannot create project context: project context is pending approval. " +
32
- "Run `bun nvst approve project-context` or `bun nvst refine project-context` first.",
33
- );
34
- }
35
-
36
- if (projectContext.status === "created") {
37
- throw new Error(
38
- "Cannot create project context: project context already exists. " +
39
- "Use `bun nvst refine project-context` to iterate on it.",
40
- );
41
- }
42
-
43
- if (projectContext.status !== "pending") {
44
- throw new Error(
45
- `Cannot create project context from status '${projectContext.status}'. Expected pending.`,
46
- );
47
- }
32
+ await assertGuardrail(
33
+ state,
34
+ projectContext.status === "pending_approval",
35
+ "Cannot create project context: project context is pending approval. " +
36
+ "Run `bun nvst approve project-context` or `bun nvst refine project-context` first.",
37
+ { force },
38
+ );
39
+
40
+ await assertGuardrail(
41
+ state,
42
+ projectContext.status === "created",
43
+ "Cannot create project context: project context already exists. " +
44
+ "Use `bun nvst refine project-context` to iterate on it.",
45
+ { force },
46
+ );
47
+
48
+ await assertGuardrail(
49
+ state,
50
+ projectContext.status !== "pending",
51
+ `Cannot create project context from status '${projectContext.status}'. Expected pending.`,
52
+ { force },
53
+ );
48
54
 
49
55
  const prdFile = state.phases.define.prd_generation.file;
50
56
  if (!prdFile) {
@@ -63,6 +63,20 @@ async function seedState(projectRoot: string, state: State): Promise<void> {
63
63
  await writeState(projectRoot, state);
64
64
  }
65
65
 
66
+ const MINIMAL_PRD = {
67
+ goals: [] as string[],
68
+ userStories: [{ id: "US-001", title: "T", description: "D", acceptanceCriteria: [{ id: "AC1", text: "T" }] }],
69
+ functionalRequirements: [] as Array<{ id?: string; description: string }>,
70
+ };
71
+
72
+ async function seedPrd(projectRoot: string, iteration: string): Promise<void> {
73
+ await writeFile(
74
+ join(projectRoot, ".agents", "flow", `it_${iteration}_PRD.json`),
75
+ JSON.stringify(MINIMAL_PRD),
76
+ "utf8",
77
+ );
78
+ }
79
+
66
80
  const createdRoots: string[] = [];
67
81
 
68
82
  afterEach(async () => {
@@ -73,7 +87,9 @@ describe("create prototype phase validation", () => {
73
87
  test("throws when current_phase is define and PRD is not completed", async () => {
74
88
  const root = await createProjectRoot();
75
89
  createdRoots.push(root);
76
- await seedState(root, makeState({ currentPhase: "define", prdStatus: "pending", projectContextStatus: "pending" }));
90
+ const iteration = "000009";
91
+ await seedState(root, makeState({ currentPhase: "define", prdStatus: "pending", projectContextStatus: "pending", iteration }));
92
+ await seedPrd(root, iteration);
77
93
 
78
94
  await withCwd(root, async () => {
79
95
  await expect(runCreatePrototype({ provider: "claude" })).rejects.toThrow(
@@ -85,7 +101,9 @@ describe("create prototype phase validation", () => {
85
101
  test("throws when current_phase is define and project_context is not created", async () => {
86
102
  const root = await createProjectRoot();
87
103
  createdRoots.push(root);
88
- await seedState(root, makeState({ currentPhase: "define", prdStatus: "completed", projectContextStatus: "pending" }));
104
+ const iteration = "000009";
105
+ await seedState(root, makeState({ currentPhase: "define", prdStatus: "completed", projectContextStatus: "pending", iteration }));
106
+ await seedPrd(root, iteration);
89
107
 
90
108
  await withCwd(root, async () => {
91
109
  await expect(runCreatePrototype({ provider: "claude" })).rejects.toThrow(
@@ -99,29 +117,27 @@ describe("create prototype phase validation", () => {
99
117
  createdRoots.push(root);
100
118
  const iteration = "000009";
101
119
  await seedState(root, makeState({ currentPhase: "define", prdStatus: "completed", projectContextStatus: "created", iteration }));
120
+ await seedPrd(root, iteration);
102
121
 
103
- const prdContent = {
104
- goals: ["Test"],
105
- userStories: [
106
- { id: "US-001", title: "One", description: "D", acceptanceCriteria: [{ id: "AC1", text: "T" }] },
107
- ],
108
- functionalRequirements: [{ id: "FR-001", description: "F" }],
109
- };
122
+ await mkdir(join(root, ".agents"), { recursive: true });
110
123
  await writeFile(
111
- join(root, ".agents", "flow", `it_${iteration}_PRD.json`),
112
- JSON.stringify(prdContent),
124
+ join(root, ".agents", "PROJECT_CONTEXT.md"),
125
+ "# Project\n## Testing Strategy\n### Quality Checks\n```\nbun test\n```\n",
113
126
  "utf8",
114
127
  );
115
128
 
116
129
  const { $ } = await import("bun");
117
130
  await $`git init`.cwd(root).nothrow().quiet();
131
+ await $`git config user.email "test@test" && git config user.name "Test"`.cwd(root).nothrow().quiet();
132
+ await $`git add -A && git commit -m init`.cwd(root).nothrow().quiet();
118
133
 
119
134
  await withCwd(root, async () => {
120
135
  await expect(runCreatePrototype({ provider: "claude" })).rejects.toThrow(
121
- "Required skill missing",
136
+ "Git working tree is dirty",
122
137
  );
123
138
  });
124
139
 
140
+ // Transition writes state before the git check; phase is prototype
125
141
  const updatedState = await readState(root);
126
142
  expect(updatedState.current_phase).toBe("prototype");
127
143
  });
@@ -129,7 +145,9 @@ describe("create prototype phase validation", () => {
129
145
  test("throws when current_phase is refactor", async () => {
130
146
  const root = await createProjectRoot();
131
147
  createdRoots.push(root);
132
- await seedState(root, makeState({ currentPhase: "refactor" }));
148
+ const iteration = "000009";
149
+ await seedState(root, makeState({ currentPhase: "refactor", iteration }));
150
+ await seedPrd(root, iteration);
133
151
 
134
152
  await withCwd(root, async () => {
135
153
  await expect(runCreatePrototype({ provider: "claude" })).rejects.toThrow(
@@ -10,6 +10,7 @@ import {
10
10
  loadSkill,
11
11
  type AgentProvider,
12
12
  } from "../agent";
13
+ import { assertGuardrail } from "../guardrail";
13
14
  import { exists, FLOW_REL_DIR, readState, writeState } from "../state";
14
15
 
15
16
  export interface CreatePrototypeOptions {
@@ -17,6 +18,7 @@ export interface CreatePrototypeOptions {
17
18
  iterations?: number;
18
19
  retryOnFail?: number;
19
20
  stopOnCritical?: boolean;
21
+ force?: boolean;
20
22
  }
21
23
 
22
24
  const ProgressEntrySchema = z.object({
@@ -106,6 +108,7 @@ function parseQualityChecks(projectContextContent: string): string[] {
106
108
  export async function runCreatePrototype(opts: CreatePrototypeOptions): Promise<void> {
107
109
  const projectRoot = process.cwd();
108
110
  const state = await readState(projectRoot);
111
+ const force = opts.force ?? false;
109
112
 
110
113
  if (opts.iterations !== undefined && (!Number.isInteger(opts.iterations) || opts.iterations < 1)) {
111
114
  throw new Error(
@@ -167,21 +170,28 @@ export async function runCreatePrototype(opts: CreatePrototypeOptions): Promise<
167
170
  state.current_phase = "prototype";
168
171
  await writeState(projectRoot, state);
169
172
  } else {
170
- throw new Error(
173
+ await assertGuardrail(
174
+ state,
175
+ true,
171
176
  "Cannot create prototype: current_phase is define and prerequisites are not met. Complete define phase and run `bun nvst create project-context --agent <provider>` then `bun nvst approve project-context` first.",
177
+ { force },
172
178
  );
173
179
  }
174
180
  } else if (state.current_phase !== "prototype") {
175
- throw new Error(
181
+ await assertGuardrail(
182
+ state,
183
+ true,
176
184
  "Cannot create prototype: current_phase must be define (with approved PRD) or prototype. Complete define phase and run `bun nvst create project-context --agent <provider>` then `bun nvst approve project-context` first.",
185
+ { force },
177
186
  );
178
187
  }
179
188
 
180
- if (state.phases.prototype.project_context.status !== "created") {
181
- throw new Error(
182
- "Cannot create prototype: prototype.project_context.status must be created. Run `bun nvst create project-context --agent <provider>` and `bun nvst approve project-context` first.",
183
- );
184
- }
189
+ await assertGuardrail(
190
+ state,
191
+ state.phases.prototype.project_context.status !== "created",
192
+ "Cannot create prototype: prototype.project_context.status must be created. Run `bun nvst create project-context --agent <provider>` and `bun nvst approve project-context` first.",
193
+ { force },
194
+ );
185
195
 
186
196
  const workingTreeAfterPhase = await dollar`git status --porcelain`.cwd(projectRoot).nothrow().quiet();
187
197
  if (workingTreeAfterPhase.exitCode !== 0) {
@@ -10,8 +10,9 @@ import {
10
10
  type AgentProvider,
11
11
  type AgentResult,
12
12
  } from "../agent";
13
+ import { assertGuardrail } from "../guardrail";
13
14
  import { exists, FLOW_REL_DIR, readState, writeState } from "../state";
14
- import { TestPlanSchema, type TestPlan } from "../../schemas/test-plan";
15
+ import { TestPlanSchema, type TestPlan } from "../../scaffold/schemas/tmpl_test-plan";
15
16
 
16
17
  export interface CreateTestPlanOptions {
17
18
  provider: AgentProvider;
@@ -155,11 +156,12 @@ export async function runCreateTestPlan(
155
156
  const state = await readState(projectRoot);
156
157
  const mergedDeps: CreateTestPlanDeps = { ...defaultDeps, ...deps };
157
158
 
158
- if (state.phases.prototype.project_context.status !== "created") {
159
- throw new Error(
160
- "Cannot create test plan: prototype.project_context.status must be created. Run `bun nvst approve project-context` first.",
161
- );
162
- }
159
+ await assertGuardrail(
160
+ state,
161
+ state.phases.prototype.project_context.status !== "created",
162
+ "Cannot create test plan: prototype.project_context.status must be created. Run `bun nvst approve project-context` first.",
163
+ { force: opts.force },
164
+ );
163
165
 
164
166
  const iteration = state.current_iteration;
165
167
  const fileName = `it_${iteration}_test-plan.md`;