@maintainabilityai/research-runner 0.1.1
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/LICENSE +21 -0
- package/README.md +82 -0
- package/bin/research-runner.js +2 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +209 -0
- package/dist/llm/anthropic-client.d.ts +39 -0
- package/dist/llm/anthropic-client.js +74 -0
- package/dist/llm/github-models-client.d.ts +46 -0
- package/dist/llm/github-models-client.js +78 -0
- package/dist/llm/llm-router.d.ts +46 -0
- package/dist/llm/llm-router.js +60 -0
- package/dist/mesh/get-mesh-sha.d.ts +1 -0
- package/dist/mesh/get-mesh-sha.js +27 -0
- package/dist/mesh/mesh-reader.d.ts +14 -0
- package/dist/mesh/mesh-reader.js +392 -0
- package/dist/mesh/prompt-loader.d.ts +22 -0
- package/dist/mesh/prompt-loader.js +119 -0
- package/dist/mesh/threat-model-reader.d.ts +33 -0
- package/dist/mesh/threat-model-reader.js +123 -0
- package/dist/runner/archeologist.d.ts +39 -0
- package/dist/runner/archeologist.js +620 -0
- package/dist/runner/audit-emitter.d.ts +62 -0
- package/dist/runner/audit-emitter.js +210 -0
- package/dist/runner/hatters-tag-builder.d.ts +52 -0
- package/dist/runner/hatters-tag-builder.js +40 -0
- package/dist/runner/nodes/analyze-architecture.d.ts +10 -0
- package/dist/runner/nodes/analyze-architecture.js +447 -0
- package/dist/runner/nodes/arxiv-search.d.ts +12 -0
- package/dist/runner/nodes/arxiv-search.js +52 -0
- package/dist/runner/nodes/clone-and-index.d.ts +32 -0
- package/dist/runner/nodes/clone-and-index.js +158 -0
- package/dist/runner/nodes/dedupe-and-rank.d.ts +27 -0
- package/dist/runner/nodes/dedupe-and-rank.js +98 -0
- package/dist/runner/nodes/deterministic-review.d.ts +55 -0
- package/dist/runner/nodes/deterministic-review.js +206 -0
- package/dist/runner/nodes/expert-review.d.ts +68 -0
- package/dist/runner/nodes/expert-review.js +197 -0
- package/dist/runner/nodes/gap-analysis.d.ts +48 -0
- package/dist/runner/nodes/gap-analysis.js +153 -0
- package/dist/runner/nodes/generate-prd-manifest.d.ts +53 -0
- package/dist/runner/nodes/generate-prd-manifest.js +209 -0
- package/dist/runner/nodes/hackernews-search.d.ts +12 -0
- package/dist/runner/nodes/hackernews-search.js +63 -0
- package/dist/runner/nodes/identify-gaps.d.ts +33 -0
- package/dist/runner/nodes/identify-gaps.js +185 -0
- package/dist/runner/nodes/plan-queries.d.ts +28 -0
- package/dist/runner/nodes/plan-queries.js +120 -0
- package/dist/runner/nodes/prd-validator.d.ts +51 -0
- package/dist/runner/nodes/prd-validator.js +203 -0
- package/dist/runner/nodes/synthesis-archaeology-validator.d.ts +22 -0
- package/dist/runner/nodes/synthesis-archaeology-validator.js +131 -0
- package/dist/runner/nodes/synthesis-validator.d.ts +51 -0
- package/dist/runner/nodes/synthesis-validator.js +185 -0
- package/dist/runner/nodes/synthesize-prd.d.ts +84 -0
- package/dist/runner/nodes/synthesize-prd.js +202 -0
- package/dist/runner/nodes/synthesize-report.d.ts +53 -0
- package/dist/runner/nodes/synthesize-report.js +188 -0
- package/dist/runner/nodes/tavily-search.d.ts +21 -0
- package/dist/runner/nodes/tavily-search.js +57 -0
- package/dist/runner/nodes/uspto-search.d.ts +13 -0
- package/dist/runner/nodes/uspto-search.js +62 -0
- package/dist/runner/nodes/verify-grounding.d.ts +54 -0
- package/dist/runner/nodes/verify-grounding.js +134 -0
- package/dist/runner/prd.d.ts +28 -0
- package/dist/runner/prd.js +494 -0
- package/dist/schemas/audit-event.d.ts +1151 -0
- package/dist/schemas/audit-event.js +141 -0
- package/dist/schemas/index.d.ts +17 -0
- package/dist/schemas/index.js +33 -0
- package/dist/schemas/mesh-context.d.ts +415 -0
- package/dist/schemas/mesh-context.js +95 -0
- package/dist/schemas/observed-architecture.d.ts +262 -0
- package/dist/schemas/observed-architecture.js +90 -0
- package/dist/schemas/prd-brief.d.ts +111 -0
- package/dist/schemas/prd-brief.js +37 -0
- package/dist/schemas/prd-doc.d.ts +249 -0
- package/dist/schemas/prd-doc.js +42 -0
- package/dist/schemas/prd-manifest.d.ts +171 -0
- package/dist/schemas/prd-manifest.js +73 -0
- package/dist/schemas/primitives.d.ts +47 -0
- package/dist/schemas/primitives.js +41 -0
- package/dist/schemas/query-plan.d.ts +33 -0
- package/dist/schemas/query-plan.js +25 -0
- package/dist/schemas/ranked-source.d.ts +82 -0
- package/dist/schemas/ranked-source.js +29 -0
- package/dist/schemas/research-brief.d.ts +114 -0
- package/dist/schemas/research-brief.js +49 -0
- package/dist/schemas/research-doc.d.ts +104 -0
- package/dist/schemas/research-doc.js +37 -0
- package/dist/search/arxiv-client.d.ts +41 -0
- package/dist/search/arxiv-client.js +88 -0
- package/dist/search/hackernews-client.d.ts +33 -0
- package/dist/search/hackernews-client.js +44 -0
- package/dist/search/provider-result.d.ts +25 -0
- package/dist/search/provider-result.js +2 -0
- package/dist/search/tavily-client.d.ts +38 -0
- package/dist/search/tavily-client.js +53 -0
- package/dist/search/uspto-client.d.ts +50 -0
- package/dist/search/uspto-client.js +112 -0
- package/dist/utils/run-id.d.ts +2 -0
- package/dist/utils/run-id.js +22 -0
- package/package.json +53 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.runPrd = runPrd;
|
|
37
|
+
/**
|
|
38
|
+
* PRD pipeline orchestrator — Phase 4.
|
|
39
|
+
*
|
|
40
|
+
* validate_brief (pure)
|
|
41
|
+
* gather_mesh_context (pure)
|
|
42
|
+
* --- refinement loop (1..max_iterations) ---
|
|
43
|
+
* synthesize_prd (llm)
|
|
44
|
+
* architecture_review (llm, parallel)
|
|
45
|
+
* security_review (llm, parallel)
|
|
46
|
+
* verify_grounding (pure: combines LLM scores + citation parser)
|
|
47
|
+
* verdict ∈ { PASS, ITERATE, EXHAUSTED }
|
|
48
|
+
* --- end loop ---
|
|
49
|
+
* generate_prd_manifest (pure)
|
|
50
|
+
* publish (pure)
|
|
51
|
+
* verify_and_trigger (run_complete)
|
|
52
|
+
*
|
|
53
|
+
* Refinement loop convergence: ITERATE re-runs synthesize_prd with prior
|
|
54
|
+
* review feedback. EXHAUSTED is the terminal verdict when max_iterations
|
|
55
|
+
* is reached — the orchestrator still publishes with passed=false so the
|
|
56
|
+
* audit trail shows the loop gave up.
|
|
57
|
+
*/
|
|
58
|
+
const fs = __importStar(require("node:fs"));
|
|
59
|
+
const path = __importStar(require("node:path"));
|
|
60
|
+
const schemas_1 = require("../schemas");
|
|
61
|
+
const mesh_reader_1 = require("../mesh/mesh-reader");
|
|
62
|
+
const run_id_1 = require("../utils/run-id");
|
|
63
|
+
const audit_emitter_1 = require("./audit-emitter");
|
|
64
|
+
const hatters_tag_builder_1 = require("./hatters-tag-builder");
|
|
65
|
+
const synthesize_prd_1 = require("./nodes/synthesize-prd");
|
|
66
|
+
const expert_review_1 = require("./nodes/expert-review");
|
|
67
|
+
const verify_grounding_1 = require("./nodes/verify-grounding");
|
|
68
|
+
const generate_prd_manifest_1 = require("./nodes/generate-prd-manifest");
|
|
69
|
+
const deterministic_review_1 = require("./nodes/deterministic-review");
|
|
70
|
+
async function runPrd(opts) {
|
|
71
|
+
const briefParsed = schemas_1.PrdBrief.safeParse(opts.brief);
|
|
72
|
+
if (!briefParsed.success) {
|
|
73
|
+
throw new Error(`Invalid PRD brief: ${briefParsed.error.message}`);
|
|
74
|
+
}
|
|
75
|
+
const brief = briefParsed.data;
|
|
76
|
+
const runId = (0, run_id_1.generateRunId)('PRD');
|
|
77
|
+
const startedAt = new Date();
|
|
78
|
+
const anthropicApiKey = opts.anthropicApiKey ?? process.env.ANTHROPIC_API_KEY ?? '';
|
|
79
|
+
const githubToken = opts.githubToken ?? process.env.GITHUB_TOKEN ?? '';
|
|
80
|
+
const absoluteAuditDir = path.resolve(opts.meshDir, opts.auditDir);
|
|
81
|
+
const absoluteOutputDir = path.resolve(opts.meshDir, opts.outputDir);
|
|
82
|
+
fs.mkdirSync(absoluteOutputDir, { recursive: true });
|
|
83
|
+
const emitter = new audit_emitter_1.AuditEmitter(absoluteAuditDir, runId);
|
|
84
|
+
// ----- validate_brief -----
|
|
85
|
+
emitter.emit({
|
|
86
|
+
node_kind: 'pure',
|
|
87
|
+
node_name: 'validate_brief',
|
|
88
|
+
duration_ms: 0,
|
|
89
|
+
pure: {
|
|
90
|
+
inputs_summary: `scope=${brief.scope.level}${brief.scope.id ? `(${brief.scope.id})` : ''}; mode=${brief.mode}; grounding=${brief.grounding}@${brief.grounding_threshold}; max=${brief.max_iterations}`,
|
|
91
|
+
outputs_summary: 'PrdBrief validated',
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
// ----- gather_mesh_context -----
|
|
95
|
+
const meshStart = Date.now();
|
|
96
|
+
const meshContext = (0, mesh_reader_1.gatherMeshContext)({
|
|
97
|
+
meshDir: opts.meshDir,
|
|
98
|
+
scope: { level: brief.scope.level, id: brief.scope.id },
|
|
99
|
+
});
|
|
100
|
+
emitter.emit({
|
|
101
|
+
node_kind: 'pure',
|
|
102
|
+
node_name: 'gather_mesh_context',
|
|
103
|
+
duration_ms: Date.now() - meshStart,
|
|
104
|
+
pure: {
|
|
105
|
+
inputs_summary: `scope=${meshContext.scope.level}${meshContext.scope.bar_id ? `(${meshContext.scope.bar_id})` : ''}; mesh_sha=${meshContext.mesh_sha.slice(0, 7)}`,
|
|
106
|
+
outputs_summary: `bar_loaded=${!!meshContext.bar}; calm_nodes=${calmNodeCount(meshContext)}; threats=${threatCount(meshContext)}; adrs=${meshContext.bar?.adrs.length ?? 0}`,
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
// Phase 4 doesn't load ranked sources from the research PR yet — the
|
|
110
|
+
// research-source linker lands in phase 4b. For now we run with an empty
|
|
111
|
+
// ranked-source list; the synthesis prompt + validator both tolerate this.
|
|
112
|
+
const rankedSources = [];
|
|
113
|
+
// ----- refinement loop -----
|
|
114
|
+
let totalInputTokens = 0;
|
|
115
|
+
let totalOutputTokens = 0;
|
|
116
|
+
let totalCostUsd = 0;
|
|
117
|
+
const reviewHistory = [];
|
|
118
|
+
let priorFeedback;
|
|
119
|
+
let lastSynthesis = null;
|
|
120
|
+
let lastGrounding = null;
|
|
121
|
+
let verdict = 'EXHAUSTED';
|
|
122
|
+
let iteration = 0;
|
|
123
|
+
const snapshots = [];
|
|
124
|
+
for (iteration = 1; iteration <= brief.max_iterations; iteration++) {
|
|
125
|
+
// --- synthesize_prd (llm) ---
|
|
126
|
+
const synthStart = Date.now();
|
|
127
|
+
const synthesis = await (0, synthesize_prd_1.synthesizePrd)({
|
|
128
|
+
meshDir: opts.meshDir,
|
|
129
|
+
brief,
|
|
130
|
+
meshContext,
|
|
131
|
+
rankedSources,
|
|
132
|
+
provider: brief.llm_provider,
|
|
133
|
+
anthropicApiKey,
|
|
134
|
+
githubToken,
|
|
135
|
+
priorFeedback,
|
|
136
|
+
fetchImpl: opts.fetchImpl,
|
|
137
|
+
});
|
|
138
|
+
lastSynthesis = synthesis;
|
|
139
|
+
totalInputTokens += synthesis.llm.inputTokens;
|
|
140
|
+
totalOutputTokens += synthesis.llm.outputTokens;
|
|
141
|
+
totalCostUsd += synthesis.llm.costUsd;
|
|
142
|
+
emitter.emit({
|
|
143
|
+
node_kind: 'llm',
|
|
144
|
+
node_name: `synthesize_prd[iter${iteration}]`,
|
|
145
|
+
duration_ms: Date.now() - synthStart,
|
|
146
|
+
llm: {
|
|
147
|
+
provider: synthesis.llm.provider,
|
|
148
|
+
model: synthesis.llm.model,
|
|
149
|
+
prompt_pack: { path: synthesis.prompt.packPath, sha256: synthesis.prompt.packSha256 },
|
|
150
|
+
input_tokens: synthesis.llm.inputTokens,
|
|
151
|
+
output_tokens: synthesis.llm.outputTokens,
|
|
152
|
+
cost_usd: synthesis.llm.costUsd,
|
|
153
|
+
guardrails: { mode: brief.guardrails, pre: 'PASS', post: 'PASS' },
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
// --- architect_expert_review + security_expert_review (parallel llm) ---
|
|
157
|
+
const reviewStart = Date.now();
|
|
158
|
+
const priorArch = findLast(reviewHistory, r => r.expert === 'architecture');
|
|
159
|
+
const priorSec = findLast(reviewHistory, r => r.expert === 'security');
|
|
160
|
+
const [archResult, secResult] = await Promise.all([
|
|
161
|
+
(0, expert_review_1.runExpertReview)({
|
|
162
|
+
meshDir: opts.meshDir,
|
|
163
|
+
expert: 'architecture',
|
|
164
|
+
iteration,
|
|
165
|
+
prdBody: synthesis.body_md,
|
|
166
|
+
meshContext,
|
|
167
|
+
priorReview: priorArch,
|
|
168
|
+
provider: brief.llm_provider,
|
|
169
|
+
anthropicApiKey,
|
|
170
|
+
githubToken,
|
|
171
|
+
fetchImpl: opts.fetchImpl,
|
|
172
|
+
}),
|
|
173
|
+
(0, expert_review_1.runExpertReview)({
|
|
174
|
+
meshDir: opts.meshDir,
|
|
175
|
+
expert: 'security',
|
|
176
|
+
iteration,
|
|
177
|
+
prdBody: synthesis.body_md,
|
|
178
|
+
meshContext,
|
|
179
|
+
priorReview: priorSec,
|
|
180
|
+
provider: brief.llm_provider,
|
|
181
|
+
anthropicApiKey,
|
|
182
|
+
githubToken,
|
|
183
|
+
fetchImpl: opts.fetchImpl,
|
|
184
|
+
}),
|
|
185
|
+
]);
|
|
186
|
+
const reviewDuration = Date.now() - reviewStart;
|
|
187
|
+
totalInputTokens += archResult.llm.inputTokens + secResult.llm.inputTokens;
|
|
188
|
+
totalOutputTokens += archResult.llm.outputTokens + secResult.llm.outputTokens;
|
|
189
|
+
totalCostUsd += archResult.llm.costUsd + secResult.llm.costUsd;
|
|
190
|
+
const llmEventName = (k) => k === 'architecture' ? `architect_expert_review[iter${iteration}]` : `security_expert_review[iter${iteration}]`;
|
|
191
|
+
for (const r of [archResult, secResult]) {
|
|
192
|
+
emitter.emit({
|
|
193
|
+
node_kind: 'llm',
|
|
194
|
+
node_name: llmEventName(r.review.expert),
|
|
195
|
+
duration_ms: Math.round(reviewDuration / 2),
|
|
196
|
+
llm: {
|
|
197
|
+
provider: r.llm.provider,
|
|
198
|
+
model: r.llm.model,
|
|
199
|
+
prompt_pack: { path: r.prompt.packPath, sha256: r.prompt.packSha256 },
|
|
200
|
+
input_tokens: r.llm.inputTokens,
|
|
201
|
+
output_tokens: r.llm.outputTokens,
|
|
202
|
+
cost_usd: r.llm.costUsd,
|
|
203
|
+
guardrails: { mode: brief.guardrails, pre: 'PASS', post: 'PASS' },
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
reviewHistory.push(archResult.review, secResult.review);
|
|
208
|
+
// --- deterministic_architecture_review + deterministic_security_review (pure, parallel) ---
|
|
209
|
+
const detStart = Date.now();
|
|
210
|
+
const detArch = (0, deterministic_review_1.deterministicArchitectureReview)({
|
|
211
|
+
iteration,
|
|
212
|
+
signals: synthesis.signals,
|
|
213
|
+
meshContext,
|
|
214
|
+
});
|
|
215
|
+
const detSec = (0, deterministic_review_1.deterministicSecurityReview)({
|
|
216
|
+
iteration,
|
|
217
|
+
signals: synthesis.signals,
|
|
218
|
+
meshContext,
|
|
219
|
+
});
|
|
220
|
+
const detDuration = Date.now() - detStart;
|
|
221
|
+
for (const d of [detArch, detSec]) {
|
|
222
|
+
emitter.emit({
|
|
223
|
+
node_kind: 'pure',
|
|
224
|
+
node_name: `deterministic_${d.expert}_review[iter${iteration}]`,
|
|
225
|
+
duration_ms: Math.round(detDuration / 2),
|
|
226
|
+
pure: {
|
|
227
|
+
inputs_summary: `signals(fr=${synthesis.signals.fr_entries.length}, sr=${synthesis.signals.sr_entries.length}, coverage_rows=${synthesis.signals.coverage_rows.length})`,
|
|
228
|
+
outputs_summary: `severity=${d.severity}; invalid=${d.invalid_citations.length}; discrepancies=${d.coverage_discrepancies.length}`,
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
// --- verify_grounding (pure — 4-input now) ---
|
|
233
|
+
const verifyStart = Date.now();
|
|
234
|
+
const grounding = (0, verify_grounding_1.verifyGrounding)({
|
|
235
|
+
iteration,
|
|
236
|
+
threshold: brief.grounding_threshold,
|
|
237
|
+
mode: brief.grounding,
|
|
238
|
+
signals: synthesis.signals,
|
|
239
|
+
architecture: archResult.review,
|
|
240
|
+
security: secResult.review,
|
|
241
|
+
det_architecture: detArch,
|
|
242
|
+
det_security: detSec,
|
|
243
|
+
meshContext,
|
|
244
|
+
history: reviewHistory.slice(0, -2),
|
|
245
|
+
});
|
|
246
|
+
lastGrounding = grounding;
|
|
247
|
+
verdict = grounding.verdict;
|
|
248
|
+
emitter.emit({
|
|
249
|
+
node_kind: 'pure',
|
|
250
|
+
node_name: `verify_grounding[iter${iteration}]`,
|
|
251
|
+
duration_ms: Date.now() - verifyStart,
|
|
252
|
+
pure: {
|
|
253
|
+
inputs_summary: `arch=${archResult.review.score}/${archResult.review.severity}; sec=${secResult.review.score}/${secResult.review.severity}; det_arch=${detArch.severity}; det_sec=${detSec.severity}; threshold=${brief.grounding_threshold}`,
|
|
254
|
+
outputs_summary: `verdict=${verdict}; composite=${grounding.grounding.final_score}; disagreement=${grounding.signals_snapshot.disagreement_delta}; reason="${grounding.reason.slice(0, 200)}"`,
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
// --- iteration_summary (new audit event variant) ---
|
|
258
|
+
snapshots.push({
|
|
259
|
+
iteration,
|
|
260
|
+
det_arch: detArch,
|
|
261
|
+
det_sec: detSec,
|
|
262
|
+
llm_arch: archResult.review,
|
|
263
|
+
llm_sec: secResult.review,
|
|
264
|
+
composite_score: grounding.signals_snapshot.composite_score,
|
|
265
|
+
disagreement_delta: grounding.signals_snapshot.disagreement_delta,
|
|
266
|
+
verdict,
|
|
267
|
+
});
|
|
268
|
+
emitter.emit({
|
|
269
|
+
node_kind: 'iteration_summary',
|
|
270
|
+
node_name: `iteration_summary[iter${iteration}]`,
|
|
271
|
+
duration_ms: 0,
|
|
272
|
+
iteration,
|
|
273
|
+
summary: {
|
|
274
|
+
det_arch: {
|
|
275
|
+
severity: detArch.severity,
|
|
276
|
+
invalid_citations: detArch.invalid_citations.length,
|
|
277
|
+
coverage_discrepancies: detArch.coverage_discrepancies.length,
|
|
278
|
+
},
|
|
279
|
+
det_sec: {
|
|
280
|
+
severity: detSec.severity,
|
|
281
|
+
invalid_citations: detSec.invalid_citations.length,
|
|
282
|
+
coverage_discrepancies: detSec.coverage_discrepancies.length,
|
|
283
|
+
},
|
|
284
|
+
llm_arch: { score: archResult.review.score, severity: archResult.review.severity },
|
|
285
|
+
llm_sec: { score: secResult.review.score, severity: secResult.review.severity },
|
|
286
|
+
composite_score: grounding.signals_snapshot.composite_score,
|
|
287
|
+
disagreement_delta: grounding.signals_snapshot.disagreement_delta,
|
|
288
|
+
verdict,
|
|
289
|
+
reason: grounding.reason.slice(0, 500),
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
if (verdict === 'PASS') {
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
// Compose the next iteration's feedback from ALL 4 reviewers.
|
|
296
|
+
priorFeedback = {
|
|
297
|
+
iteration,
|
|
298
|
+
architecture: { score: archResult.review.score, severity: archResult.review.severity, changes: archResult.review.changes },
|
|
299
|
+
security: { score: secResult.review.score, severity: secResult.review.severity, changes: secResult.review.changes },
|
|
300
|
+
det_architecture: {
|
|
301
|
+
severity: detArch.severity,
|
|
302
|
+
invalid_citations: detArch.invalid_citations,
|
|
303
|
+
coverage_discrepancies: detArch.coverage_discrepancies.map(d => ({
|
|
304
|
+
premise: d.premise, claimed_status: String(d.claimed_status), detail: d.detail,
|
|
305
|
+
})),
|
|
306
|
+
},
|
|
307
|
+
det_security: {
|
|
308
|
+
severity: detSec.severity,
|
|
309
|
+
invalid_citations: detSec.invalid_citations,
|
|
310
|
+
coverage_discrepancies: detSec.coverage_discrepancies.map(d => ({
|
|
311
|
+
premise: d.premise, claimed_status: String(d.claimed_status), detail: d.detail,
|
|
312
|
+
})),
|
|
313
|
+
},
|
|
314
|
+
disagreement_delta: grounding.signals_snapshot.disagreement_delta,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
// Hit max_iterations without PASS → EXHAUSTED
|
|
318
|
+
if (verdict !== 'PASS') {
|
|
319
|
+
verdict = 'EXHAUSTED';
|
|
320
|
+
}
|
|
321
|
+
if (!lastSynthesis || !lastGrounding) {
|
|
322
|
+
throw new Error('PRD pipeline: no synthesis completed (refinement loop did not run)');
|
|
323
|
+
}
|
|
324
|
+
// ----- generate_prd_manifest (pure) -----
|
|
325
|
+
const manifestStart = Date.now();
|
|
326
|
+
const manifest = (0, generate_prd_manifest_1.generatePrdManifest)({
|
|
327
|
+
runId,
|
|
328
|
+
brief,
|
|
329
|
+
meshContext,
|
|
330
|
+
prdBody: lastSynthesis.body_md,
|
|
331
|
+
signals: lastSynthesis.signals,
|
|
332
|
+
grounding: lastGrounding.grounding,
|
|
333
|
+
threshold: brief.grounding_threshold,
|
|
334
|
+
});
|
|
335
|
+
emitter.emit({
|
|
336
|
+
node_kind: 'pure',
|
|
337
|
+
node_name: 'generate_prd_manifest',
|
|
338
|
+
duration_ms: Date.now() - manifestStart,
|
|
339
|
+
pure: {
|
|
340
|
+
inputs_summary: `iteration=${iteration - (verdict === 'PASS' ? 0 : 0)}; verdict=${verdict}; threshold=${brief.grounding_threshold}`,
|
|
341
|
+
outputs_summary: `endpoints=${manifest.endpoints.length}; security_reqs=${manifest.security_requirements.length}; target_repos=${manifest.target_repos.length}; grounding.passed=${manifest.grounding.passed}`,
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
// ----- publish (pure) -----
|
|
345
|
+
const topic = manifest.prd_topic;
|
|
346
|
+
const slug = topic.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 60) || 'prd';
|
|
347
|
+
const today = startedAt.toISOString().slice(0, 10);
|
|
348
|
+
const artifactPath = path.join(absoluteOutputDir, `${slug}-${today}.md`);
|
|
349
|
+
const manifestPath = path.join(absoluteOutputDir, `${slug}-${today}.manifest.json`);
|
|
350
|
+
const bodyMd = composePrdDoc({
|
|
351
|
+
runId,
|
|
352
|
+
meshSha: meshContext.mesh_sha,
|
|
353
|
+
brief,
|
|
354
|
+
verdict,
|
|
355
|
+
iterations: lastGrounding.grounding.final_iteration,
|
|
356
|
+
finalScore: lastGrounding.grounding.final_score,
|
|
357
|
+
synthesisBody: lastSynthesis.body_md,
|
|
358
|
+
snapshots,
|
|
359
|
+
});
|
|
360
|
+
const writeStart = Date.now();
|
|
361
|
+
fs.writeFileSync(artifactPath, bodyMd, 'utf8');
|
|
362
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
|
|
363
|
+
emitter.emit({
|
|
364
|
+
node_kind: 'pure',
|
|
365
|
+
node_name: 'publish',
|
|
366
|
+
duration_ms: Date.now() - writeStart,
|
|
367
|
+
pure: {
|
|
368
|
+
inputs_summary: `wrote ${path.basename(artifactPath)} + ${path.basename(manifestPath)}`,
|
|
369
|
+
outputs_summary: `${bodyMd.length} bytes md + manifest`,
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
// ----- run_complete -----
|
|
373
|
+
const complete = emitter.emitRunComplete({
|
|
374
|
+
node_kind: 'run_complete',
|
|
375
|
+
node_name: 'verify_and_trigger',
|
|
376
|
+
duration_ms: Date.now() - startedAt.getTime(),
|
|
377
|
+
outcome: {
|
|
378
|
+
status: verdict === 'PASS' ? 'ok' : 'partial',
|
|
379
|
+
mesh_sha: meshContext.mesh_sha,
|
|
380
|
+
total_input_tokens: totalInputTokens,
|
|
381
|
+
total_output_tokens: totalOutputTokens,
|
|
382
|
+
total_cost_usd: roundUsd(totalCostUsd),
|
|
383
|
+
artifact_paths: [
|
|
384
|
+
path.relative(opts.meshDir, artifactPath),
|
|
385
|
+
path.relative(opts.meshDir, manifestPath),
|
|
386
|
+
],
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
// ----- optional PR body -----
|
|
390
|
+
let prBodyPath = null;
|
|
391
|
+
if (opts.emitPrBodyPath) {
|
|
392
|
+
const hattersTag = (0, hatters_tag_builder_1.buildHattersTag)({
|
|
393
|
+
run_id: runId,
|
|
394
|
+
mesh_sha: meshContext.mesh_sha,
|
|
395
|
+
prompt_library_version: 'phase4',
|
|
396
|
+
agent_version: opts.agentVersion,
|
|
397
|
+
published_at: new Date().toISOString(),
|
|
398
|
+
llm: {
|
|
399
|
+
provider: brief.llm_provider,
|
|
400
|
+
model: lastSynthesis.llm.model,
|
|
401
|
+
input_tokens: totalInputTokens,
|
|
402
|
+
output_tokens: totalOutputTokens,
|
|
403
|
+
cost_usd: roundUsd(totalCostUsd),
|
|
404
|
+
},
|
|
405
|
+
guardrails: { mode: brief.guardrails, blocks: 0, warns: 0 },
|
|
406
|
+
grounding: {
|
|
407
|
+
final_score: lastGrounding.grounding.final_score,
|
|
408
|
+
threshold: brief.grounding_threshold,
|
|
409
|
+
iterations: lastGrounding.grounding.final_iteration,
|
|
410
|
+
passed: lastGrounding.grounding.passed,
|
|
411
|
+
},
|
|
412
|
+
audit: {
|
|
413
|
+
event_count: complete.event_id,
|
|
414
|
+
chain_root_hash: complete.outcome.chain_root_hash,
|
|
415
|
+
audit_log_path: path.relative(opts.meshDir, emitter.path),
|
|
416
|
+
},
|
|
417
|
+
});
|
|
418
|
+
fs.writeFileSync(opts.emitPrBodyPath, `${bodyMd}\n\n${hattersTag}`, 'utf8');
|
|
419
|
+
prBodyPath = opts.emitPrBodyPath;
|
|
420
|
+
}
|
|
421
|
+
return {
|
|
422
|
+
run_id: runId,
|
|
423
|
+
topic,
|
|
424
|
+
artifact_path: artifactPath,
|
|
425
|
+
manifest_path: manifestPath,
|
|
426
|
+
audit_log_path: emitter.path,
|
|
427
|
+
chain_root_hash: complete.outcome.chain_root_hash,
|
|
428
|
+
pr_body_path: prBodyPath,
|
|
429
|
+
verdict,
|
|
430
|
+
final_score: lastGrounding.grounding.final_score,
|
|
431
|
+
iterations: lastGrounding.grounding.final_iteration,
|
|
432
|
+
total_input_tokens: totalInputTokens,
|
|
433
|
+
total_output_tokens: totalOutputTokens,
|
|
434
|
+
total_cost_usd: roundUsd(totalCostUsd),
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
// ============================================================================
|
|
438
|
+
// helpers
|
|
439
|
+
// ============================================================================
|
|
440
|
+
function calmNodeCount(mesh) {
|
|
441
|
+
const calm = mesh.bar?.calm_model;
|
|
442
|
+
if (!calm || typeof calm !== 'object') {
|
|
443
|
+
return 0;
|
|
444
|
+
}
|
|
445
|
+
const nodes = calm.nodes;
|
|
446
|
+
return Array.isArray(nodes) ? nodes.length : 0;
|
|
447
|
+
}
|
|
448
|
+
function threatCount(mesh) {
|
|
449
|
+
const t = mesh.bar?.threats;
|
|
450
|
+
return Array.isArray(t) ? t.length : 0;
|
|
451
|
+
}
|
|
452
|
+
function findLast(arr, pred) {
|
|
453
|
+
for (let i = arr.length - 1; i >= 0; i--) {
|
|
454
|
+
if (pred(arr[i])) {
|
|
455
|
+
return arr[i];
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return undefined;
|
|
459
|
+
}
|
|
460
|
+
function roundUsd(n) {
|
|
461
|
+
return Math.round(n * 10000) / 10000;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Compose the published artifact:
|
|
465
|
+
* <metadata header (verdict + score + iterations)>
|
|
466
|
+
* <H2 "Refinement Loop Trace" + 4-column score-progression table>
|
|
467
|
+
* <synthesis body — H2 Input Premises through H2 References>
|
|
468
|
+
* Hatter's Tag is appended separately by the PR-body path.
|
|
469
|
+
*/
|
|
470
|
+
function composePrdDoc(opts) {
|
|
471
|
+
const lines = [];
|
|
472
|
+
lines.push(`- **Run id:** \`${opts.runId}\``);
|
|
473
|
+
lines.push(`- **Mesh sha:** \`${opts.meshSha.slice(0, 12)}\``);
|
|
474
|
+
lines.push(`- **Scope:** ${opts.brief.scope.level}${opts.brief.scope.id ? ` / ${opts.brief.scope.id}` : ''}`);
|
|
475
|
+
lines.push(`- **Grounding verdict:** ${opts.verdict} (final score ${opts.finalScore.toFixed(4)} after ${opts.iterations} iteration${opts.iterations === 1 ? '' : 's'})`);
|
|
476
|
+
lines.push('');
|
|
477
|
+
if (opts.snapshots.length > 0) {
|
|
478
|
+
lines.push('## Refinement Loop Trace');
|
|
479
|
+
lines.push('');
|
|
480
|
+
lines.push('| Iter | det_arch | det_sec | llm_arch | llm_sec | composite | Δ | verdict |');
|
|
481
|
+
lines.push('|---|---|---|---|---|---|---|---|');
|
|
482
|
+
for (const s of opts.snapshots) {
|
|
483
|
+
const archLlm = `${s.llm_arch.score.toFixed(2)} / ${s.llm_arch.severity}`;
|
|
484
|
+
const secLlm = `${s.llm_sec.score.toFixed(2)} / ${s.llm_sec.severity}`;
|
|
485
|
+
const archDet = `${s.det_arch.severity}${s.det_arch.invalid_citations.length > 0 ? ` (×${s.det_arch.invalid_citations.length} bad cites)` : ''}`;
|
|
486
|
+
const secDet = `${s.det_sec.severity}${s.det_sec.invalid_citations.length > 0 ? ` (×${s.det_sec.invalid_citations.length} bad cites)` : ''}`;
|
|
487
|
+
lines.push(`| ${s.iteration} | ${archDet} | ${secDet} | ${archLlm} | ${secLlm} | ${s.composite_score.toFixed(4)} | ${s.disagreement_delta.toFixed(2)} | ${s.verdict} |`);
|
|
488
|
+
}
|
|
489
|
+
lines.push('');
|
|
490
|
+
}
|
|
491
|
+
lines.push(opts.synthesisBody.trim());
|
|
492
|
+
lines.push('');
|
|
493
|
+
return lines.join('\n');
|
|
494
|
+
}
|