@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.
Files changed (102) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +82 -0
  3. package/bin/research-runner.js +2 -0
  4. package/dist/cli.d.ts +1 -0
  5. package/dist/cli.js +209 -0
  6. package/dist/llm/anthropic-client.d.ts +39 -0
  7. package/dist/llm/anthropic-client.js +74 -0
  8. package/dist/llm/github-models-client.d.ts +46 -0
  9. package/dist/llm/github-models-client.js +78 -0
  10. package/dist/llm/llm-router.d.ts +46 -0
  11. package/dist/llm/llm-router.js +60 -0
  12. package/dist/mesh/get-mesh-sha.d.ts +1 -0
  13. package/dist/mesh/get-mesh-sha.js +27 -0
  14. package/dist/mesh/mesh-reader.d.ts +14 -0
  15. package/dist/mesh/mesh-reader.js +392 -0
  16. package/dist/mesh/prompt-loader.d.ts +22 -0
  17. package/dist/mesh/prompt-loader.js +119 -0
  18. package/dist/mesh/threat-model-reader.d.ts +33 -0
  19. package/dist/mesh/threat-model-reader.js +123 -0
  20. package/dist/runner/archeologist.d.ts +39 -0
  21. package/dist/runner/archeologist.js +620 -0
  22. package/dist/runner/audit-emitter.d.ts +62 -0
  23. package/dist/runner/audit-emitter.js +210 -0
  24. package/dist/runner/hatters-tag-builder.d.ts +52 -0
  25. package/dist/runner/hatters-tag-builder.js +40 -0
  26. package/dist/runner/nodes/analyze-architecture.d.ts +10 -0
  27. package/dist/runner/nodes/analyze-architecture.js +447 -0
  28. package/dist/runner/nodes/arxiv-search.d.ts +12 -0
  29. package/dist/runner/nodes/arxiv-search.js +52 -0
  30. package/dist/runner/nodes/clone-and-index.d.ts +32 -0
  31. package/dist/runner/nodes/clone-and-index.js +158 -0
  32. package/dist/runner/nodes/dedupe-and-rank.d.ts +27 -0
  33. package/dist/runner/nodes/dedupe-and-rank.js +98 -0
  34. package/dist/runner/nodes/deterministic-review.d.ts +55 -0
  35. package/dist/runner/nodes/deterministic-review.js +206 -0
  36. package/dist/runner/nodes/expert-review.d.ts +68 -0
  37. package/dist/runner/nodes/expert-review.js +197 -0
  38. package/dist/runner/nodes/gap-analysis.d.ts +48 -0
  39. package/dist/runner/nodes/gap-analysis.js +153 -0
  40. package/dist/runner/nodes/generate-prd-manifest.d.ts +53 -0
  41. package/dist/runner/nodes/generate-prd-manifest.js +209 -0
  42. package/dist/runner/nodes/hackernews-search.d.ts +12 -0
  43. package/dist/runner/nodes/hackernews-search.js +63 -0
  44. package/dist/runner/nodes/identify-gaps.d.ts +33 -0
  45. package/dist/runner/nodes/identify-gaps.js +185 -0
  46. package/dist/runner/nodes/plan-queries.d.ts +28 -0
  47. package/dist/runner/nodes/plan-queries.js +120 -0
  48. package/dist/runner/nodes/prd-validator.d.ts +51 -0
  49. package/dist/runner/nodes/prd-validator.js +203 -0
  50. package/dist/runner/nodes/synthesis-archaeology-validator.d.ts +22 -0
  51. package/dist/runner/nodes/synthesis-archaeology-validator.js +131 -0
  52. package/dist/runner/nodes/synthesis-validator.d.ts +51 -0
  53. package/dist/runner/nodes/synthesis-validator.js +185 -0
  54. package/dist/runner/nodes/synthesize-prd.d.ts +84 -0
  55. package/dist/runner/nodes/synthesize-prd.js +202 -0
  56. package/dist/runner/nodes/synthesize-report.d.ts +53 -0
  57. package/dist/runner/nodes/synthesize-report.js +188 -0
  58. package/dist/runner/nodes/tavily-search.d.ts +21 -0
  59. package/dist/runner/nodes/tavily-search.js +57 -0
  60. package/dist/runner/nodes/uspto-search.d.ts +13 -0
  61. package/dist/runner/nodes/uspto-search.js +62 -0
  62. package/dist/runner/nodes/verify-grounding.d.ts +54 -0
  63. package/dist/runner/nodes/verify-grounding.js +134 -0
  64. package/dist/runner/prd.d.ts +28 -0
  65. package/dist/runner/prd.js +494 -0
  66. package/dist/schemas/audit-event.d.ts +1151 -0
  67. package/dist/schemas/audit-event.js +141 -0
  68. package/dist/schemas/index.d.ts +17 -0
  69. package/dist/schemas/index.js +33 -0
  70. package/dist/schemas/mesh-context.d.ts +415 -0
  71. package/dist/schemas/mesh-context.js +95 -0
  72. package/dist/schemas/observed-architecture.d.ts +262 -0
  73. package/dist/schemas/observed-architecture.js +90 -0
  74. package/dist/schemas/prd-brief.d.ts +111 -0
  75. package/dist/schemas/prd-brief.js +37 -0
  76. package/dist/schemas/prd-doc.d.ts +249 -0
  77. package/dist/schemas/prd-doc.js +42 -0
  78. package/dist/schemas/prd-manifest.d.ts +171 -0
  79. package/dist/schemas/prd-manifest.js +73 -0
  80. package/dist/schemas/primitives.d.ts +47 -0
  81. package/dist/schemas/primitives.js +41 -0
  82. package/dist/schemas/query-plan.d.ts +33 -0
  83. package/dist/schemas/query-plan.js +25 -0
  84. package/dist/schemas/ranked-source.d.ts +82 -0
  85. package/dist/schemas/ranked-source.js +29 -0
  86. package/dist/schemas/research-brief.d.ts +114 -0
  87. package/dist/schemas/research-brief.js +49 -0
  88. package/dist/schemas/research-doc.d.ts +104 -0
  89. package/dist/schemas/research-doc.js +37 -0
  90. package/dist/search/arxiv-client.d.ts +41 -0
  91. package/dist/search/arxiv-client.js +88 -0
  92. package/dist/search/hackernews-client.d.ts +33 -0
  93. package/dist/search/hackernews-client.js +44 -0
  94. package/dist/search/provider-result.d.ts +25 -0
  95. package/dist/search/provider-result.js +2 -0
  96. package/dist/search/tavily-client.d.ts +38 -0
  97. package/dist/search/tavily-client.js +53 -0
  98. package/dist/search/uspto-client.d.ts +50 -0
  99. package/dist/search/uspto-client.js +112 -0
  100. package/dist/utils/run-id.d.ts +2 -0
  101. package/dist/utils/run-id.js +22 -0
  102. package/package.json +53 -0
@@ -0,0 +1,210 @@
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.AuditEmitter = void 0;
37
+ exports.readAuditLog = readAuditLog;
38
+ exports.verifyChain = verifyChain;
39
+ /**
40
+ * AuditEmitter — append-only JSONL writer with SHA-256 hash chain.
41
+ *
42
+ * Every node in both pipelines emits one event through this emitter; the
43
+ * emitter:
44
+ * 1. Assigns `event_id` (1, 2, 3, …) and `prev_event_hash` (sha256 of the
45
+ * previous event's full serialization).
46
+ * 2. Computes `event_hash` (sha256 of this event with `event_hash` set to
47
+ * the canonical placeholder `''`).
48
+ * 3. Appends the serialized event as one JSON line to
49
+ * `<audit-dir>/<run_id>.jsonl`.
50
+ *
51
+ * The chain is tamper-evident: changing any past event breaks every
52
+ * subsequent `prev_event_hash`. The `run_complete` event pins the chain
53
+ * root hash (= the final event's event_hash) so auditors only need to
54
+ * verify one number to trust the whole run.
55
+ *
56
+ * Hash computation is deterministic regardless of object key order: we
57
+ * canonicalize via a recursive key-sorted JSON stringify before hashing.
58
+ */
59
+ const node_crypto_1 = require("node:crypto");
60
+ const fs = __importStar(require("node:fs"));
61
+ const path = __importStar(require("node:path"));
62
+ const schemas_1 = require("../schemas");
63
+ const PLACEHOLDER_HASH = '0'.repeat(64);
64
+ /** Recursively sort object keys so JSON.stringify produces a canonical form. */
65
+ function canonicalize(value) {
66
+ if (value === null || typeof value !== 'object') {
67
+ return value;
68
+ }
69
+ if (Array.isArray(value)) {
70
+ return value.map(canonicalize);
71
+ }
72
+ const obj = value;
73
+ const sorted = {};
74
+ for (const key of Object.keys(obj).sort()) {
75
+ sorted[key] = canonicalize(obj[key]);
76
+ }
77
+ return sorted;
78
+ }
79
+ function sha256(text) {
80
+ return (0, node_crypto_1.createHash)('sha256').update(text, 'utf8').digest('hex');
81
+ }
82
+ /** Hash an event with `event_hash` zeroed out so the hash itself is part of the JSON body. */
83
+ function hashEventBody(event) {
84
+ const placeholderEvent = { ...event, event_hash: PLACEHOLDER_HASH };
85
+ return sha256(JSON.stringify(canonicalize(placeholderEvent)));
86
+ }
87
+ class AuditEmitter {
88
+ runId;
89
+ filePath;
90
+ nextEventId = 1;
91
+ prevEventHash = null;
92
+ rootHash = null;
93
+ closed = false;
94
+ /**
95
+ * @param auditDir target directory (created on demand)
96
+ * @param runId the run id; becomes `<runId>.jsonl`
97
+ */
98
+ constructor(auditDir, runId) {
99
+ this.runId = runId;
100
+ fs.mkdirSync(auditDir, { recursive: true });
101
+ this.filePath = path.join(auditDir, `${runId}.jsonl`);
102
+ // Refuse to clobber an existing run's audit file — log immutability is load-bearing.
103
+ if (fs.existsSync(this.filePath)) {
104
+ throw new Error(`Audit log already exists at ${this.filePath}; refusing to overwrite.`);
105
+ }
106
+ // Touch the file so concurrent readers see it.
107
+ fs.writeFileSync(this.filePath, '', { flag: 'wx' });
108
+ }
109
+ /**
110
+ * Emit one event. Returns the canonical serialized form (useful for tests).
111
+ * Validates against the AuditEvent schema before writing.
112
+ */
113
+ emit(input) {
114
+ if (this.closed) {
115
+ throw new Error('AuditEmitter is closed; cannot emit further events.');
116
+ }
117
+ const ts = input.ts ?? new Date().toISOString();
118
+ // Build with a placeholder hash, hash, then overwrite event_hash with the real hash.
119
+ const draft = {
120
+ ...input,
121
+ run_id: this.runId,
122
+ event_id: this.nextEventId,
123
+ ts,
124
+ prev_event_hash: this.prevEventHash,
125
+ event_hash: PLACEHOLDER_HASH,
126
+ };
127
+ const parsed = schemas_1.AuditEvent.parse(draft);
128
+ const eventHash = hashEventBody(parsed);
129
+ const finalEvent = { ...parsed, event_hash: eventHash };
130
+ fs.appendFileSync(this.filePath, JSON.stringify(finalEvent) + '\n', 'utf8');
131
+ this.nextEventId += 1;
132
+ this.prevEventHash = eventHash;
133
+ this.rootHash = eventHash;
134
+ if (finalEvent.node_kind === 'run_complete') {
135
+ this.closed = true;
136
+ }
137
+ return finalEvent;
138
+ }
139
+ /**
140
+ * Emit a `run_complete` event. The emitter computes `chain_root_hash` itself
141
+ * (the hash of the run_complete event), so callers leave that field blank
142
+ * (or omit it) — it's filled in here.
143
+ */
144
+ emitRunComplete(input) {
145
+ const enriched = {
146
+ ...input,
147
+ outcome: { ...input.outcome, chain_root_hash: PLACEHOLDER_HASH },
148
+ };
149
+ const event = this.emit(enriched);
150
+ // The emitted event_hash IS the chain root by definition — rewrite the
151
+ // outcome.chain_root_hash to that value on the in-memory record we return.
152
+ // (The serialized line on disk uses the placeholder for chain_root_hash;
153
+ // we keep it that way so the event_hash matches the line. Auditors
154
+ // verify the chain root by hashing the line as written.)
155
+ return { ...event, outcome: { ...event.outcome, chain_root_hash: event.event_hash } };
156
+ }
157
+ /** SHA-256 of the most recent event — equal to `chain_root_hash` after run_complete. */
158
+ get currentRootHash() {
159
+ return this.rootHash;
160
+ }
161
+ /** Absolute path to the JSONL file this emitter writes to. */
162
+ get path() {
163
+ return this.filePath;
164
+ }
165
+ }
166
+ exports.AuditEmitter = AuditEmitter;
167
+ /**
168
+ * Parse a JSONL audit file back into typed events. Re-validates every event
169
+ * against the schema; returns null on malformed input.
170
+ */
171
+ function readAuditLog(filePath) {
172
+ try {
173
+ const raw = fs.readFileSync(filePath, 'utf8');
174
+ const lines = raw.split('\n').filter(l => l.trim().length > 0);
175
+ const events = [];
176
+ for (const line of lines) {
177
+ const parsed = schemas_1.AuditEvent.parse(JSON.parse(line));
178
+ events.push(parsed);
179
+ }
180
+ return events;
181
+ }
182
+ catch {
183
+ return null;
184
+ }
185
+ }
186
+ /**
187
+ * Verify the hash chain of a sequence of events.
188
+ * - Each event's prev_event_hash must match the previous event's event_hash.
189
+ * - Each event's event_hash must match a recomputation against the line.
190
+ * - The first event must have prev_event_hash === null.
191
+ * Returns the chain root hash (= final event_hash) on success, null on any failure.
192
+ */
193
+ function verifyChain(events) {
194
+ let prev = null;
195
+ for (let i = 0; i < events.length; i++) {
196
+ const e = events[i];
197
+ if (e.prev_event_hash !== prev) {
198
+ return null;
199
+ }
200
+ const recomputed = hashEventBody(e);
201
+ if (e.event_hash !== recomputed) {
202
+ return null;
203
+ }
204
+ if (e.event_id !== i + 1) {
205
+ return null;
206
+ }
207
+ prev = e.event_hash;
208
+ }
209
+ return prev;
210
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Hatter's Tag — versioned, signed-style YAML block appended to every
3
+ * research / PRD artifact. Auditors use it to verify the artifact against
4
+ * the recorded mesh sha + prompt-library version + audit chain root.
5
+ *
6
+ * Format choice: hand-rolled YAML emitter (no js-yaml dependency) because
7
+ * the shape is fixed and the values are scalar — strings, numbers, dates,
8
+ * and one nested object for guardrails. Keeps the package zero-runtime-dep
9
+ * beyond zod.
10
+ *
11
+ * Output is always wrapped in a ```yaml fenced code block so it renders
12
+ * cleanly in GitHub PR bodies + the rendered docs site.
13
+ */
14
+ import type { GuardrailMode, LlmProvider } from '../schemas';
15
+ export interface HattersTagInput {
16
+ run_id: string;
17
+ /** Git SHA of the mesh repo at run start. */
18
+ mesh_sha: string;
19
+ /** Semver of the .caterpillar/prompts library. */
20
+ prompt_library_version: string;
21
+ /** Semver of @maintainabilityai/research-runner. */
22
+ agent_version: string;
23
+ llm: {
24
+ provider: LlmProvider;
25
+ model: string;
26
+ input_tokens: number;
27
+ output_tokens: number;
28
+ cost_usd: number;
29
+ };
30
+ guardrails: {
31
+ mode: GuardrailMode;
32
+ blocks: number;
33
+ warns: number;
34
+ };
35
+ /** Present on PRDs; omitted on research docs. */
36
+ grounding?: {
37
+ final_score: number;
38
+ threshold: number;
39
+ iterations: number;
40
+ passed: boolean;
41
+ };
42
+ audit: {
43
+ event_count: number;
44
+ chain_root_hash: string;
45
+ /** Path to the JSONL audit file relative to mesh root. */
46
+ audit_log_path: string;
47
+ };
48
+ /** ISO timestamp the artifact was published (PR created). */
49
+ published_at: string;
50
+ }
51
+ /** Build the full Hatter's Tag block, including heading + fenced YAML. */
52
+ export declare function buildHattersTag(input: HattersTagInput): string;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildHattersTag = buildHattersTag;
4
+ /** Build the full Hatter's Tag block, including heading + fenced YAML. */
5
+ function buildHattersTag(input) {
6
+ const lines = [];
7
+ lines.push('## Hatter’s Tag');
8
+ lines.push('');
9
+ lines.push('```yaml');
10
+ lines.push(`run_id: ${input.run_id}`);
11
+ lines.push(`mesh_sha: ${input.mesh_sha}`);
12
+ lines.push(`prompt_library_version: ${input.prompt_library_version}`);
13
+ lines.push(`agent_version: ${input.agent_version}`);
14
+ lines.push(`published_at: ${input.published_at}`);
15
+ lines.push('llm:');
16
+ lines.push(` provider: ${input.llm.provider}`);
17
+ lines.push(` model: ${input.llm.model}`);
18
+ lines.push(` input_tokens: ${input.llm.input_tokens}`);
19
+ lines.push(` output_tokens: ${input.llm.output_tokens}`);
20
+ lines.push(` cost_usd: ${input.llm.cost_usd.toFixed(4)}`);
21
+ lines.push('guardrails:');
22
+ lines.push(` mode: ${input.guardrails.mode}`);
23
+ lines.push(` blocks: ${input.guardrails.blocks}`);
24
+ lines.push(` warns: ${input.guardrails.warns}`);
25
+ if (input.grounding) {
26
+ lines.push('grounding:');
27
+ lines.push(` final_score: ${input.grounding.final_score.toFixed(4)}`);
28
+ lines.push(` threshold: ${input.grounding.threshold.toFixed(2)}`);
29
+ lines.push(` iterations: ${input.grounding.iterations}`);
30
+ lines.push(` passed: ${input.grounding.passed}`);
31
+ }
32
+ lines.push('audit:');
33
+ lines.push(` event_count: ${input.audit.event_count}`);
34
+ lines.push(` chain_root_hash: ${input.audit.chain_root_hash}`);
35
+ lines.push(` audit_log_path: ${input.audit.audit_log_path}`);
36
+ lines.push('```');
37
+ lines.push('');
38
+ lines.push('> **How to verify this artifact yourself:** check out the mesh at the recorded `mesh_sha`, replay the audit log at `audit_log_path`, and confirm the final event’s `event_hash` matches `chain_root_hash`.');
39
+ return lines.join('\n');
40
+ }
@@ -0,0 +1,10 @@
1
+ import type { ObservedArchitecture } from '../../schemas';
2
+ import type { FileInventory } from './clone-and-index';
3
+ export interface AnalyzeArchitectureOpts {
4
+ cloneDir: string;
5
+ targetRepo: string;
6
+ cloneSha: string;
7
+ inventory: FileInventory;
8
+ }
9
+ export declare const ANALYZER_VERSION = "file-based-v1";
10
+ export declare function analyzeArchitecture(opts: AnalyzeArchitectureOpts): ObservedArchitecture;