nexus-agents 2.113.0 → 2.114.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{child-mcp-config-UMNWY25S.js → child-mcp-config-BTYHNG2N.js} +2 -2
- package/dist/{chunk-5NE37CDW.js → chunk-36G75SD2.js} +1243 -3339
- package/dist/chunk-36G75SD2.js.map +1 -0
- package/dist/{chunk-5OOFU3EY.js → chunk-3S4N6OCF.js} +2 -2
- package/dist/{chunk-7E2ZCYKA.js → chunk-4K4OVG4O.js} +2 -2
- package/dist/{chunk-5EH7WMME.js → chunk-5RDUP5K7.js} +2 -2
- package/dist/{chunk-3XGPHTO6.js → chunk-7AO4FKAD.js} +2 -2
- package/dist/{chunk-P2K5GCLL.js → chunk-7BZKZUVR.js} +4 -4
- package/dist/{chunk-5KBR7SYI.js → chunk-AREFHG4Q.js} +7 -7
- package/dist/chunk-ATI7W2FT.js +2474 -0
- package/dist/chunk-ATI7W2FT.js.map +1 -0
- package/dist/{chunk-OMZD4OZP.js → chunk-B32GCV25.js} +5 -5
- package/dist/{chunk-LUCPCWIZ.js → chunk-BXEOO2KK.js} +2 -2
- package/dist/{chunk-RPWYEJ66.js → chunk-CLL2GXNL.js} +4 -4
- package/dist/chunk-E2ER3LQF.js +149 -0
- package/dist/chunk-E2ER3LQF.js.map +1 -0
- package/dist/{chunk-TIOXPJIO.js → chunk-E4HZTSBP.js} +2 -2
- package/dist/{chunk-SXG2NPZP.js → chunk-F6NOHVYQ.js} +3 -3
- package/dist/{chunk-JPCEQZPE.js → chunk-FSZNZZO5.js} +7 -7
- package/dist/{chunk-NCWD4UOI.js → chunk-IBRRXLMJ.js} +7 -7
- package/dist/{chunk-FSODHLOV.js → chunk-KTPMJSLZ.js} +3 -3
- package/dist/{chunk-2JIV7Q6A.js → chunk-MD6TVSOI.js} +3 -3
- package/dist/{chunk-WDOJESN6.js → chunk-MYN7CMD5.js} +2 -2
- package/dist/{chunk-ZSJG7EB2.js → chunk-N7NCTB3O.js} +5 -144
- package/dist/chunk-N7NCTB3O.js.map +1 -0
- package/dist/{chunk-5VGGVVPG.js → chunk-OTRXF5NM.js} +3 -3
- package/dist/{chunk-3DB4DEFY.js → chunk-Q6HUW6EI.js} +2 -2
- package/dist/{chunk-VV47MWHN.js → chunk-R2G4GZID.js} +3 -3
- package/dist/{chunk-AWXWDGG5.js → chunk-S7RTQRQ3.js} +2 -2
- package/dist/{chunk-N2HYXBQ6.js → chunk-VNU7OT7O.js} +2 -2
- package/dist/{chunk-VQVKI4XO.js → chunk-WJSQ5FRV.js} +2 -2
- package/dist/{chunk-F2ULTSZ6.js → chunk-XMNGHZBR.js} +7 -7
- package/dist/{chunk-2QBR4RVO.js → chunk-YJSXYHSK.js} +2 -2
- package/dist/{chunk-TI2BJ2OB.js → chunk-YKNLQLFM.js} +6 -6
- package/dist/{chunk-LMMMFZ64.js → chunk-YQW7MHST.js} +2 -2
- package/dist/{chunk-JRBUSUYD.js → chunk-ZXF2XA4R.js} +3 -3
- package/dist/{cli-circuit-breaker-XB5FPZJ5.js → cli-circuit-breaker-OR2Z2ZXU.js} +4 -4
- package/dist/cli.js +66 -63
- package/dist/cli.js.map +1 -1
- package/dist/{composite-router-VWQ65U6B.js → composite-router-QC4OLIQ5.js} +2 -2
- package/dist/{consensus-vote-EW5HWO6Z.js → consensus-vote-JMIK4USL.js} +12 -12
- package/dist/context-retriever-CFINY4KH.js +24 -0
- package/dist/{doctor-deep-RFHTNO2P.js → doctor-deep-53KZ3IYQ.js} +3 -3
- package/dist/{expert-bridge-LDWFPVHK.js → expert-bridge-WWAGNKZY.js} +4 -4
- package/dist/factory-3FKPAH26.js +21 -0
- package/dist/{factory-IYL24RFV.js → factory-KIGMY2XP.js} +5 -5
- package/dist/{improvement-review-7R5Q4OD3.js → improvement-review-A4HQ7KIQ.js} +4 -4
- package/dist/index.d.ts +8 -8
- package/dist/index.js +31 -30
- package/dist/index.js.map +1 -1
- package/dist/{init-opencode-26HTJ5QO.js → init-opencode-IMP5HG2J.js} +5 -5
- package/dist/{issue-triage-MVLE3SRA.js → issue-triage-VPL3N2AG.js} +5 -5
- package/dist/{pr-reviewer-helpers-HXEJ437Q.js → pr-reviewer-helpers-6FGXGRYA.js} +4 -4
- package/dist/{registry-command-RQH4YGK5.js → registry-command-IGVBSZX6.js} +2 -2
- package/dist/{repo-security-plan-SIOA64KN.js → repo-security-plan-PWFK3LCK.js} +3 -3
- package/dist/{research-helpers-synthesize-K43DWQVG.js → research-helpers-synthesize-IXWXFRKC.js} +4 -3
- package/dist/{routing-memory-AEYB4NPO.js → routing-memory-RQUM4XHT.js} +2 -2
- package/dist/{session-memory-GZOSVFDD.js → session-memory-7TDKNMRE.js} +3 -3
- package/dist/{setup-command-UZTCNASI.js → setup-command-MWZ5M3UB.js} +11 -11
- package/dist/{setup-config-R7GWRUW2.js → setup-config-ICI5OIGW.js} +3 -3
- package/dist/{setup-custom-api-DXS3Y4XW.js → setup-custom-api-IVJLG6WI.js} +3 -3
- package/dist/{tool-memory-CXHXCI6Q.js → tool-memory-USBUCDTD.js} +5 -5
- package/dist/{weather-report-PYDBRULK.js → weather-report-ZLUBKEG3.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-5NE37CDW.js.map +0 -1
- package/dist/chunk-KWHJL7HX.js +0 -276
- package/dist/chunk-KWHJL7HX.js.map +0 -1
- package/dist/chunk-ZSJG7EB2.js.map +0 -1
- package/dist/context-retriever-5VFAIPHM.js +0 -19
- package/dist/factory-AIERXI3O.js +0 -21
- /package/dist/{child-mcp-config-UMNWY25S.js.map → child-mcp-config-BTYHNG2N.js.map} +0 -0
- /package/dist/{chunk-5OOFU3EY.js.map → chunk-3S4N6OCF.js.map} +0 -0
- /package/dist/{chunk-7E2ZCYKA.js.map → chunk-4K4OVG4O.js.map} +0 -0
- /package/dist/{chunk-5EH7WMME.js.map → chunk-5RDUP5K7.js.map} +0 -0
- /package/dist/{chunk-3XGPHTO6.js.map → chunk-7AO4FKAD.js.map} +0 -0
- /package/dist/{chunk-P2K5GCLL.js.map → chunk-7BZKZUVR.js.map} +0 -0
- /package/dist/{chunk-5KBR7SYI.js.map → chunk-AREFHG4Q.js.map} +0 -0
- /package/dist/{chunk-OMZD4OZP.js.map → chunk-B32GCV25.js.map} +0 -0
- /package/dist/{chunk-LUCPCWIZ.js.map → chunk-BXEOO2KK.js.map} +0 -0
- /package/dist/{chunk-RPWYEJ66.js.map → chunk-CLL2GXNL.js.map} +0 -0
- /package/dist/{chunk-TIOXPJIO.js.map → chunk-E4HZTSBP.js.map} +0 -0
- /package/dist/{chunk-SXG2NPZP.js.map → chunk-F6NOHVYQ.js.map} +0 -0
- /package/dist/{chunk-JPCEQZPE.js.map → chunk-FSZNZZO5.js.map} +0 -0
- /package/dist/{chunk-NCWD4UOI.js.map → chunk-IBRRXLMJ.js.map} +0 -0
- /package/dist/{chunk-FSODHLOV.js.map → chunk-KTPMJSLZ.js.map} +0 -0
- /package/dist/{chunk-2JIV7Q6A.js.map → chunk-MD6TVSOI.js.map} +0 -0
- /package/dist/{chunk-WDOJESN6.js.map → chunk-MYN7CMD5.js.map} +0 -0
- /package/dist/{chunk-5VGGVVPG.js.map → chunk-OTRXF5NM.js.map} +0 -0
- /package/dist/{chunk-3DB4DEFY.js.map → chunk-Q6HUW6EI.js.map} +0 -0
- /package/dist/{chunk-VV47MWHN.js.map → chunk-R2G4GZID.js.map} +0 -0
- /package/dist/{chunk-AWXWDGG5.js.map → chunk-S7RTQRQ3.js.map} +0 -0
- /package/dist/{chunk-N2HYXBQ6.js.map → chunk-VNU7OT7O.js.map} +0 -0
- /package/dist/{chunk-VQVKI4XO.js.map → chunk-WJSQ5FRV.js.map} +0 -0
- /package/dist/{chunk-F2ULTSZ6.js.map → chunk-XMNGHZBR.js.map} +0 -0
- /package/dist/{chunk-2QBR4RVO.js.map → chunk-YJSXYHSK.js.map} +0 -0
- /package/dist/{chunk-TI2BJ2OB.js.map → chunk-YKNLQLFM.js.map} +0 -0
- /package/dist/{chunk-LMMMFZ64.js.map → chunk-YQW7MHST.js.map} +0 -0
- /package/dist/{chunk-JRBUSUYD.js.map → chunk-ZXF2XA4R.js.map} +0 -0
- /package/dist/{cli-circuit-breaker-XB5FPZJ5.js.map → cli-circuit-breaker-OR2Z2ZXU.js.map} +0 -0
- /package/dist/{composite-router-VWQ65U6B.js.map → composite-router-QC4OLIQ5.js.map} +0 -0
- /package/dist/{consensus-vote-EW5HWO6Z.js.map → consensus-vote-JMIK4USL.js.map} +0 -0
- /package/dist/{context-retriever-5VFAIPHM.js.map → context-retriever-CFINY4KH.js.map} +0 -0
- /package/dist/{doctor-deep-RFHTNO2P.js.map → doctor-deep-53KZ3IYQ.js.map} +0 -0
- /package/dist/{expert-bridge-LDWFPVHK.js.map → expert-bridge-WWAGNKZY.js.map} +0 -0
- /package/dist/{factory-AIERXI3O.js.map → factory-3FKPAH26.js.map} +0 -0
- /package/dist/{factory-IYL24RFV.js.map → factory-KIGMY2XP.js.map} +0 -0
- /package/dist/{improvement-review-7R5Q4OD3.js.map → improvement-review-A4HQ7KIQ.js.map} +0 -0
- /package/dist/{init-opencode-26HTJ5QO.js.map → init-opencode-IMP5HG2J.js.map} +0 -0
- /package/dist/{issue-triage-MVLE3SRA.js.map → issue-triage-VPL3N2AG.js.map} +0 -0
- /package/dist/{pr-reviewer-helpers-HXEJ437Q.js.map → pr-reviewer-helpers-6FGXGRYA.js.map} +0 -0
- /package/dist/{registry-command-RQH4YGK5.js.map → registry-command-IGVBSZX6.js.map} +0 -0
- /package/dist/{repo-security-plan-SIOA64KN.js.map → repo-security-plan-PWFK3LCK.js.map} +0 -0
- /package/dist/{research-helpers-synthesize-K43DWQVG.js.map → research-helpers-synthesize-IXWXFRKC.js.map} +0 -0
- /package/dist/{routing-memory-AEYB4NPO.js.map → routing-memory-RQUM4XHT.js.map} +0 -0
- /package/dist/{session-memory-GZOSVFDD.js.map → session-memory-7TDKNMRE.js.map} +0 -0
- /package/dist/{setup-command-UZTCNASI.js.map → setup-command-MWZ5M3UB.js.map} +0 -0
- /package/dist/{setup-config-R7GWRUW2.js.map → setup-config-ICI5OIGW.js.map} +0 -0
- /package/dist/{setup-custom-api-DXS3Y4XW.js.map → setup-custom-api-IVJLG6WI.js.map} +0 -0
- /package/dist/{tool-memory-CXHXCI6Q.js.map → tool-memory-USBUCDTD.js.map} +0 -0
- /package/dist/{weather-report-PYDBRULK.js.map → weather-report-ZLUBKEG3.js.map} +0 -0
|
@@ -0,0 +1,2474 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveToken
|
|
3
|
+
} from "./chunk-YJSXYHSK.js";
|
|
4
|
+
import {
|
|
5
|
+
REGISTRY_PATH,
|
|
6
|
+
getProjectRoot,
|
|
7
|
+
loadPapersRegistry,
|
|
8
|
+
loadTechniquesRegistry,
|
|
9
|
+
savePapersRegistry
|
|
10
|
+
} from "./chunk-E2ER3LQF.js";
|
|
11
|
+
import {
|
|
12
|
+
getToolMemory
|
|
13
|
+
} from "./chunk-7BZKZUVR.js";
|
|
14
|
+
import {
|
|
15
|
+
resolveInsideRoot
|
|
16
|
+
} from "./chunk-NUBSJGQZ.js";
|
|
17
|
+
import {
|
|
18
|
+
capitalizeWords
|
|
19
|
+
} from "./chunk-6T3EPABN.js";
|
|
20
|
+
import {
|
|
21
|
+
API_TIMEOUTS,
|
|
22
|
+
CLI_NAMES,
|
|
23
|
+
CLI_SUBPROCESS_TIMEOUTS,
|
|
24
|
+
StrategyDistiller,
|
|
25
|
+
createLogger,
|
|
26
|
+
getErrorMessage,
|
|
27
|
+
getOutcomeStore,
|
|
28
|
+
getTimeProvider,
|
|
29
|
+
registerPersistentDistillerFactory
|
|
30
|
+
} from "./chunk-ZXF2XA4R.js";
|
|
31
|
+
import {
|
|
32
|
+
ensureLearningDir,
|
|
33
|
+
getRulesFile
|
|
34
|
+
} from "./chunk-CH7QIDHQ.js";
|
|
35
|
+
|
|
36
|
+
// src/learning/strategy-distiller-persistence.ts
|
|
37
|
+
import { writeFileSync, readFileSync, renameSync, unlinkSync, existsSync } from "fs";
|
|
38
|
+
import { dirname } from "path";
|
|
39
|
+
import { z } from "zod";
|
|
40
|
+
var DistilledRuleSchema = z.object({
|
|
41
|
+
id: z.string(),
|
|
42
|
+
patternType: z.enum(["failure-rate", "success-rate", "latency-spike"]),
|
|
43
|
+
cli: z.enum(CLI_NAMES),
|
|
44
|
+
category: z.string(),
|
|
45
|
+
action: z.enum(["penalize", "boost", "avoid"]),
|
|
46
|
+
confidence: z.number(),
|
|
47
|
+
observationCount: z.number(),
|
|
48
|
+
metric: z.number(),
|
|
49
|
+
status: z.enum(["draft", "active", "promoted", "expired"]),
|
|
50
|
+
createdAt: z.number(),
|
|
51
|
+
updatedAt: z.number(),
|
|
52
|
+
tainted: z.boolean()
|
|
53
|
+
});
|
|
54
|
+
var RulesSnapshotSchema = z.object({
|
|
55
|
+
version: z.literal(1),
|
|
56
|
+
savedAt: z.string(),
|
|
57
|
+
rules: z.array(DistilledRuleSchema)
|
|
58
|
+
});
|
|
59
|
+
var PersistentStrategyDistiller = class extends StrategyDistiller {
|
|
60
|
+
filePath;
|
|
61
|
+
persistLogger;
|
|
62
|
+
constructor(outcomeStore, persistConfig, logger, distillerConfig) {
|
|
63
|
+
super(outcomeStore, logger, distillerConfig);
|
|
64
|
+
this.filePath = persistConfig?.filePath ?? getRulesFile();
|
|
65
|
+
this.persistLogger = logger ?? createLogger({ component: "PersistentStrategyDistiller" });
|
|
66
|
+
const dataDir = persistConfig?.dataDir;
|
|
67
|
+
ensureLearningDir(dataDir);
|
|
68
|
+
this.hydrate();
|
|
69
|
+
}
|
|
70
|
+
/** Override distill to persist rules after each run. */
|
|
71
|
+
distill() {
|
|
72
|
+
super.distill();
|
|
73
|
+
this.saveSnapshot();
|
|
74
|
+
}
|
|
75
|
+
// ==========================================================================
|
|
76
|
+
// Private
|
|
77
|
+
// ==========================================================================
|
|
78
|
+
hydrate() {
|
|
79
|
+
if (!existsSync(this.filePath)) {
|
|
80
|
+
this.persistLogger.debug("No rules file found, starting fresh", {
|
|
81
|
+
path: this.filePath
|
|
82
|
+
});
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const content = readFileSync(this.filePath, "utf-8");
|
|
87
|
+
const parsed = JSON.parse(content);
|
|
88
|
+
const result = RulesSnapshotSchema.safeParse(parsed);
|
|
89
|
+
if (!result.success) {
|
|
90
|
+
this.persistLogger.warn("Rules file failed validation, starting fresh", {
|
|
91
|
+
path: this.filePath,
|
|
92
|
+
error: result.error.message
|
|
93
|
+
});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
this.loadRules(result.data.rules);
|
|
97
|
+
this.persistLogger.info("Hydrated distilled rules from disk", {
|
|
98
|
+
ruleCount: result.data.rules.length,
|
|
99
|
+
savedAt: result.data.savedAt,
|
|
100
|
+
path: this.filePath
|
|
101
|
+
});
|
|
102
|
+
} catch (error) {
|
|
103
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
104
|
+
this.persistLogger.warn("Failed to hydrate rules from disk", {
|
|
105
|
+
error: msg,
|
|
106
|
+
path: this.filePath
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
saveSnapshot() {
|
|
111
|
+
const rules = this.getRules();
|
|
112
|
+
const snapshot = {
|
|
113
|
+
version: 1,
|
|
114
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
115
|
+
rules
|
|
116
|
+
};
|
|
117
|
+
const tmpPath = `${this.filePath}.tmp.${String(process.pid)}`;
|
|
118
|
+
try {
|
|
119
|
+
ensureLearningDir(dirname(this.filePath));
|
|
120
|
+
writeFileSync(tmpPath, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
121
|
+
renameSync(tmpPath, this.filePath);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
124
|
+
this.persistLogger.warn("Failed to persist rules to disk", {
|
|
125
|
+
error: msg,
|
|
126
|
+
path: this.filePath
|
|
127
|
+
});
|
|
128
|
+
try {
|
|
129
|
+
if (existsSync(tmpPath)) unlinkSync(tmpPath);
|
|
130
|
+
} catch (cleanupErr) {
|
|
131
|
+
const msg2 = cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr);
|
|
132
|
+
this.persistLogger.debug("Failed to clean up temp file during persist", {
|
|
133
|
+
path: tmpPath,
|
|
134
|
+
error: msg2
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
registerPersistentDistillerFactory(
|
|
141
|
+
(outcomeStore, logger) => new PersistentStrategyDistiller(outcomeStore, void 0, logger)
|
|
142
|
+
);
|
|
143
|
+
function loadPersistedRules(filePath = getRulesFile()) {
|
|
144
|
+
if (!existsSync(filePath)) return [];
|
|
145
|
+
try {
|
|
146
|
+
const content = readFileSync(filePath, "utf-8");
|
|
147
|
+
const parsed = JSON.parse(content);
|
|
148
|
+
const result = RulesSnapshotSchema.safeParse(parsed);
|
|
149
|
+
if (!result.success) return [];
|
|
150
|
+
return result.data.rules;
|
|
151
|
+
} catch {
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/cli/research-helpers-status.ts
|
|
157
|
+
function toStatusSummary(id, entry) {
|
|
158
|
+
return {
|
|
159
|
+
id,
|
|
160
|
+
name: entry.name,
|
|
161
|
+
status: entry.status,
|
|
162
|
+
priority: entry.priority,
|
|
163
|
+
topic: entry.topic,
|
|
164
|
+
implementationIssue: entry.implementation_issue
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function filterByStatus(techniques, status) {
|
|
168
|
+
return Object.entries(techniques).filter(([, entry]) => status === "all" || entry.status === status).map(([id, entry]) => toStatusSummary(id, entry)).sort((a, b) => {
|
|
169
|
+
const priorityOrder = { P1: 0, P2: 1, P3: 2, P4: 3 };
|
|
170
|
+
const aPriority = a.priority !== null ? priorityOrder[a.priority] ?? 4 : 4;
|
|
171
|
+
const bPriority = b.priority !== null ? priorityOrder[b.priority] ?? 4 : 4;
|
|
172
|
+
if (aPriority !== bPriority) return aPriority - bPriority;
|
|
173
|
+
return a.name.localeCompare(b.name);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
function countByStatus(techniques) {
|
|
177
|
+
let implemented = 0;
|
|
178
|
+
let planned = 0;
|
|
179
|
+
let notStarted = 0;
|
|
180
|
+
let rejected = 0;
|
|
181
|
+
for (const entry of Object.values(techniques)) {
|
|
182
|
+
switch (entry.status) {
|
|
183
|
+
case "implemented":
|
|
184
|
+
implemented++;
|
|
185
|
+
break;
|
|
186
|
+
case "planned":
|
|
187
|
+
case "in-progress":
|
|
188
|
+
planned++;
|
|
189
|
+
break;
|
|
190
|
+
case "not-started":
|
|
191
|
+
notStarted++;
|
|
192
|
+
break;
|
|
193
|
+
case "rejected":
|
|
194
|
+
rejected++;
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
implemented,
|
|
200
|
+
planned,
|
|
201
|
+
notStarted,
|
|
202
|
+
rejected,
|
|
203
|
+
total: implemented + planned + notStarted + rejected
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
async function getResearchStatus(options) {
|
|
207
|
+
const result = await loadTechniquesRegistry();
|
|
208
|
+
if (!result.ok) {
|
|
209
|
+
return {
|
|
210
|
+
success: false,
|
|
211
|
+
techniques: [],
|
|
212
|
+
counts: { implemented: 0, planned: 0, notStarted: 0, rejected: 0, total: 0 }
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
const registry = result.value;
|
|
216
|
+
if (options.techniqueId !== void 0 && options.techniqueId !== "") {
|
|
217
|
+
const entry = registry.techniques[options.techniqueId];
|
|
218
|
+
if (entry === void 0) {
|
|
219
|
+
return {
|
|
220
|
+
success: false,
|
|
221
|
+
techniques: [],
|
|
222
|
+
counts: countByStatus(registry.techniques)
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
success: true,
|
|
227
|
+
techniques: [toStatusSummary(options.techniqueId, entry)],
|
|
228
|
+
counts: countByStatus(registry.techniques)
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
const statusFilter = options.status === "all" ? "all" : options.status;
|
|
232
|
+
const techniques = filterByStatus(registry.techniques, statusFilter);
|
|
233
|
+
return {
|
|
234
|
+
success: true,
|
|
235
|
+
techniques,
|
|
236
|
+
counts: countByStatus(registry.techniques)
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function formatStatusResult(result, format) {
|
|
240
|
+
if (format === "json") {
|
|
241
|
+
return JSON.stringify(result, null, 2);
|
|
242
|
+
}
|
|
243
|
+
if (format === "compact") {
|
|
244
|
+
const lines2 = result.techniques.map(
|
|
245
|
+
(t) => `${t.status.padEnd(12)} ${(t.priority ?? "-").padEnd(3)} ${t.id}`
|
|
246
|
+
);
|
|
247
|
+
return lines2.join("\n");
|
|
248
|
+
}
|
|
249
|
+
const lines = [];
|
|
250
|
+
lines.push("Research Registry Status");
|
|
251
|
+
lines.push("=".repeat(60));
|
|
252
|
+
lines.push("");
|
|
253
|
+
const { counts } = result;
|
|
254
|
+
lines.push(
|
|
255
|
+
`Implemented: ${String(counts.implemented)} | Planned: ${String(counts.planned)} | Not Started: ${String(counts.notStarted)} | Rejected: ${String(counts.rejected)}`
|
|
256
|
+
);
|
|
257
|
+
lines.push(`Total: ${String(counts.total)}`);
|
|
258
|
+
lines.push("");
|
|
259
|
+
if (result.techniques.length === 0) {
|
|
260
|
+
lines.push("No techniques found matching criteria.");
|
|
261
|
+
return lines.join("\n");
|
|
262
|
+
}
|
|
263
|
+
lines.push("Status | Pri | Topic | ID");
|
|
264
|
+
lines.push("-".repeat(60));
|
|
265
|
+
for (const tech of result.techniques) {
|
|
266
|
+
const status = tech.status.padEnd(12);
|
|
267
|
+
const priority = (tech.priority ?? "-").padEnd(3);
|
|
268
|
+
const topic = tech.topic.slice(0, 14).padEnd(14);
|
|
269
|
+
lines.push(`${status} | ${priority} | ${topic} | ${tech.id}`);
|
|
270
|
+
}
|
|
271
|
+
return lines.join("\n");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/cli/research-helpers-overlap.ts
|
|
275
|
+
function calculateTagOverlap(tags1, tags2) {
|
|
276
|
+
const set1 = new Set(tags1);
|
|
277
|
+
const set2 = new Set(tags2);
|
|
278
|
+
const intersection = [...set1].filter((tag) => set2.has(tag));
|
|
279
|
+
const union = /* @__PURE__ */ new Set([...set1, ...set2]);
|
|
280
|
+
return union.size === 0 ? 0 : intersection.length / union.size;
|
|
281
|
+
}
|
|
282
|
+
function findSharedTags(tags1, tags2) {
|
|
283
|
+
const set2 = new Set(tags2);
|
|
284
|
+
return tags1.filter((tag) => set2.has(tag));
|
|
285
|
+
}
|
|
286
|
+
function determineRelationship(source, target, overlapScore) {
|
|
287
|
+
if (source.topic === target.topic && overlapScore > 0.5) {
|
|
288
|
+
return "overlapping";
|
|
289
|
+
}
|
|
290
|
+
if (source.topic === target.topic) {
|
|
291
|
+
return "complementary";
|
|
292
|
+
}
|
|
293
|
+
if (overlapScore > 0.3) {
|
|
294
|
+
return "enhances";
|
|
295
|
+
}
|
|
296
|
+
return "complementary";
|
|
297
|
+
}
|
|
298
|
+
async function findOverlaps(options) {
|
|
299
|
+
const result = await loadTechniquesRegistry();
|
|
300
|
+
if (!result.ok) {
|
|
301
|
+
return {
|
|
302
|
+
success: false,
|
|
303
|
+
sourceId: options.techniqueId,
|
|
304
|
+
matches: [],
|
|
305
|
+
suggestedAlignments: []
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
const registry = result.value;
|
|
309
|
+
const sourceEntry = registry.techniques[options.techniqueId];
|
|
310
|
+
if (!sourceEntry) {
|
|
311
|
+
return {
|
|
312
|
+
success: false,
|
|
313
|
+
sourceId: options.techniqueId,
|
|
314
|
+
matches: [],
|
|
315
|
+
suggestedAlignments: []
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
const matches = [];
|
|
319
|
+
for (const [id, entry] of Object.entries(registry.techniques)) {
|
|
320
|
+
if (id === options.techniqueId) continue;
|
|
321
|
+
const overlapScore = calculateTagOverlap(sourceEntry.tags, entry.tags);
|
|
322
|
+
const sharedTopic = sourceEntry.topic === entry.topic;
|
|
323
|
+
if (overlapScore >= options.threshold || sharedTopic) {
|
|
324
|
+
matches.push({
|
|
325
|
+
techniqueId: id,
|
|
326
|
+
name: entry.name,
|
|
327
|
+
overlapScore,
|
|
328
|
+
sharedTags: findSharedTags(sourceEntry.tags, entry.tags),
|
|
329
|
+
sharedTopic,
|
|
330
|
+
relationship: determineRelationship(sourceEntry, entry, overlapScore)
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
matches.sort((a, b) => b.overlapScore - a.overlapScore);
|
|
335
|
+
const suggestedAlignments = matches.filter((m) => m.relationship === "overlapping" || m.relationship === "enhances").slice(0, 3).map((m) => `${options.techniqueId} -> ${m.techniqueId}: ${m.relationship}`);
|
|
336
|
+
return {
|
|
337
|
+
success: true,
|
|
338
|
+
sourceId: options.techniqueId,
|
|
339
|
+
matches,
|
|
340
|
+
suggestedAlignments
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
function formatOverlapResult(result, format) {
|
|
344
|
+
if (format === "json") {
|
|
345
|
+
return JSON.stringify(result, null, 2);
|
|
346
|
+
}
|
|
347
|
+
const lines = [];
|
|
348
|
+
lines.push(`Overlap Analysis: ${result.sourceId}`);
|
|
349
|
+
lines.push("=".repeat(60));
|
|
350
|
+
lines.push("");
|
|
351
|
+
if (!result.success) {
|
|
352
|
+
lines.push(`Technique '${result.sourceId}' not found in registry.`);
|
|
353
|
+
return lines.join("\n");
|
|
354
|
+
}
|
|
355
|
+
if (result.matches.length === 0) {
|
|
356
|
+
lines.push("No overlapping techniques found.");
|
|
357
|
+
return lines.join("\n");
|
|
358
|
+
}
|
|
359
|
+
lines.push(`Found ${String(result.matches.length)} related technique(s):`);
|
|
360
|
+
lines.push("");
|
|
361
|
+
for (const match of result.matches) {
|
|
362
|
+
const score = (match.overlapScore * 100).toFixed(0);
|
|
363
|
+
lines.push(` ${match.techniqueId}`);
|
|
364
|
+
lines.push(` Name: ${match.name}`);
|
|
365
|
+
lines.push(` Overlap: ${score}% | Relationship: ${match.relationship}`);
|
|
366
|
+
if (match.sharedTags.length > 0) {
|
|
367
|
+
lines.push(` Shared tags: ${match.sharedTags.join(", ")}`);
|
|
368
|
+
}
|
|
369
|
+
lines.push("");
|
|
370
|
+
}
|
|
371
|
+
if (result.suggestedAlignments.length > 0) {
|
|
372
|
+
lines.push("Suggested alignments.yaml entries:");
|
|
373
|
+
for (const alignment of result.suggestedAlignments) {
|
|
374
|
+
lines.push(` - ${alignment}`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return lines.join("\n");
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// src/research/research-quality.ts
|
|
381
|
+
var TIER_3_VENUES = /* @__PURE__ */ new Set([
|
|
382
|
+
"neurips",
|
|
383
|
+
"nips",
|
|
384
|
+
"icml",
|
|
385
|
+
"iclr",
|
|
386
|
+
"aaai",
|
|
387
|
+
"acl",
|
|
388
|
+
"emnlp",
|
|
389
|
+
"cvpr",
|
|
390
|
+
"iccv",
|
|
391
|
+
"eccv",
|
|
392
|
+
"sigir",
|
|
393
|
+
"kdd",
|
|
394
|
+
"www",
|
|
395
|
+
"icse",
|
|
396
|
+
"fse"
|
|
397
|
+
]);
|
|
398
|
+
var TIER_2_VENUES = /* @__PURE__ */ new Set([
|
|
399
|
+
"naacl",
|
|
400
|
+
"coling",
|
|
401
|
+
"eacl",
|
|
402
|
+
"ijcai",
|
|
403
|
+
"ecai",
|
|
404
|
+
"aistats",
|
|
405
|
+
"uai",
|
|
406
|
+
"colt",
|
|
407
|
+
"interspeech",
|
|
408
|
+
"ase",
|
|
409
|
+
"issta"
|
|
410
|
+
]);
|
|
411
|
+
function classifyVenue(venue) {
|
|
412
|
+
if (venue === null || venue === void 0 || venue.length === 0) return 0;
|
|
413
|
+
const normalized = venue.toLowerCase().replace(/[^a-z]/g, "");
|
|
414
|
+
if (TIER_3_VENUES.has(normalized)) return 3;
|
|
415
|
+
if (TIER_2_VENUES.has(normalized)) return 2;
|
|
416
|
+
if (!normalized.includes("arxiv")) return 1;
|
|
417
|
+
return 0;
|
|
418
|
+
}
|
|
419
|
+
function recencyBoost(publicationDate) {
|
|
420
|
+
if (publicationDate === void 0) return 0;
|
|
421
|
+
const now = /* @__PURE__ */ new Date();
|
|
422
|
+
const pubDate = new Date(publicationDate);
|
|
423
|
+
const monthsAgo = (now.getTime() - pubDate.getTime()) / (30 * 24 * 60 * 60 * 1e3);
|
|
424
|
+
if (monthsAgo < 6) return 2;
|
|
425
|
+
if (monthsAgo < 12) return 1;
|
|
426
|
+
return 0;
|
|
427
|
+
}
|
|
428
|
+
function citationScore(count) {
|
|
429
|
+
if (count === void 0 || count === 0) return 0;
|
|
430
|
+
if (count < 10) return 1;
|
|
431
|
+
if (count < 100) return 2;
|
|
432
|
+
return 3;
|
|
433
|
+
}
|
|
434
|
+
var PREPRINT_CAP_CITATION_BYPASS = 100;
|
|
435
|
+
var PREPRINT_QUALITY_CAP = 6;
|
|
436
|
+
function isPreprintOnly(paper) {
|
|
437
|
+
const venueTier = paper.venue_tier ?? classifyVenue(paper.venue);
|
|
438
|
+
const hasNoVenue = paper.venue === null || paper.venue === void 0 || paper.venue.length === 0;
|
|
439
|
+
return (paper.source === "arxiv" || paper.source === "preprint") && hasNoVenue && venueTier === 0;
|
|
440
|
+
}
|
|
441
|
+
function computeQualityScore(paper) {
|
|
442
|
+
const citations = citationScore(paper.citation_count);
|
|
443
|
+
const venue = paper.venue_tier ?? classifyVenue(paper.venue);
|
|
444
|
+
const code = paper.has_code === true ? 2 : 0;
|
|
445
|
+
const recency = recencyBoost(paper.publication_date);
|
|
446
|
+
let score = Math.min(10, citations + venue + code + recency);
|
|
447
|
+
const citationCount = paper.citation_count ?? 0;
|
|
448
|
+
if (isPreprintOnly(paper) && citationCount < PREPRINT_CAP_CITATION_BYPASS) {
|
|
449
|
+
score = Math.min(score, PREPRINT_QUALITY_CAP);
|
|
450
|
+
}
|
|
451
|
+
return score;
|
|
452
|
+
}
|
|
453
|
+
function computeEvidenceTier(paper) {
|
|
454
|
+
const score = paper.quality_score ?? computeQualityScore(paper);
|
|
455
|
+
const tags = new Set(paper.rigor_tags);
|
|
456
|
+
if (tags.has("peer-reviewed") && tags.has("has-code") && tags.has("has-baselines")) {
|
|
457
|
+
return "high";
|
|
458
|
+
}
|
|
459
|
+
if (score >= 7) return "high";
|
|
460
|
+
if (tags.has("has-code") || score >= 4) return "medium";
|
|
461
|
+
return "low";
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// src/cli/research-helpers-registry.ts
|
|
465
|
+
function getCurrentDate() {
|
|
466
|
+
const now = new Date(getTimeProvider().now());
|
|
467
|
+
return now.toLocaleDateString("en-CA", { timeZone: "America/New_York" });
|
|
468
|
+
}
|
|
469
|
+
function extractPublicationDate(arxivId) {
|
|
470
|
+
const match = /^(\d{2})(\d{2})\./.exec(arxivId);
|
|
471
|
+
const yearStr = match?.[1];
|
|
472
|
+
const monthStr = match?.[2];
|
|
473
|
+
if (yearStr === void 0 || monthStr === void 0) {
|
|
474
|
+
return "";
|
|
475
|
+
}
|
|
476
|
+
const year = parseInt(yearStr, 10);
|
|
477
|
+
const month = monthStr;
|
|
478
|
+
const fullYear = year < 50 ? 2e3 + year : 1900 + year;
|
|
479
|
+
return `${String(fullYear)}-${month}`;
|
|
480
|
+
}
|
|
481
|
+
function detectTopicFromMetadata(metadata) {
|
|
482
|
+
const text = `${metadata.title} ${metadata.summary}`.toLowerCase();
|
|
483
|
+
const topicKeywords = {
|
|
484
|
+
consensus: ["consensus", "voting", "agreement", "byzantine", "debate"],
|
|
485
|
+
routing: ["routing", "route", "cascade", "model selection", "cost-quality"],
|
|
486
|
+
memory: ["memory", "context", "kv-cache", "long-term", "retrieval"],
|
|
487
|
+
"code-generation": ["code", "self-improve", "refine", "debug", "skill"],
|
|
488
|
+
orchestration: ["orchestration", "multi-agent", "coordination", "workflow"],
|
|
489
|
+
security: ["safety", "security", "adversarial", "harmful", "jailbreak"]
|
|
490
|
+
};
|
|
491
|
+
for (const [topic, keywords] of Object.entries(topicKeywords)) {
|
|
492
|
+
for (const keyword of keywords) {
|
|
493
|
+
if (text.includes(keyword)) {
|
|
494
|
+
return topic;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return void 0;
|
|
499
|
+
}
|
|
500
|
+
function generateTagsFromMetadata(metadata) {
|
|
501
|
+
const tags = [];
|
|
502
|
+
const title = metadata.title.toLowerCase();
|
|
503
|
+
const tagPatterns = {
|
|
504
|
+
survey: ["survey", "review", "overview"],
|
|
505
|
+
benchmark: ["benchmark", "evaluation", "dataset"],
|
|
506
|
+
"multi-agent": ["multi-agent", "multiagent", "multiple agents"],
|
|
507
|
+
llm: ["llm", "large language model", "language model"],
|
|
508
|
+
"reinforcement-learning": ["reinforcement learning", "rl", "reward"]
|
|
509
|
+
};
|
|
510
|
+
for (const [tag, patterns] of Object.entries(tagPatterns)) {
|
|
511
|
+
for (const pattern of patterns) {
|
|
512
|
+
if (title.includes(pattern)) {
|
|
513
|
+
tags.push(tag);
|
|
514
|
+
break;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return tags;
|
|
519
|
+
}
|
|
520
|
+
function createRegistryError(code, message, paperId, cause) {
|
|
521
|
+
const error = { code, message };
|
|
522
|
+
if (paperId !== void 0) {
|
|
523
|
+
return { ...error, paperId, cause };
|
|
524
|
+
}
|
|
525
|
+
if (cause !== void 0) {
|
|
526
|
+
return { ...error, cause };
|
|
527
|
+
}
|
|
528
|
+
return error;
|
|
529
|
+
}
|
|
530
|
+
function paperEntryToResearchPaper(entry) {
|
|
531
|
+
return {
|
|
532
|
+
title: entry.title,
|
|
533
|
+
authors: [...entry.authors],
|
|
534
|
+
source: entry.source,
|
|
535
|
+
arxiv_id: entry.arxiv_id,
|
|
536
|
+
url: entry.url,
|
|
537
|
+
publication_date: entry.publication_date,
|
|
538
|
+
venue: entry.venue,
|
|
539
|
+
topics: [...entry.topics],
|
|
540
|
+
tags: [...entry.tags],
|
|
541
|
+
reviewed_date: entry.reviewed_date,
|
|
542
|
+
reviewed_in: entry.reviewed_in,
|
|
543
|
+
summary: entry.summary,
|
|
544
|
+
key_findings: [...entry.key_findings],
|
|
545
|
+
relevance: entry.relevance,
|
|
546
|
+
techniques_extracted: [...entry.techniques_extracted],
|
|
547
|
+
related_issues: [...entry.related_issues],
|
|
548
|
+
implementation_status: entry.implementation_status,
|
|
549
|
+
rigor_tags: entry.rigor_tags ? [...entry.rigor_tags] : [],
|
|
550
|
+
...entry.venue_tier !== void 0 ? { venue_tier: entry.venue_tier } : {},
|
|
551
|
+
...entry.quality_score !== void 0 ? { quality_score: entry.quality_score } : {},
|
|
552
|
+
...entry.evidence_tier !== void 0 ? { evidence_tier: entry.evidence_tier } : {},
|
|
553
|
+
...entry.citation_count !== void 0 ? { citation_count: entry.citation_count } : {},
|
|
554
|
+
...entry.has_code !== void 0 ? { has_code: entry.has_code } : {},
|
|
555
|
+
...entry.code_url !== void 0 ? { code_url: entry.code_url } : {},
|
|
556
|
+
...entry.quality_notes !== void 0 ? { quality_notes: entry.quality_notes } : {},
|
|
557
|
+
...entry.last_quality_check !== void 0 ? { last_quality_check: entry.last_quality_check } : {}
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
function generateRegistryEntry(metadata, topic) {
|
|
561
|
+
if (metadata.id === "" || metadata.title === "") {
|
|
562
|
+
return {
|
|
563
|
+
ok: false,
|
|
564
|
+
error: createRegistryError(
|
|
565
|
+
"INVALID_METADATA",
|
|
566
|
+
"arXiv metadata is missing required fields (id or title)",
|
|
567
|
+
void 0,
|
|
568
|
+
void 0
|
|
569
|
+
)
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
const detectedTopic = topic ?? detectTopicFromMetadata(metadata);
|
|
573
|
+
const topics = detectedTopic !== void 0 ? [detectedTopic] : [];
|
|
574
|
+
const tags = generateTagsFromMetadata(metadata);
|
|
575
|
+
const publicationDate = extractPublicationDate(metadata.id);
|
|
576
|
+
const entry = {
|
|
577
|
+
title: metadata.title,
|
|
578
|
+
authors: metadata.authors,
|
|
579
|
+
source: "arxiv",
|
|
580
|
+
arxiv_id: metadata.id,
|
|
581
|
+
url: `https://arxiv.org/abs/${metadata.id}`,
|
|
582
|
+
publication_date: publicationDate,
|
|
583
|
+
venue: null,
|
|
584
|
+
topics,
|
|
585
|
+
tags,
|
|
586
|
+
reviewed_date: getCurrentDate(),
|
|
587
|
+
reviewed_in: "",
|
|
588
|
+
summary: "",
|
|
589
|
+
key_findings: [],
|
|
590
|
+
relevance: "medium",
|
|
591
|
+
techniques_extracted: [],
|
|
592
|
+
related_issues: [],
|
|
593
|
+
implementation_status: "not-started"
|
|
594
|
+
};
|
|
595
|
+
const paperForScoring = paperEntryToResearchPaper(entry);
|
|
596
|
+
const qualityScore = computeQualityScore(paperForScoring);
|
|
597
|
+
const evidenceTier = computeEvidenceTier({ ...paperForScoring, quality_score: qualityScore });
|
|
598
|
+
const scoredEntry = {
|
|
599
|
+
...entry,
|
|
600
|
+
venue_tier: 0,
|
|
601
|
+
quality_score: qualityScore,
|
|
602
|
+
evidence_tier: evidenceTier
|
|
603
|
+
};
|
|
604
|
+
return { ok: true, value: scoredEntry };
|
|
605
|
+
}
|
|
606
|
+
function paperExistsInRegistry(arxivId, registry) {
|
|
607
|
+
const paperId = `arxiv-${arxivId}`;
|
|
608
|
+
return paperId in registry.papers;
|
|
609
|
+
}
|
|
610
|
+
function createAddResult(paperId, entry, message, dryRun) {
|
|
611
|
+
return { ok: true, value: { success: true, paperId, entry, message, dryRun } };
|
|
612
|
+
}
|
|
613
|
+
async function persistPaperEntry(registry, paperId, entry, rootDir) {
|
|
614
|
+
const updated = {
|
|
615
|
+
schema_version: registry.schema_version,
|
|
616
|
+
papers: { ...registry.papers, [paperId]: entry }
|
|
617
|
+
};
|
|
618
|
+
const saveResult = await savePapersRegistry(updated, rootDir);
|
|
619
|
+
if (!saveResult.ok) {
|
|
620
|
+
return {
|
|
621
|
+
ok: false,
|
|
622
|
+
error: createRegistryError(
|
|
623
|
+
"SAVE_ERROR",
|
|
624
|
+
`Failed to save papers registry: ${saveResult.error.message}`,
|
|
625
|
+
paperId,
|
|
626
|
+
saveResult.error
|
|
627
|
+
)
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
return { ok: true, value: void 0 };
|
|
631
|
+
}
|
|
632
|
+
async function addPaperToRegistry(options) {
|
|
633
|
+
const { metadata, topic, dryRun = false, rootDir } = options;
|
|
634
|
+
const paperId = `arxiv-${metadata.id}`;
|
|
635
|
+
const entryResult = generateRegistryEntry(metadata, topic);
|
|
636
|
+
if (!entryResult.ok) return entryResult;
|
|
637
|
+
const entry = entryResult.value;
|
|
638
|
+
const loadResult = await loadPapersRegistry(rootDir);
|
|
639
|
+
if (!loadResult.ok) {
|
|
640
|
+
return {
|
|
641
|
+
ok: false,
|
|
642
|
+
error: createRegistryError(
|
|
643
|
+
"LOAD_ERROR",
|
|
644
|
+
`Failed to load papers registry: ${loadResult.error.message}`,
|
|
645
|
+
paperId,
|
|
646
|
+
loadResult.error
|
|
647
|
+
)
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
if (paperExistsInRegistry(metadata.id, loadResult.value)) {
|
|
651
|
+
return {
|
|
652
|
+
ok: false,
|
|
653
|
+
error: createRegistryError(
|
|
654
|
+
"DUPLICATE",
|
|
655
|
+
`Paper ${paperId} already exists in registry`,
|
|
656
|
+
paperId
|
|
657
|
+
)
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
if (dryRun) {
|
|
661
|
+
return createAddResult(paperId, entry, `[DRY RUN] Would add paper: ${metadata.title}`, true);
|
|
662
|
+
}
|
|
663
|
+
const persistResult = await persistPaperEntry(loadResult.value, paperId, entry, rootDir);
|
|
664
|
+
if (!persistResult.ok) return { ok: false, error: persistResult.error };
|
|
665
|
+
return createAddResult(paperId, entry, `Added paper: ${metadata.title}`, false);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// src/cli/research-helpers-arxiv.ts
|
|
669
|
+
var ARXIV_API_TIMEOUT_MS = API_TIMEOUTS.arxivMs;
|
|
670
|
+
function extractEntryXml(xml) {
|
|
671
|
+
const entryMatch = xml.match(/<entry>([\s\S]*?)<\/entry>/);
|
|
672
|
+
return entryMatch?.[1] ?? null;
|
|
673
|
+
}
|
|
674
|
+
var XML_ENTITIES = {
|
|
675
|
+
"&": "&",
|
|
676
|
+
"<": "<",
|
|
677
|
+
">": ">",
|
|
678
|
+
""": '"',
|
|
679
|
+
"'": "'"
|
|
680
|
+
};
|
|
681
|
+
function decodeXmlEntities(s) {
|
|
682
|
+
return s.replace(/&(?:amp|lt|gt|quot|apos);/g, (m) => XML_ENTITIES[m] ?? m);
|
|
683
|
+
}
|
|
684
|
+
function parseArxivXml(arxivId, xml) {
|
|
685
|
+
const entryXml = extractEntryXml(xml);
|
|
686
|
+
if (entryXml === null) return null;
|
|
687
|
+
const titleMatch = entryXml.match(/<title>([^<]+)<\/title>/);
|
|
688
|
+
const summaryMatch = entryXml.match(/<summary>([^<]+)<\/summary>/s);
|
|
689
|
+
const publishedMatch = entryXml.match(/<published>([^<]+)<\/published>/);
|
|
690
|
+
const titleContent = titleMatch?.[1];
|
|
691
|
+
if (titleContent === void 0 || titleContent === "") return null;
|
|
692
|
+
return {
|
|
693
|
+
id: arxivId,
|
|
694
|
+
title: decodeXmlEntities(titleContent.trim().replace(/\s+/g, " ")),
|
|
695
|
+
authors: [],
|
|
696
|
+
// Would need more complex parsing
|
|
697
|
+
summary: decodeXmlEntities(summaryMatch?.[1]?.trim().replace(/\s+/g, " ") ?? ""),
|
|
698
|
+
published: publishedMatch?.[1] ?? "",
|
|
699
|
+
updated: "",
|
|
700
|
+
categories: [],
|
|
701
|
+
pdfUrl: `https://arxiv.org/pdf/${arxivId}.pdf`
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
function createArxivError(code, arxivId, message, cause) {
|
|
705
|
+
return { code, message, arxivId, cause };
|
|
706
|
+
}
|
|
707
|
+
function getErrorCodeFromException(err) {
|
|
708
|
+
if (err instanceof Error && err.name === "TimeoutError") {
|
|
709
|
+
return "TIMEOUT";
|
|
710
|
+
}
|
|
711
|
+
return "NETWORK";
|
|
712
|
+
}
|
|
713
|
+
async function fetchArxivMetadataResult(arxivId) {
|
|
714
|
+
const url = `https://export.arxiv.org/api/query?id_list=${arxivId}`;
|
|
715
|
+
try {
|
|
716
|
+
const response = await fetch(url, {
|
|
717
|
+
signal: AbortSignal.timeout(ARXIV_API_TIMEOUT_MS)
|
|
718
|
+
});
|
|
719
|
+
if (!response.ok) {
|
|
720
|
+
return {
|
|
721
|
+
ok: false,
|
|
722
|
+
error: createArxivError(
|
|
723
|
+
"HTTP_ERROR",
|
|
724
|
+
arxivId,
|
|
725
|
+
`arXiv API returned ${String(response.status)} ${response.statusText}`
|
|
726
|
+
)
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
const xml = await response.text();
|
|
730
|
+
const metadata = parseArxivXml(arxivId, xml);
|
|
731
|
+
if (metadata === null) {
|
|
732
|
+
return {
|
|
733
|
+
ok: false,
|
|
734
|
+
error: createArxivError("PARSE_ERROR", arxivId, "Failed to parse arXiv XML response")
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
return { ok: true, value: metadata };
|
|
738
|
+
} catch (err) {
|
|
739
|
+
const code = getErrorCodeFromException(err);
|
|
740
|
+
const message = code === "TIMEOUT" ? `arXiv API request timed out after ${String(ARXIV_API_TIMEOUT_MS / 1e3)} seconds` : "Network error while fetching from arXiv API";
|
|
741
|
+
return { ok: false, error: createArxivError(code, arxivId, message, err) };
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
async function paperExists(arxivId) {
|
|
745
|
+
const result = await loadPapersRegistry();
|
|
746
|
+
if (!result.ok) {
|
|
747
|
+
return false;
|
|
748
|
+
}
|
|
749
|
+
const paperId = `arxiv-${arxivId}`;
|
|
750
|
+
return paperId in result.value.papers;
|
|
751
|
+
}
|
|
752
|
+
async function addResearchPaper(options) {
|
|
753
|
+
const paperId = `arxiv-${options.arxivId}`;
|
|
754
|
+
const metadataResult = await fetchArxivMetadataResult(options.arxivId);
|
|
755
|
+
if (!metadataResult.ok) {
|
|
756
|
+
return {
|
|
757
|
+
success: false,
|
|
758
|
+
paperId,
|
|
759
|
+
title: "",
|
|
760
|
+
message: `Could not fetch metadata for arXiv ID ${options.arxivId}: ${metadataResult.error.message}`,
|
|
761
|
+
dryRun: options.dryRun
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
const metadata = metadataResult.value;
|
|
765
|
+
const addOptions = {
|
|
766
|
+
metadata,
|
|
767
|
+
dryRun: options.dryRun
|
|
768
|
+
};
|
|
769
|
+
if (options.topic !== void 0) {
|
|
770
|
+
addOptions.topic = options.topic;
|
|
771
|
+
}
|
|
772
|
+
const addResult = await addPaperToRegistry(addOptions);
|
|
773
|
+
if (!addResult.ok) {
|
|
774
|
+
return {
|
|
775
|
+
success: false,
|
|
776
|
+
paperId,
|
|
777
|
+
title: metadata.title,
|
|
778
|
+
message: addResult.error.message,
|
|
779
|
+
dryRun: options.dryRun
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
return {
|
|
783
|
+
success: addResult.value.success,
|
|
784
|
+
paperId: addResult.value.paperId,
|
|
785
|
+
title: metadata.title,
|
|
786
|
+
message: addResult.value.message,
|
|
787
|
+
dryRun: addResult.value.dryRun
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// src/cli/research-helpers-sources.ts
|
|
792
|
+
import { z as z2 } from "zod";
|
|
793
|
+
var SOURCE_API_TIMEOUT_MS = 3e4;
|
|
794
|
+
var GitHubRepoSchema = z2.object({
|
|
795
|
+
full_name: z2.string().optional(),
|
|
796
|
+
html_url: z2.string().optional(),
|
|
797
|
+
description: z2.string().nullable().optional(),
|
|
798
|
+
stargazers_count: z2.number().optional()
|
|
799
|
+
});
|
|
800
|
+
var GitHubSearchResponseSchema = z2.object({
|
|
801
|
+
items: z2.array(GitHubRepoSchema).optional()
|
|
802
|
+
});
|
|
803
|
+
function createError(code, source, message, cause) {
|
|
804
|
+
return { code, message, source, cause };
|
|
805
|
+
}
|
|
806
|
+
function getToday() {
|
|
807
|
+
return (/* @__PURE__ */ new Date()).toISOString().split("T")[0] ?? "";
|
|
808
|
+
}
|
|
809
|
+
function buildHttpErrorResult(status, source) {
|
|
810
|
+
const isRateLimit = status === 429;
|
|
811
|
+
const envHint = source === "github" ? "GITHUB_TOKEN" : `${source.toUpperCase()}_API_KEY`;
|
|
812
|
+
const message = isRateLimit ? `${source} rate-limited (HTTP 429) \u2014 set ${envHint} or retry later` : `API returned ${String(status)}`;
|
|
813
|
+
return createError(isRateLimit ? "RATE_LIMIT" : "HTTP_ERROR", source, message);
|
|
814
|
+
}
|
|
815
|
+
async function fetchSource(options) {
|
|
816
|
+
const { url, source, headers, timeoutMs = SOURCE_API_TIMEOUT_MS } = options;
|
|
817
|
+
try {
|
|
818
|
+
const fetchInit = { signal: AbortSignal.timeout(timeoutMs) };
|
|
819
|
+
if (headers !== void 0) fetchInit.headers = headers;
|
|
820
|
+
const response = await fetch(url, fetchInit);
|
|
821
|
+
if (!response.ok) {
|
|
822
|
+
return { ok: false, error: buildHttpErrorResult(response.status, source) };
|
|
823
|
+
}
|
|
824
|
+
return { ok: true, value: response };
|
|
825
|
+
} catch (error) {
|
|
826
|
+
const isTimeout = error instanceof Error && error.name === "TimeoutError";
|
|
827
|
+
return {
|
|
828
|
+
ok: false,
|
|
829
|
+
error: createError(
|
|
830
|
+
isTimeout ? "TIMEOUT" : "NETWORK",
|
|
831
|
+
source,
|
|
832
|
+
isTimeout ? `${source} API timed out` : `Network error querying ${source}`,
|
|
833
|
+
error
|
|
834
|
+
)
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
function parseGitHubRepos(data) {
|
|
839
|
+
return (data.items ?? []).map((repo) => ({
|
|
840
|
+
source: "github",
|
|
841
|
+
title: repo.full_name ?? "",
|
|
842
|
+
url: repo.html_url ?? "",
|
|
843
|
+
description: repo.description ?? "",
|
|
844
|
+
relevance: (repo.stargazers_count ?? 0) > 1e3 ? "high" : (repo.stargazers_count ?? 0) > 100 ? "medium" : "low",
|
|
845
|
+
discoveredAt: getToday()
|
|
846
|
+
}));
|
|
847
|
+
}
|
|
848
|
+
async function discoverGitHubRepos(topic, maxResults = 10) {
|
|
849
|
+
const query = encodeURIComponent(topic);
|
|
850
|
+
const url = `https://api.github.com/search/repositories?q=${query}&sort=stars&order=desc&per_page=${String(maxResults)}`;
|
|
851
|
+
const headers = {
|
|
852
|
+
Accept: "application/vnd.github.v3+json",
|
|
853
|
+
"User-Agent": "nexus-agents"
|
|
854
|
+
};
|
|
855
|
+
const tokenResult = await resolveToken({ platform: "github" });
|
|
856
|
+
if (tokenResult.ok) {
|
|
857
|
+
headers["Authorization"] = `Bearer ${tokenResult.value.value}`;
|
|
858
|
+
}
|
|
859
|
+
const fetchResult = await fetchSource({ url, source: "github", headers });
|
|
860
|
+
if (!fetchResult.ok) return fetchResult;
|
|
861
|
+
let raw;
|
|
862
|
+
try {
|
|
863
|
+
raw = await fetchResult.value.json();
|
|
864
|
+
} catch {
|
|
865
|
+
return {
|
|
866
|
+
ok: false,
|
|
867
|
+
error: createError("PARSE_ERROR", "github", "Response is not valid JSON")
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
const parsed = GitHubSearchResponseSchema.safeParse(raw);
|
|
871
|
+
if (!parsed.success) {
|
|
872
|
+
return {
|
|
873
|
+
ok: false,
|
|
874
|
+
error: createError("PARSE_ERROR", "github", "GitHub API response schema mismatch")
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
return { ok: true, value: parseGitHubRepos(parsed.data) };
|
|
878
|
+
}
|
|
879
|
+
function buildArxivUrl(opts) {
|
|
880
|
+
const words = opts.topic.split(/\s+/).filter((w) => w.length > 0);
|
|
881
|
+
const topicQuery = words.length <= 1 ? `(ti:${opts.topic} OR abs:${opts.topic})` : `(${words.map((w) => `(ti:${w} OR abs:${w})`).join(" AND ")})`;
|
|
882
|
+
let fullQuery = opts.authorFilter !== "" ? `${topicQuery} AND ${opts.authorFilter}` : topicQuery;
|
|
883
|
+
if (opts.sinceDate !== void 0 && /^\d{4}-\d{2}-\d{2}$/.test(opts.sinceDate)) {
|
|
884
|
+
const dateNum = opts.sinceDate.replace(/-/g, "");
|
|
885
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0]?.replace(/-/g, "") ?? "";
|
|
886
|
+
fullQuery = `${fullQuery} AND submittedDate:[${dateNum} TO ${today}]`;
|
|
887
|
+
}
|
|
888
|
+
const encoded = encodeURIComponent(fullQuery);
|
|
889
|
+
return `https://export.arxiv.org/api/query?search_query=${encoded}&start=0&max_results=${String(opts.maxResults)}&sortBy=submittedDate&sortOrder=descending`;
|
|
890
|
+
}
|
|
891
|
+
async function discoverFromArxiv(topic, authorFilter, source, maxResults, sinceDate) {
|
|
892
|
+
const url = buildArxivUrl({ topic, authorFilter, maxResults, sinceDate });
|
|
893
|
+
const fetchResult = await fetchSource({ url, source });
|
|
894
|
+
if (!fetchResult.ok) return fetchResult;
|
|
895
|
+
const xml = await fetchResult.value.text();
|
|
896
|
+
const items = parseArxivEntries(xml, source, topic);
|
|
897
|
+
return { ok: true, value: items };
|
|
898
|
+
}
|
|
899
|
+
async function discoverArxiv(topic, maxResults = 10, sinceDate) {
|
|
900
|
+
return discoverFromArxiv(topic, "", "arxiv", maxResults, sinceDate);
|
|
901
|
+
}
|
|
902
|
+
async function discoverGoogleAI(topic, maxResults = 10, sinceDate) {
|
|
903
|
+
return discoverFromArxiv(topic, "", "google_ai", maxResults, sinceDate);
|
|
904
|
+
}
|
|
905
|
+
async function discoverMetaFAIR(topic, maxResults = 10, sinceDate) {
|
|
906
|
+
return discoverFromArxiv(topic, "", "meta_fair", maxResults, sinceDate);
|
|
907
|
+
}
|
|
908
|
+
async function discoverMicrosoftResearch(topic, maxResults = 10, sinceDate) {
|
|
909
|
+
return discoverFromArxiv(topic, "", "microsoft", maxResults, sinceDate);
|
|
910
|
+
}
|
|
911
|
+
async function discoverDeepMind(topic, maxResults = 10, sinceDate) {
|
|
912
|
+
return discoverFromArxiv(topic, "", "deepmind", maxResults, sinceDate);
|
|
913
|
+
}
|
|
914
|
+
function extractTag(entry, tag) {
|
|
915
|
+
const escaped = tag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
916
|
+
const regex = new RegExp(`<${escaped}>(.*?)</${escaped}>`, "s");
|
|
917
|
+
return regex.exec(entry)?.[1]?.trim().replace(/\s+/g, " ") ?? "";
|
|
918
|
+
}
|
|
919
|
+
function truncate(text, maxLen = 200) {
|
|
920
|
+
return text.length > maxLen ? text.slice(0, maxLen - 3) + "..." : text;
|
|
921
|
+
}
|
|
922
|
+
function scoreRelevance(title, topic) {
|
|
923
|
+
const titleLower = title.toLowerCase();
|
|
924
|
+
const topicWords = topic.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
|
|
925
|
+
if (topicWords.length === 0) return "medium";
|
|
926
|
+
const matched = topicWords.filter((w) => titleLower.includes(w)).length;
|
|
927
|
+
const ratio = matched / topicWords.length;
|
|
928
|
+
if (ratio >= 0.6) return "high";
|
|
929
|
+
if (ratio >= 0.3) return "medium";
|
|
930
|
+
return "low";
|
|
931
|
+
}
|
|
932
|
+
function parseArxivEntries(xml, source, topic = "") {
|
|
933
|
+
const items = [];
|
|
934
|
+
const entryRegex = /<entry>([\s\S]*?)<\/entry>/g;
|
|
935
|
+
let match;
|
|
936
|
+
while ((match = entryRegex.exec(xml)) !== null) {
|
|
937
|
+
const entry = match[1];
|
|
938
|
+
if (entry === void 0) continue;
|
|
939
|
+
const title = extractTag(entry, "title");
|
|
940
|
+
if (title === "") continue;
|
|
941
|
+
const id = extractTag(entry, "id");
|
|
942
|
+
const arxivId = id.match(/(\d{4}\.\d{4,5})/)?.[1] ?? "";
|
|
943
|
+
items.push({
|
|
944
|
+
source,
|
|
945
|
+
title,
|
|
946
|
+
url: arxivId !== "" ? `https://arxiv.org/abs/${arxivId}` : id,
|
|
947
|
+
description: truncate(extractTag(entry, "summary")),
|
|
948
|
+
relevance: topic !== "" ? scoreRelevance(title, topic) : "medium",
|
|
949
|
+
discoveredAt: getToday()
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
return items;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// src/cli/research-helpers-sources-io.ts
|
|
956
|
+
import * as fs from "fs/promises";
|
|
957
|
+
import * as path from "path";
|
|
958
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
959
|
+
var SOURCES_FILE = "sources.yaml";
|
|
960
|
+
function validatePath(constructedPath, allowedRoot) {
|
|
961
|
+
const resolved = resolveInsideRoot(constructedPath, allowedRoot);
|
|
962
|
+
if (resolved === null) {
|
|
963
|
+
const root = path.resolve(allowedRoot);
|
|
964
|
+
return {
|
|
965
|
+
ok: false,
|
|
966
|
+
error: {
|
|
967
|
+
code: "PATH_TRAVERSAL",
|
|
968
|
+
message: `Path ${path.resolve(constructedPath)} is outside ${root}`
|
|
969
|
+
}
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
return { ok: true, value: resolved };
|
|
973
|
+
}
|
|
974
|
+
async function loadSourcesRegistry(rootDir) {
|
|
975
|
+
const root = rootDir ?? getProjectRoot();
|
|
976
|
+
const filePath = path.join(root, REGISTRY_PATH, SOURCES_FILE);
|
|
977
|
+
const pathResult = validatePath(filePath, root);
|
|
978
|
+
if (!pathResult.ok) return pathResult;
|
|
979
|
+
try {
|
|
980
|
+
const content = await fs.readFile(pathResult.value, "utf-8");
|
|
981
|
+
const parsed = parseYaml(content);
|
|
982
|
+
return { ok: true, value: parsed };
|
|
983
|
+
} catch (error) {
|
|
984
|
+
const code = error instanceof Error && "code" in error ? error.code : void 0;
|
|
985
|
+
if (code === "ENOENT") {
|
|
986
|
+
return {
|
|
987
|
+
ok: true,
|
|
988
|
+
value: { schema_version: "1.0", sources: {} }
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
return {
|
|
992
|
+
ok: false,
|
|
993
|
+
error: {
|
|
994
|
+
code: "PARSE_ERROR",
|
|
995
|
+
message: `Failed to load sources: ${getErrorMessage(error)}`
|
|
996
|
+
}
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
async function saveSourcesRegistry(registry, rootDir) {
|
|
1001
|
+
const root = rootDir ?? getProjectRoot();
|
|
1002
|
+
const filePath = path.join(root, REGISTRY_PATH, SOURCES_FILE);
|
|
1003
|
+
const pathResult = validatePath(filePath, root);
|
|
1004
|
+
if (!pathResult.ok) return pathResult;
|
|
1005
|
+
try {
|
|
1006
|
+
const yaml2 = stringifyYaml(registry, { indent: 2 });
|
|
1007
|
+
await fs.writeFile(pathResult.value, yaml2, "utf-8");
|
|
1008
|
+
return { ok: true, value: void 0 };
|
|
1009
|
+
} catch (error) {
|
|
1010
|
+
return {
|
|
1011
|
+
ok: false,
|
|
1012
|
+
error: {
|
|
1013
|
+
code: "WRITE_ERROR",
|
|
1014
|
+
message: `Failed to save sources: ${getErrorMessage(error)}`
|
|
1015
|
+
}
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
async function sourceExistsInRegistry(url, rootDir) {
|
|
1020
|
+
const result = await loadSourcesRegistry(rootDir);
|
|
1021
|
+
if (!result.ok) return false;
|
|
1022
|
+
return Object.values(result.value.sources).some((s) => s.url === url);
|
|
1023
|
+
}
|
|
1024
|
+
async function addSourceToRegistry(id, entry, rootDir) {
|
|
1025
|
+
const loadResult = await loadSourcesRegistry(rootDir);
|
|
1026
|
+
if (!loadResult.ok) return loadResult;
|
|
1027
|
+
const registry = loadResult.value;
|
|
1028
|
+
const updatedSources = { ...registry.sources, [id]: entry };
|
|
1029
|
+
const updatedRegistry = {
|
|
1030
|
+
...registry,
|
|
1031
|
+
sources: updatedSources
|
|
1032
|
+
};
|
|
1033
|
+
return saveSourcesRegistry(updatedRegistry, rootDir);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// src/cli/research-helpers-sources-academic.ts
|
|
1037
|
+
import { z as z3 } from "zod";
|
|
1038
|
+
function getToday2() {
|
|
1039
|
+
return (/* @__PURE__ */ new Date()).toISOString().split("T")[0] ?? "";
|
|
1040
|
+
}
|
|
1041
|
+
function truncate2(text, maxLen = 200) {
|
|
1042
|
+
return text.length > maxLen ? text.slice(0, maxLen - 3) + "..." : text;
|
|
1043
|
+
}
|
|
1044
|
+
var SemanticScholarPaperSchema = z3.object({
|
|
1045
|
+
paperId: z3.string().optional(),
|
|
1046
|
+
title: z3.string().optional(),
|
|
1047
|
+
url: z3.string().optional(),
|
|
1048
|
+
abstract: z3.string().nullable().optional(),
|
|
1049
|
+
citationCount: z3.number().optional(),
|
|
1050
|
+
year: z3.number().optional(),
|
|
1051
|
+
isOpenAccess: z3.boolean().optional(),
|
|
1052
|
+
externalIds: z3.object({
|
|
1053
|
+
ArXiv: z3.string().optional(),
|
|
1054
|
+
DOI: z3.string().optional()
|
|
1055
|
+
}).optional()
|
|
1056
|
+
});
|
|
1057
|
+
var SemanticScholarResponseSchema = z3.object({
|
|
1058
|
+
data: z3.array(SemanticScholarPaperSchema).optional()
|
|
1059
|
+
});
|
|
1060
|
+
var PapersWithCodePaperSchema = z3.object({
|
|
1061
|
+
id: z3.string().optional(),
|
|
1062
|
+
title: z3.string().optional(),
|
|
1063
|
+
url_abs: z3.string().optional(),
|
|
1064
|
+
url_pdf: z3.string().optional(),
|
|
1065
|
+
abstract: z3.string().nullable().optional(),
|
|
1066
|
+
proceeding: z3.string().nullable().optional(),
|
|
1067
|
+
repository_count: z3.number().optional()
|
|
1068
|
+
});
|
|
1069
|
+
var PapersWithCodeResponseSchema = z3.object({
|
|
1070
|
+
results: z3.array(PapersWithCodePaperSchema).optional()
|
|
1071
|
+
});
|
|
1072
|
+
function citationRelevance(count) {
|
|
1073
|
+
if (count > 100) return "high";
|
|
1074
|
+
if (count > 20) return "medium";
|
|
1075
|
+
return "low";
|
|
1076
|
+
}
|
|
1077
|
+
function mapSemanticScholarPaper(paper) {
|
|
1078
|
+
const arxivId = paper.externalIds?.ArXiv;
|
|
1079
|
+
const paperUrl = arxivId !== void 0 ? `https://arxiv.org/abs/${arxivId}` : paper.url ?? `https://api.semanticscholar.org/paper/${paper.paperId ?? ""}`;
|
|
1080
|
+
return {
|
|
1081
|
+
source: "semantic_scholar",
|
|
1082
|
+
title: paper.title ?? "",
|
|
1083
|
+
url: paperUrl,
|
|
1084
|
+
description: truncate2(paper.abstract ?? ""),
|
|
1085
|
+
relevance: citationRelevance(paper.citationCount ?? 0),
|
|
1086
|
+
discoveredAt: getToday2()
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
async function discoverSemanticScholar(topic, maxResults = 10) {
|
|
1090
|
+
const query = encodeURIComponent(topic);
|
|
1091
|
+
const fields = "title,url,abstract,citationCount,year,isOpenAccess,externalIds";
|
|
1092
|
+
const url = `https://api.semanticscholar.org/graph/v1/paper/search?query=${query}&limit=${String(maxResults)}&fields=${fields}`;
|
|
1093
|
+
const headers = {
|
|
1094
|
+
"User-Agent": "nexus-agents",
|
|
1095
|
+
Accept: "application/json"
|
|
1096
|
+
};
|
|
1097
|
+
const apiKey = process.env["SEMANTIC_SCHOLAR_API_KEY"];
|
|
1098
|
+
if (apiKey !== void 0 && apiKey !== "") {
|
|
1099
|
+
headers["x-api-key"] = apiKey;
|
|
1100
|
+
}
|
|
1101
|
+
const fetchResult = await fetchSource({
|
|
1102
|
+
url,
|
|
1103
|
+
source: "semantic_scholar",
|
|
1104
|
+
headers
|
|
1105
|
+
});
|
|
1106
|
+
if (!fetchResult.ok) return fetchResult;
|
|
1107
|
+
let raw;
|
|
1108
|
+
try {
|
|
1109
|
+
raw = await fetchResult.value.json();
|
|
1110
|
+
} catch {
|
|
1111
|
+
return {
|
|
1112
|
+
ok: false,
|
|
1113
|
+
error: {
|
|
1114
|
+
code: "PARSE_ERROR",
|
|
1115
|
+
source: "semantic_scholar",
|
|
1116
|
+
message: "Response is not valid JSON"
|
|
1117
|
+
}
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
const parsed = SemanticScholarResponseSchema.safeParse(raw);
|
|
1121
|
+
if (!parsed.success) {
|
|
1122
|
+
return {
|
|
1123
|
+
ok: false,
|
|
1124
|
+
error: {
|
|
1125
|
+
code: "PARSE_ERROR",
|
|
1126
|
+
source: "semantic_scholar",
|
|
1127
|
+
message: "Response schema mismatch"
|
|
1128
|
+
}
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
const items = (parsed.data.data ?? []).filter((p) => p.title !== void 0 && p.title !== "").map(mapSemanticScholarPaper);
|
|
1132
|
+
return { ok: true, value: items };
|
|
1133
|
+
}
|
|
1134
|
+
function mapPwcPaper(paper) {
|
|
1135
|
+
const hasCode = (paper.repository_count ?? 0) > 0;
|
|
1136
|
+
return {
|
|
1137
|
+
source: "papers_with_code",
|
|
1138
|
+
title: paper.title ?? "",
|
|
1139
|
+
url: paper.url_abs ?? "",
|
|
1140
|
+
description: truncate2(paper.abstract ?? ""),
|
|
1141
|
+
relevance: hasCode ? "high" : "medium",
|
|
1142
|
+
discoveredAt: getToday2()
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
function pwcParseError(message) {
|
|
1146
|
+
return { ok: false, error: { code: "PARSE_ERROR", source: "papers_with_code", message } };
|
|
1147
|
+
}
|
|
1148
|
+
function isNonJsonResponse(response) {
|
|
1149
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
1150
|
+
return contentType !== "" && !contentType.includes("application/json");
|
|
1151
|
+
}
|
|
1152
|
+
async function discoverPapersWithCode(topic, maxResults = 10) {
|
|
1153
|
+
const query = encodeURIComponent(topic);
|
|
1154
|
+
const url = `https://paperswithcode.com/api/v1/papers/?q=${query}&items_per_page=${String(maxResults)}`;
|
|
1155
|
+
const fetchResult = await fetchSource({
|
|
1156
|
+
url,
|
|
1157
|
+
source: "papers_with_code",
|
|
1158
|
+
headers: { "User-Agent": "nexus-agents", Accept: "application/json" }
|
|
1159
|
+
});
|
|
1160
|
+
if (!fetchResult.ok) return fetchResult;
|
|
1161
|
+
if (isNonJsonResponse(fetchResult.value)) return { ok: true, value: [] };
|
|
1162
|
+
let raw;
|
|
1163
|
+
try {
|
|
1164
|
+
raw = await fetchResult.value.json();
|
|
1165
|
+
} catch {
|
|
1166
|
+
return pwcParseError("Response is not valid JSON (possible HTML error page)");
|
|
1167
|
+
}
|
|
1168
|
+
const parsed = PapersWithCodeResponseSchema.safeParse(raw);
|
|
1169
|
+
if (!parsed.success) return pwcParseError("Response schema mismatch");
|
|
1170
|
+
const items = (parsed.data.results ?? []).filter((p) => p.title !== void 0 && p.title !== "").map(mapPwcPaper);
|
|
1171
|
+
return { ok: true, value: items };
|
|
1172
|
+
}
|
|
1173
|
+
var OpenAlexWorkSchema = z3.object({
|
|
1174
|
+
id: z3.string().optional(),
|
|
1175
|
+
title: z3.string().optional(),
|
|
1176
|
+
doi: z3.string().nullable().optional(),
|
|
1177
|
+
publication_date: z3.string().nullable().optional(),
|
|
1178
|
+
cited_by_count: z3.number().optional(),
|
|
1179
|
+
is_oa: z3.boolean().optional(),
|
|
1180
|
+
abstract_inverted_index: z3.record(z3.string(), z3.array(z3.number())).nullable().optional(),
|
|
1181
|
+
primary_location: z3.object({
|
|
1182
|
+
landing_page_url: z3.string().nullable().optional()
|
|
1183
|
+
}).nullable().optional(),
|
|
1184
|
+
ids: z3.object({
|
|
1185
|
+
openalex: z3.string().optional(),
|
|
1186
|
+
doi: z3.string().optional()
|
|
1187
|
+
}).optional()
|
|
1188
|
+
});
|
|
1189
|
+
var OpenAlexResponseSchema = z3.object({
|
|
1190
|
+
results: z3.array(OpenAlexWorkSchema).optional()
|
|
1191
|
+
});
|
|
1192
|
+
function reconstructAbstract(invertedIndex) {
|
|
1193
|
+
if (!invertedIndex) return "";
|
|
1194
|
+
const words = [];
|
|
1195
|
+
for (const [word, positions] of Object.entries(invertedIndex)) {
|
|
1196
|
+
for (const pos of positions) {
|
|
1197
|
+
words.push([word, pos]);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
words.sort((a, b) => a[1] - b[1]);
|
|
1201
|
+
return words.map(([w]) => w).join(" ");
|
|
1202
|
+
}
|
|
1203
|
+
function mapOpenAlexWork(work) {
|
|
1204
|
+
const url = work.primary_location?.landing_page_url ?? work.ids?.doi ?? work.id ?? "";
|
|
1205
|
+
return {
|
|
1206
|
+
source: "openalex",
|
|
1207
|
+
title: work.title ?? "",
|
|
1208
|
+
url,
|
|
1209
|
+
description: truncate2(reconstructAbstract(work.abstract_inverted_index)),
|
|
1210
|
+
relevance: citationRelevance(work.cited_by_count ?? 0),
|
|
1211
|
+
discoveredAt: getToday2()
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
function openAlexParseError(message) {
|
|
1215
|
+
return { ok: false, error: { code: "PARSE_ERROR", source: "openalex", message } };
|
|
1216
|
+
}
|
|
1217
|
+
async function discoverOpenAlex(topic, maxResults = 10, apiKey) {
|
|
1218
|
+
const query = encodeURIComponent(topic);
|
|
1219
|
+
const mailto = "nexus-agents@example.com";
|
|
1220
|
+
const url = `https://api.openalex.org/works?search=${query}&per_page=${String(maxResults)}&mailto=${mailto}`;
|
|
1221
|
+
const headers = {
|
|
1222
|
+
"User-Agent": "nexus-agents",
|
|
1223
|
+
Accept: "application/json"
|
|
1224
|
+
};
|
|
1225
|
+
if (apiKey !== void 0 && apiKey !== "") {
|
|
1226
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
1227
|
+
}
|
|
1228
|
+
const fetchResult = await fetchSource({
|
|
1229
|
+
url,
|
|
1230
|
+
source: "openalex",
|
|
1231
|
+
headers
|
|
1232
|
+
});
|
|
1233
|
+
if (!fetchResult.ok) return fetchResult;
|
|
1234
|
+
let raw;
|
|
1235
|
+
try {
|
|
1236
|
+
raw = await fetchResult.value.json();
|
|
1237
|
+
} catch {
|
|
1238
|
+
return openAlexParseError("Response is not valid JSON");
|
|
1239
|
+
}
|
|
1240
|
+
const parsed = OpenAlexResponseSchema.safeParse(raw);
|
|
1241
|
+
if (!parsed.success) return openAlexParseError("Response schema mismatch");
|
|
1242
|
+
const items = (parsed.data.results ?? []).filter((w) => w.title !== void 0 && w.title !== "").map(mapOpenAlexWork);
|
|
1243
|
+
return { ok: true, value: items };
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
// src/cli/research-helpers-scoring.ts
|
|
1247
|
+
var WEIGHTS = {
|
|
1248
|
+
relevance: 0.35,
|
|
1249
|
+
impact: 0.25,
|
|
1250
|
+
recency: 0.2,
|
|
1251
|
+
reproducibility: 0.2
|
|
1252
|
+
};
|
|
1253
|
+
function scoreRelevance2(title, topic) {
|
|
1254
|
+
const titleLower = title.toLowerCase();
|
|
1255
|
+
const topicWords = topic.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
|
|
1256
|
+
if (topicWords.length === 0) return 0.5;
|
|
1257
|
+
const matched = topicWords.filter((w) => titleLower.includes(w)).length;
|
|
1258
|
+
return matched / topicWords.length;
|
|
1259
|
+
}
|
|
1260
|
+
function relevanceLabelToScore(relevance) {
|
|
1261
|
+
switch (relevance) {
|
|
1262
|
+
case "high":
|
|
1263
|
+
return 0.9;
|
|
1264
|
+
case "medium":
|
|
1265
|
+
return 0.5;
|
|
1266
|
+
case "low":
|
|
1267
|
+
return 0.2;
|
|
1268
|
+
default:
|
|
1269
|
+
return 0.5;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
function scoreRecency(discoveredAt) {
|
|
1273
|
+
const discoveredDate = new Date(discoveredAt);
|
|
1274
|
+
if (isNaN(discoveredDate.getTime())) return 0.5;
|
|
1275
|
+
const now = /* @__PURE__ */ new Date();
|
|
1276
|
+
const daysSince = (now.getTime() - discoveredDate.getTime()) / (1e3 * 60 * 60 * 24);
|
|
1277
|
+
return Math.max(0, 1 - daysSince / 730);
|
|
1278
|
+
}
|
|
1279
|
+
function scoreReproducibility(source) {
|
|
1280
|
+
switch (source) {
|
|
1281
|
+
case "github":
|
|
1282
|
+
return 1;
|
|
1283
|
+
case "papers_with_code":
|
|
1284
|
+
return 0.8;
|
|
1285
|
+
default:
|
|
1286
|
+
return 0.3;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
function scoreDiscoveredItem(item, topic) {
|
|
1290
|
+
const relevance = topic !== "" ? scoreRelevance2(item.title, topic) : relevanceLabelToScore(item.relevance);
|
|
1291
|
+
const impact = relevanceLabelToScore(item.relevance);
|
|
1292
|
+
const recency = scoreRecency(item.discoveredAt);
|
|
1293
|
+
const reproducibility = scoreReproducibility(item.source);
|
|
1294
|
+
const composite = WEIGHTS.relevance * relevance + WEIGHTS.impact * impact + WEIGHTS.recency * recency + WEIGHTS.reproducibility * reproducibility;
|
|
1295
|
+
return {
|
|
1296
|
+
relevance: Math.round(relevance * 100) / 100,
|
|
1297
|
+
impact: Math.round(impact * 100) / 100,
|
|
1298
|
+
recency: Math.round(recency * 100) / 100,
|
|
1299
|
+
reproducibility: Math.round(reproducibility * 100) / 100,
|
|
1300
|
+
composite: Math.round(composite * 100) / 100
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
function rankDiscoveredItems(items, topic) {
|
|
1304
|
+
return items.map((item) => ({ item, score: scoreDiscoveredItem(item, topic) })).sort((a, b) => b.score.composite - a.score.composite);
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
// src/cli/research-helpers-index-ops.ts
|
|
1308
|
+
import * as fs3 from "fs/promises";
|
|
1309
|
+
import * as path3 from "path";
|
|
1310
|
+
|
|
1311
|
+
// src/indexer/research-index/research-index-base-types.ts
|
|
1312
|
+
import { z as z4 } from "zod";
|
|
1313
|
+
var ResearchTopicSchema = z4.string().min(1);
|
|
1314
|
+
var TechniqueStatusSchema = z4.enum([
|
|
1315
|
+
"implemented",
|
|
1316
|
+
"planned",
|
|
1317
|
+
"in-progress",
|
|
1318
|
+
"not-started",
|
|
1319
|
+
"rejected"
|
|
1320
|
+
]);
|
|
1321
|
+
var PaperStatusSchema = z4.enum([
|
|
1322
|
+
"implemented",
|
|
1323
|
+
"planned",
|
|
1324
|
+
"partial",
|
|
1325
|
+
"in-progress",
|
|
1326
|
+
"not-started",
|
|
1327
|
+
"rejected",
|
|
1328
|
+
"deferred"
|
|
1329
|
+
]);
|
|
1330
|
+
var TechniquePrioritySchema = z4.enum(["P1", "P2", "P3", "P4"]).nullable();
|
|
1331
|
+
var TechniqueComplexitySchema = z4.enum(["low", "medium", "high"]);
|
|
1332
|
+
var SourceTypeSchema = z4.enum([
|
|
1333
|
+
"product_docs",
|
|
1334
|
+
"specification",
|
|
1335
|
+
"research_blog",
|
|
1336
|
+
"code_analysis",
|
|
1337
|
+
"open_source_repo"
|
|
1338
|
+
]);
|
|
1339
|
+
var DecisionHistoryEntrySchema = z4.object({
|
|
1340
|
+
date: z4.string(),
|
|
1341
|
+
decision: z4.string(),
|
|
1342
|
+
rationale: z4.string()
|
|
1343
|
+
});
|
|
1344
|
+
var PaperMetricsSchema = z4.record(z4.string(), z4.string());
|
|
1345
|
+
var ResearchPaperSchema = z4.object({
|
|
1346
|
+
/** Paper title */
|
|
1347
|
+
title: z4.string(),
|
|
1348
|
+
/** Paper authors */
|
|
1349
|
+
authors: z4.array(z4.string()).optional().default([]),
|
|
1350
|
+
/** Source type (arxiv, conference, journal, preprint) */
|
|
1351
|
+
source: z4.string().optional(),
|
|
1352
|
+
/** arXiv ID (e.g., '2501.06322') */
|
|
1353
|
+
arxiv_id: z4.string().optional(),
|
|
1354
|
+
/** URL to the paper */
|
|
1355
|
+
url: z4.string().optional(),
|
|
1356
|
+
/** Publication date (YYYY-MM format) */
|
|
1357
|
+
publication_date: z4.string().optional(),
|
|
1358
|
+
/** Publication venue */
|
|
1359
|
+
venue: z4.string().nullable().optional(),
|
|
1360
|
+
/** Research topics */
|
|
1361
|
+
topics: z4.array(ResearchTopicSchema).optional().default([]),
|
|
1362
|
+
/** Tags for searching */
|
|
1363
|
+
tags: z4.array(z4.string()).optional().default([]),
|
|
1364
|
+
/** Date when the paper was reviewed */
|
|
1365
|
+
reviewed_date: z4.string().optional(),
|
|
1366
|
+
/** File where the paper is reviewed */
|
|
1367
|
+
reviewed_in: z4.string().optional(),
|
|
1368
|
+
/** Summary of the paper */
|
|
1369
|
+
summary: z4.string().optional(),
|
|
1370
|
+
/** Key findings from the paper */
|
|
1371
|
+
key_findings: z4.array(z4.string()).optional().default([]),
|
|
1372
|
+
/** Relevance to the project */
|
|
1373
|
+
relevance: z4.enum(["high", "medium", "low"]).optional(),
|
|
1374
|
+
/** Techniques extracted from this paper */
|
|
1375
|
+
techniques_extracted: z4.array(z4.string()).optional().default([]),
|
|
1376
|
+
/** Related GitHub issues */
|
|
1377
|
+
related_issues: z4.array(z4.number()).optional().default([]),
|
|
1378
|
+
/** Implementation status */
|
|
1379
|
+
implementation_status: PaperStatusSchema.optional().default("not-started"),
|
|
1380
|
+
// ── Quality Assessment (Issue #1571) ──────────────────────────
|
|
1381
|
+
/** Citation count from Semantic Scholar */
|
|
1382
|
+
citation_count: z4.number().nonnegative().optional(),
|
|
1383
|
+
/** Venue quality tier: 3=top, 2=good, 1=workshop, 0=preprint */
|
|
1384
|
+
venue_tier: z4.number().min(0).max(3).optional(),
|
|
1385
|
+
/** Whether the paper links to a code repository */
|
|
1386
|
+
has_code: z4.boolean().optional(),
|
|
1387
|
+
/** Code repository URL */
|
|
1388
|
+
code_url: z4.string().optional(),
|
|
1389
|
+
/** Rigor assessment tags */
|
|
1390
|
+
rigor_tags: z4.array(
|
|
1391
|
+
z4.enum(["has-code", "has-dataset", "has-baselines", "peer-reviewed", "single-model-eval"])
|
|
1392
|
+
).optional().default([]),
|
|
1393
|
+
/** Composite quality score (0-10) */
|
|
1394
|
+
quality_score: z4.number().min(0).max(10).optional(),
|
|
1395
|
+
/** Evidence confidence tier */
|
|
1396
|
+
evidence_tier: z4.enum(["high", "medium", "low"]).optional(),
|
|
1397
|
+
/** Quality notes for low-scoring papers */
|
|
1398
|
+
quality_notes: z4.string().optional(),
|
|
1399
|
+
/** When quality was last assessed (ISO date) */
|
|
1400
|
+
last_quality_check: z4.string().optional()
|
|
1401
|
+
});
|
|
1402
|
+
var ResearchTechniqueSchema = z4.object({
|
|
1403
|
+
/** Technique name */
|
|
1404
|
+
name: z4.string(),
|
|
1405
|
+
/** Description of the technique */
|
|
1406
|
+
description: z4.string(),
|
|
1407
|
+
/** Source papers (arXiv IDs) */
|
|
1408
|
+
source_papers: z4.array(z4.string()).optional().default([]),
|
|
1409
|
+
/** Primary topic */
|
|
1410
|
+
topic: ResearchTopicSchema,
|
|
1411
|
+
/** Tags for searching */
|
|
1412
|
+
tags: z4.array(z4.string()).optional().default([]),
|
|
1413
|
+
/** Metrics associated with this technique */
|
|
1414
|
+
metrics: z4.record(z4.string(), z4.string()).optional().default({}),
|
|
1415
|
+
/** Implementation status */
|
|
1416
|
+
status: TechniqueStatusSchema,
|
|
1417
|
+
/** Priority level */
|
|
1418
|
+
priority: TechniquePrioritySchema.optional().default(null),
|
|
1419
|
+
/** Complexity level */
|
|
1420
|
+
complexity: TechniqueComplexitySchema.optional(),
|
|
1421
|
+
/** Files where this technique is implemented */
|
|
1422
|
+
integration_files: z4.array(z4.string()).optional().default([]),
|
|
1423
|
+
/** GitHub issue number for implementation */
|
|
1424
|
+
implementation_issue: z4.number().nullable().optional().default(null),
|
|
1425
|
+
/** Related PR numbers */
|
|
1426
|
+
related_prs: z4.array(z4.number()).optional().default([]),
|
|
1427
|
+
/** Implementation notes */
|
|
1428
|
+
notes: z4.string().optional(),
|
|
1429
|
+
/** Dependent techniques */
|
|
1430
|
+
dependencies: z4.array(z4.string()).optional().default([]),
|
|
1431
|
+
/** Decision history */
|
|
1432
|
+
decision_history: z4.array(DecisionHistoryEntrySchema).optional().default([]),
|
|
1433
|
+
// ── Evaluation Plan (Issue #1576 Wave 6) ──────────────────────
|
|
1434
|
+
/** Structured evaluation plan for keeping or removing the technique */
|
|
1435
|
+
evaluation_plan: z4.object({
|
|
1436
|
+
success_criteria: z4.string().optional(),
|
|
1437
|
+
evaluation_method: z4.string().optional(),
|
|
1438
|
+
evaluation_deadline: z4.string().optional(),
|
|
1439
|
+
removal_criteria: z4.string().optional()
|
|
1440
|
+
}).optional(),
|
|
1441
|
+
/** Evaluation status: pending (not yet evaluated), passed, failed, deferred */
|
|
1442
|
+
evaluation_status: z4.enum(["pending", "passed", "failed", "deferred"]).optional()
|
|
1443
|
+
});
|
|
1444
|
+
var ResearchSourceSchema = z4.object({
|
|
1445
|
+
/** Source name */
|
|
1446
|
+
name: z4.string(),
|
|
1447
|
+
/** Source type */
|
|
1448
|
+
type: SourceTypeSchema,
|
|
1449
|
+
/** URL */
|
|
1450
|
+
url: z4.string(),
|
|
1451
|
+
/** Vendor/organization */
|
|
1452
|
+
vendor: z4.string().optional(),
|
|
1453
|
+
/** Topics covered */
|
|
1454
|
+
topics: z4.array(ResearchTopicSchema).optional().default([]),
|
|
1455
|
+
/** Tags for searching */
|
|
1456
|
+
tags: z4.array(z4.string()).optional().default([]),
|
|
1457
|
+
/** Date when reviewed */
|
|
1458
|
+
reviewed_date: z4.string().optional(),
|
|
1459
|
+
/** File where reviewed */
|
|
1460
|
+
reviewed_in: z4.string().nullable().optional(),
|
|
1461
|
+
/** Key information extracted */
|
|
1462
|
+
key_info: z4.array(z4.string()).optional().default([]),
|
|
1463
|
+
/** Best practices documented */
|
|
1464
|
+
best_practices: z4.array(z4.string()).optional().default([]),
|
|
1465
|
+
/** Version checked */
|
|
1466
|
+
version_checked: z4.string().optional(),
|
|
1467
|
+
// ── Repo/Tool Quality Signals (for open_source_repo type) ──────
|
|
1468
|
+
/** Quality signals for repositories and tools */
|
|
1469
|
+
quality_signals: z4.object({
|
|
1470
|
+
stars_at_review: z4.number().nonnegative().optional(),
|
|
1471
|
+
language: z4.string().optional(),
|
|
1472
|
+
has_tests: z4.boolean().optional(),
|
|
1473
|
+
has_docs: z4.boolean().optional(),
|
|
1474
|
+
has_paper: z4.boolean().optional(),
|
|
1475
|
+
arxiv_id: z4.string().optional()
|
|
1476
|
+
}).optional(),
|
|
1477
|
+
/** Techniques extracted from this source */
|
|
1478
|
+
techniques_extracted: z4.array(z4.string()).optional().default([]),
|
|
1479
|
+
/** Adoption verdict: adopted, partially_adopted, rejected, monitoring, planned */
|
|
1480
|
+
verdict: z4.enum(["adopted", "partially_adopted", "rejected", "monitoring", "planned"]).optional(),
|
|
1481
|
+
/** Notes explaining the verdict */
|
|
1482
|
+
verdict_notes: z4.string().optional(),
|
|
1483
|
+
/** Composite quality score (0-10) */
|
|
1484
|
+
quality_score: z4.number().min(0).max(10).optional(),
|
|
1485
|
+
/** Evidence confidence tier */
|
|
1486
|
+
evidence_tier: z4.enum(["high", "medium", "low"]).optional()
|
|
1487
|
+
});
|
|
1488
|
+
var PapersRegistrySchema = z4.object({
|
|
1489
|
+
schema_version: z4.string(),
|
|
1490
|
+
papers: z4.record(z4.string(), ResearchPaperSchema)
|
|
1491
|
+
});
|
|
1492
|
+
var TechniquesRegistrySchema = z4.object({
|
|
1493
|
+
schema_version: z4.string(),
|
|
1494
|
+
techniques: z4.record(z4.string(), ResearchTechniqueSchema)
|
|
1495
|
+
});
|
|
1496
|
+
var SourcesRegistrySchema = z4.object({
|
|
1497
|
+
schema_version: z4.string(),
|
|
1498
|
+
sources: z4.record(z4.string(), ResearchSourceSchema)
|
|
1499
|
+
});
|
|
1500
|
+
|
|
1501
|
+
// src/indexer/research-index/research-index-stats-types.ts
|
|
1502
|
+
var DEFAULT_RESEARCH_INDEX_PARSER_OPTIONS = {
|
|
1503
|
+
registryPath: "docs/research/registry",
|
|
1504
|
+
strictValidation: true
|
|
1505
|
+
};
|
|
1506
|
+
var DEFAULT_RESEARCH_INDEX_GENERATOR_OPTIONS = {
|
|
1507
|
+
includeP1Table: true,
|
|
1508
|
+
includeP2Table: true,
|
|
1509
|
+
includeRecentPapers: true,
|
|
1510
|
+
recentPapersLimit: 5,
|
|
1511
|
+
includePapersByTopic: true,
|
|
1512
|
+
includeGitHubIssues: true
|
|
1513
|
+
};
|
|
1514
|
+
var ResearchIndexParseError = class extends Error {
|
|
1515
|
+
constructor(message, file, details) {
|
|
1516
|
+
super(message);
|
|
1517
|
+
this.file = file;
|
|
1518
|
+
this.details = details;
|
|
1519
|
+
this.name = "ResearchIndexParseError";
|
|
1520
|
+
}
|
|
1521
|
+
};
|
|
1522
|
+
var ResearchIndexGeneratorError = class extends Error {
|
|
1523
|
+
constructor(message, details) {
|
|
1524
|
+
super(message);
|
|
1525
|
+
this.details = details;
|
|
1526
|
+
this.name = "ResearchIndexGeneratorError";
|
|
1527
|
+
}
|
|
1528
|
+
};
|
|
1529
|
+
|
|
1530
|
+
// src/indexer/research-index/research-index-parser.ts
|
|
1531
|
+
import * as fs2 from "fs";
|
|
1532
|
+
import * as path2 from "path";
|
|
1533
|
+
import * as yaml from "yaml";
|
|
1534
|
+
|
|
1535
|
+
// src/research/source-quality.ts
|
|
1536
|
+
function starScore(stars) {
|
|
1537
|
+
if (stars === void 0 || stars === 0) return 0;
|
|
1538
|
+
if (stars < 100) return 1;
|
|
1539
|
+
if (stars < 1e3) return 2;
|
|
1540
|
+
return 3;
|
|
1541
|
+
}
|
|
1542
|
+
function reviewRecencyScore(reviewedDate) {
|
|
1543
|
+
if (reviewedDate === void 0) return 0;
|
|
1544
|
+
const now = /* @__PURE__ */ new Date();
|
|
1545
|
+
const reviewed = new Date(reviewedDate);
|
|
1546
|
+
const monthsAgo = (now.getTime() - reviewed.getTime()) / (30 * 24 * 60 * 60 * 1e3);
|
|
1547
|
+
if (monthsAgo < 6) return 2;
|
|
1548
|
+
if (monthsAgo < 12) return 1;
|
|
1549
|
+
return 0;
|
|
1550
|
+
}
|
|
1551
|
+
function computeSourceQualityScore(source) {
|
|
1552
|
+
const signals = source.quality_signals;
|
|
1553
|
+
if (signals === void 0) {
|
|
1554
|
+
return source.type === "open_source_repo" ? 0 : 3;
|
|
1555
|
+
}
|
|
1556
|
+
const stars = starScore(signals.stars_at_review);
|
|
1557
|
+
const tests = signals.has_tests === true ? 1 : 0;
|
|
1558
|
+
const docs = signals.has_docs === true ? 1 : 0;
|
|
1559
|
+
const recency = reviewRecencyScore(source.reviewed_date);
|
|
1560
|
+
const paper = signals.has_paper === true ? 1 : 0;
|
|
1561
|
+
const raw = stars + tests + docs + recency + paper;
|
|
1562
|
+
return Math.min(10, Math.round(raw * 10 / 8));
|
|
1563
|
+
}
|
|
1564
|
+
function computeSourceEvidenceTier(source) {
|
|
1565
|
+
const score = source.quality_score ?? computeSourceQualityScore(source);
|
|
1566
|
+
const signals = source.quality_signals;
|
|
1567
|
+
const hasTests = signals?.has_tests === true;
|
|
1568
|
+
const hasPaper = signals?.has_paper === true;
|
|
1569
|
+
if (score >= 7 && (hasTests || hasPaper)) return "high";
|
|
1570
|
+
if (score >= 4 || hasTests) return "medium";
|
|
1571
|
+
return "low";
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
// src/indexer/research-index/research-index-parser.ts
|
|
1575
|
+
function buildValidationError(filePath, validationErr) {
|
|
1576
|
+
const zodErr = validationErr;
|
|
1577
|
+
const issues = zodErr.issues ?? [];
|
|
1578
|
+
const issuesPreview = issues.slice(0, 5).map((i) => `${i.path.slice(0, 4).join(".")}: ${i.message}`).join("; ");
|
|
1579
|
+
const more = issues.length > 5 ? ` (+${String(issues.length - 5)} more)` : "";
|
|
1580
|
+
return new ResearchIndexParseError(
|
|
1581
|
+
issuesPreview.length > 0 ? `Validation failed for ${filePath} \u2014 ${issuesPreview}${more}` : `Validation failed for ${filePath}`,
|
|
1582
|
+
filePath,
|
|
1583
|
+
validationErr
|
|
1584
|
+
);
|
|
1585
|
+
}
|
|
1586
|
+
function readYamlFile(filePath, schema, defaultIfMissing) {
|
|
1587
|
+
try {
|
|
1588
|
+
if (!fs2.existsSync(filePath)) {
|
|
1589
|
+
if (defaultIfMissing !== void 0) {
|
|
1590
|
+
return { ok: true, value: defaultIfMissing };
|
|
1591
|
+
}
|
|
1592
|
+
return {
|
|
1593
|
+
ok: false,
|
|
1594
|
+
error: new ResearchIndexParseError(
|
|
1595
|
+
`File not found: ${filePath}. Run 'nexus-agents research add ...' to create it.`,
|
|
1596
|
+
filePath
|
|
1597
|
+
)
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
1601
|
+
const parsed = yaml.parse(content);
|
|
1602
|
+
const result = schema.safeParse(parsed);
|
|
1603
|
+
if (!result.success) {
|
|
1604
|
+
return { ok: false, error: buildValidationError(filePath, result.error) };
|
|
1605
|
+
}
|
|
1606
|
+
return { ok: true, value: result.data };
|
|
1607
|
+
} catch (error) {
|
|
1608
|
+
return {
|
|
1609
|
+
ok: false,
|
|
1610
|
+
error: new ResearchIndexParseError(
|
|
1611
|
+
`Failed to parse ${filePath}: ${getErrorMessage(error)}`,
|
|
1612
|
+
filePath,
|
|
1613
|
+
error
|
|
1614
|
+
)
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
function parsePapersRegistry(registryPath) {
|
|
1619
|
+
const filePath = path2.join(registryPath, "papers.yaml");
|
|
1620
|
+
const result = readYamlFile(filePath, PapersRegistrySchema, {
|
|
1621
|
+
schema_version: "1.0",
|
|
1622
|
+
papers: {}
|
|
1623
|
+
});
|
|
1624
|
+
if (!result.ok) {
|
|
1625
|
+
return result;
|
|
1626
|
+
}
|
|
1627
|
+
const papers = Object.entries(result.value.papers).map(([id, paper]) => ({
|
|
1628
|
+
id,
|
|
1629
|
+
...paper
|
|
1630
|
+
}));
|
|
1631
|
+
return { ok: true, value: papers };
|
|
1632
|
+
}
|
|
1633
|
+
function parseTechniquesRegistry(registryPath) {
|
|
1634
|
+
const filePath = path2.join(registryPath, "techniques.yaml");
|
|
1635
|
+
const result = readYamlFile(filePath, TechniquesRegistrySchema, {
|
|
1636
|
+
schema_version: "1.0",
|
|
1637
|
+
techniques: {}
|
|
1638
|
+
});
|
|
1639
|
+
if (!result.ok) {
|
|
1640
|
+
return result;
|
|
1641
|
+
}
|
|
1642
|
+
const techniques = Object.entries(result.value.techniques).map(
|
|
1643
|
+
([id, technique]) => ({
|
|
1644
|
+
id,
|
|
1645
|
+
...technique
|
|
1646
|
+
})
|
|
1647
|
+
);
|
|
1648
|
+
return { ok: true, value: techniques };
|
|
1649
|
+
}
|
|
1650
|
+
function parseSourcesRegistry(registryPath) {
|
|
1651
|
+
const filePath = path2.join(registryPath, "sources.yaml");
|
|
1652
|
+
const result = readYamlFile(filePath, SourcesRegistrySchema, {
|
|
1653
|
+
schema_version: "1.0",
|
|
1654
|
+
sources: {}
|
|
1655
|
+
});
|
|
1656
|
+
if (!result.ok) {
|
|
1657
|
+
return result;
|
|
1658
|
+
}
|
|
1659
|
+
const sources = Object.entries(result.value.sources).map(
|
|
1660
|
+
([id, source]) => {
|
|
1661
|
+
const score = source.quality_score ?? computeSourceQualityScore(source);
|
|
1662
|
+
const tier = source.evidence_tier ?? computeSourceEvidenceTier({ ...source, quality_score: score });
|
|
1663
|
+
return { id, ...source, quality_score: score, evidence_tier: tier };
|
|
1664
|
+
}
|
|
1665
|
+
);
|
|
1666
|
+
return { ok: true, value: sources };
|
|
1667
|
+
}
|
|
1668
|
+
function countTechniquesByStatus(techniques) {
|
|
1669
|
+
const counts = {
|
|
1670
|
+
implemented: 0,
|
|
1671
|
+
planned: 0,
|
|
1672
|
+
"in-progress": 0,
|
|
1673
|
+
"not-started": 0,
|
|
1674
|
+
rejected: 0
|
|
1675
|
+
};
|
|
1676
|
+
for (const technique of techniques) {
|
|
1677
|
+
counts[technique.status]++;
|
|
1678
|
+
}
|
|
1679
|
+
return {
|
|
1680
|
+
implemented: counts["implemented"],
|
|
1681
|
+
planned: counts["planned"],
|
|
1682
|
+
inProgress: counts["in-progress"],
|
|
1683
|
+
notStarted: counts["not-started"],
|
|
1684
|
+
rejected: counts["rejected"]
|
|
1685
|
+
};
|
|
1686
|
+
}
|
|
1687
|
+
function countTechniquesByPriority(techniques) {
|
|
1688
|
+
const counts = {
|
|
1689
|
+
P1: 0,
|
|
1690
|
+
P2: 0,
|
|
1691
|
+
P3: 0,
|
|
1692
|
+
P4: 0,
|
|
1693
|
+
none: 0
|
|
1694
|
+
};
|
|
1695
|
+
for (const technique of techniques) {
|
|
1696
|
+
const priority = technique.priority;
|
|
1697
|
+
if (priority === null) {
|
|
1698
|
+
counts.none++;
|
|
1699
|
+
} else {
|
|
1700
|
+
counts[priority]++;
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
return counts;
|
|
1704
|
+
}
|
|
1705
|
+
function deriveAllTopics(papers, techniques) {
|
|
1706
|
+
const topicSet = /* @__PURE__ */ new Set();
|
|
1707
|
+
for (const p of papers) {
|
|
1708
|
+
for (const t of p.topics) topicSet.add(t);
|
|
1709
|
+
}
|
|
1710
|
+
for (const t of techniques) {
|
|
1711
|
+
topicSet.add(t.topic);
|
|
1712
|
+
}
|
|
1713
|
+
return [...topicSet].sort();
|
|
1714
|
+
}
|
|
1715
|
+
function computeTopicStats(papers, techniques) {
|
|
1716
|
+
const allTopics = deriveAllTopics(papers, techniques);
|
|
1717
|
+
return allTopics.map((topic) => {
|
|
1718
|
+
const paperCount = papers.filter((p) => p.topics.includes(topic)).length;
|
|
1719
|
+
const techniqueCount = techniques.filter((t) => t.topic === topic).length;
|
|
1720
|
+
return {
|
|
1721
|
+
topic,
|
|
1722
|
+
paperCount,
|
|
1723
|
+
techniqueCount
|
|
1724
|
+
};
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
function computeStats(papers, techniques, sources) {
|
|
1728
|
+
return {
|
|
1729
|
+
totalPapers: papers.length,
|
|
1730
|
+
totalTechniques: techniques.length,
|
|
1731
|
+
totalSources: sources.length,
|
|
1732
|
+
totalTopics: deriveAllTopics(papers, techniques).length,
|
|
1733
|
+
techniquesByStatus: countTechniquesByStatus(techniques),
|
|
1734
|
+
techniquesByPriority: countTechniquesByPriority(techniques),
|
|
1735
|
+
topicStats: computeTopicStats(papers, techniques),
|
|
1736
|
+
qualityDistribution: computeQualityDistribution(papers)
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1739
|
+
function computeQualityDistribution(papers) {
|
|
1740
|
+
let high = 0;
|
|
1741
|
+
let medium = 0;
|
|
1742
|
+
let low = 0;
|
|
1743
|
+
let unscored = 0;
|
|
1744
|
+
let totalScore = 0;
|
|
1745
|
+
let scored = 0;
|
|
1746
|
+
for (const p of papers) {
|
|
1747
|
+
const tier = p.evidence_tier;
|
|
1748
|
+
if (tier === "high") high++;
|
|
1749
|
+
else if (tier === "medium") medium++;
|
|
1750
|
+
else if (tier === "low") low++;
|
|
1751
|
+
else unscored++;
|
|
1752
|
+
const score = p.quality_score;
|
|
1753
|
+
if (score !== void 0) {
|
|
1754
|
+
totalScore += score;
|
|
1755
|
+
scored++;
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
return {
|
|
1759
|
+
high,
|
|
1760
|
+
medium,
|
|
1761
|
+
low,
|
|
1762
|
+
unscored,
|
|
1763
|
+
averageScore: scored > 0 ? Math.round(totalScore / scored * 10) / 10 : 0
|
|
1764
|
+
};
|
|
1765
|
+
}
|
|
1766
|
+
function parseRegistry(options = {}) {
|
|
1767
|
+
const opts = { ...DEFAULT_RESEARCH_INDEX_PARSER_OPTIONS, ...options };
|
|
1768
|
+
const papersResult = parsePapersRegistry(opts.registryPath);
|
|
1769
|
+
if (!papersResult.ok) {
|
|
1770
|
+
return papersResult;
|
|
1771
|
+
}
|
|
1772
|
+
const techniquesResult = parseTechniquesRegistry(opts.registryPath);
|
|
1773
|
+
if (!techniquesResult.ok) {
|
|
1774
|
+
return techniquesResult;
|
|
1775
|
+
}
|
|
1776
|
+
const sourcesResult = parseSourcesRegistry(opts.registryPath);
|
|
1777
|
+
if (!sourcesResult.ok) {
|
|
1778
|
+
return sourcesResult;
|
|
1779
|
+
}
|
|
1780
|
+
const stats = computeStats(papersResult.value, techniquesResult.value, sourcesResult.value);
|
|
1781
|
+
const now = new Date(getTimeProvider().now());
|
|
1782
|
+
const etFormatter = new Intl.DateTimeFormat("en-US", {
|
|
1783
|
+
timeZone: "America/New_York",
|
|
1784
|
+
year: "numeric",
|
|
1785
|
+
month: "2-digit",
|
|
1786
|
+
day: "2-digit",
|
|
1787
|
+
hour: "2-digit",
|
|
1788
|
+
minute: "2-digit",
|
|
1789
|
+
second: "2-digit",
|
|
1790
|
+
hour12: false
|
|
1791
|
+
});
|
|
1792
|
+
const parts = etFormatter.formatToParts(now);
|
|
1793
|
+
const getPartValue = (type) => parts.find((p) => p.type === type)?.value ?? "";
|
|
1794
|
+
const generatedAt = `${getPartValue("year")}-${getPartValue("month")}-${getPartValue("day")} (ET)`;
|
|
1795
|
+
return {
|
|
1796
|
+
ok: true,
|
|
1797
|
+
value: {
|
|
1798
|
+
schemaVersion: "1.0",
|
|
1799
|
+
generatedAt,
|
|
1800
|
+
papers: papersResult.value,
|
|
1801
|
+
techniques: techniquesResult.value,
|
|
1802
|
+
sources: sourcesResult.value,
|
|
1803
|
+
stats
|
|
1804
|
+
}
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
function getTechniquesByPriority(index, priority) {
|
|
1808
|
+
return index.techniques.filter((t) => t.priority === priority);
|
|
1809
|
+
}
|
|
1810
|
+
function getPapersByTopic(index, topic) {
|
|
1811
|
+
return index.papers.filter((p) => p.topics.includes(topic));
|
|
1812
|
+
}
|
|
1813
|
+
function getRecentlyReviewedPapers(index, limit = 5) {
|
|
1814
|
+
return [...index.papers].filter(
|
|
1815
|
+
(p) => typeof p.reviewed_date === "string"
|
|
1816
|
+
).sort((a, b) => b.reviewed_date.localeCompare(a.reviewed_date)).slice(0, limit);
|
|
1817
|
+
}
|
|
1818
|
+
function getTechniquesWithIssues(index) {
|
|
1819
|
+
return index.techniques.filter((t) => t.implementation_issue !== null);
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
// src/indexer/research-index/research-index-generator-helpers.ts
|
|
1823
|
+
function generateHeader(index) {
|
|
1824
|
+
const { stats } = index;
|
|
1825
|
+
return `# Nexus-Agents Research Index
|
|
1826
|
+
|
|
1827
|
+
**Generated:** ${index.generatedAt}
|
|
1828
|
+
**Total Papers:** ${String(stats.totalPapers)} | **Techniques:** ${String(stats.totalTechniques)} | **Topics:** ${String(stats.totalTopics)}
|
|
1829
|
+
|
|
1830
|
+
---`;
|
|
1831
|
+
}
|
|
1832
|
+
function generateQuickStats(index) {
|
|
1833
|
+
const { techniquesByStatus } = index.stats;
|
|
1834
|
+
return `## Quick Stats
|
|
1835
|
+
|
|
1836
|
+
| Status | Papers | Techniques |
|
|
1837
|
+
| ----------- | ------ | ---------- |
|
|
1838
|
+
| Implemented | - | ${String(techniquesByStatus.implemented)} |
|
|
1839
|
+
| In Progress | - | ${String(techniquesByStatus.inProgress)} |
|
|
1840
|
+
| Planned | - | ${String(techniquesByStatus.planned)} |
|
|
1841
|
+
| Not Started | - | ${String(techniquesByStatus.notStarted)} |
|
|
1842
|
+
| Rejected | - | ${String(techniquesByStatus.rejected)} |
|
|
1843
|
+
|
|
1844
|
+
> **Note:** Paper-level status tracking deprecated. Technique status is source of truth.`;
|
|
1845
|
+
}
|
|
1846
|
+
function generateTopicsTable(index) {
|
|
1847
|
+
const { topicStats } = index.stats;
|
|
1848
|
+
const topicDescriptions = {
|
|
1849
|
+
consensus: "Multi-agent decision protocols",
|
|
1850
|
+
routing: "Cost-efficient model routing",
|
|
1851
|
+
memory: "Context and long-term memory",
|
|
1852
|
+
"code-generation": "Self-improvement and skill learning",
|
|
1853
|
+
"cli-tools": "External CLI integration",
|
|
1854
|
+
orchestration: "Multi-agent coordination",
|
|
1855
|
+
security: "Agent safety and security evaluation",
|
|
1856
|
+
"multi-agent-worker-dispatch": "Worker dispatch and wave execution"
|
|
1857
|
+
};
|
|
1858
|
+
const rows = topicStats.map((stat) => {
|
|
1859
|
+
const description = topicDescriptions[stat.topic];
|
|
1860
|
+
const topicLink = `[${capitalizeWords(stat.topic.replaceAll("-", " "))}](topics/${stat.topic}/README.md)`;
|
|
1861
|
+
return `| ${topicLink} | ${String(stat.paperCount)} | ${String(stat.techniqueCount)} | ${description ?? ""} |`;
|
|
1862
|
+
});
|
|
1863
|
+
return `---
|
|
1864
|
+
|
|
1865
|
+
## Topics
|
|
1866
|
+
|
|
1867
|
+
| Topic | Papers | Techniques | Description |
|
|
1868
|
+
| ----- | ------ | ---------- | ----------- |
|
|
1869
|
+
${rows.join("\n")}`;
|
|
1870
|
+
}
|
|
1871
|
+
function generateTechniqueRow(technique) {
|
|
1872
|
+
const name = `[${technique.name}](registry/techniques.yaml#${technique.id})`;
|
|
1873
|
+
const topic = technique.topic;
|
|
1874
|
+
const metricsEntries = Object.entries(technique.metrics);
|
|
1875
|
+
const metrics = metricsEntries.length > 0 ? metricsEntries.map(([key, value]) => `${key}: ${value}`).join(", ") : "-";
|
|
1876
|
+
const issue = technique.implementation_issue !== null ? `#${String(technique.implementation_issue)}` : "-";
|
|
1877
|
+
return `| ${name} | ${topic} | ${metrics} | ${issue} |`;
|
|
1878
|
+
}
|
|
1879
|
+
function generateP1Table(index) {
|
|
1880
|
+
const p1Techniques = getTechniquesByPriority(index, "P1");
|
|
1881
|
+
if (p1Techniques.length === 0) {
|
|
1882
|
+
return "";
|
|
1883
|
+
}
|
|
1884
|
+
const rows = p1Techniques.map(generateTechniqueRow);
|
|
1885
|
+
return `---
|
|
1886
|
+
|
|
1887
|
+
## Priority 1 (P1) Techniques
|
|
1888
|
+
|
|
1889
|
+
These techniques are high-impact and align well with the current architecture.
|
|
1890
|
+
|
|
1891
|
+
| Technique | Topic | Key Metrics | Issue |
|
|
1892
|
+
| --------- | ----- | ----------- | ----- |
|
|
1893
|
+
${rows.join("\n")}`;
|
|
1894
|
+
}
|
|
1895
|
+
function generateP2Table(index) {
|
|
1896
|
+
const p2Techniques = getTechniquesByPriority(index, "P2");
|
|
1897
|
+
if (p2Techniques.length === 0) {
|
|
1898
|
+
return "";
|
|
1899
|
+
}
|
|
1900
|
+
const rows = p2Techniques.map(generateTechniqueRow);
|
|
1901
|
+
return `---
|
|
1902
|
+
|
|
1903
|
+
## Priority 2 (P2) Techniques
|
|
1904
|
+
|
|
1905
|
+
Medium-impact or requiring moderate changes.
|
|
1906
|
+
|
|
1907
|
+
| Technique | Topic | Key Metrics | Issue |
|
|
1908
|
+
| --------- | ----- | ----------- | ----- |
|
|
1909
|
+
${rows.join("\n")}`;
|
|
1910
|
+
}
|
|
1911
|
+
function generateRecentPapers(index, limit) {
|
|
1912
|
+
const recentPapers = getRecentlyReviewedPapers(index, limit);
|
|
1913
|
+
if (recentPapers.length === 0) {
|
|
1914
|
+
return "";
|
|
1915
|
+
}
|
|
1916
|
+
const rows = recentPapers.map((paper) => {
|
|
1917
|
+
const paperUrl = paper.url ?? "";
|
|
1918
|
+
const title = paperUrl !== "" ? `[${paper.title.replace(/\|/g, "-")}](${paperUrl})` : paper.title.replace(/\|/g, "-");
|
|
1919
|
+
const topic = paper.topics[0] ?? "-";
|
|
1920
|
+
const priority = "-";
|
|
1921
|
+
const dateStr = paper.reviewed_date ?? "-";
|
|
1922
|
+
return `| ${dateStr} | ${title} | ${topic} | ${priority} |`;
|
|
1923
|
+
});
|
|
1924
|
+
return `---
|
|
1925
|
+
|
|
1926
|
+
## Recently Reviewed Papers
|
|
1927
|
+
|
|
1928
|
+
| Date | Paper | Topic | Priority |
|
|
1929
|
+
| ---- | ----- | ----- | -------- |
|
|
1930
|
+
${rows.join("\n")}`;
|
|
1931
|
+
}
|
|
1932
|
+
function generatePapersByTopic(index) {
|
|
1933
|
+
const topics = [
|
|
1934
|
+
"consensus",
|
|
1935
|
+
"routing",
|
|
1936
|
+
"memory",
|
|
1937
|
+
"code-generation",
|
|
1938
|
+
"orchestration"
|
|
1939
|
+
];
|
|
1940
|
+
const sections = topics.map((topic) => {
|
|
1941
|
+
const papers = getPapersByTopic(index, topic);
|
|
1942
|
+
if (papers.length === 0) {
|
|
1943
|
+
return "";
|
|
1944
|
+
}
|
|
1945
|
+
const paperList = papers.map((paper) => {
|
|
1946
|
+
const paperUrl = paper.url ?? "";
|
|
1947
|
+
const title = paperUrl !== "" ? `[${paper.title}](${paperUrl})` : paper.title;
|
|
1948
|
+
const summaryText = paper.summary?.trim().split("\n")[0] ?? "";
|
|
1949
|
+
const summaryPart = summaryText !== "" ? ` - ${summaryText}` : "";
|
|
1950
|
+
return `- ${title}${summaryPart}`;
|
|
1951
|
+
}).join("\n");
|
|
1952
|
+
return `### ${capitalizeWords(topic.replaceAll("-", " "))} (${String(papers.length)} papers)
|
|
1953
|
+
|
|
1954
|
+
${paperList}`;
|
|
1955
|
+
});
|
|
1956
|
+
const nonEmptySections = sections.filter((s) => s.length > 0);
|
|
1957
|
+
if (nonEmptySections.length === 0) {
|
|
1958
|
+
return "";
|
|
1959
|
+
}
|
|
1960
|
+
return `---
|
|
1961
|
+
|
|
1962
|
+
## Papers by Topic
|
|
1963
|
+
|
|
1964
|
+
${nonEmptySections.join("\n\n")}`;
|
|
1965
|
+
}
|
|
1966
|
+
function generateGitHubIssues(index) {
|
|
1967
|
+
const techniquesWithIssues = getTechniquesWithIssues(index);
|
|
1968
|
+
if (techniquesWithIssues.length === 0) {
|
|
1969
|
+
return "";
|
|
1970
|
+
}
|
|
1971
|
+
const rows = techniquesWithIssues.map((technique) => {
|
|
1972
|
+
const papers = technique.source_papers.length > 0 ? technique.source_papers.join(", ") : "-";
|
|
1973
|
+
const issueStr = String(technique.implementation_issue ?? 0);
|
|
1974
|
+
return `| #${issueStr} | ${technique.name} | ${papers} |`;
|
|
1975
|
+
});
|
|
1976
|
+
return `---
|
|
1977
|
+
|
|
1978
|
+
## GitHub Issues
|
|
1979
|
+
|
|
1980
|
+
| Issue | Feature | Related Papers |
|
|
1981
|
+
| ----- | ------- | -------------- |
|
|
1982
|
+
${rows.join("\n")}`;
|
|
1983
|
+
}
|
|
1984
|
+
function generateSearchTags(index) {
|
|
1985
|
+
const allTags = /* @__PURE__ */ new Set();
|
|
1986
|
+
for (const technique of index.techniques) {
|
|
1987
|
+
for (const tag of technique.tags) {
|
|
1988
|
+
allTags.add(tag);
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
const tagList = Array.from(allTags).sort().slice(0, 20).map((tag) => `\`#${tag}\``).join(" ");
|
|
1992
|
+
return `---
|
|
1993
|
+
|
|
1994
|
+
## Search Tags
|
|
1995
|
+
|
|
1996
|
+
${tagList}`;
|
|
1997
|
+
}
|
|
1998
|
+
function generateRegistryFiles(index) {
|
|
1999
|
+
const paperCount = String(index.stats.totalPapers);
|
|
2000
|
+
const techniqueCount = String(index.stats.totalTechniques);
|
|
2001
|
+
return `---
|
|
2002
|
+
|
|
2003
|
+
## Registry Files
|
|
2004
|
+
|
|
2005
|
+
- [papers.yaml](registry/papers.yaml) - All ${paperCount} papers with metadata
|
|
2006
|
+
- [techniques.yaml](registry/techniques.yaml) - All ${techniqueCount} techniques with status
|
|
2007
|
+
- [sources.yaml](registry/sources.yaml) - Product docs and other sources`;
|
|
2008
|
+
}
|
|
2009
|
+
function generateContributingSection() {
|
|
2010
|
+
const dateStr = getTimeProvider().nowDateString();
|
|
2011
|
+
return `---
|
|
2012
|
+
|
|
2013
|
+
## How to Contribute
|
|
2014
|
+
|
|
2015
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on adding new research.
|
|
2016
|
+
|
|
2017
|
+
---
|
|
2018
|
+
|
|
2019
|
+
_Generated from YAML registries. Last updated: ${dateStr} (ET)_`;
|
|
2020
|
+
}
|
|
2021
|
+
function addSection(sections, section) {
|
|
2022
|
+
if (section !== "") {
|
|
2023
|
+
sections.push(section);
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
function buildOptionalSections(index, opts) {
|
|
2027
|
+
const sections = [];
|
|
2028
|
+
if (opts.includeP1Table) {
|
|
2029
|
+
addSection(sections, generateP1Table(index));
|
|
2030
|
+
}
|
|
2031
|
+
if (opts.includeP2Table) {
|
|
2032
|
+
addSection(sections, generateP2Table(index));
|
|
2033
|
+
}
|
|
2034
|
+
if (opts.includeRecentPapers) {
|
|
2035
|
+
addSection(sections, generateRecentPapers(index, opts.recentPapersLimit));
|
|
2036
|
+
}
|
|
2037
|
+
if (opts.includePapersByTopic) {
|
|
2038
|
+
addSection(sections, generatePapersByTopic(index));
|
|
2039
|
+
}
|
|
2040
|
+
if (opts.includeGitHubIssues) {
|
|
2041
|
+
addSection(sections, generateGitHubIssues(index));
|
|
2042
|
+
}
|
|
2043
|
+
return sections;
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
// src/indexer/research-index/research-index-generator.ts
|
|
2047
|
+
function generateIndexMarkdown(index, options = {}) {
|
|
2048
|
+
try {
|
|
2049
|
+
const opts = { ...DEFAULT_RESEARCH_INDEX_GENERATOR_OPTIONS, ...options };
|
|
2050
|
+
const sections = [
|
|
2051
|
+
generateHeader(index),
|
|
2052
|
+
generateQuickStats(index),
|
|
2053
|
+
generateTopicsTable(index),
|
|
2054
|
+
...buildOptionalSections(index, opts),
|
|
2055
|
+
generateSearchTags(index),
|
|
2056
|
+
generateRegistryFiles(index),
|
|
2057
|
+
generateContributingSection()
|
|
2058
|
+
];
|
|
2059
|
+
const markdown = sections.join("\n\n");
|
|
2060
|
+
return { ok: true, value: markdown };
|
|
2061
|
+
} catch (error) {
|
|
2062
|
+
return {
|
|
2063
|
+
ok: false,
|
|
2064
|
+
error: new ResearchIndexGeneratorError(
|
|
2065
|
+
`Failed to generate markdown: ${getErrorMessage(error)}`,
|
|
2066
|
+
error
|
|
2067
|
+
)
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
function generateStatsJson(index) {
|
|
2072
|
+
return JSON.stringify(
|
|
2073
|
+
{
|
|
2074
|
+
schemaVersion: index.schemaVersion,
|
|
2075
|
+
generatedAt: index.generatedAt,
|
|
2076
|
+
stats: index.stats
|
|
2077
|
+
},
|
|
2078
|
+
null,
|
|
2079
|
+
2
|
|
2080
|
+
);
|
|
2081
|
+
}
|
|
2082
|
+
function generateSummaryReport(index) {
|
|
2083
|
+
const { stats } = index;
|
|
2084
|
+
const { techniquesByStatus } = stats;
|
|
2085
|
+
const implementedPercent = stats.totalTechniques > 0 ? Math.round(techniquesByStatus.implemented / stats.totalTechniques * 100) : 0;
|
|
2086
|
+
const topicLines = stats.topicStats.map(
|
|
2087
|
+
(t) => `- ${t.topic}: ${String(t.techniqueCount)} techniques, ${String(t.paperCount)} papers`
|
|
2088
|
+
).join("\n");
|
|
2089
|
+
return `# Research Index Summary
|
|
2090
|
+
|
|
2091
|
+
**Generated:** ${index.generatedAt}
|
|
2092
|
+
|
|
2093
|
+
## Overview
|
|
2094
|
+
- **Total Papers:** ${String(stats.totalPapers)}
|
|
2095
|
+
- **Total Techniques:** ${String(stats.totalTechniques)}
|
|
2096
|
+
- **Implementation Rate:** ${String(implementedPercent)}%
|
|
2097
|
+
|
|
2098
|
+
## Technique Status
|
|
2099
|
+
- Implemented: ${String(techniquesByStatus.implemented)}
|
|
2100
|
+
- Planned: ${String(techniquesByStatus.planned)}
|
|
2101
|
+
- Not Started: ${String(techniquesByStatus.notStarted)}
|
|
2102
|
+
- Rejected: ${String(techniquesByStatus.rejected)}
|
|
2103
|
+
|
|
2104
|
+
## Evidence Quality Distribution
|
|
2105
|
+
${stats.qualityDistribution !== void 0 ? `- High evidence: ${String(stats.qualityDistribution.high)} papers
|
|
2106
|
+
- Medium evidence: ${String(stats.qualityDistribution.medium)} papers
|
|
2107
|
+
- Low evidence: ${String(stats.qualityDistribution.low)} papers
|
|
2108
|
+
- Unscored: ${String(stats.qualityDistribution.unscored)} papers
|
|
2109
|
+
- Average quality score: ${String(stats.qualityDistribution.averageScore)}/10` : "- Quality scoring not yet applied"}
|
|
2110
|
+
|
|
2111
|
+
## Topics Coverage
|
|
2112
|
+
${topicLines}
|
|
2113
|
+
`;
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
// src/cli/research-helpers-index-ops.ts
|
|
2117
|
+
function getRegistryPath() {
|
|
2118
|
+
return path3.resolve(process.cwd(), "docs/research/registry");
|
|
2119
|
+
}
|
|
2120
|
+
function getIndexPath() {
|
|
2121
|
+
return path3.resolve(process.cwd(), "docs/research/RESEARCH_INDEX.md");
|
|
2122
|
+
}
|
|
2123
|
+
async function handleStatsCommand(options) {
|
|
2124
|
+
const registryPath = getRegistryPath();
|
|
2125
|
+
const result = parseRegistry({ registryPath });
|
|
2126
|
+
if (!result.ok) {
|
|
2127
|
+
return `Error: Failed to parse registry: ${result.error.message}`;
|
|
2128
|
+
}
|
|
2129
|
+
const index = result.value;
|
|
2130
|
+
const formatVal = options["format"];
|
|
2131
|
+
const format = typeof formatVal === "string" ? formatVal : void 0;
|
|
2132
|
+
await Promise.resolve();
|
|
2133
|
+
if (format === "json") {
|
|
2134
|
+
return generateStatsJson(index);
|
|
2135
|
+
}
|
|
2136
|
+
return generateSummaryReport(index);
|
|
2137
|
+
}
|
|
2138
|
+
async function handleRefreshCommand(options) {
|
|
2139
|
+
const outputVal = options["output"];
|
|
2140
|
+
const outputPath = (typeof outputVal === "string" ? outputVal : void 0) ?? getIndexPath();
|
|
2141
|
+
const registryPath = getRegistryPath();
|
|
2142
|
+
const result = parseRegistry({ registryPath });
|
|
2143
|
+
if (!result.ok) {
|
|
2144
|
+
return `Error: Failed to parse registry: ${result.error.message}`;
|
|
2145
|
+
}
|
|
2146
|
+
const index = result.value;
|
|
2147
|
+
const mdResult = generateIndexMarkdown(index);
|
|
2148
|
+
if (!mdResult.ok) {
|
|
2149
|
+
return `Error: Failed to generate markdown: ${mdResult.error.message}`;
|
|
2150
|
+
}
|
|
2151
|
+
await fs3.mkdir(path3.dirname(outputPath), { recursive: true });
|
|
2152
|
+
await fs3.writeFile(outputPath, mdResult.value, "utf-8");
|
|
2153
|
+
const stats = index.stats;
|
|
2154
|
+
return [
|
|
2155
|
+
`Research index regenerated successfully`,
|
|
2156
|
+
` Output: ${outputPath}`,
|
|
2157
|
+
` Papers: ${String(stats.totalPapers)}`,
|
|
2158
|
+
` Techniques: ${String(stats.totalTechniques)}`,
|
|
2159
|
+
` Implemented: ${String(stats.techniquesByStatus.implemented)}`
|
|
2160
|
+
].join("\n");
|
|
2161
|
+
}
|
|
2162
|
+
async function handleCheckCommand() {
|
|
2163
|
+
const indexPath = getIndexPath();
|
|
2164
|
+
try {
|
|
2165
|
+
await fs3.access(indexPath);
|
|
2166
|
+
} catch {
|
|
2167
|
+
return `Error: Research index not found: ${indexPath}. Run 'nexus-agents research refresh' first.`;
|
|
2168
|
+
}
|
|
2169
|
+
const registryPath = getRegistryPath();
|
|
2170
|
+
const result = parseRegistry({ registryPath });
|
|
2171
|
+
if (!result.ok) {
|
|
2172
|
+
return `Error: Failed to parse registry: ${result.error.message}`;
|
|
2173
|
+
}
|
|
2174
|
+
const mdResult = generateIndexMarkdown(result.value);
|
|
2175
|
+
if (!mdResult.ok) {
|
|
2176
|
+
return `Error: Failed to generate markdown: ${mdResult.error.message}`;
|
|
2177
|
+
}
|
|
2178
|
+
const existingContent = await fs3.readFile(indexPath, "utf-8");
|
|
2179
|
+
const freshContent = mdResult.value;
|
|
2180
|
+
const normalize = (s) => s.replace(/\s+/g, " ").trim();
|
|
2181
|
+
const isFresh = normalize(existingContent) === normalize(freshContent);
|
|
2182
|
+
if (isFresh) {
|
|
2183
|
+
return `Research index is up to date (${String(result.value.stats.totalTechniques)} techniques)`;
|
|
2184
|
+
}
|
|
2185
|
+
return `Research index is out of date. Run "nexus-agents research refresh" to update.`;
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
// src/cli/research-helpers-issues.ts
|
|
2189
|
+
import { execFile } from "child_process";
|
|
2190
|
+
import { promisify } from "util";
|
|
2191
|
+
var execFileAsync = promisify(execFile);
|
|
2192
|
+
function formatResearchIssueBody(findings, voteResult) {
|
|
2193
|
+
const lines = [];
|
|
2194
|
+
lines.push("## Research Findings");
|
|
2195
|
+
lines.push("");
|
|
2196
|
+
for (const finding of findings) {
|
|
2197
|
+
lines.push(`### ${finding.title}`);
|
|
2198
|
+
lines.push("");
|
|
2199
|
+
lines.push(`- **Source:** ${finding.source}`);
|
|
2200
|
+
lines.push(`- **URL:** ${finding.url}`);
|
|
2201
|
+
lines.push(`- **Relevance:** ${finding.relevance}`);
|
|
2202
|
+
if (finding.priority !== void 0) {
|
|
2203
|
+
lines.push(`- **Priority:** ${finding.priority}`);
|
|
2204
|
+
}
|
|
2205
|
+
lines.push("");
|
|
2206
|
+
lines.push(finding.description);
|
|
2207
|
+
lines.push("");
|
|
2208
|
+
}
|
|
2209
|
+
if (voteResult !== void 0) {
|
|
2210
|
+
lines.push("## Consensus Vote Result");
|
|
2211
|
+
lines.push("");
|
|
2212
|
+
lines.push(`- **Decision:** ${voteResult.decision}`);
|
|
2213
|
+
lines.push(`- **Approval:** ${String(voteResult.approvalPercentage)}%`);
|
|
2214
|
+
lines.push(`- **Strategy:** ${voteResult.strategy}`);
|
|
2215
|
+
lines.push("");
|
|
2216
|
+
}
|
|
2217
|
+
lines.push("---");
|
|
2218
|
+
lines.push("*Created by nexus-agents research workflow*");
|
|
2219
|
+
return lines.join("\n");
|
|
2220
|
+
}
|
|
2221
|
+
function buildIssueArgs(options) {
|
|
2222
|
+
const args = ["issue", "create", "--title", options.title, "--body", options.body];
|
|
2223
|
+
if (options.labels.length > 0) args.push("--label", options.labels.join(","));
|
|
2224
|
+
if (options.assignee !== void 0) args.push("--assignee", options.assignee);
|
|
2225
|
+
return args;
|
|
2226
|
+
}
|
|
2227
|
+
function mapGhError(message) {
|
|
2228
|
+
if (message.includes("not found") || message.includes("ENOENT")) {
|
|
2229
|
+
return { code: "GH_NOT_FOUND", message: "GitHub CLI (gh) not found" };
|
|
2230
|
+
}
|
|
2231
|
+
if (message.includes("auth") || message.includes("401")) {
|
|
2232
|
+
return { code: "GH_AUTH_FAILED", message: "GitHub CLI not authenticated" };
|
|
2233
|
+
}
|
|
2234
|
+
return { code: "GH_ERROR", message: `Failed to create issue: ${message}` };
|
|
2235
|
+
}
|
|
2236
|
+
async function createResearchIssue(options) {
|
|
2237
|
+
try {
|
|
2238
|
+
const { stdout } = await execFileAsync("gh", buildIssueArgs(options), {
|
|
2239
|
+
timeout: CLI_SUBPROCESS_TIMEOUTS.ghCommandMs
|
|
2240
|
+
});
|
|
2241
|
+
const url = stdout.trim();
|
|
2242
|
+
return { ok: true, value: { success: true, url, message: `Issue created: ${url}` } };
|
|
2243
|
+
} catch (error) {
|
|
2244
|
+
const message = getErrorMessage(error);
|
|
2245
|
+
return { ok: false, error: mapGhError(message) };
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
// src/context/context-retriever.ts
|
|
2250
|
+
var DEFAULT_LIMIT = 5;
|
|
2251
|
+
async function getContextForTask(options) {
|
|
2252
|
+
const limit = options.limit ?? DEFAULT_LIMIT;
|
|
2253
|
+
const logger = options.logger ?? createLogger({ component: "ContextRetriever" });
|
|
2254
|
+
const [
|
|
2255
|
+
beliefs,
|
|
2256
|
+
similarMemories,
|
|
2257
|
+
recentLearnings,
|
|
2258
|
+
experiencePatterns,
|
|
2259
|
+
outcomes,
|
|
2260
|
+
researchInsights
|
|
2261
|
+
] = await Promise.all([
|
|
2262
|
+
fetchBeliefs(options.task, limit, logger),
|
|
2263
|
+
fetchSimilarMemories(options.task, limit, logger),
|
|
2264
|
+
fetchRecentLearnings(options.task, limit, logger),
|
|
2265
|
+
fetchExperiencePatterns(options.task, limit, logger),
|
|
2266
|
+
fetchOutcomes(options.category, logger),
|
|
2267
|
+
fetchResearchInsights(options.task, limit, logger)
|
|
2268
|
+
]);
|
|
2269
|
+
const priorStrategies = fetchPriorStrategies(options.category, limit, logger);
|
|
2270
|
+
return {
|
|
2271
|
+
beliefs,
|
|
2272
|
+
similarMemories,
|
|
2273
|
+
recentLearnings,
|
|
2274
|
+
experiencePatterns,
|
|
2275
|
+
outcomes,
|
|
2276
|
+
priorStrategies,
|
|
2277
|
+
researchInsights
|
|
2278
|
+
};
|
|
2279
|
+
}
|
|
2280
|
+
var MIN_RELEVANCE_TOKEN = 4;
|
|
2281
|
+
function selectRelevantResearch(techniques, task, limit) {
|
|
2282
|
+
const taskTokens = tokenize(task);
|
|
2283
|
+
if (taskTokens.size === 0) return [];
|
|
2284
|
+
const matches = [];
|
|
2285
|
+
for (const t of techniques) {
|
|
2286
|
+
const fieldTokens = tokenize(`${t.name} ${t.topic}`);
|
|
2287
|
+
let hit = false;
|
|
2288
|
+
for (const tok of fieldTokens) {
|
|
2289
|
+
if (taskTokens.has(tok)) {
|
|
2290
|
+
hit = true;
|
|
2291
|
+
break;
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
if (hit) {
|
|
2295
|
+
matches.push(t);
|
|
2296
|
+
if (matches.length >= limit) break;
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
return matches;
|
|
2300
|
+
}
|
|
2301
|
+
function tokenize(text) {
|
|
2302
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
2303
|
+
for (const raw of text.toLowerCase().split(/[^a-z0-9]+/)) {
|
|
2304
|
+
if (raw.length >= MIN_RELEVANCE_TOKEN) tokens.add(raw);
|
|
2305
|
+
}
|
|
2306
|
+
return tokens;
|
|
2307
|
+
}
|
|
2308
|
+
async function fetchResearchInsights(task, limit, logger) {
|
|
2309
|
+
try {
|
|
2310
|
+
const result = await getResearchStatus({ status: "all", format: "json" });
|
|
2311
|
+
if (!result.success) return [];
|
|
2312
|
+
return selectRelevantResearch(result.techniques, task, limit);
|
|
2313
|
+
} catch (error) {
|
|
2314
|
+
logger.debug("ContextRetriever: research insights fetch failed", {
|
|
2315
|
+
error: formatError(error)
|
|
2316
|
+
});
|
|
2317
|
+
return [];
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
function fetchPriorStrategies(category, limit, logger) {
|
|
2321
|
+
try {
|
|
2322
|
+
const all = loadPersistedRules();
|
|
2323
|
+
return all.filter((r) => r.status === "active" && !r.tainted).filter((r) => r.category === category || r.category === "*").slice(0, limit);
|
|
2324
|
+
} catch (error) {
|
|
2325
|
+
logger.debug("ContextRetriever: prior-strategies fetch failed", {
|
|
2326
|
+
error: formatError(error)
|
|
2327
|
+
});
|
|
2328
|
+
return [];
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
async function fetchBeliefs(task, limit, logger) {
|
|
2332
|
+
try {
|
|
2333
|
+
const tm = getToolMemory(logger);
|
|
2334
|
+
const beliefs = tm.getBeliefMemory();
|
|
2335
|
+
const result = await beliefs.recallBySubject(task, limit);
|
|
2336
|
+
return result.ok ? result.value : [];
|
|
2337
|
+
} catch (error) {
|
|
2338
|
+
logger.debug("ContextRetriever: belief fetch failed", { error: formatError(error) });
|
|
2339
|
+
return [];
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
async function fetchSimilarMemories(task, limit, logger) {
|
|
2343
|
+
try {
|
|
2344
|
+
const tm = getToolMemory(logger);
|
|
2345
|
+
const agentic = tm.getAgenticMemoryBackend();
|
|
2346
|
+
if (agentic === null) return [];
|
|
2347
|
+
const result = await agentic.searchAgentic(task, limit);
|
|
2348
|
+
return result.ok ? result.value : [];
|
|
2349
|
+
} catch (error) {
|
|
2350
|
+
logger.debug("ContextRetriever: agentic search failed", { error: formatError(error) });
|
|
2351
|
+
return [];
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
async function fetchRecentLearnings(task, limit, logger) {
|
|
2355
|
+
try {
|
|
2356
|
+
const tm = getToolMemory(logger);
|
|
2357
|
+
const adaptive = tm.getAdaptiveMemoryBackend();
|
|
2358
|
+
if (adaptive === null) return [];
|
|
2359
|
+
const result = await adaptive.retrieveByPriority({ query: task, limit });
|
|
2360
|
+
return result.ok ? result.value : [];
|
|
2361
|
+
} catch (error) {
|
|
2362
|
+
logger.debug("ContextRetriever: adaptive fetch failed", { error: formatError(error) });
|
|
2363
|
+
return [];
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
function fetchExperiencePatterns(task, limit, logger) {
|
|
2367
|
+
try {
|
|
2368
|
+
const tm = getToolMemory(logger);
|
|
2369
|
+
const mobimem = tm.getMobiMem();
|
|
2370
|
+
if (mobimem === null) return Promise.resolve([]);
|
|
2371
|
+
return Promise.resolve(mobimem.experience.findPatterns(task, limit));
|
|
2372
|
+
} catch (error) {
|
|
2373
|
+
logger.debug("ContextRetriever: mobimem fetch failed", { error: formatError(error) });
|
|
2374
|
+
return Promise.resolve([]);
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
function fetchOutcomes(category, logger) {
|
|
2378
|
+
try {
|
|
2379
|
+
const store = getOutcomeStore();
|
|
2380
|
+
return Promise.resolve(store.summarize({ category }));
|
|
2381
|
+
} catch (error) {
|
|
2382
|
+
logger.debug("ContextRetriever: outcomes fetch failed", { error: formatError(error) });
|
|
2383
|
+
return Promise.resolve(null);
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
function formatError(error) {
|
|
2387
|
+
return error instanceof Error ? error.message : String(error);
|
|
2388
|
+
}
|
|
2389
|
+
function inferTaskCategory(task) {
|
|
2390
|
+
const t = task.toLowerCase();
|
|
2391
|
+
if (/security|vulnerab|cve|threat|owasp|injection|xss/.test(t)) return "security_review";
|
|
2392
|
+
if (/architect|design doc|rfc|adr|system design/.test(t)) return "architecture";
|
|
2393
|
+
if (/test|spec|coverage|vitest|jest|pytest/.test(t)) return "testing";
|
|
2394
|
+
if (/review|audit|critique|feedback/.test(t)) return "code_review";
|
|
2395
|
+
if (/docs|documentation|readme|tutorial|guide/.test(t)) return "documentation";
|
|
2396
|
+
if (/plan|roadmap|epic|sprint|breakdown/.test(t)) return "planning";
|
|
2397
|
+
if (/research|investigate|explore|survey|analyze/.test(t)) return "research";
|
|
2398
|
+
if (/deploy| ci |\bcd\b|pipeline|kubernetes|docker|infra|terraform/.test(t)) return "devops";
|
|
2399
|
+
if (/implement|build|create|add|refactor|fix|bug|feature/.test(t)) return "code_generation";
|
|
2400
|
+
return "exploration";
|
|
2401
|
+
}
|
|
2402
|
+
function summarizeContextForPrompt(ctx) {
|
|
2403
|
+
const sections = [];
|
|
2404
|
+
if (ctx.beliefs.length > 0) {
|
|
2405
|
+
const lines = ctx.beliefs.slice(0, 5).map((b) => `- ${b.subject} ${b.predicate} ${b.object} (confidence: ${b.confidence})`);
|
|
2406
|
+
sections.push(`### Beliefs
|
|
2407
|
+
${lines.join("\n")}`);
|
|
2408
|
+
}
|
|
2409
|
+
if (ctx.similarMemories.length > 0) {
|
|
2410
|
+
const lines = ctx.similarMemories.slice(0, 3).map((m) => `- ${m.attributes.contextDescription}`);
|
|
2411
|
+
sections.push(`### Similar prior work
|
|
2412
|
+
${lines.join("\n")}`);
|
|
2413
|
+
}
|
|
2414
|
+
if (ctx.experiencePatterns.length > 0) {
|
|
2415
|
+
const lines = ctx.experiencePatterns.slice(0, 3).map(
|
|
2416
|
+
(p) => `- ${p.taskType}: ${(p.successRate * 100).toFixed(0)}% success over ${String(p.attemptCount)} attempts`
|
|
2417
|
+
);
|
|
2418
|
+
sections.push(`### Observed patterns
|
|
2419
|
+
${lines.join("\n")}`);
|
|
2420
|
+
}
|
|
2421
|
+
if (ctx.outcomes !== null && ctx.outcomes.totalTasks > 0) {
|
|
2422
|
+
sections.push(
|
|
2423
|
+
`### Outcomes for this category
|
|
2424
|
+
- ${String(ctx.outcomes.totalTasks)} prior tasks, ${(ctx.outcomes.successRate * 100).toFixed(0)}% success`
|
|
2425
|
+
);
|
|
2426
|
+
}
|
|
2427
|
+
if (ctx.researchInsights.length > 0) {
|
|
2428
|
+
const lines = ctx.researchInsights.slice(0, 5).map((r) => `- ${r.name} (${r.status}) \u2014 ${r.topic}`);
|
|
2429
|
+
sections.push(`### Prior research on this topic
|
|
2430
|
+
${lines.join("\n")}`);
|
|
2431
|
+
}
|
|
2432
|
+
return sections.length === 0 ? "" : `## Prior Context (Nexus Memory)
|
|
2433
|
+
${sections.join("\n\n")}`;
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
export {
|
|
2437
|
+
RulesSnapshotSchema,
|
|
2438
|
+
PersistentStrategyDistiller,
|
|
2439
|
+
getResearchStatus,
|
|
2440
|
+
formatStatusResult,
|
|
2441
|
+
findOverlaps,
|
|
2442
|
+
formatOverlapResult,
|
|
2443
|
+
paperExists,
|
|
2444
|
+
addResearchPaper,
|
|
2445
|
+
fetchSource,
|
|
2446
|
+
discoverGitHubRepos,
|
|
2447
|
+
discoverArxiv,
|
|
2448
|
+
discoverGoogleAI,
|
|
2449
|
+
discoverMetaFAIR,
|
|
2450
|
+
discoverMicrosoftResearch,
|
|
2451
|
+
discoverDeepMind,
|
|
2452
|
+
loadSourcesRegistry,
|
|
2453
|
+
saveSourcesRegistry,
|
|
2454
|
+
sourceExistsInRegistry,
|
|
2455
|
+
addSourceToRegistry,
|
|
2456
|
+
discoverSemanticScholar,
|
|
2457
|
+
discoverPapersWithCode,
|
|
2458
|
+
discoverOpenAlex,
|
|
2459
|
+
rankDiscoveredItems,
|
|
2460
|
+
PaperStatusSchema,
|
|
2461
|
+
computeSourceQualityScore,
|
|
2462
|
+
parseRegistry,
|
|
2463
|
+
generateStatsJson,
|
|
2464
|
+
handleStatsCommand,
|
|
2465
|
+
handleRefreshCommand,
|
|
2466
|
+
handleCheckCommand,
|
|
2467
|
+
formatResearchIssueBody,
|
|
2468
|
+
createResearchIssue,
|
|
2469
|
+
getContextForTask,
|
|
2470
|
+
selectRelevantResearch,
|
|
2471
|
+
inferTaskCategory,
|
|
2472
|
+
summarizeContextForPrompt
|
|
2473
|
+
};
|
|
2474
|
+
//# sourceMappingURL=chunk-ATI7W2FT.js.map
|