@quinteroac/agents-coding-toolkit 0.1.0-preview → 0.2.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 (71) hide show
  1. package/README.md +29 -15
  2. package/package.json +14 -4
  3. package/scaffold/.agents/flow/tmpl_it_000001_progress.example.json +20 -0
  4. package/scaffold/.agents/skills/execute-refactor-item/tmpl_SKILL.md +59 -0
  5. package/scaffold/.agents/skills/plan-refactor/tmpl_SKILL.md +89 -9
  6. package/scaffold/.agents/skills/refine-refactor-plan/tmpl_SKILL.md +30 -0
  7. package/scaffold/.agents/tmpl_state_rules.md +0 -1
  8. package/scaffold/schemas/tmpl_prototype-progress.ts +22 -0
  9. package/scaffold/schemas/tmpl_refactor-execution-progress.ts +16 -0
  10. package/scaffold/schemas/tmpl_refactor-prd.ts +14 -0
  11. package/scaffold/schemas/tmpl_state.ts +1 -0
  12. package/scaffold/schemas/tmpl_test-execution-progress.ts +17 -0
  13. package/schemas/issues.ts +19 -0
  14. package/schemas/prototype-progress.ts +22 -0
  15. package/schemas/refactor-execution-progress.ts +16 -0
  16. package/schemas/refactor-prd.ts +14 -0
  17. package/schemas/state.test.ts +58 -0
  18. package/schemas/state.ts +1 -0
  19. package/schemas/test-execution-progress.ts +17 -0
  20. package/schemas/test-plan.test.ts +1 -1
  21. package/schemas/validate-progress.ts +1 -1
  22. package/schemas/validate-state.ts +1 -1
  23. package/src/cli.test.ts +57 -0
  24. package/src/cli.ts +227 -58
  25. package/src/commands/approve-project-context.ts +13 -6
  26. package/src/commands/approve-prototype.test.ts +427 -0
  27. package/src/commands/approve-prototype.ts +185 -0
  28. package/src/commands/approve-refactor-plan.test.ts +254 -0
  29. package/src/commands/approve-refactor-plan.ts +200 -0
  30. package/src/commands/approve-requirement.test.ts +224 -0
  31. package/src/commands/approve-requirement.ts +75 -16
  32. package/src/commands/approve-test-plan.test.ts +2 -2
  33. package/src/commands/approve-test-plan.ts +21 -7
  34. package/src/commands/create-issue.test.ts +2 -2
  35. package/src/commands/create-project-context.ts +31 -25
  36. package/src/commands/create-prototype.test.ts +488 -18
  37. package/src/commands/create-prototype.ts +185 -63
  38. package/src/commands/create-test-plan.ts +8 -6
  39. package/src/commands/define-refactor-plan.test.ts +208 -0
  40. package/src/commands/define-refactor-plan.ts +96 -0
  41. package/src/commands/define-requirement.ts +15 -9
  42. package/src/commands/execute-automated-fix.test.ts +78 -33
  43. package/src/commands/execute-automated-fix.ts +34 -101
  44. package/src/commands/execute-refactor.test.ts +954 -0
  45. package/src/commands/execute-refactor.ts +332 -0
  46. package/src/commands/execute-test-plan.test.ts +24 -16
  47. package/src/commands/execute-test-plan.ts +29 -55
  48. package/src/commands/flow-config.ts +79 -0
  49. package/src/commands/flow.test.ts +755 -0
  50. package/src/commands/flow.ts +405 -0
  51. package/src/commands/refine-project-context.ts +9 -7
  52. package/src/commands/refine-refactor-plan.test.ts +210 -0
  53. package/src/commands/refine-refactor-plan.ts +95 -0
  54. package/src/commands/refine-requirement.ts +9 -6
  55. package/src/commands/refine-test-plan.test.ts +2 -2
  56. package/src/commands/refine-test-plan.ts +9 -6
  57. package/src/commands/start-iteration.test.ts +52 -0
  58. package/src/commands/start-iteration.ts +5 -0
  59. package/src/commands/write-json.ts +102 -97
  60. package/src/flow-cli.test.ts +18 -0
  61. package/src/force-flag.test.ts +144 -0
  62. package/src/guardrail.test.ts +411 -0
  63. package/src/guardrail.ts +82 -0
  64. package/src/install.test.ts +7 -5
  65. package/src/pack.test.ts +2 -1
  66. package/src/progress-utils.ts +34 -0
  67. package/src/readline.ts +23 -0
  68. package/src/write-json-artifact.ts +33 -0
  69. package/scaffold/.agents/flow/tmpl_README.md +0 -7
  70. package/scaffold/.agents/flow/tmpl_iteration_close_checklist.example.md +0 -11
  71. package/schemas/test-plan.ts +0 -20
@@ -0,0 +1,57 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { readFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+
5
+ describe("US-005: execute refactor CLI routing and help text", () => {
6
+ // AC01: cli.ts routes `execute refactor` to runExecuteRefactor handler
7
+ test("cli.ts routes execute refactor subcommand to runExecuteRefactor", async () => {
8
+ const source = await readFile(join(import.meta.dir, "cli.ts"), "utf8");
9
+ expect(source).toContain('import { runExecuteRefactor } from "./commands/execute-refactor"');
10
+ expect(source).toContain('if (subcommand === "refactor")');
11
+ expect(source).toContain("await runExecuteRefactor({ provider, force })");
12
+ });
13
+
14
+ // AC02: printUsage includes `execute refactor --agent <provider>` with a one-line description
15
+ test("printUsage includes execute refactor --agent <provider> with one-line description", async () => {
16
+ const source = await readFile(join(import.meta.dir, "cli.ts"), "utf8");
17
+ expect(source).toContain("execute refactor --agent <provider>");
18
+ expect(source).toContain("Execute approved refactor items via agent in order");
19
+ });
20
+
21
+ // AC03: Unknown options after --agent <provider> cause clear error and exit code 1
22
+ test("unknown options after --agent <provider> cause clear error message and exit code 1", async () => {
23
+ const cliPath = join(import.meta.dir, "cli.ts");
24
+ const proc = Bun.spawn(
25
+ ["bun", cliPath, "execute", "refactor", "--agent", "claude", "--unknown-flag"],
26
+ {
27
+ stdout: "pipe",
28
+ stderr: "pipe",
29
+ },
30
+ );
31
+ const exitCode = await proc.exited;
32
+ const stderrText = await new Response(proc.stderr).text();
33
+
34
+ expect(exitCode).toBe(1);
35
+ expect(stderrText).toContain("Unknown option(s) for execute refactor");
36
+ expect(stderrText).toContain("--unknown-flag");
37
+ });
38
+
39
+ // AC03: Multiple unknown options after --agent <provider> are all reported
40
+ test("multiple unknown options after --agent <provider> are all listed in error message", async () => {
41
+ const cliPath = join(import.meta.dir, "cli.ts");
42
+ const proc = Bun.spawn(
43
+ ["bun", cliPath, "execute", "refactor", "--agent", "claude", "--foo", "--bar"],
44
+ {
45
+ stdout: "pipe",
46
+ stderr: "pipe",
47
+ },
48
+ );
49
+ const exitCode = await proc.exited;
50
+ const stderrText = await new Response(proc.stderr).text();
51
+
52
+ expect(exitCode).toBe(1);
53
+ expect(stderrText).toContain("Unknown option(s) for execute refactor");
54
+ expect(stderrText).toContain("--foo");
55
+ expect(stderrText).toContain("--bar");
56
+ });
57
+ });
package/src/cli.ts CHANGED
@@ -1,21 +1,28 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
3
  import { join } from "node:path";
4
- import { parseAgentArg } from "./agent";
4
+ import { parseAgentArg, parseProvider, type AgentProvider } from "./agent";
5
+ import { GuardrailAbortError } from "./guardrail";
5
6
  import { runApproveProjectContext } from "./commands/approve-project-context";
7
+ import { runApprovePrototype } from "./commands/approve-prototype";
8
+ import { runApproveRefactorPlan } from "./commands/approve-refactor-plan";
6
9
  import { runApproveRequirement } from "./commands/approve-requirement";
7
10
  import { runApproveTestPlan } from "./commands/approve-test-plan";
8
11
  import { runCreateIssue, runCreateIssueFromTestReport } from "./commands/create-issue";
9
12
  import { runCreateProjectContext } from "./commands/create-project-context";
10
13
  import { runCreatePrototype } from "./commands/create-prototype";
11
14
  import { runCreateTestPlan } from "./commands/create-test-plan";
15
+ import { runDefineRefactorPlan } from "./commands/define-refactor-plan";
12
16
  import { runDefineRequirement } from "./commands/define-requirement";
13
17
  import { runDestroy } from "./commands/destroy";
14
18
  import { runExecuteAutomatedFix } from "./commands/execute-automated-fix";
15
19
  import { runExecuteManualFix } from "./commands/execute-manual-fix";
20
+ import { runExecuteRefactor } from "./commands/execute-refactor";
16
21
  import { runExecuteTestPlan } from "./commands/execute-test-plan";
22
+ import { runFlow } from "./commands/flow";
17
23
  import { runInit } from "./commands/init";
18
24
  import { runRefineProjectContext } from "./commands/refine-project-context";
25
+ import { runRefineRefactorPlan } from "./commands/refine-refactor-plan";
19
26
  import { runRefineRequirement } from "./commands/refine-requirement";
20
27
  import { runRefineTestPlan } from "./commands/refine-test-plan";
21
28
  import { runStartIteration } from "./commands/start-iteration";
@@ -76,18 +83,43 @@ function parseOptionalIntegerFlag(
76
83
  return { value: parsed, remainingArgs };
77
84
  }
78
85
 
86
+ function parseForce(args: string[]): { force: boolean; remainingArgs: string[] } {
87
+ const force = args.includes("--force");
88
+ return {
89
+ force,
90
+ remainingArgs: args.filter((arg) => arg !== "--force"),
91
+ };
92
+ }
93
+
94
+ function parseOptionalAgentArg(args: string[]): {
95
+ provider: AgentProvider | undefined;
96
+ remainingArgs: string[];
97
+ } {
98
+ const { value, remainingArgs } = extractFlagValue(args, "--agent");
99
+ if (value === null) {
100
+ return { provider: undefined, remainingArgs };
101
+ }
102
+
103
+ return {
104
+ provider: parseProvider(value),
105
+ remainingArgs,
106
+ };
107
+ }
108
+
79
109
  function printUsage() {
80
110
  console.log(`Usage: nvst <command> [options]
81
111
 
82
112
  Commands:
83
113
  init Initialize toolkit files in the current directory
84
114
  start iteration Start a new iteration (archives previous if exists)
85
- create project-context --agent <provider> [--mode strict|yolo]
115
+ create project-context --agent <provider> [--mode strict|yolo] [--force]
86
116
  Generate/update .agents/PROJECT_CONTEXT.md via agent
87
117
  create test-plan --agent <provider> [--force]
88
118
  Generate test plan document for current iteration
89
119
  create prototype --agent <provider> [--iterations <N>] [--retry-on-fail <N>] [--stop-on-critical]
90
120
  Initialize prototype build for current iteration
121
+ flow [--agent <provider>] [--force]
122
+ Run the next pending flow step(s) until an approval gate or completion
91
123
  create issue --agent <provider>
92
124
  Create issues interactively via agent
93
125
  create issue --test-execution-report
@@ -96,20 +128,30 @@ Commands:
96
128
  Mark project context as approved
97
129
  approve test-plan
98
130
  Mark test plan as approved and generate structured TP JSON
131
+ approve prototype
132
+ Stage and commit all pending changes for current iteration
133
+ approve refactor-plan
134
+ Mark refactor plan as approved and generate structured refactor PRD JSON
99
135
  refine project-context --agent <provider> [--challenge]
100
136
  Refine project context via agent (editor or challenge mode)
101
- define requirement --agent <provider>
137
+ define requirement --agent <provider> [--force]
102
138
  Create requirement document via agent
103
- refine requirement --agent <provider> [--challenge]
139
+ define refactor-plan --agent <provider> [--force]
140
+ Create refactor plan document via agent
141
+ refine requirement --agent <provider> [--challenge] [--force]
104
142
  Refine requirement document via agent
105
- refine test-plan --agent <provider> [--challenge]
143
+ refine test-plan --agent <provider> [--challenge] [--force]
106
144
  Refine test plan document via agent
107
- execute test-plan --agent <provider>
145
+ refine refactor-plan --agent <provider> [--challenge] [--force]
146
+ Refine refactor plan document via agent
147
+ execute test-plan --agent <provider> [--force]
108
148
  Execute approved structured test-plan JSON via agent
109
149
  execute automated-fix --agent <provider> [--iterations <N>] [--retry-on-fail <N>]
110
150
  Attempt automated fixes for open issues in current iteration
111
151
  execute manual-fix --agent <provider>
112
152
  Find manual-fix issues for current iteration and confirm execution
153
+ execute refactor --agent <provider> [--force]
154
+ Execute approved refactor items via agent in order
113
155
  approve requirement
114
156
  Mark requirement definition as approved
115
157
  write-json --schema <name> --out <path> [--data '<json>']
@@ -122,7 +164,7 @@ Options:
122
164
  --iterations Maximum prototype passes (integer >= 1)
123
165
  --retry-on-fail Retry attempts per failed story (integer >= 0)
124
166
  --stop-on-critical Stop execution after critical failures
125
- --force Overwrite output file without confirmation
167
+ --force Bypass flow guardrail confirmation (and overwrite test-plan output)
126
168
  --challenge Run refine in challenger mode
127
169
  --clean When used with destroy, also removes .agents/flow/archived
128
170
  -h, --help Show this help message
@@ -153,8 +195,9 @@ async function main() {
153
195
  }
154
196
 
155
197
  if (command === "init") {
156
- if (args.length > 0) {
157
- console.error(`Unknown option(s) for init: ${args.join(" ")}`);
198
+ const { remainingArgs: unknownArgs } = parseForce(args);
199
+ if (unknownArgs.length > 0) {
200
+ console.error(`Unknown option(s) for init: ${unknownArgs.join(" ")}`);
158
201
  printUsage();
159
202
  process.exitCode = 1;
160
203
  return;
@@ -164,8 +207,9 @@ async function main() {
164
207
  }
165
208
 
166
209
  if (command === "destroy") {
167
- const clean = args.includes("--clean");
168
- const unknownArgs = args.filter((arg) => arg !== "--clean");
210
+ const { remainingArgs: argsWithoutForce } = parseForce(args);
211
+ const clean = argsWithoutForce.includes("--clean");
212
+ const unknownArgs = argsWithoutForce.filter((arg) => arg !== "--clean");
169
213
  if (unknownArgs.length > 0) {
170
214
  console.error(`Unknown option(s) for destroy: ${unknownArgs.join(" ")}`);
171
215
  printUsage();
@@ -177,7 +221,8 @@ async function main() {
177
221
  }
178
222
 
179
223
  if (command === "start") {
180
- if (args[0] !== "iteration" || args.length !== 1) {
224
+ const { remainingArgs: argsWithoutForce } = parseForce(args);
225
+ if (argsWithoutForce[0] !== "iteration" || argsWithoutForce.length !== 1) {
181
226
  console.error(`Usage for start: nvst start iteration`);
182
227
  printUsage();
183
228
  process.exitCode = 1;
@@ -187,6 +232,26 @@ async function main() {
187
232
  return;
188
233
  }
189
234
 
235
+ if (command === "flow") {
236
+ try {
237
+ const { provider, remainingArgs: postAgentArgs } = parseOptionalAgentArg(args);
238
+ const { force, remainingArgs: postForceArgs } = parseForce(postAgentArgs);
239
+ if (postForceArgs.length > 0) {
240
+ console.error(`Unknown option(s) for flow: ${postForceArgs.join(" ")}`);
241
+ printUsage();
242
+ process.exitCode = 1;
243
+ return;
244
+ }
245
+ await runFlow({ provider, force });
246
+ return;
247
+ } catch (error) {
248
+ console.error(error instanceof Error ? error.message : String(error));
249
+ printUsage();
250
+ process.exitCode = 1;
251
+ return;
252
+ }
253
+ }
254
+
190
255
  if (command === "create") {
191
256
  if (args.length === 0) {
192
257
  console.error(
@@ -203,15 +268,16 @@ async function main() {
203
268
  try {
204
269
  const { provider, remainingArgs: postAgentArgs } = parseAgentArg(args.slice(1));
205
270
  const { mode, remainingArgs: postModeArgs } = parseMode(postAgentArgs);
271
+ const { force, remainingArgs: postForceArgs } = parseForce(postModeArgs);
206
272
 
207
- if (postModeArgs.length > 0) {
208
- console.error(`Unknown option(s) for create project-context: ${postModeArgs.join(" ")}`);
273
+ if (postForceArgs.length > 0) {
274
+ console.error(`Unknown option(s) for create project-context: ${postForceArgs.join(" ")}`);
209
275
  printUsage();
210
276
  process.exitCode = 1;
211
277
  return;
212
278
  }
213
279
 
214
- await runCreateProjectContext({ provider, mode });
280
+ await runCreateProjectContext({ provider, mode, force });
215
281
  return;
216
282
  } catch (error) {
217
283
  console.error(error instanceof Error ? error.message : String(error));
@@ -234,8 +300,9 @@ async function main() {
234
300
  remainingArgs: postRetryArgs,
235
301
  } = parseOptionalIntegerFlag(postIterationsArgs, "--retry-on-fail", 0);
236
302
 
237
- const stopOnCritical = postRetryArgs.includes("--stop-on-critical");
238
- const unknownArgs = postRetryArgs.filter((arg) => arg !== "--stop-on-critical");
303
+ const { force, remainingArgs: postForceArgs } = parseForce(postRetryArgs);
304
+ const stopOnCritical = postForceArgs.includes("--stop-on-critical");
305
+ const unknownArgs = postForceArgs.filter((arg) => arg !== "--stop-on-critical");
239
306
  if (unknownArgs.length > 0) {
240
307
  console.error(`Unknown option(s) for create prototype: ${unknownArgs.join(" ")}`);
241
308
  printUsage();
@@ -243,7 +310,7 @@ async function main() {
243
310
  return;
244
311
  }
245
312
 
246
- await runCreatePrototype({ provider, iterations, retryOnFail, stopOnCritical });
313
+ await runCreatePrototype({ provider, iterations, retryOnFail, stopOnCritical, force });
247
314
  return;
248
315
  } catch (error) {
249
316
  console.error(error instanceof Error ? error.message : String(error));
@@ -278,9 +345,10 @@ async function main() {
278
345
 
279
346
  if (subcommand === "issue") {
280
347
  const subArgs = args.slice(1);
348
+ const { remainingArgs: subArgsWithoutForce } = parseForce(subArgs);
281
349
 
282
350
  // Check for --help before parsing
283
- if (subArgs.includes("--help") || subArgs.includes("-h")) {
351
+ if (subArgsWithoutForce.includes("--help") || subArgsWithoutForce.includes("-h")) {
284
352
  console.log(`Usage for create issue:
285
353
  nvst create issue --agent <provider> Create issues interactively via agent
286
354
  nvst create issue --test-execution-report Derive issues from test execution results
@@ -292,8 +360,8 @@ Providers: claude, codex, gemini, cursor`);
292
360
 
293
361
  try {
294
362
  // Check for --test-execution-report flag
295
- if (subArgs.includes("--test-execution-report")) {
296
- const remaining = subArgs.filter((a) => a !== "--test-execution-report");
363
+ if (subArgsWithoutForce.includes("--test-execution-report")) {
364
+ const remaining = subArgsWithoutForce.filter((a) => a !== "--test-execution-report");
297
365
  if (remaining.length > 0) {
298
366
  console.error(`Unknown option(s) for create issue --test-execution-report: ${remaining.join(" ")}`);
299
367
  printUsage();
@@ -304,7 +372,7 @@ Providers: claude, codex, gemini, cursor`);
304
372
  return;
305
373
  }
306
374
 
307
- const { provider, remainingArgs: postAgentArgs } = parseAgentArg(subArgs);
375
+ const { provider, remainingArgs: postAgentArgs } = parseAgentArg(subArgsWithoutForce);
308
376
 
309
377
  if (postAgentArgs.length > 0) {
310
378
  console.error(`Unknown option(s) for create issue: ${postAgentArgs.join(" ")}`);
@@ -330,37 +398,67 @@ Providers: claude, codex, gemini, cursor`);
330
398
  }
331
399
 
332
400
  if (command === "define") {
333
- if (args.length === 0 || args[0] !== "requirement") {
334
- console.error(`Usage for define: nvst define requirement --agent <provider>`);
401
+ if (args.length === 0) {
402
+ console.error(`Usage for define: nvst define <requirement|refactor-plan> --agent <provider>`);
335
403
  printUsage();
336
404
  process.exitCode = 1;
337
405
  return;
338
406
  }
339
407
 
340
- try {
341
- const { provider, remainingArgs: postAgentArgs } = parseAgentArg(args.slice(1));
408
+ const subcommand = args[0];
342
409
 
343
- if (postAgentArgs.length > 0) {
344
- console.error(`Unknown option(s) for define requirement: ${postAgentArgs.join(" ")}`);
410
+ if (subcommand === "requirement") {
411
+ try {
412
+ const { provider, remainingArgs: postAgentArgs } = parseAgentArg(args.slice(1));
413
+ const { force, remainingArgs: postForceArgs } = parseForce(postAgentArgs);
414
+ if (postForceArgs.length > 0) {
415
+ console.error(`Unknown option(s) for define requirement: ${postForceArgs.join(" ")}`);
416
+ printUsage();
417
+ process.exitCode = 1;
418
+ return;
419
+ }
420
+
421
+ await runDefineRequirement({ provider, force });
422
+ return;
423
+ } catch (error) {
424
+ console.error(error instanceof Error ? error.message : String(error));
345
425
  printUsage();
346
426
  process.exitCode = 1;
347
427
  return;
348
428
  }
429
+ }
349
430
 
350
- await runDefineRequirement({ provider });
351
- return;
352
- } catch (error) {
353
- console.error(error instanceof Error ? error.message : String(error));
354
- printUsage();
355
- process.exitCode = 1;
356
- return;
431
+ if (subcommand === "refactor-plan") {
432
+ try {
433
+ const { provider, remainingArgs: postAgentArgs } = parseAgentArg(args.slice(1));
434
+ const { force, remainingArgs: postForceArgs } = parseForce(postAgentArgs);
435
+ if (postForceArgs.length > 0) {
436
+ console.error(`Unknown option(s) for define refactor-plan: ${postForceArgs.join(" ")}`);
437
+ printUsage();
438
+ process.exitCode = 1;
439
+ return;
440
+ }
441
+
442
+ await runDefineRefactorPlan({ provider, force });
443
+ return;
444
+ } catch (error) {
445
+ console.error(error instanceof Error ? error.message : String(error));
446
+ printUsage();
447
+ process.exitCode = 1;
448
+ return;
449
+ }
357
450
  }
451
+
452
+ console.error(`Unknown define subcommand: ${subcommand}`);
453
+ printUsage();
454
+ process.exitCode = 1;
455
+ return;
358
456
  }
359
457
 
360
458
  if (command === "refine") {
361
459
  if (args.length === 0) {
362
460
  console.error(
363
- `Usage for refine: nvst refine <requirement|project-context|test-plan> --agent <provider> [--challenge]`,
461
+ `Usage for refine: nvst refine <requirement|project-context|test-plan|refactor-plan> --agent <provider> [--challenge]`,
364
462
  );
365
463
  printUsage();
366
464
  process.exitCode = 1;
@@ -372,8 +470,9 @@ Providers: claude, codex, gemini, cursor`);
372
470
  if (subcommand === "requirement") {
373
471
  try {
374
472
  const { provider, remainingArgs: postAgentArgs } = parseAgentArg(args.slice(1));
375
- const challenge = postAgentArgs.includes("--challenge");
376
- const unknownArgs = postAgentArgs.filter((arg) => arg !== "--challenge");
473
+ const { force, remainingArgs: postForceArgs } = parseForce(postAgentArgs);
474
+ const challenge = postForceArgs.includes("--challenge");
475
+ const unknownArgs = postForceArgs.filter((arg) => arg !== "--challenge");
377
476
 
378
477
  if (unknownArgs.length > 0) {
379
478
  console.error(`Unknown option(s) for refine requirement: ${unknownArgs.join(" ")}`);
@@ -382,7 +481,7 @@ Providers: claude, codex, gemini, cursor`);
382
481
  return;
383
482
  }
384
483
 
385
- await runRefineRequirement({ provider, challenge });
484
+ await runRefineRequirement({ provider, challenge, force });
386
485
  return;
387
486
  } catch (error) {
388
487
  console.error(error instanceof Error ? error.message : String(error));
@@ -395,8 +494,9 @@ Providers: claude, codex, gemini, cursor`);
395
494
  if (subcommand === "project-context") {
396
495
  try {
397
496
  const { provider, remainingArgs: postAgentArgs } = parseAgentArg(args.slice(1));
398
- const challenge = postAgentArgs.includes("--challenge");
399
- const unknownArgs = postAgentArgs.filter((arg) => arg !== "--challenge");
497
+ const { force, remainingArgs: postForceArgs } = parseForce(postAgentArgs);
498
+ const challenge = postForceArgs.includes("--challenge");
499
+ const unknownArgs = postForceArgs.filter((arg) => arg !== "--challenge");
400
500
 
401
501
  if (unknownArgs.length > 0) {
402
502
  console.error(
@@ -407,7 +507,7 @@ Providers: claude, codex, gemini, cursor`);
407
507
  return;
408
508
  }
409
509
 
410
- await runRefineProjectContext({ provider, challenge });
510
+ await runRefineProjectContext({ provider, challenge, force });
411
511
  return;
412
512
  } catch (error) {
413
513
  console.error(error instanceof Error ? error.message : String(error));
@@ -420,8 +520,9 @@ Providers: claude, codex, gemini, cursor`);
420
520
  if (subcommand === "test-plan") {
421
521
  try {
422
522
  const { provider, remainingArgs: postAgentArgs } = parseAgentArg(args.slice(1));
423
- const challenge = postAgentArgs.includes("--challenge");
424
- const unknownArgs = postAgentArgs.filter((arg) => arg !== "--challenge");
523
+ const { force, remainingArgs: postForceArgs } = parseForce(postAgentArgs);
524
+ const challenge = postForceArgs.includes("--challenge");
525
+ const unknownArgs = postForceArgs.filter((arg) => arg !== "--challenge");
425
526
 
426
527
  if (unknownArgs.length > 0) {
427
528
  console.error(`Unknown option(s) for refine test-plan: ${unknownArgs.join(" ")}`);
@@ -430,7 +531,31 @@ Providers: claude, codex, gemini, cursor`);
430
531
  return;
431
532
  }
432
533
 
433
- await runRefineTestPlan({ provider, challenge });
534
+ await runRefineTestPlan({ provider, challenge, force });
535
+ return;
536
+ } catch (error) {
537
+ console.error(error instanceof Error ? error.message : String(error));
538
+ printUsage();
539
+ process.exitCode = 1;
540
+ return;
541
+ }
542
+ }
543
+
544
+ if (subcommand === "refactor-plan") {
545
+ try {
546
+ const { provider, remainingArgs: postAgentArgs } = parseAgentArg(args.slice(1));
547
+ const { force, remainingArgs: postForceArgs } = parseForce(postAgentArgs);
548
+ const challenge = postForceArgs.includes("--challenge");
549
+ const unknownArgs = postForceArgs.filter((arg) => arg !== "--challenge");
550
+
551
+ if (unknownArgs.length > 0) {
552
+ console.error(`Unknown option(s) for refine refactor-plan: ${unknownArgs.join(" ")}`);
553
+ printUsage();
554
+ process.exitCode = 1;
555
+ return;
556
+ }
557
+
558
+ await runRefineRefactorPlan({ provider, challenge, force });
434
559
  return;
435
560
  } catch (error) {
436
561
  console.error(error instanceof Error ? error.message : String(error));
@@ -447,27 +572,44 @@ Providers: claude, codex, gemini, cursor`);
447
572
  }
448
573
 
449
574
  if (command === "approve") {
450
- if (args.length !== 1) {
451
- console.error(`Usage for approve: nvst approve <requirement|project-context|test-plan>`);
575
+ if (args.length === 0) {
576
+ console.error(`Usage for approve: nvst approve <requirement|project-context|test-plan|prototype|refactor-plan>`);
452
577
  printUsage();
453
578
  process.exitCode = 1;
454
579
  return;
455
580
  }
456
581
 
457
582
  const subcommand = args[0];
583
+ const { force, remainingArgs: unknownArgs } = parseForce(args.slice(1));
584
+ if (unknownArgs.length > 0) {
585
+ console.error(`Unknown option(s) for approve ${subcommand}: ${unknownArgs.join(" ")}`);
586
+ printUsage();
587
+ process.exitCode = 1;
588
+ return;
589
+ }
458
590
 
459
591
  if (subcommand === "requirement") {
460
- await runApproveRequirement();
592
+ await runApproveRequirement({ force });
461
593
  return;
462
594
  }
463
595
 
464
596
  if (subcommand === "project-context") {
465
- await runApproveProjectContext();
597
+ await runApproveProjectContext({ force });
466
598
  return;
467
599
  }
468
600
 
469
601
  if (subcommand === "test-plan") {
470
- await runApproveTestPlan();
602
+ await runApproveTestPlan({ force });
603
+ return;
604
+ }
605
+
606
+ if (subcommand === "prototype") {
607
+ await runApprovePrototype({ force });
608
+ return;
609
+ }
610
+
611
+ if (subcommand === "refactor-plan") {
612
+ await runApproveRefactorPlan({ force });
471
613
  return;
472
614
  }
473
615
 
@@ -490,14 +632,15 @@ Providers: claude, codex, gemini, cursor`);
490
632
  if (subcommand === "test-plan") {
491
633
  try {
492
634
  const { provider, remainingArgs: postAgentArgs } = parseAgentArg(args.slice(1));
493
- if (postAgentArgs.length > 0) {
494
- console.error(`Unknown option(s) for execute test-plan: ${postAgentArgs.join(" ")}`);
635
+ const { force, remainingArgs: postForceArgs } = parseForce(postAgentArgs);
636
+ if (postForceArgs.length > 0) {
637
+ console.error(`Unknown option(s) for execute test-plan: ${postForceArgs.join(" ")}`);
495
638
  printUsage();
496
639
  process.exitCode = 1;
497
640
  return;
498
641
  }
499
642
 
500
- await runExecuteTestPlan({ provider });
643
+ await runExecuteTestPlan({ provider, force });
501
644
  return;
502
645
  } catch (error) {
503
646
  console.error(error instanceof Error ? error.message : String(error));
@@ -516,11 +659,11 @@ Providers: claude, codex, gemini, cursor`);
516
659
  } = parseOptionalIntegerFlag(postAgentArgs, "--iterations", 1);
517
660
  const {
518
661
  value: retryOnFail,
519
- remainingArgs: postRetryArgs,
662
+ remainingArgs: unknownArgs,
520
663
  } = parseOptionalIntegerFlag(postIterationsArgs, "--retry-on-fail", 0);
521
664
 
522
- if (postRetryArgs.length > 0) {
523
- console.error(`Unknown option(s) for execute automated-fix: ${postRetryArgs.join(" ")}`);
665
+ if (unknownArgs.length > 0) {
666
+ console.error(`Unknown option(s) for execute automated-fix: ${unknownArgs.join(" ")}`);
524
667
  printUsage();
525
668
  process.exitCode = 1;
526
669
  return;
@@ -539,8 +682,9 @@ Providers: claude, codex, gemini, cursor`);
539
682
  if (subcommand === "manual-fix") {
540
683
  try {
541
684
  const { provider, remainingArgs: postAgentArgs } = parseAgentArg(args.slice(1));
542
- if (postAgentArgs.length > 0) {
543
- console.error(`Unknown option(s) for execute manual-fix: ${postAgentArgs.join(" ")}`);
685
+ const { remainingArgs: postForceArgs } = parseForce(postAgentArgs);
686
+ if (postForceArgs.length > 0) {
687
+ console.error(`Unknown option(s) for execute manual-fix: ${postForceArgs.join(" ")}`);
544
688
  printUsage();
545
689
  process.exitCode = 1;
546
690
  return;
@@ -556,6 +700,27 @@ Providers: claude, codex, gemini, cursor`);
556
700
  }
557
701
  }
558
702
 
703
+ if (subcommand === "refactor") {
704
+ try {
705
+ const { provider, remainingArgs: postAgentArgs } = parseAgentArg(args.slice(1));
706
+ const { force, remainingArgs: postForceArgs } = parseForce(postAgentArgs);
707
+ if (postForceArgs.length > 0) {
708
+ console.error(`Unknown option(s) for execute refactor: ${postForceArgs.join(" ")}`);
709
+ printUsage();
710
+ process.exitCode = 1;
711
+ return;
712
+ }
713
+
714
+ await runExecuteRefactor({ provider, force });
715
+ return;
716
+ } catch (error) {
717
+ console.error(error instanceof Error ? error.message : String(error));
718
+ printUsage();
719
+ process.exitCode = 1;
720
+ return;
721
+ }
722
+ }
723
+
559
724
  console.error(`Unknown execute subcommand: ${subcommand}`);
560
725
  printUsage();
561
726
  process.exitCode = 1;
@@ -573,6 +738,10 @@ Providers: claude, codex, gemini, cursor`);
573
738
  }
574
739
 
575
740
  main().catch((error) => {
741
+ if (error instanceof GuardrailAbortError) {
742
+ // exitCode already set and "Aborted." already written by assertGuardrail
743
+ return;
744
+ }
576
745
  console.error("nvst failed:", error);
577
746
  process.exitCode = 1;
578
747
  });
@@ -1,18 +1,25 @@
1
1
  import { join } from "node:path";
2
2
 
3
+ import { assertGuardrail } from "../guardrail";
3
4
  import { exists, readState, writeState } from "../state";
4
5
 
5
- export async function runApproveProjectContext(): Promise<void> {
6
+ export interface ApproveProjectContextOptions {
7
+ force?: boolean;
8
+ }
9
+
10
+ export async function runApproveProjectContext(opts: ApproveProjectContextOptions = {}): Promise<void> {
11
+ const { force = false } = opts;
6
12
  const projectRoot = process.cwd();
7
13
  const state = await readState(projectRoot);
8
14
 
9
15
  // US-002-AC01: Validate status is pending_approval
10
16
  const projectContext = state.phases.prototype.project_context;
11
- if (projectContext.status !== "pending_approval") {
12
- throw new Error(
13
- `Cannot approve project context from status '${projectContext.status}'. Expected pending_approval.`,
14
- );
15
- }
17
+ await assertGuardrail(
18
+ state,
19
+ projectContext.status !== "pending_approval",
20
+ `Cannot approve project context from status '${projectContext.status}'. Expected pending_approval.`,
21
+ { force },
22
+ );
16
23
 
17
24
  // US-002-AC01: Validate project context file exists
18
25
  const contextFile = projectContext.file;