@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.
Files changed (100) hide show
  1. package/README.md +78 -84
  2. package/build/cli.js +131 -25
  3. package/build/cli.js.map +1 -1
  4. package/build/config/loader.d.ts.map +1 -1
  5. package/build/config/loader.js +2 -1
  6. package/build/config/loader.js.map +1 -1
  7. package/build/config/types.d.ts +2 -1
  8. package/build/config/types.d.ts.map +1 -1
  9. package/build/mcp/tools.d.ts.map +1 -1
  10. package/build/mcp/tools.js +51 -65
  11. package/build/mcp/tools.js.map +1 -1
  12. package/build/prompts/ExecutorPrompt.d.ts +4 -2
  13. package/build/prompts/ExecutorPrompt.d.ts.map +1 -1
  14. package/build/prompts/ExecutorPrompt.js +72 -34
  15. package/build/prompts/ExecutorPrompt.js.map +1 -1
  16. package/build/prompts/PartitionerPrompt.d.ts +12 -0
  17. package/build/prompts/PartitionerPrompt.d.ts.map +1 -0
  18. package/build/prompts/PartitionerPrompt.js +54 -0
  19. package/build/prompts/PartitionerPrompt.js.map +1 -0
  20. package/build/prompts/ResolverPrompt.d.ts +11 -0
  21. package/build/prompts/ResolverPrompt.d.ts.map +1 -0
  22. package/build/prompts/ResolverPrompt.js +45 -0
  23. package/build/prompts/ResolverPrompt.js.map +1 -0
  24. package/build/renderers/review/MarkdownRenderer.d.ts.map +1 -1
  25. package/build/renderers/review/MarkdownRenderer.js +30 -11
  26. package/build/renderers/review/MarkdownRenderer.js.map +1 -1
  27. package/build/renderers/review/TerminalRenderer.d.ts.map +1 -1
  28. package/build/renderers/review/TerminalRenderer.js +38 -13
  29. package/build/renderers/review/TerminalRenderer.js.map +1 -1
  30. package/build/renderers/review/WatchRenderer.d.ts.map +1 -1
  31. package/build/renderers/review/WatchRenderer.js +12 -3
  32. package/build/renderers/review/WatchRenderer.js.map +1 -1
  33. package/build/renderers/shared.d.ts +11 -11
  34. package/build/renderers/shared.d.ts.map +1 -1
  35. package/build/renderers/shared.js +15 -15
  36. package/build/renderers/shared.js.map +1 -1
  37. package/build/server/controllers/ReviewController.d.ts +12 -3
  38. package/build/server/controllers/ReviewController.d.ts.map +1 -1
  39. package/build/server/controllers/ReviewController.js +50 -6
  40. package/build/server/controllers/ReviewController.js.map +1 -1
  41. package/build/server/server.d.ts.map +1 -1
  42. package/build/server/server.js +22 -1
  43. package/build/server/server.js.map +1 -1
  44. package/build/services/ExecutorService.d.ts +17 -2
  45. package/build/services/ExecutorService.d.ts.map +1 -1
  46. package/build/services/ExecutorService.js +37 -5
  47. package/build/services/ExecutorService.js.map +1 -1
  48. package/build/services/FindingsParserService.d.ts +7 -6
  49. package/build/services/FindingsParserService.d.ts.map +1 -1
  50. package/build/services/FindingsParserService.js +44 -14
  51. package/build/services/FindingsParserService.js.map +1 -1
  52. package/build/services/criteria/module-parser.d.ts +1 -1
  53. package/build/services/criteria/module-parser.d.ts.map +1 -1
  54. package/build/services/criteria/module-parser.js +20 -16
  55. package/build/services/criteria/module-parser.js.map +1 -1
  56. package/build/services/review/CodeSnippetService.d.ts +10 -0
  57. package/build/services/review/CodeSnippetService.d.ts.map +1 -0
  58. package/build/services/review/CodeSnippetService.js +54 -0
  59. package/build/services/review/CodeSnippetService.js.map +1 -0
  60. package/build/services/review/ReviewInputResolverService.d.ts +25 -0
  61. package/build/services/review/ReviewInputResolverService.d.ts.map +1 -0
  62. package/build/services/review/ReviewInputResolverService.js +106 -0
  63. package/build/services/review/ReviewInputResolverService.js.map +1 -0
  64. package/build/services/review/ReviewOrchestratorService.d.ts +2 -2
  65. package/build/services/review/ReviewOrchestratorService.d.ts.map +1 -1
  66. package/build/services/review/ReviewOrchestratorService.js +28 -27
  67. package/build/services/review/ReviewOrchestratorService.js.map +1 -1
  68. package/build/services/review/ReviewPartitionerService.d.ts +46 -0
  69. package/build/services/review/ReviewPartitionerService.d.ts.map +1 -0
  70. package/build/services/review/ReviewPartitionerService.js +208 -0
  71. package/build/services/review/ReviewPartitionerService.js.map +1 -0
  72. package/build/services/review/ReviewPlanBuilderService.d.ts +25 -7
  73. package/build/services/review/ReviewPlanBuilderService.d.ts.map +1 -1
  74. package/build/services/review/ReviewPlanBuilderService.js +88 -30
  75. package/build/services/review/ReviewPlanBuilderService.js.map +1 -1
  76. package/build/services/review/ReviewStorageService.d.ts +35 -11
  77. package/build/services/review/ReviewStorageService.d.ts.map +1 -1
  78. package/build/services/review/ReviewStorageService.js +136 -35
  79. package/build/services/review/ReviewStorageService.js.map +1 -1
  80. package/build/services/testing/TestRunnerService.d.ts.map +1 -1
  81. package/build/services/testing/TestRunnerService.js +10 -8
  82. package/build/services/testing/TestRunnerService.js.map +1 -1
  83. package/build/types/criteria.d.ts +8 -6
  84. package/build/types/criteria.d.ts.map +1 -1
  85. package/build/types/review.d.ts +173 -50
  86. package/build/types/review.d.ts.map +1 -1
  87. package/package.json +3 -1
  88. package/ui/dist/index.html +12 -63
  89. package/build/prompts/PlannerPrompt.d.ts +0 -12
  90. package/build/prompts/PlannerPrompt.d.ts.map +0 -1
  91. package/build/prompts/PlannerPrompt.js +0 -34
  92. package/build/prompts/PlannerPrompt.js.map +0 -1
  93. package/build/services/review/ReviewContextExtractorService.d.ts +0 -17
  94. package/build/services/review/ReviewContextExtractorService.d.ts.map +0 -1
  95. package/build/services/review/ReviewContextExtractorService.js +0 -69
  96. package/build/services/review/ReviewContextExtractorService.js.map +0 -1
  97. package/build/services/review/ReviewPlannerService.d.ts +0 -29
  98. package/build/services/review/ReviewPlannerService.d.ts.map +0 -1
  99. package/build/services/review/ReviewPlannerService.js +0 -122
  100. 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 modules,
4
- * generate tasks, and finalize.
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
- * This is the single authoritative implementation of plan+task creation,
7
- * shared by both the planner agent's in-process MCP tool and any other
8
- * entry point that needs to create a plan from a file list.
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, source, files, modules) {
11
- // Create the plan shell
12
- const plan = storage.createPlan(name, source);
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
- // Create tasks based on module mode
39
- for (const match of matches) {
40
- const isGrouped = match.module.mode.toLowerCase().includes("grouped") ||
41
- match.module.mode.toLowerCase().includes("all files");
42
- if (isGrouped) {
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: match.matchedFiles,
47
- hint: null,
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
- return storage.finalizePlan(plan.plan_id);
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":"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"}
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 { ContextType, Finding, ModuleSummary, ReviewPlan, ReviewResults, ReviewSource, ReviewTask, TaskUsage } from "../../types/review.js";
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, source: ReviewSource): ReviewPlan;
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" | "context" | "context_type" | "symbol" | "prompt">): ReviewTask;
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", fills in the context fields
64
- * (context type, content, symbol, prompt), and records the start time.
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, context: {
67
- contextType: ContextType;
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, findings: Finding[], usage?: TaskUsage | null): void;
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,OAAO,EAGP,aAAa,EACb,UAAU,EACV,aAAa,EACb,YAAY,EACZ,UAAU,EAEV,SAAS,EAEV,MAAM,uBAAuB,CAAC;AAsC/B;;;;;;GAMG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IAEpC,+EAA+E;IAC/E,OAAO,CAAC,MAAM,CAAC,WAAW,CAAqB;gBAanC,UAAU,EAAE,MAAM;IAI9B;;;;;;;;OAQG;IACH,OAAO,CAAC,QAAQ;IAoDhB,yCAAyC;IACzC,OAAO,CAAC,OAAO;IAQf;;;;;OAKG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,UAAU;IA0B1D,8CAA8C;IAC9C,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU;IAMnC;;;OAGG;IACH,eAAe,IAAI,MAAM,GAAG,IAAI;IAsBhC,0CAA0C;IAC1C,SAAS,IAAI,KAAK,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IAqCF;;;OAGG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU;IAsBxC;;;;;;OAMG;IACH,OAAO,CACL,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,IAAI,CACR,UAAU,EACR,SAAS,GACT,QAAQ,GACR,YAAY,GACZ,YAAY,GACZ,cAAc,GACd,SAAS,GACT,cAAc,GACd,QAAQ,GACR,QAAQ,CACX,GACA,UAAU;IAkCb;;;;;OAKG;IACH,SAAS,CACP,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,OAAO,EAAE;QACP,WAAW,EAAE,WAAW,CAAC;QACzB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;KAChB,GACA,UAAU;IA6Bb;;;OAGG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,EAAE;IAkB7C;;;;;;OAMG;IACH,YAAY,CACV,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,CAAC,EAAE,SAAS,GAAG,IAAI,GACvB,IAAI;IAiDP;;;;;;OAMG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI;IA8C/F,iDAAiD;IACjD,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa;IAgBzC,4DAA4D;IAC5D,eAAe,CACb,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EAAE,EACjB,SAAS,EAAE,MAAM,EAAE,GAClB,IAAI;IASP,gDAAgD;IAChD,UAAU,CACR,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,GACrC,IAAI;IAYP,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,kBAAkB;IAkC1B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAW3B;;;;;;;OAOG;IACH,OAAO,CAAC,qBAAqB;CAkH9B"}
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, source) {
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
- source,
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
- context_type: plan.source.type,
250
- context: null,
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", fills in the context fields
263
- * (context type, content, symbol, prompt), and records the start time.
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, context) {
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
- task.context_type = context.contextType;
275
- task.context = context.content;
276
- task.symbol = context.symbol ?? null;
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, findings, usage) {
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
- findings,
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
- findings: [],
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
- // ---- Summary (aggregate finding counts) ----
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 finding of taskResult.findings) {
601
+ for (const issue of taskResult.issues) {
509
602
  summary.total++;
510
- summary[finding.severity]++;
603
+ summary[issue.severity]++;
511
604
  }
512
605
  }
513
606
  results.summary = summary;
514
- // ---- by_file (group all findings by file path) ----
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 finding of taskResult.findings) {
518
- const fileFinding = {
519
- ...finding,
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
- if (!byFile[finding.file]) {
524
- byFile[finding.file] = [];
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 findings by criterion) ----
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
- findings: [],
645
+ issues: [],
545
646
  };
546
647
  }
547
- const moduleFindings = byModule[reviewId];
548
- moduleFindings.completed++;
549
- for (const finding of taskResult.findings) {
550
- moduleFindings.counts.total++;
551
- moduleFindings.counts[finding.severity]++;
552
- moduleFindings.findings.push(finding);
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;