@prowi/deskcheck 0.1.0 → 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 (176) hide show
  1. package/README.md +67 -12
  2. package/build/cli.js +110 -22
  3. package/build/cli.js.map +1 -1
  4. package/build/{core/config.d.ts → config/loader.d.ts} +9 -1
  5. package/build/config/loader.d.ts.map +1 -0
  6. package/build/{core/config.js → config/loader.js} +4 -2
  7. package/build/config/loader.js.map +1 -0
  8. package/build/config/types.d.ts +45 -0
  9. package/build/config/types.d.ts.map +1 -0
  10. package/build/config/types.js +2 -0
  11. package/build/config/types.js.map +1 -0
  12. package/build/mcp/tools.d.ts +1 -1
  13. package/build/mcp/tools.d.ts.map +1 -1
  14. package/build/mcp/tools.js +5 -5
  15. package/build/mcp/tools.js.map +1 -1
  16. package/build/mcp-server.js +1 -1
  17. package/build/mcp-server.js.map +1 -1
  18. package/build/{agents/executor-prompt.d.ts → prompts/ExecutorPrompt.d.ts} +6 -2
  19. package/build/prompts/ExecutorPrompt.d.ts.map +1 -0
  20. package/build/prompts/ExecutorPrompt.js +80 -0
  21. package/build/prompts/ExecutorPrompt.js.map +1 -0
  22. package/build/prompts/JudgePrompt.d.ts +16 -0
  23. package/build/prompts/JudgePrompt.d.ts.map +1 -0
  24. package/build/prompts/JudgePrompt.js +57 -0
  25. package/build/prompts/JudgePrompt.js.map +1 -0
  26. package/build/prompts/PlannerPrompt.d.ts +12 -0
  27. package/build/prompts/PlannerPrompt.d.ts.map +1 -0
  28. package/build/prompts/PlannerPrompt.js +34 -0
  29. package/build/prompts/PlannerPrompt.js.map +1 -0
  30. package/build/renderers/{json.d.ts → review/JsonRenderer.d.ts} +2 -2
  31. package/build/renderers/review/JsonRenderer.d.ts.map +1 -0
  32. package/build/renderers/{json.js → review/JsonRenderer.js} +1 -1
  33. package/build/renderers/review/JsonRenderer.js.map +1 -0
  34. package/build/renderers/{markdown.d.ts → review/MarkdownRenderer.d.ts} +2 -2
  35. package/build/renderers/review/MarkdownRenderer.d.ts.map +1 -0
  36. package/build/renderers/{markdown.js → review/MarkdownRenderer.js} +2 -2
  37. package/build/renderers/review/MarkdownRenderer.js.map +1 -0
  38. package/build/renderers/{terminal.d.ts → review/TerminalRenderer.d.ts} +2 -2
  39. package/build/renderers/review/TerminalRenderer.d.ts.map +1 -0
  40. package/build/renderers/{terminal.js → review/TerminalRenderer.js} +2 -2
  41. package/build/renderers/review/TerminalRenderer.js.map +1 -0
  42. package/build/renderers/{watch.d.ts → review/WatchRenderer.d.ts} +2 -2
  43. package/build/renderers/review/WatchRenderer.d.ts.map +1 -0
  44. package/build/renderers/{watch.js → review/WatchRenderer.js} +1 -1
  45. package/build/renderers/review/WatchRenderer.js.map +1 -0
  46. package/build/renderers/shared.d.ts +1 -1
  47. package/build/renderers/shared.d.ts.map +1 -1
  48. package/build/renderers/test/TerminalRenderer.d.ts +14 -0
  49. package/build/renderers/test/TerminalRenderer.d.ts.map +1 -0
  50. package/build/renderers/test/TerminalRenderer.js +233 -0
  51. package/build/renderers/test/TerminalRenderer.js.map +1 -0
  52. package/build/server/controllers/ReviewController.d.ts +23 -0
  53. package/build/server/controllers/ReviewController.d.ts.map +1 -0
  54. package/build/server/controllers/ReviewController.js +90 -0
  55. package/build/server/controllers/ReviewController.js.map +1 -0
  56. package/build/server/controllers/TestController.d.ts +2 -0
  57. package/build/server/controllers/TestController.d.ts.map +1 -0
  58. package/build/server/controllers/TestController.js +3 -0
  59. package/build/server/controllers/TestController.js.map +1 -0
  60. package/build/server/middleware/cors.d.ts +9 -0
  61. package/build/server/middleware/cors.d.ts.map +1 -0
  62. package/build/server/middleware/cors.js +18 -0
  63. package/build/server/middleware/cors.js.map +1 -0
  64. package/build/{serve.d.ts → server/server.d.ts} +2 -2
  65. package/build/server/server.d.ts.map +1 -0
  66. package/build/server/server.js +102 -0
  67. package/build/server/server.js.map +1 -0
  68. package/build/server/sse/FileWatcherSSE.d.ts +10 -0
  69. package/build/server/sse/FileWatcherSSE.d.ts.map +1 -0
  70. package/build/server/sse/FileWatcherSSE.js +89 -0
  71. package/build/server/sse/FileWatcherSSE.js.map +1 -0
  72. package/build/services/ExecutorService.d.ts +51 -0
  73. package/build/services/ExecutorService.d.ts.map +1 -0
  74. package/build/services/ExecutorService.js +133 -0
  75. package/build/services/ExecutorService.js.map +1 -0
  76. package/build/services/FindingsParserService.d.ts +10 -0
  77. package/build/services/FindingsParserService.d.ts.map +1 -0
  78. package/build/services/FindingsParserService.js +64 -0
  79. package/build/services/FindingsParserService.js.map +1 -0
  80. package/build/services/criteria/CriteriaService.d.ts +10 -0
  81. package/build/services/criteria/CriteriaService.d.ts.map +1 -0
  82. package/build/services/criteria/CriteriaService.js +10 -0
  83. package/build/services/criteria/CriteriaService.js.map +1 -0
  84. package/build/{core → services/criteria}/glob-matcher.d.ts +1 -1
  85. package/build/services/criteria/glob-matcher.d.ts.map +1 -0
  86. package/build/services/criteria/glob-matcher.js.map +1 -0
  87. package/build/{core → services/criteria}/module-parser.d.ts +13 -1
  88. package/build/services/criteria/module-parser.d.ts.map +1 -0
  89. package/build/{core → services/criteria}/module-parser.js +38 -0
  90. package/build/services/criteria/module-parser.js.map +1 -0
  91. package/build/{core/context-extractor.d.ts → services/review/ReviewContextExtractorService.d.ts} +2 -2
  92. package/build/services/review/ReviewContextExtractorService.d.ts.map +1 -0
  93. package/build/{core/context-extractor.js → services/review/ReviewContextExtractorService.js} +1 -1
  94. package/build/services/review/ReviewContextExtractorService.js.map +1 -0
  95. package/build/{agents/orchestrator.d.ts → services/review/ReviewOrchestratorService.d.ts} +5 -3
  96. package/build/services/review/ReviewOrchestratorService.d.ts.map +1 -0
  97. package/build/{agents/orchestrator.js → services/review/ReviewOrchestratorService.js} +15 -171
  98. package/build/services/review/ReviewOrchestratorService.js.map +1 -0
  99. package/build/{core/plan-builder.d.ts → services/review/ReviewPlanBuilderService.d.ts} +5 -4
  100. package/build/services/review/ReviewPlanBuilderService.d.ts.map +1 -0
  101. package/build/{core/plan-builder.js → services/review/ReviewPlanBuilderService.js} +2 -2
  102. package/build/services/review/ReviewPlanBuilderService.js.map +1 -0
  103. package/build/{agents/planner.d.ts → services/review/ReviewPlannerService.d.ts} +5 -4
  104. package/build/services/review/ReviewPlannerService.d.ts.map +1 -0
  105. package/build/{agents/planner.js → services/review/ReviewPlannerService.js} +13 -29
  106. package/build/services/review/ReviewPlannerService.js.map +1 -0
  107. package/build/{core/storage.d.ts → services/review/ReviewStorageService.d.ts} +3 -3
  108. package/build/services/review/ReviewStorageService.d.ts.map +1 -0
  109. package/build/{core/storage.js → services/review/ReviewStorageService.js} +6 -6
  110. package/build/services/review/ReviewStorageService.js.map +1 -0
  111. package/build/services/testing/JudgeService.d.ts +30 -0
  112. package/build/services/testing/JudgeService.d.ts.map +1 -0
  113. package/build/services/testing/JudgeService.js +95 -0
  114. package/build/services/testing/JudgeService.js.map +1 -0
  115. package/build/services/testing/TestDiscoveryService.d.ts +16 -0
  116. package/build/services/testing/TestDiscoveryService.d.ts.map +1 -0
  117. package/build/services/testing/TestDiscoveryService.js +66 -0
  118. package/build/services/testing/TestDiscoveryService.js.map +1 -0
  119. package/build/services/testing/TestRunnerService.d.ts +35 -0
  120. package/build/services/testing/TestRunnerService.d.ts.map +1 -0
  121. package/build/services/testing/TestRunnerService.js +140 -0
  122. package/build/services/testing/TestRunnerService.js.map +1 -0
  123. package/build/services/testing/TestScorerService.d.ts +9 -0
  124. package/build/services/testing/TestScorerService.d.ts.map +1 -0
  125. package/build/services/testing/TestScorerService.js +35 -0
  126. package/build/services/testing/TestScorerService.js.map +1 -0
  127. package/build/services/testing/TestStorageService.d.ts +39 -0
  128. package/build/services/testing/TestStorageService.d.ts.map +1 -0
  129. package/build/services/testing/TestStorageService.js +144 -0
  130. package/build/services/testing/TestStorageService.js.map +1 -0
  131. package/build/types/criteria.d.ts +24 -0
  132. package/build/types/criteria.d.ts.map +1 -0
  133. package/build/{core/types.js → types/criteria.js} +2 -2
  134. package/build/types/criteria.js.map +1 -0
  135. package/build/{core/types.d.ts → types/review.d.ts} +2 -64
  136. package/build/types/review.d.ts.map +1 -0
  137. package/build/types/review.js +2 -0
  138. package/build/types/review.js.map +1 -0
  139. package/build/types/testing.d.ts +109 -0
  140. package/build/types/testing.d.ts.map +1 -0
  141. package/build/types/testing.js +2 -0
  142. package/build/types/testing.js.map +1 -0
  143. package/package.json +1 -1
  144. package/build/agents/executor-prompt.d.ts.map +0 -1
  145. package/build/agents/executor-prompt.js +0 -65
  146. package/build/agents/executor-prompt.js.map +0 -1
  147. package/build/agents/orchestrator.d.ts.map +0 -1
  148. package/build/agents/orchestrator.js.map +0 -1
  149. package/build/agents/planner.d.ts.map +0 -1
  150. package/build/agents/planner.js.map +0 -1
  151. package/build/core/config.d.ts.map +0 -1
  152. package/build/core/config.js.map +0 -1
  153. package/build/core/context-extractor.d.ts.map +0 -1
  154. package/build/core/context-extractor.js.map +0 -1
  155. package/build/core/glob-matcher.d.ts.map +0 -1
  156. package/build/core/glob-matcher.js.map +0 -1
  157. package/build/core/module-parser.d.ts.map +0 -1
  158. package/build/core/module-parser.js.map +0 -1
  159. package/build/core/plan-builder.d.ts.map +0 -1
  160. package/build/core/plan-builder.js.map +0 -1
  161. package/build/core/storage.d.ts.map +0 -1
  162. package/build/core/storage.js.map +0 -1
  163. package/build/core/types.d.ts.map +0 -1
  164. package/build/core/types.js.map +0 -1
  165. package/build/renderers/json.d.ts.map +0 -1
  166. package/build/renderers/json.js.map +0 -1
  167. package/build/renderers/markdown.d.ts.map +0 -1
  168. package/build/renderers/markdown.js.map +0 -1
  169. package/build/renderers/terminal.d.ts.map +0 -1
  170. package/build/renderers/terminal.js.map +0 -1
  171. package/build/renderers/watch.d.ts.map +0 -1
  172. package/build/renderers/watch.js.map +0 -1
  173. package/build/serve.d.ts.map +0 -1
  174. package/build/serve.js +0 -249
  175. package/build/serve.js.map +0 -1
  176. /package/build/{core → services/criteria}/glob-matcher.js +0 -0
@@ -0,0 +1,95 @@
1
+ import { ExecutorService } from "../ExecutorService.js";
2
+ import { buildJudgePrompt } from "../../prompts/JudgePrompt.js";
3
+ // =============================================================================
4
+ // JudgeService
5
+ // =============================================================================
6
+ /**
7
+ * Spawns a judge agent to evaluate executor findings against expected results.
8
+ *
9
+ * The judge is a pure reasoning agent (no tools, single turn) that compares
10
+ * findings to expectations and produces structured verdicts. Model is
11
+ * configurable via config.agents.judge.model (default: opus).
12
+ */
13
+ export class JudgeService {
14
+ executorService;
15
+ judgeModel;
16
+ constructor(config, projectRoot) {
17
+ this.executorService = new ExecutorService(config, projectRoot);
18
+ this.judgeModel = config.agents.judge.model ?? "opus";
19
+ }
20
+ /**
21
+ * Evaluate executor findings against expected results for a criterion.
22
+ *
23
+ * @param criterion - The criterion that was tested.
24
+ * @param expectedContent - Content of expected.md describing what should be found.
25
+ * @param findings - Findings produced by the executor agent.
26
+ * @param fixtureContent - Content of the fixture file that was reviewed.
27
+ * @returns Judge result with finding and expectation reviews, plus usage data.
28
+ */
29
+ async evaluate(criterion, expectedContent, findings, fixtureContent) {
30
+ const prompt = buildJudgePrompt(criterion.prompt, expectedContent, findings, fixtureContent);
31
+ // Judge uses configured model (default opus), no tools, single turn
32
+ const executorResult = await this.executorService.execute(prompt, this.judgeModel, {
33
+ tools: [],
34
+ maxTurns: 1,
35
+ });
36
+ const result = parseJudgeOutput(executorResult.resultText);
37
+ return { result, usage: executorResult.usage };
38
+ }
39
+ }
40
+ // =============================================================================
41
+ // Parsing
42
+ // =============================================================================
43
+ /**
44
+ * Parse the judge agent's output into a typed JudgeResult.
45
+ *
46
+ * Looks for a JSON object (not array) in the output, strips markdown fences,
47
+ * and validates the structure. Returns empty results rather than crashing on
48
+ * malformed output.
49
+ */
50
+ function parseJudgeOutput(output) {
51
+ let jsonText = output.trim();
52
+ // Strip markdown code fences if present
53
+ const fencedMatch = jsonText.match(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/);
54
+ if (fencedMatch) {
55
+ jsonText = fencedMatch[1].trim();
56
+ }
57
+ // Find the JSON object boundaries
58
+ const startIdx = jsonText.indexOf("{");
59
+ const endIdx = jsonText.lastIndexOf("}");
60
+ if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) {
61
+ console.error(`[deskcheck] Warning: judge output did not contain a JSON object. Output: ${output.slice(0, 200)}`);
62
+ return emptyJudgeResult();
63
+ }
64
+ jsonText = jsonText.slice(startIdx, endIdx + 1);
65
+ let parsed;
66
+ try {
67
+ parsed = JSON.parse(jsonText);
68
+ }
69
+ catch (error) {
70
+ console.error(`[deskcheck] Warning: failed to parse judge JSON output: ${error instanceof Error ? error.message : String(error)}. Output: ${jsonText.slice(0, 200)}`);
71
+ return emptyJudgeResult();
72
+ }
73
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
74
+ console.error(`[deskcheck] Warning: judge output parsed but was not an object. Type: ${typeof parsed}`);
75
+ return emptyJudgeResult();
76
+ }
77
+ const record = parsed;
78
+ // Validate structure
79
+ if (!Array.isArray(record.findings_review) || !Array.isArray(record.expectations_review)) {
80
+ console.error(`[deskcheck] Warning: judge output missing findings_review or expectations_review arrays.`);
81
+ return emptyJudgeResult();
82
+ }
83
+ return {
84
+ findings_review: record.findings_review,
85
+ expectations_review: record.expectations_review,
86
+ };
87
+ }
88
+ /** Return an empty judge result for error cases. */
89
+ function emptyJudgeResult() {
90
+ return {
91
+ findings_review: [],
92
+ expectations_review: [],
93
+ };
94
+ }
95
+ //# sourceMappingURL=JudgeService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JudgeService.js","sourceRoot":"","sources":["../../../src/services/testing/JudgeService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { TestCase } from "../../types/testing.js";
2
+ /**
3
+ * Discover test cases by scanning the tests directory.
4
+ *
5
+ * Test cases mirror the criteria directory structure:
6
+ * tests/{criterionId}/{testName}/
7
+ * - One fixture file (any extension except .md)
8
+ * - One expected.md describing expected findings
9
+ *
10
+ * @param testsDir - Path to the tests directory.
11
+ * @param criteriaDir - Path to the criteria directory.
12
+ * @param criterionFilter - Optional list of criterion name patterns to include.
13
+ * @returns Sorted array of discovered test cases.
14
+ */
15
+ export declare function discoverTests(testsDir: string, criteriaDir: string, criterionFilter?: string[]): TestCase[];
16
+ //# sourceMappingURL=TestDiscoveryService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestDiscoveryService.d.ts","sourceRoot":"","sources":["../../../src/services/testing/TestDiscoveryService.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAGvD;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,eAAe,CAAC,EAAE,MAAM,EAAE,GACzB,QAAQ,EAAE,CA4DZ"}
@@ -0,0 +1,66 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { discoverCriteria, filterCriteria } from "../criteria/CriteriaService.js";
4
+ /**
5
+ * Discover test cases by scanning the tests directory.
6
+ *
7
+ * Test cases mirror the criteria directory structure:
8
+ * tests/{criterionId}/{testName}/
9
+ * - One fixture file (any extension except .md)
10
+ * - One expected.md describing expected findings
11
+ *
12
+ * @param testsDir - Path to the tests directory.
13
+ * @param criteriaDir - Path to the criteria directory.
14
+ * @param criterionFilter - Optional list of criterion name patterns to include.
15
+ * @returns Sorted array of discovered test cases.
16
+ */
17
+ export function discoverTests(testsDir, criteriaDir, criterionFilter) {
18
+ const absoluteTestsDir = path.resolve(testsDir);
19
+ const absoluteCriteriaDir = path.resolve(criteriaDir);
20
+ // Discover all criteria, optionally filtered
21
+ let criteria = discoverCriteria(absoluteCriteriaDir);
22
+ if (criterionFilter && criterionFilter.length > 0) {
23
+ criteria = filterCriteria(criteria, criterionFilter);
24
+ }
25
+ const testCases = [];
26
+ for (const criterion of criteria) {
27
+ // Look for matching test directory: testsDir/{criterion.id}/
28
+ const criterionTestDir = path.join(absoluteTestsDir, criterion.id);
29
+ if (!fs.existsSync(criterionTestDir) || !fs.statSync(criterionTestDir).isDirectory()) {
30
+ continue;
31
+ }
32
+ // Each subdirectory is a test case
33
+ const entries = fs.readdirSync(criterionTestDir, { withFileTypes: true });
34
+ const testDirs = entries.filter((entry) => entry.isDirectory()).sort((a, b) => a.name.localeCompare(b.name));
35
+ for (const testDir of testDirs) {
36
+ const testPath = path.join(criterionTestDir, testDir.name);
37
+ const testFiles = fs.readdirSync(testPath);
38
+ // Find the fixture file (first non-.md file)
39
+ const fixtureFileName = testFiles
40
+ .filter((f) => !f.endsWith(".md"))
41
+ .sort()[0];
42
+ // Check for expected.md
43
+ const hasExpectedMd = testFiles.includes("expected.md");
44
+ if (!fixtureFileName || !hasExpectedMd) {
45
+ console.error(`[deskcheck] Warning: skipping test "${testDir.name}" for criterion "${criterion.id}": ` +
46
+ `missing ${!fixtureFileName ? "fixture file" : "expected.md"}`);
47
+ continue;
48
+ }
49
+ testCases.push({
50
+ criterionId: criterion.id,
51
+ name: testDir.name,
52
+ criterionFile: criterion.file,
53
+ fixtureFile: path.join(testPath, fixtureFileName),
54
+ expectedFile: path.join(testPath, "expected.md"),
55
+ });
56
+ }
57
+ }
58
+ // Sort by criterionId, then by name for deterministic ordering
59
+ return testCases.sort((a, b) => {
60
+ const idCompare = a.criterionId.localeCompare(b.criterionId);
61
+ if (idCompare !== 0)
62
+ return idCompare;
63
+ return a.name.localeCompare(b.name);
64
+ });
65
+ }
66
+ //# sourceMappingURL=TestDiscoveryService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestDiscoveryService.js","sourceRoot":"","sources":["../../../src/services/testing/TestDiscoveryService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import type { ReviewConfig } from "../../config/types.js";
2
+ import type { TestCase, TestCaseResult, TestRun } from "../../types/testing.js";
3
+ /** Options for controlling test run behavior. */
4
+ export interface TestRunOptions {
5
+ /** Called after each test case completes (or errors), for live progress reporting. */
6
+ onTestComplete?: (criterionId: string, testName: string, result: TestCaseResult) => void;
7
+ }
8
+ /**
9
+ * Orchestrates the full test pipeline: executor -> judge -> scorer -> storage.
10
+ *
11
+ * Runs test cases sequentially (no concurrency). For each test case:
12
+ * 1. Execute the criterion against the fixture file
13
+ * 2. Judge the findings against expected results
14
+ * 3. Score the judge's verdicts
15
+ * 4. Persist everything to storage
16
+ */
17
+ export declare class TestRunnerService {
18
+ private readonly config;
19
+ private readonly projectRoot;
20
+ constructor(config: ReviewConfig, projectRoot: string);
21
+ /**
22
+ * Run all test cases and return the completed test run.
23
+ *
24
+ * @param testCases - Discovered test cases to execute.
25
+ * @param storageDir - Directory where test run results are persisted.
26
+ * @param options - Optional callbacks for progress reporting.
27
+ * @returns The final TestRun with all results.
28
+ */
29
+ run(testCases: TestCase[], storageDir: string, options?: TestRunOptions): Promise<TestRun>;
30
+ /**
31
+ * Execute a single test case through the full pipeline.
32
+ */
33
+ private executeTestCase;
34
+ }
35
+ //# sourceMappingURL=TestRunnerService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestRunnerService.d.ts","sourceRoot":"","sources":["../../../src/services/testing/TestRunnerService.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAchF,iDAAiD;AACjD,MAAM,WAAW,cAAc;IAC7B,sFAAsF;IACtF,cAAc,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;CAC1F;AAMD;;;;;;;;GAQG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;gBAEzB,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM;IAKrD;;;;;;;OAOG;IACG,GAAG,CACP,SAAS,EAAE,QAAQ,EAAE,EACrB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,OAAO,CAAC;IAyDnB;;OAEG;YACW,eAAe;CA6E9B"}
@@ -0,0 +1,140 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { ExecutorService } from "../ExecutorService.js";
4
+ import { buildExecutorPrompt } from "../../prompts/ExecutorPrompt.js";
5
+ import { parseFindings } from "../FindingsParserService.js";
6
+ import { parseCriterion } from "../criteria/CriteriaService.js";
7
+ import { TestStorageService } from "./TestStorageService.js";
8
+ import { JudgeService } from "./JudgeService.js";
9
+ import { calculateScores } from "./TestScorerService.js";
10
+ // =============================================================================
11
+ // TestRunnerService
12
+ // =============================================================================
13
+ /**
14
+ * Orchestrates the full test pipeline: executor -> judge -> scorer -> storage.
15
+ *
16
+ * Runs test cases sequentially (no concurrency). For each test case:
17
+ * 1. Execute the criterion against the fixture file
18
+ * 2. Judge the findings against expected results
19
+ * 3. Score the judge's verdicts
20
+ * 4. Persist everything to storage
21
+ */
22
+ export class TestRunnerService {
23
+ config;
24
+ projectRoot;
25
+ constructor(config, projectRoot) {
26
+ this.config = config;
27
+ this.projectRoot = projectRoot;
28
+ }
29
+ /**
30
+ * Run all test cases and return the completed test run.
31
+ *
32
+ * @param testCases - Discovered test cases to execute.
33
+ * @param storageDir - Directory where test run results are persisted.
34
+ * @param options - Optional callbacks for progress reporting.
35
+ * @returns The final TestRun with all results.
36
+ */
37
+ async run(testCases, storageDir, options) {
38
+ const storage = new TestStorageService(storageDir);
39
+ const executorService = new ExecutorService(this.config, this.projectRoot);
40
+ const judgeService = new JudgeService(this.config, this.projectRoot);
41
+ // Initialize the run with all tests as "pending"
42
+ const run = storage.createRun(testCases);
43
+ const runId = run.run_id;
44
+ // Track which suites have been started
45
+ const startedSuites = new Set();
46
+ for (const testCase of testCases) {
47
+ // Mark suite as running if not yet started
48
+ if (!startedSuites.has(testCase.criterionId)) {
49
+ storage.updateSuiteStatus(runId, testCase.criterionId, "running");
50
+ startedSuites.add(testCase.criterionId);
51
+ }
52
+ try {
53
+ await this.executeTestCase(testCase, runId, storage, executorService, judgeService);
54
+ }
55
+ catch (error) {
56
+ // Catch unexpected errors and record them
57
+ const errorMessage = error instanceof Error ? error.message : String(error);
58
+ storage.updateTestCase(runId, testCase.criterionId, testCase.name, {
59
+ status: "error",
60
+ error: errorMessage,
61
+ });
62
+ }
63
+ // Notify caller of test completion (whether success or error)
64
+ if (options?.onTestComplete) {
65
+ const currentRun = storage.getRun(runId);
66
+ const result = currentRun.suites[testCase.criterionId]?.tests[testCase.name];
67
+ if (result) {
68
+ options.onTestComplete(testCase.criterionId, testCase.name, result);
69
+ }
70
+ }
71
+ }
72
+ // Update suite statuses to "complete"
73
+ for (const criterionId of startedSuites) {
74
+ storage.updateSuiteStatus(runId, criterionId, "complete");
75
+ }
76
+ // Complete the run
77
+ storage.completeRun(runId);
78
+ return storage.getRun(runId);
79
+ }
80
+ /**
81
+ * Execute a single test case through the full pipeline.
82
+ */
83
+ async executeTestCase(testCase, runId, storage, executorService, judgeService) {
84
+ // Step 1: Update status to "executing"
85
+ storage.updateTestCase(runId, testCase.criterionId, testCase.name, {
86
+ status: "executing",
87
+ });
88
+ // Step 2: Read the fixture file
89
+ const fixtureContent = fs.readFileSync(testCase.fixtureFile, "utf-8");
90
+ // Step 3: Parse the criterion
91
+ // criterionFile is relative to the parent of the criteria dir (e.g. "criteria/backend/controller-conventions.md")
92
+ // The criteria dir config value is e.g. "deskcheck/criteria", so the parent is "deskcheck/"
93
+ // parseCriterion needs the absolute file path and the absolute criteria dir as base
94
+ const criteriaDir = path.resolve(this.projectRoot, this.config.modules_dir);
95
+ const absoluteCriterionFile = path.resolve(path.dirname(criteriaDir), testCase.criterionFile);
96
+ const criterion = parseCriterion(absoluteCriterionFile, criteriaDir);
97
+ // Step 4: Build a synthetic ReviewTask for the executor
98
+ const task = {
99
+ task_id: `test-${testCase.criterionId}-${testCase.name}`,
100
+ review_id: testCase.criterionId,
101
+ review_file: testCase.criterionFile,
102
+ files: [testCase.fixtureFile],
103
+ hint: null,
104
+ model: criterion.model,
105
+ status: "pending",
106
+ created_at: new Date().toISOString(),
107
+ started_at: null,
108
+ completed_at: null,
109
+ context_type: "file",
110
+ context: fixtureContent,
111
+ symbol: null,
112
+ prompt: criterion.prompt,
113
+ };
114
+ // Step 5: Build executor prompt and execute
115
+ const prompt = buildExecutorPrompt(task);
116
+ const executorResult = await executorService.execute(prompt, criterion.model);
117
+ // Step 6: Parse findings
118
+ const findings = parseFindings(executorResult.resultText);
119
+ // Step 7: Update storage with findings and executor usage, move to "judging"
120
+ storage.updateTestCase(runId, testCase.criterionId, testCase.name, {
121
+ status: "judging",
122
+ findings,
123
+ executor_usage: executorResult.usage,
124
+ });
125
+ // Step 8: Read expected.md
126
+ const expectedContent = fs.readFileSync(testCase.expectedFile, "utf-8");
127
+ // Step 9: Judge findings against expectations
128
+ const judgeOutput = await judgeService.evaluate(criterion, expectedContent, findings, fixtureContent);
129
+ // Step 10: Score the judge's verdicts
130
+ const scores = calculateScores(judgeOutput.result);
131
+ // Step 11: Update storage with judge result, scores, and judge usage
132
+ storage.updateTestCase(runId, testCase.criterionId, testCase.name, {
133
+ status: "complete",
134
+ judge: judgeOutput.result,
135
+ scores,
136
+ judge_usage: judgeOutput.usage,
137
+ });
138
+ }
139
+ }
140
+ //# sourceMappingURL=TestRunnerService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestRunnerService.js","sourceRoot":"","sources":["../../../src/services/testing/TestRunnerService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { JudgeResult, TestScores } from "../../types/testing.js";
2
+ /**
3
+ * Calculate test scores from judge verdicts.
4
+ *
5
+ * Pure math — no LLM, no I/O. Computes recall, precision, and scope
6
+ * compliance from the judge's finding and expectation reviews.
7
+ */
8
+ export declare function calculateScores(judgeResult: JudgeResult): TestScores;
9
+ //# sourceMappingURL=TestScorerService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestScorerService.d.ts","sourceRoot":"","sources":["../../../src/services/testing/TestScorerService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEtE;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,WAAW,GAAG,UAAU,CA+BpE"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Calculate test scores from judge verdicts.
3
+ *
4
+ * Pure math — no LLM, no I/O. Computes recall, precision, and scope
5
+ * compliance from the judge's finding and expectation reviews.
6
+ */
7
+ export function calculateScores(judgeResult) {
8
+ const { findings_review, expectations_review } = judgeResult;
9
+ // Finding counts by verdict
10
+ const total_findings = findings_review.length;
11
+ const correct = findings_review.filter((f) => f.verdict === "correct").length;
12
+ const out_of_scope = findings_review.filter((f) => f.verdict === "out_of_scope").length;
13
+ const incorrect_severity = findings_review.filter((f) => f.verdict === "incorrect_severity").length;
14
+ // Expectation counts by verdict
15
+ const total_expectations = expectations_review.length;
16
+ const expectations_found = expectations_review.filter((e) => e.verdict === "found").length;
17
+ const expectations_missed = expectations_review.filter((e) => e.verdict === "missed").length;
18
+ // Derived ratios (default to 1.0 when denominator is zero)
19
+ const recall = total_expectations > 0 ? expectations_found / total_expectations : 1.0;
20
+ const precision = total_findings > 0 ? correct / total_findings : 1.0;
21
+ const scope_compliance = total_findings > 0 ? (total_findings - out_of_scope) / total_findings : 1.0;
22
+ return {
23
+ total_findings,
24
+ correct,
25
+ out_of_scope,
26
+ incorrect_severity,
27
+ total_expectations,
28
+ expectations_found,
29
+ expectations_missed,
30
+ recall,
31
+ precision,
32
+ scope_compliance,
33
+ };
34
+ }
35
+ //# sourceMappingURL=TestScorerService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestScorerService.js","sourceRoot":"","sources":["../../../src/services/testing/TestScorerService.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,WAAwB;IACtD,MAAM,EAAE,eAAe,EAAE,mBAAmB,EAAE,GAAG,WAAW,CAAC;IAE7D,4BAA4B;IAC5B,MAAM,cAAc,GAAG,eAAe,CAAC,MAAM,CAAC;IAC9C,MAAM,OAAO,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,MAAM,CAAC;IAC9E,MAAM,YAAY,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,cAAc,CAAC,CAAC,MAAM,CAAC;IACxF,MAAM,kBAAkB,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,oBAAoB,CAAC,CAAC,MAAM,CAAC;IAEpG,gCAAgC;IAChC,MAAM,kBAAkB,GAAG,mBAAmB,CAAC,MAAM,CAAC;IACtD,MAAM,kBAAkB,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC,MAAM,CAAC;IAC3F,MAAM,mBAAmB,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,MAAM,CAAC;IAE7F,2DAA2D;IAC3D,MAAM,MAAM,GAAG,kBAAkB,GAAG,CAAC,CAAC,CAAC,CAAC,kBAAkB,GAAG,kBAAkB,CAAC,CAAC,CAAC,GAAG,CAAC;IACtF,MAAM,SAAS,GAAG,cAAc,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC;IACtE,MAAM,gBAAgB,GAAG,cAAc,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,GAAG,YAAY,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC;IAErG,OAAO;QACL,cAAc;QACd,OAAO;QACP,YAAY;QACZ,kBAAkB;QAClB,kBAAkB;QAClB,kBAAkB;QAClB,mBAAmB;QACnB,MAAM;QACN,SAAS;QACT,gBAAgB;KACjB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,39 @@
1
+ import type { TestCase, TestCaseResult, TestRun, TestSuiteStatus } from "../../types/testing.js";
2
+ /**
3
+ * Persists test runs to .deskcheck/test-runs/ using atomic writes.
4
+ *
5
+ * Each test run lives in a timestamped directory containing a single
6
+ * results.json file. Uses write-to-tmp + rename for atomic updates.
7
+ * No file locking needed since test runs execute sequentially.
8
+ */
9
+ export declare class TestStorageService {
10
+ private readonly storageDir;
11
+ constructor(storageDir: string);
12
+ /**
13
+ * Create a new test run with all test cases initialized as "pending".
14
+ *
15
+ * Creates a timestamped directory and writes an initial results.json.
16
+ */
17
+ createRun(testCases: TestCase[]): TestRun;
18
+ /** Read the results.json for a given run ID. */
19
+ getRun(runId: string): TestRun;
20
+ /**
21
+ * Return the most recent run directory name, or null if no runs exist.
22
+ */
23
+ getLatestRunId(): string | null;
24
+ /**
25
+ * Update a specific test case in results.json.
26
+ *
27
+ * Reads the current run, applies the partial update to the specified
28
+ * test case, and writes back atomically.
29
+ */
30
+ updateTestCase(runId: string, criterionId: string, testName: string, update: Partial<TestCaseResult>): void;
31
+ /** Update a suite's status. */
32
+ updateSuiteStatus(runId: string, criterionId: string, status: TestSuiteStatus): void;
33
+ /** Mark the run as complete with a completion timestamp. */
34
+ completeRun(runId: string): void;
35
+ private resultsPath;
36
+ /** Atomic write: write to .tmp then rename. */
37
+ private writeRun;
38
+ }
39
+ //# sourceMappingURL=TestStorageService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestStorageService.d.ts","sourceRoot":"","sources":["../../../src/services/testing/TestStorageService.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,QAAQ,EACR,cAAc,EACd,OAAO,EACP,eAAe,EAChB,MAAM,wBAAwB,CAAC;AAmBhC;;;;;;GAMG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,UAAU,EAAE,MAAM;IAI9B;;;;OAIG;IACH,SAAS,CAAC,SAAS,EAAE,QAAQ,EAAE,GAAG,OAAO;IA8CzC,gDAAgD;IAChD,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAM9B;;OAEG;IACH,cAAc,IAAI,MAAM,GAAG,IAAI;IAsB/B;;;;;OAKG;IACH,cAAc,CACZ,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,OAAO,CAAC,cAAc,CAAC,GAC9B,IAAI;IAiBP,+BAA+B;IAC/B,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI;IAYpF,4DAA4D;IAC5D,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAWhC,OAAO,CAAC,WAAW;IAInB,+CAA+C;IAC/C,OAAO,CAAC,QAAQ;CAMjB"}
@@ -0,0 +1,144 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ // =============================================================================
4
+ // Helpers
5
+ // =============================================================================
6
+ /** Format a Date as YYYY-MM-DD_HHmmss for use as a run ID / directory name. */
7
+ function formatTimestamp(date) {
8
+ const pad2 = (n) => String(n).padStart(2, "0");
9
+ return (`${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}` +
10
+ `_${pad2(date.getHours())}${pad2(date.getMinutes())}${pad2(date.getSeconds())}`);
11
+ }
12
+ // =============================================================================
13
+ // TestStorageService
14
+ // =============================================================================
15
+ /**
16
+ * Persists test runs to .deskcheck/test-runs/ using atomic writes.
17
+ *
18
+ * Each test run lives in a timestamped directory containing a single
19
+ * results.json file. Uses write-to-tmp + rename for atomic updates.
20
+ * No file locking needed since test runs execute sequentially.
21
+ */
22
+ export class TestStorageService {
23
+ storageDir;
24
+ constructor(storageDir) {
25
+ this.storageDir = storageDir;
26
+ }
27
+ /**
28
+ * Create a new test run with all test cases initialized as "pending".
29
+ *
30
+ * Creates a timestamped directory and writes an initial results.json.
31
+ */
32
+ createRun(testCases) {
33
+ const now = new Date();
34
+ const runId = formatTimestamp(now);
35
+ const runDir = path.join(this.storageDir, runId);
36
+ fs.mkdirSync(runDir, { recursive: true });
37
+ // Group test cases by criterionId to build suites
38
+ const suites = {};
39
+ for (const testCase of testCases) {
40
+ if (!suites[testCase.criterionId]) {
41
+ suites[testCase.criterionId] = {
42
+ status: "pending",
43
+ criterion_file: testCase.criterionFile,
44
+ tests: {},
45
+ };
46
+ }
47
+ const initialResult = {
48
+ status: "pending",
49
+ fixture_file: testCase.fixtureFile,
50
+ expected_file: testCase.expectedFile,
51
+ findings: null,
52
+ judge: null,
53
+ scores: null,
54
+ error: null,
55
+ executor_usage: null,
56
+ judge_usage: null,
57
+ };
58
+ suites[testCase.criterionId].tests[testCase.name] = initialResult;
59
+ }
60
+ const run = {
61
+ run_id: runId,
62
+ status: "running",
63
+ started_at: now.toISOString(),
64
+ completed_at: null,
65
+ suites,
66
+ };
67
+ this.writeRun(runId, run);
68
+ return run;
69
+ }
70
+ /** Read the results.json for a given run ID. */
71
+ getRun(runId) {
72
+ const resultsPath = this.resultsPath(runId);
73
+ const raw = fs.readFileSync(resultsPath, "utf-8");
74
+ return JSON.parse(raw);
75
+ }
76
+ /**
77
+ * Return the most recent run directory name, or null if no runs exist.
78
+ */
79
+ getLatestRunId() {
80
+ if (!fs.existsSync(this.storageDir)) {
81
+ return null;
82
+ }
83
+ const entries = fs.readdirSync(this.storageDir, { withFileTypes: true });
84
+ const runDirs = entries
85
+ .filter((entry) => entry.isDirectory() &&
86
+ fs.existsSync(path.join(this.storageDir, entry.name, "results.json")))
87
+ .map((entry) => entry.name)
88
+ .sort();
89
+ if (runDirs.length === 0) {
90
+ return null;
91
+ }
92
+ return runDirs[runDirs.length - 1];
93
+ }
94
+ /**
95
+ * Update a specific test case in results.json.
96
+ *
97
+ * Reads the current run, applies the partial update to the specified
98
+ * test case, and writes back atomically.
99
+ */
100
+ updateTestCase(runId, criterionId, testName, update) {
101
+ const run = this.getRun(runId);
102
+ const suite = run.suites[criterionId];
103
+ if (!suite) {
104
+ throw new Error(`Suite "${criterionId}" not found in run "${runId}"`);
105
+ }
106
+ const testCase = suite.tests[testName];
107
+ if (!testCase) {
108
+ throw new Error(`Test "${testName}" not found in suite "${criterionId}" of run "${runId}"`);
109
+ }
110
+ Object.assign(testCase, update);
111
+ this.writeRun(runId, run);
112
+ }
113
+ /** Update a suite's status. */
114
+ updateSuiteStatus(runId, criterionId, status) {
115
+ const run = this.getRun(runId);
116
+ const suite = run.suites[criterionId];
117
+ if (!suite) {
118
+ throw new Error(`Suite "${criterionId}" not found in run "${runId}"`);
119
+ }
120
+ suite.status = status;
121
+ this.writeRun(runId, run);
122
+ }
123
+ /** Mark the run as complete with a completion timestamp. */
124
+ completeRun(runId) {
125
+ const run = this.getRun(runId);
126
+ run.status = "complete";
127
+ run.completed_at = new Date().toISOString();
128
+ this.writeRun(runId, run);
129
+ }
130
+ // ---------------------------------------------------------------------------
131
+ // Private Helpers
132
+ // ---------------------------------------------------------------------------
133
+ resultsPath(runId) {
134
+ return path.join(this.storageDir, runId, "results.json");
135
+ }
136
+ /** Atomic write: write to .tmp then rename. */
137
+ writeRun(runId, run) {
138
+ const target = this.resultsPath(runId);
139
+ const tmp = target + ".tmp";
140
+ fs.writeFileSync(tmp, JSON.stringify(run, null, 2) + "\n");
141
+ fs.renameSync(tmp, target);
142
+ }
143
+ }
144
+ //# sourceMappingURL=TestStorageService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestStorageService.js","sourceRoot":"","sources":["../../../src/services/testing/TestStorageService.ts"],"names":[],"mappings":"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"}