@hstm-labs/forge-reviewer 0.1.11

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 ADDED
@@ -0,0 +1,43 @@
1
+ # @hstm-labs/forge-reviewer
2
+
3
+ Multi-pass AI code review stage for Forge — performs file-level, cross-file, spec-alignment, and synthesis review passes across all generated artifacts, producing structured findings with severity and remediation guidance.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @hstm-labs/forge-reviewer
9
+ ```
10
+
11
+ ## Public API
12
+
13
+ ### Types
14
+
15
+ - `CodeReviewReport` — complete review output with findings and summary
16
+ - `CodeReviewFinding` — individual finding with file, line, severity, remediation
17
+ - `ReviewDimension` — review category (correctness, security, performance, etc.)
18
+ - `ReviewPassResult` — output from a single review pass
19
+ - `ReviewFile` — file metadata for review input
20
+
21
+ ### Classes
22
+
23
+ - `ReviewStage` — pipeline stage implementing `PipelineStage` interface (4 LLM calls)
24
+ - `ReviewPassValidator` — validates individual review pass output
25
+ - `ReviewSynthesisValidator` — validates final synthesis output
26
+
27
+ ### Functions
28
+
29
+ - `collectReviewFiles(artifactsDir)` — gather generated files for review input
30
+
31
+ ## Usage
32
+
33
+ ```typescript
34
+ import { ReviewStage } from '@hstm-labs/forge-reviewer';
35
+
36
+ const stage = new ReviewStage();
37
+ const result = await stage.execute(input);
38
+ // result.data contains CodeReviewReport with findings from all 4 passes
39
+ ```
40
+
41
+ ## License
42
+
43
+ [MIT](../../LICENSE)
@@ -0,0 +1,36 @@
1
+ /**
2
+ * File collection helper for the AI code review stage.
3
+ *
4
+ * Extracts generated source code files from prior stage outputs
5
+ * into a flat list for review by the LLM.
6
+ */
7
+ import type { ApiArtifact } from '@hstm-labs/forge-services-generator';
8
+ import type { UiArtifact } from '@hstm-labs/forge-apps-generator';
9
+ import type { SecurityArtifact } from '@hstm-labs/forge-security-generator';
10
+ import type { InfraArtifact } from '@hstm-labs/forge-infra-generator';
11
+ /** A file collected for review from a prior generation stage. */
12
+ export interface ReviewFile {
13
+ /** File name or relative path. */
14
+ fileName: string;
15
+ /** File content (source code). */
16
+ content: string;
17
+ /** Which generation stage produced this file. */
18
+ stage: string;
19
+ }
20
+ /**
21
+ * Collect all generated source files from prior stage outputs for review.
22
+ *
23
+ * Source files are extracted from:
24
+ * - API: handlers, types
25
+ * - UI: pages, components, layouts, forms, navigation
26
+ * - Security: authModel, rbac, middleware, inputValidation, securityHeaders
27
+ * - Infra: containers, compose, ciPipeline
28
+ *
29
+ * @param api - API artifact (may be undefined)
30
+ * @param ui - UI artifact (may be undefined)
31
+ * @param security - Security artifact (may be undefined)
32
+ * @param infra - Infra artifact (may be undefined)
33
+ * @returns Array of review files
34
+ */
35
+ export declare function collectReviewFiles(api: ApiArtifact | undefined, ui: UiArtifact | undefined, security: SecurityArtifact | undefined, infra: InfraArtifact | undefined): ReviewFile[];
36
+ //# sourceMappingURL=collect-review-files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collect-review-files.d.ts","sourceRoot":"","sources":["../src/collect-review-files.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAC;AACvE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;AAC5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAMtE,iEAAiE;AACjE,MAAM,WAAW,UAAU;IACzB,kCAAkC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;CACf;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,WAAW,GAAG,SAAS,EAC5B,EAAE,EAAE,UAAU,GAAG,SAAS,EAC1B,QAAQ,EAAE,gBAAgB,GAAG,SAAS,EACtC,KAAK,EAAE,aAAa,GAAG,SAAS,GAC/B,UAAU,EAAE,CAyDd"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * File collection helper for the AI code review stage.
3
+ *
4
+ * Extracts generated source code files from prior stage outputs
5
+ * into a flat list for review by the LLM.
6
+ */
7
+ // ---------------------------------------------------------------------------
8
+ // Collector
9
+ // ---------------------------------------------------------------------------
10
+ /**
11
+ * Collect all generated source files from prior stage outputs for review.
12
+ *
13
+ * Source files are extracted from:
14
+ * - API: handlers, types
15
+ * - UI: pages, components, layouts, forms, navigation
16
+ * - Security: authModel, rbac, middleware, inputValidation, securityHeaders
17
+ * - Infra: containers, compose, ciPipeline
18
+ *
19
+ * @param api - API artifact (may be undefined)
20
+ * @param ui - UI artifact (may be undefined)
21
+ * @param security - Security artifact (may be undefined)
22
+ * @param infra - Infra artifact (may be undefined)
23
+ * @returns Array of review files
24
+ */
25
+ export function collectReviewFiles(api, ui, security, infra) {
26
+ const files = [];
27
+ // API: handlers + types
28
+ if (api !== undefined) {
29
+ for (const handler of api.handlers) {
30
+ files.push({ fileName: handler.fileName, content: handler.content, stage: 'services-generate' });
31
+ }
32
+ for (const typeDef of api.types) {
33
+ files.push({ fileName: typeDef.fileName, content: typeDef.content, stage: 'services-generate' });
34
+ }
35
+ }
36
+ // UI: pages, components, layouts, forms, navigation
37
+ if (ui !== undefined) {
38
+ for (const page of ui.pages) {
39
+ files.push({ fileName: page.fileName, content: page.content, stage: 'apps-generate' });
40
+ }
41
+ for (const component of ui.components) {
42
+ files.push({ fileName: component.fileName, content: component.content, stage: 'apps-generate' });
43
+ }
44
+ for (const layout of ui.layouts) {
45
+ files.push({ fileName: layout.fileName, content: layout.content, stage: 'apps-generate' });
46
+ }
47
+ for (const form of ui.forms) {
48
+ files.push({ fileName: form.fileName, content: form.content, stage: 'apps-generate' });
49
+ }
50
+ files.push({ fileName: ui.navigation.fileName, content: ui.navigation.content, stage: 'apps-generate' });
51
+ }
52
+ // Security: authModel, rbac, middleware, inputValidation, securityHeaders
53
+ if (security !== undefined) {
54
+ files.push({ fileName: security.authModel.fileName, content: security.authModel.content, stage: 'security-generate' });
55
+ files.push({ fileName: security.rbac.fileName, content: security.rbac.content, stage: 'security-generate' });
56
+ for (const mw of security.middleware) {
57
+ files.push({ fileName: mw.fileName, content: mw.content, stage: 'security-generate' });
58
+ }
59
+ for (const rule of security.inputValidation) {
60
+ files.push({ fileName: rule.fileName, content: rule.content, stage: 'security-generate' });
61
+ }
62
+ files.push({ fileName: security.securityHeaders.fileName, content: security.securityHeaders.content, stage: 'security-generate' });
63
+ }
64
+ // Infra: containers, compose, ciPipeline
65
+ if (infra !== undefined) {
66
+ for (const container of infra.containers) {
67
+ files.push({ fileName: container.fileName, content: container.content, stage: 'infra-generate' });
68
+ }
69
+ if (infra.compose !== undefined && infra.compose !== null) {
70
+ files.push({ fileName: infra.compose.fileName, content: infra.compose.content, stage: 'infra-generate' });
71
+ }
72
+ if (infra.ciPipeline !== undefined && infra.ciPipeline !== null) {
73
+ files.push({ fileName: infra.ciPipeline.fileName, content: infra.ciPipeline.content, stage: 'infra-generate' });
74
+ }
75
+ }
76
+ return files;
77
+ }
78
+ //# sourceMappingURL=collect-review-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collect-review-files.js","sourceRoot":"","sources":["../src/collect-review-files.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @hstm-labs/forge-reviewer — Multi-pass AI code review stage for Forge.
3
+ *
4
+ * Provides the {@link ReviewStage} pipeline stage, the
5
+ * {@link ReviewPassValidator} and {@link ReviewSynthesisValidator},
6
+ * and the {@link CodeReviewReport} type family describing review output.
7
+ *
8
+ * @packageDocumentation
9
+ */
10
+ export type { CodeReviewReport, CodeReviewFinding, ReviewDimension, ReviewPassResult, } from './types.js';
11
+ export { ReviewStage } from './review-stage.js';
12
+ export { ReviewPassValidator, ReviewSynthesisValidator } from './validator.js';
13
+ export { collectReviewFiles } from './collect-review-files.js';
14
+ export type { ReviewFile } from './collect-review-files.js';
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,YAAY,EACV,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAC/E,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,YAAY,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @hstm-labs/forge-reviewer — Multi-pass AI code review stage for Forge.
3
+ *
4
+ * Provides the {@link ReviewStage} pipeline stage, the
5
+ * {@link ReviewPassValidator} and {@link ReviewSynthesisValidator},
6
+ * and the {@link CodeReviewReport} type family describing review output.
7
+ *
8
+ * @packageDocumentation
9
+ */
10
+ export { ReviewStage } from './review-stage.js';
11
+ export { ReviewPassValidator, ReviewSynthesisValidator } from './validator.js';
12
+ export { collectReviewFiles } from './collect-review-files.js';
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAC/E,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Multi-pass AI code review pipeline stage.
3
+ *
4
+ * Performs 4 LLM passes to analyze all generated artifacts:
5
+ * 1. **File-level pass** — individual file quality, security, best practices
6
+ * 2. **Cross-file pass** — import resolution, type consistency, contract alignment
7
+ * 3. **Spec-alignment pass** — requirements coverage, entity completeness
8
+ * 4. **Synthesis pass** — deduplication, severity validation, summary, pass/fail
9
+ *
10
+ * Each pass uses a shared system template but a distinct user prompt.
11
+ * The synthesis pass aggregates findings from passes 1-3.
12
+ *
13
+ * In agent mode, the pipeline runner intercepts before calling
14
+ * `execute()` and exports a prompt via {@link AgentStageExecutor}.
15
+ */
16
+ import type { StageName } from '@hstm-labs/forge-common';
17
+ import type { PipelineStage, PipelineStageInput, PipelineStageOutput, PipelineContext, AgentPromptContext } from '@hstm-labs/forge-core';
18
+ /**
19
+ * Pipeline stage that performs a multi-pass AI code review.
20
+ *
21
+ * Produces a {@link CodeReviewReport} with findings from 4 LLM passes.
22
+ * The report is written as `review-report.json` and stored in
23
+ * `data.review` in the pipeline output.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * const stage = new ReviewStage();
28
+ * const output = await stage.execute(input, context);
29
+ * const report = output.data?.review as CodeReviewReport;
30
+ * ```
31
+ */
32
+ export declare class ReviewStage implements PipelineStage {
33
+ readonly name: StageName;
34
+ readonly dependsOn: StageName[];
35
+ readonly requiresLLM = true;
36
+ /**
37
+ * Return rendered agent-mode prompt context using `review-agent.hbs`.
38
+ *
39
+ * @param input - Input from prior stages
40
+ * @param context - Pipeline context with config, workspace, and runId
41
+ * @returns Agent prompt context with rendered template and output schema
42
+ */
43
+ getAgentContext(input: PipelineStageInput, context: PipelineContext): AgentPromptContext;
44
+ /**
45
+ * Execute the multi-pass review stage.
46
+ *
47
+ * @param input - Input from all prior generation stages
48
+ * @param context - Pipeline context with config, workspace, and adapter
49
+ * @returns Stage output with review report artifact and CodeReviewReport in data
50
+ * @throws {ForgeError} FORGE-PIPE-003 if required dependency stage output is missing
51
+ * @throws {ForgeError} FORGE-PIPE-001 if adapter is missing
52
+ */
53
+ execute(input: PipelineStageInput, context: PipelineContext): Promise<PipelineStageOutput>;
54
+ /**
55
+ * Extract and validate all required dependency outputs.
56
+ *
57
+ * @param input - Pipeline stage input map
58
+ * @returns Structured dependency outputs
59
+ * @throws {ForgeError} if required dependencies are missing
60
+ */
61
+ private collectDependencies;
62
+ /**
63
+ * Parse findings array from LLM output JSON.
64
+ *
65
+ * @param content - Raw JSON string with `findings` key
66
+ * @returns Array of code review findings
67
+ */
68
+ private parseFindings;
69
+ }
70
+ //# sourceMappingURL=review-stage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review-stage.d.ts","sourceRoot":"","sources":["../src/review-stage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEzD,OAAO,KAAK,EACV,aAAa,EACb,kBAAkB,EAClB,mBAAmB,EACnB,eAAe,EAEf,kBAAkB,EACnB,MAAM,uBAAuB,CAAC;AA6B/B;;;;;;;;;;;;;GAaG;AACH,qBAAa,WAAY,YAAW,aAAa;IAC/C,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAY;IACpC,QAAQ,CAAC,SAAS,EAAE,SAAS,EAAE,CAO7B;IACF,QAAQ,CAAC,WAAW,QAAQ;IAE5B;;;;;;OAMG;IACH,eAAe,CACb,KAAK,EAAE,kBAAkB,EACzB,OAAO,EAAE,eAAe,GACvB,kBAAkB;IA+CrB;;;;;;;;OAQG;IACG,OAAO,CACX,KAAK,EAAE,kBAAkB,EACzB,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,mBAAmB,CAAC;IA2Q/B;;;;;;OAMG;IACH,OAAO,CAAC,mBAAmB;IA4G3B;;;;;OAKG;IACH,OAAO,CAAC,aAAa;CAItB"}
@@ -0,0 +1,364 @@
1
+ /**
2
+ * Multi-pass AI code review pipeline stage.
3
+ *
4
+ * Performs 4 LLM passes to analyze all generated artifacts:
5
+ * 1. **File-level pass** — individual file quality, security, best practices
6
+ * 2. **Cross-file pass** — import resolution, type consistency, contract alignment
7
+ * 3. **Spec-alignment pass** — requirements coverage, entity completeness
8
+ * 4. **Synthesis pass** — deduplication, severity validation, summary, pass/fail
9
+ *
10
+ * Each pass uses a shared system template but a distinct user prompt.
11
+ * The synthesis pass aggregates findings from passes 1-3.
12
+ *
13
+ * In agent mode, the pipeline runner intercepts before calling
14
+ * `execute()` and exports a prompt via {@link AgentStageExecutor}.
15
+ */
16
+ import { join, dirname } from 'node:path';
17
+ import { writeFileSync, mkdirSync } from 'node:fs';
18
+ import { ForgeError, ErrorCodes, hashContent } from '@hstm-labs/forge-common';
19
+ import { ApiStageExecutor } from '@hstm-labs/forge-core';
20
+ import { loadProfile } from '@hstm-labs/forge-profiles';
21
+ import { loadTemplate, renderTemplate } from '@hstm-labs/forge-templates';
22
+ import { ReviewPassValidator, ReviewSynthesisValidator } from './validator.js';
23
+ import { collectReviewFiles } from './collect-review-files.js';
24
+ // ---------------------------------------------------------------------------
25
+ // Constants
26
+ // ---------------------------------------------------------------------------
27
+ const RETRY_POLICY = {
28
+ maxRetries: 3,
29
+ backoffMs: 1000,
30
+ includeErrorInRetry: true,
31
+ };
32
+ // ---------------------------------------------------------------------------
33
+ // Stage
34
+ // ---------------------------------------------------------------------------
35
+ /**
36
+ * Pipeline stage that performs a multi-pass AI code review.
37
+ *
38
+ * Produces a {@link CodeReviewReport} with findings from 4 LLM passes.
39
+ * The report is written as `review-report.json` and stored in
40
+ * `data.review` in the pipeline output.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * const stage = new ReviewStage();
45
+ * const output = await stage.execute(input, context);
46
+ * const report = output.data?.review as CodeReviewReport;
47
+ * ```
48
+ */
49
+ export class ReviewStage {
50
+ name = 'review';
51
+ dependsOn = [
52
+ 'architect',
53
+ 'services-generate',
54
+ 'apps-generate',
55
+ 'infra-generate',
56
+ 'security-generate',
57
+ 'seed-data',
58
+ ];
59
+ requiresLLM = true;
60
+ /**
61
+ * Return rendered agent-mode prompt context using `review-agent.hbs`.
62
+ *
63
+ * @param input - Input from prior stages
64
+ * @param context - Pipeline context with config, workspace, and runId
65
+ * @returns Agent prompt context with rendered template and output schema
66
+ */
67
+ getAgentContext(input, context) {
68
+ const parsedSpec = input['validate']?.data?.['parsedSpec'];
69
+ const architecture = input['architect']?.data?.['architecture'];
70
+ const api = input['services-generate']?.data?.['api'];
71
+ const profile = loadProfile(context.config.profileName, context.workspace.rootDir);
72
+ const outputDir = join(context.workspace.forgeDir, 'runs', context.runId, 'stages', 'review', 'artifacts');
73
+ const agentTemplate = loadTemplate('review-agent', context.workspace.rootDir);
74
+ const rendered = renderTemplate(agentTemplate, {
75
+ spec: (parsedSpec ?? {}),
76
+ profile: profile,
77
+ architecture: (architecture ?? {}),
78
+ api: (api ?? {}),
79
+ stage: { outputDir },
80
+ });
81
+ return {
82
+ prompt: rendered.content,
83
+ outputSchema: {
84
+ format: 'json',
85
+ requiredFiles: ['review-report.json'],
86
+ },
87
+ };
88
+ }
89
+ /**
90
+ * Execute the multi-pass review stage.
91
+ *
92
+ * @param input - Input from all prior generation stages
93
+ * @param context - Pipeline context with config, workspace, and adapter
94
+ * @returns Stage output with review report artifact and CodeReviewReport in data
95
+ * @throws {ForgeError} FORGE-PIPE-003 if required dependency stage output is missing
96
+ * @throws {ForgeError} FORGE-PIPE-001 if adapter is missing
97
+ */
98
+ async execute(input, context) {
99
+ // 1. Collect all prior stage outputs
100
+ const { parsedSpec, architecture, api, ui, security, infra } = this.collectDependencies(input);
101
+ // 2. Require adapter
102
+ if (context.adapter === undefined) {
103
+ throw new ForgeError(ErrorCodes.PIPE.STAGE_FAILURE, 'Review stage requires an LLM adapter in API mode, but none was provided. ' +
104
+ 'Configure an LLM provider in forge.config.json or use agent mode.');
105
+ }
106
+ // 3. Load profile
107
+ const profile = loadProfile(context.config.profileName, context.workspace.rootDir);
108
+ // 4. Compute output directory
109
+ const outputDir = join(context.workspace.forgeDir, 'runs', context.runId, 'stages', 'review', 'artifacts');
110
+ // 5. Load shared system template
111
+ const systemTemplate = loadTemplate('review-system', context.workspace.rootDir);
112
+ const userTemplate = loadTemplate('review-user', context.workspace.rootDir);
113
+ const profileCtx = profile;
114
+ const systemPrompt = renderTemplate(systemTemplate, {
115
+ profile: profileCtx,
116
+ });
117
+ // 6. Collect review files
118
+ const reviewFiles = collectReviewFiles(api, ui, security, infra);
119
+ // 7. Shared template context for pass-specific templates
120
+ const specCtx = parsedSpec;
121
+ const archCtx = architecture;
122
+ const apiCtx = api;
123
+ const uiCtx = ui;
124
+ // -----------------------------------------------------------------------
125
+ // Pass 1: File-level review
126
+ // -----------------------------------------------------------------------
127
+ const filePassTemplate = loadTemplate('review-file-pass', context.workspace.rootDir);
128
+ const filePassRendered = renderTemplate(filePassTemplate, {
129
+ profile: profileCtx,
130
+ files: reviewFiles,
131
+ });
132
+ const filePassUserPrompt = renderTemplate(userTemplate, {
133
+ passPrompt: filePassRendered.content,
134
+ });
135
+ const passValidator = new ReviewPassValidator();
136
+ const filePassExecutor = new ApiStageExecutor({
137
+ adapter: context.adapter,
138
+ validator: passValidator,
139
+ retryPolicy: { ...RETRY_POLICY },
140
+ });
141
+ const filePassResult = await filePassExecutor.execute({
142
+ prompt: filePassUserPrompt.content,
143
+ systemPrompt: systemPrompt.content,
144
+ stageName: 'review',
145
+ outputDir,
146
+ runId: context.runId,
147
+ mode: 'api',
148
+ outputSchema: { format: 'json' },
149
+ });
150
+ const filePassFindings = this.parseFindings(filePassResult.artifacts[0]?.content ?? '{"findings":[]}');
151
+ // -----------------------------------------------------------------------
152
+ // Pass 2: Cross-file review
153
+ // -----------------------------------------------------------------------
154
+ const crossFileTemplate = loadTemplate('review-crossfile-pass', context.workspace.rootDir);
155
+ const crossFileRendered = renderTemplate(crossFileTemplate, {
156
+ api: apiCtx,
157
+ ui: uiCtx,
158
+ architecture: archCtx,
159
+ });
160
+ const crossFileUserPrompt = renderTemplate(userTemplate, {
161
+ passPrompt: crossFileRendered.content,
162
+ });
163
+ const crossFileExecutor = new ApiStageExecutor({
164
+ adapter: context.adapter,
165
+ validator: passValidator,
166
+ retryPolicy: { ...RETRY_POLICY },
167
+ });
168
+ const crossFileResult = await crossFileExecutor.execute({
169
+ prompt: crossFileUserPrompt.content,
170
+ systemPrompt: systemPrompt.content,
171
+ stageName: 'review',
172
+ outputDir,
173
+ runId: context.runId,
174
+ mode: 'api',
175
+ outputSchema: { format: 'json' },
176
+ });
177
+ const crossFileFindings = this.parseFindings(crossFileResult.artifacts[0]?.content ?? '{"findings":[]}');
178
+ // -----------------------------------------------------------------------
179
+ // Pass 3: Spec-alignment review
180
+ // -----------------------------------------------------------------------
181
+ const specAlignTemplate = loadTemplate('review-specalign-pass', context.workspace.rootDir);
182
+ const specAlignRendered = renderTemplate(specAlignTemplate, {
183
+ spec: specCtx,
184
+ architecture: archCtx,
185
+ api: apiCtx,
186
+ ui: uiCtx,
187
+ });
188
+ const specAlignUserPrompt = renderTemplate(userTemplate, {
189
+ passPrompt: specAlignRendered.content,
190
+ });
191
+ const specAlignExecutor = new ApiStageExecutor({
192
+ adapter: context.adapter,
193
+ validator: passValidator,
194
+ retryPolicy: { ...RETRY_POLICY },
195
+ });
196
+ const specAlignResult = await specAlignExecutor.execute({
197
+ prompt: specAlignUserPrompt.content,
198
+ systemPrompt: systemPrompt.content,
199
+ stageName: 'review',
200
+ outputDir,
201
+ runId: context.runId,
202
+ mode: 'api',
203
+ outputSchema: { format: 'json' },
204
+ });
205
+ const specAlignFindings = this.parseFindings(specAlignResult.artifacts[0]?.content ?? '{"findings":[]}');
206
+ // -----------------------------------------------------------------------
207
+ // Aggregate and renumber findings from passes 1-3
208
+ // -----------------------------------------------------------------------
209
+ const allPreviousFindings = [
210
+ ...filePassFindings,
211
+ ...crossFileFindings,
212
+ ...specAlignFindings,
213
+ ];
214
+ const renumberedFindings = allPreviousFindings.map((f, idx) => ({
215
+ ...f,
216
+ id: `REVIEW-${String(idx + 1).padStart(3, '0')}`,
217
+ }));
218
+ // -----------------------------------------------------------------------
219
+ // Pass 4: Synthesis
220
+ // -----------------------------------------------------------------------
221
+ const synthesisTemplate = loadTemplate('review-synthesis-pass', context.workspace.rootDir);
222
+ const synthesisRendered = renderTemplate(synthesisTemplate, {
223
+ previousFindings: renumberedFindings,
224
+ });
225
+ const synthesisUserPrompt = renderTemplate(userTemplate, {
226
+ passPrompt: synthesisRendered.content,
227
+ });
228
+ const synthesisValidator = new ReviewSynthesisValidator();
229
+ const synthesisExecutor = new ApiStageExecutor({
230
+ adapter: context.adapter,
231
+ validator: synthesisValidator,
232
+ retryPolicy: { ...RETRY_POLICY },
233
+ });
234
+ const synthesisResult = await synthesisExecutor.execute({
235
+ prompt: synthesisUserPrompt.content,
236
+ systemPrompt: systemPrompt.content,
237
+ stageName: 'review',
238
+ outputDir,
239
+ runId: context.runId,
240
+ mode: 'api',
241
+ outputSchema: { format: 'json' },
242
+ });
243
+ const synthesisData = JSON.parse(synthesisResult.artifacts[0]?.content ?? '{"findings":[],"summary":"","passOrFail":"pass"}');
244
+ // -----------------------------------------------------------------------
245
+ // Build CodeReviewReport
246
+ // -----------------------------------------------------------------------
247
+ const uniqueFiles = new Set(reviewFiles.map((f) => f.fileName));
248
+ const report = {
249
+ runId: context.runId,
250
+ reviewedAt: new Date().toISOString(),
251
+ totalFiles: uniqueFiles.size,
252
+ totalFindings: synthesisData.findings.length,
253
+ findingsBySeverity: {
254
+ critical: synthesisData.findings.filter((f) => f.severity === 'critical').length,
255
+ warning: synthesisData.findings.filter((f) => f.severity === 'warning').length,
256
+ suggestion: synthesisData.findings.filter((f) => f.severity === 'suggestion').length,
257
+ },
258
+ findings: synthesisData.findings,
259
+ summary: synthesisData.summary,
260
+ passOrFail: synthesisData.passOrFail,
261
+ };
262
+ // -----------------------------------------------------------------------
263
+ // Write report
264
+ // -----------------------------------------------------------------------
265
+ const reportJson = JSON.stringify(report, null, 2);
266
+ const reportPath = 'review-report.json';
267
+ const fullPath = join(outputDir, reportPath);
268
+ mkdirSync(dirname(fullPath), { recursive: true });
269
+ writeFileSync(fullPath, reportJson, 'utf-8');
270
+ const artifacts = [
271
+ {
272
+ filePath: reportPath,
273
+ content: reportJson,
274
+ contentHash: hashContent(reportJson),
275
+ sizeBytes: Buffer.byteLength(reportJson, 'utf-8'),
276
+ },
277
+ ];
278
+ return {
279
+ artifacts,
280
+ data: {
281
+ review: report,
282
+ },
283
+ };
284
+ }
285
+ // -------------------------------------------------------------------------
286
+ // Private helpers
287
+ // -------------------------------------------------------------------------
288
+ /**
289
+ * Extract and validate all required dependency outputs.
290
+ *
291
+ * @param input - Pipeline stage input map
292
+ * @returns Structured dependency outputs
293
+ * @throws {ForgeError} if required dependencies are missing
294
+ */
295
+ collectDependencies(input) {
296
+ // Validate stage
297
+ const validateOutput = input['validate'];
298
+ if (validateOutput === undefined) {
299
+ throw new ForgeError(ErrorCodes.PIPE.DEPENDENCY_UNMET, "Review stage requires 'validate' stage output, but it was not found. " +
300
+ 'Ensure the validate stage runs before the review stage.');
301
+ }
302
+ const parsedSpec = validateOutput.data?.['parsedSpec'];
303
+ if (parsedSpec === undefined) {
304
+ throw new ForgeError(ErrorCodes.PIPE.STAGE_FAILURE, 'Validate stage did not produce a parsed specification in its output data.');
305
+ }
306
+ // Architect stage
307
+ const architectOutput = input['architect'];
308
+ if (architectOutput === undefined) {
309
+ throw new ForgeError(ErrorCodes.PIPE.DEPENDENCY_UNMET, "Review stage requires 'architect' stage output, but it was not found.");
310
+ }
311
+ const architecture = architectOutput.data?.['architecture'];
312
+ if (architecture === undefined) {
313
+ throw new ForgeError(ErrorCodes.PIPE.STAGE_FAILURE, 'Architect stage did not produce an architecture in its output data.');
314
+ }
315
+ // API generate stage
316
+ const apiOutput = input['services-generate'];
317
+ if (apiOutput === undefined) {
318
+ throw new ForgeError(ErrorCodes.PIPE.DEPENDENCY_UNMET, "Review stage requires 'services-generate' stage output, but it was not found.");
319
+ }
320
+ const api = apiOutput.data?.['api'];
321
+ if (api === undefined) {
322
+ throw new ForgeError(ErrorCodes.PIPE.STAGE_FAILURE, 'API generate stage did not produce an API artifact in its output data.');
323
+ }
324
+ // UI generate stage
325
+ const uiOutput = input['apps-generate'];
326
+ if (uiOutput === undefined) {
327
+ throw new ForgeError(ErrorCodes.PIPE.DEPENDENCY_UNMET, "Review stage requires 'apps-generate' stage output, but it was not found.");
328
+ }
329
+ const ui = uiOutput.data?.['ui'];
330
+ if (ui === undefined) {
331
+ throw new ForgeError(ErrorCodes.PIPE.STAGE_FAILURE, 'UI generate stage did not produce a UI artifact in its output data.');
332
+ }
333
+ // Security generate stage
334
+ const securityOutput = input['security-generate'];
335
+ if (securityOutput === undefined) {
336
+ throw new ForgeError(ErrorCodes.PIPE.DEPENDENCY_UNMET, "Review stage requires 'security-generate' stage output, but it was not found.");
337
+ }
338
+ const security = securityOutput.data?.['security'];
339
+ if (security === undefined) {
340
+ throw new ForgeError(ErrorCodes.PIPE.STAGE_FAILURE, 'Security generate stage did not produce a security artifact in its output data.');
341
+ }
342
+ // Infra generate stage
343
+ const infraOutput = input['infra-generate'];
344
+ if (infraOutput === undefined) {
345
+ throw new ForgeError(ErrorCodes.PIPE.DEPENDENCY_UNMET, "Review stage requires 'infra-generate' stage output, but it was not found.");
346
+ }
347
+ const infra = infraOutput.data?.['infra'];
348
+ if (infra === undefined) {
349
+ throw new ForgeError(ErrorCodes.PIPE.STAGE_FAILURE, 'Infra generate stage did not produce an infra artifact in its output data.');
350
+ }
351
+ return { parsedSpec, architecture, api, ui, security, infra };
352
+ }
353
+ /**
354
+ * Parse findings array from LLM output JSON.
355
+ *
356
+ * @param content - Raw JSON string with `findings` key
357
+ * @returns Array of code review findings
358
+ */
359
+ parseFindings(content) {
360
+ const parsed = JSON.parse(content);
361
+ return parsed.findings;
362
+ }
363
+ }
364
+ //# sourceMappingURL=review-stage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review-stage.js","sourceRoot":"","sources":["../src/review-stage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Code review artifact type definitions for the Forge review stage.
3
+ *
4
+ * Defines the multi-pass review output schema including individual
5
+ * findings, the aggregated report, and per-pass result structure.
6
+ */
7
+ /** Review dimensions checked by the AI code review stage. */
8
+ export type ReviewDimension = 'code-quality' | 'security' | 'performance' | 'best-practices' | 'consistency' | 'architecture' | 'spec-adherence';
9
+ /** A single code review finding. */
10
+ export interface CodeReviewFinding {
11
+ /** Finding ID: REVIEW-{NNN}. */
12
+ id: string;
13
+ /** Severity level. */
14
+ severity: 'critical' | 'warning' | 'suggestion';
15
+ /** Review dimension. */
16
+ dimension: ReviewDimension;
17
+ /** Relative path to reviewed file. */
18
+ file: string;
19
+ /** Line number if applicable. */
20
+ line?: number | undefined;
21
+ /** One-line summary. */
22
+ title: string;
23
+ /** Detailed explanation. */
24
+ description: string;
25
+ /** Code snippet with suggested correction. */
26
+ suggestedFix?: string | undefined;
27
+ /** Requirement ID if spec-related. */
28
+ specReference?: string | undefined;
29
+ }
30
+ /** Complete code review report produced by the review stage. */
31
+ export interface CodeReviewReport {
32
+ /** Run ID. */
33
+ runId: string;
34
+ /** ISO timestamp of review. */
35
+ reviewedAt: string;
36
+ /** Total files reviewed. */
37
+ totalFiles: number;
38
+ /** Total findings. */
39
+ totalFindings: number;
40
+ /** Findings broken down by severity. */
41
+ findingsBySeverity: {
42
+ critical: number;
43
+ warning: number;
44
+ suggestion: number;
45
+ };
46
+ /** All findings. */
47
+ findings: CodeReviewFinding[];
48
+ /** LLM-generated natural language summary. */
49
+ summary: string;
50
+ /** Pass if no critical findings; fail if any critical findings. */
51
+ passOrFail: 'pass' | 'fail';
52
+ }
53
+ /** Raw findings from a single review pass (what the LLM returns). */
54
+ export interface ReviewPassResult {
55
+ /** Pass name. */
56
+ pass: 'file-level' | 'cross-file' | 'spec-alignment' | 'synthesis';
57
+ /** Findings from this pass. */
58
+ findings: CodeReviewFinding[];
59
+ }
60
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,6DAA6D;AAC7D,MAAM,MAAM,eAAe,GACvB,cAAc,GACd,UAAU,GACV,aAAa,GACb,gBAAgB,GAChB,aAAa,GACb,cAAc,GACd,gBAAgB,CAAC;AAMrB,oCAAoC;AACpC,MAAM,WAAW,iBAAiB;IAChC,gCAAgC;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,sBAAsB;IACtB,QAAQ,EAAE,UAAU,GAAG,SAAS,GAAG,YAAY,CAAC;IAChD,wBAAwB;IACxB,SAAS,EAAE,eAAe,CAAC;IAC3B,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,wBAAwB;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,8CAA8C;IAC9C,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,sCAAsC;IACtC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC;AAMD,gEAAgE;AAChE,MAAM,WAAW,gBAAgB;IAC/B,cAAc;IACd,KAAK,EAAE,MAAM,CAAC;IACd,+BAA+B;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,4BAA4B;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,sBAAsB;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,wCAAwC;IACxC,kBAAkB,EAAE;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,oBAAoB;IACpB,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;CAC7B;AAMD,qEAAqE;AACrE,MAAM,WAAW,gBAAgB;IAC/B,iBAAiB;IACjB,IAAI,EAAE,YAAY,GAAG,YAAY,GAAG,gBAAgB,GAAG,WAAW,CAAC;IACnE,+BAA+B;IAC/B,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B"}
package/dist/types.js ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Code review artifact type definitions for the Forge review stage.
3
+ *
4
+ * Defines the multi-pass review output schema including individual
5
+ * findings, the aggregated report, and per-pass result structure.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Code review output validators for the Forge review stage.
3
+ *
4
+ * Two validators are provided:
5
+ * - {@link ReviewPassValidator} — validates individual pass output (file-level, cross-file, spec-alignment)
6
+ * - {@link ReviewSynthesisValidator} — validates the synthesis pass output (aggregated report)
7
+ */
8
+ import type { OutputValidator, OutputSchema, ValidationResult } from '@hstm-labs/forge-core';
9
+ /**
10
+ * Validates individual review pass output from the LLM.
11
+ *
12
+ * Checks:
13
+ * 1. JSON parse validation
14
+ * 2. `findings` key is an array
15
+ * 3. Each finding has required fields: id, severity, dimension, file, title, description
16
+ * 4. `severity` is one of: critical, warning, suggestion
17
+ * 5. `dimension` is a valid ReviewDimension
18
+ * 6. `id` matches REVIEW-{NNN} pattern
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * const validator = new ReviewPassValidator();
23
+ * const result = validator.validate(llmOutput, { format: 'json' });
24
+ * ```
25
+ */
26
+ export declare class ReviewPassValidator implements OutputValidator {
27
+ /**
28
+ * Validate a single review pass output.
29
+ *
30
+ * @param output - Raw LLM output text
31
+ * @param _schema - Output schema (format expected to be 'json')
32
+ * @returns Validation result with descriptive errors
33
+ */
34
+ validate(output: string, _schema: OutputSchema): ValidationResult;
35
+ }
36
+ /**
37
+ * Validates the synthesis pass output from the LLM.
38
+ *
39
+ * Checks:
40
+ * 1. JSON parse validation
41
+ * 2. `findings` array, `summary` string, `passOrFail` string
42
+ * 3. `passOrFail` is 'pass' or 'fail'
43
+ * 4. Consistency: if any finding has severity 'critical', passOrFail must be 'fail'
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * const validator = new ReviewSynthesisValidator();
48
+ * const result = validator.validate(llmOutput, { format: 'json' });
49
+ * ```
50
+ */
51
+ export declare class ReviewSynthesisValidator implements OutputValidator {
52
+ /**
53
+ * Validate the synthesis pass output.
54
+ *
55
+ * @param output - Raw LLM output text
56
+ * @param _schema - Output schema (format expected to be 'json')
57
+ * @returns Validation result with descriptive errors
58
+ */
59
+ validate(output: string, _schema: OutputSchema): ValidationResult;
60
+ }
61
+ //# sourceMappingURL=validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EACZ,gBAAgB,EAEjB,MAAM,uBAAuB,CAAC;AA0B/B;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,mBAAoB,YAAW,eAAe;IACzD;;;;;;OAMG;IAEH,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,gBAAgB;CA0FlE;AAMD;;;;;;;;;;;;;;GAcG;AACH,qBAAa,wBAAyB,YAAW,eAAe;IAC9D;;;;;;OAMG;IAEH,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,gBAAgB;CAuElE"}
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Code review output validators for the Forge review stage.
3
+ *
4
+ * Two validators are provided:
5
+ * - {@link ReviewPassValidator} — validates individual pass output (file-level, cross-file, spec-alignment)
6
+ * - {@link ReviewSynthesisValidator} — validates the synthesis pass output (aggregated report)
7
+ */
8
+ // ---------------------------------------------------------------------------
9
+ // Constants
10
+ // ---------------------------------------------------------------------------
11
+ const VALID_SEVERITIES = new Set(['critical', 'warning', 'suggestion']);
12
+ const VALID_DIMENSIONS = new Set([
13
+ 'code-quality',
14
+ 'security',
15
+ 'performance',
16
+ 'best-practices',
17
+ 'consistency',
18
+ 'architecture',
19
+ 'spec-adherence',
20
+ ]);
21
+ const FINDING_ID_PATTERN = /^REVIEW-\d{3,}$/;
22
+ // ---------------------------------------------------------------------------
23
+ // ReviewPassValidator
24
+ // ---------------------------------------------------------------------------
25
+ /**
26
+ * Validates individual review pass output from the LLM.
27
+ *
28
+ * Checks:
29
+ * 1. JSON parse validation
30
+ * 2. `findings` key is an array
31
+ * 3. Each finding has required fields: id, severity, dimension, file, title, description
32
+ * 4. `severity` is one of: critical, warning, suggestion
33
+ * 5. `dimension` is a valid ReviewDimension
34
+ * 6. `id` matches REVIEW-{NNN} pattern
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * const validator = new ReviewPassValidator();
39
+ * const result = validator.validate(llmOutput, { format: 'json' });
40
+ * ```
41
+ */
42
+ export class ReviewPassValidator {
43
+ /**
44
+ * Validate a single review pass output.
45
+ *
46
+ * @param output - Raw LLM output text
47
+ * @param _schema - Output schema (format expected to be 'json')
48
+ * @returns Validation result with descriptive errors
49
+ */
50
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
51
+ validate(output, _schema) {
52
+ const errors = [];
53
+ // 1. Parse as JSON
54
+ let parsed;
55
+ try {
56
+ parsed = JSON.parse(output);
57
+ }
58
+ catch {
59
+ errors.push({
60
+ message: 'Review pass output is not valid JSON. Ensure the LLM produces only raw JSON without markdown fences or explanatory text.',
61
+ severity: 'error',
62
+ });
63
+ return { valid: false, errors };
64
+ }
65
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
66
+ errors.push({
67
+ message: 'Review pass output must be a JSON object, not an array or primitive.',
68
+ severity: 'error',
69
+ });
70
+ return { valid: false, errors };
71
+ }
72
+ const obj = parsed;
73
+ // 2. Verify findings key is an array
74
+ const findings = obj['findings'];
75
+ if (!Array.isArray(findings)) {
76
+ errors.push({
77
+ message: "Key 'findings' must be an array of finding objects.",
78
+ path: 'findings',
79
+ severity: 'error',
80
+ });
81
+ return { valid: false, errors };
82
+ }
83
+ // 3-6. Validate each finding
84
+ for (let i = 0; i < findings.length; i++) {
85
+ const f = findings[i];
86
+ if (f === undefined || typeof f !== 'object' || f === null) {
87
+ errors.push({
88
+ message: `findings[${String(i)}] is not a valid object.`,
89
+ path: `findings[${String(i)}]`,
90
+ severity: 'error',
91
+ });
92
+ continue;
93
+ }
94
+ // 3. Required fields
95
+ for (const field of ['id', 'severity', 'dimension', 'file', 'title', 'description']) {
96
+ if (typeof f[field] !== 'string' || f[field].length === 0) {
97
+ errors.push({
98
+ message: `findings[${String(i)}] is missing required field '${field}'.`,
99
+ path: `findings[${String(i)}].${field}`,
100
+ severity: 'error',
101
+ });
102
+ }
103
+ }
104
+ // 4. Valid severity
105
+ if (typeof f['severity'] === 'string' && !VALID_SEVERITIES.has(f['severity'])) {
106
+ errors.push({
107
+ message: `findings[${String(i)}].severity '${f['severity']}' is invalid. Must be one of: critical, warning, suggestion.`,
108
+ path: `findings[${String(i)}].severity`,
109
+ severity: 'error',
110
+ });
111
+ }
112
+ // 5. Valid dimension
113
+ if (typeof f['dimension'] === 'string' && !VALID_DIMENSIONS.has(f['dimension'])) {
114
+ errors.push({
115
+ message: `findings[${String(i)}].dimension '${f['dimension']}' is invalid. Must be a valid ReviewDimension.`,
116
+ path: `findings[${String(i)}].dimension`,
117
+ severity: 'error',
118
+ });
119
+ }
120
+ // 6. ID pattern
121
+ if (typeof f['id'] === 'string' && !FINDING_ID_PATTERN.test(f['id'])) {
122
+ errors.push({
123
+ message: `findings[${String(i)}].id '${f['id']}' does not match REVIEW-{NNN} pattern.`,
124
+ path: `findings[${String(i)}].id`,
125
+ severity: 'error',
126
+ });
127
+ }
128
+ }
129
+ return { valid: errors.length === 0, errors };
130
+ }
131
+ }
132
+ // ---------------------------------------------------------------------------
133
+ // ReviewSynthesisValidator
134
+ // ---------------------------------------------------------------------------
135
+ /**
136
+ * Validates the synthesis pass output from the LLM.
137
+ *
138
+ * Checks:
139
+ * 1. JSON parse validation
140
+ * 2. `findings` array, `summary` string, `passOrFail` string
141
+ * 3. `passOrFail` is 'pass' or 'fail'
142
+ * 4. Consistency: if any finding has severity 'critical', passOrFail must be 'fail'
143
+ *
144
+ * @example
145
+ * ```ts
146
+ * const validator = new ReviewSynthesisValidator();
147
+ * const result = validator.validate(llmOutput, { format: 'json' });
148
+ * ```
149
+ */
150
+ export class ReviewSynthesisValidator {
151
+ /**
152
+ * Validate the synthesis pass output.
153
+ *
154
+ * @param output - Raw LLM output text
155
+ * @param _schema - Output schema (format expected to be 'json')
156
+ * @returns Validation result with descriptive errors
157
+ */
158
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
159
+ validate(output, _schema) {
160
+ const errors = [];
161
+ // 1. Parse as JSON
162
+ let parsed;
163
+ try {
164
+ parsed = JSON.parse(output);
165
+ }
166
+ catch {
167
+ errors.push({
168
+ message: 'Synthesis pass output is not valid JSON. Ensure the LLM produces only raw JSON without markdown fences or explanatory text.',
169
+ severity: 'error',
170
+ });
171
+ return { valid: false, errors };
172
+ }
173
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
174
+ errors.push({
175
+ message: 'Synthesis pass output must be a JSON object, not an array or primitive.',
176
+ severity: 'error',
177
+ });
178
+ return { valid: false, errors };
179
+ }
180
+ const obj = parsed;
181
+ // 2. Verify findings, summary, passOrFail
182
+ if (!Array.isArray(obj['findings'])) {
183
+ errors.push({
184
+ message: "Key 'findings' must be an array of finding objects.",
185
+ path: 'findings',
186
+ severity: 'error',
187
+ });
188
+ }
189
+ if (typeof obj['summary'] !== 'string' || obj['summary'].length === 0) {
190
+ errors.push({
191
+ message: "Key 'summary' must be a non-empty string.",
192
+ path: 'summary',
193
+ severity: 'error',
194
+ });
195
+ }
196
+ // 3. passOrFail is 'pass' or 'fail'
197
+ if (obj['passOrFail'] !== 'pass' && obj['passOrFail'] !== 'fail') {
198
+ errors.push({
199
+ message: "Key 'passOrFail' must be 'pass' or 'fail'.",
200
+ path: 'passOrFail',
201
+ severity: 'error',
202
+ });
203
+ }
204
+ if (errors.length > 0) {
205
+ return { valid: false, errors };
206
+ }
207
+ // 4. Consistency check: critical findings → passOrFail must be 'fail'
208
+ const findings = obj['findings'];
209
+ const hasCritical = findings.some((f) => f['severity'] === 'critical');
210
+ if (hasCritical && obj['passOrFail'] === 'pass') {
211
+ errors.push({
212
+ message: "Consistency error: findings contain critical severity issues but passOrFail is 'pass'. " +
213
+ "Any critical finding must result in passOrFail='fail'.",
214
+ path: 'passOrFail',
215
+ severity: 'error',
216
+ });
217
+ }
218
+ return { valid: errors.length === 0, errors };
219
+ }
220
+ }
221
+ //# sourceMappingURL=validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.js","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@hstm-labs/forge-reviewer",
3
+ "version": "0.1.11",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "test": "vitest run",
16
+ "prepublishOnly": "npm run build"
17
+ },
18
+ "dependencies": {
19
+ "@hstm-labs/forge-common": "0.1.11",
20
+ "@hstm-labs/forge-core": "0.1.11",
21
+ "@hstm-labs/forge-architect": "0.1.11",
22
+ "@hstm-labs/forge-services-generator": "0.1.11",
23
+ "@hstm-labs/forge-apps-generator": "0.1.11",
24
+ "@hstm-labs/forge-infra-generator": "0.1.11",
25
+ "@hstm-labs/forge-security-generator": "0.1.11",
26
+ "@hstm-labs/forge-seed-data": "0.1.11",
27
+ "@hstm-labs/forge-spec-parser": "0.1.11",
28
+ "@hstm-labs/forge-profiles": "0.1.11",
29
+ "@hstm-labs/forge-templates": "0.1.11"
30
+ }
31
+ }