forge-cc 0.1.13 → 0.1.15

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.
@@ -16,8 +16,30 @@ export function loadConfig(projectDir) {
16
16
  // Auto-detect from package.json
17
17
  return autoDetectConfig(projectDir);
18
18
  }
19
+ function detectTestDir(projectDir) {
20
+ for (const dir of ["tests", "__tests__", "test"]) {
21
+ if (existsSync(join(projectDir, dir))) {
22
+ return dir;
23
+ }
24
+ }
25
+ return "tests";
26
+ }
27
+ function detectTestingConfig(projectDir, allDeps) {
28
+ const runner = allDeps.vitest ? "vitest" : allDeps.jest ? "jest" : null;
29
+ if (!runner)
30
+ return undefined;
31
+ return {
32
+ enforce: false,
33
+ runner,
34
+ testDir: detectTestDir(projectDir),
35
+ sourceDir: "src",
36
+ structural: true,
37
+ categories: [],
38
+ };
39
+ }
19
40
  function autoDetectConfig(projectDir) {
20
41
  const gates = [];
42
+ let testing;
21
43
  try {
22
44
  const pkgPath = join(projectDir, "package.json");
23
45
  const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
@@ -31,6 +53,7 @@ function autoDetectConfig(projectDir) {
31
53
  gates.push("lint");
32
54
  if (pkg.scripts?.test)
33
55
  gates.push("tests");
56
+ testing = detectTestingConfig(projectDir, allDeps);
34
57
  }
35
58
  catch {
36
59
  // No package.json or invalid — use defaults
@@ -39,6 +62,6 @@ function autoDetectConfig(projectDir) {
39
62
  if (gates.length === 0) {
40
63
  gates.push("types", "lint", "tests");
41
64
  }
42
- return forgeConfigSchema.parse({ gates });
65
+ return forgeConfigSchema.parse({ gates, testing });
43
66
  }
44
67
  //# sourceMappingURL=loader.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGhD,MAAM,UAAU,UAAU,CAAC,UAAkB;IAC3C,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;IAEnD,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;YAC1D,OAAO,iBAAiB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACtC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,OAAO,CAAC,IAAI,CAAC,yCAAyC,OAAO,iCAAiC,CAAC,CAAC;QAClG,CAAC;IACH,CAAC;IAED,gCAAgC;IAChC,OAAO,gBAAgB,CAAC,UAAU,CAAC,CAAC;AACtC,CAAC;AAED,SAAS,gBAAgB,CAAC,UAAkB;IAC1C,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;QACjD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;QAEvD,MAAM,OAAO,GAAG;YACd,GAAG,GAAG,CAAC,YAAY;YACnB,GAAG,GAAG,CAAC,eAAe;SACvB,CAAC;QAEF,IAAI,OAAO,CAAC,UAAU;YAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC5C,IAAI,OAAO,CAAC,gBAAgB,CAAC,IAAI,OAAO,CAAC,KAAK;YAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnE,IAAI,GAAG,CAAC,OAAO,EAAE,IAAI;YAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,4CAA4C;QAC5C,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,CAAC;IAED,OAAO,iBAAiB,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAC5C,CAAC"}
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"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"}
@@ -19,6 +19,28 @@ export declare const reviewConfigSchema: z.ZodObject<{
19
19
  }, {
20
20
  blocking?: boolean | undefined;
21
21
  }>;
22
+ export declare const testingConfigSchema: z.ZodObject<{
23
+ enforce: z.ZodDefault<z.ZodBoolean>;
24
+ runner: z.ZodDefault<z.ZodEnum<["vitest", "jest", "none"]>>;
25
+ testDir: z.ZodDefault<z.ZodString>;
26
+ sourceDir: z.ZodDefault<z.ZodString>;
27
+ structural: z.ZodDefault<z.ZodBoolean>;
28
+ categories: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
29
+ }, "strip", z.ZodTypeAny, {
30
+ runner: "vitest" | "jest" | "none";
31
+ categories: string[];
32
+ enforce: boolean;
33
+ testDir: string;
34
+ sourceDir: string;
35
+ structural: boolean;
36
+ }, {
37
+ runner?: "vitest" | "jest" | "none" | undefined;
38
+ categories?: string[] | undefined;
39
+ enforce?: boolean | undefined;
40
+ testDir?: string | undefined;
41
+ sourceDir?: string | undefined;
42
+ structural?: boolean | undefined;
43
+ }>;
22
44
  export declare const forgeConfigSchema: z.ZodObject<{
23
45
  gates: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
24
46
  maxIterations: z.ZodDefault<z.ZodNumber>;
@@ -45,25 +67,51 @@ export declare const forgeConfigSchema: z.ZodObject<{
45
67
  }, {
46
68
  blocking?: boolean | undefined;
47
69
  }>>;
70
+ testing: z.ZodOptional<z.ZodObject<{
71
+ enforce: z.ZodDefault<z.ZodBoolean>;
72
+ runner: z.ZodDefault<z.ZodEnum<["vitest", "jest", "none"]>>;
73
+ testDir: z.ZodDefault<z.ZodString>;
74
+ sourceDir: z.ZodDefault<z.ZodString>;
75
+ structural: z.ZodDefault<z.ZodBoolean>;
76
+ categories: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
77
+ }, "strip", z.ZodTypeAny, {
78
+ runner: "vitest" | "jest" | "none";
79
+ categories: string[];
80
+ enforce: boolean;
81
+ testDir: string;
82
+ sourceDir: string;
83
+ structural: boolean;
84
+ }, {
85
+ runner?: "vitest" | "jest" | "none" | undefined;
86
+ categories?: string[] | undefined;
87
+ enforce?: boolean | undefined;
88
+ testDir?: string | undefined;
89
+ sourceDir?: string | undefined;
90
+ structural?: boolean | undefined;
91
+ }>>;
48
92
  }, "strip", z.ZodTypeAny, {
49
93
  gates: string[];
50
94
  maxIterations: number;
51
95
  verifyFreshness: number;
52
- prdPath?: string | undefined;
53
- review?: {
54
- blocking: boolean;
55
- } | undefined;
56
96
  devServer?: {
57
97
  port: number;
58
98
  command: string;
59
99
  readyPattern?: string | undefined;
60
100
  } | undefined;
61
- linearProject?: string | undefined;
62
- }, {
63
101
  prdPath?: string | undefined;
102
+ linearProject?: string | undefined;
64
103
  review?: {
65
- blocking?: boolean | undefined;
104
+ blocking: boolean;
105
+ } | undefined;
106
+ testing?: {
107
+ runner: "vitest" | "jest" | "none";
108
+ categories: string[];
109
+ enforce: boolean;
110
+ testDir: string;
111
+ sourceDir: string;
112
+ structural: boolean;
66
113
  } | undefined;
114
+ }, {
67
115
  gates?: string[] | undefined;
68
116
  maxIterations?: number | undefined;
69
117
  verifyFreshness?: number | undefined;
@@ -72,6 +120,18 @@ export declare const forgeConfigSchema: z.ZodObject<{
72
120
  command: string;
73
121
  readyPattern?: string | undefined;
74
122
  } | undefined;
123
+ prdPath?: string | undefined;
75
124
  linearProject?: string | undefined;
125
+ review?: {
126
+ blocking?: boolean | undefined;
127
+ } | undefined;
128
+ testing?: {
129
+ runner?: "vitest" | "jest" | "none" | undefined;
130
+ categories?: string[] | undefined;
131
+ enforce?: boolean | undefined;
132
+ testDir?: string | undefined;
133
+ sourceDir?: string | undefined;
134
+ structural?: boolean | undefined;
135
+ } | undefined;
76
136
  }>;
77
137
  export type ForgeConfigInput = z.input<typeof forgeConfigSchema>;
@@ -7,6 +7,14 @@ export const devServerSchema = z.object({
7
7
  export const reviewConfigSchema = z.object({
8
8
  blocking: z.boolean().default(false),
9
9
  });
10
+ export const testingConfigSchema = z.object({
11
+ enforce: z.boolean().default(true),
12
+ runner: z.enum(["vitest", "jest", "none"]).default("vitest"),
13
+ testDir: z.string().default("tests"),
14
+ sourceDir: z.string().default("src"),
15
+ structural: z.boolean().default(true),
16
+ categories: z.array(z.string()).default([]),
17
+ });
10
18
  export const forgeConfigSchema = z.object({
11
19
  gates: z.array(z.string()).default(["types", "lint", "tests"]),
12
20
  maxIterations: z.number().int().positive().default(5),
@@ -15,5 +23,6 @@ export const forgeConfigSchema = z.object({
15
23
  prdPath: z.string().optional(),
16
24
  linearProject: z.string().optional(),
17
25
  review: reviewConfigSchema.optional(),
26
+ testing: testingConfigSchema.optional(),
18
27
  });
19
28
  //# sourceMappingURL=schema.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACjC,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACpC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;CACrC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9D,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IACrD,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC;IAC7D,SAAS,EAAE,eAAe,CAAC,QAAQ,EAAE;IACrC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC9B,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACpC,MAAM,EAAE,kBAAkB,CAAC,QAAQ,EAAE;CACtC,CAAC,CAAC"}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACjC,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACpC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;CACrC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1C,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IAClC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;IAC5D,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC;IACpC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IACpC,UAAU,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IACrC,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;CAC5C,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9D,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IACrD,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC;IAC7D,SAAS,EAAE,eAAe,CAAC,QAAQ,EAAE;IACrC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC9B,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACpC,MAAM,EAAE,kBAAkB,CAAC,QAAQ,EAAE;IACrC,OAAO,EAAE,mBAAmB,CAAC,QAAQ,EAAE;CACxC,CAAC,CAAC"}
@@ -36,3 +36,11 @@ export declare function buildVisualRemediation(error: GateError): string;
36
36
  * next-step guidance so fix agents know exactly where to look.
37
37
  */
38
38
  export declare function buildReviewRemediation(error: GateError): string;
39
+ /**
40
+ * Build actionable remediation text for a test coverage error.
41
+ *
42
+ * Handles missing test files (enforcement mode) and zero-coverage baseline
43
+ * failures. Returns file-specific instructions when a source file is
44
+ * identified, or a general scaffolding instruction otherwise.
45
+ */
46
+ export declare function buildTestCoverageRemediation(error: GateError): string;
@@ -372,6 +372,33 @@ export function buildReviewRemediation(error) {
372
372
  return `${location}Review finding: ${error.message} — address the issue and re-run the review gate.`;
373
373
  }
374
374
  // ---------------------------------------------------------------------------
375
+ // Test Coverage Remediation
376
+ // ---------------------------------------------------------------------------
377
+ /**
378
+ * Build actionable remediation text for a test coverage error.
379
+ *
380
+ * Handles missing test files (enforcement mode) and zero-coverage baseline
381
+ * failures. Returns file-specific instructions when a source file is
382
+ * identified, or a general scaffolding instruction otherwise.
383
+ */
384
+ export function buildTestCoverageRemediation(error) {
385
+ const location = formatLocation(error);
386
+ // Missing test file for a specific source file (enforcement mode)
387
+ if (error.file && /missing.*test|no.*test.*file/i.test(error.message)) {
388
+ return `${location}Create a test file for this source file to satisfy enforcement. Run \`/forge:setup\` to scaffold tests automatically.`;
389
+ }
390
+ // Zero coverage / baseline failure
391
+ if (/no tests found|zero.*coverage/i.test(error.message)) {
392
+ return `${location}No tests exist in this project. Run \`/forge:setup\` to scaffold a test suite with the correct runner and directory structure.`;
393
+ }
394
+ // Thin coverage warning
395
+ if (/thin.*coverage|low.*ratio/i.test(error.message)) {
396
+ return `${location}Test coverage is very low. Prioritize adding tests for critical paths (API routes, business logic) first.`;
397
+ }
398
+ // Generic coverage remediation
399
+ return `${location}Add test coverage for the identified file. Run \`/forge:setup\` to scaffold tests.`;
400
+ }
401
+ // ---------------------------------------------------------------------------
375
402
  // Shared Utilities
376
403
  // ---------------------------------------------------------------------------
377
404
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"remediation.js","sourceRoot":"","sources":["../../src/gates/remediation.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"remediation.js","sourceRoot":"","sources":["../../src/gates/remediation.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,7 @@
1
1
  import { execSync } from "node:child_process";
2
- import { buildTestRemediation } from "./remediation.js";
2
+ import { buildTestRemediation, buildTestCoverageRemediation } from "./remediation.js";
3
+ import { analyzeTestCoverage } from "./test-analysis.js";
4
+ import { loadConfig } from "../config/loader.js";
3
5
  /**
4
6
  * Common test failure patterns with file/line info:
5
7
  * FAIL src/foo.test.ts > suite > test name
@@ -11,7 +13,12 @@ export async function verifyTests(projectDir) {
11
13
  const start = Date.now();
12
14
  const errors = [];
13
15
  const warnings = [];
14
- // Check if the test script exists in package.json
16
+ // Load config and run test analysis
17
+ const config = loadConfig(projectDir);
18
+ const testingConfig = config.testing;
19
+ const analysis = await analyzeTestCoverage(projectDir);
20
+ // Detect whether a test script exists in package.json
21
+ let hasTestScript = false;
15
22
  try {
16
23
  const pkgRaw = execSync("node -e \"process.stdout.write(JSON.stringify(require('./package.json')))\"", {
17
24
  cwd: projectDir,
@@ -19,103 +26,194 @@ export async function verifyTests(projectDir) {
19
26
  timeout: 10_000,
20
27
  });
21
28
  const pkg = JSON.parse(String(pkgRaw));
22
- if (!pkg.scripts?.test) {
23
- return {
24
- gate: "tests",
25
- passed: true,
26
- errors: [],
27
- warnings: ["No test script found"],
28
- duration_ms: Date.now() - start,
29
- };
30
- }
29
+ hasTestScript = !!pkg.scripts?.test;
31
30
  }
32
31
  catch {
32
+ // No package.json or invalid — hasTestScript stays false
33
+ }
34
+ // -----------------------------------------------------------------------
35
+ // Baseline check: If zero test files AND no test script, FAIL immediately
36
+ // -----------------------------------------------------------------------
37
+ if (analysis.coverage.testFiles === 0 && !hasTestScript) {
38
+ const categoryNames = analysis.categories.map(c => c.name).join(", ");
39
+ const msg = `No tests found. ${analysis.coverage.sourceFiles} source file${analysis.coverage.sourceFiles === 1 ? "" : "s"} across ${analysis.categories.length} categor${analysis.categories.length === 1 ? "y" : "ies"} (${categoryNames || "none"}) have no test coverage. Run \`/forge:setup\` to scaffold tests.`;
40
+ const error = { message: msg };
41
+ error.remediation = buildTestCoverageRemediation(error);
42
+ errors.push(error);
33
43
  return {
34
44
  gate: "tests",
35
- passed: true,
36
- errors: [],
37
- warnings: ["No test script found"],
45
+ passed: false,
46
+ errors,
47
+ warnings,
38
48
  duration_ms: Date.now() - start,
39
49
  };
40
50
  }
41
- try {
42
- const result = execSync("npm run test -- --run", {
43
- cwd: projectDir,
44
- stdio: "pipe",
45
- timeout: 300_000,
46
- });
47
- const output = String(result);
48
- // Parse test summary from Vitest output
49
- const summaryMatch = output.match(/Tests\s+(\d+)\s+passed(?:\s*\|\s*(\d+)\s+failed)?/);
50
- if (summaryMatch) {
51
- const passed = summaryMatch[1];
52
- const failed = summaryMatch[2] ?? "0";
53
- warnings.push(`${passed} passed, ${failed} failed`);
54
- }
51
+ // Also baseline-fail if test files exist but no test script to run them
52
+ if (analysis.coverage.testFiles === 0 && hasTestScript) {
53
+ // Test script exists but no test files found — still baseline fail
54
+ const msg = `No test files found. A test script exists in package.json but no test files were detected. Run \`/forge:setup\` to scaffold tests.`;
55
+ const error = { message: msg };
56
+ error.remediation = buildTestCoverageRemediation(error);
57
+ errors.push(error);
55
58
  return {
56
59
  gate: "tests",
57
- passed: true,
58
- errors: [],
60
+ passed: false,
61
+ errors,
59
62
  warnings,
60
63
  duration_ms: Date.now() - start,
61
64
  };
62
65
  }
63
- catch (err) {
64
- const stdout = err instanceof Error && "stdout" in err
65
- ? String(err.stdout)
66
- : "";
67
- const stderr = err instanceof Error && "stderr" in err
68
- ? String(err.stderr)
69
- : "";
70
- const output = `${stdout}\n${stderr}`;
71
- let lastFailFile;
72
- for (const line of output.split("\n")) {
73
- const trimmed = line.trim();
74
- if (!trimmed)
75
- continue;
76
- // Track which test file we're in
77
- const failMatch = TEST_FILE_RE.exec(trimmed);
78
- if (failMatch) {
79
- lastFailFile = failMatch[1];
66
+ // -----------------------------------------------------------------------
67
+ // Run tests (if a test script exists)
68
+ // -----------------------------------------------------------------------
69
+ let testsRanSuccessfully = true;
70
+ if (hasTestScript) {
71
+ try {
72
+ const result = execSync("npm run test -- --run", {
73
+ cwd: projectDir,
74
+ stdio: "pipe",
75
+ timeout: 300_000,
76
+ });
77
+ const output = String(result);
78
+ // Parse test summary from Vitest output
79
+ const summaryMatch = output.match(/Tests\s+(\d+)\s+passed(?:\s*\|\s*(\d+)\s+failed)?/);
80
+ if (summaryMatch) {
81
+ const passed = summaryMatch[1];
82
+ const failed = summaryMatch[2] ?? "0";
83
+ warnings.push(`${passed} passed, ${failed} failed`);
80
84
  }
81
- // Try to extract stack trace location
82
- const stackMatch = STACKTRACE_RE.exec(trimmed);
83
- if (stackMatch) {
84
- lastFailFile = stackMatch[1];
85
+ }
86
+ catch (err) {
87
+ testsRanSuccessfully = false;
88
+ const stdout = err instanceof Error && "stdout" in err
89
+ ? String(err.stdout)
90
+ : "";
91
+ const stderr = err instanceof Error && "stderr" in err
92
+ ? String(err.stderr)
93
+ : "";
94
+ const output = `${stdout}\n${stderr}`;
95
+ let lastFailFile;
96
+ for (const line of output.split("\n")) {
97
+ const trimmed = line.trim();
98
+ if (!trimmed)
99
+ continue;
100
+ // Track which test file we're in
101
+ const failMatch = TEST_FILE_RE.exec(trimmed);
102
+ if (failMatch) {
103
+ lastFailFile = failMatch[1];
104
+ }
105
+ // Try to extract stack trace location
106
+ const stackMatch = STACKTRACE_RE.exec(trimmed);
107
+ if (stackMatch) {
108
+ lastFailFile = stackMatch[1];
109
+ }
110
+ if (trimmed.includes("FAIL") ||
111
+ trimmed.includes("AssertionError") ||
112
+ trimmed.includes("AssertionError") ||
113
+ trimmed.includes("Expected") ||
114
+ trimmed.includes("Received")) {
115
+ errors.push({
116
+ file: lastFailFile,
117
+ line: stackMatch ? Number.parseInt(stackMatch[2], 10) : undefined,
118
+ message: trimmed,
119
+ });
120
+ }
121
+ }
122
+ // Also try to extract the summary even on failure
123
+ const summaryMatch = output.match(/Tests\s+(?:(\d+)\s+passed\s*\|\s*)?(\d+)\s+failed/);
124
+ if (summaryMatch) {
125
+ const passed = summaryMatch[1] ?? "0";
126
+ const failed = summaryMatch[2];
127
+ warnings.push(`${passed} passed, ${failed} failed`);
85
128
  }
86
- if (trimmed.includes("FAIL") ||
87
- trimmed.includes("AssertionError") ||
88
- trimmed.includes("AssertionError") ||
89
- trimmed.includes("Expected") ||
90
- trimmed.includes("Received")) {
91
- errors.push({
92
- file: lastFailFile,
93
- line: stackMatch ? Number.parseInt(stackMatch[2], 10) : undefined,
94
- message: trimmed,
95
- });
129
+ if (errors.length === 0) {
130
+ errors.push({ message: "Test runner exited with non-zero status" });
131
+ }
132
+ // Enrich errors with remediation hints
133
+ for (const error of errors) {
134
+ error.remediation = buildTestRemediation(error);
96
135
  }
97
136
  }
98
- // Also try to extract the summary even on failure
99
- const summaryMatch = output.match(/Tests\s+(?:(\d+)\s+passed\s*\|\s*)?(\d+)\s+failed/);
100
- if (summaryMatch) {
101
- const passed = summaryMatch[1] ?? "0";
102
- const failed = summaryMatch[2];
103
- warnings.push(`${passed} passed, ${failed} failed`);
137
+ }
138
+ // -----------------------------------------------------------------------
139
+ // Enforcement check: Verify changed files have corresponding tests
140
+ // -----------------------------------------------------------------------
141
+ if (testingConfig?.enforce) {
142
+ const changedSourceFiles = getChangedSourceFiles(projectDir);
143
+ if (changedSourceFiles.length > 0) {
144
+ const untestedSet = new Set(analysis.coverage.untestedFiles);
145
+ for (const file of changedSourceFiles) {
146
+ const normalized = file.replace(/\\/g, "/");
147
+ if (untestedSet.has(normalized)) {
148
+ const error = {
149
+ file: normalized,
150
+ message: `Missing test file for changed source: ${normalized}`,
151
+ };
152
+ error.remediation = buildTestCoverageRemediation(error);
153
+ errors.push(error);
154
+ }
155
+ }
104
156
  }
105
- if (errors.length === 0) {
106
- errors.push({ message: "Test runner exited with non-zero status" });
157
+ }
158
+ // -----------------------------------------------------------------------
159
+ // Thin coverage advisory
160
+ // -----------------------------------------------------------------------
161
+ if (analysis.coverage.testFiles > 0 && analysis.coverage.ratio < 0.3) {
162
+ warnings.push(`Thin test coverage: ratio ${analysis.coverage.ratio} (${analysis.coverage.testFiles} test file${analysis.coverage.testFiles === 1 ? "" : "s"} for ${analysis.coverage.sourceFiles} source file${analysis.coverage.sourceFiles === 1 ? "" : "s"}). Consider adding tests for untested files.`);
163
+ }
164
+ const passed = testsRanSuccessfully && errors.length === 0;
165
+ return {
166
+ gate: "tests",
167
+ passed,
168
+ errors,
169
+ warnings,
170
+ duration_ms: Date.now() - start,
171
+ };
172
+ }
173
+ /**
174
+ * Get source files changed relative to HEAD~1 or the staging area.
175
+ * Returns paths relative to projectDir, normalized with forward slashes.
176
+ */
177
+ function getChangedSourceFiles(projectDir) {
178
+ const files = [];
179
+ // Try git diff against HEAD~1 first, fall back to cached diff
180
+ for (const cmd of [
181
+ "git diff --name-only HEAD~1",
182
+ "git diff --cached --name-only",
183
+ ]) {
184
+ try {
185
+ const output = execSync(cmd, {
186
+ cwd: projectDir,
187
+ stdio: "pipe",
188
+ timeout: 10_000,
189
+ }).toString().trim();
190
+ if (output) {
191
+ for (const line of output.split("\n")) {
192
+ const trimmed = line.trim();
193
+ if (!trimmed)
194
+ continue;
195
+ // Only include source files (not test files, not configs)
196
+ if (isSourceFilePath(trimmed)) {
197
+ files.push(trimmed.replace(/\\/g, "/"));
198
+ }
199
+ }
200
+ break; // Use the first successful command
201
+ }
107
202
  }
108
- // Enrich errors with remediation hints
109
- for (const error of errors) {
110
- error.remediation = buildTestRemediation(error);
203
+ catch {
204
+ // Command failed try the next one
111
205
  }
112
- return {
113
- gate: "tests",
114
- passed: false,
115
- errors,
116
- warnings,
117
- duration_ms: Date.now() - start,
118
- };
119
206
  }
207
+ return files;
208
+ }
209
+ /** Check if a path looks like a source file (TS/JS, not a test, not a declaration). */
210
+ function isSourceFilePath(filePath) {
211
+ if (!/\.(ts|tsx|js|jsx)$/.test(filePath))
212
+ return false;
213
+ if (/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filePath))
214
+ return false;
215
+ if (filePath.endsWith(".d.ts"))
216
+ return false;
217
+ return true;
120
218
  }
121
219
  //# sourceMappingURL=tests-gate.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tests-gate.js","sourceRoot":"","sources":["../../src/gates/tests-gate.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tests-gate.js","sourceRoot":"","sources":["../../src/gates/tests-gate.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,11 @@
1
+ import type { TestingConfig } from "../types.js";
1
2
  export interface SetupContext {
2
3
  projectName: string;
3
4
  techStack: string;
4
5
  description: string;
5
6
  gates: string[];
6
7
  date: string;
8
+ testing?: TestingConfig;
7
9
  }
8
10
  export declare function forgeConfigTemplate(ctx: SetupContext): string;
9
11
  export declare function claudeMdTemplate(ctx: SetupContext): string;
@@ -6,6 +6,9 @@ export function forgeConfigTemplate(ctx) {
6
6
  gates: ctx.gates,
7
7
  maxIterations: 5,
8
8
  };
9
+ if (ctx.testing) {
10
+ config.testing = ctx.testing;
11
+ }
9
12
  return JSON.stringify(config, null, 2) + "\n";
10
13
  }
11
14
  // ── Project CLAUDE.md ───────────────────────────────────────────────
@@ -1 +1 @@
1
- {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/setup/templates.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,4EAA4E;AAU5E,uEAAuE;AAEvE,MAAM,UAAU,mBAAmB,CAAC,GAAiB;IACnD,MAAM,MAAM,GAAG;QACb,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,aAAa,EAAE,CAAC;KACjB,CAAC;IACF,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;AAChD,CAAC;AAED,uEAAuE;AAEvE,MAAM,UAAU,gBAAgB,CAAC,GAAiB;IAChD,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAE9D,OAAO,KAAK,GAAG,CAAC,WAAW;;;EAG3B,GAAG,CAAC,WAAW;;YAEL,GAAG,CAAC,SAAS;;;;;;;mDAO0B,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBAqCtD,SAAS;;;;CAIxB,CAAC;AACF,CAAC;AAED,uEAAuE;AAEvE,MAAM,UAAU,eAAe,CAAC,GAAiB;IAC/C,OAAO,aAAa,GAAG,CAAC,WAAW;;;;;;;;sCAQC,GAAG,CAAC,IAAI;;;;;CAK7C,CAAC;AACF,CAAC;AAED,uEAAuE;AAEvE,MAAM,UAAU,iBAAiB,CAAC,GAAiB;IACjD,OAAO,eAAe,GAAG,CAAC,WAAW;;;;;;;;;;CAUtC,CAAC;AACF,CAAC;AAED,uEAAuE;AAEvE,MAAM,UAAU,iBAAiB,CAAC,GAAiB;IACjD,OAAO,uBAAuB,GAAG,CAAC,WAAW;;;;;;CAM9C,CAAC;AACF,CAAC;AAED,uEAAuE;AAEvE,MAAM,UAAU,sBAAsB;IACpC,OAAO;;;;;;;;;;;;;;CAcR,CAAC;AACF,CAAC;AAED,uEAAuE;AAEvE,MAAM,UAAU,mBAAmB;IACjC,OAAO;CACR,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/setup/templates.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,4EAA4E;AAa5E,uEAAuE;AAEvE,MAAM,UAAU,mBAAmB,CAAC,GAAiB;IACnD,MAAM,MAAM,GAA4B;QACtC,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,aAAa,EAAE,CAAC;KACjB,CAAC;IACF,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;QAChB,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;IAC/B,CAAC;IACD,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;AAChD,CAAC;AAED,uEAAuE;AAEvE,MAAM,UAAU,gBAAgB,CAAC,GAAiB;IAChD,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAE9D,OAAO,KAAK,GAAG,CAAC,WAAW;;;EAG3B,GAAG,CAAC,WAAW;;YAEL,GAAG,CAAC,SAAS;;;;;;;mDAO0B,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBAqCtD,SAAS;;;;CAIxB,CAAC;AACF,CAAC;AAED,uEAAuE;AAEvE,MAAM,UAAU,eAAe,CAAC,GAAiB;IAC/C,OAAO,aAAa,GAAG,CAAC,WAAW;;;;;;;;sCAQC,GAAG,CAAC,IAAI;;;;;CAK7C,CAAC;AACF,CAAC;AAED,uEAAuE;AAEvE,MAAM,UAAU,iBAAiB,CAAC,GAAiB;IACjD,OAAO,eAAe,GAAG,CAAC,WAAW;;;;;;;;;;CAUtC,CAAC;AACF,CAAC;AAED,uEAAuE;AAEvE,MAAM,UAAU,iBAAiB,CAAC,GAAiB;IACjD,OAAO,uBAAuB,GAAG,CAAC,WAAW;;;;;;CAM9C,CAAC;AACF,CAAC;AAED,uEAAuE;AAEvE,MAAM,UAAU,sBAAsB;IACpC,OAAO;;;;;;;;;;;;;;CAcR,CAAC;AACF,CAAC;AAED,uEAAuE;AAEvE,MAAM,UAAU,mBAAmB;IACjC,OAAO;CACR,CAAC;AACF,CAAC"}
@@ -0,0 +1,38 @@
1
+ import type { TestAnalysisReport } from "../gates/test-analysis.js";
2
+ import type { TestingConfig } from "../types.js";
3
+ import type { ScaffoldPlan } from "./test-scaffold.js";
4
+ export interface TestPlanningData {
5
+ /** The test analysis report from scanning the project */
6
+ report: TestAnalysisReport;
7
+ /** Human-readable summary of findings */
8
+ summary: string;
9
+ /** Per-category summaries for user confirmation prompts */
10
+ categorySummaries: CategorySummary[];
11
+ }
12
+ export interface CategorySummary {
13
+ name: string;
14
+ sourceCount: number;
15
+ testCount: number;
16
+ untestedCount: number;
17
+ description: string;
18
+ }
19
+ export interface TestPlannerResult {
20
+ /** The test analysis report from scanning the project */
21
+ report: TestAnalysisReport;
22
+ /** Categories the user confirmed for test scaffolding */
23
+ confirmedCategories: string[];
24
+ /** The chosen test runner */
25
+ runner: "vitest" | "jest";
26
+ /** The test directory */
27
+ testDir: string;
28
+ /** Whether structural tests are included */
29
+ structural: boolean;
30
+ /** The generated testing config to persist to .forge.json */
31
+ testingConfig: TestingConfig;
32
+ /** The scaffold plan (ready to execute) */
33
+ scaffoldPlan: ScaffoldPlan;
34
+ }
35
+ /** Analyze the project and prepare the test planning data. */
36
+ export declare function analyzeForTestPlanning(projectDir: string): Promise<TestPlanningData>;
37
+ /** Build the final plan + config after user confirms categories. */
38
+ export declare function buildTestPlan(report: TestAnalysisReport, confirmedCategories: string[], runner: "vitest" | "jest", testDir: string, structural: boolean, projectDir: string): Promise<TestPlannerResult>;
@@ -0,0 +1,91 @@
1
+ // ── Test Planner ────────────────────────────────────────────────────
2
+ // Interactive test planning module for /forge:setup integration.
3
+ // Analyzes project test coverage, then builds a scaffold plan + config
4
+ // from user-confirmed categories.
5
+ import { analyzeTestCoverage } from "../gates/test-analysis.js";
6
+ import { buildScaffoldPlan } from "./test-scaffold.js";
7
+ // ---------------------------------------------------------------------------
8
+ // Category Description Helpers
9
+ // ---------------------------------------------------------------------------
10
+ function describeCategoryName(name) {
11
+ switch (name) {
12
+ case "api-routes":
13
+ return "API routes and endpoint handlers";
14
+ case "components":
15
+ return "UI components (React/TSX)";
16
+ case "utils":
17
+ return "Utility functions and helpers";
18
+ case "middleware":
19
+ return "Middleware and interceptors";
20
+ case "models":
21
+ return "Data models, schemas, and entities";
22
+ case "other":
23
+ return "Other source files";
24
+ default:
25
+ return name;
26
+ }
27
+ }
28
+ // ---------------------------------------------------------------------------
29
+ // Analysis Phase
30
+ // ---------------------------------------------------------------------------
31
+ /** Analyze the project and prepare the test planning data. */
32
+ export async function analyzeForTestPlanning(projectDir) {
33
+ const report = await analyzeTestCoverage(projectDir);
34
+ const categorySummaries = report.categories.map((cat) => ({
35
+ name: cat.name,
36
+ sourceCount: cat.sourceFiles.length,
37
+ testCount: cat.testFiles.length,
38
+ untestedCount: cat.untestedFiles.length,
39
+ description: describeCategoryName(cat.name),
40
+ }));
41
+ const lines = [];
42
+ lines.push(`Framework: ${report.framework.appFramework}`);
43
+ lines.push(`Test runner: ${report.framework.testRunner}`);
44
+ lines.push(`Coverage: ${report.coverage.testFiles} test files / ${report.coverage.sourceFiles} source files (ratio: ${report.coverage.ratio})`);
45
+ if (report.framework.detectedPatterns.length > 0) {
46
+ lines.push(`Detected: ${report.framework.detectedPatterns.join(", ")}`);
47
+ }
48
+ if (categorySummaries.length > 0) {
49
+ lines.push("");
50
+ lines.push("Categories:");
51
+ for (const cat of categorySummaries) {
52
+ lines.push(` - ${cat.name}: ${cat.sourceCount} source, ${cat.testCount} tested, ${cat.untestedCount} untested`);
53
+ }
54
+ }
55
+ return {
56
+ report,
57
+ summary: lines.join("\n"),
58
+ categorySummaries,
59
+ };
60
+ }
61
+ // ---------------------------------------------------------------------------
62
+ // Plan Building Phase
63
+ // ---------------------------------------------------------------------------
64
+ /** Build the final plan + config after user confirms categories. */
65
+ export async function buildTestPlan(report, confirmedCategories, runner, testDir, structural, projectDir) {
66
+ const scaffoldPlan = await buildScaffoldPlan(report, {
67
+ projectDir,
68
+ testDir,
69
+ runner,
70
+ structural,
71
+ categories: confirmedCategories,
72
+ });
73
+ const testingConfig = {
74
+ enforce: true,
75
+ runner,
76
+ testDir,
77
+ sourceDir: "src",
78
+ structural,
79
+ categories: confirmedCategories,
80
+ };
81
+ return {
82
+ report,
83
+ confirmedCategories,
84
+ runner,
85
+ testDir,
86
+ structural,
87
+ testingConfig,
88
+ scaffoldPlan,
89
+ };
90
+ }
91
+ //# sourceMappingURL=test-planner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-planner.js","sourceRoot":"","sources":["../../src/setup/test-planner.ts"],"names":[],"mappings":"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"}
@@ -59,7 +59,7 @@ function buildPackageJsonUpdates(runner, report) {
59
59
  const devDependencies = {};
60
60
  if (runner === "vitest") {
61
61
  scripts["test"] = "vitest run";
62
- devDependencies["vitest"] = "^3.0.0";
62
+ devDependencies["vitest"] = "^4.0.0";
63
63
  }
64
64
  else {
65
65
  scripts["test"] = "jest";
@@ -89,6 +89,7 @@ export declare const MilestoneSchema: z.ZodObject<{
89
89
  verificationCommands: z.ZodArray<z.ZodString, "many">;
90
90
  maxContextWindowFit: z.ZodDefault<z.ZodBoolean>;
91
91
  dependsOn: z.ZodDefault<z.ZodArray<z.ZodNumber, "many">>;
92
+ testCriteria: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
92
93
  }, "strip", z.ZodTypeAny, {
93
94
  number: number;
94
95
  name: string;
@@ -105,6 +106,7 @@ export declare const MilestoneSchema: z.ZodObject<{
105
106
  verificationCommands: string[];
106
107
  maxContextWindowFit: boolean;
107
108
  dependsOn: number[];
109
+ testCriteria: string[];
108
110
  }, {
109
111
  number: number;
110
112
  name: string;
@@ -121,6 +123,7 @@ export declare const MilestoneSchema: z.ZodObject<{
121
123
  verificationCommands: string[];
122
124
  maxContextWindowFit?: boolean | undefined;
123
125
  dependsOn?: number[] | undefined;
126
+ testCriteria?: string[] | undefined;
124
127
  }>;
125
128
  export declare const PRDSchema: z.ZodObject<{
126
129
  project: z.ZodString;
@@ -166,14 +169,14 @@ export declare const PRDSchema: z.ZodObject<{
166
169
  dependencies: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
167
170
  existingCode: z.ZodOptional<z.ZodString>;
168
171
  }, "strip", z.ZodTypeAny, {
172
+ dependencies?: string[] | undefined;
169
173
  projectStructure?: string | undefined;
170
174
  keyTypes?: string | undefined;
171
- dependencies?: string[] | undefined;
172
175
  existingCode?: string | undefined;
173
176
  }, {
177
+ dependencies?: string[] | undefined;
174
178
  projectStructure?: string | undefined;
175
179
  keyTypes?: string | undefined;
176
- dependencies?: string[] | undefined;
177
180
  existingCode?: string | undefined;
178
181
  }>;
179
182
  milestones: z.ZodArray<z.ZodObject<{
@@ -214,6 +217,7 @@ export declare const PRDSchema: z.ZodObject<{
214
217
  verificationCommands: z.ZodArray<z.ZodString, "many">;
215
218
  maxContextWindowFit: z.ZodDefault<z.ZodBoolean>;
216
219
  dependsOn: z.ZodDefault<z.ZodArray<z.ZodNumber, "many">>;
220
+ testCriteria: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
217
221
  }, "strip", z.ZodTypeAny, {
218
222
  number: number;
219
223
  name: string;
@@ -230,6 +234,7 @@ export declare const PRDSchema: z.ZodObject<{
230
234
  verificationCommands: string[];
231
235
  maxContextWindowFit: boolean;
232
236
  dependsOn: number[];
237
+ testCriteria: string[];
233
238
  }, {
234
239
  number: number;
235
240
  name: string;
@@ -246,6 +251,7 @@ export declare const PRDSchema: z.ZodObject<{
246
251
  verificationCommands: string[];
247
252
  maxContextWindowFit?: boolean | undefined;
248
253
  dependsOn?: number[] | undefined;
254
+ testCriteria?: string[] | undefined;
249
255
  }>, "many">;
250
256
  verification: z.ZodObject<{
251
257
  perMilestone: z.ZodArray<z.ZodString, "many">;
@@ -277,6 +283,7 @@ export declare const PRDSchema: z.ZodObject<{
277
283
  verificationCommands: string[];
278
284
  maxContextWindowFit: boolean;
279
285
  dependsOn: number[];
286
+ testCriteria: string[];
280
287
  }[];
281
288
  assignedTo: string;
282
289
  created: string;
@@ -294,9 +301,9 @@ export declare const PRDSchema: z.ZodObject<{
294
301
  acceptanceCriteria: string[];
295
302
  }[];
296
303
  technicalDesign: {
304
+ dependencies?: string[] | undefined;
297
305
  projectStructure?: string | undefined;
298
306
  keyTypes?: string | undefined;
299
- dependencies?: string[] | undefined;
300
307
  existingCode?: string | undefined;
301
308
  };
302
309
  verification: {
@@ -324,6 +331,7 @@ export declare const PRDSchema: z.ZodObject<{
324
331
  verificationCommands: string[];
325
332
  maxContextWindowFit?: boolean | undefined;
326
333
  dependsOn?: number[] | undefined;
334
+ testCriteria?: string[] | undefined;
327
335
  }[];
328
336
  assignedTo: string;
329
337
  created: string;
@@ -341,9 +349,9 @@ export declare const PRDSchema: z.ZodObject<{
341
349
  acceptanceCriteria: string[];
342
350
  }[];
343
351
  technicalDesign: {
352
+ dependencies?: string[] | undefined;
344
353
  projectStructure?: string | undefined;
345
354
  keyTypes?: string | undefined;
346
- dependencies?: string[] | undefined;
347
355
  existingCode?: string | undefined;
348
356
  };
349
357
  verification: {
@@ -29,6 +29,7 @@ export const MilestoneSchema = z.object({
29
29
  verificationCommands: z.array(z.string()),
30
30
  maxContextWindowFit: z.boolean().default(true),
31
31
  dependsOn: z.array(z.number()).default([]),
32
+ testCriteria: z.array(z.string()).default([]),
32
33
  });
33
34
  export const PRDSchema = z.object({
34
35
  project: z.string(),
@@ -1 +1 @@
1
- {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/spec/templates.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/spec/templates.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -55,6 +55,15 @@ export interface PipelineResult {
55
55
  gates: GateResult[];
56
56
  report: string;
57
57
  }
58
+ /** Testing configuration from .forge.json */
59
+ export interface TestingConfig {
60
+ enforce: boolean;
61
+ runner: "vitest" | "jest" | "none";
62
+ testDir: string;
63
+ sourceDir: string;
64
+ structural: boolean;
65
+ categories: string[];
66
+ }
58
67
  /** Configuration from .forge.json */
59
68
  export interface ForgeConfig {
60
69
  gates: string[];
@@ -70,6 +79,7 @@ export interface ForgeConfig {
70
79
  review?: {
71
80
  blocking: boolean;
72
81
  };
82
+ testing?: TestingConfig;
73
83
  }
74
84
  /** Verification cache written to .forge/last-verify.json */
75
85
  export interface VerifyCache {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-cc",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "Pre-PR verification harness for Claude Code agents — gate runner + CLI + MCP server",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -86,6 +86,17 @@ Print the pre-flight summary:
86
86
  Ready to execute Milestone 4.
87
87
  ```
88
88
 
89
+ **Visual baseline (if visual gate enabled):**
90
+
91
+ If `.forge.json` includes `visual` in gates or specifies a `devServerUrl`, capture before screenshots:
92
+
93
+ 1. Start the dev server (from `.forge.json` devServerUrl or `npm run dev`)
94
+ 2. Run `npx forge verify --gate visual --before-only` (or manually call the screenshot capture)
95
+ 3. Screenshots save to `.forge/screenshots/before/`
96
+ 4. Stop the dev server
97
+
98
+ This establishes the visual baseline for regression detection. Skip if no visual gate is configured.
99
+
89
100
  ### Step 2.5 — Session Isolation (Automatic)
90
101
 
91
102
  The execution engine automatically creates a git worktree for isolated execution. This happens transparently — you don't need to manage it manually.
@@ -207,20 +218,31 @@ If verification **passes**: proceed to reviewer.
207
218
 
208
219
  #### 3f. Reviewer Consensus Protocol
209
220
 
221
+ **MANDATORY: Do NOT proceed to the next wave or Step 5 until the reviewer has completed its review. This is the real quality gate — not tests.**
222
+
210
223
  After mechanical gates pass, engage the reviewer agent:
211
224
 
212
225
  1. **Send diff to reviewer:** Use SendMessage to send the wave's git diff to the reviewer agent, along with:
213
- - The PRD milestone section (goals and acceptance criteria)
226
+ - The PRD milestone section including:
227
+ - The **Goal** line (what this milestone delivers)
228
+ - All `- [ ]` checklist items from the milestone (these are the acceptance criteria)
229
+ - The **User Stories** section from the PRD (for context on what users expect)
230
+ - The full git diff for the wave
214
231
  - CLAUDE.md rules and architecture decisions
215
232
  - The list of files changed and their ownership
233
+ - If the visual gate ran, include the screenshot file paths:
234
+ - Before: `.forge/screenshots/before/*.png` (if captured in Step 2)
235
+ - After: `.forge/screenshots/after/*.png`
236
+ - Tell the reviewer: "Use the Read tool to view these screenshots. Compare the rendered UI against the PRD's acceptance criteria and user stories. Check for: layout correctness, missing UI elements, visual regressions from the before state, responsive behavior across viewports (desktop 1280x800, tablet 768x1024, mobile 390x844)."
216
237
 
217
238
  2. **Reviewer analyzes diff:** The reviewer examines the changes for:
218
- - PRD alignment — does the code achieve what the milestone specifies?
219
- - Architecture adherence does the code follow CLAUDE.md patterns?
220
- - Logic errors not mechanical (tsc catches those), but semantic issues
221
- - Cross-agent integration do the pieces from different builders fit together?
239
+ - Does the code achieve the milestone's **Goal** line?
240
+ - Does it handle the acceptance criteria from the PRD? (Check each `- [ ]` item)
241
+ - Are there logic errors, missing error handling, or dead code paths?
242
+ - Would `npx forge verify` catch this, or is this a semantic issue only a reviewer would find?
243
+ - **If screenshots provided:** Does the rendered UI match the PRD's described behavior? Are there visual issues (broken layouts, missing elements, incorrect spacing) across any viewport?
222
244
 
223
- 3. **Reviewer sends findings:** The reviewer sends structured findings via SendMessage to the relevant builder(s):
245
+ 3. **Reviewer sends findings:** The reviewer sends structured findings to the orchestrator (you). **Print each finding to the terminal** so the user sees them:
224
246
  ```
225
247
  Finding: {description}
226
248
  Severity: error | warning
@@ -228,23 +250,12 @@ After mechanical gates pass, engage the reviewer agent:
228
250
  Line: {number}
229
251
  ```
230
252
 
231
- 4. **Builder responds:** The builder can:
232
- - **Agree** — finding is queued for fix agent
233
- - **Disagree: {reason}** — builder explains why the finding is incorrect
234
- - **Alternative: {proposal}** — builder proposes a different approach
235
-
236
- 5. **Round 2 (if disagreement):** The reviewer re-evaluates the builder's reasoning. Either:
237
- - Accepts the builder's position (finding dropped)
238
- - Maintains the finding (escalates to executive)
239
-
240
- 6. **Escalation (deadlock after 2 rounds):** The executive (you) reviews both positions and makes the final call.
241
-
242
- 7. **Fix agreed findings:** Spawn a fix agent to address all agreed-upon findings. The fix agent receives:
253
+ 4. **Fix findings:** Spawn a fix agent to address all findings. The fix agent receives:
243
254
  - The specific findings to fix
244
255
  - The files to modify
245
- - "Fix ONLY the agreed findings. Do not refactor or add features."
256
+ - "Fix ONLY the listed findings. Do not refactor or add features."
246
257
 
247
- 8. **Re-verify:** Restage files and re-run mechanical gates after fixes.
258
+ 5. **Re-verify:** Restage files and re-run mechanical gates after fixes.
248
259
 
249
260
  If no findings (or all findings resolved): print wave completion summary and proceed to the next wave.
250
261
 
@@ -254,6 +265,7 @@ If no findings (or all findings resolved): print wave completion summary and pro
254
265
  - agent-1: OK (created file1.ts, file2.ts)
255
266
  - agent-2: OK (modified file3.ts)
256
267
  - Mechanical verification: PASSED
268
+ - Visual gate: PASSED (3 viewports captured) | SKIPPED (not configured)
257
269
  - Reviewer: {N} findings, {M} resolved, 0 outstanding
258
270
 
259
271
  Proceeding to Wave {N+1}...
@@ -69,6 +69,66 @@ question: "Tech stack? (e.g., TypeScript, React, Node.js)"
69
69
  question: "One-line project description?"
70
70
  </AskUserQuestion>
71
71
 
72
+ ### Step 3.5 — Interactive Test Planning
73
+
74
+ **Skip this step if "tests" was NOT selected in Step 3.**
75
+
76
+ Run the test analysis engine on the project:
77
+
78
+ ```typescript
79
+ import { analyzeForTestPlanning } from 'forge-cc/src/test-scaffold/analyze';
80
+ const analysis = await analyzeForTestPlanning(projectDir);
81
+ ```
82
+
83
+ Present findings to the user:
84
+
85
+ ```
86
+ ## Test Analysis
87
+
88
+ **Framework detected:** {e.g., "Next.js App Router with Vitest"}
89
+ **Coverage summary:** Found {N} source files, {M} test files
90
+ ```
91
+
92
+ For each category with untested files, ask:
93
+
94
+ <AskUserQuestion>
95
+ question: "Scaffold tests for {category description}? ({N} untested files)"
96
+ options:
97
+ - "Yes — scaffold test stubs for these files"
98
+ - "No — skip this category"
99
+ </AskUserQuestion>
100
+
101
+ If the test runner was NOT already detected from `package.json`, ask:
102
+
103
+ <AskUserQuestion>
104
+ question: "Which test runner?"
105
+ options:
106
+ - "Vitest (Recommended)"
107
+ - "Jest"
108
+ </AskUserQuestion>
109
+
110
+ Ask about structural tests:
111
+
112
+ <AskUserQuestion>
113
+ question: "Include structural tests? (circular import detection, file naming conventions)"
114
+ options:
115
+ - "Yes (Recommended)"
116
+ - "No"
117
+ </AskUserQuestion>
118
+
119
+ Print a summary of what will be scaffolded:
120
+
121
+ ```
122
+ ## Test Scaffold Plan
123
+
124
+ - Config file: {vitest.config.ts / jest.config.ts}
125
+ - Package.json updates: test script, devDependencies
126
+ - Test stubs: {count} across {categories}
127
+ - Structural tests: {Yes / No}
128
+ ```
129
+
130
+ Store the scaffold plan for execution in Step 4. The testing config will be persisted to `.forge.json`.
131
+
72
132
  ### Step 4 — Create or Update Files
73
133
 
74
134
  Use the template functions from `forge-cc/src/setup/templates.ts` to generate file contents. The templates are:
@@ -86,6 +146,13 @@ Use the template functions from `forge-cc/src/setup/templates.ts` to generate fi
86
146
 
87
147
  Write the actual files using the Write tool. Do not just print them.
88
148
 
149
+ **If a test scaffold plan exists from Step 3.5**, also execute it now:
150
+ - Write the test runner config file (`vitest.config.ts` or `jest.config.ts`)
151
+ - Write test stub files for each selected category
152
+ - Write structural test files if selected
153
+ - Update `package.json` with test script and devDependencies
154
+ - Persist the `testing` section to `.forge.json`
155
+
89
156
  ### Step 5 — Patch Global Config
90
157
 
91
158
  Check if `~/.claude/CLAUDE.md` exists:
@@ -200,6 +267,10 @@ Print a summary of everything that was created or updated:
200
267
  - .gitignore (forge lines) ✓
201
268
  - .claude/settings.local.json ✓ (version-check hook)
202
269
 
270
+ ### Test Planning
271
+ {If tests gate enabled: "Test planning: {N} test stubs scaffolded, {runner} configured, structural tests {included/skipped}"}
272
+ {If tests gate not enabled: "Test planning: skipped (tests gate not enabled)"}
273
+
203
274
  ### Next Steps
204
275
  1. Review the generated `CLAUDE.md` and customize the Code Map section
205
276
  2. Run `npx forge verify` to test your gate configuration
@@ -193,6 +193,10 @@ The PRD should follow this structure:
193
193
 
194
194
  **Milestone sizing check:** Before finalizing, review each milestone against the sizing constraint. Every milestone MUST fit in one agent context window (~4 agents across 2-3 waves max). If any milestone exceeds this, split it into smaller milestones before writing the final PRD. Set `maxContextWindowFit: true` on all milestones — if you cannot make a milestone fit, flag it as `maxContextWindowFit: false` and warn the user.
195
195
 
196
+ **Test criteria guidance:** Test criteria are optional. Only include them when the milestone produces testable behavior. Verification commands (`npx tsc --noEmit`, `npx forge verify`) are always included automatically — test criteria are for additional functional checks beyond mechanical gates. When including test criteria, make them **functional**, not structural:
197
+ - Good: "CLI `npx forge verify` runs and exits 0", "tsc compiles with no errors", "the new gate produces structured JSON output"
198
+ - Bad: "All new source files must have corresponding test files", "Run npx forge verify --gate tests"
199
+
196
200
  Write the final PRD to `.planning/prds/{project-slug}.md`.
197
201
 
198
202
  After writing the PRD file, also: