@prowi/deskcheck 0.2.0 → 0.4.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 +78 -84
- package/build/cli.js +131 -25
- package/build/cli.js.map +1 -1
- package/build/config/loader.d.ts.map +1 -1
- package/build/config/loader.js +2 -1
- package/build/config/loader.js.map +1 -1
- package/build/config/types.d.ts +2 -1
- package/build/config/types.d.ts.map +1 -1
- package/build/mcp/tools.d.ts.map +1 -1
- package/build/mcp/tools.js +51 -65
- package/build/mcp/tools.js.map +1 -1
- package/build/prompts/ExecutorPrompt.d.ts +4 -2
- package/build/prompts/ExecutorPrompt.d.ts.map +1 -1
- package/build/prompts/ExecutorPrompt.js +72 -34
- package/build/prompts/ExecutorPrompt.js.map +1 -1
- package/build/prompts/PartitionerPrompt.d.ts +12 -0
- package/build/prompts/PartitionerPrompt.d.ts.map +1 -0
- package/build/prompts/PartitionerPrompt.js +54 -0
- package/build/prompts/PartitionerPrompt.js.map +1 -0
- package/build/prompts/ResolverPrompt.d.ts +11 -0
- package/build/prompts/ResolverPrompt.d.ts.map +1 -0
- package/build/prompts/ResolverPrompt.js +45 -0
- package/build/prompts/ResolverPrompt.js.map +1 -0
- package/build/renderers/review/MarkdownRenderer.d.ts.map +1 -1
- package/build/renderers/review/MarkdownRenderer.js +30 -11
- package/build/renderers/review/MarkdownRenderer.js.map +1 -1
- package/build/renderers/review/TerminalRenderer.d.ts.map +1 -1
- package/build/renderers/review/TerminalRenderer.js +38 -13
- package/build/renderers/review/TerminalRenderer.js.map +1 -1
- package/build/renderers/review/WatchRenderer.d.ts.map +1 -1
- package/build/renderers/review/WatchRenderer.js +12 -3
- package/build/renderers/review/WatchRenderer.js.map +1 -1
- package/build/renderers/shared.d.ts +11 -11
- package/build/renderers/shared.d.ts.map +1 -1
- package/build/renderers/shared.js +15 -15
- package/build/renderers/shared.js.map +1 -1
- package/build/server/controllers/ReviewController.d.ts +12 -3
- package/build/server/controllers/ReviewController.d.ts.map +1 -1
- package/build/server/controllers/ReviewController.js +50 -6
- package/build/server/controllers/ReviewController.js.map +1 -1
- package/build/server/server.d.ts.map +1 -1
- package/build/server/server.js +22 -1
- package/build/server/server.js.map +1 -1
- package/build/services/ExecutorService.d.ts +17 -2
- package/build/services/ExecutorService.d.ts.map +1 -1
- package/build/services/ExecutorService.js +37 -5
- package/build/services/ExecutorService.js.map +1 -1
- package/build/services/FindingsParserService.d.ts +7 -6
- package/build/services/FindingsParserService.d.ts.map +1 -1
- package/build/services/FindingsParserService.js +44 -14
- package/build/services/FindingsParserService.js.map +1 -1
- package/build/services/criteria/module-parser.d.ts +1 -1
- package/build/services/criteria/module-parser.d.ts.map +1 -1
- package/build/services/criteria/module-parser.js +20 -16
- package/build/services/criteria/module-parser.js.map +1 -1
- package/build/services/review/CodeSnippetService.d.ts +10 -0
- package/build/services/review/CodeSnippetService.d.ts.map +1 -0
- package/build/services/review/CodeSnippetService.js +54 -0
- package/build/services/review/CodeSnippetService.js.map +1 -0
- package/build/services/review/ReviewInputResolverService.d.ts +25 -0
- package/build/services/review/ReviewInputResolverService.d.ts.map +1 -0
- package/build/services/review/ReviewInputResolverService.js +106 -0
- package/build/services/review/ReviewInputResolverService.js.map +1 -0
- package/build/services/review/ReviewOrchestratorService.d.ts +2 -2
- package/build/services/review/ReviewOrchestratorService.d.ts.map +1 -1
- package/build/services/review/ReviewOrchestratorService.js +28 -27
- package/build/services/review/ReviewOrchestratorService.js.map +1 -1
- package/build/services/review/ReviewPartitionerService.d.ts +46 -0
- package/build/services/review/ReviewPartitionerService.d.ts.map +1 -0
- package/build/services/review/ReviewPartitionerService.js +208 -0
- package/build/services/review/ReviewPartitionerService.js.map +1 -0
- package/build/services/review/ReviewPlanBuilderService.d.ts +25 -7
- package/build/services/review/ReviewPlanBuilderService.d.ts.map +1 -1
- package/build/services/review/ReviewPlanBuilderService.js +88 -30
- package/build/services/review/ReviewPlanBuilderService.js.map +1 -1
- package/build/services/review/ReviewStorageService.d.ts +35 -11
- package/build/services/review/ReviewStorageService.d.ts.map +1 -1
- package/build/services/review/ReviewStorageService.js +136 -35
- package/build/services/review/ReviewStorageService.js.map +1 -1
- package/build/services/testing/TestRunnerService.d.ts.map +1 -1
- package/build/services/testing/TestRunnerService.js +10 -8
- package/build/services/testing/TestRunnerService.js.map +1 -1
- package/build/types/criteria.d.ts +8 -6
- package/build/types/criteria.d.ts.map +1 -1
- package/build/types/review.d.ts +173 -50
- package/build/types/review.d.ts.map +1 -1
- package/package.json +3 -1
- package/ui/dist/index.html +12 -63
- package/build/prompts/PlannerPrompt.d.ts +0 -12
- package/build/prompts/PlannerPrompt.d.ts.map +0 -1
- package/build/prompts/PlannerPrompt.js +0 -34
- package/build/prompts/PlannerPrompt.js.map +0 -1
- package/build/services/review/ReviewContextExtractorService.d.ts +0 -17
- package/build/services/review/ReviewContextExtractorService.d.ts.map +0 -1
- package/build/services/review/ReviewContextExtractorService.js +0 -69
- package/build/services/review/ReviewContextExtractorService.js.map +0 -1
- package/build/services/review/ReviewPlannerService.d.ts +0 -29
- package/build/services/review/ReviewPlannerService.d.ts.map +0 -1
- package/build/services/review/ReviewPlannerService.js +0 -122
- package/build/services/review/ReviewPlannerService.js.map +0 -1
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import { findMatchingModules } from "../criteria/glob-matcher.js";
|
|
2
|
+
import { PartitionerError } from "./ReviewPartitionerService.js";
|
|
2
3
|
/**
|
|
3
|
-
* Build a complete review plan: create the plan, match files to
|
|
4
|
-
*
|
|
4
|
+
* Build a complete review plan: create the plan, glob-match files to
|
|
5
|
+
* criteria, run one partitioner agent per matched criterion to produce
|
|
6
|
+
* subtasks, write everything to storage, and finalize.
|
|
5
7
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
8
|
+
* Partitioners run concurrently with `Promise.all`. ALL partitioners must
|
|
9
|
+
* complete before any tasks are added — this is a deliberate barrier so
|
|
10
|
+
* that downstream reviewers see a fully-partitioned plan and so that the
|
|
11
|
+
* partitioning step can be inspected as a unit (e.g. via `--dry-run`).
|
|
12
|
+
*
|
|
13
|
+
* If any partitioner throws, the error propagates and the plan is left in
|
|
14
|
+
* its pre-finalized state. Per design, partitioner failures fail the run.
|
|
9
15
|
*/
|
|
10
|
-
export function buildPlanWithTasks(storage, name,
|
|
11
|
-
// Create the plan shell
|
|
12
|
-
const plan = storage.createPlan(name,
|
|
13
|
-
// Match files against criteria
|
|
16
|
+
export async function buildPlanWithTasks(storage, partitioner, name, scope, invocation, files, modules, events) {
|
|
17
|
+
// Create the plan shell — already at step "matching" by default.
|
|
18
|
+
const plan = storage.createPlan(name, scope, invocation);
|
|
19
|
+
// Match files against criteria (programmatic, no LLM)
|
|
14
20
|
const matches = findMatchingModules(files, modules);
|
|
21
|
+
events?.onMatchingComplete?.(matches.length, new Set(matches.flatMap((m) => m.matchedFiles)).size);
|
|
15
22
|
// Track coverage
|
|
16
23
|
const matchedFileSet = new Set();
|
|
17
24
|
for (const match of matches) {
|
|
@@ -28,39 +35,90 @@ export function buildPlanWithTasks(storage, name, source, files, modules) {
|
|
|
28
35
|
moduleSummaries[match.module.id] = {
|
|
29
36
|
review_id: match.module.id,
|
|
30
37
|
description: match.module.description,
|
|
31
|
-
severity: match.module.severity,
|
|
32
38
|
model: match.module.model,
|
|
39
|
+
partition: match.module.partition,
|
|
33
40
|
task_count: 0,
|
|
34
41
|
matched_files: match.matchedFiles,
|
|
35
42
|
};
|
|
36
43
|
}
|
|
37
44
|
storage.setModules(plan.plan_id, moduleSummaries);
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Partition step — one fresh agent per matched criterion, all in parallel.
|
|
47
|
+
// Must complete entirely before any tasks are added. If anything throws,
|
|
48
|
+
// we stamp the failure on the plan so the UI can render it before
|
|
49
|
+
// re-raising for the CLI to surface.
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
storage.setStep(plan.plan_id, "partitioning");
|
|
52
|
+
let decisions;
|
|
53
|
+
try {
|
|
54
|
+
decisions = await Promise.all(matches.map(async (match) => {
|
|
55
|
+
events?.onPartitionStarted?.(match.module.id, match.matchedFiles.length);
|
|
56
|
+
const result = await partitioner.partition(match.module, match.matchedFiles, scope);
|
|
57
|
+
// Persist the partitioner's full SDK transcript as a side-file.
|
|
58
|
+
try {
|
|
59
|
+
storage.writePartitionerLog(plan.plan_id, match.module.id, result.messages);
|
|
60
|
+
}
|
|
61
|
+
catch (logErr) {
|
|
62
|
+
console.error(`[deskcheck] Warning: failed to write partitioner log for ${match.module.id}: ${logErr instanceof Error ? logErr.message : String(logErr)}`);
|
|
63
|
+
}
|
|
64
|
+
const decision = {
|
|
65
|
+
review_id: match.module.id,
|
|
66
|
+
matched_files: match.matchedFiles,
|
|
67
|
+
reasoning: result.reasoning,
|
|
68
|
+
subtasks: result.subtasks,
|
|
69
|
+
completed_at: new Date().toISOString(),
|
|
70
|
+
model: result.model,
|
|
71
|
+
usage: result.usage,
|
|
72
|
+
};
|
|
73
|
+
storage.setPartitionDecision(plan.plan_id, decision);
|
|
74
|
+
events?.onPartitionCompleted?.(decision);
|
|
75
|
+
return decision;
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
80
|
+
const reviewId = err instanceof PartitionerError ? err.reviewId : null;
|
|
81
|
+
storage.setFailure(plan.plan_id, {
|
|
82
|
+
step: "partitioning",
|
|
83
|
+
review_id: reviewId,
|
|
84
|
+
message,
|
|
85
|
+
});
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Materialize partitioner output as ReviewTasks. We do this after all
|
|
90
|
+
// partitioners finish so storage writes are batched and the plan is never
|
|
91
|
+
// half-partitioned on disk.
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
for (let i = 0; i < matches.length; i++) {
|
|
94
|
+
const match = matches[i];
|
|
95
|
+
const decision = decisions[i];
|
|
96
|
+
for (const subtask of decision.subtasks) {
|
|
43
97
|
storage.addTask(plan.plan_id, {
|
|
44
98
|
review_id: match.module.id,
|
|
45
99
|
review_file: match.module.file,
|
|
46
|
-
files:
|
|
47
|
-
|
|
100
|
+
files: subtask.files,
|
|
101
|
+
focus: subtask.focus,
|
|
102
|
+
hint: subtask.hint,
|
|
48
103
|
model: match.module.model,
|
|
104
|
+
tools: match.module.tools,
|
|
105
|
+
prompt: match.module.prompt,
|
|
49
106
|
});
|
|
50
107
|
}
|
|
51
|
-
else {
|
|
52
|
-
for (const file of match.matchedFiles) {
|
|
53
|
-
storage.addTask(plan.plan_id, {
|
|
54
|
-
review_id: match.module.id,
|
|
55
|
-
review_file: match.module.file,
|
|
56
|
-
files: [file],
|
|
57
|
-
hint: null,
|
|
58
|
-
model: match.module.model,
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
108
|
}
|
|
63
|
-
// Finalize the plan (sets status to "ready", recounts tasks per module)
|
|
64
|
-
|
|
109
|
+
// Finalize the plan (sets status to "ready", recounts tasks per module).
|
|
110
|
+
// After finalize, the orchestrator takes over — it flips step to "complete"
|
|
111
|
+
// when all tasks reach a terminal state. We set "reviewing" here as the
|
|
112
|
+
// intermediate so the UI sees the transition immediately on plan load.
|
|
113
|
+
const finalized = storage.finalizePlan(plan.plan_id);
|
|
114
|
+
if (Object.keys(finalized.tasks).length > 0) {
|
|
115
|
+
storage.setStep(plan.plan_id, "reviewing");
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// No tasks (no criteria matched, or all partitioners produced empty
|
|
119
|
+
// sets — though the validator forbids the latter). Mark complete.
|
|
120
|
+
storage.setStep(plan.plan_id, "complete");
|
|
121
|
+
}
|
|
122
|
+
return storage.getPlan(plan.plan_id);
|
|
65
123
|
}
|
|
66
124
|
//# sourceMappingURL=ReviewPlanBuilderService.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReviewPlanBuilderService.js","sourceRoot":"","sources":["../../../src/services/review/ReviewPlanBuilderService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"ReviewPlanBuilderService.js","sourceRoot":"","sources":["../../../src/services/review/ReviewPlanBuilderService.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Issue, ModuleSummary, PartitionDecision, PipelineStep, PlanFailure, PlanInvocation, ReviewPlan, ReviewResults, ReviewTask, Scope, TaskUsage } from "../../types/review.js";
|
|
2
2
|
/**
|
|
3
3
|
* Manages the two-file storage format (plan.json + results.json) for deskcheck runs.
|
|
4
4
|
*
|
|
@@ -29,7 +29,7 @@ export declare class ReviewStorageService {
|
|
|
29
29
|
* Creates the timestamped directory and writes an initial plan.json with
|
|
30
30
|
* status "planning" and empty collections.
|
|
31
31
|
*/
|
|
32
|
-
createPlan(name: string,
|
|
32
|
+
createPlan(name: string, scope: Scope, invocation: PlanInvocation): ReviewPlan;
|
|
33
33
|
/** Read the plan.json for a given plan ID. */
|
|
34
34
|
getPlan(planId: string): ReviewPlan;
|
|
35
35
|
/**
|
|
@@ -56,18 +56,42 @@ export declare class ReviewStorageService {
|
|
|
56
56
|
* appending a zero-padded incrementing suffix (e.g., "-001", "-002").
|
|
57
57
|
* The task is created with status "pending" and null context fields.
|
|
58
58
|
*/
|
|
59
|
-
addTask(planId: string, task: Omit<ReviewTask, "task_id" | "status" | "created_at" | "started_at" | "completed_at" | "
|
|
59
|
+
addTask(planId: string, task: Omit<ReviewTask, "task_id" | "status" | "created_at" | "started_at" | "completed_at" | "scope" | "focus" | "tools" | "error" | "prompt"> & {
|
|
60
|
+
focus?: string | null;
|
|
61
|
+
tools?: string[];
|
|
62
|
+
prompt?: string | null;
|
|
63
|
+
}): ReviewTask;
|
|
64
|
+
/** Record a partitioner decision for one criterion. */
|
|
65
|
+
setPartitionDecision(planId: string, decision: PartitionDecision): void;
|
|
66
|
+
/**
|
|
67
|
+
* Update the pipeline step the plan is currently in. When transitioning
|
|
68
|
+
* to a terminal step (`complete`/`failed`), also promotes `status` to the
|
|
69
|
+
* matching terminal value and stamps `completed_at` if not already set —
|
|
70
|
+
* this keeps the invariant `step terminal ⟺ status terminal`.
|
|
71
|
+
*/
|
|
72
|
+
setStep(planId: string, step: PipelineStep): void;
|
|
73
|
+
/**
|
|
74
|
+
* Mark the plan as failed at a specific step. Sets `status = "failed"`,
|
|
75
|
+
* `step = "failed"`, and stamps the failure details. Idempotent — if the
|
|
76
|
+
* plan is already failed, the new failure overwrites the old.
|
|
77
|
+
*/
|
|
78
|
+
setFailure(planId: string, failure: PlanFailure): void;
|
|
79
|
+
/** Persist the full SDK message stream from one reviewer's run. */
|
|
80
|
+
writeTaskLog(planId: string, taskId: string, messages: unknown[]): void;
|
|
81
|
+
/** Read a previously persisted reviewer transcript. */
|
|
82
|
+
getTaskLog(planId: string, taskId: string): unknown[];
|
|
83
|
+
/** Persist the full SDK message stream from one partitioner's run. */
|
|
84
|
+
writePartitionerLog(planId: string, reviewId: string, messages: unknown[]): void;
|
|
85
|
+
/** Read a previously persisted partitioner transcript. */
|
|
86
|
+
getPartitionerLog(planId: string, reviewId: string): unknown[];
|
|
60
87
|
/**
|
|
61
88
|
* Claim a pending task for execution.
|
|
62
89
|
*
|
|
63
|
-
* Sets the task status to "in_progress",
|
|
64
|
-
*
|
|
90
|
+
* Sets the task status to "in_progress", records the start time, and
|
|
91
|
+
* optionally stores the criterion prompt for later inspection.
|
|
65
92
|
*/
|
|
66
|
-
claimTask(planId: string, taskId: string,
|
|
67
|
-
|
|
68
|
-
content: string;
|
|
69
|
-
symbol?: string;
|
|
70
|
-
prompt: string;
|
|
93
|
+
claimTask(planId: string, taskId: string, options?: {
|
|
94
|
+
prompt?: string;
|
|
71
95
|
}): ReviewTask;
|
|
72
96
|
/**
|
|
73
97
|
* Return tasks eligible for execution: those with status "pending" or
|
|
@@ -81,7 +105,7 @@ export declare class ReviewStorageService {
|
|
|
81
105
|
* results.json, and recomputes all aggregations (by_file, by_module,
|
|
82
106
|
* summary, completion).
|
|
83
107
|
*/
|
|
84
|
-
completeTask(planId: string, taskId: string,
|
|
108
|
+
completeTask(planId: string, taskId: string, issues: Issue[], usage?: TaskUsage | null): void;
|
|
85
109
|
/**
|
|
86
110
|
* Mark a task as errored.
|
|
87
111
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReviewStorageService.d.ts","sourceRoot":"","sources":["../../../src/services/review/ReviewStorageService.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,WAAW,EACX,
|
|
1
|
+
{"version":3,"file":"ReviewStorageService.d.ts","sourceRoot":"","sources":["../../../src/services/review/ReviewStorageService.ts"],"names":[],"mappings":"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"}
|
|
@@ -130,7 +130,7 @@ export class ReviewStorageService {
|
|
|
130
130
|
* Creates the timestamped directory and writes an initial plan.json with
|
|
131
131
|
* status "planning" and empty collections.
|
|
132
132
|
*/
|
|
133
|
-
createPlan(name,
|
|
133
|
+
createPlan(name, scope, invocation) {
|
|
134
134
|
const now = new Date();
|
|
135
135
|
const planId = formatTimestamp(now);
|
|
136
136
|
const planDir = path.join(this.storageDir, planId);
|
|
@@ -138,8 +138,11 @@ export class ReviewStorageService {
|
|
|
138
138
|
const plan = {
|
|
139
139
|
plan_id: planId,
|
|
140
140
|
name,
|
|
141
|
-
|
|
141
|
+
invocation,
|
|
142
|
+
scope,
|
|
142
143
|
status: "planning",
|
|
144
|
+
step: "matching",
|
|
145
|
+
failure: null,
|
|
143
146
|
created_at: now.toISOString(),
|
|
144
147
|
finalized_at: null,
|
|
145
148
|
started_at: null,
|
|
@@ -148,6 +151,7 @@ export class ReviewStorageService {
|
|
|
148
151
|
unmatched_files: [],
|
|
149
152
|
tasks: {},
|
|
150
153
|
modules: {},
|
|
154
|
+
partition_decisions: {},
|
|
151
155
|
};
|
|
152
156
|
this.writePlan(planId, plan);
|
|
153
157
|
return plan;
|
|
@@ -240,29 +244,110 @@ export class ReviewStorageService {
|
|
|
240
244
|
review_id: task.review_id,
|
|
241
245
|
review_file: task.review_file,
|
|
242
246
|
files: task.files,
|
|
247
|
+
scope: plan.scope,
|
|
248
|
+
focus: task.focus ?? null,
|
|
243
249
|
hint: task.hint,
|
|
244
250
|
model: task.model,
|
|
251
|
+
tools: task.tools ?? [],
|
|
245
252
|
status: "pending",
|
|
246
253
|
created_at: new Date().toISOString(),
|
|
247
254
|
started_at: null,
|
|
248
255
|
completed_at: null,
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
symbol: null,
|
|
252
|
-
prompt: null,
|
|
256
|
+
error: null,
|
|
257
|
+
prompt: task.prompt ?? null,
|
|
253
258
|
};
|
|
254
259
|
plan.tasks[taskId] = newTask;
|
|
255
260
|
this.writePlan(planId, plan);
|
|
256
261
|
return newTask;
|
|
257
262
|
});
|
|
258
263
|
}
|
|
264
|
+
/** Record a partitioner decision for one criterion. */
|
|
265
|
+
setPartitionDecision(planId, decision) {
|
|
266
|
+
this.withLock(planId, () => {
|
|
267
|
+
const plan = this.getPlan(planId);
|
|
268
|
+
plan.partition_decisions[decision.review_id] = decision;
|
|
269
|
+
this.writePlan(planId, plan);
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Update the pipeline step the plan is currently in. When transitioning
|
|
274
|
+
* to a terminal step (`complete`/`failed`), also promotes `status` to the
|
|
275
|
+
* matching terminal value and stamps `completed_at` if not already set —
|
|
276
|
+
* this keeps the invariant `step terminal ⟺ status terminal`.
|
|
277
|
+
*/
|
|
278
|
+
setStep(planId, step) {
|
|
279
|
+
this.withLock(planId, () => {
|
|
280
|
+
const plan = this.getPlan(planId);
|
|
281
|
+
plan.step = step;
|
|
282
|
+
if (step === "complete" && plan.status !== "complete") {
|
|
283
|
+
plan.status = "complete";
|
|
284
|
+
plan.completed_at = plan.completed_at ?? new Date().toISOString();
|
|
285
|
+
}
|
|
286
|
+
else if (step === "failed" && plan.status !== "failed") {
|
|
287
|
+
plan.status = "failed";
|
|
288
|
+
plan.completed_at = plan.completed_at ?? new Date().toISOString();
|
|
289
|
+
}
|
|
290
|
+
this.writePlan(planId, plan);
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Mark the plan as failed at a specific step. Sets `status = "failed"`,
|
|
295
|
+
* `step = "failed"`, and stamps the failure details. Idempotent — if the
|
|
296
|
+
* plan is already failed, the new failure overwrites the old.
|
|
297
|
+
*/
|
|
298
|
+
setFailure(planId, failure) {
|
|
299
|
+
this.withLock(planId, () => {
|
|
300
|
+
const plan = this.getPlan(planId);
|
|
301
|
+
plan.status = "failed";
|
|
302
|
+
plan.step = "failed";
|
|
303
|
+
plan.failure = failure;
|
|
304
|
+
plan.completed_at = plan.completed_at ?? new Date().toISOString();
|
|
305
|
+
this.writePlan(planId, plan);
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
// ---------------------------------------------------------------------------
|
|
309
|
+
// Agent transcripts
|
|
310
|
+
//
|
|
311
|
+
// Each agent run (executor for a task, partitioner for a criterion) writes
|
|
312
|
+
// its full SDK message stream to a side-file in the plan directory. Kept
|
|
313
|
+
// out of plan.json/results.json so those stay small enough to load
|
|
314
|
+
// routinely. The CLI/server only loads transcripts on demand.
|
|
315
|
+
// ---------------------------------------------------------------------------
|
|
316
|
+
/** Persist the full SDK message stream from one reviewer's run. */
|
|
317
|
+
writeTaskLog(planId, taskId, messages) {
|
|
318
|
+
const target = path.join(this.planDir(planId), `task_${taskId}.log.json`);
|
|
319
|
+
const tmp = target + ".tmp";
|
|
320
|
+
fs.writeFileSync(tmp, JSON.stringify(messages, null, 2) + "\n");
|
|
321
|
+
fs.renameSync(tmp, target);
|
|
322
|
+
}
|
|
323
|
+
/** Read a previously persisted reviewer transcript. */
|
|
324
|
+
getTaskLog(planId, taskId) {
|
|
325
|
+
const target = path.join(this.planDir(planId), `task_${taskId}.log.json`);
|
|
326
|
+
if (!fs.existsSync(target))
|
|
327
|
+
return [];
|
|
328
|
+
return JSON.parse(fs.readFileSync(target, "utf-8"));
|
|
329
|
+
}
|
|
330
|
+
/** Persist the full SDK message stream from one partitioner's run. */
|
|
331
|
+
writePartitionerLog(planId, reviewId, messages) {
|
|
332
|
+
const target = path.join(this.planDir(planId), `partitioner_${flattenReviewId(reviewId)}.log.json`);
|
|
333
|
+
const tmp = target + ".tmp";
|
|
334
|
+
fs.writeFileSync(tmp, JSON.stringify(messages, null, 2) + "\n");
|
|
335
|
+
fs.renameSync(tmp, target);
|
|
336
|
+
}
|
|
337
|
+
/** Read a previously persisted partitioner transcript. */
|
|
338
|
+
getPartitionerLog(planId, reviewId) {
|
|
339
|
+
const target = path.join(this.planDir(planId), `partitioner_${flattenReviewId(reviewId)}.log.json`);
|
|
340
|
+
if (!fs.existsSync(target))
|
|
341
|
+
return [];
|
|
342
|
+
return JSON.parse(fs.readFileSync(target, "utf-8"));
|
|
343
|
+
}
|
|
259
344
|
/**
|
|
260
345
|
* Claim a pending task for execution.
|
|
261
346
|
*
|
|
262
|
-
* Sets the task status to "in_progress",
|
|
263
|
-
*
|
|
347
|
+
* Sets the task status to "in_progress", records the start time, and
|
|
348
|
+
* optionally stores the criterion prompt for later inspection.
|
|
264
349
|
*/
|
|
265
|
-
claimTask(planId, taskId,
|
|
350
|
+
claimTask(planId, taskId, options) {
|
|
266
351
|
return this.withLock(planId, () => {
|
|
267
352
|
const plan = this.getPlan(planId);
|
|
268
353
|
const task = plan.tasks[taskId];
|
|
@@ -271,10 +356,9 @@ export class ReviewStorageService {
|
|
|
271
356
|
}
|
|
272
357
|
task.status = "in_progress";
|
|
273
358
|
task.started_at = new Date().toISOString();
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
task.prompt = context.prompt;
|
|
359
|
+
if (options?.prompt !== undefined) {
|
|
360
|
+
task.prompt = options.prompt;
|
|
361
|
+
}
|
|
278
362
|
// Set plan to executing if it was ready
|
|
279
363
|
if (plan.status === "ready") {
|
|
280
364
|
plan.started_at = task.started_at;
|
|
@@ -311,7 +395,7 @@ export class ReviewStorageService {
|
|
|
311
395
|
* results.json, and recomputes all aggregations (by_file, by_module,
|
|
312
396
|
* summary, completion).
|
|
313
397
|
*/
|
|
314
|
-
completeTask(planId, taskId,
|
|
398
|
+
completeTask(planId, taskId, issues, usage) {
|
|
315
399
|
this.withLock(planId, () => {
|
|
316
400
|
const now = new Date().toISOString();
|
|
317
401
|
// Update plan.json task status
|
|
@@ -326,6 +410,7 @@ export class ReviewStorageService {
|
|
|
326
410
|
const allDone = Object.values(plan.tasks).every((t) => t.status === "complete" || t.status === "error");
|
|
327
411
|
if (allDone) {
|
|
328
412
|
plan.status = "complete";
|
|
413
|
+
plan.step = "complete";
|
|
329
414
|
plan.completed_at = now;
|
|
330
415
|
}
|
|
331
416
|
this.writePlan(planId, plan);
|
|
@@ -335,7 +420,7 @@ export class ReviewStorageService {
|
|
|
335
420
|
review_id: task.review_id,
|
|
336
421
|
files: task.files,
|
|
337
422
|
completed_at: now,
|
|
338
|
-
|
|
423
|
+
issues,
|
|
339
424
|
usage: usage ?? null,
|
|
340
425
|
};
|
|
341
426
|
const results = this.loadOrCreateResults(planId);
|
|
@@ -362,10 +447,12 @@ export class ReviewStorageService {
|
|
|
362
447
|
}
|
|
363
448
|
task.status = "error";
|
|
364
449
|
task.completed_at = now;
|
|
450
|
+
task.error = errorMessage;
|
|
365
451
|
// Check if all tasks have reached a terminal status
|
|
366
452
|
const allDone = Object.values(plan.tasks).every((t) => t.status === "complete" || t.status === "error");
|
|
367
453
|
if (allDone) {
|
|
368
454
|
plan.status = "complete";
|
|
455
|
+
plan.step = "complete";
|
|
369
456
|
plan.completed_at = now;
|
|
370
457
|
}
|
|
371
458
|
this.writePlan(planId, plan);
|
|
@@ -377,7 +464,7 @@ export class ReviewStorageService {
|
|
|
377
464
|
review_id: task.review_id,
|
|
378
465
|
files: task.files,
|
|
379
466
|
completed_at: now,
|
|
380
|
-
|
|
467
|
+
issues: [],
|
|
381
468
|
usage: usage ?? null,
|
|
382
469
|
};
|
|
383
470
|
results.task_results[taskId] = taskResult;
|
|
@@ -502,32 +589,47 @@ export class ReviewStorageService {
|
|
|
502
589
|
in_progress: tasks.filter((t) => t.status === "in_progress").length,
|
|
503
590
|
errored: tasks.filter((t) => t.status === "error").length,
|
|
504
591
|
};
|
|
505
|
-
// ----
|
|
592
|
+
// ---- Stamp issue_id on all issues ----
|
|
593
|
+
for (const taskResult of Object.values(results.task_results)) {
|
|
594
|
+
for (let i = 0; i < taskResult.issues.length; i++) {
|
|
595
|
+
taskResult.issues[i].issue_id = `${taskResult.task_id}:${i}`;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
// ---- Summary (aggregate issue counts) ----
|
|
506
599
|
const summary = { total: 0, critical: 0, warning: 0, info: 0 };
|
|
507
600
|
for (const taskResult of Object.values(results.task_results)) {
|
|
508
|
-
for (const
|
|
601
|
+
for (const issue of taskResult.issues) {
|
|
509
602
|
summary.total++;
|
|
510
|
-
summary[
|
|
603
|
+
summary[issue.severity]++;
|
|
511
604
|
}
|
|
512
605
|
}
|
|
513
606
|
results.summary = summary;
|
|
514
|
-
// ---- by_file (group
|
|
607
|
+
// ---- by_file (group issues by reference file paths) ----
|
|
608
|
+
// An issue can appear under multiple files if it has multiple references.
|
|
515
609
|
const byFile = {};
|
|
516
610
|
for (const taskResult of Object.values(results.task_results)) {
|
|
517
|
-
for (const
|
|
518
|
-
const
|
|
519
|
-
...
|
|
611
|
+
for (const issue of taskResult.issues) {
|
|
612
|
+
const fileIssue = {
|
|
613
|
+
...issue,
|
|
614
|
+
issue_id: issue.issue_id,
|
|
520
615
|
review_id: taskResult.review_id,
|
|
521
616
|
task_id: taskResult.task_id,
|
|
522
617
|
};
|
|
523
|
-
|
|
524
|
-
|
|
618
|
+
// Index under each referenced file path
|
|
619
|
+
const seenFiles = new Set();
|
|
620
|
+
for (const ref of issue.references) {
|
|
621
|
+
if (ref.file && !seenFiles.has(ref.file)) {
|
|
622
|
+
seenFiles.add(ref.file);
|
|
623
|
+
if (!byFile[ref.file]) {
|
|
624
|
+
byFile[ref.file] = [];
|
|
625
|
+
}
|
|
626
|
+
byFile[ref.file].push(fileIssue);
|
|
627
|
+
}
|
|
525
628
|
}
|
|
526
|
-
byFile[finding.file].push(fileFinding);
|
|
527
629
|
}
|
|
528
630
|
}
|
|
529
631
|
results.by_file = byFile;
|
|
530
|
-
// ---- by_module (group
|
|
632
|
+
// ---- by_module (group issues by criterion) ----
|
|
531
633
|
const byModule = {};
|
|
532
634
|
for (const taskResult of Object.values(results.task_results)) {
|
|
533
635
|
const reviewId = taskResult.review_id;
|
|
@@ -537,19 +639,18 @@ export class ReviewStorageService {
|
|
|
537
639
|
byModule[reviewId] = {
|
|
538
640
|
review_id: reviewId,
|
|
539
641
|
description: moduleSummary?.description ?? "",
|
|
540
|
-
severity: moduleSummary?.severity ?? "medium",
|
|
541
642
|
task_count: moduleSummary?.task_count ?? 0,
|
|
542
643
|
completed: 0,
|
|
543
644
|
counts: { critical: 0, warning: 0, info: 0, total: 0 },
|
|
544
|
-
|
|
645
|
+
issues: [],
|
|
545
646
|
};
|
|
546
647
|
}
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
for (const
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
648
|
+
const moduleIssues = byModule[reviewId];
|
|
649
|
+
moduleIssues.completed++;
|
|
650
|
+
for (const issue of taskResult.issues) {
|
|
651
|
+
moduleIssues.counts.total++;
|
|
652
|
+
moduleIssues.counts[issue.severity]++;
|
|
653
|
+
moduleIssues.issues.push(issue);
|
|
553
654
|
}
|
|
554
655
|
}
|
|
555
656
|
results.by_module = byModule;
|