@mindrian_os/install 1.13.0-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (597) hide show
  1. package/.claude-plugin/plugin.json +21 -0
  2. package/.mcp.json +9 -0
  3. package/CHANGELOG.md +3333 -0
  4. package/LICENSE +123 -0
  5. package/README.md +673 -0
  6. package/agents/brain-query.md +80 -0
  7. package/agents/framework-runner.md +237 -0
  8. package/agents/grading.md +188 -0
  9. package/agents/investor.md +128 -0
  10. package/agents/larry-extended.md +135 -0
  11. package/agents/opportunity-scanner.md +91 -0
  12. package/agents/persona-analyst.md +132 -0
  13. package/agents/research.md +89 -0
  14. package/agents/reverse-salient-agent.md +27 -0
  15. package/bin/cli.js +142 -0
  16. package/bin/mindrian-mcp-server.cjs +182 -0
  17. package/bin/mindrian-tools.cjs +765 -0
  18. package/commands/act.md +439 -0
  19. package/commands/admin.md +404 -0
  20. package/commands/analyze-needs.md +42 -0
  21. package/commands/analyze-systems.md +39 -0
  22. package/commands/analyze-timing.md +42 -0
  23. package/commands/auto-explore.md +64 -0
  24. package/commands/beautiful-question.md +40 -0
  25. package/commands/brain-derive.md +78 -0
  26. package/commands/build-knowledge.md +42 -0
  27. package/commands/build-thesis.md +46 -0
  28. package/commands/causal.md +234 -0
  29. package/commands/challenge-assumptions.md +33 -0
  30. package/commands/compare-ventures.md +83 -0
  31. package/commands/dashboard.md +110 -0
  32. package/commands/deep-grade.md +82 -0
  33. package/commands/diagnose.md +58 -0
  34. package/commands/diagnostics.md +151 -0
  35. package/commands/doctor.md +151 -0
  36. package/commands/dominant-designs.md +40 -0
  37. package/commands/explain-decision.md +87 -0
  38. package/commands/explore-domains.md +42 -0
  39. package/commands/explore-futures.md +40 -0
  40. package/commands/explore-trends.md +42 -0
  41. package/commands/export.md +103 -0
  42. package/commands/file-meeting.md +724 -0
  43. package/commands/find-analogies.md +188 -0
  44. package/commands/find-bottlenecks.md +62 -0
  45. package/commands/find-connections.md +76 -0
  46. package/commands/funding.md +81 -0
  47. package/commands/grade.md +203 -0
  48. package/commands/graph.md +128 -0
  49. package/commands/hat-briefing.md +125 -0
  50. package/commands/heal.md +196 -0
  51. package/commands/help.md +399 -0
  52. package/commands/hmi-status.md +172 -0
  53. package/commands/jtbd.md +241 -0
  54. package/commands/leadership.md +73 -0
  55. package/commands/lean-canvas.md +40 -0
  56. package/commands/macro-trends.md +40 -0
  57. package/commands/map-unknowns.md +40 -0
  58. package/commands/memory.md +173 -0
  59. package/commands/models.md +175 -0
  60. package/commands/mos-reason.md +285 -0
  61. package/commands/mullins.md +120 -0
  62. package/commands/new-project.md +481 -0
  63. package/commands/onboard.md +434 -0
  64. package/commands/operator.md +149 -0
  65. package/commands/opportunities.md +144 -0
  66. package/commands/organize.md +497 -0
  67. package/commands/persona.md +198 -0
  68. package/commands/pipeline.md +112 -0
  69. package/commands/present.md +91 -0
  70. package/commands/publish.md +201 -0
  71. package/commands/query.md +124 -0
  72. package/commands/radar.md +72 -0
  73. package/commands/reanalyze.md +91 -0
  74. package/commands/research.md +196 -0
  75. package/commands/room.md +352 -0
  76. package/commands/rooms.md +598 -0
  77. package/commands/root-cause.md +40 -0
  78. package/commands/rs-experts.md +85 -0
  79. package/commands/rs-explain.md +100 -0
  80. package/commands/rs-fetch.md +94 -0
  81. package/commands/rs-thesis.md +85 -0
  82. package/commands/scenario-plan.md +40 -0
  83. package/commands/scheduled-tasks.md +285 -0
  84. package/commands/score-innovation.md +43 -0
  85. package/commands/scout.md +239 -0
  86. package/commands/setup.md +618 -0
  87. package/commands/snapshot.md +147 -0
  88. package/commands/speakers.md +84 -0
  89. package/commands/splash.md +28 -0
  90. package/commands/status.md +75 -0
  91. package/commands/structure-argument.md +42 -0
  92. package/commands/suggest-next.md +80 -0
  93. package/commands/systems-thinking.md +40 -0
  94. package/commands/think-hats.md +42 -0
  95. package/commands/update.md +181 -0
  96. package/commands/user-needs.md +40 -0
  97. package/commands/validate.md +40 -0
  98. package/commands/value-proposition.md +61 -0
  99. package/commands/vault.md +180 -0
  100. package/commands/visualize.md +52 -0
  101. package/commands/whitespace.md +507 -0
  102. package/commands/wiki.md +69 -0
  103. package/hooks/hooks.json +381 -0
  104. package/hooks/run-hook.cmd +64 -0
  105. package/lib/__init__.py +0 -0
  106. package/lib/__pycache__/__init__.cpython-312.pyc +0 -0
  107. package/lib/agents/auto-explore-agent.cjs +1043 -0
  108. package/lib/agents/reverse-salient-agent.cjs +679 -0
  109. package/lib/agents/tension-hook-agent.cjs +544 -0
  110. package/lib/brain/ROOM.md +44 -0
  111. package/lib/brain/chain-recommender.cjs +301 -0
  112. package/lib/chat/chat-context.js +185 -0
  113. package/lib/chat/chat-panel.js +721 -0
  114. package/lib/chat/fabric-chat.cjs +288 -0
  115. package/lib/chat/generative-tools.js +219 -0
  116. package/lib/conversation/ROOM.md +39 -0
  117. package/lib/conversation/classifier-rules.json +38 -0
  118. package/lib/conversation/classifier.cjs +264 -0
  119. package/lib/conversation/operator.cjs +287 -0
  120. package/lib/copy/115-spec-strings.cjs +55 -0
  121. package/lib/core/__init__.py +0 -0
  122. package/lib/core/__nav-stub.cjs +14 -0
  123. package/lib/core/__pycache__/__init__.cpython-312.pyc +0 -0
  124. package/lib/core/__pycache__/rs-math.cpython-312.pyc +0 -0
  125. package/lib/core/__pycache__/rs_cache.cpython-312.pyc +0 -0
  126. package/lib/core/__pycache__/rs_corpus.cpython-312.pyc +0 -0
  127. package/lib/core/__pycache__/rs_hybrid.cpython-312.pyc +0 -0
  128. package/lib/core/__pycache__/rs_math.cpython-312.pyc +0 -0
  129. package/lib/core/__pycache__/rs_rooms.cpython-312.pyc +0 -0
  130. package/lib/core/artifact-id.cjs +148 -0
  131. package/lib/core/asset-ops.cjs +151 -0
  132. package/lib/core/auto-commit-throttle.cjs +129 -0
  133. package/lib/core/bearer-token.cjs +199 -0
  134. package/lib/core/brain-client.cjs +865 -0
  135. package/lib/core/brain-derivation-prompts.cjs +326 -0
  136. package/lib/core/brain-derivation-queue.cjs +431 -0
  137. package/lib/core/brain-derivation.cjs +580 -0
  138. package/lib/core/brain-md-schema.cjs +528 -0
  139. package/lib/core/brain-md-staleness.cjs +357 -0
  140. package/lib/core/brain-response-sanitize.cjs +188 -0
  141. package/lib/core/bridge-writer.cjs +477 -0
  142. package/lib/core/chat-context-builder.cjs +253 -0
  143. package/lib/core/cross-room-aggregator.cjs +762 -0
  144. package/lib/core/daily-briefing.cjs +438 -0
  145. package/lib/core/decision-capture.cjs +618 -0
  146. package/lib/core/deep-links.cjs +82 -0
  147. package/lib/core/dispatch-optimizer.cjs +354 -0
  148. package/lib/core/dual-path-detector.cjs +84 -0
  149. package/lib/core/dual-path-detector.test.cjs +334 -0
  150. package/lib/core/exports-log.cjs +79 -0
  151. package/lib/core/feynman-minto-invariants.cjs +605 -0
  152. package/lib/core/folder-memory-async.cjs +338 -0
  153. package/lib/core/folder-memory-shared.cjs +890 -0
  154. package/lib/core/folder-memory.cjs +416 -0
  155. package/lib/core/framework-chain-composer.cjs +411 -0
  156. package/lib/core/frontmatter-schemas.cjs +330 -0
  157. package/lib/core/git-ops.cjs +141 -0
  158. package/lib/core/graph-ops.cjs +258 -0
  159. package/lib/core/hat-persistence.cjs +362 -0
  160. package/lib/core/index.cjs +60 -0
  161. package/lib/core/integration-registry.cjs +232 -0
  162. package/lib/core/intelligence-cascade.cjs +661 -0
  163. package/lib/core/lazygraph-ops.cjs +1057 -0
  164. package/lib/core/lru-cache.cjs +139 -0
  165. package/lib/core/mcp-profiles.cjs +182 -0
  166. package/lib/core/meeting-ops.cjs +54 -0
  167. package/lib/core/memory-ops.cjs +600 -0
  168. package/lib/core/migrations/ROOM.md +33 -0
  169. package/lib/core/migrations/phase-109-nodes-provenance.cjs +339 -0
  170. package/lib/core/migrations/phase-109-session-focus.cjs +99 -0
  171. package/lib/core/model-profiles.cjs +246 -0
  172. package/lib/core/mullins-scaffold.cjs +160 -0
  173. package/lib/core/nav-dial.cjs +316 -0
  174. package/lib/core/navigation/ROOM.md +15 -0
  175. package/lib/core/navigation/explanation.cjs +43 -0
  176. package/lib/core/navigation/focus.cjs +135 -0
  177. package/lib/core/navigation/ingestion.cjs +82 -0
  178. package/lib/core/navigation/insights.cjs +350 -0
  179. package/lib/core/navigation/memory-events.cjs +118 -0
  180. package/lib/core/navigation/neighborhood.cjs +78 -0
  181. package/lib/core/navigation/packet.cjs +182 -0
  182. package/lib/core/navigation/room-home.cjs +127 -0
  183. package/lib/core/navigation/transitions.cjs +82 -0
  184. package/lib/core/navigation-engine-shared.cjs +242 -0
  185. package/lib/core/navigation-engine.cjs +664 -0
  186. package/lib/core/navigation.cjs +60 -0
  187. package/lib/core/nl-graph-queries.cjs +164 -0
  188. package/lib/core/offer-presenter.cjs +406 -0
  189. package/lib/core/opportunity-extractor.cjs +183 -0
  190. package/lib/core/opportunity-ops.cjs +1371 -0
  191. package/lib/core/persona-ops.cjs +537 -0
  192. package/lib/core/persona-taxonomy.cjs +190 -0
  193. package/lib/core/platform-gates.cjs +120 -0
  194. package/lib/core/platform.cjs +257 -0
  195. package/lib/core/proactive-intelligence.cjs +528 -0
  196. package/lib/core/problem-type-router.cjs +315 -0
  197. package/lib/core/reasoning-ops.cjs +639 -0
  198. package/lib/core/reverse-salient-persona-suffix.cjs +115 -0
  199. package/lib/core/room-classifier-strict-mode.cjs +229 -0
  200. package/lib/core/room-db.cjs +127 -0
  201. package/lib/core/room-ops-async.cjs +92 -0
  202. package/lib/core/room-ops-shared.cjs +64 -0
  203. package/lib/core/room-ops-sync.cjs +70 -0
  204. package/lib/core/room-ops.cjs +32 -0
  205. package/lib/core/room-type-detector.cjs +386 -0
  206. package/lib/core/rs-brain-substrate-prompts.cjs +129 -0
  207. package/lib/core/rs-brain-substrate.cjs +570 -0
  208. package/lib/core/rs-breakthrough-scorer.cjs +255 -0
  209. package/lib/core/rs-canon-violations.cjs +82 -0
  210. package/lib/core/rs-chain-feeder.cjs +343 -0
  211. package/lib/core/rs-commercial-assessor.cjs +280 -0
  212. package/lib/core/rs-differential-scorer.cjs +376 -0
  213. package/lib/core/rs-domain-analyzer.cjs +385 -0
  214. package/lib/core/rs-egress-prompts.cjs +113 -0
  215. package/lib/core/rs-egress-telemetry.cjs +225 -0
  216. package/lib/core/rs-egress-violations.cjs +53 -0
  217. package/lib/core/rs-expert-mapper.cjs +467 -0
  218. package/lib/core/rs-fetcher-academic.cjs +697 -0
  219. package/lib/core/rs-fetcher-experts.cjs +314 -0
  220. package/lib/core/rs-fetcher-industry.cjs +731 -0
  221. package/lib/core/rs-fetcher-patents.cjs +564 -0
  222. package/lib/core/rs-innovation-classifier.cjs +194 -0
  223. package/lib/core/rs-mind-map.cjs +656 -0
  224. package/lib/core/rs-neo4j-writer.cjs +388 -0
  225. package/lib/core/rs-nl-to-query.cjs +425 -0
  226. package/lib/core/rs-pinecone-bridge.cjs +303 -0
  227. package/lib/core/rs-preprocessor.cjs +350 -0
  228. package/lib/core/rs-query-matrix.cjs +316 -0
  229. package/lib/core/rs-query-to-text.cjs +438 -0
  230. package/lib/core/rs-sqlite-mirror.cjs +443 -0
  231. package/lib/core/rs-thesis-generator.cjs +188 -0
  232. package/lib/core/rs_cache.py +479 -0
  233. package/lib/core/rs_corpus.py +468 -0
  234. package/lib/core/rs_hybrid.py +586 -0
  235. package/lib/core/rs_math.py +287 -0
  236. package/lib/core/rs_rooms.py +193 -0
  237. package/lib/core/scheduled-scanner.cjs +463 -0
  238. package/lib/core/scratchpad-ops.cjs +201 -0
  239. package/lib/core/section-8-trace-schema.cjs +138 -0
  240. package/lib/core/section-registry.cjs +111 -0
  241. package/lib/core/session-state.cjs +144 -0
  242. package/lib/core/shallow-doc-parser.cjs +174 -0
  243. package/lib/core/shallow-doc-parser.test.cjs +226 -0
  244. package/lib/core/skill-activation-router.cjs +284 -0
  245. package/lib/core/state-ops.cjs +46 -0
  246. package/lib/core/statusline-cache.cjs +266 -0
  247. package/lib/core/token-estimator.cjs +348 -0
  248. package/lib/core/user-archetype.cjs +239 -0
  249. package/lib/core/user-md-ops.cjs +524 -0
  250. package/lib/core/visual-ops.cjs +624 -0
  251. package/lib/core/write-lock.cjs +149 -0
  252. package/lib/graph/canvas-graph.js +467 -0
  253. package/lib/graph/constellation-config.cjs +299 -0
  254. package/lib/graph/graph-detail-panel.js +165 -0
  255. package/lib/hmi/ROOM.md +47 -0
  256. package/lib/hmi/across-session-memory.cjs +604 -0
  257. package/lib/hmi/cross-room-memory.cjs +575 -0
  258. package/lib/hmi/decoy-tier.cjs +395 -0
  259. package/lib/hmi/jtbd-classifier.cjs +219 -0
  260. package/lib/hmi/jtbd-state.cjs +199 -0
  261. package/lib/hmi/jtbd-taxonomy.json +392 -0
  262. package/lib/hmi/selector-dispatcher.cjs +546 -0
  263. package/lib/hmi/selector-telemetry.cjs +263 -0
  264. package/lib/hmi/shape-f0-renderer.cjs +139 -0
  265. package/lib/hmi/shape-f1-fallback.cjs +80 -0
  266. package/lib/hmi/shape-f1-renderer.cjs +138 -0
  267. package/lib/hmi/shape-f2-renderer.cjs +132 -0
  268. package/lib/hmi/shape-f3-renderer.cjs +66 -0
  269. package/lib/hmi/shape-f4-renderer.cjs +72 -0
  270. package/lib/hmi/shape-f5-renderer.cjs +155 -0
  271. package/lib/hmi/shape-f6-plan-review-renderer.cjs +312 -0
  272. package/lib/hmi/shape-f6-renderer.cjs +144 -0
  273. package/lib/hmi/shape-g-renderer.cjs +219 -0
  274. package/lib/hmi/shape-h-renderer.cjs +222 -0
  275. package/lib/hmi/tier-check.cjs +63 -0
  276. package/lib/import/PRECONDITIONS.md +41 -0
  277. package/lib/import/branding.cjs +210 -0
  278. package/lib/import/branding.test.cjs +235 -0
  279. package/lib/import/classifications-sync.cjs +104 -0
  280. package/lib/import/classifications-sync.test.cjs +129 -0
  281. package/lib/import/enricher.cjs +296 -0
  282. package/lib/import/enricher.test.cjs +273 -0
  283. package/lib/import/integration.test.cjs +376 -0
  284. package/lib/import/manifest.cjs +129 -0
  285. package/lib/import/manifest.schema.json +185 -0
  286. package/lib/import/manifest.test.cjs +123 -0
  287. package/lib/import/meeting-detector.cjs +92 -0
  288. package/lib/import/meeting-detector.test.cjs +100 -0
  289. package/lib/import/person-detector.cjs +229 -0
  290. package/lib/import/person-detector.test.cjs +149 -0
  291. package/lib/import/report.cjs +186 -0
  292. package/lib/import/report.test.cjs +186 -0
  293. package/lib/import/room-md-scaffolder.cjs +49 -0
  294. package/lib/import/router.cjs +224 -0
  295. package/lib/import/router.test.cjs +356 -0
  296. package/lib/import/run-all-tests.cjs +36 -0
  297. package/lib/import/smoke-test.cjs +213 -0
  298. package/lib/import/smoke-test.test.cjs +148 -0
  299. package/lib/import/test-fixtures/collision-vault/preexisting-room/STATE.md +8 -0
  300. package/lib/import/test-fixtures/collision-vault/preexisting-room/problem-definition/onboarding/onboarding.md +7 -0
  301. package/lib/import/test-fixtures/collision-vault/source/onboarding.md +5 -0
  302. package/lib/import/test-fixtures/obsidian-vault/.obsidian/workspace.json +1 -0
  303. package/lib/import/test-fixtures/obsidian-vault/notes/with-wikilinks.md +4 -0
  304. package/lib/import/test-fixtures/tiny-vault/notes/2026-01-15-team-sync.md +9 -0
  305. package/lib/import/test-fixtures/tiny-vault/notes/empty.md +3 -0
  306. package/lib/import/test-fixtures/tiny-vault/notes/onboarding.md +5 -0
  307. package/lib/import/test-fixtures/tiny-vault/notes/pricing.md +5 -0
  308. package/lib/import/test-fixtures/tiny-vault/notes/random.md +4 -0
  309. package/lib/import/undo.test.cjs +199 -0
  310. package/lib/import/vault-scanner.cjs +105 -0
  311. package/lib/import/vault-scanner.test.cjs +67 -0
  312. package/lib/mcp/app-html/dashboard.html +316 -0
  313. package/lib/mcp/app-html/graph.html +428 -0
  314. package/lib/mcp/app-html/mindrian-platform.html +1841 -0
  315. package/lib/mcp/app-html/wiki.html +383 -0
  316. package/lib/mcp/app-views.cjs +322 -0
  317. package/lib/mcp/brain-router.cjs +418 -0
  318. package/lib/mcp/capability-registry.cjs +62 -0
  319. package/lib/mcp/larry-context.cjs +46 -0
  320. package/lib/mcp/larry-server-instructions.md +114 -0
  321. package/lib/mcp/pipeline-state.cjs +275 -0
  322. package/lib/mcp/prompts.cjs +302 -0
  323. package/lib/mcp/resources.cjs +227 -0
  324. package/lib/mcp/session-catchup.cjs +327 -0
  325. package/lib/mcp/surface-detect.cjs +75 -0
  326. package/lib/mcp/tool-router.cjs +1034 -0
  327. package/lib/memory/aaak-compress.cjs +403 -0
  328. package/lib/memory/aaak-compress.test.cjs +288 -0
  329. package/lib/memory/async-artifact-auto-commit.test.cjs +223 -0
  330. package/lib/memory/bearer-token.test.cjs +315 -0
  331. package/lib/memory/brain-cache-lru.test.cjs +259 -0
  332. package/lib/memory/brain-client-query-shape.test.cjs +160 -0
  333. package/lib/memory/brain-derivation-graceful-degradation.test.cjs +1019 -0
  334. package/lib/memory/brain-derivation-queue.test.cjs +539 -0
  335. package/lib/memory/brain-derivation.test.cjs +634 -0
  336. package/lib/memory/brain-derive-command.test.cjs +534 -0
  337. package/lib/memory/brain-md-invariants-validator.test.cjs +704 -0
  338. package/lib/memory/brain-md-schema.test.cjs +467 -0
  339. package/lib/memory/brain-md-staleness.test.cjs +525 -0
  340. package/lib/memory/brain-server-resolution.test.cjs +314 -0
  341. package/lib/memory/chain-recommender.test.cjs +233 -0
  342. package/lib/memory/chat-context.test.cjs +128 -0
  343. package/lib/memory/command-registry.test.cjs +220 -0
  344. package/lib/memory/cross-room-aggregator.test.cjs +909 -0
  345. package/lib/memory/dashboard-server.test.cjs +256 -0
  346. package/lib/memory/debouncer-drain-at-prompt.test.cjs +389 -0
  347. package/lib/memory/decision-capture.test.cjs +632 -0
  348. package/lib/memory/decision-capture.worker.cjs +70 -0
  349. package/lib/memory/explain-decision-command.test.cjs +521 -0
  350. package/lib/memory/explain-decision-footer.test.cjs +316 -0
  351. package/lib/memory/explored-materials-store.cjs +392 -0
  352. package/lib/memory/feynman-minto-guardian.test.cjs +736 -0
  353. package/lib/memory/feynman-minto-invariants.test.cjs +511 -0
  354. package/lib/memory/feynman-prompts-drift.test.cjs +144 -0
  355. package/lib/memory/feynman-prompts.cjs +151 -0
  356. package/lib/memory/feynman-prompts.test.cjs +96 -0
  357. package/lib/memory/folder-memory-quadruple.test.cjs +548 -0
  358. package/lib/memory/folder-memory.test.cjs +503 -0
  359. package/lib/memory/framework-chain-composer.test.cjs +515 -0
  360. package/lib/memory/frontmatter-schema-validator.test.cjs +290 -0
  361. package/lib/memory/heal-command.test.cjs +604 -0
  362. package/lib/memory/index-artifact-transaction.test.cjs +333 -0
  363. package/lib/memory/lazygraph-rs-discoveries-view.test.cjs +122 -0
  364. package/lib/memory/mcp-input-validation.test.cjs +240 -0
  365. package/lib/memory/mcp-server-brain-deps.test.cjs +270 -0
  366. package/lib/memory/mcp-stack-fallback.test.cjs +433 -0
  367. package/lib/memory/minto-debouncer.test.cjs +407 -0
  368. package/lib/memory/minto-debouncer.worker.cjs +46 -0
  369. package/lib/memory/minto-migration-v88.test.cjs +265 -0
  370. package/lib/memory/minto-schema-v88.test.cjs +390 -0
  371. package/lib/memory/mos-status-renderer.test.cjs +631 -0
  372. package/lib/memory/narrative-schema.cjs +376 -0
  373. package/lib/memory/narrative-schema.test.cjs +209 -0
  374. package/lib/memory/nav-dial.test.cjs +414 -0
  375. package/lib/memory/navigation-engine-core.test.cjs +722 -0
  376. package/lib/memory/navigation-invariants.test.cjs +483 -0
  377. package/lib/memory/offer-presenter.test.cjs +554 -0
  378. package/lib/memory/on-stop-snapshot.test.cjs +404 -0
  379. package/lib/memory/pending-tension-store.cjs +373 -0
  380. package/lib/memory/post-compact-reinjection.test.cjs +854 -0
  381. package/lib/memory/post-write-triple.test.cjs +317 -0
  382. package/lib/memory/pre-compact-snapshot.test.cjs +495 -0
  383. package/lib/memory/problem-type-router.test.cjs +656 -0
  384. package/lib/memory/query-efficiency-telemetry.test.cjs +370 -0
  385. package/lib/memory/recompile-room-references.test.cjs +392 -0
  386. package/lib/memory/recompile-room-references.worker.cjs +42 -0
  387. package/lib/memory/record-decision-dual-write.test.cjs +454 -0
  388. package/lib/memory/room-classifier-strict-mode.test.cjs +417 -0
  389. package/lib/memory/room-minto-hook.test.cjs +398 -0
  390. package/lib/memory/rs-discovery-engine.test.cjs +323 -0
  391. package/lib/memory/run-feynman-tests.cjs +1247 -0
  392. package/lib/memory/security-trifecta.test.cjs +312 -0
  393. package/lib/memory/session-start-brain-staleness.test.cjs +363 -0
  394. package/lib/memory/session-start-triple-injection.test.cjs +514 -0
  395. package/lib/memory/sessionstart-banner-formatter.cjs +318 -0
  396. package/lib/memory/sessionstart-minto-banner.test.cjs +373 -0
  397. package/lib/memory/skill-activation-router.test.cjs +419 -0
  398. package/lib/memory/stamp-artifact-write.test.cjs +304 -0
  399. package/lib/memory/statusline-active-room.test.cjs +315 -0
  400. package/lib/memory/statusline-minto-segment.test.cjs +292 -0
  401. package/lib/memory/sync-async-entry-points.test.cjs +204 -0
  402. package/lib/memory/test-bridge-writer-enhanced.cjs +452 -0
  403. package/lib/memory/test-rs-brain-substrate-shape.cjs +529 -0
  404. package/lib/memory/test-rs-brain-substrate.cjs +636 -0
  405. package/lib/memory/test-rs-breakthrough-scorer.cjs +375 -0
  406. package/lib/memory/test-rs-canon-violations.cjs +218 -0
  407. package/lib/memory/test-rs-chain-feeder-core.cjs +344 -0
  408. package/lib/memory/test-rs-chain-feeder-skill-spawn.cjs +297 -0
  409. package/lib/memory/test-rs-commercial-assessor.cjs +385 -0
  410. package/lib/memory/test-rs-differential-scorer.cjs +480 -0
  411. package/lib/memory/test-rs-discovery-engine.cjs +603 -0
  412. package/lib/memory/test-rs-domain-analyzer.cjs +492 -0
  413. package/lib/memory/test-rs-egress-primitives.cjs +420 -0
  414. package/lib/memory/test-rs-expert-mapper.cjs +547 -0
  415. package/lib/memory/test-rs-explain-command.cjs +443 -0
  416. package/lib/memory/test-rs-fetcher-academic.cjs +848 -0
  417. package/lib/memory/test-rs-fetcher-experts.cjs +496 -0
  418. package/lib/memory/test-rs-fetcher-industry.cjs +702 -0
  419. package/lib/memory/test-rs-fetcher-patents.cjs +674 -0
  420. package/lib/memory/test-rs-innovation-classifier.cjs +301 -0
  421. package/lib/memory/test-rs-mind-map.cjs +646 -0
  422. package/lib/memory/test-rs-neo4j-writer.cjs +518 -0
  423. package/lib/memory/test-rs-nl-to-query.cjs +449 -0
  424. package/lib/memory/test-rs-pinecone-bridge.cjs +277 -0
  425. package/lib/memory/test-rs-preprocessor.cjs +433 -0
  426. package/lib/memory/test-rs-query-matrix.cjs +391 -0
  427. package/lib/memory/test-rs-query-to-text.cjs +551 -0
  428. package/lib/memory/test-rs-sqlite-mirror.cjs +649 -0
  429. package/lib/memory/test-rs-thesis-generator.cjs +360 -0
  430. package/lib/memory/triple-context-formatter.cjs +473 -0
  431. package/lib/memory/triple-context-formatter.test.cjs +442 -0
  432. package/lib/memory/user-md-persona.test.cjs +565 -0
  433. package/lib/memory/userpromptsubmit-integration.test.cjs +690 -0
  434. package/lib/memory/validators/README.md +157 -0
  435. package/lib/memory/validators/brain-md-invariants.cjs +475 -0
  436. package/lib/memory/validators/brain-substrate-invariants.cjs +285 -0
  437. package/lib/memory/validators/external-academic-invariants.cjs +249 -0
  438. package/lib/memory/validators/external-industry-invariants.cjs +271 -0
  439. package/lib/memory/validators/external-patents-invariants.cjs +266 -0
  440. package/lib/memory/validators/minto-invariants.cjs +62 -0
  441. package/lib/memory/validators/navigation-invariants.cjs +340 -0
  442. package/lib/memory/validators/queue-health.cjs +95 -0
  443. package/lib/memory/validators/snapshot-integrity.cjs +129 -0
  444. package/lib/memory/validators/stale-lifecycle.cjs +116 -0
  445. package/lib/memory/vault-section-minto-generator-atomic.test.cjs +556 -0
  446. package/lib/memory/vault-section-minto-generator-atomic.worker.cjs +73 -0
  447. package/lib/memory/write-lock-atomic.test.cjs +137 -0
  448. package/lib/memory/write-lock-atomic.worker.cjs +55 -0
  449. package/lib/parity/check-parity.cjs +83 -0
  450. package/lib/presentation/presentation-server.cjs +101 -0
  451. package/lib/presentation/presentation-watcher.cjs +123 -0
  452. package/lib/quickview/hub-server.cjs +719 -0
  453. package/lib/quickview/server.cjs +533 -0
  454. package/lib/render/JTBD-PALETTES.md +145 -0
  455. package/lib/render/ROOM.md +59 -0
  456. package/lib/render/render-v2.cjs +486 -0
  457. package/lib/render/render-v2.test.cjs +267 -0
  458. package/lib/render/render.cjs +65 -0
  459. package/lib/state/ROOM.md +46 -0
  460. package/lib/state/state-md-parser.cjs +215 -0
  461. package/lib/statusline/ROOM.md +38 -0
  462. package/lib/statusline/banner-suppression.cjs +50 -0
  463. package/lib/statusline/surface-detect.cjs +85 -0
  464. package/lib/update-bootstrap.sh.template +145 -0
  465. package/lib/vault/frontmatter-schema.cjs +297 -0
  466. package/lib/vault/room-scanner.cjs +352 -0
  467. package/lib/vault/wikilink-builder.cjs +231 -0
  468. package/lib/vault/wikilink-builder.test.cjs +182 -0
  469. package/lib/wiki/graph-links.cjs +281 -0
  470. package/lib/wiki/page-renderer.cjs +229 -0
  471. package/lib/wiki/wiki-chat.cjs +81 -0
  472. package/lib/wiki/wiki-layout.cjs +1459 -0
  473. package/lib/wiki/wiki-search.cjs +142 -0
  474. package/lib/wiki/wiki-server.cjs +678 -0
  475. package/lib/wiki/wiki-watcher.cjs +105 -0
  476. package/lib/workflow/ROOM.md +47 -0
  477. package/lib/workflow/command-resolver.cjs +155 -0
  478. package/lib/workflow/command-resolver.test.cjs +235 -0
  479. package/package.json +44 -0
  480. package/pipelines/analogy/01-decompose.md +80 -0
  481. package/pipelines/analogy/02-abstract.md +87 -0
  482. package/pipelines/analogy/03-search.md +135 -0
  483. package/pipelines/analogy/04-transfer.md +101 -0
  484. package/pipelines/analogy/05-validate.md +106 -0
  485. package/pipelines/analogy/CHAIN.md +56 -0
  486. package/pipelines/discovery/01-explore-domains.md +44 -0
  487. package/pipelines/discovery/02-think-hats.md +50 -0
  488. package/pipelines/discovery/03-analyze-needs.md +54 -0
  489. package/pipelines/discovery/CHAIN.md +37 -0
  490. package/pipelines/thesis/01-structure-argument.md +45 -0
  491. package/pipelines/thesis/02-challenge-assumptions.md +48 -0
  492. package/pipelines/thesis/03-build-thesis.md +54 -0
  493. package/pipelines/thesis/CHAIN.md +37 -0
  494. package/references/brain/causal-directives.md +91 -0
  495. package/references/brain/causal-enrichment.cypher +165 -0
  496. package/references/brain/command-triggers-schema.md +226 -0
  497. package/references/brain/graph-architecture.md +317 -0
  498. package/references/brain/query-patterns.md +460 -0
  499. package/references/brain/room-hierarchy-schema.md +218 -0
  500. package/references/brain/schema.md +76 -0
  501. package/references/capability-radar/capabilities-index.md +241 -0
  502. package/references/capability-radar/changelog-cache.md +81 -0
  503. package/references/causal/causal-schema.md +103 -0
  504. package/references/design/email-template-standard.md +155 -0
  505. package/references/design/graph-visualization-standard.md +178 -0
  506. package/references/document-generation.md +179 -0
  507. package/references/hsi/HSI-TOOLS-REFERENCE.md +222 -0
  508. package/references/import-config.md +141 -0
  509. package/references/integrations/detection-patterns.md +101 -0
  510. package/references/meeting/artifact-template.md +377 -0
  511. package/references/meeting/cross-meeting-intelligence.md +216 -0
  512. package/references/meeting/cross-relationship-patterns.md +202 -0
  513. package/references/meeting/live-join-interface.md +244 -0
  514. package/references/meeting/section-mapping.md +192 -0
  515. package/references/meeting/segment-classification.md +258 -0
  516. package/references/meeting/speaker-profile-template.md +219 -0
  517. package/references/meeting/summary-template.md +348 -0
  518. package/references/meeting/transcript-patterns.md +226 -0
  519. package/references/methodology/analyze-needs.md +135 -0
  520. package/references/methodology/analyze-systems.md +121 -0
  521. package/references/methodology/analyze-timing.md +149 -0
  522. package/references/methodology/beautiful-question.md +109 -0
  523. package/references/methodology/build-knowledge.md +161 -0
  524. package/references/methodology/build-thesis.md +237 -0
  525. package/references/methodology/challenge-assumptions.md +127 -0
  526. package/references/methodology/diagnose.md +169 -0
  527. package/references/methodology/dominant-designs.md +212 -0
  528. package/references/methodology/explore-domains.md +147 -0
  529. package/references/methodology/explore-futures.md +163 -0
  530. package/references/methodology/explore-trends.md +129 -0
  531. package/references/methodology/find-bottlenecks.md +131 -0
  532. package/references/methodology/grade.md +211 -0
  533. package/references/methodology/index.md +97 -0
  534. package/references/methodology/leadership.md +200 -0
  535. package/references/methodology/lean-canvas.md +116 -0
  536. package/references/methodology/macro-trends.md +192 -0
  537. package/references/methodology/map-unknowns.md +137 -0
  538. package/references/methodology/mullins-7-domains.md +104 -0
  539. package/references/methodology/problem-types.md +65 -0
  540. package/references/methodology/root-cause.md +178 -0
  541. package/references/methodology/sapphire-encoding.md +355 -0
  542. package/references/methodology/scenario-plan.md +178 -0
  543. package/references/methodology/score-innovation.md +154 -0
  544. package/references/methodology/structure-argument.md +158 -0
  545. package/references/methodology/systems-thinking.md +159 -0
  546. package/references/methodology/think-hats.md +147 -0
  547. package/references/methodology/triz-matrix.json +751 -0
  548. package/references/methodology/triz-principles.md +501 -0
  549. package/references/methodology/user-needs.md +199 -0
  550. package/references/methodology/validate.md +163 -0
  551. package/references/methodology/value-proposition.md +244 -0
  552. package/references/opportunities/funding-lifecycle.md +103 -0
  553. package/references/opportunities/grant-api-patterns.md +99 -0
  554. package/references/opportunities/opportunity-template.md +84 -0
  555. package/references/personality/assessment-philosophy.md +72 -0
  556. package/references/personality/lexicon.md +100 -0
  557. package/references/personality/persona-chains.md +56 -0
  558. package/references/personality/pws-lexicon-full.md +499 -0
  559. package/references/personality/voice-dna.md +156 -0
  560. package/references/personas/hat-perspectives.md +76 -0
  561. package/references/personas/persona-template.md +63 -0
  562. package/references/pipeline/act-output-contract.md +88 -0
  563. package/references/pipeline/chains-index.md +39 -0
  564. package/references/pws-profile-generation.md +79 -0
  565. package/references/reasoning/reasoning-schema.md +143 -0
  566. package/references/reasoning/reasoning-template.md +68 -0
  567. package/references/reasoning/run-template.md +38 -0
  568. package/references/research/RESEARCH_14_CLAUDE_CODE_SOURCE_ARCHITECTURE.md +209 -0
  569. package/references/research/RESEARCH_15_V1.8_OPTIMIZATION_JTBD.md +375 -0
  570. package/references/research/RESEARCH_16_NATIVE_FIRST_PLUGIN_ARCHITECTURE.md +575 -0
  571. package/references/research/RESEARCH_17_MCP_UI_FRAMEWORKS.md +272 -0
  572. package/references/taxonomy/TAXONOMY.md +192 -0
  573. package/references/templates/MINTO.md +36 -0
  574. package/references/user-research/2026-04-05-leah-lawrence-session.md +202 -0
  575. package/references/vault-kit/README.md +35 -0
  576. package/references/vault-kit/app.json +12 -0
  577. package/references/vault-kit/appearance.json +12 -0
  578. package/references/vault-kit/graph.json +35 -0
  579. package/references/vault-kit/snippets/mindrian-destijl.css +297 -0
  580. package/references/vault-kit/templates/new-artifact.md +37 -0
  581. package/references/vault-kit/templates/new-meeting-note.md +35 -0
  582. package/references/vault-kit/templates/new-team-profile.md +29 -0
  583. package/references/vault-kit/templates/new-xref.md +35 -0
  584. package/references/visual/symbol-system.md +151 -0
  585. package/skills/MOSDeckEngine/SKILL.md +325 -0
  586. package/skills/brain-connector/SKILL.md +114 -0
  587. package/skills/context-engine/SKILL.md +147 -0
  588. package/skills/conversation-mode/SKILL.md +102 -0
  589. package/skills/larry-personality/SKILL.md +219 -0
  590. package/skills/larry-personality/framework-chains.md +92 -0
  591. package/skills/larry-personality/mode-engine.md +185 -0
  592. package/skills/mullins-scaffold/SKILL.md +61 -0
  593. package/skills/mullins-scaffold/scaffold.json +146 -0
  594. package/skills/pws-methodology/SKILL.md +49 -0
  595. package/skills/room-passive/SKILL.md +165 -0
  596. package/skills/room-proactive/SKILL.md +250 -0
  597. package/skills/ui-system/SKILL.md +277 -0
@@ -0,0 +1,697 @@
1
+ /*
2
+ * Copyright (c) 2026 Mindrian. BSL 1.1.
3
+ * Phase 89.2 Plan 02 -- Academic external-egress fetcher.
4
+ * Phase 94 Plan 05 amendment (2026-04-28): envelope wrap only.
5
+ * Free-tier paths (openalex / arxiv / pubmed) preserved byte-identical;
6
+ * the wrap layer adds {tier, source, results} on top of the existing
7
+ * {papers, telemetry} return. tier='paid' annotates that a real keyed
8
+ * or keyless API tier produced data; backward-compat papers[] key is
9
+ * preserved for downstream consumers (rs-expert-mapper, rs-discovery-
10
+ * engine). Per Canon Part 4 the tier annotation feeds the section-8
11
+ * trace web_research_tier field; per Canon Part 7 envelope wrap reuses
12
+ * the existing Phase 89.2 fetcher logic without rewriting it.
13
+ *
14
+ * Six sources covered with one Canon Part 8 chokepoint:
15
+ * openalex no API key polite pool via OPENALEX_EMAIL env
16
+ * arxiv no API key Atom XML; ~3 req/s soft limit
17
+ * pubmed no API key eutils JSON; 3 req/s
18
+ * scopus SCOPUS_API_KEY Elsevier search API
19
+ * ieee IEEE_API_KEY IEEEXplore search API
20
+ * nature NATURE_API_KEY Springer Nature meta API
21
+ *
22
+ * Single chokepoint: buildAcademicQuery(query, source, opts) is the ONLY
23
+ * function that constructs an outbound URL. Every call invokes
24
+ * auditQueryString(query, 'academic') from rs-egress-prompts.cjs BEFORE
25
+ * the URL is returned. ExternalEgressViolation throws pre-egress on any
26
+ * FORBIDDEN_PATTERNS hit, so adversarial input never reaches the wire.
27
+ *
28
+ * Per-source rate-limit graceful degradation per Phase 88.6-03 pattern:
29
+ * 429 / 503 -> recordTelemetry(status='rate_limited') + return empty
30
+ * for that source + continue with remaining sources
31
+ * timeout -> recordTelemetry(status='timeout') + continue
32
+ * parse err -> recordTelemetry(status='api_error') + continue
33
+ * no key -> recordTelemetry(status='api_key_missing') + skip source
34
+ * budget==0 -> skip source (no telemetry record; budget itself is the trace)
35
+ *
36
+ * NEVER throws on rate-limit. ONLY throws on Canon Part 8 violation.
37
+ *
38
+ * Network: native Node 18+ global fetch. AbortController gives a per-request
39
+ * 10s default timeout. NO node-fetch, NO axios, NO additional npm dep.
40
+ *
41
+ * Output shape per paper (after dedupe):
42
+ * { id, title, abstract, authors[], institution, doi, source, fetched_at }
43
+ *
44
+ * Pure CJS, zero npm deps, node built-ins only beyond the three rs-egress-*
45
+ * primitives shipped in Wave 1 (Plan 89.2-01).
46
+ */
47
+ 'use strict';
48
+
49
+ const { ExternalEgressViolation } = require('./rs-egress-violations.cjs');
50
+ const { auditQueryString } = require('./rs-egress-prompts.cjs');
51
+ const {
52
+ recordTelemetry,
53
+ computeRemainingBudget,
54
+ DEFAULT_BUDGETS,
55
+ } = require('./rs-egress-telemetry.cjs');
56
+
57
+ // ---------- Frozen invariants ----------
58
+
59
+ // Iteration order is deliberate: openalex first so it wins doi ties on dedupe;
60
+ // arxiv second; pubmed third (the three no-key sources); then the env-key-gated
61
+ // sources in alphabetical order for predictability.
62
+ const SOURCES = Object.freeze(['openalex', 'arxiv', 'pubmed', 'scopus', 'ieee', 'nature']);
63
+
64
+ // Sources that require an env-var API key. openalex / arxiv / pubmed are free.
65
+ const SOURCE_ENV_VARS = Object.freeze({
66
+ scopus: 'SCOPUS_API_KEY',
67
+ ieee: 'IEEE_API_KEY',
68
+ nature: 'NATURE_API_KEY',
69
+ });
70
+
71
+ const DEFAULT_TIMEOUT_MS = 10000;
72
+
73
+ const USER_AGENT = 'MindrianOS-Plugin/1.11.0 (https://github.com/jsagir/mindrian-os-plugin)';
74
+
75
+ // Per-source endpoint base URLs.
76
+ const ENDPOINTS = Object.freeze({
77
+ openalex: 'https://api.openalex.org/works',
78
+ arxiv: 'https://export.arxiv.org/api/query',
79
+ pubmed: 'https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi',
80
+ scopus: 'https://api.elsevier.com/content/search/scopus',
81
+ ieee: 'https://ieeexploreapi.ieee.org/api/v1/search/articles',
82
+ nature: 'https://api.springernature.com/meta/v2/json',
83
+ });
84
+
85
+ // ---------- buildAcademicQuery (THE chokepoint) ----------
86
+ //
87
+ // This is the ONLY function in the module that constructs an outbound URL.
88
+ // Every per-source dispatcher invokes it. auditQueryString runs before the
89
+ // URL is returned, so adversarial input throws ExternalEgressViolation
90
+ // pre-egress and the URL is never built (and never reaches fetch()).
91
+ //
92
+ // Returns one of:
93
+ // { skip: true, reason: 'api_key_missing' } when env var absent for gated source
94
+ // { skip: false, url, headers, method, source } when ready to fetch
95
+ //
96
+ // Throws:
97
+ // TypeError if query is not a non-empty string or source unknown
98
+ // ExternalEgressViolation if query matches FORBIDDEN_PATTERNS
99
+
100
+ function buildAcademicQuery(query, source, opts) {
101
+ if (typeof query !== 'string' || query.length === 0) {
102
+ throw new TypeError('buildAcademicQuery: query must be a non-empty string');
103
+ }
104
+ if (!SOURCES.includes(source)) {
105
+ throw new TypeError('buildAcademicQuery: unknown source: ' + String(source));
106
+ }
107
+
108
+ // Canon Part 8 chokepoint: throws on adversarial query BEFORE URL build.
109
+ auditQueryString(query, 'academic');
110
+
111
+ // Env-var gate for paid sources.
112
+ const envVar = SOURCE_ENV_VARS[source];
113
+ if (envVar && !process.env[envVar]) {
114
+ return { skip: true, reason: 'api_key_missing' };
115
+ }
116
+
117
+ const headers = {
118
+ 'User-Agent': USER_AGENT,
119
+ 'Accept': source === 'arxiv' ? 'application/atom+xml' : 'application/json',
120
+ };
121
+
122
+ const encoded = encodeURIComponent(query);
123
+ let url;
124
+ switch (source) {
125
+ case 'openalex': {
126
+ const email = process.env.OPENALEX_EMAIL || 'noreply@mindrian.ai';
127
+ url = ENDPOINTS.openalex
128
+ + '?search=' + encoded
129
+ + '&per-page=200'
130
+ + '&select=' + encodeURIComponent('id,title,abstract_inverted_index,publication_year,authorships,doi')
131
+ + '&mailto=' + encodeURIComponent(email);
132
+ break;
133
+ }
134
+ case 'arxiv': {
135
+ url = ENDPOINTS.arxiv
136
+ + '?search_query=' + encodeURIComponent('all:' + query)
137
+ + '&start=0&max_results=200';
138
+ break;
139
+ }
140
+ case 'pubmed': {
141
+ url = ENDPOINTS.pubmed
142
+ + '?db=pubmed&term=' + encoded
143
+ + '&retmax=200&retmode=json';
144
+ break;
145
+ }
146
+ case 'scopus': {
147
+ url = ENDPOINTS.scopus
148
+ + '?query=' + encoded
149
+ + '&apiKey=' + encodeURIComponent(process.env.SCOPUS_API_KEY);
150
+ break;
151
+ }
152
+ case 'ieee': {
153
+ url = ENDPOINTS.ieee
154
+ + '?querytext=' + encoded
155
+ + '&apikey=' + encodeURIComponent(process.env.IEEE_API_KEY)
156
+ + '&max_records=200';
157
+ break;
158
+ }
159
+ case 'nature': {
160
+ url = ENDPOINTS.nature
161
+ + '?q=' + encoded
162
+ + '&api_key=' + encodeURIComponent(process.env.NATURE_API_KEY);
163
+ break;
164
+ }
165
+ default:
166
+ // SOURCES list is frozen; this path is unreachable but the linter
167
+ // appreciates the explicit fallthrough.
168
+ throw new TypeError('buildAcademicQuery: unhandled source: ' + source);
169
+ }
170
+
171
+ return { skip: false, url: url, headers: headers, method: 'GET', source: source };
172
+ }
173
+
174
+ // ---------- fetchWithTimeout (the ONE native fetch call site) ----------
175
+ //
176
+ // Per the chokepoint exclusivity rule: every per-source dispatcher routes
177
+ // through this helper. This is the only place that touches global.fetch.
178
+ // Returns the raw Response on success; throws on network error or timeout.
179
+
180
+ async function fetchWithTimeout(built, opts) {
181
+ const timeoutMs = (opts && typeof opts.timeoutMs === 'number') ? opts.timeoutMs : DEFAULT_TIMEOUT_MS;
182
+ const controller = new AbortController();
183
+ const t = setTimeout(function () { controller.abort(); }, timeoutMs);
184
+ try {
185
+ const res = await fetch(built.url, {
186
+ method: built.method,
187
+ headers: built.headers,
188
+ signal: controller.signal,
189
+ });
190
+ return res;
191
+ } finally {
192
+ clearTimeout(t);
193
+ }
194
+ }
195
+
196
+ // ---------- parseRateLimit ----------
197
+ //
198
+ // Pulls a remaining-budget signal out of common HTTP headers. Returns
199
+ // undefined if absent. The shape varies per source so we probe a few names.
200
+
201
+ function parseRateLimit(headers) {
202
+ if (!headers) return undefined;
203
+ const probes = ['x-ratelimit-remaining', 'X-RateLimit-Remaining', 'ratelimit-remaining'];
204
+ for (const k of probes) {
205
+ let v;
206
+ if (typeof headers.get === 'function') {
207
+ v = headers.get(k);
208
+ } else if (Object.prototype.hasOwnProperty.call(headers, k)) {
209
+ v = headers[k];
210
+ }
211
+ if (v !== undefined && v !== null) {
212
+ const n = parseInt(String(v), 10);
213
+ if (!Number.isNaN(n)) return n;
214
+ }
215
+ }
216
+ return undefined;
217
+ }
218
+
219
+ // ---------- Abstract reconstruction (port of rs_corpus.py invert_abstract) ----------
220
+
221
+ function invertAbstract(invertedIndex) {
222
+ if (!invertedIndex || typeof invertedIndex !== 'object') return '';
223
+ const pairs = [];
224
+ for (const word of Object.keys(invertedIndex)) {
225
+ const positions = invertedIndex[word];
226
+ if (!Array.isArray(positions)) continue;
227
+ for (const pos of positions) {
228
+ const n = parseInt(pos, 10);
229
+ if (Number.isNaN(n)) continue;
230
+ pairs.push([n, word]);
231
+ }
232
+ }
233
+ pairs.sort(function (a, b) { return a[0] - b[0]; });
234
+ return pairs.map(function (p) { return p[1]; }).join(' ');
235
+ }
236
+
237
+ // ---------- Title normalization + dedup key (port of rs_corpus.py) ----------
238
+
239
+ function normalizeTitle(s) {
240
+ if (!s || typeof s !== 'string') return '';
241
+ return s.toLowerCase()
242
+ .replace(/[^a-z0-9\s]+/g, ' ')
243
+ .replace(/\s+/g, ' ')
244
+ .trim();
245
+ }
246
+
247
+ function dedupKey(doc) {
248
+ if (doc && typeof doc.doi === 'string' && doc.doi.trim().length > 0) {
249
+ let norm = doc.doi.trim().toLowerCase();
250
+ norm = norm.replace('https://doi.org/', '').replace('http://doi.org/', '');
251
+ return 'doi:' + norm;
252
+ }
253
+ const titleNorm = normalizeTitle((doc && doc.title) || '');
254
+ let firstAuthor = '';
255
+ if (doc && Array.isArray(doc.authors) && doc.authors.length > 0) {
256
+ const a = String(doc.authors[0] || '');
257
+ const parts = a.split(/\s+/);
258
+ firstAuthor = parts.length > 0 ? parts[parts.length - 1].toLowerCase() : '';
259
+ }
260
+ if (titleNorm) {
261
+ return 'title:' + titleNorm + '|' + firstAuthor;
262
+ }
263
+ return 'id:' + ((doc && doc.id) || '');
264
+ }
265
+
266
+ function dedupe(docs) {
267
+ const seen = new Set();
268
+ const out = [];
269
+ for (const doc of docs) {
270
+ const key = dedupKey(doc);
271
+ if (seen.has(key)) continue;
272
+ seen.add(key);
273
+ out.push(doc);
274
+ }
275
+ return out;
276
+ }
277
+
278
+ // ---------- Per-source response parsers ----------
279
+
280
+ function parseOpenAlex(json) {
281
+ const out = [];
282
+ if (!json || !Array.isArray(json.results)) return out;
283
+ for (const w of json.results) {
284
+ if (!w || typeof w !== 'object') continue;
285
+ const abstract = invertAbstract(w.abstract_inverted_index);
286
+ const authors = [];
287
+ let institution = '';
288
+ if (Array.isArray(w.authorships)) {
289
+ for (const a of w.authorships.slice(0, 5)) {
290
+ if (a && a.author && typeof a.author.display_name === 'string') {
291
+ authors.push(a.author.display_name);
292
+ }
293
+ if (!institution && a && Array.isArray(a.institutions) && a.institutions.length > 0) {
294
+ institution = a.institutions[0].display_name || '';
295
+ }
296
+ }
297
+ }
298
+ out.push({
299
+ id: w.id || '',
300
+ title: w.title || '',
301
+ abstract: abstract,
302
+ authors: authors,
303
+ institution: institution,
304
+ doi: w.doi || '',
305
+ source: 'openalex',
306
+ fetched_at: new Date().toISOString(),
307
+ });
308
+ }
309
+ return out;
310
+ }
311
+
312
+ function parseArxivXml(xmlText) {
313
+ const out = [];
314
+ if (typeof xmlText !== 'string' || xmlText.length === 0) return out;
315
+ // Minimalist regex-based Atom parser. Accepts only well-formed entries.
316
+ // Throws on shape mismatch upstream so api_error telemetry is recorded.
317
+ if (xmlText.indexOf('<feed') < 0 && xmlText.indexOf('<entry') < 0) {
318
+ throw new Error('arxiv: not an atom feed');
319
+ }
320
+ const entryRe = /<entry>([\s\S]*?)<\/entry>/g;
321
+ const entries = xmlText.match(entryRe) || [];
322
+ for (const e of entries) {
323
+ const idMatch = e.match(/<id>([\s\S]*?)<\/id>/);
324
+ const titleMatch = e.match(/<title>([\s\S]*?)<\/title>/);
325
+ const summaryMatch = e.match(/<summary>([\s\S]*?)<\/summary>/);
326
+ const authorMatches = [];
327
+ const authorRe = /<author>[\s\S]*?<name>([\s\S]*?)<\/name>[\s\S]*?<\/author>/g;
328
+ let m;
329
+ while ((m = authorRe.exec(e)) !== null) {
330
+ authorMatches.push(m[1].trim());
331
+ if (authorMatches.length >= 5) break;
332
+ }
333
+ const id = (idMatch && idMatch[1] || '').trim();
334
+ const title = (titleMatch && titleMatch[1] || '').trim();
335
+ const abstract = (summaryMatch && summaryMatch[1] || '').trim();
336
+ if (!abstract) continue;
337
+ out.push({
338
+ id: id,
339
+ title: title,
340
+ abstract: abstract,
341
+ authors: authorMatches,
342
+ institution: '',
343
+ doi: '',
344
+ source: 'arxiv',
345
+ fetched_at: new Date().toISOString(),
346
+ });
347
+ }
348
+ return out;
349
+ }
350
+
351
+ function parsePubMed(json) {
352
+ const out = [];
353
+ if (!json || !json.esearchresult || !Array.isArray(json.esearchresult.idlist)) return out;
354
+ for (const id of json.esearchresult.idlist) {
355
+ out.push({
356
+ id: 'pubmed:' + id,
357
+ title: 'PubMed record ' + id,
358
+ abstract: '',
359
+ authors: [],
360
+ institution: '',
361
+ doi: '',
362
+ source: 'pubmed',
363
+ fetched_at: new Date().toISOString(),
364
+ });
365
+ }
366
+ return out;
367
+ }
368
+
369
+ function parseScopus(json) {
370
+ const out = [];
371
+ if (!json || !json['search-results'] || !Array.isArray(json['search-results'].entry)) return out;
372
+ for (const e of json['search-results'].entry) {
373
+ if (!e || typeof e !== 'object') continue;
374
+ let institution = '';
375
+ if (Array.isArray(e.affiliation) && e.affiliation.length > 0) {
376
+ institution = e.affiliation[0].affilname || '';
377
+ }
378
+ out.push({
379
+ id: e['dc:identifier'] || '',
380
+ title: e['dc:title'] || '',
381
+ abstract: e['dc:description'] || '',
382
+ authors: e['dc:creator'] ? [e['dc:creator']] : [],
383
+ institution: institution,
384
+ doi: e['prism:doi'] || '',
385
+ source: 'scopus',
386
+ fetched_at: new Date().toISOString(),
387
+ });
388
+ }
389
+ return out;
390
+ }
391
+
392
+ function parseIeee(json) {
393
+ const out = [];
394
+ if (!json || !Array.isArray(json.articles)) return out;
395
+ for (const a of json.articles) {
396
+ if (!a || typeof a !== 'object') continue;
397
+ const authors = [];
398
+ let institution = '';
399
+ if (a.authors && Array.isArray(a.authors.authors)) {
400
+ for (const au of a.authors.authors.slice(0, 5)) {
401
+ if (au && au.full_name) authors.push(au.full_name);
402
+ if (!institution && au && au.affiliation) institution = au.affiliation;
403
+ }
404
+ }
405
+ out.push({
406
+ id: 'ieee:' + (a.article_number || ''),
407
+ title: a.title || '',
408
+ abstract: a.abstract || '',
409
+ authors: authors,
410
+ institution: institution,
411
+ doi: a.doi || '',
412
+ source: 'ieee',
413
+ fetched_at: new Date().toISOString(),
414
+ });
415
+ }
416
+ return out;
417
+ }
418
+
419
+ function parseNature(json) {
420
+ const out = [];
421
+ if (!json || !Array.isArray(json.records)) return out;
422
+ for (const r of json.records) {
423
+ if (!r || typeof r !== 'object') continue;
424
+ const authors = [];
425
+ if (Array.isArray(r.creators)) {
426
+ for (const c of r.creators.slice(0, 5)) {
427
+ if (c && c.creator) authors.push(c.creator);
428
+ }
429
+ }
430
+ out.push({
431
+ id: r.identifier || '',
432
+ title: r.title || '',
433
+ abstract: r.abstract || '',
434
+ authors: authors,
435
+ institution: '',
436
+ doi: r.doi || '',
437
+ source: 'nature',
438
+ fetched_at: new Date().toISOString(),
439
+ });
440
+ }
441
+ return out;
442
+ }
443
+
444
+ function parseSourceResponse(payload, source) {
445
+ switch (source) {
446
+ case 'openalex': return parseOpenAlex(payload);
447
+ case 'arxiv': return parseArxivXml(payload);
448
+ case 'pubmed': return parsePubMed(payload);
449
+ case 'scopus': return parseScopus(payload);
450
+ case 'ieee': return parseIeee(payload);
451
+ case 'nature': return parseNature(payload);
452
+ default: return [];
453
+ }
454
+ }
455
+
456
+ // ---------- normalizePaper ----------
457
+ //
458
+ // Final shape guarantee. Per-source parsers already produce this shape
459
+ // but normalizePaper is exposed so future call sites can canonicalize
460
+ // hand-constructed records.
461
+
462
+ function normalizePaper(raw) {
463
+ return {
464
+ id: raw && raw.id ? String(raw.id) : '',
465
+ title: raw && raw.title ? String(raw.title) : '',
466
+ abstract: raw && raw.abstract ? String(raw.abstract) : '',
467
+ authors: raw && Array.isArray(raw.authors) ? raw.authors.slice(0, 5).map(String) : [],
468
+ institution: raw && raw.institution ? String(raw.institution) : '',
469
+ doi: raw && raw.doi ? String(raw.doi) : '',
470
+ source: raw && raw.source ? String(raw.source) : '',
471
+ fetched_at: raw && raw.fetched_at ? String(raw.fetched_at) : new Date().toISOString(),
472
+ };
473
+ }
474
+
475
+ // ---------- fetchAcademic ----------
476
+ //
477
+ // Top-level orchestrator. Iterates SOURCES in frozen order; per source
478
+ // iterates the input queries. Per (source, query) tuple:
479
+ // 1. Check budget. budget==0 -> skip source for this run.
480
+ // 2. Build query via chokepoint (throws ExternalEgressViolation on adversarial).
481
+ // 3. If skip (api_key_missing) -> recordTelemetry + continue.
482
+ // 4. fetchWithTimeout -> on timeout: recordTelemetry + continue.
483
+ // 5. status 429/503 -> recordTelemetry(rate_limited) + continue.
484
+ // 6. parse response. parse error -> recordTelemetry(api_error) + continue.
485
+ // 7. recordTelemetry(ok) + accumulate papers.
486
+ // After all sources, dedupe and return.
487
+
488
+ async function fetchAcademic(queries, opts) {
489
+ if (!Array.isArray(queries)) {
490
+ throw new TypeError('fetchAcademic: queries must be an array of non-empty strings');
491
+ }
492
+ for (const q of queries) {
493
+ if (typeof q !== 'string' || q.length === 0) {
494
+ throw new TypeError('fetchAcademic: each query must be a non-empty string');
495
+ }
496
+ }
497
+ opts = opts || {};
498
+ const budgetOverrides = opts.budget || {};
499
+
500
+ const out = { papers: [], telemetry: [] };
501
+
502
+ for (const source of SOURCES) {
503
+ const budgetCap = (typeof budgetOverrides[source] === 'number')
504
+ ? budgetOverrides[source]
505
+ : DEFAULT_BUDGETS[source];
506
+ const remaining = computeRemainingBudget(source, budgetCap);
507
+ if (remaining <= 0) {
508
+ // Skip source for this run; budget itself is the trace. We do NOT
509
+ // call recordTelemetry here (status 'budget_exhausted' is not in
510
+ // ALLOWED_STATUSES for the v1 telemetry primitive).
511
+ out.telemetry.push({ source: source, status: 'budget_exhausted' });
512
+ continue;
513
+ }
514
+
515
+ for (const query of queries) {
516
+ // Build first. Throws ExternalEgressViolation if adversarial.
517
+ // We intentionally do NOT catch here so the adversarial query
518
+ // surfaces immediately and ZERO telemetry is recorded for it.
519
+ const built = buildAcademicQuery(query, source, opts);
520
+
521
+ if (built.skip) {
522
+ // env var missing for gated source.
523
+ recordTelemetry({
524
+ source: source,
525
+ query_text: query,
526
+ status: built.reason,
527
+ });
528
+ out.telemetry.push({ source: source, status: built.reason });
529
+ // No need to keep iterating queries for a source with no key:
530
+ // every query will hit the same gate. Break early to save work.
531
+ break;
532
+ }
533
+
534
+ let res = null;
535
+ try {
536
+ res = await fetchWithTimeout(built, opts);
537
+ } catch (err) {
538
+ if (err && err.name === 'AbortError') {
539
+ recordTelemetry({
540
+ source: source,
541
+ query_text: query,
542
+ status: 'timeout',
543
+ });
544
+ out.telemetry.push({ source: source, status: 'timeout' });
545
+ continue;
546
+ }
547
+ recordTelemetry({
548
+ source: source,
549
+ query_text: query,
550
+ status: 'network_error',
551
+ });
552
+ out.telemetry.push({ source: source, status: 'network_error' });
553
+ continue;
554
+ }
555
+
556
+ if (!res.ok) {
557
+ const httpStatus = res.status || 0;
558
+ if (httpStatus === 429 || httpStatus === 503) {
559
+ recordTelemetry({
560
+ source: source,
561
+ query_text: query,
562
+ status: 'rate_limited',
563
+ http_status: httpStatus,
564
+ });
565
+ out.telemetry.push({ source: source, status: 'rate_limited', http_status: httpStatus });
566
+ continue;
567
+ }
568
+ recordTelemetry({
569
+ source: source,
570
+ query_text: query,
571
+ status: 'api_error',
572
+ http_status: httpStatus,
573
+ });
574
+ out.telemetry.push({ source: source, status: 'api_error', http_status: httpStatus });
575
+ continue;
576
+ }
577
+
578
+ let parsed = null;
579
+ try {
580
+ // arxiv returns Atom XML; everyone else returns JSON.
581
+ const payload = (source === 'arxiv') ? await res.text() : await res.json();
582
+ parsed = parseSourceResponse(payload, source);
583
+ } catch (_err) {
584
+ recordTelemetry({
585
+ source: source,
586
+ query_text: query,
587
+ status: 'api_error',
588
+ http_status: res.status || 0,
589
+ });
590
+ out.telemetry.push({ source: source, status: 'api_error', http_status: res.status || 0 });
591
+ continue;
592
+ }
593
+
594
+ const rateRemaining = parseRateLimit(res.headers);
595
+ const recOpts = {
596
+ source: source,
597
+ query_text: query,
598
+ status: 'ok',
599
+ http_status: res.status || 200,
600
+ };
601
+ if (typeof rateRemaining === 'number') {
602
+ recOpts.rate_limit_remaining = rateRemaining;
603
+ }
604
+ recordTelemetry(recOpts);
605
+ out.telemetry.push({ source: source, status: 'ok', http_status: res.status || 200 });
606
+
607
+ for (const p of parsed) {
608
+ out.papers.push(p);
609
+ }
610
+ }
611
+ }
612
+
613
+ // First-seen wins on dedupe; openalex ran first so it wins doi ties.
614
+ out.papers = dedupe(out.papers);
615
+
616
+ // ---- Phase 94 Plan 05 amendment: envelope wrap ----
617
+ // Determine source tag: prefer the first source in telemetry that
618
+ // returned status:'ok'. Fall back to SOURCES[0] when no source
619
+ // produced data (so the envelope still has a valid source tag and
620
+ // the assertEnvelope T5/academic gate passes on shape uniformity).
621
+ let source = SOURCES[0];
622
+ for (const t of out.telemetry) {
623
+ if (t && t.status === 'ok' && typeof t.source === 'string') {
624
+ source = t.source;
625
+ break;
626
+ }
627
+ }
628
+ return {
629
+ tier: 'paid',
630
+ source: source,
631
+ results: out.papers.slice(),
632
+ papers: out.papers,
633
+ telemetry: out.telemetry,
634
+ };
635
+ }
636
+
637
+ // ---------- Per-source dispatchers ----------
638
+ //
639
+ // Convenience entry points for callers that want one source. All four
640
+ // route through fetchAcademic internally (limiting SOURCES to the one
641
+ // requested) so the chokepoint exclusivity rule is preserved.
642
+
643
+ async function fetchOneSource(source, queries, opts) {
644
+ const orig = SOURCES.slice();
645
+ // Build a one-source orchestrator by delegating to fetchAcademic with
646
+ // a budget map that zeroes out every other source. This keeps the
647
+ // chokepoint exclusivity invariant intact (no new fetch sites).
648
+ opts = opts || {};
649
+ const budget = Object.assign({}, opts.budget || {});
650
+ for (const s of orig) {
651
+ if (s !== source) budget[s] = 0;
652
+ }
653
+ const merged = Object.assign({}, opts, { budget: budget });
654
+ return fetchAcademic(queries, merged);
655
+ }
656
+
657
+ async function fetchOpenAlex(queries, opts) { return fetchOneSource('openalex', queries, opts); }
658
+ async function fetchArxiv(queries, opts) { return fetchOneSource('arxiv', queries, opts); }
659
+ async function fetchPubMed(queries, opts) { return fetchOneSource('pubmed', queries, opts); }
660
+ async function fetchScopus(queries, opts) { return fetchOneSource('scopus', queries, opts); }
661
+ async function fetchIeee(queries, opts) { return fetchOneSource('ieee', queries, opts); }
662
+ async function fetchNature(queries, opts) { return fetchOneSource('nature', queries, opts); }
663
+
664
+ // ---------- Exports ----------
665
+
666
+ module.exports = {
667
+ fetchAcademic,
668
+ buildAcademicQuery,
669
+ fetchOpenAlex,
670
+ fetchArxiv,
671
+ fetchPubMed,
672
+ fetchScopus,
673
+ fetchIeee,
674
+ fetchNature,
675
+ // Test surface (private; do NOT consume in production).
676
+ _test: {
677
+ dedupe,
678
+ dedupKey,
679
+ normalizeTitle,
680
+ normalizePaper,
681
+ invertAbstract,
682
+ fetchWithTimeout,
683
+ parseSourceResponse,
684
+ parseOpenAlex,
685
+ parseArxivXml,
686
+ parsePubMed,
687
+ parseScopus,
688
+ parseIeee,
689
+ parseNature,
690
+ parseRateLimit,
691
+ SOURCES,
692
+ SOURCE_ENV_VARS,
693
+ ENDPOINTS,
694
+ DEFAULT_TIMEOUT_MS,
695
+ USER_AGENT,
696
+ },
697
+ };