@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,38 @@
1
+ /**
2
+ * tavily-client — minimal `fetch`-based wrapper around Tavily's `/search`
3
+ * endpoint. Returns normalized results the dedupe_and_rank node can score.
4
+ */
5
+ export interface TavilySearchOpts {
6
+ apiKey: string;
7
+ query: string;
8
+ /** 1..20. Tavily default is 5. */
9
+ maxResults?: number;
10
+ /** "basic" (cheap, fast) or "advanced" (better quality, slower). */
11
+ searchDepth?: 'basic' | 'advanced';
12
+ /** Whether to include domains-only filter etc. — pass through to Tavily. */
13
+ includeDomains?: string[];
14
+ excludeDomains?: string[];
15
+ /** Test injection point; defaults to globalThis.fetch. */
16
+ fetchImpl?: typeof fetch;
17
+ /** Abort timeout (ms). Default 30s. */
18
+ timeoutMs?: number;
19
+ }
20
+ export interface TavilyResult {
21
+ title: string;
22
+ url: string;
23
+ /** Snippet/excerpt that matched the query. */
24
+ content: string;
25
+ /** Tavily relevance score 0..1. */
26
+ score: number;
27
+ /** Publication date if Tavily resolved one (ISO). */
28
+ publishedDate?: string;
29
+ }
30
+ export interface TavilySearchResult {
31
+ query: string;
32
+ results: TavilyResult[];
33
+ /** Total response bytes (for audit). */
34
+ responseBytes: number;
35
+ /** HTTP status. */
36
+ httpStatus: number;
37
+ }
38
+ export declare function tavilySearch(opts: TavilySearchOpts): Promise<TavilySearchResult>;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ /**
3
+ * tavily-client — minimal `fetch`-based wrapper around Tavily's `/search`
4
+ * endpoint. Returns normalized results the dedupe_and_rank node can score.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.tavilySearch = tavilySearch;
8
+ async function tavilySearch(opts) {
9
+ if (!opts.apiKey) {
10
+ throw new Error('TAVILY_API_KEY missing — set the env var or pass apiKey directly');
11
+ }
12
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
13
+ const controller = new AbortController();
14
+ const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 30_000);
15
+ let response;
16
+ try {
17
+ response = await fetchImpl('https://api.tavily.com/search', {
18
+ method: 'POST',
19
+ headers: { 'content-type': 'application/json' },
20
+ body: JSON.stringify({
21
+ api_key: opts.apiKey,
22
+ query: opts.query,
23
+ max_results: opts.maxResults ?? 5,
24
+ search_depth: opts.searchDepth ?? 'basic',
25
+ ...(opts.includeDomains?.length ? { include_domains: opts.includeDomains } : {}),
26
+ ...(opts.excludeDomains?.length ? { exclude_domains: opts.excludeDomains } : {}),
27
+ }),
28
+ signal: controller.signal,
29
+ });
30
+ }
31
+ finally {
32
+ clearTimeout(timer);
33
+ }
34
+ const httpStatus = response.status;
35
+ const rawText = await response.text();
36
+ if (!response.ok) {
37
+ throw new Error(`Tavily returned ${httpStatus}: ${rawText.slice(0, 400)}`);
38
+ }
39
+ const data = JSON.parse(rawText);
40
+ const results = (data.results ?? []).map(r => ({
41
+ title: r.title ?? '',
42
+ url: r.url ?? '',
43
+ content: r.content ?? '',
44
+ score: typeof r.score === 'number' ? r.score : 0,
45
+ publishedDate: r.published_date,
46
+ })).filter(r => r.url.length > 0);
47
+ return {
48
+ query: opts.query,
49
+ results,
50
+ responseBytes: Buffer.byteLength(rawText, 'utf8'),
51
+ httpStatus,
52
+ };
53
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * uspto-client — query USPTO's Open Data Portal for patents related to the
3
+ * research topic, then enrich each hit with its abstract by following the
4
+ * grant / pre-grant publication XML URI.
5
+ *
6
+ * Two-stage pipeline (mirrors the NCMS archeologist_agent.py reference at
7
+ * github.com/AliceNN-ucdenver/ncms/.../archeologist_agent.py#L810):
8
+ *
9
+ * 1. GET https://api.uspto.gov/api/v1/patent/applications/search
10
+ * ?q=<urlencoded-AND-joined-terms>&limit=<n>&offset=0
11
+ * Headers: X-API-Key: <USPTO_API_KEY>, Accept: application/json
12
+ * Returns { patentFileWrapperDataBag: [{ applicationMetaData, grantDocumentMetaData, pgpubDocumentMetaData, ... }] }
13
+ *
14
+ * 2. For each hit, follow `grantDocumentMetaData.fileLocationURI` (or
15
+ * `pgpubDocumentMetaData.fileLocationURI` as fallback) and parse the
16
+ * <abstract> element out of the XML. Stage 2 is best-effort —
17
+ * a missing/failed abstract just leaves abstract: '' on the result
18
+ * and the synthesis prompt falls back on the title alone.
19
+ *
20
+ * Migrated from the deprecated PatentsView v1 endpoint
21
+ * (https://search.patentsview.org/api/v1/patent/) — USPTO has consolidated
22
+ * onto the Open Data Portal at api.uspto.gov. The PatentsView endpoint may
23
+ * still respond intermittently but is no longer the recommended path.
24
+ *
25
+ * Get a key at https://data.uspto.gov/apis/getting-started (USPTO ODP).
26
+ */
27
+ export interface UsptoResult {
28
+ patentNumber: string;
29
+ title: string;
30
+ /** Best abstract / first claim summary, fetched in stage 2. Empty when stage 2 fails. */
31
+ abstract: string;
32
+ url: string;
33
+ grantedAt: string;
34
+ inventors: string[];
35
+ }
36
+ export interface UsptoSearchOpts {
37
+ apiKey: string;
38
+ query: string;
39
+ maxResults?: number;
40
+ fetchImpl?: typeof fetch;
41
+ timeoutMs?: number;
42
+ endpoint?: string;
43
+ }
44
+ export interface UsptoSearchResult {
45
+ query: string;
46
+ results: UsptoResult[];
47
+ responseBytes: number;
48
+ httpStatus: number;
49
+ }
50
+ export declare function usptoSearch(opts: UsptoSearchOpts): Promise<UsptoSearchResult>;
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ /**
3
+ * uspto-client — query USPTO's Open Data Portal for patents related to the
4
+ * research topic, then enrich each hit with its abstract by following the
5
+ * grant / pre-grant publication XML URI.
6
+ *
7
+ * Two-stage pipeline (mirrors the NCMS archeologist_agent.py reference at
8
+ * github.com/AliceNN-ucdenver/ncms/.../archeologist_agent.py#L810):
9
+ *
10
+ * 1. GET https://api.uspto.gov/api/v1/patent/applications/search
11
+ * ?q=<urlencoded-AND-joined-terms>&limit=<n>&offset=0
12
+ * Headers: X-API-Key: <USPTO_API_KEY>, Accept: application/json
13
+ * Returns { patentFileWrapperDataBag: [{ applicationMetaData, grantDocumentMetaData, pgpubDocumentMetaData, ... }] }
14
+ *
15
+ * 2. For each hit, follow `grantDocumentMetaData.fileLocationURI` (or
16
+ * `pgpubDocumentMetaData.fileLocationURI` as fallback) and parse the
17
+ * <abstract> element out of the XML. Stage 2 is best-effort —
18
+ * a missing/failed abstract just leaves abstract: '' on the result
19
+ * and the synthesis prompt falls back on the title alone.
20
+ *
21
+ * Migrated from the deprecated PatentsView v1 endpoint
22
+ * (https://search.patentsview.org/api/v1/patent/) — USPTO has consolidated
23
+ * onto the Open Data Portal at api.uspto.gov. The PatentsView endpoint may
24
+ * still respond intermittently but is no longer the recommended path.
25
+ *
26
+ * Get a key at https://data.uspto.gov/apis/getting-started (USPTO ODP).
27
+ */
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.usptoSearch = usptoSearch;
30
+ const DEFAULT_ENDPOINT = 'https://api.uspto.gov/api/v1/patent/applications/search';
31
+ async function usptoSearch(opts) {
32
+ if (!opts.apiKey) {
33
+ throw new Error('USPTO_API_KEY missing — request one at https://data.uspto.gov/apis/getting-started');
34
+ }
35
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
36
+ const endpoint = opts.endpoint ?? DEFAULT_ENDPOINT;
37
+ const size = Math.min(Math.max(1, opts.maxResults ?? 5), 20);
38
+ // The api.uspto.gov endpoint takes free-text queries with AND operators
39
+ // directly in the q= param (URL-encoded). No JSON DSL.
40
+ const url = `${endpoint}?q=${encodeURIComponent(opts.query)}&limit=${size}&offset=0`;
41
+ const controller = new AbortController();
42
+ const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 30_000);
43
+ let response;
44
+ try {
45
+ response = await fetchImpl(url, {
46
+ method: 'GET',
47
+ headers: {
48
+ accept: 'application/json',
49
+ 'X-API-Key': opts.apiKey,
50
+ },
51
+ signal: controller.signal,
52
+ });
53
+ }
54
+ finally {
55
+ clearTimeout(timer);
56
+ }
57
+ const httpStatus = response.status;
58
+ const rawText = await response.text();
59
+ if (!response.ok) {
60
+ throw new Error(`USPTO ODP returned ${httpStatus}: ${rawText.slice(0, 400)}`);
61
+ }
62
+ const data = JSON.parse(rawText);
63
+ const records = data.patentFileWrapperDataBag ?? [];
64
+ const stage1 = records.map(r => {
65
+ const meta = r.applicationMetaData ?? {};
66
+ const xmlUri = r.grantDocumentMetaData?.fileLocationURI
67
+ || r.pgpubDocumentMetaData?.fileLocationURI
68
+ || '';
69
+ const num = meta.patentNumber || meta.earliestPublicationNumber || '';
70
+ return {
71
+ patentNumber: num,
72
+ title: meta.inventionTitle ?? '',
73
+ abstract: '',
74
+ url: num ? `https://patents.google.com/patent/US${num}` : '',
75
+ grantedAt: meta.grantDate || meta.filingDate || meta.effectiveFilingDate || '',
76
+ inventors: meta.firstInventorName ? [meta.firstInventorName] : [],
77
+ _xmlUri: xmlUri,
78
+ };
79
+ });
80
+ // Stage 2: parallel best-effort abstract fetch. The full-text XML carries
81
+ // the <abstract> element; we regex it out rather than parsing the whole
82
+ // document (the XML is large and we only want the abstract).
83
+ await Promise.all(stage1.map(async (r) => {
84
+ if (!r._xmlUri) {
85
+ return;
86
+ }
87
+ try {
88
+ const xmlRes = await fetchImpl(r._xmlUri, {
89
+ method: 'GET',
90
+ headers: { 'X-API-Key': opts.apiKey, accept: 'application/xml' },
91
+ });
92
+ if (!xmlRes.ok) {
93
+ return;
94
+ }
95
+ const xml = await xmlRes.text();
96
+ const m = xml.match(/<abstract[^>]*>([\s\S]*?)<\/abstract>/i);
97
+ if (m) {
98
+ const stripped = m[1].replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
99
+ r.abstract = stripped.slice(0, 1000);
100
+ }
101
+ }
102
+ catch { /* ignore — best-effort */ }
103
+ }));
104
+ // Drop the internal _xmlUri marker before returning.
105
+ const results = stage1.map(({ _xmlUri: _ignored, ...rest }) => rest);
106
+ return {
107
+ query: opts.query,
108
+ results,
109
+ responseBytes: rawText.length,
110
+ httpStatus,
111
+ };
112
+ }
@@ -0,0 +1,2 @@
1
+ export type RunKind = 'RES' | 'PRD';
2
+ export declare function generateRunId(kind: RunKind, now?: Date): string;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateRunId = generateRunId;
4
+ /**
5
+ * run-id — generate stable, sortable, low-collision run identifiers.
6
+ *
7
+ * Format: `<KIND>-YYYY-MM-DD-<8 hex chars>`
8
+ * KIND ∈ {RES, PRD}
9
+ *
10
+ * The date is the run's UTC start day; the 8-hex tail is the first 4 bytes
11
+ * of a crypto.randomBytes call. 32 bits gives birthday-collision probability
12
+ * around 1 in 65,536 for the same date — more than enough for human-scale
13
+ * mesh-wide audit.
14
+ */
15
+ const node_crypto_1 = require("node:crypto");
16
+ function generateRunId(kind, now = new Date()) {
17
+ const yyyy = now.getUTCFullYear();
18
+ const mm = String(now.getUTCMonth() + 1).padStart(2, '0');
19
+ const dd = String(now.getUTCDate()).padStart(2, '0');
20
+ const tail = (0, node_crypto_1.randomBytes)(4).toString('hex');
21
+ return `${kind}-${yyyy}-${mm}-${dd}-${tail}`;
22
+ }
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@maintainabilityai/research-runner",
3
+ "version": "0.1.1",
4
+ "description": "Research + PRD agent runner — orchestrates the Archeologist and PRD pipelines for the MaintainabilityAI governance mesh",
5
+ "license": "MIT",
6
+ "author": "MaintainabilityAI",
7
+ "homepage": "https://maintainability.ai",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/AliceNN-ucdenver/MaintainabilityAI",
11
+ "directory": "packages/research-runner"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/AliceNN-ucdenver/MaintainabilityAI/issues"
15
+ },
16
+ "keywords": [
17
+ "research",
18
+ "prd",
19
+ "governance",
20
+ "calm",
21
+ "ai-agents",
22
+ "tavily",
23
+ "anthropic"
24
+ ],
25
+ "bin": {
26
+ "research-runner": "./bin/research-runner.js"
27
+ },
28
+ "main": "./dist/cli.js",
29
+ "files": [
30
+ "bin/",
31
+ "dist/",
32
+ "README.md",
33
+ "LICENSE"
34
+ ],
35
+ "engines": {
36
+ "node": ">=20"
37
+ },
38
+ "scripts": {
39
+ "build": "tsc -p tsconfig.json",
40
+ "test": "node --test --import tsx 'src/**/*.test.ts'",
41
+ "typecheck": "tsc -p tsconfig.json --noEmit"
42
+ },
43
+ "dependencies": {
44
+ "js-yaml": "^4.1.0",
45
+ "zod": "^3.25.76"
46
+ },
47
+ "devDependencies": {
48
+ "@types/js-yaml": "^4.0.9",
49
+ "@types/node": "^20.11.0",
50
+ "tsx": "^4.19.0",
51
+ "typescript": "^5.5.0"
52
+ }
53
+ }