@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
@@ -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) {