@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.
- package/README.md +67 -12
- package/build/cli.js +110 -22
- package/build/cli.js.map +1 -1
- package/build/{core/config.d.ts → config/loader.d.ts} +9 -1
- package/build/config/loader.d.ts.map +1 -0
- package/build/{core/config.js → config/loader.js} +4 -2
- package/build/config/loader.js.map +1 -0
- package/build/config/types.d.ts +45 -0
- package/build/config/types.d.ts.map +1 -0
- package/build/config/types.js +2 -0
- package/build/config/types.js.map +1 -0
- package/build/mcp/tools.d.ts +1 -1
- package/build/mcp/tools.d.ts.map +1 -1
- package/build/mcp/tools.js +5 -5
- package/build/mcp/tools.js.map +1 -1
- package/build/mcp-server.js +1 -1
- package/build/mcp-server.js.map +1 -1
- package/build/{agents/executor-prompt.d.ts → prompts/ExecutorPrompt.d.ts} +6 -2
- package/build/prompts/ExecutorPrompt.d.ts.map +1 -0
- package/build/prompts/ExecutorPrompt.js +80 -0
- package/build/prompts/ExecutorPrompt.js.map +1 -0
- package/build/prompts/JudgePrompt.d.ts +16 -0
- package/build/prompts/JudgePrompt.d.ts.map +1 -0
- package/build/prompts/JudgePrompt.js +57 -0
- package/build/prompts/JudgePrompt.js.map +1 -0
- package/build/prompts/PlannerPrompt.d.ts +12 -0
- package/build/prompts/PlannerPrompt.d.ts.map +1 -0
- package/build/prompts/PlannerPrompt.js +34 -0
- package/build/prompts/PlannerPrompt.js.map +1 -0
- package/build/renderers/{json.d.ts → review/JsonRenderer.d.ts} +2 -2
- package/build/renderers/review/JsonRenderer.d.ts.map +1 -0
- package/build/renderers/{json.js → review/JsonRenderer.js} +1 -1
- package/build/renderers/review/JsonRenderer.js.map +1 -0
- package/build/renderers/{markdown.d.ts → review/MarkdownRenderer.d.ts} +2 -2
- package/build/renderers/review/MarkdownRenderer.d.ts.map +1 -0
- package/build/renderers/{markdown.js → review/MarkdownRenderer.js} +2 -2
- package/build/renderers/review/MarkdownRenderer.js.map +1 -0
- package/build/renderers/{terminal.d.ts → review/TerminalRenderer.d.ts} +2 -2
- package/build/renderers/review/TerminalRenderer.d.ts.map +1 -0
- package/build/renderers/{terminal.js → review/TerminalRenderer.js} +2 -2
- package/build/renderers/review/TerminalRenderer.js.map +1 -0
- package/build/renderers/{watch.d.ts → review/WatchRenderer.d.ts} +2 -2
- package/build/renderers/review/WatchRenderer.d.ts.map +1 -0
- package/build/renderers/{watch.js → review/WatchRenderer.js} +1 -1
- package/build/renderers/review/WatchRenderer.js.map +1 -0
- package/build/renderers/shared.d.ts +1 -1
- package/build/renderers/shared.d.ts.map +1 -1
- package/build/renderers/test/TerminalRenderer.d.ts +14 -0
- package/build/renderers/test/TerminalRenderer.d.ts.map +1 -0
- package/build/renderers/test/TerminalRenderer.js +233 -0
- package/build/renderers/test/TerminalRenderer.js.map +1 -0
- package/build/server/controllers/ReviewController.d.ts +23 -0
- package/build/server/controllers/ReviewController.d.ts.map +1 -0
- package/build/server/controllers/ReviewController.js +90 -0
- package/build/server/controllers/ReviewController.js.map +1 -0
- package/build/server/controllers/TestController.d.ts +2 -0
- package/build/server/controllers/TestController.d.ts.map +1 -0
- package/build/server/controllers/TestController.js +3 -0
- package/build/server/controllers/TestController.js.map +1 -0
- package/build/server/middleware/cors.d.ts +9 -0
- package/build/server/middleware/cors.d.ts.map +1 -0
- package/build/server/middleware/cors.js +18 -0
- package/build/server/middleware/cors.js.map +1 -0
- package/build/{serve.d.ts → server/server.d.ts} +2 -2
- package/build/server/server.d.ts.map +1 -0
- package/build/server/server.js +102 -0
- package/build/server/server.js.map +1 -0
- package/build/server/sse/FileWatcherSSE.d.ts +10 -0
- package/build/server/sse/FileWatcherSSE.d.ts.map +1 -0
- package/build/server/sse/FileWatcherSSE.js +89 -0
- package/build/server/sse/FileWatcherSSE.js.map +1 -0
- package/build/services/ExecutorService.d.ts +51 -0
- package/build/services/ExecutorService.d.ts.map +1 -0
- package/build/services/ExecutorService.js +133 -0
- package/build/services/ExecutorService.js.map +1 -0
- package/build/services/FindingsParserService.d.ts +10 -0
- package/build/services/FindingsParserService.d.ts.map +1 -0
- package/build/services/FindingsParserService.js +64 -0
- package/build/services/FindingsParserService.js.map +1 -0
- package/build/services/criteria/CriteriaService.d.ts +10 -0
- package/build/services/criteria/CriteriaService.d.ts.map +1 -0
- package/build/services/criteria/CriteriaService.js +10 -0
- package/build/services/criteria/CriteriaService.js.map +1 -0
- package/build/{core → services/criteria}/glob-matcher.d.ts +1 -1
- package/build/services/criteria/glob-matcher.d.ts.map +1 -0
- package/build/services/criteria/glob-matcher.js.map +1 -0
- package/build/{core → services/criteria}/module-parser.d.ts +13 -1
- package/build/services/criteria/module-parser.d.ts.map +1 -0
- package/build/{core → services/criteria}/module-parser.js +38 -0
- package/build/services/criteria/module-parser.js.map +1 -0
- package/build/{core/context-extractor.d.ts → services/review/ReviewContextExtractorService.d.ts} +2 -2
- package/build/services/review/ReviewContextExtractorService.d.ts.map +1 -0
- package/build/{core/context-extractor.js → services/review/ReviewContextExtractorService.js} +1 -1
- package/build/services/review/ReviewContextExtractorService.js.map +1 -0
- package/build/{agents/orchestrator.d.ts → services/review/ReviewOrchestratorService.d.ts} +5 -3
- package/build/services/review/ReviewOrchestratorService.d.ts.map +1 -0
- package/build/{agents/orchestrator.js → services/review/ReviewOrchestratorService.js} +15 -171
- package/build/services/review/ReviewOrchestratorService.js.map +1 -0
- package/build/{core/plan-builder.d.ts → services/review/ReviewPlanBuilderService.d.ts} +5 -4
- package/build/services/review/ReviewPlanBuilderService.d.ts.map +1 -0
- package/build/{core/plan-builder.js → services/review/ReviewPlanBuilderService.js} +2 -2
- package/build/services/review/ReviewPlanBuilderService.js.map +1 -0
- package/build/{agents/planner.d.ts → services/review/ReviewPlannerService.d.ts} +5 -4
- package/build/services/review/ReviewPlannerService.d.ts.map +1 -0
- package/build/{agents/planner.js → services/review/ReviewPlannerService.js} +13 -29
- package/build/services/review/ReviewPlannerService.js.map +1 -0
- package/build/{core/storage.d.ts → services/review/ReviewStorageService.d.ts} +3 -3
- package/build/services/review/ReviewStorageService.d.ts.map +1 -0
- package/build/{core/storage.js → services/review/ReviewStorageService.js} +6 -6
- package/build/services/review/ReviewStorageService.js.map +1 -0
- package/build/services/testing/JudgeService.d.ts +30 -0
- package/build/services/testing/JudgeService.d.ts.map +1 -0
- package/build/services/testing/JudgeService.js +95 -0
- package/build/services/testing/JudgeService.js.map +1 -0
- package/build/services/testing/TestDiscoveryService.d.ts +16 -0
- package/build/services/testing/TestDiscoveryService.d.ts.map +1 -0
- package/build/services/testing/TestDiscoveryService.js +66 -0
- package/build/services/testing/TestDiscoveryService.js.map +1 -0
- package/build/services/testing/TestRunnerService.d.ts +35 -0
- package/build/services/testing/TestRunnerService.d.ts.map +1 -0
- package/build/services/testing/TestRunnerService.js +140 -0
- package/build/services/testing/TestRunnerService.js.map +1 -0
- package/build/services/testing/TestScorerService.d.ts +9 -0
- package/build/services/testing/TestScorerService.d.ts.map +1 -0
- package/build/services/testing/TestScorerService.js +35 -0
- package/build/services/testing/TestScorerService.js.map +1 -0
- package/build/services/testing/TestStorageService.d.ts +39 -0
- package/build/services/testing/TestStorageService.d.ts.map +1 -0
- package/build/services/testing/TestStorageService.js +144 -0
- package/build/services/testing/TestStorageService.js.map +1 -0
- package/build/types/criteria.d.ts +24 -0
- package/build/types/criteria.d.ts.map +1 -0
- package/build/{core/types.js → types/criteria.js} +2 -2
- package/build/types/criteria.js.map +1 -0
- package/build/{core/types.d.ts → types/review.d.ts} +2 -64
- package/build/types/review.d.ts.map +1 -0
- package/build/types/review.js +2 -0
- package/build/types/review.js.map +1 -0
- package/build/types/testing.d.ts +109 -0
- package/build/types/testing.d.ts.map +1 -0
- package/build/types/testing.js +2 -0
- package/build/types/testing.js.map +1 -0
- package/package.json +1 -1
- package/build/agents/executor-prompt.d.ts.map +0 -1
- package/build/agents/executor-prompt.js +0 -65
- package/build/agents/executor-prompt.js.map +0 -1
- package/build/agents/orchestrator.d.ts.map +0 -1
- package/build/agents/orchestrator.js.map +0 -1
- package/build/agents/planner.d.ts.map +0 -1
- package/build/agents/planner.js.map +0 -1
- package/build/core/config.d.ts.map +0 -1
- package/build/core/config.js.map +0 -1
- package/build/core/context-extractor.d.ts.map +0 -1
- package/build/core/context-extractor.js.map +0 -1
- package/build/core/glob-matcher.d.ts.map +0 -1
- package/build/core/glob-matcher.js.map +0 -1
- package/build/core/module-parser.d.ts.map +0 -1
- package/build/core/module-parser.js.map +0 -1
- package/build/core/plan-builder.d.ts.map +0 -1
- package/build/core/plan-builder.js.map +0 -1
- package/build/core/storage.d.ts.map +0 -1
- package/build/core/storage.js.map +0 -1
- package/build/core/types.d.ts.map +0 -1
- package/build/core/types.js.map +0 -1
- package/build/renderers/json.d.ts.map +0 -1
- package/build/renderers/json.js.map +0 -1
- package/build/renderers/markdown.d.ts.map +0 -1
- package/build/renderers/markdown.js.map +0 -1
- package/build/renderers/terminal.d.ts.map +0 -1
- package/build/renderers/terminal.js.map +0 -1
- package/build/renderers/watch.d.ts.map +0 -1
- package/build/renderers/watch.js.map +0 -1
- package/build/serve.d.ts.map +0 -1
- package/build/serve.js +0 -249
- package/build/serve.js.map +0 -1
- /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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"}
|