@maycombhq/atticus 0.0.0-stage → 2.0.0-beta.2

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 (475) hide show
  1. package/LICENSE +177 -0
  2. package/README.md +17 -2
  3. package/THIRD-PARTY-LICENSES.txt +2607 -0
  4. package/dist/bin.js +47 -0
  5. package/dist/chunk-23W7VWKR.js +2609 -0
  6. package/dist/chunk-27NKUS3X.js +4330 -0
  7. package/dist/chunk-2ANOROX3.js +2522 -0
  8. package/dist/chunk-2BF2EYJV.js +174 -0
  9. package/dist/chunk-2BFCZ3RK.js +315 -0
  10. package/dist/chunk-2DIQQJCB.js +378 -0
  11. package/dist/chunk-2RMYG36Q.js +1227 -0
  12. package/dist/chunk-2VHF7OXM.js +55 -0
  13. package/dist/chunk-3ALD2W2A.js +95 -0
  14. package/dist/chunk-3C7EYRFZ.js +41 -0
  15. package/dist/chunk-3G4YI4ZU.js +16 -0
  16. package/dist/chunk-3HI6Z55Y.js +5949 -0
  17. package/dist/chunk-3USYZGRF.js +324 -0
  18. package/dist/chunk-42EVSKME.js +37 -0
  19. package/dist/chunk-4AK7IBVP.js +915 -0
  20. package/dist/chunk-4BKGMUIK.js +53 -0
  21. package/dist/chunk-4BN53TG3.js +343 -0
  22. package/dist/chunk-4NDAAFQT.js +103 -0
  23. package/dist/chunk-4Q3NN7VF.js +32 -0
  24. package/dist/chunk-4RS5KZQD.js +25 -0
  25. package/dist/chunk-4SKZDI62.js +22 -0
  26. package/dist/chunk-4WBVUHXN.js +69 -0
  27. package/dist/chunk-5GDRCDQB.js +32 -0
  28. package/dist/chunk-5GGOLAKC.js +856 -0
  29. package/dist/chunk-5KLGBXG5.js +48 -0
  30. package/dist/chunk-5WG5YP4T.js +34 -0
  31. package/dist/chunk-64EQYWCA.js +180 -0
  32. package/dist/chunk-652QWTLZ.js +242 -0
  33. package/dist/chunk-65NYPGRC.js +766 -0
  34. package/dist/chunk-66YVAGGM.js +46 -0
  35. package/dist/chunk-6BNC6EQ4.js +41 -0
  36. package/dist/chunk-6BVONWJ7.js +50 -0
  37. package/dist/chunk-6D2CQ5II.js +41 -0
  38. package/dist/chunk-6IU67AEK.js +325 -0
  39. package/dist/chunk-6J5OKA4Y.js +49 -0
  40. package/dist/chunk-6JCPI55B.js +92 -0
  41. package/dist/chunk-6JUBZBMW.js +56 -0
  42. package/dist/chunk-6M5NK3K3.js +415 -0
  43. package/dist/chunk-6QSERWXH.js +53 -0
  44. package/dist/chunk-6TACQN3N.js +41 -0
  45. package/dist/chunk-76WKMA4J.js +14 -0
  46. package/dist/chunk-7AVHLZGD.js +45 -0
  47. package/dist/chunk-7EFILTCI.js +67 -0
  48. package/dist/chunk-7EKXJZ2Y.js +2902 -0
  49. package/dist/chunk-7EOBHUMC.js +133 -0
  50. package/dist/chunk-7FBGJFEE.js +66 -0
  51. package/dist/chunk-7JUKDRSZ.js +1726 -0
  52. package/dist/chunk-7SW2QWLU.js +212 -0
  53. package/dist/chunk-7WD63HU2.js +143 -0
  54. package/dist/chunk-7ZUIMTOU.js +108 -0
  55. package/dist/chunk-A3GY6SOA.js +95 -0
  56. package/dist/chunk-A7UPUQFY.js +2356 -0
  57. package/dist/chunk-AA6YULYB.js +376 -0
  58. package/dist/chunk-ABWHYWNI.js +101 -0
  59. package/dist/chunk-AIOQPLA5.js +540 -0
  60. package/dist/chunk-AVJDSJ2F.js +18 -0
  61. package/dist/chunk-B2WLAWNJ.js +374 -0
  62. package/dist/chunk-BKVWTZ6A.js +211 -0
  63. package/dist/chunk-BOHJZTEQ.js +1991 -0
  64. package/dist/chunk-BWZ77RQT.js +25 -0
  65. package/dist/chunk-C6EKFH7J.js +169 -0
  66. package/dist/chunk-CAQZUNOH.js +1150 -0
  67. package/dist/chunk-CEXSU4S6.js +1217 -0
  68. package/dist/chunk-CKSITVS4.js +16101 -0
  69. package/dist/chunk-CNUKCS2T.js +2741 -0
  70. package/dist/chunk-CPA6HPVR.js +3206 -0
  71. package/dist/chunk-CR3MTH32.js +207 -0
  72. package/dist/chunk-CSZ4C5N6.js +307 -0
  73. package/dist/chunk-D3PO4DOZ.js +261 -0
  74. package/dist/chunk-D46WUV3T.js +33 -0
  75. package/dist/chunk-DE76JROX.js +80 -0
  76. package/dist/chunk-DFBCSHVT.js +69 -0
  77. package/dist/chunk-DHPACDG7.js +104 -0
  78. package/dist/chunk-DKJA76CJ.js +113 -0
  79. package/dist/chunk-DPUYDDQG.js +164 -0
  80. package/dist/chunk-DPZHZQKE.js +285 -0
  81. package/dist/chunk-E36QOJMD.js +47 -0
  82. package/dist/chunk-EADP3PEN.js +450 -0
  83. package/dist/chunk-EBEW3XXQ.js +166 -0
  84. package/dist/chunk-EDS2L2LD.js +106 -0
  85. package/dist/chunk-EFHQ3TWP.js +637 -0
  86. package/dist/chunk-EPTBZ3GP.js +101 -0
  87. package/dist/chunk-EYWIDJFU.js +130 -0
  88. package/dist/chunk-F7HYOUFF.js +287 -0
  89. package/dist/chunk-FHH4I4ZV.js +128 -0
  90. package/dist/chunk-FITWGOJ2.js +131 -0
  91. package/dist/chunk-FQXYPF7E.js +147 -0
  92. package/dist/chunk-G2B4ZQGH.js +240 -0
  93. package/dist/chunk-GBRFVT63.js +45 -0
  94. package/dist/chunk-GDLULHTC.js +2919 -0
  95. package/dist/chunk-GLUX4EEP.js +583 -0
  96. package/dist/chunk-GQ4N4WKF.js +1142 -0
  97. package/dist/chunk-GT4I4ZWM.js +38 -0
  98. package/dist/chunk-HFQCFSJR.js +60 -0
  99. package/dist/chunk-HITVBIJS.js +1247 -0
  100. package/dist/chunk-HLJAXMM4.js +233 -0
  101. package/dist/chunk-HVUPKCGJ.js +162 -0
  102. package/dist/chunk-I3E7CX4H.js +155 -0
  103. package/dist/chunk-I4JRCD6M.js +184 -0
  104. package/dist/chunk-ICY36XCH.js +34 -0
  105. package/dist/chunk-IHMLFRVV.js +228 -0
  106. package/dist/chunk-IPKHE5D6.js +1963 -0
  107. package/dist/chunk-J4K3KVLD.js +131 -0
  108. package/dist/chunk-JBTK5OP2.js +55 -0
  109. package/dist/chunk-JCZIKA4C.js +163 -0
  110. package/dist/chunk-JGZCG5VM.js +189 -0
  111. package/dist/chunk-JIITQXFB.js +4087 -0
  112. package/dist/chunk-JMSCMUCA.js +271 -0
  113. package/dist/chunk-JZU3H6UQ.js +54 -0
  114. package/dist/chunk-K65BJIMT.js +49 -0
  115. package/dist/chunk-KEM4SDDP.js +3898 -0
  116. package/dist/chunk-KQDZXGNR.js +14 -0
  117. package/dist/chunk-L24C55VN.js +7923 -0
  118. package/dist/chunk-L6SGQIR3.js +471 -0
  119. package/dist/chunk-LR3R6Z2A.js +400 -0
  120. package/dist/chunk-M766ZJ6W.js +119 -0
  121. package/dist/chunk-MC3H7736.js +19910 -0
  122. package/dist/chunk-MGYZM27B.js +205 -0
  123. package/dist/chunk-MMOSZPWJ.js +273 -0
  124. package/dist/chunk-MVZVGYXH.js +1611 -0
  125. package/dist/chunk-N7IXEHK3.js +549 -0
  126. package/dist/chunk-NE2W3OO7.js +230 -0
  127. package/dist/chunk-NN2ZQEB4.js +38 -0
  128. package/dist/chunk-NXFPS2LR.js +182 -0
  129. package/dist/chunk-O3WI5KWW.js +31 -0
  130. package/dist/chunk-OLQJTJJU.js +76 -0
  131. package/dist/chunk-ONLS5QA5.js +102 -0
  132. package/dist/chunk-OT2O76OS.js +1496 -0
  133. package/dist/chunk-PAMPPUR6.js +4500 -0
  134. package/dist/chunk-PCOCACY3.js +385 -0
  135. package/dist/chunk-PCVDV3UZ.js +430 -0
  136. package/dist/chunk-PRPPGYZG.js +104 -0
  137. package/dist/chunk-Q4D5X7QB.js +264 -0
  138. package/dist/chunk-Q5AMFEWD.js +119 -0
  139. package/dist/chunk-QRAOANID.js +214 -0
  140. package/dist/chunk-QSXK2RGH.js +688 -0
  141. package/dist/chunk-QZSX3F7C.js +389 -0
  142. package/dist/chunk-R6RY4Q5T.js +51 -0
  143. package/dist/chunk-R6XF6UR3.js +149 -0
  144. package/dist/chunk-R77KLRB3.js +166 -0
  145. package/dist/chunk-RRTKKSLQ.js +251 -0
  146. package/dist/chunk-RTEYOSJY.js +29 -0
  147. package/dist/chunk-RUVFXE2L.js +666 -0
  148. package/dist/chunk-RVH5SUW5.js +69 -0
  149. package/dist/chunk-RZPYKDWS.js +26 -0
  150. package/dist/chunk-S2A7YC3Q.js +41433 -0
  151. package/dist/chunk-S3QNRWYR.js +25 -0
  152. package/dist/chunk-SJRGMG6A.js +174 -0
  153. package/dist/chunk-SP2I63OO.js +252 -0
  154. package/dist/chunk-T2UYZSCQ.js +545 -0
  155. package/dist/chunk-T6AIP7H5.js +3851 -0
  156. package/dist/chunk-T6XQL73F.js +94 -0
  157. package/dist/chunk-TANFV3MZ.js +531 -0
  158. package/dist/chunk-TDD55CJH.js +119 -0
  159. package/dist/chunk-TV45EUGM.js +51 -0
  160. package/dist/chunk-TXLH6WHQ.js +2078 -0
  161. package/dist/chunk-TYAID7B7.js +408 -0
  162. package/dist/chunk-UBBC2XSG.js +873 -0
  163. package/dist/chunk-UGLN7QHV.js +27 -0
  164. package/dist/chunk-UNM6ZOKP.js +878 -0
  165. package/dist/chunk-V2YTAIQO.js +1075 -0
  166. package/dist/chunk-VN3DP4LQ.js +43 -0
  167. package/dist/chunk-VOGCSYOI.js +16 -0
  168. package/dist/chunk-VYDBJ73M.js +32 -0
  169. package/dist/chunk-W3BQAZ3Q.js +34 -0
  170. package/dist/chunk-W5BVQF44.js +36 -0
  171. package/dist/chunk-WVPJSRRJ.js +52 -0
  172. package/dist/chunk-WY6PP6AA.js +53 -0
  173. package/dist/chunk-XAKWMCXI.js +285 -0
  174. package/dist/chunk-XPCM366L.js +70 -0
  175. package/dist/chunk-XPTOBS2D.js +48 -0
  176. package/dist/chunk-XY3P7JW7.js +3479 -0
  177. package/dist/chunk-XZRON6IV.js +131 -0
  178. package/dist/chunk-YEDDOYSC.js +27809 -0
  179. package/dist/chunk-YISXLPHN.js +496 -0
  180. package/dist/chunk-YMZ3TNYB.js +18 -0
  181. package/dist/chunk-YY6ZQQBP.js +203 -0
  182. package/dist/chunk-YYQN46FY.js +334 -0
  183. package/dist/chunk-Z6LF7HQN.js +22 -0
  184. package/dist/chunk-ZCJDOK2S.js +116 -0
  185. package/dist/chunk-ZDPAS56H.js +76 -0
  186. package/dist/chunk-ZMAINWBW.js +18 -0
  187. package/dist/chunk-ZMFPW66A.js +22 -0
  188. package/dist/chunk-ZNCEKR4J.js +296 -0
  189. package/dist/chunk-ZVM6AKKM.js +47 -0
  190. package/dist/files.json +1888 -0
  191. package/dist/fixtures/forge/README.md +19 -0
  192. package/dist/fixtures/forge/expected/discrepancies.yaml +54 -0
  193. package/dist/fixtures/forge/expected/inputs.yaml +38 -0
  194. package/dist/fixtures/forge/expected/scan.yaml +342 -0
  195. package/dist/fixtures/forge/inputs/diagrams/forge-context.drawio +23 -0
  196. package/dist/fixtures/forge/inputs/partner-specs/northwind-pay-openapi.yaml +11 -0
  197. package/dist/fixtures/forge/inputs/tickets/ado-stories.json +4 -0
  198. package/dist/fixtures/forge/inputs/tickets/closed-service-requests.json +5 -0
  199. package/dist/fixtures/forge/inputs/wiki/integration-patterns.md +11 -0
  200. package/dist/fixtures/forge/inputs/wiki/ledger-solution-spec.md +9 -0
  201. package/dist/fixtures/forge/inputs/wiki/snow-firewall-request.md +4 -0
  202. package/dist/fixtures/forge/metarepo/judgments/reaches/84af5b6af24f7ca8c23013f3.yaml +17 -0
  203. package/dist/fixtures/forge/metarepo/judgments/reaches/c3917555edebddce8cecb647.yaml +17 -0
  204. package/dist/fixtures/forge/metarepo/judgments/reaches/c5fafd6601f8ca9e34e0bbd0.yaml +17 -0
  205. package/dist/fixtures/forge/metarepo/org-pack/agents/architecture-critic.yaml +9 -0
  206. package/dist/fixtures/forge/metarepo/org-pack/agents/story-writer.yaml +10 -0
  207. package/dist/fixtures/forge/metarepo/org-pack/checklists/architecture.yaml +115 -0
  208. package/dist/fixtures/forge/metarepo/org-pack/dialects/forge.yaml +111 -0
  209. package/dist/fixtures/forge/metarepo/org-pack/gates.yaml +22 -0
  210. package/dist/fixtures/forge/metarepo/org-pack/pack.yaml +11 -0
  211. package/dist/fixtures/forge/metarepo/org-pack/patterns/A.EB.PUB.yaml +12 -0
  212. package/dist/fixtures/forge/metarepo/org-pack/patterns/D.JDBC.yaml +12 -0
  213. package/dist/fixtures/forge/metarepo/org-pack/patterns/S.DIR.BFF.yaml +12 -0
  214. package/dist/fixtures/forge/metarepo/org-pack/patterns/S.GW.SOR.yaml +13 -0
  215. package/dist/fixtures/forge/metarepo/org-pack/patterns/S.GW.VND.yaml +14 -0
  216. package/dist/fixtures/forge/metarepo/org-pack/process/change-templates/feature.md +37 -0
  217. package/dist/fixtures/forge/metarepo/org-pack/process/skills/add-forge-endpoint/SKILL.md +17 -0
  218. package/dist/fixtures/forge/metarepo/org-pack/process/snow/firewall-rule.yaml +14 -0
  219. package/dist/fixtures/forge/metarepo/org-pack/process/snow/gateway-route.yaml +13 -0
  220. package/dist/fixtures/forge/metarepo/org-pack/profiles/forge/profile.yaml +12 -0
  221. package/dist/fixtures/forge/metarepo/org-pack/profiles/forge/repos.manifest.yaml +44 -0
  222. package/dist/fixtures/forge/metarepo/org-pack/roles.yaml +14 -0
  223. package/dist/fixtures/forge/metarepo/org-pack/runtime.yaml +14 -0
  224. package/dist/fixtures/forge/metarepo/org-pack/sources.yaml +3 -0
  225. package/dist/fixtures/forge/repos/forge-events/topics/payments.initiated.yaml +4 -0
  226. package/dist/fixtures/forge/repos/forge-events/topics/transfers.completed.yaml +4 -0
  227. package/dist/fixtures/forge/repos/forge-gateway/proxies/ledger-v1/apiproxy/ledger-v1.xml +3 -0
  228. package/dist/fixtures/forge/repos/forge-gateway/proxies/ledger-v1/apiproxy/policies/SpikeArrest.xml +3 -0
  229. package/dist/fixtures/forge/repos/forge-gateway/proxies/ledger-v1/apiproxy/policies/VerifyOAuth.xml +4 -0
  230. package/dist/fixtures/forge/repos/forge-gateway/proxies/ledger-v1/apiproxy/proxies/default.xml +14 -0
  231. package/dist/fixtures/forge/repos/forge-gateway/proxies/ledger-v1/apiproxy/targets/ledger.xml +5 -0
  232. package/dist/fixtures/forge/repos/forge-infra/alerts/ledger-api.yaml +5 -0
  233. package/dist/fixtures/forge/repos/forge-infra/k8s/ledger-api.yaml +15 -0
  234. package/dist/fixtures/forge/repos/forge-infra/k8s/portal-bff.yaml +7 -0
  235. package/dist/fixtures/forge/repos/forge-infra/terraform/ledger-db.tf +9 -0
  236. package/dist/fixtures/forge/repos/forge-ledger/api/ledger-openapi.yaml +31 -0
  237. package/dist/fixtures/forge/repos/forge-ledger/forge.yaml +4 -0
  238. package/dist/fixtures/forge/repos/forge-ledger/src/main/java/forge/ledger/LedgerEndpoints.java +13 -0
  239. package/dist/fixtures/forge/repos/forge-ledger/src/main/java/forge/ledger/LedgerEvents.java +11 -0
  240. package/dist/fixtures/forge/repos/forge-ledger/src/main/java/forge/ledger/NorthwindClient.java +10 -0
  241. package/dist/fixtures/forge/repos/forge-ledger/src/main/resources/application.yml +3 -0
  242. package/dist/fixtures/forge/repos/forge-ledger/src/main/resources/db/migration/V1__create_account.sql +5 -0
  243. package/dist/fixtures/forge/repos/forge-ledger/src/main/resources/db/migration/V2__create_entry.sql +7 -0
  244. package/dist/fixtures/forge/repos/forge-portal-bff/pom.xml +1 -0
  245. package/dist/fixtures/forge/repos/forge-portal-bff/src/main/java/forge/portal/AuditPublisher.java +12 -0
  246. package/dist/fixtures/forge/repos/forge-portal-bff/src/main/java/forge/portal/BalanceController.java +16 -0
  247. package/dist/fixtures/forge/repos/forge-portal-bff/src/main/java/forge/portal/LedgerClient.java +13 -0
  248. package/dist/fixtures/forge/repos/forge-portal-bff/src/main/java/forge/portal/PaymentEvents.java +13 -0
  249. package/dist/fixtures/forge/repos/forge-portal-bff/src/main/resources/application.yml +8 -0
  250. package/dist/fixtures/forge/repos/forge-portal-ui/package.json +1 -0
  251. package/dist/fixtures/forge/repos/forge-portal-ui/src/api/balance.ts +5 -0
  252. package/dist/fixtures/forge/repos/forge-reports-job/src/main/java/forge/reports/DailyEntriesJob.java +19 -0
  253. package/dist/fixtures/forge/repos/forge-reports-job/src/main/resources/application.yml +3 -0
  254. package/dist/fixtures/forge-change/README.md +41 -0
  255. package/dist/fixtures/forge-change/expected/findings.yaml +301 -0
  256. package/dist/fixtures/forge-change/expected/stories.yaml +101 -0
  257. package/dist/fixtures/forge-change/judgments/personal-data/0b5f3dbf5257a22275d8c74b.yaml +18 -0
  258. package/dist/fixtures/forge-change/judgments/personal-data/2759a66770134c417440bf2d.yaml +18 -0
  259. package/dist/fixtures/forge-change/judgments/personal-data/31b1ea2f14d0737f1aca79fd.yaml +18 -0
  260. package/dist/fixtures/forge-change/judgments/personal-data/49afd70c08228e65ad6ca1ed.yaml +18 -0
  261. package/dist/fixtures/forge-change/judgments/personal-data/761cabcb8a39f5e5866d2dd6.yaml +18 -0
  262. package/dist/fixtures/forge-change/judgments/personal-data/adae902b90cc8ffa0c765145.yaml +18 -0
  263. package/dist/fixtures/forge-change/judgments/personal-data/ba11bb933105ec9493e065dd.yaml +18 -0
  264. package/dist/fixtures/forge-change/judgments/personal-data/bdbcd1cdac3021c68063c3e7.yaml +18 -0
  265. package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/brief.md +35 -0
  266. package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/change.yaml +120 -0
  267. package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/data.md +18 -0
  268. package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/design.md +29 -0
  269. package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/interfaces.md +17 -0
  270. package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/mappings.md +26 -0
  271. package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/proposal.md +35 -0
  272. package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/snow.md +12 -0
  273. package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/specs/transfer-limits/spec.md +19 -0
  274. package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/stories/s1-refuse-over-limit.md +17 -0
  275. package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/stories/s2-show-remaining.md +16 -0
  276. package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/stories/s3-audit-refusals.md +18 -0
  277. package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/stories/s4-velocity-check.md +16 -0
  278. package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/stories.md +8 -0
  279. package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/tasks.md +12 -0
  280. package/dist/fixtures/forge-change/openspec/changes/add-transfer-limit/ux.md +7 -0
  281. package/dist/fixtures/forge-change/org-pack/agents/code-and-test-critic.yaml +13 -0
  282. package/dist/fixtures/forge-change/org-pack/agents/complete-critic.yaml +12 -0
  283. package/dist/fixtures/forge-change/org-pack/agents/safe-critic.yaml +12 -0
  284. package/dist/fixtures/forge-change/org-pack/agents/ship-critic.yaml +11 -0
  285. package/dist/fixtures/forge-change/org-pack/agents/true-critic.yaml +11 -0
  286. package/dist/fixtures/forge-change/org-pack/checklists/review.yaml +54 -0
  287. package/dist/fixtures/forge-change/org-pack/gates.yaml +25 -0
  288. package/dist/fixtures/forge-change/org-pack/teams/team-ledger/checklists/ledger-team.yaml +227 -0
  289. package/dist/fixtures/forge-change/recordings/complete-critic/2ad84f84bf39c1d5412697e69fe23034d6983ec45a00314bf0975e627589a3c5.json +12 -0
  290. package/dist/fixtures/forge-change/recordings/complete-critic/417c98d8e54a4387d0fd93441c49f44c2868958739070d855c1dce0c41e24db3.json +12 -0
  291. package/dist/fixtures/forge-change/recordings/complete-critic/50203ee22e9cb3ef047295edb6538fe7d5695e1e4b139f3c929013dc6ce47021.json +12 -0
  292. package/dist/fixtures/forge-change/recordings/complete-critic/89f18d67f5d9c325bf35e2a9e020f9ddb6ee13b8523315037f0905cc57c040b1.json +12 -0
  293. package/dist/fixtures/forge-change/recordings/complete-critic/b4097867d26ae8eadc8b9efa8cdd86897013358035f0b40086c299a59c4ba121.json +12 -0
  294. package/dist/fixtures/forge-change/recordings/safe-critic/302d06f49664dd67ad2e21ab9cf74c1b0f7d3f750709c6ed8eff2b70983274e2.json +12 -0
  295. package/dist/fixtures/forge-change/recordings/safe-critic/32f1049c9a5ab4a759543290db4644ec6e221cc45fdfdcf06193837c6a8ce0b9.json +12 -0
  296. package/dist/fixtures/forge-change/recordings/safe-critic/4e4a84bfc04b9f26ca4250319a879901ff7a1c081c71e46a117675fee05026b0.json +12 -0
  297. package/dist/fixtures/forge-change/recordings/safe-critic/604af0fcdd6df6b76208fa88305fdc15c37606c2a4d1ae816477e46ac8c2dd04.json +12 -0
  298. package/dist/fixtures/forge-change/recordings/safe-critic/f0bf50af11f14c50d07904f0dffe1c8c20fb0cd89770645a8824c1d30a443955.json +12 -0
  299. package/dist/fixtures/forge-change/recordings/ship-critic/350954b58833b2c04880c67db45a1b328b7fd541d9f0d943b63905b8875488a6.json +12 -0
  300. package/dist/fixtures/forge-change/recordings/ship-critic/4af95c6c96a206777cec9de1f9c2d8c55a36ddc9b8db2f81d6eb83217b835efa.json +12 -0
  301. package/dist/fixtures/forge-change/recordings/ship-critic/76c34522a49cf7587a4e5689a5030d384603fa780b8aba97d27815cbc829683e.json +12 -0
  302. package/dist/fixtures/forge-change/recordings/ship-critic/a3b2cd1ace48587e5994333db03b34c3a0f9dec4f1932f1730ddfb687f6918c8.json +12 -0
  303. package/dist/fixtures/forge-change/recordings/ship-critic/de288a4fa64d0fc20890f9a787080ce6eac5203b7b9b72d884df11e8cf111835.json +12 -0
  304. package/dist/fixtures/forge-change/recordings/story-writer/1ddb00eac2aaf91a5ec5d9dfd220116be9f75aec6546a4a3a3b050ed89af2864.json +12 -0
  305. package/dist/packages/assistant-kit/frameworks-kit/README.md +86 -0
  306. package/dist/packages/assistant-kit/frameworks-kit/checklist.md +11 -0
  307. package/dist/packages/assistant-kit/frameworks-kit/examples/tide-lantern.yaml +56 -0
  308. package/dist/packages/assistant-kit/frameworks-kit/examples/tide-platform.yaml +43 -0
  309. package/dist/packages/assistant-kit/frameworks-kit/prompts/write-framework-model.md +12 -0
  310. package/dist/packages/assistant-kit/kit/README.md +98 -0
  311. package/dist/packages/assistant-kit/kit/c2-reference.md +334 -0
  312. package/dist/packages/assistant-kit/kit/checklist.md +39 -0
  313. package/dist/packages/assistant-kit/kit/examples/house-http-client.yaml +102 -0
  314. package/dist/packages/assistant-kit/kit/examples/tide-apigee.yaml +56 -0
  315. package/dist/packages/assistant-kit/kit/examples/tide-apim.yaml +32 -0
  316. package/dist/packages/assistant-kit/kit/examples/tide-kong.yaml +44 -0
  317. package/dist/packages/assistant-kit/kit/prompts/write-dialect.md +34 -0
  318. package/dist/packages/assistant-kit/kit/worked-example.md +198 -0
  319. package/dist/packages/conformance/sample/README.md +4 -0
  320. package/dist/packages/conformance/sample/inputs/notes.md +3 -0
  321. package/dist/packages/conformance/sample/inputs/systems.csv +3 -0
  322. package/dist/packages/conformance/sample/repo/api/greeter.proto +6 -0
  323. package/dist/packages/conformance/sample/repo/api/openapi.yaml +10 -0
  324. package/dist/packages/conformance/sample/repo/api/schema.graphql +3 -0
  325. package/dist/packages/conformance/sample/repo/db/V1__create_greeting.sql +4 -0
  326. package/dist/packages/conformance/sample/repo/package.json +7 -0
  327. package/dist/packages/conformance/sample/repo/src/main/java/sample/HelloController.java +10 -0
  328. package/dist/packages/conformance/sample/repo/web/app.ts +4 -0
  329. package/dist/packages/evals/datasets/critic-lenses.yaml +62 -0
  330. package/dist/packages/evals/datasets/dialect-writer.yaml +14 -0
  331. package/dist/packages/evals/datasets/feature-namer.yaml +34 -0
  332. package/dist/packages/evals/datasets/glossary-definer.yaml +25 -0
  333. package/dist/packages/evals/datasets/story-writer.yaml +13 -0
  334. package/dist/packages/evals/datasets/template-writer.yaml +16 -0
  335. package/dist/packages/evals/datasets/test-writer.yaml +18 -0
  336. package/dist/packages/executors/reference/reference-executor.mjs +233 -0
  337. package/dist/packages/extractors/dialects/argocd.yaml +45 -0
  338. package/dist/packages/extractors/dialects/fixtures/jaxrs/accounts-service/src/main/java/forge/accounts/AccountResource.java +47 -0
  339. package/dist/packages/extractors/dialects/fixtures/jaxrs/accounts-service/src/main/java/forge/accounts/AuditClient.java +12 -0
  340. package/dist/packages/extractors/dialects/fixtures/jaxrs/accounts-service/src/main/java/forge/accounts/LedgerClient.java +17 -0
  341. package/dist/packages/extractors/dialects/fixtures/micronaut/statements-service/src/main/java/forge/statements/LedgerClient.java +14 -0
  342. package/dist/packages/extractors/dialects/fixtures/micronaut/statements-service/src/main/java/forge/statements/RatesClient.java +11 -0
  343. package/dist/packages/extractors/dialects/fixtures/micronaut/statements-service/src/main/java/forge/statements/StatementController.java +36 -0
  344. package/dist/packages/extractors/dialects/fixtures/micronaut/statements-service/src/main/resources/application.yml +7 -0
  345. package/dist/packages/extractors/dialects/fixtures/quarkus-reactive-messaging/fraud-service/src/main/java/forge/fraud/FraudScorer.java +27 -0
  346. package/dist/packages/extractors/dialects/fixtures/quarkus-reactive-messaging/fraud-service/src/main/resources/application.properties +4 -0
  347. package/dist/packages/extractors/dialects/fixtures/terraform-aws/forge-aws-infra/envs/prod/api.tf +38 -0
  348. package/dist/packages/extractors/dialects/fixtures/terraform-aws/forge-aws-infra/envs/prod/main.tf +95 -0
  349. package/dist/packages/extractors/dialects/fixtures/terraform-aws/forge-aws-infra/envs/prod/terraform.tfvars +1 -0
  350. package/dist/packages/extractors/dialects/fixtures/terraform-aws/forge-aws-infra/envs/prod/variables.tf +4 -0
  351. package/dist/packages/extractors/dialects/fixtures/terraform-azure/forge-azure-infra/main.tf +102 -0
  352. package/dist/packages/extractors/dialects/fixtures/terraform-gcp/forge-gcp-infra/main.tf +51 -0
  353. package/dist/packages/extractors/dialects/harness.yaml +101 -0
  354. package/dist/packages/extractors/dialects/jaxrs.yaml +155 -0
  355. package/dist/packages/extractors/dialects/micronaut.yaml +207 -0
  356. package/dist/packages/extractors/dialects/quarkus-reactive-messaging.yaml +158 -0
  357. package/dist/packages/extractors/dialects/tekton.yaml +71 -0
  358. package/dist/packages/extractors/dialects/terraform-aws.yaml +267 -0
  359. package/dist/packages/extractors/dialects/terraform-azure.yaml +212 -0
  360. package/dist/packages/extractors/dialects/terraform-gcp.yaml +109 -0
  361. package/dist/packages/extractors/grammars/README.md +11 -0
  362. package/dist/packages/extractors/grammars/tree-sitter-groovy.LICENSE +21 -0
  363. package/dist/packages/extractors/grammars/tree-sitter-groovy.wasm +0 -0
  364. package/dist/packages/extractors/grammars/vendored.json +11 -0
  365. package/dist/packages/learn/lessons/architect-diagrams-and-code.yaml +69 -0
  366. package/dist/packages/learn/lessons/business-analyst-story-bar.yaml +94 -0
  367. package/dist/packages/learn/lessons/delivery-lead-release-readiness.yaml +44 -0
  368. package/dist/packages/learn/lessons/designer-features-in-user-words.yaml +57 -0
  369. package/dist/packages/learn/lessons/developer-find-your-way.yaml +60 -0
  370. package/dist/packages/learn/lessons/everyone-first-hour.yaml +74 -0
  371. package/dist/packages/learn/lessons/platform-lead-coverage-and-defences.yaml +58 -0
  372. package/dist/packages/learn/lessons/product-owner-first-change.yaml +167 -0
  373. package/dist/packages/learn/lessons/qa-tests-and-trace.yaml +53 -0
  374. package/dist/packages/learn/lessons/team-lead-initiative.yaml +146 -0
  375. package/dist/packages/naysayer/constitution/constitution.yaml +286 -0
  376. package/dist/packages/naysayer/starter/review.yaml +256 -0
  377. package/dist/packages/sdk/npm/index.js +8892 -0
  378. package/dist/packages/speckit/rules/drift.yaml +78 -0
  379. package/dist/packages/taskrun/rules/conform.yaml +38 -0
  380. package/dist/packages/testgen/rules/trace.yaml +49 -0
  381. package/dist/packages/ui/dist/assets/elk-api-tma0MCuh.js +2 -0
  382. package/dist/packages/ui/dist/assets/elk-worker.min--dAJroGl.js +6401 -0
  383. package/dist/packages/ui/dist/assets/elk-worker.min-DdYmS5kP.js +2 -0
  384. package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-400-normal-BTotfTJu.woff +0 -0
  385. package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-400-normal-DZqxrq2p.woff2 +0 -0
  386. package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-500-normal-ByOcLdNv.woff +0 -0
  387. package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-500-normal-CocWQlwt.woff2 +0 -0
  388. package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-600-normal-71GNu3SW.woff2 +0 -0
  389. package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-600-normal-BGq0mW3O.woff +0 -0
  390. package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-ext-400-normal-Dsrv2Tcn.woff +0 -0
  391. package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-ext-400-normal-g30qAdWV.woff2 +0 -0
  392. package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-ext-500-normal-Cs5J6C77.woff2 +0 -0
  393. package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-ext-500-normal-DB5PtV2g.woff +0 -0
  394. package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-ext-600-normal-Bz0x94Yp.woff +0 -0
  395. package/dist/packages/ui/dist/assets/ibm-plex-sans-cyrillic-ext-600-normal-DUMzJB7m.woff2 +0 -0
  396. package/dist/packages/ui/dist/assets/ibm-plex-sans-greek-400-normal-D9ESIMu3.woff +0 -0
  397. package/dist/packages/ui/dist/assets/ibm-plex-sans-greek-400-normal-_efipK4i.woff2 +0 -0
  398. package/dist/packages/ui/dist/assets/ibm-plex-sans-greek-500-normal-CuWXN6rf.woff +0 -0
  399. package/dist/packages/ui/dist/assets/ibm-plex-sans-greek-500-normal-JMMifIXV.woff2 +0 -0
  400. package/dist/packages/ui/dist/assets/ibm-plex-sans-greek-600-normal-D-CqTdkO.woff +0 -0
  401. package/dist/packages/ui/dist/assets/ibm-plex-sans-greek-600-normal-DzTrcv_p.woff2 +0 -0
  402. package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
  403. package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
  404. package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
  405. package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
  406. package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
  407. package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
  408. package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-ext-400-normal-C5H60-Va.woff2 +0 -0
  409. package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-ext-400-normal-RBey6euL.woff +0 -0
  410. package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-ext-500-normal-D0aIdm-b.woff +0 -0
  411. package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-ext-500-normal-DakdToA3.woff2 +0 -0
  412. package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-ext-600-normal-DIrixKbi.woff +0 -0
  413. package/dist/packages/ui/dist/assets/ibm-plex-sans-latin-ext-600-normal-DOrvGEcy.woff2 +0 -0
  414. package/dist/packages/ui/dist/assets/ibm-plex-sans-vietnamese-400-normal-DG4YqDda.woff2 +0 -0
  415. package/dist/packages/ui/dist/assets/ibm-plex-sans-vietnamese-400-normal-fK1oJ5dG.woff +0 -0
  416. package/dist/packages/ui/dist/assets/ibm-plex-sans-vietnamese-500-normal-BEb3_waV.woff +0 -0
  417. package/dist/packages/ui/dist/assets/ibm-plex-sans-vietnamese-500-normal-e4dixQRQ.woff2 +0 -0
  418. package/dist/packages/ui/dist/assets/ibm-plex-sans-vietnamese-600-normal-DgdngZtN.woff +0 -0
  419. package/dist/packages/ui/dist/assets/ibm-plex-sans-vietnamese-600-normal-DpPYBSTl.woff2 +0 -0
  420. package/dist/packages/ui/dist/assets/index-B54fm9FQ.js +29 -0
  421. package/dist/packages/ui/dist/assets/index-DD_hufQz.css +1 -0
  422. package/dist/packages/ui/dist/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  423. package/dist/packages/ui/dist/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  424. package/dist/packages/ui/dist/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
  425. package/dist/packages/ui/dist/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
  426. package/dist/packages/ui/dist/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  427. package/dist/packages/ui/dist/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  428. package/dist/packages/ui/dist/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
  429. package/dist/packages/ui/dist/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
  430. package/dist/packages/ui/dist/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  431. package/dist/packages/ui/dist/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  432. package/dist/packages/ui/dist/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
  433. package/dist/packages/ui/dist/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
  434. package/dist/packages/ui/dist/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  435. package/dist/packages/ui/dist/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  436. package/dist/packages/ui/dist/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
  437. package/dist/packages/ui/dist/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
  438. package/dist/packages/ui/dist/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  439. package/dist/packages/ui/dist/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
  440. package/dist/packages/ui/dist/assets/rajdhani-devanagari-600-normal-BtY48IV_.woff +0 -0
  441. package/dist/packages/ui/dist/assets/rajdhani-devanagari-600-normal-DhS7ScYx.woff2 +0 -0
  442. package/dist/packages/ui/dist/assets/rajdhani-devanagari-700-normal-BQQOj9BB.woff2 +0 -0
  443. package/dist/packages/ui/dist/assets/rajdhani-devanagari-700-normal-DBRwWuxA.woff +0 -0
  444. package/dist/packages/ui/dist/assets/rajdhani-latin-600-normal-CXCVEoA9.woff2 +0 -0
  445. package/dist/packages/ui/dist/assets/rajdhani-latin-600-normal-Dx02qI2_.woff +0 -0
  446. package/dist/packages/ui/dist/assets/rajdhani-latin-700-normal-BPDUZo87.woff2 +0 -0
  447. package/dist/packages/ui/dist/assets/rajdhani-latin-700-normal-e0Uj8ZlN.woff +0 -0
  448. package/dist/packages/ui/dist/assets/rajdhani-latin-ext-600-normal-BleIgkPp.woff +0 -0
  449. package/dist/packages/ui/dist/assets/rajdhani-latin-ext-600-normal-C9ZjJQAt.woff2 +0 -0
  450. package/dist/packages/ui/dist/assets/rajdhani-latin-ext-700-normal-BPaQ7tlD.woff +0 -0
  451. package/dist/packages/ui/dist/assets/rajdhani-latin-ext-700-normal-S6aYESQK.woff2 +0 -0
  452. package/dist/packages/ui/dist/favicon.svg +1 -0
  453. package/dist/packages/ui/dist/index.html +15 -0
  454. package/dist/third-party.json +575 -0
  455. package/dist/vendor/@fission-ai/openspec/bin/openspec.js +85986 -0
  456. package/dist/vendor/@fission-ai/openspec/package.json +82 -0
  457. package/dist/vendor/@fission-ai/openspec/schemas/spec-driven/schema.yaml +248 -0
  458. package/dist/vendor/@fission-ai/openspec/schemas/spec-driven/templates/design.md +21 -0
  459. package/dist/vendor/@fission-ai/openspec/schemas/spec-driven/templates/proposal.md +34 -0
  460. package/dist/vendor/@fission-ai/openspec/schemas/spec-driven/templates/spec.md +13 -0
  461. package/dist/vendor/@fission-ai/openspec/schemas/spec-driven/templates/tasks.md +11 -0
  462. package/dist/vendor/tree-sitter-wasms/out/tree-sitter-c_sharp.wasm +0 -0
  463. package/dist/vendor/tree-sitter-wasms/out/tree-sitter-go.wasm +0 -0
  464. package/dist/vendor/tree-sitter-wasms/out/tree-sitter-java.wasm +0 -0
  465. package/dist/vendor/tree-sitter-wasms/out/tree-sitter-javascript.wasm +0 -0
  466. package/dist/vendor/tree-sitter-wasms/out/tree-sitter-kotlin.wasm +0 -0
  467. package/dist/vendor/tree-sitter-wasms/out/tree-sitter-php.wasm +0 -0
  468. package/dist/vendor/tree-sitter-wasms/out/tree-sitter-python.wasm +0 -0
  469. package/dist/vendor/tree-sitter-wasms/out/tree-sitter-ruby.wasm +0 -0
  470. package/dist/vendor/tree-sitter-wasms/out/tree-sitter-rust.wasm +0 -0
  471. package/dist/vendor/tree-sitter-wasms/out/tree-sitter-scala.wasm +0 -0
  472. package/dist/vendor/tree-sitter-wasms/out/tree-sitter-tsx.wasm +0 -0
  473. package/dist/vendor/tree-sitter-wasms/out/tree-sitter-typescript.wasm +0 -0
  474. package/dist/vendor/web-tree-sitter/tree-sitter.wasm +0 -0
  475. package/package.json +36 -3
@@ -0,0 +1,3851 @@
1
+ import { createRequire as __atticusCreateRequire } from "node:module";
2
+ import { fileURLToPath as __atticusFileURLToPath } from "node:url";
3
+ var require = __atticusCreateRequire(import.meta.url);
4
+ var __atticusUrl = (p) => new URL("./" + p, import.meta.url).href;
5
+ var __atticusPath = (p) => __atticusFileURLToPath(__atticusUrl(p));
6
+ import {
7
+ dotInt8,
8
+ textSha
9
+ } from "./chunk-CNUKCS2T.js";
10
+ import {
11
+ Question,
12
+ RECORD_KINDS,
13
+ RecordId,
14
+ changesDirOf,
15
+ deserializeRecord,
16
+ identifierWords,
17
+ knowledgeDirOf,
18
+ knowledgeKindOf,
19
+ layoutOf,
20
+ orgPackDirOf,
21
+ parseRecord,
22
+ questionsDirOf,
23
+ recordReferences,
24
+ require_dist,
25
+ serializeRecord,
26
+ specsDirOf,
27
+ specsRelOf,
28
+ splitForms,
29
+ stateDirOf,
30
+ toCanonicalYaml
31
+ } from "./chunk-S2A7YC3Q.js";
32
+ import {
33
+ __toESM
34
+ } from "./chunk-TV45EUGM.js";
35
+
36
+ // packages/index/dist/query.js
37
+ var QueryError = class extends Error {
38
+ at;
39
+ constructor(message, at) {
40
+ super(message);
41
+ this.at = at;
42
+ this.name = "QueryError";
43
+ }
44
+ };
45
+ var FIELD = /^[A-Za-z][A-Za-z0-9]*(\.[A-Za-z][A-Za-z0-9]*)*$/;
46
+ var KINDS = new Map(RECORD_KINDS.map((k) => [k.toLowerCase(), k]));
47
+ function kindNamed(value) {
48
+ return KINDS.get(value.toLowerCase());
49
+ }
50
+ function tokens(text) {
51
+ const out = [];
52
+ let i = 0;
53
+ while (i < text.length) {
54
+ while (i < text.length && /\s/.test(text[i]))
55
+ i++;
56
+ if (i >= text.length)
57
+ break;
58
+ const at = i;
59
+ let quoted = false;
60
+ let t = "";
61
+ while (i < text.length && (quoted || !/\s/.test(text[i]))) {
62
+ if (text[i] === '"')
63
+ quoted = !quoted;
64
+ t += text[i];
65
+ i++;
66
+ }
67
+ if (quoted)
68
+ throw new QueryError(`A quoted value starting at ${at + 1} has no closing quote`, at);
69
+ out.push({ text: t, at });
70
+ }
71
+ return out;
72
+ }
73
+ function values(raw) {
74
+ const out = [];
75
+ let quoted = false;
76
+ let cur = "";
77
+ for (const c of raw) {
78
+ if (c === '"')
79
+ quoted = !quoted;
80
+ else if (c === "," && !quoted) {
81
+ out.push(cur);
82
+ cur = "";
83
+ } else
84
+ cur += c;
85
+ }
86
+ out.push(cur);
87
+ return out.filter((v) => v !== "");
88
+ }
89
+ function parseQuery(text) {
90
+ const terms = [];
91
+ for (const tok of tokens(text)) {
92
+ let t = tok.text;
93
+ const neg = t.startsWith("-");
94
+ if (neg)
95
+ t = t.slice(1);
96
+ if (t === "")
97
+ throw new QueryError(`There is nothing after "-" at ${tok.at + 1}`, tok.at);
98
+ const colon = t.startsWith('"') ? -1 : t.indexOf(":");
99
+ if (colon < 0) {
100
+ terms.push({ neg, type: "text", values: values(t) });
101
+ continue;
102
+ }
103
+ const name = t.slice(0, colon);
104
+ const rest = t.slice(colon + 1);
105
+ if (name === "has" || name === "missing") {
106
+ if (rest === "")
107
+ throw new QueryError(`"${name}:" needs a field, e.g. ${name}:owner`, tok.at);
108
+ if (!FIELD.test(rest))
109
+ throw new QueryError(`"${rest}" is not a field: field names are letters and digits, with "." for nested fields`, tok.at);
110
+ terms.push({ neg, type: name, field: rest });
111
+ continue;
112
+ }
113
+ const vs = values(rest);
114
+ if (vs.length === 0)
115
+ throw new QueryError(`"${name}:" needs a value, e.g. ${name}:something`, tok.at);
116
+ if (name === "refs" || name === "refby") {
117
+ terms.push({ neg, type: name, values: vs });
118
+ continue;
119
+ }
120
+ if (!FIELD.test(name))
121
+ throw new QueryError(`"${name}" is not a field: field names are letters and digits, with "." for nested fields`, tok.at);
122
+ if (name === "kind") {
123
+ for (const v of vs)
124
+ if (!v.includes("*") && !kindNamed(v))
125
+ throw new QueryError(`Unknown kind "${v}". Kinds: ${RECORD_KINDS.join(", ")}`, tok.at);
126
+ }
127
+ terms.push({ neg, type: "field", field: name, values: vs });
128
+ }
129
+ return terms;
130
+ }
131
+ function glob(v) {
132
+ return v.toLowerCase().replace(/[[?]/g, (c) => `[${c}]`);
133
+ }
134
+ var VALUE = `lower(CASE je.type WHEN 'true' THEN 'true' WHEN 'false' THEN 'false' ELSE CAST(je.value AS TEXT) END)`;
135
+ function compileQuery(terms) {
136
+ const parts = [];
137
+ const params = [];
138
+ for (const t of terms) {
139
+ let sql;
140
+ if (t.type === "field" && t.field === "kind") {
141
+ const ors = t.values.map((v) => {
142
+ params.push(glob(v));
143
+ return "lower(r.kind) GLOB ?";
144
+ });
145
+ sql = `(${ors.join(" OR ")})`;
146
+ } else if (t.type === "field" && t.field === "system") {
147
+ const ors = t.values.map((v) => {
148
+ params.push(glob(v));
149
+ return "lower(coalesce(r.system, '')) GLOB ?";
150
+ });
151
+ sql = `(${ors.join(" OR ")})`;
152
+ } else if (t.type === "field") {
153
+ params.push(`$.${t.field}`);
154
+ const ors = t.values.map((v) => {
155
+ params.push(glob(v));
156
+ return `${VALUE} GLOB ?`;
157
+ });
158
+ sql = `EXISTS (SELECT 1 FROM json_each(r.json, ?) je WHERE ${ors.join(" OR ")})`;
159
+ } else if (t.type === "has" || t.type === "missing") {
160
+ params.push(`$.${t.field}`);
161
+ sql = `EXISTS (SELECT 1 FROM json_each(r.json, ?) je WHERE je.type <> 'null')`;
162
+ if (t.type === "missing")
163
+ sql = `NOT ${sql}`;
164
+ } else if (t.type === "refs") {
165
+ sql = `EXISTS (SELECT 1 FROM refs x WHERE x.from_kind = r.kind AND x.from_id = r.id AND x.to_id IN (${t.values.map(() => "?").join(", ")}))`;
166
+ params.push(...t.values);
167
+ } else if (t.type === "refby") {
168
+ sql = `EXISTS (SELECT 1 FROM refs x WHERE x.to_id = r.id AND x.from_id IN (${t.values.map(() => "?").join(", ")}))`;
169
+ params.push(...t.values);
170
+ } else {
171
+ const ors = t.values.map((v) => {
172
+ params.push(`%${v.toLowerCase()}%`, `%${v.toLowerCase()}%`);
173
+ return "(lower(r.id) LIKE ? OR lower(r.name) LIKE ?)";
174
+ });
175
+ sql = `(${ors.join(" OR ")})`;
176
+ }
177
+ parts.push(t.neg ? `NOT ${sql}` : sql);
178
+ }
179
+ return { where: parts.length > 0 ? parts.join(" AND ") : "1", params };
180
+ }
181
+
182
+ // packages/index/dist/select.js
183
+ import { createHash as createHash2 } from "node:crypto";
184
+
185
+ // packages/index/dist/sources.js
186
+ var import_yaml = __toESM(require_dist(), 1);
187
+ import { readdir, readFile, stat } from "node:fs/promises";
188
+ import { dirname, join, relative, sep } from "node:path";
189
+
190
+ // packages/index/dist/gaps.js
191
+ var GAP_STEP = "context-gap";
192
+ function gapQuestion(name) {
193
+ return `The change names "${name}", and nothing Atticus has read says what it is: no catalog record, knowledge item, flow, wiki section or answered question. What is ${name}, who owns it, and where is it described?`;
194
+ }
195
+ function gapNameOf(q) {
196
+ if (q.askedBy.step !== GAP_STEP)
197
+ return void 0;
198
+ return /^The change names "([^"]+)"/.exec(q.question)?.[1];
199
+ }
200
+
201
+ // packages/index/dist/sources.js
202
+ var toPosix = (p) => p.split(sep).join("/");
203
+ var rel = (root, abs) => toPosix(relative(root, abs));
204
+ var str = (v) => typeof v === "string" && v.trim() ? v : void 0;
205
+ var arr = (v) => Array.isArray(v) ? v : [];
206
+ var foldName = (s) => s.replace(/[‘’]/g, "'").replace(/\s+/g, " ").trim().toLowerCase();
207
+ function endpointName(method, path) {
208
+ const p = path.replace(/\?.*$/, "").replace(/\{[^}]*\}/g, "{}").replace(/(.)\/$/, "$1");
209
+ return `${method.toLowerCase()} ${p.toLowerCase()}`;
210
+ }
211
+ async function walk(dir, test, out, skipDirs = /* @__PURE__ */ new Set()) {
212
+ let entries;
213
+ try {
214
+ entries = await readdir(dir, { withFileTypes: true });
215
+ } catch {
216
+ return;
217
+ }
218
+ for (const e of entries) {
219
+ const full = join(dir, e.name);
220
+ if (e.isDirectory()) {
221
+ if (!skipDirs.has(e.name))
222
+ await walk(full, test, out, skipDirs);
223
+ } else if (test(e.name))
224
+ out.push(full);
225
+ }
226
+ }
227
+ var YAML = (n) => /\.ya?ml$/.test(n);
228
+ var CATALOG_DIRS = ["catalog/generated", "catalog/curated"];
229
+ var INPUTS_SOURCE = "inputs";
230
+ async function inputsSkipped(kdir) {
231
+ const dir = join(kdir, "snapshots", INPUTS_SOURCE);
232
+ const newest = (await readdir(dir).catch(() => [])).filter((d) => /^\d{4}-\d{2}-\d{2}$/.test(d)).sort().at(-1);
233
+ if (!newest)
234
+ return /* @__PURE__ */ new Set();
235
+ try {
236
+ const m = (0, import_yaml.parse)(await readFile(join(dir, newest, "snapshot.yaml"), "utf8"));
237
+ return new Set((m?.skipped ?? []).map((x) => typeof x?.page === "string" ? x.page.replace(/\\/g, "/") : "").filter(Boolean).map((p) => p.startsWith("inputs/") ? p : `inputs/${p}`));
238
+ } catch {
239
+ return /* @__PURE__ */ new Set();
240
+ }
241
+ }
242
+ async function scannedPages(kdir) {
243
+ const out = /* @__PURE__ */ new Map();
244
+ try {
245
+ const r = (0, import_yaml.parse)(await readFile(join(kdir, "scan.yaml"), "utf8"));
246
+ for (const s of r?.snapshots ?? [])
247
+ if (typeof s?.source === "string" && s.hashes && typeof s.hashes === "object")
248
+ out.set(s.source, new Set(Object.keys(s.hashes)));
249
+ } catch {
250
+ }
251
+ return out;
252
+ }
253
+ async function wikiPagesToIndex(dir, source, scanned) {
254
+ let pages;
255
+ try {
256
+ const m = (0, import_yaml.parse)(await readFile(join(dir, "snapshot.yaml"), "utf8"));
257
+ pages = (m?.pages ?? []).flatMap((p) => typeof p?.page === "string" && typeof p.file === "string" ? [
258
+ {
259
+ page: p.page,
260
+ file: p.file.replace(/\\/g, "/"),
261
+ ...typeof p.in === "string" && /^\d{4}-\d{2}-\d{2}$/.test(p.in) ? { in: p.in } : {}
262
+ }
263
+ ] : []);
264
+ } catch {
265
+ return void 0;
266
+ }
267
+ const read = scanned.get(source);
268
+ const out = [];
269
+ for (const p of pages) {
270
+ if (read && !read.has(p.page))
271
+ continue;
272
+ const f = join(p.in ? join(dir, "..", p.in) : dir, ...p.file.split("/"));
273
+ if (await stat(f).catch(() => void 0))
274
+ out.push(f);
275
+ }
276
+ return out;
277
+ }
278
+ async function listSources(root) {
279
+ const found = [];
280
+ const add = (source) => (files) => found.push(...files.map((abs) => ({ abs, source })));
281
+ const catalog = layoutOf(root).catalog;
282
+ for (const d of CATALOG_DIRS) {
283
+ const files = [];
284
+ await walk(join(root, ...d.replace(/^catalog\//, `${catalog}/`).split("/")), YAML, files);
285
+ add("catalog")(files);
286
+ }
287
+ const kdir = knowledgeDirOf(root);
288
+ {
289
+ const files = [];
290
+ await walk(join(kdir, "items"), YAML, files);
291
+ add("knowledge")(files);
292
+ }
293
+ {
294
+ const files = [];
295
+ await walk(join(kdir, "reviews"), YAML, files);
296
+ add("review")(files);
297
+ }
298
+ {
299
+ const files = [];
300
+ await walk(specsDirOf(root), (n) => YAML(n) && n !== "spec.yaml" && n !== "findings.yaml", files);
301
+ add("flow")(files.filter((f) => dirname(f) !== specsDirOf(root)));
302
+ }
303
+ {
304
+ const dir = changesDirOf(root);
305
+ const ids = (await readdir(dir, { withFileTypes: true }).catch(() => [])).filter((d) => d.isDirectory() && d.name !== "archive").map((d) => d.name);
306
+ for (const id of ids) {
307
+ const files = [];
308
+ for (const f of ["change.yaml", "brief.md", "proposal.md", "feature.yaml", "needs.yaml"])
309
+ files.push(join(dir, id, f));
310
+ await walk(join(dir, id, "target-flows"), YAML, files);
311
+ await walk(join(dir, id, "inputs", "read"), (n) => n.endsWith(".md"), files);
312
+ const exist = [];
313
+ for (const f of files)
314
+ if (await stat(f).catch(() => void 0))
315
+ exist.push(f);
316
+ add("change")(exist);
317
+ }
318
+ const archive = join(dir, "archive");
319
+ for (const d of await readdir(archive, { withFileTypes: true }).catch(() => [])) {
320
+ if (!d.isDirectory())
321
+ continue;
322
+ for (const f of ["brief.md", "proposal.md"])
323
+ if (await stat(join(archive, d.name, f)).catch(() => void 0)) {
324
+ add("change")([join(archive, d.name, f)]);
325
+ break;
326
+ }
327
+ }
328
+ }
329
+ {
330
+ const files = [];
331
+ await walk(questionsDirOf(root), YAML, files);
332
+ add("question")(files);
333
+ }
334
+ {
335
+ const sdir = join(kdir, "snapshots");
336
+ const scanned = await scannedPages(kdir);
337
+ const sources = (await readdir(sdir, { withFileTypes: true }).catch(() => [])).filter((d) => d.isDirectory());
338
+ for (const s of sources) {
339
+ if (s.name === INPUTS_SOURCE)
340
+ continue;
341
+ const dates = (await readdir(join(sdir, s.name), { withFileTypes: true }).catch(() => [])).filter((d) => d.isDirectory() && /^\d{4}-\d{2}-\d{2}$/.test(d.name)).map((d) => d.name).sort();
342
+ const newest = dates.at(-1);
343
+ if (!newest)
344
+ continue;
345
+ const dir = join(sdir, s.name, newest);
346
+ const listed = await wikiPagesToIndex(dir, s.name, scanned);
347
+ if (listed)
348
+ add("wiki")(listed);
349
+ else {
350
+ const files = [];
351
+ await walk(dir, (n) => /\.(md|markdown)$/i.test(n), files);
352
+ add("wiki")(files);
353
+ }
354
+ }
355
+ }
356
+ {
357
+ const files = [];
358
+ await walk(join(orgPackDirOf(root), "profiles"), (n) => n === "profile.yaml" || n === "repos.manifest.yaml", files);
359
+ add("profile")(files);
360
+ }
361
+ {
362
+ const files = [];
363
+ await walk(join(root, "inputs"), (n) => DOCUMENT_EXTENSIONS.test(n), files);
364
+ const skipped = await inputsSkipped(kdir);
365
+ add("document")(files.filter((f) => !skipped.has(rel(root, f))));
366
+ }
367
+ const out = [];
368
+ const seen = /* @__PURE__ */ new Set();
369
+ for (const f of found) {
370
+ const path = rel(root, f.abs);
371
+ if (seen.has(path))
372
+ continue;
373
+ seen.add(path);
374
+ const s = await stat(f.abs).catch(() => void 0);
375
+ if (!s)
376
+ continue;
377
+ out.push({ path, source: f.source, size: s.size, mtimeMs: Math.floor(s.mtimeMs) });
378
+ }
379
+ return out.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
380
+ }
381
+ function labelOf(r) {
382
+ for (const f of ["name", "title", "term", "pathOrTopic", "key", "raw"])
383
+ if (typeof r[f] === "string" && r[f])
384
+ return r[f];
385
+ if (r.kind === "Interface")
386
+ return `${String(r.from)} \u2192 ${String(r.to)}${r.method ? ` ${String(r.method)}` : ""} ${String(r.normalizedPath ?? "")}`.trim();
387
+ return String(r.id);
388
+ }
389
+ function prose(v, out = [], depth = 0) {
390
+ if (depth > 6 || out.length > 400)
391
+ return out;
392
+ if (typeof v === "string")
393
+ out.push(v);
394
+ else if (Array.isArray(v))
395
+ for (const x of v)
396
+ prose(x, out, depth + 1);
397
+ else if (v && typeof v === "object") {
398
+ for (const [k, x] of Object.entries(v))
399
+ if (!["evidence", "sha256", "hash", "schemaVersion", "status", "confidence"].includes(k))
400
+ prose(x, out, depth + 1);
401
+ }
402
+ return out;
403
+ }
404
+ var idWords = (id) => `${id} ${id.replace(/[^A-Za-z0-9]+/g, " ")}`;
405
+ function cites(r) {
406
+ for (const e of arr(r.evidence)) {
407
+ const p = str(e.path);
408
+ if (p)
409
+ return `${e.repo ? `${String(e.repo)}/` : ""}${p}${typeof e.line === "number" ? `:${e.line}` : ""}`;
410
+ }
411
+ const q = r.quote;
412
+ if (q && str(q.page))
413
+ return `${String(q.source)}/${String(q.page)}${q.section ? `#${String(q.section)}` : ""}`;
414
+ return void 0;
415
+ }
416
+ var NAMED_KINDS = /* @__PURE__ */ new Set(["System", "Component", "Party", "Product", "DataStore", "Capability"]);
417
+ function recordLine(r, label) {
418
+ const k = String(r.kind);
419
+ const id = String(r.id);
420
+ let s;
421
+ if (k === "System")
422
+ s = `system ${id} "${label}"${str(r.boundary) ? ` (${String(r.boundary)})` : ""}${str(r.owner) ? `, owner ${String(r.owner)}` : ""}`;
423
+ else if (k === "Component")
424
+ s = `component ${id}: system ${str(r.system) ?? "?"}${str(r.repo) ? `, repo ${String(r.repo)}` : ""}${str(r.type) ? `, ${String(r.type)}` : ""}`;
425
+ else if (k === "Endpoint")
426
+ s = `endpoint ${id}: ${str(r.component) ?? "?"} serves ${str(r.method) ?? ""} ${str(r.pathOrTopic) ?? ""}`;
427
+ else if (k === "Interface")
428
+ s = `interface ${id}: ${String(r.from)} calls ${String(r.to)}${str(r.method) ? ` ${String(r.method)}` : ""} ${str(r.normalizedPath) ?? ""}${str(r.via) ? ` via ${String(r.via)}` : ""}`;
429
+ else {
430
+ const kk = knowledgeKindOf(r);
431
+ if (kk)
432
+ s = `${kk} ${id} "${label}"${str(r.appliesWhen) ? `: applies when ${String(r.appliesWhen)}` : ""}`;
433
+ else
434
+ s = `${k.toLowerCase()} ${id}${label !== id ? ` "${label}"` : ""}`;
435
+ }
436
+ const c = cites(r);
437
+ return `${s.replace(/ {2,}/g, " ").trim()}${c ? ` [${c}]` : ""}`;
438
+ }
439
+ function recordNode(r, file, grp) {
440
+ const x = r;
441
+ const label = labelOf(x);
442
+ const key = `${grp}:${r.kind}:${r.id}`;
443
+ const names = [{ name: foldName(r.id), id: r.id, kind: r.kind, how: "id" }];
444
+ const aliases = arr(x.aliases).filter((a) => typeof a === "string");
445
+ if (label !== r.id && (NAMED_KINDS.has(r.kind) || grp === "knowledge"))
446
+ names.push({ name: foldName(label), id: r.id, kind: r.kind, how: `name:${grp}` });
447
+ for (const a of aliases)
448
+ names.push({ name: foldName(a), id: r.id, kind: r.kind, how: `alias:${grp}` });
449
+ if (r.kind === "Endpoint" && str(x.method) && str(x.pathOrTopic))
450
+ names.push({
451
+ name: endpointName(String(x.method), String(x.pathOrTopic)),
452
+ id: r.id,
453
+ kind: r.kind,
454
+ how: "endpoint"
455
+ });
456
+ if (r.kind === "Interface" && str(x.method) && str(x.normalizedPath))
457
+ names.push({
458
+ name: endpointName(String(x.method), String(x.normalizedPath)),
459
+ id: r.id,
460
+ kind: r.kind,
461
+ how: "call"
462
+ });
463
+ const links = [];
464
+ for (const ref of recordReferences(r))
465
+ links.push({ from: key, to: ref.id, by: "id", type: "ref", how: `field:${ref.field}` });
466
+ if (r.kind === "GlossaryTerm") {
467
+ if (str(x.term) && foldName(String(x.term)) !== foldName(r.id))
468
+ names.push({ name: foldName(String(x.term)), id: r.id, kind: r.kind, how: `name:${grp}` });
469
+ for (const f of arr(x.fields))
470
+ if (typeof f === "string")
471
+ links.push({ from: key, to: f, by: "id", type: "defines", how: "field:fields" });
472
+ }
473
+ if (r.kind === "CuratedDecision" && x.action === "alias") {
474
+ for (const v of typeof x.value === "string" ? [x.value] : arr(x.value))
475
+ if (typeof v === "string" && v.trim())
476
+ names.push({ name: foldName(v), id: String(x.target), kind: "", how: "alias:curated" });
477
+ }
478
+ if (grp === "knowledge") {
479
+ for (const s of arr(x.systems))
480
+ if (typeof s === "string")
481
+ links.push({ from: key, to: s, by: "id", type: "names", how: "field:systems" });
482
+ const l = x.links;
483
+ if (l) {
484
+ for (const f of ["systems", "interfaces"])
485
+ for (const s of arr(l[f]))
486
+ if (typeof s === "string")
487
+ links.push({ from: key, to: s, by: "id", type: "names", how: `field:links.${f}` });
488
+ }
489
+ const quotes = [x.quote, ...arr(x.alsoOn)];
490
+ quotes.forEach((q, i) => {
491
+ if (q && str(q.source) && str(q.page))
492
+ links.push({
493
+ from: key,
494
+ to: sectionId(String(q.source), pageFile(String(q.page)), str(q.section) ?? ""),
495
+ by: "id",
496
+ type: "quotes",
497
+ how: i === 0 ? "field:quote" : "field:alsoOn"
498
+ });
499
+ });
500
+ }
501
+ const body = prose(x).filter((s) => s !== r.id && s !== label).join("\n").slice(0, 6e3);
502
+ return {
503
+ nodes: [
504
+ {
505
+ key,
506
+ kind: r.kind,
507
+ id: r.id,
508
+ grp,
509
+ name: label,
510
+ file,
511
+ verified: r.confidence === "verified",
512
+ line: recordLine(x, label),
513
+ detail: serializeRecord(r),
514
+ names: [label, ...aliases].join("\n"),
515
+ ids: idWords(r.id),
516
+ body,
517
+ mentions: grp === "knowledge",
518
+ json: JSON.stringify(r)
519
+ }
520
+ ],
521
+ names,
522
+ links
523
+ };
524
+ }
525
+ var pageFile = (page) => /\.(md|markdown)$/i.test(page) ? page : `${page}.md`;
526
+ var sectionId = (source, page, heading) => `${source}/${page}#${heading}`;
527
+ function flowNodes(text, file) {
528
+ let data;
529
+ try {
530
+ data = (0, import_yaml.parse)(text);
531
+ } catch (e) {
532
+ return { nodes: [], names: [], links: [], problem: e.message.split("\n")[0] };
533
+ }
534
+ const r = parseRecord(data);
535
+ if (!r.ok)
536
+ return {
537
+ nodes: [],
538
+ names: [],
539
+ links: [],
540
+ problem: r.issues.map((i) => `${i.path || "(top)"}: ${i.message}`).join("; ")
541
+ };
542
+ if (r.record.kind !== "Flow")
543
+ return { nodes: [], names: [], links: [], problem: `is a ${r.record.kind}, not a Flow` };
544
+ const f = r.record;
545
+ const key = `flow:Flow:${f.id}`;
546
+ const nodes = [];
547
+ const links = [
548
+ { from: key, to: f.component, by: "id", type: "ref", how: "field:component" }
549
+ ];
550
+ if (f.trigger.ref)
551
+ links.push({ from: key, to: f.trigger.ref, by: "id", type: "ref", how: "field:trigger.ref" });
552
+ if (f.trigger.caller?.ref)
553
+ links.push({
554
+ from: key,
555
+ to: f.trigger.caller.ref,
556
+ by: "id",
557
+ type: "ref",
558
+ how: "field:trigger.caller.ref"
559
+ });
560
+ const stepLines = [];
561
+ f.steps.forEach((s, i) => {
562
+ const n = i + 1;
563
+ const skey = `flow:FlowStep:${f.id}#${n}`;
564
+ const out = [{ from: skey, to: f.id, by: "id", type: "step-of", how: "flow step" }];
565
+ const add = (to, how, by = "id") => {
566
+ out.push({ from: skey, to, by, type: "calls", how: `step ${n} ${how}` });
567
+ links.push({ from: key, to, by, type: "calls", how: `step ${n} ${how}` });
568
+ };
569
+ for (const ref of s.refs)
570
+ add(ref, "field:refs");
571
+ if (s.to) {
572
+ add(s.to.component, "field:to.component");
573
+ const m = /^\s*([A-Za-z]+)\s+(\/\S*)/.exec(s.to.entry);
574
+ if (m)
575
+ add(endpointName(m[1], m[2]), "field:to.entry", "name");
576
+ else
577
+ add(s.to.entry, "field:to.entry");
578
+ }
579
+ if (s.store)
580
+ add(s.store.table, "field:store.table");
581
+ const fields = [
582
+ ...s.data?.sends ?? [],
583
+ ...s.data?.returns ?? [],
584
+ ...s.data?.reads ?? [],
585
+ ...s.store?.columns ?? []
586
+ ];
587
+ const where = s.to ? ` \u2192 ${s.to.component} ${s.to.entry}` : s.store ? ` ${s.store.table}` : "";
588
+ const line = `${n} ${s.kind}${where}${fields.length ? ` [${[...new Set(fields)].join(", ")}]` : ""}`;
589
+ stepLines.push(line);
590
+ const c = s.evidence[0];
591
+ nodes.push({
592
+ key: skey,
593
+ kind: "FlowStep",
594
+ id: `${f.id}#${n}`,
595
+ grp: "flow",
596
+ name: s.what,
597
+ file,
598
+ verified: !!s.confirmedBy,
599
+ line: `step ${f.id}#${n} ${s.kind}: ${s.what}${where}${c?.path ? ` [${c.repo ? `${c.repo}/` : ""}${c.path}${c.line ? `:${c.line}` : ""}]` : ""}`,
600
+ detail: `Step ${n} of flow ${f.id} (${file}):
601
+ ${toCanonicalYaml(s, ["kind", "what"])}`,
602
+ names: "",
603
+ ids: idWords(`${f.id}#${n}`),
604
+ body: [s.what, ...fields].join("\n"),
605
+ mentions: false
606
+ });
607
+ links.push(...out);
608
+ });
609
+ const allConfirmed = f.steps.every((s) => !!s.confirmedBy);
610
+ nodes.unshift({
611
+ key,
612
+ kind: "Flow",
613
+ id: f.id,
614
+ grp: "flow",
615
+ name: f.name,
616
+ file,
617
+ verified: allConfirmed,
618
+ line: `flow ${f.id} (component ${f.component}) "${f.name}": ${f.trigger.kind} ${f.trigger.what}. Steps: ${stepLines.join("; ")} [${file}]`,
619
+ detail: serializeRecord(f),
620
+ names: f.name,
621
+ ids: idWords(f.id),
622
+ body: [
623
+ f.trigger.what,
624
+ ...f.steps.map((s) => s.what),
625
+ ...f.errors.map((e) => `${e.when} ${e.then}`)
626
+ ].join("\n"),
627
+ mentions: false,
628
+ json: JSON.stringify(f)
629
+ });
630
+ return {
631
+ nodes,
632
+ names: [
633
+ { name: foldName(f.id), id: f.id, kind: "Flow", how: "id" },
634
+ { name: foldName(f.name), id: f.id, kind: "Flow", how: "name:flow" }
635
+ ],
636
+ links
637
+ };
638
+ }
639
+ var ASKED_ONLY_KINDS = /* @__PURE__ */ new Set(["PastChange"]);
640
+ function changeNodes(path, text) {
641
+ const parts = path.split("/");
642
+ const fileName = parts.slice(-1)[0];
643
+ if (parts.at(-3) === "archive") {
644
+ const id2 = parts.at(-2);
645
+ const title2 = /^#\s+(.+)$/m.exec(text)?.[1]?.trim() ?? id2;
646
+ return {
647
+ nodes: [
648
+ {
649
+ key: `change:PastChange:${id2}`,
650
+ kind: "PastChange",
651
+ id: id2,
652
+ grp: "change",
653
+ name: title2,
654
+ file: path,
655
+ verified: false,
656
+ line: `archived change ${id2} "${title2}" [${path}]`,
657
+ detail: `${path}:
658
+ ${text}`,
659
+ names: title2,
660
+ ids: idWords(id2),
661
+ body: text.slice(0, 2e4),
662
+ mentions: false
663
+ }
664
+ ],
665
+ names: [],
666
+ links: []
667
+ };
668
+ }
669
+ const inTargets = parts.at(-2) === "target-flows";
670
+ const inInputs = parts.at(-2) === "read" && parts.at(-3) === "inputs";
671
+ const change = inTargets ? parts.at(-3) : inInputs ? parts.at(-4) : parts.at(-2);
672
+ const ckey = `change:Change:${change}`;
673
+ const out = { nodes: [], names: [], links: [] };
674
+ if (inInputs) {
675
+ const input = `inputs/${fileName.replace(/\.md$/, "")}`;
676
+ const id2 = `${change}/${input}`;
677
+ const key2 = `change:ChangeDoc:${id2}`;
678
+ out.links.push({ from: key2, to: change, by: "id", type: "part-of", how: input });
679
+ out.nodes.push({
680
+ key: key2,
681
+ kind: "ChangeDoc",
682
+ id: id2,
683
+ grp: "change",
684
+ name: input,
685
+ file: path,
686
+ verified: false,
687
+ line: `${input} attached to change ${change} [${path}]`,
688
+ detail: `${path}:
689
+ ${text}`,
690
+ names: input,
691
+ ids: idWords(id2),
692
+ body: text.slice(0, 2e4),
693
+ mentions: true
694
+ });
695
+ return out;
696
+ }
697
+ const data = () => {
698
+ try {
699
+ const d = (0, import_yaml.parse)(text);
700
+ return d && typeof d === "object" ? d : void 0;
701
+ } catch (e) {
702
+ out.problem = e.message.split("\n")[0];
703
+ return void 0;
704
+ }
705
+ };
706
+ if (inTargets) {
707
+ const d = data();
708
+ if (d && str(d.flow))
709
+ out.links.push({
710
+ from: ckey,
711
+ to: String(d.flow),
712
+ by: "id",
713
+ type: "touches",
714
+ how: "target flow"
715
+ });
716
+ return out;
717
+ }
718
+ if (fileName === "change.yaml") {
719
+ const d = data();
720
+ if (!d)
721
+ return out;
722
+ const title2 = str(d.title) ?? change;
723
+ for (const s of arr(d.systems))
724
+ if (typeof s === "string")
725
+ out.links.push({
726
+ from: ckey,
727
+ to: s,
728
+ by: "id",
729
+ type: "touches",
730
+ how: "change.yaml systems"
731
+ });
732
+ const t = d.touches ?? {};
733
+ for (const f of ["interfaces", "tables", "dataStores", "records", "repos"])
734
+ for (const v of arr(t[f])) {
735
+ const id2 = typeof v === "string" ? v : str(v?.id);
736
+ if (id2)
737
+ out.links.push({
738
+ from: ckey,
739
+ to: id2,
740
+ by: "id",
741
+ type: "touches",
742
+ how: `change.yaml touches.${f}`
743
+ });
744
+ }
745
+ out.nodes.push({
746
+ key: ckey,
747
+ kind: "Change",
748
+ id: change,
749
+ grp: "change",
750
+ name: title2,
751
+ file: path,
752
+ verified: false,
753
+ line: `change ${change} "${title2}"${str(d.state) ? ` (${String(d.state)})` : ""} [${path}]`,
754
+ detail: `Change ${change} "${title2}" (${path}): state ${str(d.state) ?? "?"}; systems ${arr(d.systems).join(", ") || "none named"}.`,
755
+ names: title2,
756
+ ids: idWords(change),
757
+ body: [title2, str(d.type) ?? ""].join("\n"),
758
+ mentions: false
759
+ });
760
+ out.names.push({ name: foldName(change), id: change, kind: "Change", how: "id" });
761
+ return out;
762
+ }
763
+ if (fileName === "feature.yaml") {
764
+ const d = data();
765
+ if (!d)
766
+ return out;
767
+ for (const f of arr(d.flows)) {
768
+ const id2 = str(f?.id);
769
+ if (id2)
770
+ out.links.push({ from: ckey, to: id2, by: "id", type: "touches", how: "placement flows" });
771
+ }
772
+ for (const f of arr(d.features)) {
773
+ const id2 = str(f?.id);
774
+ if (id2)
775
+ out.links.push({
776
+ from: ckey,
777
+ to: id2,
778
+ by: "id",
779
+ type: "touches",
780
+ how: "placement features"
781
+ });
782
+ }
783
+ return out;
784
+ }
785
+ if (fileName === "needs.yaml") {
786
+ const d = data();
787
+ if (!d)
788
+ return out;
789
+ for (const n of arr(d.needs)) {
790
+ const item = str(n.item);
791
+ const target = str(n.for) ?? "change";
792
+ if (!item)
793
+ continue;
794
+ const id2 = `${change}:${item}:${target}`;
795
+ const key2 = `change:Need:${id2}`;
796
+ out.links.push({ from: key2, to: item, by: "id", type: "needs", how: "needs.yaml item" }, { from: key2, to: change, by: "id", type: "part-of", how: "needs.yaml" });
797
+ const flow = /^flow:(.+)$/.exec(target)?.[1];
798
+ if (flow)
799
+ out.links.push({ from: key2, to: flow, by: "id", type: "for", how: "needs.yaml for" });
800
+ const title2 = str(n.title) ?? item;
801
+ out.nodes.push({
802
+ key: key2,
803
+ kind: "Need",
804
+ id: id2,
805
+ grp: "change",
806
+ name: title2,
807
+ file: path,
808
+ verified: false,
809
+ line: `need ${item} for ${target} of ${change}: ${title2} (${str(n.status) ?? "open"}) [${path}]`,
810
+ detail: `Need ${item} for ${target} of change ${change} (${path}): ${title2}. Why: ${str(n.why) ?? ""}. Status: ${str(n.status) ?? "open"}.`,
811
+ names: title2,
812
+ ids: idWords(id2),
813
+ body: [str(n.why) ?? "", str(n.because?.quote) ?? ""].join("\n"),
814
+ mentions: false
815
+ });
816
+ }
817
+ return out;
818
+ }
819
+ const id = `${change}/${fileName}`;
820
+ const key = `change:ChangeDoc:${id}`;
821
+ const title = /^#\s+(.+)$/m.exec(text)?.[1]?.trim() ?? id;
822
+ out.links.push({ from: key, to: change, by: "id", type: "part-of", how: fileName });
823
+ out.nodes.push({
824
+ key,
825
+ kind: "ChangeDoc",
826
+ id,
827
+ grp: "change",
828
+ name: title,
829
+ file: path,
830
+ verified: false,
831
+ line: `${fileName} of change ${change}: ${title} [${path}]`,
832
+ detail: `${path}:
833
+ ${text}`,
834
+ names: title,
835
+ ids: idWords(id),
836
+ body: text.slice(0, 2e4),
837
+ mentions: true
838
+ });
839
+ return out;
840
+ }
841
+ function questionNodes(path, text) {
842
+ let q;
843
+ try {
844
+ const r = Question.safeParse((0, import_yaml.parse)(text));
845
+ if (!r.success)
846
+ return {
847
+ nodes: [],
848
+ names: [],
849
+ links: [],
850
+ problem: r.error.issues[0]?.message ?? "not a question"
851
+ };
852
+ q = r.data;
853
+ } catch (e) {
854
+ return { nodes: [], names: [], links: [], problem: e.message.split("\n")[0] };
855
+ }
856
+ if (q.state !== "answered" || !q.answer)
857
+ return { nodes: [], names: [], links: [] };
858
+ const a = q.answer;
859
+ const picked = a.option !== void 0 ? q.options[a.option - 1] : void 0;
860
+ const answer = picked ? `${picked.label}: ${picked.means}` : a.text ?? "";
861
+ const key = `question:Question:${q.id}`;
862
+ const gap = gapNameOf(q);
863
+ return {
864
+ nodes: [
865
+ {
866
+ key,
867
+ kind: "Question",
868
+ id: q.id,
869
+ grp: "question",
870
+ name: q.question,
871
+ file: path,
872
+ verified: false,
873
+ line: `answered question ${q.id}: ${q.question} Answer: ${answer} (${a.by}, ${a.at}) [${path}]`,
874
+ detail: `Question ${q.id} (${path}), asked by ${q.askedBy.step} about repo ${q.repo}: ${q.question}
875
+ Answer, by ${a.by} at ${a.at}: ${answer}
876
+ Evidence: ${q.evidence.join(", ")}`,
877
+ names: "",
878
+ ids: q.id,
879
+ body: [q.question, answer].join("\n"),
880
+ mentions: true
881
+ }
882
+ ],
883
+ // An answered gap (ADR 0107 point 5): the name it was about is known from now on, by this answer.
884
+ // An endpoint is also named as the selection looks endpoints up (U2): method, path, {} params.
885
+ names: gap ? answeredGapNames(gap).map((name) => ({
886
+ name,
887
+ id: q.id,
888
+ kind: "Question",
889
+ how: "answered gap"
890
+ })) : [],
891
+ links: [{ from: key, to: q.repo, by: "id", type: "about", how: "question repo" }]
892
+ };
893
+ }
894
+ function answeredGapNames(gap) {
895
+ const m = /^\s*(GET|POST|PUT|PATCH|DELETE)\s+(\/\S*)\s*$/i.exec(gap);
896
+ return [.../* @__PURE__ */ new Set([foldName(gap), ...m ? [endpointName(m[1], m[2])] : []])];
897
+ }
898
+ function wikiNodes(root, path, text) {
899
+ const k = rel(root, knowledgeDirOf(root));
900
+ const rest = path.slice(k.length + "/snapshots/".length).split("/");
901
+ const source = rest[0];
902
+ const date = rest[1];
903
+ const page = rest.slice(2).join("/");
904
+ const lines = text.replace(/\r\n/g, "\n").split("\n");
905
+ const sections = [
906
+ { heading: "", level: 0, line: 1, text: [] }
907
+ ];
908
+ if (lines[0]?.trim() === "---") {
909
+ const end = lines.findIndex((l, k2) => k2 > 0 && (l.trim() === "---" || l.trim() === "..."));
910
+ for (let k2 = 0; k2 <= end; k2++)
911
+ lines[k2] = "";
912
+ }
913
+ let fence;
914
+ lines.forEach((l, i) => {
915
+ const f = /^ {0,3}(```+|~~~+)/.exec(l);
916
+ if (f)
917
+ fence = fence ? l.trim().startsWith(fence) ? void 0 : fence : f[1];
918
+ const m = fence || f ? void 0 : /^(#{1,6})\s+(.+?)\s*#*\s*$/.exec(l);
919
+ if (m)
920
+ sections.push({ heading: m[2], level: m[1].length, line: i + 1, text: [] });
921
+ else
922
+ sections.at(-1).text.push(l);
923
+ });
924
+ const out = { nodes: [], names: [], links: [] };
925
+ const used = /* @__PURE__ */ new Set();
926
+ const stack = [];
927
+ for (const s of sections) {
928
+ if (s.level > 0) {
929
+ stack.length = s.level - 1;
930
+ while (stack.length < s.level - 1)
931
+ stack.push("");
932
+ stack.push(s.heading);
933
+ }
934
+ const path_ = stack.filter(Boolean).join(" \u203A ");
935
+ const paras = [];
936
+ let at = s.level > 0 ? s.line + 1 : 1;
937
+ let cur;
938
+ for (const l of s.text) {
939
+ if (l.trim())
940
+ (cur ??= { line: at, text: [] }).text.push(l);
941
+ else if (cur) {
942
+ paras.push({ line: cur.line, text: cur.text.join("\n") });
943
+ cur = void 0;
944
+ }
945
+ at++;
946
+ }
947
+ if (cur)
948
+ paras.push({ line: cur.line, text: cur.text.join("\n") });
949
+ if (!paras.length)
950
+ continue;
951
+ const id = sectionId(source, page, s.heading);
952
+ if (used.has(id))
953
+ continue;
954
+ used.add(id);
955
+ const parts = partsOf(paras);
956
+ parts.forEach((p, i) => {
957
+ const pid = i === 0 ? id : `${id}#${i + 1}`;
958
+ const line = i === 0 ? s.line : p.line;
959
+ const of = parts.length > 1 ? ` (part ${i + 1} of ${parts.length})` : "";
960
+ const name = path_ || page;
961
+ out.nodes.push({
962
+ key: `wiki:WikiSection:${pid}`,
963
+ kind: "WikiSection",
964
+ id: pid,
965
+ grp: "wiki",
966
+ name,
967
+ file: path,
968
+ verified: false,
969
+ line: `wiki ${source}/${page}${path_ ? ` \xA7 ${path_}` : ""}${of} (snapshot ${date}) [${path}:${line}]`,
970
+ detail: `${source}/${page}${path_ ? ` \u203A ${path_}` : ""}${of}, snapshot ${date} (${path}:${line}):
971
+ ${p.text}`,
972
+ names: path_,
973
+ ids: idWords(page),
974
+ body: p.text,
975
+ mentions: true
976
+ });
977
+ if (i > 0)
978
+ out.links.push({
979
+ from: `wiki:WikiSection:${pid}`,
980
+ to: id,
981
+ by: "id",
982
+ type: "part-of",
983
+ how: `part ${i + 1}`
984
+ });
985
+ });
986
+ }
987
+ return out;
988
+ }
989
+ var PART_CHARS = 1600;
990
+ function partsOf(paras) {
991
+ const parts = [];
992
+ for (const p of paras) {
993
+ const last = parts.at(-1);
994
+ if (last && last.text.length + 2 + p.text.length <= PART_CHARS)
995
+ last.text = `${last.text}
996
+
997
+ ${p.text}`;
998
+ else
999
+ parts.push({ line: p.line, text: p.text });
1000
+ }
1001
+ return parts;
1002
+ }
1003
+ var DOCUMENT_EXTENSIONS = /\.(docx|pptx|xlsx|pdf|md|markdown|html?|txt)$/i;
1004
+ async function documentNodes(path, bytes) {
1005
+ const out = { nodes: [], names: [], links: [] };
1006
+ const [{ readDocument }, { screenFile }] = await Promise.all([
1007
+ import("./chunk-4NDAAFQT.js"),
1008
+ import("./chunk-6JCPI55B.js")
1009
+ ]);
1010
+ const screen = screenFile(path, bytes);
1011
+ if (screen.problem)
1012
+ return { ...out, problem: screen.problem };
1013
+ if (screen.screening && (screen.screening.risk === "medium" || screen.screening.risk === "high"))
1014
+ return {
1015
+ ...out,
1016
+ problem: `kept out of the index: the ingested-content defences found ${screen.screening.risk} risk (${screen.screening.reason})`
1017
+ };
1018
+ let read;
1019
+ try {
1020
+ read = readDocument(path, bytes);
1021
+ } catch (e) {
1022
+ return { ...out, problem: e.message };
1023
+ }
1024
+ const title = read.text.title;
1025
+ const stack = [];
1026
+ const used = /* @__PURE__ */ new Set();
1027
+ for (const s of read.text.sections) {
1028
+ if (s.level > 0) {
1029
+ stack.length = s.level - 1;
1030
+ while (stack.length < s.level - 1)
1031
+ stack.push("");
1032
+ stack.push(s.heading);
1033
+ }
1034
+ const heading = s.heading || title;
1035
+ const trail = [title, ...stack.filter(Boolean)].filter((h, i, all) => h && all.indexOf(h) === i);
1036
+ const name = (s.level > 0 ? trail : [heading]).join(" \u203A ");
1037
+ const paras = s.text.split(/\n/).map((t, i) => ({ line: (s.line ?? 0) + i + 1, text: t.trim() })).filter((p) => p.text);
1038
+ if (!paras.length)
1039
+ continue;
1040
+ const id = `${path}#${heading}`;
1041
+ if (used.has(id))
1042
+ continue;
1043
+ used.add(id);
1044
+ const parts = partsOf(paras);
1045
+ parts.forEach((p, i) => {
1046
+ const pid = i === 0 ? id : `${id}#${i + 1}`;
1047
+ const of = parts.length > 1 ? ` (part ${i + 1} of ${parts.length})` : "";
1048
+ const where = s.page ? `, page ${s.page}` : s.line !== void 0 ? `, line ${i === 0 ? s.line : p.line}` : "";
1049
+ const cite = `${path} \xA7 ${heading}${where}`;
1050
+ out.nodes.push({
1051
+ key: `document:DocumentSection:${pid}`,
1052
+ kind: "DocumentSection",
1053
+ id: pid,
1054
+ grp: "document",
1055
+ name,
1056
+ file: path,
1057
+ verified: false,
1058
+ line: `document ${path} \xA7 ${name}${of} [${cite}]`,
1059
+ detail: `${path} \u203A ${name}${of} (${cite}):
1060
+ ${p.text}`,
1061
+ names: name,
1062
+ ids: idWords(path),
1063
+ body: p.text,
1064
+ mentions: true
1065
+ });
1066
+ if (i > 0)
1067
+ out.links.push({
1068
+ from: `document:DocumentSection:${pid}`,
1069
+ to: id,
1070
+ by: "id",
1071
+ type: "part-of",
1072
+ how: `part ${i + 1}`
1073
+ });
1074
+ });
1075
+ }
1076
+ return out;
1077
+ }
1078
+ function manifestNodes(path, text) {
1079
+ let d;
1080
+ try {
1081
+ d = (0, import_yaml.parse)(text) ?? {};
1082
+ } catch (e) {
1083
+ return { nodes: [], names: [], links: [], problem: e.message.split("\n")[0] };
1084
+ }
1085
+ const out = { nodes: [], names: [], links: [] };
1086
+ const platform = str(d.platform);
1087
+ for (const r of arr(d.repos)) {
1088
+ const id = str(r.id);
1089
+ if (!id)
1090
+ continue;
1091
+ const about = r.description ?? void 0;
1092
+ const text2 = str(about?.text);
1093
+ if (!text2)
1094
+ continue;
1095
+ const suggested = str(about?.trust) === "suggested";
1096
+ const what = `${text2}${suggested ? " (unconfirmed: drafted by the assistant)" : ""}`;
1097
+ const where = [
1098
+ str(r.system) ? `system ${String(r.system)}` : void 0,
1099
+ str(r.component) ? `component ${String(r.component)}` : void 0,
1100
+ str(r.type) ? String(r.type) : void 0,
1101
+ str(r.owner) ? `owner ${String(r.owner)}` : void 0
1102
+ ].filter(Boolean).join(", ");
1103
+ out.nodes.push({
1104
+ key: `org-pack:Repo:${id}`,
1105
+ kind: "Repo",
1106
+ id,
1107
+ grp: "org-pack",
1108
+ name: id,
1109
+ file: path,
1110
+ verified: false,
1111
+ line: `repo ${id}${platform ? ` (${platform})` : ""}${where ? `: ${where}` : ""} \u2014 ${what} [${path}]`,
1112
+ detail: `Repo ${id} of the ${platform ?? "?"} platform (${path})${where ? `: ${where}` : ""}. What it is, in a person's words: ${what}. That is a note, not read from the code.`,
1113
+ names: [id, text2].join("\n"),
1114
+ ids: idWords(id),
1115
+ body: [what, where].filter(Boolean).join("\n"),
1116
+ mentions: false
1117
+ });
1118
+ out.names.push({ name: foldName(id), id, kind: "Repo", how: "id" });
1119
+ for (const link of [str(r.system), str(r.component)])
1120
+ if (link)
1121
+ out.links.push({
1122
+ from: `org-pack:Repo:${id}`,
1123
+ to: link,
1124
+ by: "id",
1125
+ type: "part-of",
1126
+ how: "manifest"
1127
+ });
1128
+ }
1129
+ return out;
1130
+ }
1131
+ function profileNodes(path, text) {
1132
+ if (path.endsWith("repos.manifest.yaml"))
1133
+ return manifestNodes(path, text);
1134
+ let d;
1135
+ try {
1136
+ d = (0, import_yaml.parse)(text) ?? {};
1137
+ } catch (e) {
1138
+ return { nodes: [], names: [], links: [], problem: e.message.split("\n")[0] };
1139
+ }
1140
+ const out = { nodes: [], names: [], links: [] };
1141
+ for (const s of arr(d.systems)) {
1142
+ const id = str(s.id);
1143
+ if (!id)
1144
+ continue;
1145
+ const name = str(s.name) ?? id;
1146
+ const aliases = arr(s.aliases).filter((a) => typeof a === "string");
1147
+ out.nodes.push({
1148
+ key: `org-pack:System:${id}`,
1149
+ kind: "System",
1150
+ id,
1151
+ grp: "org-pack",
1152
+ name,
1153
+ file: path,
1154
+ verified: false,
1155
+ line: `system ${id} "${name}"${str(s.boundary) ? ` (${String(s.boundary)})` : ""}${str(s.owner) ? `, owner ${String(s.owner)}` : ""}${aliases.length ? `, also called ${aliases.join(", ")}` : ""} [${path}]`,
1156
+ detail: `System ${id} "${name}" in the org pack profile (${path}): boundary ${str(s.boundary) ?? "?"}${str(s.owner) ? `, owner ${String(s.owner)}` : ""}${aliases.length ? `; also called ${aliases.join(", ")}` : ""}.`,
1157
+ names: [name, ...aliases].join("\n"),
1158
+ ids: idWords(id),
1159
+ body: [str(s.boundary) ?? "", str(s.owner) ?? ""].join("\n"),
1160
+ mentions: false
1161
+ });
1162
+ out.names.push({ name: foldName(id), id, kind: "System", how: "id" }, { name: foldName(name), id, kind: "System", how: "name:org-pack" }, ...aliases.map((a) => ({ name: foldName(a), id, kind: "System", how: "alias:org-pack" })));
1163
+ }
1164
+ return out;
1165
+ }
1166
+ function readSource(root, f, text) {
1167
+ switch (f.source) {
1168
+ case "catalog": {
1169
+ const r = deserializeRecord(text);
1170
+ if (!r.ok)
1171
+ return {
1172
+ nodes: [],
1173
+ names: [],
1174
+ links: [],
1175
+ problem: r.issues.map((i) => `${i.path}: ${i.message}`).join("; ")
1176
+ };
1177
+ return { ...recordNode(r.record, f.path, "catalog"), record: r.record };
1178
+ }
1179
+ case "knowledge": {
1180
+ const r = deserializeRecord(text);
1181
+ if (!r.ok)
1182
+ return {
1183
+ nodes: [],
1184
+ names: [],
1185
+ links: [],
1186
+ problem: r.issues.map((i) => `${i.path}: ${i.message}`).join("; ")
1187
+ };
1188
+ if (!knowledgeKindOf(r.record))
1189
+ return { nodes: [], names: [], links: [] };
1190
+ return recordNode(r.record, f.path, "knowledge");
1191
+ }
1192
+ case "review": {
1193
+ try {
1194
+ const d = (0, import_yaml.parse)(text);
1195
+ if (!str(d?.item) || !str(d?.verdict))
1196
+ return { nodes: [], names: [], links: [], problem: "not a review" };
1197
+ return {
1198
+ nodes: [],
1199
+ names: [],
1200
+ links: [],
1201
+ review: {
1202
+ item: String(d.item),
1203
+ verdict: String(d.verdict),
1204
+ by: str(d.by) ?? "",
1205
+ at: str(d.at) ?? ""
1206
+ }
1207
+ };
1208
+ } catch (e) {
1209
+ return { nodes: [], names: [], links: [], problem: e.message.split("\n")[0] };
1210
+ }
1211
+ }
1212
+ case "flow":
1213
+ return flowNodes(text, f.path);
1214
+ case "change":
1215
+ return changeNodes(f.path, text);
1216
+ case "question":
1217
+ return questionNodes(f.path, text);
1218
+ case "wiki":
1219
+ return wikiNodes(root, f.path, text);
1220
+ case "profile":
1221
+ return profileNodes(f.path, text);
1222
+ case "document":
1223
+ return { nodes: [], names: [], links: [] };
1224
+ }
1225
+ }
1226
+
1227
+ // packages/index/dist/store.js
1228
+ import { createHash } from "node:crypto";
1229
+ import { readFile as readFile2 } from "node:fs/promises";
1230
+ import { join as join2 } from "node:path";
1231
+
1232
+ // packages/index/dist/vectors.js
1233
+ var VECTORS_SCHEMA = `
1234
+ CREATE TABLE IF NOT EXISTS vec.vectors (
1235
+ model TEXT NOT NULL, text_sha TEXT NOT NULL, kind TEXT NOT NULL, dim INTEGER NOT NULL,
1236
+ scale REAL NOT NULL, q BLOB NOT NULL, PRIMARY KEY (model, text_sha, kind)
1237
+ );
1238
+ `;
1239
+ function nodeText(n) {
1240
+ const words = (s) => identifierWords(s).join(" ");
1241
+ const id = n.ids.split(" ")[0] ?? "";
1242
+ return `${n.kind}: ${words(n.name) || n.name}. ${id.includes("/") ? "" : words(id)}. ${n.body}`.replace(/\s+/g, " ").slice(0, 2e3);
1243
+ }
1244
+ function attachVectors(db, file) {
1245
+ db.exec(`ATTACH DATABASE '${file.replace(/'/g, "''")}' AS vec`);
1246
+ db.exec(VECTORS_SCHEMA);
1247
+ }
1248
+ function meaningState(db, embedder, why) {
1249
+ const total = db.prepare("SELECT count(*) AS n FROM node_text").get().n;
1250
+ if (!embedder)
1251
+ return {
1252
+ on: false,
1253
+ embedded: 0,
1254
+ total,
1255
+ note: `similar in meaning is off: ${why ?? "no embedding model"}`
1256
+ };
1257
+ const embedded = db.prepare("SELECT count(*) AS n FROM node_text t JOIN vec.vectors v ON v.model = ? AND v.text_sha = t.text_sha AND v.kind = 'document'").get(embedder.id).n;
1258
+ const on = total > 0 && embedded === total;
1259
+ return {
1260
+ on,
1261
+ model: embedder.id,
1262
+ embedded,
1263
+ total,
1264
+ note: on ? `similar in meaning by ${embedder.id} (${total} nodes)` : `similar in meaning is off until every node is embedded: ${embedded} of ${total} by ${embedder.id} (atticus index embed)`
1265
+ };
1266
+ }
1267
+ var toBlob = (v) => Buffer.from(v.q.buffer, v.q.byteOffset, v.q.byteLength);
1268
+ function store(db, embedder, kind, shas, vs) {
1269
+ const ins = db.prepare("INSERT OR REPLACE INTO vec.vectors VALUES (?, ?, ?, ?, ?, ?)");
1270
+ db.exec("BEGIN IMMEDIATE");
1271
+ try {
1272
+ vs.forEach((v, i) => ins.run(embedder.id, shas[i], kind, v.q.length, v.scale, toBlob(v)));
1273
+ db.exec("COMMIT");
1274
+ } catch (e) {
1275
+ db.exec("ROLLBACK");
1276
+ throw e;
1277
+ }
1278
+ }
1279
+ async function embedMissing(db, embedder, o = {}) {
1280
+ const rows = db.prepare(`SELECT DISTINCT t.text_sha AS sha, t.text AS text FROM node_text t
1281
+ WHERE NOT EXISTS (SELECT 1 FROM vec.vectors v WHERE v.model = ? AND v.text_sha = t.text_sha AND v.kind = 'document')
1282
+ ORDER BY t.text_sha`).all(embedder.id);
1283
+ const todo = rows.slice(0, o.max ?? rows.length);
1284
+ const batch = o.batch ?? 16;
1285
+ for (let i = 0; i < todo.length; i += batch) {
1286
+ const part = todo.slice(i, i + batch);
1287
+ const vs = await embedder.embed(part.map((r) => r.text), "document");
1288
+ store(db, embedder, "document", part.map((r) => r.sha), vs);
1289
+ o.progress?.(Math.min(i + batch, todo.length), todo.length);
1290
+ }
1291
+ return todo.length;
1292
+ }
1293
+ async function queryVectors(db, embedder, texts) {
1294
+ const shas = texts.map(textSha);
1295
+ const missing = texts.filter((_, i) => !cachedQuery(db, embedder.id, shas[i]));
1296
+ if (missing.length) {
1297
+ const vs = await embedder.embed(missing, "query");
1298
+ store(db, embedder, "query", missing.map(textSha), vs);
1299
+ }
1300
+ return shas.map((s) => cachedQuery(db, embedder.id, s));
1301
+ }
1302
+ function cachedQuery(db, model, sha2) {
1303
+ const r = db.prepare("SELECT scale, q FROM vec.vectors WHERE model = ? AND text_sha = ? AND kind = 'query'").get(model, sha2);
1304
+ return r ? { scale: r.scale, q: new Int8Array(r.q.buffer, r.q.byteOffset, r.q.byteLength) } : void 0;
1305
+ }
1306
+ function loadMatrix(db, model) {
1307
+ const rows = db.prepare(`SELECT t.key AS key, n.kind AS kind, v.scale AS scale, v.q AS q, v.dim AS dim FROM node_text t
1308
+ JOIN nodes n ON n.key = t.key
1309
+ JOIN vec.vectors v ON v.model = ? AND v.text_sha = t.text_sha AND v.kind = 'document'
1310
+ ORDER BY t.key`).all(model);
1311
+ const dim = rows[0]?.dim ?? 0;
1312
+ const q = new Int8Array(rows.length * dim);
1313
+ const scales = new Float64Array(rows.length);
1314
+ rows.forEach((r, i) => {
1315
+ q.set(new Int8Array(r.q.buffer, r.q.byteOffset, r.q.byteLength), i * dim);
1316
+ scales[i] = r.scale;
1317
+ });
1318
+ return { keys: rows.map((r) => r.key), kinds: rows.map((r) => r.kind), scales, q, dim };
1319
+ }
1320
+ function nearest(m, queries, o) {
1321
+ const kinds = o.kinds?.length ? new Set(o.kinds) : void 0;
1322
+ const out = [];
1323
+ for (let i = 0; i < m.keys.length; i++) {
1324
+ if (kinds ? !kinds.has(m.kinds[i]) : ASKED_ONLY_KINDS.has(m.kinds[i]))
1325
+ continue;
1326
+ if (o.exclude?.has(m.keys[i]))
1327
+ continue;
1328
+ let best = -Infinity;
1329
+ for (const qv of queries) {
1330
+ const c = dotInt8(qv.q, m.q, i * m.dim) * qv.scale * m.scales[i];
1331
+ if (c > best)
1332
+ best = c;
1333
+ }
1334
+ if (best >= o.floor)
1335
+ out.push({ key: m.keys[i], cos: Math.round(best * 1e4) / 1e4 });
1336
+ }
1337
+ return out.sort((a, b) => b.cos - a.cos || (a.key < b.key ? -1 : a.key > b.key ? 1 : 0)).slice(0, o.limit);
1338
+ }
1339
+ function cosines(m, rows, queries, keys) {
1340
+ const out = /* @__PURE__ */ new Map();
1341
+ for (const key of keys) {
1342
+ const i = rows.get(key);
1343
+ if (i === void 0 || out.has(key))
1344
+ continue;
1345
+ let best = -Infinity;
1346
+ for (const qv of queries) {
1347
+ const c = dotInt8(qv.q, m.q, i * m.dim) * qv.scale * m.scales[i];
1348
+ if (c > best)
1349
+ best = c;
1350
+ }
1351
+ if (best > -Infinity)
1352
+ out.set(key, Math.round(best * 1e4) / 1e4);
1353
+ }
1354
+ return out;
1355
+ }
1356
+ function queryChunks(input) {
1357
+ const out = [];
1358
+ const push = (s) => {
1359
+ const t = s.replace(/\s+/g, " ").trim().slice(0, 1e3);
1360
+ if (t.length >= 3 && !out.includes(t))
1361
+ out.push(t);
1362
+ };
1363
+ if (input.title)
1364
+ push(input.title);
1365
+ for (const p of input.text.split(/\n\s*\n/))
1366
+ push(p.replace(/^#+\s*/gm, ""));
1367
+ return out.slice(0, 20);
1368
+ }
1369
+
1370
+ // packages/index/dist/store.js
1371
+ var INDEX_VERSION = 5;
1372
+ var SCHEMA = `
1373
+ CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
1374
+ CREATE TABLE files (path TEXT PRIMARY KEY, source TEXT NOT NULL, size INTEGER NOT NULL, mtime INTEGER NOT NULL, hash TEXT NOT NULL);
1375
+ CREATE TABLE records (
1376
+ kind TEXT NOT NULL, id TEXT NOT NULL, status TEXT NOT NULL, confidence TEXT NOT NULL,
1377
+ name TEXT NOT NULL, system TEXT, file TEXT NOT NULL, stale INTEGER NOT NULL, json TEXT NOT NULL,
1378
+ PRIMARY KEY (kind, id)
1379
+ );
1380
+ CREATE INDEX records_id ON records (id);
1381
+ CREATE INDEX records_system ON records (system);
1382
+ CREATE INDEX records_file ON records (file);
1383
+ CREATE TABLE refs (from_kind TEXT NOT NULL, from_id TEXT NOT NULL, field TEXT NOT NULL, to_id TEXT NOT NULL, file TEXT NOT NULL);
1384
+ CREATE INDEX refs_to ON refs (to_id);
1385
+ CREATE INDEX refs_from ON refs (from_id);
1386
+ CREATE INDEX refs_file ON refs (file);
1387
+ CREATE TABLE evidence (kind TEXT NOT NULL, id TEXT NOT NULL, source TEXT NOT NULL, repo TEXT, path TEXT, line INTEGER, file TEXT NOT NULL);
1388
+ CREATE INDEX evidence_path ON evidence (repo, path);
1389
+ CREATE INDEX evidence_file ON evidence (file);
1390
+ CREATE TABLE nodes (
1391
+ key TEXT PRIMARY KEY, kind TEXT NOT NULL, id TEXT NOT NULL, grp TEXT NOT NULL, name TEXT NOT NULL,
1392
+ file TEXT NOT NULL, verified INTEGER NOT NULL, line TEXT NOT NULL, detail TEXT NOT NULL,
1393
+ body TEXT NOT NULL, mentions INTEGER NOT NULL, json TEXT
1394
+ );
1395
+ CREATE INDEX nodes_id ON nodes (id);
1396
+ CREATE INDEX nodes_file ON nodes (file);
1397
+ CREATE TABLE names (name TEXT NOT NULL, id TEXT NOT NULL, kind TEXT NOT NULL, how TEXT NOT NULL, file TEXT NOT NULL);
1398
+ CREATE INDEX names_name ON names (name);
1399
+ CREATE INDEX names_id ON names (id);
1400
+ CREATE INDEX names_file ON names (file);
1401
+ CREATE TABLE links (from_key TEXT NOT NULL, to_ref TEXT NOT NULL, by TEXT NOT NULL, type TEXT NOT NULL, how TEXT NOT NULL, file TEXT NOT NULL, derived INTEGER NOT NULL);
1402
+ CREATE INDEX links_from ON links (from_key);
1403
+ CREATE INDEX links_to ON links (to_ref, by);
1404
+ CREATE INDEX links_file ON links (file);
1405
+ CREATE TABLE reviews (item TEXT NOT NULL, verdict TEXT NOT NULL, by TEXT NOT NULL, at TEXT NOT NULL, file TEXT NOT NULL);
1406
+ CREATE INDEX reviews_item ON reviews (item);
1407
+ CREATE INDEX reviews_file ON reviews (file);
1408
+ CREATE VIRTUAL TABLE fts USING fts5(key UNINDEXED, name, ids, body, words, tokenize = 'porter unicode61');
1409
+ CREATE TABLE selections (key TEXT PRIMARY KEY, json TEXT NOT NULL);
1410
+ CREATE TABLE node_text (key TEXT PRIMARY KEY, text_sha TEXT NOT NULL, text TEXT NOT NULL, file TEXT NOT NULL);
1411
+ CREATE INDEX node_text_file ON node_text (file);
1412
+ `;
1413
+ var NAMEABLE_KINDS = ["System", "Component", "Party", "Product"];
1414
+ var meta = (db, key) => db.prepare("SELECT value FROM meta WHERE key = ?").get(key)?.value;
1415
+ function ensureSchema(db, rebuild = false) {
1416
+ let version;
1417
+ try {
1418
+ version = meta(db, "version");
1419
+ } catch {
1420
+ version = void 0;
1421
+ }
1422
+ if (!rebuild && version === String(INDEX_VERSION))
1423
+ return false;
1424
+ const objects = db.prepare("SELECT type, name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'").all();
1425
+ db.exec("BEGIN IMMEDIATE");
1426
+ try {
1427
+ const virtual = objects.filter((o) => /VIRTUAL/i.test(db.prepare("SELECT sql FROM sqlite_master WHERE name = ?").get(o.name)?.sql ?? ""));
1428
+ for (const v of virtual)
1429
+ db.exec(`DROP TABLE IF EXISTS "${v.name}"`);
1430
+ for (const o of objects)
1431
+ db.exec(`DROP TABLE IF EXISTS "${o.name}"`);
1432
+ db.exec(SCHEMA);
1433
+ db.prepare("INSERT INTO meta VALUES ('version', ?)").run(String(INDEX_VERSION));
1434
+ db.exec("COMMIT");
1435
+ } catch (e) {
1436
+ db.exec("ROLLBACK");
1437
+ throw e;
1438
+ }
1439
+ return true;
1440
+ }
1441
+ var sha = (s) => createHash("sha256").update(s, "utf8").digest("hex");
1442
+ function systemsOf(records) {
1443
+ const key = (r) => `${r.kind}|${r.id}`;
1444
+ const systems = new Set(records.filter((r) => r.kind === "System").map((r) => r.id));
1445
+ const componentSystem = /* @__PURE__ */ new Map();
1446
+ for (const r of records)
1447
+ if (r.kind === "Component")
1448
+ componentSystem.set(r.id, r.system);
1449
+ const ofId = (id) => {
1450
+ if (typeof id !== "string")
1451
+ return void 0;
1452
+ return componentSystem.get(id) ?? (systems.has(id) ? id : void 0);
1453
+ };
1454
+ const out = /* @__PURE__ */ new Map();
1455
+ const tables = /* @__PURE__ */ new Map();
1456
+ const stores = /* @__PURE__ */ new Map();
1457
+ for (const r of records) {
1458
+ const x = r;
1459
+ let s;
1460
+ if (r.kind === "System")
1461
+ s = r.id;
1462
+ else if (r.kind === "Component")
1463
+ s = r.system;
1464
+ else if (r.kind === "Interface")
1465
+ s = ofId(r.from);
1466
+ else
1467
+ s = ofId(x.component) ?? ofId(x.ownerComponent) ?? ofId(x.owner) ?? (typeof x.system === "string" ? x.system : void 0);
1468
+ if (s)
1469
+ out.set(key(r), s);
1470
+ if (r.kind === "DataStore" && s)
1471
+ stores.set(r.id, s);
1472
+ }
1473
+ for (const r of records)
1474
+ if (r.kind === "Table" && !out.has(key(r)) && stores.has(r.dataStore))
1475
+ out.set(key(r), stores.get(r.dataStore));
1476
+ for (const r of records)
1477
+ if (r.kind === "Table" && out.has(key(r)))
1478
+ tables.set(r.id, out.get(key(r)));
1479
+ for (const r of records) {
1480
+ const x = r;
1481
+ if (out.has(key(r)))
1482
+ continue;
1483
+ const s = typeof x.table === "string" && tables.get(x.table) || typeof x.dataStore === "string" && stores.get(x.dataStore) || void 0;
1484
+ if (s)
1485
+ out.set(key(r), s);
1486
+ }
1487
+ return out;
1488
+ }
1489
+ function mentionNames(db) {
1490
+ const rows = db.prepare(`SELECT DISTINCT n.name AS name, n.id AS id, n.how AS how FROM names n
1491
+ WHERE length(n.name) >= 3 AND n.how != 'endpoint'
1492
+ AND EXISTS (SELECT 1 FROM nodes d WHERE d.id = n.id AND d.kind IN (${NAMEABLE_KINDS.map(() => "?").join(", ")}))
1493
+ ORDER BY n.name, n.id, n.how`).all(...NAMEABLE_KINDS);
1494
+ const out = /* @__PURE__ */ new Map();
1495
+ for (const r of rows) {
1496
+ if (/^\d+$/.test(r.name))
1497
+ continue;
1498
+ const l = out.get(r.name) ?? [];
1499
+ if (!l.some((x) => x.id === r.id))
1500
+ l.push({ id: r.id, how: r.how });
1501
+ out.set(r.name, l);
1502
+ }
1503
+ return out;
1504
+ }
1505
+ function nameTokens(text) {
1506
+ return foldName(text).split(/[\s,;()[\]"`*|<>!?]+/).map((t) => t.replace(/^[^a-z0-9/{]+/, "").replace(/'s$/, "").replace(/[^a-z0-9}]+$/, "")).filter(Boolean);
1507
+ }
1508
+ function namesIn(text, dict, maxWords) {
1509
+ const toks = nameTokens(text);
1510
+ const out = [];
1511
+ for (let i = 0; i < toks.length; i++) {
1512
+ for (let n = Math.min(maxWords, toks.length - i); n >= 1; n--) {
1513
+ const cand = toks.slice(i, i + n).join(" ");
1514
+ if (dict.has(cand)) {
1515
+ out.push({ name: cand, at: i });
1516
+ break;
1517
+ }
1518
+ }
1519
+ }
1520
+ return out;
1521
+ }
1522
+ var maxWordsOf = (dict) => Math.min(8, Math.max(1, ...[...dict.keys()].map((k) => k.split(" ").length)));
1523
+ async function updateIndex(db, root, files, o = {}) {
1524
+ const known = new Map(db.prepare("SELECT path, size, mtime, hash FROM files").all().map((r) => [r.path, r]));
1525
+ const present = new Set(files.map((f) => f.path));
1526
+ const removed = [...known.keys()].filter((p) => !present.has(p)).sort();
1527
+ const stale = files.filter((f) => {
1528
+ const k = known.get(f.path);
1529
+ return o.force || !k || k.size !== f.size || k.mtime !== f.mtimeMs;
1530
+ });
1531
+ const result = { changed: [], removed, problems: [], rematched: false };
1532
+ if (!stale.length && !removed.length)
1533
+ return result;
1534
+ const reads = [];
1535
+ for (const f of stale) {
1536
+ const bytes = await readFile2(join2(root, ...f.path.split("/"))).catch(() => void 0);
1537
+ if (bytes === void 0)
1538
+ continue;
1539
+ reads.push({
1540
+ f,
1541
+ bytes,
1542
+ text: bytes.toString("utf8"),
1543
+ hash: createHash("sha256").update(bytes).digest("hex")
1544
+ });
1545
+ }
1546
+ const documents = /* @__PURE__ */ new Map();
1547
+ for (const r of reads)
1548
+ if (r.f.source === "document" && (o.force || known.get(r.f.path)?.hash !== r.hash))
1549
+ documents.set(r.f.path, await documentNodes(r.f.path, r.bytes));
1550
+ const touched = new Set(reads.filter((r) => o.force || known.get(r.f.path)?.hash !== r.hash).map((r) => r.f.path));
1551
+ db.exec("BEGIN IMMEDIATE");
1552
+ try {
1553
+ const drop = (path) => {
1554
+ for (const t of [
1555
+ "records",
1556
+ "refs",
1557
+ "evidence",
1558
+ "nodes",
1559
+ "names",
1560
+ "links",
1561
+ "reviews",
1562
+ "node_text"
1563
+ ])
1564
+ db.prepare(`DELETE FROM ${t} WHERE file = ?`).run(path);
1565
+ };
1566
+ const dropFts = db.prepare("DELETE FROM fts WHERE rowid IN (SELECT rowid FROM nodes WHERE file = ?)");
1567
+ for (const p of removed) {
1568
+ dropFts.run(p);
1569
+ drop(p);
1570
+ db.prepare("DELETE FROM files WHERE path = ?").run(p);
1571
+ }
1572
+ const insRec = db.prepare("INSERT OR REPLACE INTO records VALUES (?, ?, ?, ?, ?, NULL, ?, ?, ?)");
1573
+ const insRef = db.prepare("INSERT INTO refs VALUES (?, ?, ?, ?, ?)");
1574
+ const insEv = db.prepare("INSERT INTO evidence VALUES (?, ?, ?, ?, ?, ?, ?)");
1575
+ const insNode = db.prepare("INSERT OR REPLACE INTO nodes VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
1576
+ const insName = db.prepare("INSERT INTO names VALUES (?, ?, ?, ?, ?)");
1577
+ const insLink = db.prepare("INSERT INTO links VALUES (?, ?, ?, ?, ?, ?, ?)");
1578
+ const insReview = db.prepare("INSERT INTO reviews VALUES (?, ?, ?, ?, ?)");
1579
+ const insFts = db.prepare("INSERT INTO fts (rowid, key, name, ids, body, words) VALUES (?, ?, ?, ?, ?, ?)");
1580
+ const delFtsKey = db.prepare("DELETE FROM fts WHERE rowid IN (SELECT rowid FROM nodes WHERE key = ?)");
1581
+ const insText = db.prepare("INSERT OR REPLACE INTO node_text VALUES (?, ?, ?, ?)");
1582
+ const upFile = db.prepare("INSERT OR REPLACE INTO files VALUES (?, ?, ?, ?, ?)");
1583
+ let catalogChanged = removed.some((p) => known.has(p));
1584
+ const fresh = [];
1585
+ for (const { f, text, hash } of reads) {
1586
+ if (!touched.has(f.path)) {
1587
+ upFile.run(f.path, f.source, f.size, f.mtimeMs, hash);
1588
+ continue;
1589
+ }
1590
+ if (known.has(f.path)) {
1591
+ dropFts.run(f.path);
1592
+ drop(f.path);
1593
+ }
1594
+ const r = documents.get(f.path) ?? readSource(root, f, text);
1595
+ if (r.problem)
1596
+ result.problems.push({ file: f.path, message: r.problem });
1597
+ if (f.source === "catalog")
1598
+ catalogChanged = true;
1599
+ if (r.record) {
1600
+ const rec = r.record;
1601
+ insRec.run(rec.kind, rec.id, rec.status, rec.confidence, labelOf(rec), f.path, (rec.flags ?? []).includes("stale") ? 1 : 0, JSON.stringify(rec));
1602
+ for (const l of r.links.filter((x) => x.type === "ref"))
1603
+ insRef.run(rec.kind, rec.id, l.how.replace(/^field:/, ""), l.to, f.path);
1604
+ for (const e of rec.evidence)
1605
+ insEv.run(rec.kind, rec.id, String(e.source), e.repo ?? null, e.path ?? null, typeof e.line === "number" ? e.line : null, f.path);
1606
+ }
1607
+ for (const n of r.nodes) {
1608
+ delFtsKey.run(n.key);
1609
+ const row = insNode.run(n.key, n.kind, n.id, n.grp, n.name, f.path, n.verified ? 1 : 0, n.line, n.detail, n.body, n.mentions ? 1 : 0, n.json ?? null);
1610
+ const t = nodeText(n);
1611
+ insText.run(n.key, sha(t), t, f.path);
1612
+ insFts.run(row.lastInsertRowid, n.key, n.names, n.ids, n.body, splitForms(`${n.name}
1613
+ ${n.names}
1614
+ ${n.ids}
1615
+ ${n.body}`).join("\n"));
1616
+ }
1617
+ for (const n of r.names)
1618
+ insName.run(n.name, n.id, n.kind, n.how, f.path);
1619
+ for (const l of r.links)
1620
+ insLink.run(l.from, l.to, l.by, l.type, l.how, f.path, 0);
1621
+ if (r.review)
1622
+ insReview.run(r.review.item, r.review.verdict, r.review.by, r.review.at, f.path);
1623
+ upFile.run(f.path, f.source, f.size, f.mtimeMs, hash);
1624
+ fresh.push(f.path);
1625
+ }
1626
+ result.changed = fresh.sort();
1627
+ if (catalogChanged) {
1628
+ const all2 = db.prepare("SELECT json FROM records").all().map((x) => JSON.parse(x.json));
1629
+ const systems = systemsOf(all2);
1630
+ const setSys = db.prepare("UPDATE records SET system = ? WHERE kind = ? AND id = ?");
1631
+ for (const r of all2)
1632
+ setSys.run(systems.get(`${r.kind}|${r.id}`) ?? null, r.kind, r.id);
1633
+ }
1634
+ const dict = mentionNames(db);
1635
+ const dictHash = sha([...dict].map(([k, v]) => `${k} ${v.map((x) => `${x.id}/${x.how}`).join(",")}`).join("\n"));
1636
+ const all = meta(db, "names") !== dictHash;
1637
+ result.rematched = all;
1638
+ if (all)
1639
+ db.exec("DELETE FROM links WHERE derived = 1");
1640
+ const sources = all ? db.prepare("SELECT key, id, name, body, file FROM nodes WHERE mentions = 1 ORDER BY key").all() : fresh.length ? db.prepare(`SELECT key, id, name, body, file FROM nodes WHERE mentions = 1 AND file IN (${fresh.map(() => "?").join(", ")}) ORDER BY key`).all(...fresh) : [];
1641
+ const maxWords = maxWordsOf(dict);
1642
+ for (const n of sources) {
1643
+ const seen = /* @__PURE__ */ new Set();
1644
+ for (const m of namesIn(`${n.name}
1645
+ ${n.body}`, dict, maxWords))
1646
+ for (const t of dict.get(m.name) ?? []) {
1647
+ if (t.id === n.id || seen.has(t.id))
1648
+ continue;
1649
+ seen.add(t.id);
1650
+ insLink.run(n.key, t.id, "id", "names", `mention ${t.how}`, n.file, 1);
1651
+ }
1652
+ }
1653
+ const setMeta = db.prepare("INSERT OR REPLACE INTO meta VALUES (?, ?)");
1654
+ setMeta.run("names", dictHash);
1655
+ const fileRows = db.prepare("SELECT path, hash FROM files ORDER BY path").all();
1656
+ const gen = createHash("sha256").update(`v${INDEX_VERSION}
1657
+ `);
1658
+ for (const f of fileRows)
1659
+ gen.update(`${f.path} ${f.hash}
1660
+ `);
1661
+ setMeta.run("generation", gen.digest("hex").slice(0, 16));
1662
+ setMeta.run("records", String(db.prepare("SELECT count(*) AS n FROM records").get().n));
1663
+ db.exec("DELETE FROM selections");
1664
+ db.exec("COMMIT");
1665
+ } catch (e) {
1666
+ db.exec("ROLLBACK");
1667
+ throw e;
1668
+ }
1669
+ return result;
1670
+ }
1671
+
1672
+ // packages/index/dist/select.js
1673
+ var STOP = new Set("the and for with from that this into onto over under than then them they their there these those what when where which while who whom whose why how are was were been being has have had having not but can could should would will shall may might must its it's our ours your yours his her out off per via all any each every some such only own same other more most less least very just also too both either neither nor yet one two old use used uses using make makes made does done get gets got let lets about after before again against because below between during above upon here once until within without through scope out-of-scope problem outcome open questions".split(/\s+/));
1674
+ function queryWords(text, max = 200, o = {}) {
1675
+ const out = [];
1676
+ const seen = /* @__PURE__ */ new Set();
1677
+ const push = (w) => {
1678
+ if (w.length < 3 || STOP.has(w) || /^\d+$/.test(w) || seen.has(w))
1679
+ return;
1680
+ seen.add(w);
1681
+ out.push(w);
1682
+ };
1683
+ for (const m of text.matchAll(/[A-Za-z0-9]+/g)) {
1684
+ push(m[0].toLowerCase());
1685
+ if (o.split !== false)
1686
+ for (const w of identifierWords(m[0]))
1687
+ push(w);
1688
+ if (out.length >= max)
1689
+ break;
1690
+ }
1691
+ return out.slice(0, max);
1692
+ }
1693
+ function searchIndex(ix, text, o = {}) {
1694
+ const words = queryWords(text, 200, { split: o.split !== false });
1695
+ if (!words.length)
1696
+ return [];
1697
+ const match = words.map((w) => `"${w}"`).join(" OR ");
1698
+ const kinds = o.kinds?.length ? o.kinds : void 0;
1699
+ const askedOnly = kinds ? [] : [...ASKED_ONLY_KINDS];
1700
+ const wordsWeight = o.split === false ? 0 : 6;
1701
+ const rows = prep(ix, `SELECT f.key AS key, n.kind AS kind, n.id AS id, n.name AS name, n.file AS file,
1702
+ bm25(fts, 0.0, 10.0, 8.0, 1.0, ${wordsWeight.toFixed(1)}) AS rank
1703
+ FROM fts f JOIN nodes n ON n.key = f.key
1704
+ WHERE fts MATCH ?${kinds ? ` AND n.kind IN (${kinds.map(() => "?").join(", ")})` : ""}${askedOnly.length ? ` AND n.kind NOT IN (${askedOnly.map(() => "?").join(", ")})` : ""}
1705
+ ORDER BY rank, f.key LIMIT ?`).all(match, ...kinds ?? [], ...askedOnly, o.limit ?? 20);
1706
+ return rows.map(({ rank, ...r }) => ({ ...r, score: round(-rank) }));
1707
+ }
1708
+ var statements = /* @__PURE__ */ new WeakMap();
1709
+ function prep(ix, sql) {
1710
+ let m = statements.get(ix.db);
1711
+ if (!m)
1712
+ statements.set(ix.db, m = /* @__PURE__ */ new Map());
1713
+ let st = m.get(sql);
1714
+ if (!st)
1715
+ m.set(sql, st = ix.db.prepare(sql));
1716
+ return st;
1717
+ }
1718
+ var round = (n) => Math.round(n * 1e6) / 1e6;
1719
+ var NODE_SQL = `SELECT n.key, n.kind, n.id, n.grp, n.name, n.file, n.verified, n.line, n.detail, n.json,
1720
+ (SELECT r.verdict FROM reviews r WHERE r.item = n.id ORDER BY r.at DESC, r.file LIMIT 1) AS review
1721
+ FROM nodes n`;
1722
+ function toNode(r) {
1723
+ return {
1724
+ key: r.key,
1725
+ kind: r.kind,
1726
+ id: r.id,
1727
+ grp: r.grp,
1728
+ name: r.name,
1729
+ file: r.file,
1730
+ verified: r.verified === 1 || r.review === "verified" || r.review === "corrected",
1731
+ line: r.line,
1732
+ detail: r.detail,
1733
+ ...r.json ? { json: JSON.parse(r.json) } : {},
1734
+ ...r.review ? { review: r.review } : {}
1735
+ };
1736
+ }
1737
+ function nodesById(ix, id) {
1738
+ return prep(ix, `${NODE_SQL} WHERE n.id = ? ORDER BY n.grp, n.kind`).all(id).map(toNode);
1739
+ }
1740
+ function nodeByKey(ix, key) {
1741
+ const r = prep(ix, `${NODE_SQL} WHERE n.key = ?`).get(key);
1742
+ return r ? toNode(r) : void 0;
1743
+ }
1744
+ function linksOf(ix, key) {
1745
+ const self = prep(ix, "SELECT id FROM nodes WHERE key = ?").get(key);
1746
+ if (!self)
1747
+ return [];
1748
+ const out = prep(ix, `SELECT DISTINCT d.key AS key, d.kind AS kind, d.id AS id, l.type AS type, l.how AS how
1749
+ FROM links l JOIN nodes d ON d.id = l.to_ref
1750
+ WHERE l.from_key = ? AND l.by = 'id' AND d.key != l.from_key
1751
+ UNION
1752
+ SELECT DISTINCT d.key, d.kind, d.id, l.type, l.how
1753
+ FROM links l JOIN names m ON m.name = l.to_ref JOIN nodes d ON d.id = m.id
1754
+ WHERE l.from_key = ? AND l.by = 'name' AND d.key != l.from_key`).all(key, key);
1755
+ const inn = prep(ix, `SELECT DISTINCT d.key AS key, d.kind AS kind, d.id AS id, l.type AS type, l.how AS how
1756
+ FROM links l JOIN nodes d ON d.key = l.from_key
1757
+ WHERE l.by = 'id' AND l.to_ref = ? AND l.from_key != ?
1758
+ UNION
1759
+ SELECT DISTINCT d.key, d.kind, d.id, l.type, l.how
1760
+ FROM names m JOIN links l ON l.by = 'name' AND l.to_ref = m.name JOIN nodes d ON d.key = l.from_key
1761
+ WHERE m.id = ? AND l.from_key != ?`).all(self.id, key, self.id, key);
1762
+ const all = [
1763
+ ...out.map((l) => ({ dir: "out", ...l })),
1764
+ ...inn.map((l) => ({ dir: "in", ...l }))
1765
+ ];
1766
+ return all.sort((a, b) => cmp(a.key, b.key) || cmp(a.dir, b.dir) || cmp(a.type, b.type) || cmp(a.how, b.how));
1767
+ }
1768
+ var cmp = (a, b) => a < b ? -1 : a > b ? 1 : 0;
1769
+ var RRF_K = 1;
1770
+ var RANK_FLOOR = 0.3;
1771
+ var MEANING_FLOOR = 0.3;
1772
+ function fuse(rankings, k = RRF_K) {
1773
+ const sum = /* @__PURE__ */ new Map();
1774
+ for (const r of rankings)
1775
+ r.keys.forEach((key, i) => {
1776
+ const v = 1 / (k + i + 1);
1777
+ const s = sum.get(key) ?? { score: 0, parts: [] };
1778
+ s.score += v;
1779
+ const d = r.detail?.get(key);
1780
+ s.parts.push({ name: `${r.name} #${i + 1}${d ? ` (${d})` : ""}`, rank: i + 1, v });
1781
+ sum.set(key, s);
1782
+ });
1783
+ const best = Math.max(0, ...[...sum.values()].map((s) => s.score));
1784
+ const out = /* @__PURE__ */ new Map();
1785
+ for (const [key, s] of [...sum].sort((a, b) => cmp(a[0], b[0]))) {
1786
+ const parts = [...s.parts].sort((a, b) => b.v - a.v || cmp(a.name, b.name)).slice(0, 3);
1787
+ out.set(key, {
1788
+ score: round(0.95 * s.score / best),
1789
+ via: `search (${parts.map((p) => p.name).join(", ")})`
1790
+ });
1791
+ }
1792
+ return out;
1793
+ }
1794
+ function synonymsOf(ix, names, text, maxWords) {
1795
+ const out = [];
1796
+ const seen = /* @__PURE__ */ new Set();
1797
+ const namesOfId = prep(ix, "SELECT DISTINCT name, how FROM names WHERE id = ? ORDER BY name");
1798
+ for (const m of namesIn(text, names, maxWords)) {
1799
+ if (seen.has(m.name))
1800
+ continue;
1801
+ seen.add(m.name);
1802
+ const words = /* @__PURE__ */ new Set();
1803
+ for (const id of names.get(m.name) ?? []) {
1804
+ const all = namesOfId.all(id);
1805
+ const nodes = nodesById(ix, id);
1806
+ const term = nodes.find((n) => n.kind === "GlossaryTerm");
1807
+ const alias = all.some((r) => r.name === m.name && r.how.startsWith("alias:"));
1808
+ if (!term && !alias)
1809
+ continue;
1810
+ for (const r of all)
1811
+ if (r.name !== m.name)
1812
+ words.add(r.name);
1813
+ if (term) {
1814
+ const x = term.json ?? {};
1815
+ if (x.term)
1816
+ words.add(x.term.toLowerCase());
1817
+ for (const f of x.fields ?? [])
1818
+ if (typeof f === "string")
1819
+ for (const n of nodesById(ix, f))
1820
+ words.add(identifierWords(n.name).join(" "));
1821
+ }
1822
+ }
1823
+ if (words.size)
1824
+ out.push({ name: m.name, words: [...words].sort() });
1825
+ }
1826
+ return out;
1827
+ }
1828
+ var trust = (key) => key.startsWith("document:") ? 0.8 : 1;
1829
+ var GROUP_ORDER = [
1830
+ "catalog",
1831
+ "flow",
1832
+ "knowledge",
1833
+ "change",
1834
+ "question",
1835
+ "wiki",
1836
+ "document",
1837
+ "org-pack"
1838
+ ];
1839
+ function describe(n) {
1840
+ const x = n.json ?? {};
1841
+ const out = [];
1842
+ for (const f of ["boundary", "partyKind", "productKind"])
1843
+ if (typeof x[f] === "string")
1844
+ out.push(String(x[f]).replace(/-/g, " "));
1845
+ if (n.grp === "org-pack") {
1846
+ const m = /\(([a-z-]+)\)/.exec(n.line);
1847
+ if (m)
1848
+ out.push(m[1]);
1849
+ }
1850
+ return out;
1851
+ }
1852
+ function namesMap(ix) {
1853
+ const out = /* @__PURE__ */ new Map();
1854
+ for (const r of prep(ix, "SELECT DISTINCT name, id FROM names ORDER BY name, id").all()) {
1855
+ const l = out.get(r.name) ?? [];
1856
+ l.push(r.id);
1857
+ out.set(r.name, l);
1858
+ }
1859
+ return out;
1860
+ }
1861
+ function endpointsIn(text) {
1862
+ return [...text.matchAll(/\b(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s+(\/[^\s`"'),;]*)/g)].map((m) => endpointName(m[1], m[2].replace(/[.:]+$/, "")));
1863
+ }
1864
+ var GENERIC = new Set("api apis service services system systems team app apps application platform portal ui ux gateway database db store queue topic site module library sdk client server backend frontend web mobile integration endpoint endpoints".split(" "));
1865
+ var FILE = /\.(md|markdown|ya?ml|json|java|kt|ts|tsx|js|mjs|py|go|cs|rb|sql|xml|txt|csv|html?|properties|sh|toml|ini|cfg)$/i;
1866
+ function nameCandidates(text) {
1867
+ const out = /* @__PURE__ */ new Set();
1868
+ const body = text.split(/\r?\n/).filter((l) => !/^\s*#/.test(l) && !/^\s*<!--/.test(l)).join("\n");
1869
+ for (const m of body.matchAll(/`([^`\n]{2,60})`/g)) {
1870
+ const t = m[1].trim();
1871
+ if (/^(GET|POST|PUT|PATCH|DELETE)\s+\//.test(t))
1872
+ continue;
1873
+ const id = /^[a-z0-9]+(?:[-.][a-z0-9]+)+$/.test(t) && !FILE.test(t);
1874
+ const name = /^[A-Z][A-Za-z0-9]*(?: [A-Z][A-Za-z0-9]*){0,3}$/.test(t);
1875
+ if (id || name)
1876
+ out.add(t);
1877
+ }
1878
+ for (const m of body.matchAll(/\b(GET|POST|PUT|PATCH|DELETE)\s+(\/[^\s`"'),;]*)/g))
1879
+ out.add(`${m[1]} ${m[2].replace(/[.:]+$/, "")}`);
1880
+ return [...out];
1881
+ }
1882
+ function knownName(ix, names, maxWords, candidate) {
1883
+ const m = /^(GET|POST|PUT|PATCH|DELETE)\s+(\/\S*)$/.exec(candidate);
1884
+ if (m)
1885
+ return names.has(endpointName(m[1], m[2]));
1886
+ const f = foldName(candidate);
1887
+ if (names.has(f) || names.has(f.replace(/ /g, "-")))
1888
+ return true;
1889
+ const toks = f.split(/[\s-]+/).filter(Boolean);
1890
+ const covered = new Array(toks.length).fill(false);
1891
+ for (const hit of namesIn(toks.join(" "), names, maxWords)) {
1892
+ const n = hit.name.split(" ").length;
1893
+ for (let i = hit.at; i < hit.at + n && i < toks.length; i++)
1894
+ covered[i] = true;
1895
+ }
1896
+ if (covered.some(Boolean) && toks.every((t, i) => covered[i] || GENERIC.has(t)))
1897
+ return true;
1898
+ const words = queryWords(candidate, 200, { split: false });
1899
+ if (words.length < 2 || words.length !== toks.length)
1900
+ return false;
1901
+ const phrase = `"${words.join(" ")}"`;
1902
+ return !!prep(ix, `SELECT 1 FROM fts f JOIN nodes n ON n.key = f.key WHERE fts MATCH ? AND n.grp != 'change' LIMIT 1`).get(phrase);
1903
+ }
1904
+ function candidatesOf(ix, input, o) {
1905
+ const names = namesMap(ix);
1906
+ const maxWords = Math.min(8, Math.max(1, ...[...names.keys()].map((k) => k.split(" ").length)));
1907
+ const text = input.text;
1908
+ const title = input.title ?? "";
1909
+ const rejected = new Set(o.includeRejected ? [] : prep(ix, `SELECT item FROM reviews r WHERE verdict = 'rejected' AND at = (SELECT max(at) FROM reviews x WHERE x.item = r.item)`).all().map((r) => r.item));
1910
+ const through = o.through?.length ? new Set(o.through) : void 0;
1911
+ const seeds = /* @__PURE__ */ new Map();
1912
+ const seed = (key, score, via) => {
1913
+ const was = seeds.get(key);
1914
+ if (!was)
1915
+ seeds.set(key, { score, via });
1916
+ else
1917
+ seeds.set(key, { score: 1 - (1 - was.score) * (1 - score), via: was.via });
1918
+ };
1919
+ const exactIds = new Set(input.ids ?? []);
1920
+ for (const m of namesIn(`${title}
1921
+ ${text}`, names, maxWords))
1922
+ for (const id of names.get(m.name) ?? [])
1923
+ exactIds.add(id);
1924
+ for (const e of endpointsIn(`${title}
1925
+ ${text}`))
1926
+ for (const id of names.get(e) ?? [])
1927
+ exactIds.add(id);
1928
+ const expansion = [];
1929
+ for (const id of [...exactIds].sort())
1930
+ for (const n of nodesById(ix, id)) {
1931
+ if (rejected.has(n.id))
1932
+ continue;
1933
+ seed(n.key, 1, "named in the input");
1934
+ expansion.push(...describe(n));
1935
+ }
1936
+ for (const id of [...new Set(input.expansion?.ids ?? [])].sort())
1937
+ if (!exactIds.has(id))
1938
+ for (const n of nodesById(ix, id)) {
1939
+ if (rejected.has(n.id))
1940
+ continue;
1941
+ seed(n.key, 0.9, "named in the input (the model's reading, an id the index has)");
1942
+ expansion.push(...describe(n));
1943
+ }
1944
+ const limit = o.searchLimit ?? 30;
1945
+ const split = o.lexical?.split !== false;
1946
+ const hitScore = /* @__PURE__ */ new Map();
1947
+ const hitVia = /* @__PURE__ */ new Map();
1948
+ const query = `${text}
1949
+ ${expansion.join(" ")}`;
1950
+ const about = [...new Set(expansion)].join(" ");
1951
+ const synonyms = o.lexical?.synonyms === false ? [] : synonymsOf(ix, names, `${title}
1952
+ ${text}`, maxWords);
1953
+ const extra = [
1954
+ ...input.rankings ?? [],
1955
+ ...(input.expansion?.terms ?? []).slice(0, 8).map((t) => ({ name: `the model's "${t}"`, text: t }))
1956
+ ];
1957
+ const floor = o.rankFloor ?? RANK_FLOOR;
1958
+ const ranked = (q, k) => {
1959
+ const hs = searchIndex(ix, q, k);
1960
+ const best = hs[0]?.score ?? 0;
1961
+ return hs.filter((h) => h.score >= best * floor).map((h) => h.key);
1962
+ };
1963
+ for (const kinds2 of o.kinds?.length ? [void 0, o.kinds] : [void 0]) {
1964
+ const k = { limit, split, ...kinds2 ? { kinds: kinds2 } : {} };
1965
+ const rankings = [{ name: "words", keys: ranked(query, k) }];
1966
+ if (title)
1967
+ rankings.push({ name: "title", keys: ranked(title, k) });
1968
+ if (about)
1969
+ rankings.push({ name: "what is named", keys: ranked(about, k) });
1970
+ for (const syn of synonyms)
1971
+ rankings.push({
1972
+ name: `synonyms of "${syn.name}"`,
1973
+ keys: ranked(syn.words.join(" "), k)
1974
+ });
1975
+ if (o.meaning !== false) {
1976
+ const sim = ix.similar(input, {
1977
+ limit,
1978
+ ...kinds2 ? { kinds: kinds2 } : {},
1979
+ ...o.meaningFloor !== void 0 ? { floor: o.meaningFloor } : {}
1980
+ });
1981
+ if ("hits" in sim)
1982
+ rankings.push({
1983
+ name: "similar in meaning",
1984
+ keys: sim.hits.map((h) => h.key),
1985
+ detail: new Map(sim.hits.map((h) => [h.key, `cos ${h.cos.toFixed(2)}`]))
1986
+ });
1987
+ }
1988
+ for (const r of extra)
1989
+ rankings.push({
1990
+ name: r.name,
1991
+ keys: (r.keys ?? ranked(r.text ?? "", k)).filter((key) => !kinds2 || kinds2.includes(key.split(":")[1])).slice(0, limit)
1992
+ });
1993
+ for (const [key, v] of fuse(rankings, o.rrfK ?? RRF_K)) {
1994
+ if (v.score > (hitScore.get(key) ?? 0)) {
1995
+ hitScore.set(key, v.score);
1996
+ hitVia.set(key, v.via);
1997
+ }
1998
+ }
1999
+ }
2000
+ for (const [key, s] of [...hitScore].sort((a, b) => cmp(a[0], b[0])))
2001
+ if (!rejected.has(key.split(":").slice(2).join(":")))
2002
+ seed(key, Math.min(0.95, s), hitVia.get(key) ?? "search");
2003
+ const kindOf = /* @__PURE__ */ new Map();
2004
+ const meta2 = prep(ix, "SELECT kind, id FROM nodes WHERE key = ?");
2005
+ const info = (key) => {
2006
+ let v = kindOf.get(key);
2007
+ if (!v) {
2008
+ v = meta2.get(key);
2009
+ if (v)
2010
+ kindOf.set(key, v);
2011
+ }
2012
+ return v;
2013
+ };
2014
+ const hops = o.hops ?? 2;
2015
+ const fanout = o.fanout ?? 24;
2016
+ const near = /* @__PURE__ */ new Map();
2017
+ const neighbours = (key) => {
2018
+ let l = near.get(key);
2019
+ if (!l) {
2020
+ const seen = /* @__PURE__ */ new Set();
2021
+ l = linksOf(ix, key).filter((x) => !rejected.has(x.id)).filter((x) => seen.has(x.key) ? false : (seen.add(x.key), true)).sort((a, b) => (hitScore.get(b.key) ?? 0) - (hitScore.get(a.key) ?? 0) || cmp(a.key, b.key)).slice(0, fanout);
2022
+ near.set(key, l);
2023
+ }
2024
+ return l;
2025
+ };
2026
+ const contributions = /* @__PURE__ */ new Map();
2027
+ for (const [key, s] of [...seeds].sort((a, b) => cmp(a[0], b[0]))) {
2028
+ const best = /* @__PURE__ */ new Map([[key, s]]);
2029
+ let frontier = [key];
2030
+ for (let h = 1; h <= hops && frontier.length; h++) {
2031
+ const next = [];
2032
+ for (const k of frontier) {
2033
+ const at = info(k);
2034
+ if (h > 1 && through && at && !through.has(at.kind))
2035
+ continue;
2036
+ const from = best.get(k);
2037
+ for (const n of neighbours(k)) {
2038
+ const score = from.score * 0.5;
2039
+ const was = best.get(n.key);
2040
+ if (was && was.score >= score)
2041
+ continue;
2042
+ if (!was)
2043
+ next.push(n.key);
2044
+ best.set(n.key, {
2045
+ score,
2046
+ via: `${n.dir === "out" ? `${at?.id ?? k} ${n.type} ${n.id}` : `${n.id} ${n.type} ${at?.id ?? k}`} (${n.how})`
2047
+ });
2048
+ }
2049
+ }
2050
+ frontier = next.sort();
2051
+ }
2052
+ for (const [k, v] of best) {
2053
+ const l = contributions.get(k) ?? [];
2054
+ l.push(v);
2055
+ contributions.set(k, l);
2056
+ }
2057
+ }
2058
+ const kinds = o.kinds?.length ? new Set(o.kinds) : void 0;
2059
+ const exclude = new Set(o.exclude ?? []);
2060
+ const groups = o.groups?.length ? new Set(o.groups) : void 0;
2061
+ const verified = new Map(prep(ix, `SELECT n.key AS key, n.verified AS v,
2062
+ (SELECT r.verdict FROM reviews r WHERE r.item = n.id ORDER BY r.at DESC LIMIT 1) AS review
2063
+ FROM nodes n`).all().map((r) => [r.key, r.v === 1 || r.review === "verified" || r.review === "corrected"]));
2064
+ const byKindId = /* @__PURE__ */ new Map();
2065
+ for (const [key, cs] of contributions) {
2066
+ const i = info(key);
2067
+ if (!i || rejected.has(i.id) || exclude.has(i.id))
2068
+ continue;
2069
+ if (kinds && !kinds.has(i.kind))
2070
+ continue;
2071
+ if (groups && !groups.has(key.split(":")[0]))
2072
+ continue;
2073
+ const sorted = [...cs].sort((a, b) => b.score - a.score || cmp(a.via, b.via));
2074
+ const text2 = 1 - sorted.reduce((p, c2) => p * (1 - Math.min(1, c2.score)), 1);
2075
+ const score = round(text2 * trust(key) * (verified.get(key) ? 1.2 : 1));
2076
+ if (score < (o.minScore ?? 0.1))
2077
+ continue;
2078
+ const grp = key.split(":")[0];
2079
+ const k = `${i.kind}|${i.id}`;
2080
+ const was = byKindId.get(k);
2081
+ const c = { key, kind: i.kind, id: i.id, score, via: sorted[0].via, grp };
2082
+ if (!was || GROUP_ORDER.indexOf(grp) < GROUP_ORDER.indexOf(was.grp) || grp === was.grp && key < was.key)
2083
+ byKindId.set(k, was ? { ...c, score: Math.max(score, was.score) } : c);
2084
+ else
2085
+ byKindId.set(k, { ...was, score: Math.max(score, was.score) });
2086
+ }
2087
+ const candidates = [...byKindId.values()].map(({ grp: _g, ...c }) => c).sort((a, b) => b.score - a.score || cmp(a.key, b.key));
2088
+ const asked = input.expansion ? [.../* @__PURE__ */ new Set([...input.expansion.unknown, ...nameCandidates(`${title}
2089
+ ${text}`)])] : nameCandidates(`${title}
2090
+ ${text}`);
2091
+ const unknown = asked.filter((c) => !knownName(ix, names, maxWords, c)).sort();
2092
+ return { candidates, unknown };
2093
+ }
2094
+ var candidateRef = (c) => `${c.kind} ${c.id}`;
2095
+ function orderCandidates(candidates, order) {
2096
+ if (!order)
2097
+ return { candidates, dropped: [], used: false };
2098
+ const shown = new Set(order.shown);
2099
+ const named2 = order.refs;
2100
+ const byRef = new Map(candidates.map((c) => [candidateRef(c), c]));
2101
+ const valid = new Set(named2).size === named2.length && named2.every((r) => shown.has(r) && byRef.has(r));
2102
+ if (!valid)
2103
+ return { candidates, dropped: [], used: false };
2104
+ const keep = new Set(named2);
2105
+ return {
2106
+ candidates: [
2107
+ ...named2.map((r) => byRef.get(r)),
2108
+ ...candidates.filter((c) => !shown.has(candidateRef(c)))
2109
+ ],
2110
+ dropped: candidates.filter((c) => shown.has(candidateRef(c)) && !keep.has(candidateRef(c))),
2111
+ used: true
2112
+ };
2113
+ }
2114
+ var wrapped = (id, kind, origin, textLength) => `<atticus-data id="${id}" kind="${kind}" origin="${origin}">
2115
+
2116
+ </atticus-data>`.length + textLength;
2117
+ var tokensOf = (chars) => Math.ceil(chars / 4);
2118
+ function selectContext(ix, raw, o) {
2119
+ const input = typeof raw === "string" ? { text: raw } : raw;
2120
+ const m = ix.meaning();
2121
+ const probe = o.meaning === false || !m.on ? void 0 : ix.similar(input, { limit: 1 });
2122
+ const meaning = o.meaning === false ? "similar in meaning is off for this step" : probe && "off" in probe ? `similar in meaning is off: ${probe.off}` : m.note;
2123
+ const meaningOn = !!probe && "hits" in probe;
2124
+ const version = meaningOn ? `${ix.version}+${m.model}` : ix.version;
2125
+ const keyed = {
2126
+ input,
2127
+ kinds: o.kinds ?? null,
2128
+ groups: o.groups ?? null,
2129
+ through: o.through ?? null,
2130
+ lexical: o.lexical ?? null,
2131
+ rrfK: o.rrfK ?? RRF_K,
2132
+ rankFloor: o.rankFloor ?? RANK_FLOOR,
2133
+ hops: o.hops ?? 2,
2134
+ fanout: o.fanout ?? 24,
2135
+ searchLimit: o.searchLimit ?? 30,
2136
+ minScore: o.minScore ?? 0.1,
2137
+ exclude: o.exclude ?? null,
2138
+ includeRejected: o.includeRejected ?? false,
2139
+ meaning: meaningOn ? o.meaningFloor ?? "default" : false
2140
+ };
2141
+ const cacheKey = createHash2("sha256").update(`${version}
2142
+ ${JSON.stringify(keyed)}`).digest("hex");
2143
+ let cached = false;
2144
+ let found;
2145
+ const hit = prep(ix, "SELECT json FROM selections WHERE key = ?").get(cacheKey);
2146
+ if (hit) {
2147
+ found = JSON.parse(hit.json);
2148
+ cached = true;
2149
+ } else {
2150
+ found = candidatesOf(ix, input, o);
2151
+ try {
2152
+ prep(ix, "INSERT OR REPLACE INTO selections VALUES (?, ?)").run(cacheKey, JSON.stringify(found));
2153
+ } catch {
2154
+ }
2155
+ }
2156
+ const prefix = o.idPrefix ?? "ctx-";
2157
+ const base = o.priority ?? 50;
2158
+ const classification = o.classification ?? "internal";
2159
+ const budget = Math.max(0, Math.floor(o.budget * 0.98));
2160
+ const fullBudget = budget * (o.fullShare ?? 0.6);
2161
+ const sources = [];
2162
+ const picked = [];
2163
+ const leftOut = [];
2164
+ const linesId = `${prefix}lines`;
2165
+ const linesOrigin = "the index, one line each (ask for one in full by its id)";
2166
+ const heading = o.linesHeading ?? "More that may matter, one line each; each names its id and where it is read from:";
2167
+ const lines = [];
2168
+ let linesChars = 0;
2169
+ let used = 0;
2170
+ let phase = "full";
2171
+ let full = 0;
2172
+ let over = false;
2173
+ const ordered = orderCandidates(found.candidates, o.order);
2174
+ const nameOf = (key) => prep(ix, "SELECT name FROM nodes WHERE key = ?").get(key)?.name;
2175
+ const named2 = (key) => {
2176
+ const n = nameOf(key);
2177
+ return n ? { name: n } : {};
2178
+ };
2179
+ for (const c of ordered.candidates) {
2180
+ if (over) {
2181
+ leftOut.push({ id: c.id, kind: c.kind, score: c.score, ...named2(c.key) });
2182
+ continue;
2183
+ }
2184
+ const n = nodeByKey(ix, c.key);
2185
+ if (!n)
2186
+ continue;
2187
+ const kind = n.grp === "catalog" || n.grp === "flow" || n.grp === "org-pack" ? "catalog" : "document";
2188
+ if (phase === "full") {
2189
+ const text = o.render?.(n, "full") ?? n.detail;
2190
+ const id = `${prefix}${n.kind}-${n.id}`.replace(/[^\w.#:/-]+/g, "-");
2191
+ const t2 = tokensOf(wrapped(id, kind, n.file, text.length));
2192
+ if (used + t2 <= fullBudget && (o.maxFull === void 0 || full < o.maxFull)) {
2193
+ used += t2;
2194
+ full++;
2195
+ sources.push({
2196
+ id,
2197
+ kind,
2198
+ origin: n.file,
2199
+ text,
2200
+ classification,
2201
+ priority: round(base - sources.length * 1e-3)
2202
+ });
2203
+ picked.push({
2204
+ key: n.key,
2205
+ kind: n.kind,
2206
+ id: n.id,
2207
+ score: c.score,
2208
+ level: "full",
2209
+ file: n.file,
2210
+ name: n.name,
2211
+ via: c.via
2212
+ });
2213
+ continue;
2214
+ }
2215
+ phase = "line";
2216
+ }
2217
+ const line = o.render?.(n, "line") ?? n.line;
2218
+ const nextChars = (linesChars || heading.length) + line.length + 1;
2219
+ const t = tokensOf(wrapped(linesId, "catalog", linesOrigin, nextChars));
2220
+ if (used + t <= budget) {
2221
+ lines.push(line);
2222
+ linesChars = nextChars;
2223
+ picked.push({
2224
+ key: n.key,
2225
+ kind: n.kind,
2226
+ id: n.id,
2227
+ score: c.score,
2228
+ level: "line",
2229
+ file: n.file,
2230
+ name: n.name,
2231
+ via: c.via
2232
+ });
2233
+ } else {
2234
+ over = true;
2235
+ leftOut.push({ id: n.id, kind: n.kind, score: c.score, ...n.name ? { name: n.name } : {} });
2236
+ }
2237
+ }
2238
+ for (const d of ordered.dropped)
2239
+ leftOut.push({
2240
+ id: d.id,
2241
+ kind: d.kind,
2242
+ score: d.score,
2243
+ why: "the model judged it not needed",
2244
+ ...named2(d.key)
2245
+ });
2246
+ if (lines.length)
2247
+ sources.push({
2248
+ id: linesId,
2249
+ kind: "catalog",
2250
+ origin: linesOrigin,
2251
+ text: [heading, ...lines].join("\n"),
2252
+ classification,
2253
+ priority: round(base - (sources.length + 1) * 1e-3)
2254
+ });
2255
+ const tokens2 = used + (lines.length ? tokensOf(wrapped(linesId, "catalog", linesOrigin, linesChars)) : 0);
2256
+ return { sources, picked, leftOut, unknown: found.unknown, tokens: tokens2, version, cached, meaning };
2257
+ }
2258
+
2259
+ // packages/index/dist/catalog-index.js
2260
+ import { mkdir } from "node:fs/promises";
2261
+ import { dirname as dirname2, join as join3 } from "node:path";
2262
+
2263
+ // packages/index/dist/db.js
2264
+ import { createRequire } from "node:module";
2265
+ var __atticus_import_meta_url = __atticusUrl("packages/index/dist/db.js");
2266
+ function openDb(file) {
2267
+ const emit = process.emitWarning;
2268
+ process.emitWarning = ((warning, ...rest) => {
2269
+ const text = typeof warning === "string" ? warning : warning.message;
2270
+ if (/SQLite/i.test(text))
2271
+ return;
2272
+ emit.call(process, warning, ...rest);
2273
+ });
2274
+ try {
2275
+ const { DatabaseSync } = createRequire(__atticus_import_meta_url)("node:sqlite");
2276
+ const db = new DatabaseSync(file);
2277
+ db.exec("PRAGMA busy_timeout = 5000");
2278
+ return db;
2279
+ } finally {
2280
+ process.emitWarning = emit;
2281
+ }
2282
+ }
2283
+
2284
+ // packages/index/dist/catalog-index.js
2285
+ var vectorsFileOf = (file) => join3(dirname2(file), "vectors.sqlite");
2286
+ var notInstalled = (e, pkg) => e.code === "ERR_MODULE_NOT_FOUND" && String(e.message).includes(pkg);
2287
+ async function embedderOf(root, setting, load = () => import("./chunk-TANFV3MZ.js")) {
2288
+ if (setting === "off")
2289
+ return { why: "turned off for this command" };
2290
+ if (setting && setting !== "auto")
2291
+ return { embedder: setting };
2292
+ let embed;
2293
+ try {
2294
+ embed = await load();
2295
+ } catch (e) {
2296
+ if (notInstalled(e, "@maycombhq/embed"))
2297
+ return {
2298
+ why: "search by meaning is an optional plugin that isn't installed (@maycombhq/embed)"
2299
+ };
2300
+ return { why: `the embedding model couldn't be read: ${e.message.split("\n")[0]}` };
2301
+ }
2302
+ try {
2303
+ const { embedderFor } = embed;
2304
+ const r = await embedderFor(root);
2305
+ return r.embedder ? { embedder: r.embedder } : { why: r.state.problem ?? "no embedding model" };
2306
+ } catch (e) {
2307
+ return { why: `the embedding model couldn't be read: ${e.message.split("\n")[0]}` };
2308
+ }
2309
+ }
2310
+ async function openIndex(root, o = {}) {
2311
+ const file = o.file ?? join3(stateDirOf(root), ".index", "catalog.sqlite");
2312
+ await mkdir(dirname2(file), { recursive: true });
2313
+ const files = await listSources(root);
2314
+ const db = openDb(file);
2315
+ try {
2316
+ const fresh = ensureSchema(db, o.rebuild === true);
2317
+ const u = await updateIndex(db, root, files, { force: fresh });
2318
+ const rebuilt = fresh || u.changed.length > 0 || u.removed.length > 0;
2319
+ attachVectors(db, vectorsFileOf(file));
2320
+ const e = await embedderOf(root, o.embeddings, o.loadEmbed);
2321
+ const ix = new CatalogIndex(db, file, u.problems, rebuilt, u, e.embedder, e.why);
2322
+ if (e.embedder && (o.embedOnOpen ?? 500) > 0)
2323
+ await ix.embed({ max: o.embedOnOpen ?? 500 }).catch((err) => {
2324
+ ix.meaningProblem = `the embedding model couldn't run: ${err.message.split("\n")[0]}`;
2325
+ });
2326
+ return ix;
2327
+ } catch (e) {
2328
+ db.close();
2329
+ throw e;
2330
+ }
2331
+ }
2332
+ var CatalogIndex = class {
2333
+ db;
2334
+ file;
2335
+ problems;
2336
+ rebuilt;
2337
+ update;
2338
+ embedder;
2339
+ noEmbedder;
2340
+ constructor(db, file, problems, rebuilt, update, embedder, noEmbedder) {
2341
+ this.db = db;
2342
+ this.file = file;
2343
+ this.problems = problems;
2344
+ this.rebuilt = rebuilt;
2345
+ this.update = update;
2346
+ this.embedder = embedder;
2347
+ this.noEmbedder = noEmbedder;
2348
+ }
2349
+ /** Set when the model is configured but couldn't run. */
2350
+ meaningProblem;
2351
+ matrix;
2352
+ rows;
2353
+ close() {
2354
+ this.db.close();
2355
+ }
2356
+ /** Whether "similar in meaning" is on: a model, and a vector for every node (ADR 0116). */
2357
+ meaning() {
2358
+ if (this.meaningProblem)
2359
+ return { ...meaningState(this.db, void 0, this.meaningProblem), model: this.embedder?.id };
2360
+ return meaningState(this.db, this.embedder, this.noEmbedder);
2361
+ }
2362
+ /** Embed the nodes that have no vector yet (all, or at most `max`). */
2363
+ async embed(o = {}) {
2364
+ if (!this.embedder)
2365
+ return 0;
2366
+ const n = await embedMissing(this.db, this.embedder, o);
2367
+ if (n) {
2368
+ this.matrix = void 0;
2369
+ this.rows = void 0;
2370
+ }
2371
+ return n;
2372
+ }
2373
+ /** Embed what an input will be compared by (its title and paragraphs), so a selection can. */
2374
+ async prepare(input) {
2375
+ if (!this.embedder || this.meaningProblem || !this.meaning().on)
2376
+ return;
2377
+ const i = typeof input === "string" ? { text: input } : input;
2378
+ await queryVectors(this.db, this.embedder, queryChunks(i)).catch((err) => {
2379
+ this.meaningProblem = `the embedding model couldn't run: ${err.message.split("\n")[0]}`;
2380
+ });
2381
+ }
2382
+ /**
2383
+ * The nodes nearest an input in meaning, best first: a ranking, never a link (ADR 0116).
2384
+ * Undefined, with why, when similarity is off or the input wasn't prepared.
2385
+ */
2386
+ similar(input, o = {}) {
2387
+ const state = this.meaning();
2388
+ if (!state.on || !this.embedder)
2389
+ return { off: state.note };
2390
+ let queries;
2391
+ let exclude;
2392
+ if (typeof input === "object" && "key" in input) {
2393
+ this.matrix ??= loadMatrix(this.db, this.embedder.id);
2394
+ const at = this.matrix.keys.indexOf(input.key);
2395
+ if (at < 0)
2396
+ return { off: `${input.key} has no vector` };
2397
+ const d = this.matrix.dim;
2398
+ queries = [
2399
+ { q: this.matrix.q.subarray(at * d, (at + 1) * d), scale: this.matrix.scales[at] }
2400
+ ];
2401
+ exclude = /* @__PURE__ */ new Set([input.key]);
2402
+ } else {
2403
+ const i = typeof input === "string" ? { text: input } : input;
2404
+ const chunks = queryChunks(i);
2405
+ const vs = chunks.map((c) => cachedQuery(this.db, this.embedder.id, textSha(c)));
2406
+ if (!chunks.length)
2407
+ return { hits: [] };
2408
+ if (vs.some((v) => !v))
2409
+ return { off: "the input wasn't embedded (prepare it first)" };
2410
+ queries = vs;
2411
+ }
2412
+ this.matrix ??= loadMatrix(this.db, this.embedder.id);
2413
+ return {
2414
+ hits: nearest(this.matrix, queries, {
2415
+ floor: o.floor ?? this.embedder.floor ?? MEANING_FLOOR,
2416
+ limit: o.limit ?? 30,
2417
+ ...o.kinds ? { kinds: o.kinds } : {},
2418
+ ...exclude ? { exclude } : {}
2419
+ })
2420
+ };
2421
+ }
2422
+ /**
2423
+ * How close in meaning each of these nodes is to the texts (ADR 0132 addendum): the best
2424
+ * cosine of a node's stored vector over the texts' vectors. The texts are embedded as queries
2425
+ * once and kept; a node is never embedded here (a node with no vector is left out). Off, with
2426
+ * why, when similarity is off.
2427
+ */
2428
+ async closeness(texts, keys) {
2429
+ const state = this.meaning();
2430
+ if (!state.on || !this.embedder)
2431
+ return { off: state.note };
2432
+ const asked = [...new Set(texts.map((t) => t.replace(/\s+/g, " ").trim()).filter((t) => t))];
2433
+ if (!asked.length)
2434
+ return { cos: /* @__PURE__ */ new Map() };
2435
+ let queries;
2436
+ try {
2437
+ queries = await queryVectors(this.db, this.embedder, asked);
2438
+ } catch (err) {
2439
+ this.meaningProblem = `the embedding model couldn't run: ${err.message.split("\n")[0]}`;
2440
+ return { off: this.meaning().note };
2441
+ }
2442
+ this.matrix ??= loadMatrix(this.db, this.embedder.id);
2443
+ this.rows ??= new Map(this.matrix.keys.map((k, i) => [k, i]));
2444
+ return { cos: cosines(this.matrix, this.rows, queries, keys) };
2445
+ }
2446
+ /**
2447
+ * The estate's names (ADR 0116): each id of these kinds with every name and alias it goes by,
2448
+ * in id order, for a model to map an input's names to (the expansion's digest).
2449
+ */
2450
+ names(kinds) {
2451
+ const rows = this.db.prepare(`SELECT DISTINCT d.id AS id, d.kind AS kind, n.name AS name FROM nodes d JOIN names n ON n.id = d.id
2452
+ WHERE d.kind IN (${kinds.map(() => "?").join(", ")}) ORDER BY d.id, d.kind, n.name`).all(...kinds);
2453
+ const out = /* @__PURE__ */ new Map();
2454
+ for (const r of rows) {
2455
+ const k = `${r.kind}|${r.id}`;
2456
+ const e = out.get(k) ?? { id: r.id, kind: r.kind, names: [] };
2457
+ if (r.name !== r.id && !e.names.includes(r.name))
2458
+ e.names.push(r.name);
2459
+ out.set(k, e);
2460
+ }
2461
+ return [...out.values()];
2462
+ }
2463
+ /** selectContext, with the input embedded first when similarity is on (ADR 0116). */
2464
+ async select(input, o) {
2465
+ await this.prepare(input);
2466
+ return selectContext(this, input, o);
2467
+ }
2468
+ /**
2469
+ * The index's version: a hash of every file it read, so the same files give the same version
2470
+ * on every machine. Selections are cached under it.
2471
+ */
2472
+ get version() {
2473
+ return this.db.prepare("SELECT value FROM meta WHERE key = 'generation'").get()?.value ?? "empty";
2474
+ }
2475
+ /** Full-text search over every node (ADR 0107 point 2): names and ids rank above prose. */
2476
+ search(text, o = {}) {
2477
+ return searchIndex(this, text, o);
2478
+ }
2479
+ /** The context a step should see for its input, fitted to a budget (ADR 0107 point 3). */
2480
+ selectContext(input, o) {
2481
+ return selectContext(this, input, o);
2482
+ }
2483
+ /** The nodes with this id, whatever their kind (a catalog record, a flow, an item...). */
2484
+ nodes(id) {
2485
+ return nodesById(this, id);
2486
+ }
2487
+ /** What a node links to and what links to it, each with how the link was made. */
2488
+ links(key) {
2489
+ return linksOf(this, key);
2490
+ }
2491
+ /** Records matching a query in the catalog query language, ordered by kind and id. */
2492
+ query(text, o = {}) {
2493
+ const { where, params } = compileQuery(parseQuery(text));
2494
+ const total = this.db.prepare(`SELECT count(*) AS n FROM records r WHERE ${where}`).get(...params).n;
2495
+ const rows = this.db.prepare(`SELECT kind, id, status, confidence, name, system, file FROM records r WHERE ${where} ORDER BY kind, id LIMIT ? OFFSET ?`).all(...params, o.limit ?? 100, o.offset ?? 0);
2496
+ return { rows, total };
2497
+ }
2498
+ /** The full records for rows. */
2499
+ records(rows) {
2500
+ const get = this.db.prepare("SELECT json FROM records WHERE kind = ? AND id = ?");
2501
+ return rows.map((r) => JSON.parse(get.get(r.kind, r.id).json));
2502
+ }
2503
+ /** One record, or undefined. */
2504
+ get(kind, id) {
2505
+ const row = this.db.prepare("SELECT json FROM records WHERE kind = ? AND id = ?").get(kind, id);
2506
+ return row ? JSON.parse(row.json) : void 0;
2507
+ }
2508
+ /** Every record with this id, whatever its kind. */
2509
+ find(id) {
2510
+ return this.db.prepare("SELECT json FROM records WHERE id = ? ORDER BY kind").all(id).map((r) => JSON.parse(r.json));
2511
+ }
2512
+ /** The system a record belongs to, as worked out when the index was built. */
2513
+ systemOf(kind, id) {
2514
+ return this.db.prepare("SELECT system FROM records WHERE kind = ? AND id = ?").get(kind, id)?.system ?? void 0;
2515
+ }
2516
+ /** Records that refer to id: which record, by which field. */
2517
+ referencesTo(id) {
2518
+ return this.db.prepare("SELECT from_kind AS kind, from_id AS id, field FROM refs WHERE to_id = ? ORDER BY from_kind, from_id, field").all(id);
2519
+ }
2520
+ /** Count per kind, with how many are flagged stale. */
2521
+ kinds() {
2522
+ return this.db.prepare("SELECT kind, count(*) AS count, sum(stale) AS stale FROM records GROUP BY kind ORDER BY kind").all();
2523
+ }
2524
+ /** Records of one kind, filtered and paged by id (the API's listRecords). */
2525
+ list(kind, o = {}) {
2526
+ const where = ["kind = ?"];
2527
+ const params = [kind];
2528
+ if (o.status) {
2529
+ where.push("status = ?");
2530
+ params.push(o.status);
2531
+ }
2532
+ if (o.system) {
2533
+ where.push("system = ?");
2534
+ params.push(o.system);
2535
+ }
2536
+ if (o.q) {
2537
+ where.push("(lower(id) LIKE ? OR lower(name) LIKE ?)");
2538
+ params.push(`%${o.q.toLowerCase()}%`, `%${o.q.toLowerCase()}%`);
2539
+ }
2540
+ const total = this.db.prepare(`SELECT count(*) AS n FROM records WHERE ${where.join(" AND ")}`).get(...params).n;
2541
+ const after = o.cursor ? Buffer.from(o.cursor, "base64url").toString("utf8") : void 0;
2542
+ const limit = Math.min(Math.max(o.limit ?? 50, 1), 500);
2543
+ const rows = this.db.prepare(`SELECT id, json FROM records WHERE ${where.join(" AND ")}${after !== void 0 ? " AND id > ?" : ""} ORDER BY id LIMIT ?`).all(...params, ...after !== void 0 ? [after] : [], limit + 1);
2544
+ const page = rows.slice(0, limit);
2545
+ return {
2546
+ items: page.map((r) => JSON.parse(r.json)),
2547
+ total,
2548
+ ...rows.length > limit ? { next: Buffer.from(page[page.length - 1].id, "utf8").toString("base64url") } : {}
2549
+ };
2550
+ }
2551
+ };
2552
+
2553
+ // packages/index/dist/graph.js
2554
+ function graph(ix, o = {}) {
2555
+ const hops = ix.db.prepare(`SELECT id, json_extract(json, '$.from') AS "from", json_extract(json, '$.to') AS "to", json_extract(json, '$.via') AS via,
2556
+ json_extract(json, '$.pattern.code') AS pattern, confidence, status
2557
+ FROM records WHERE kind = 'Interface'${o.status ? " AND status = ?" : ""} ORDER BY id`).all(...o.status ? [o.status] : []).map((h) => ({ ...h, pattern: h.pattern ?? void 0 }));
2558
+ for (const c of ix.db.prepare(`SELECT id, json_extract(json, '$.uses') AS uses, confidence, status FROM records
2559
+ WHERE kind = 'Component' AND json_extract(json, '$.uses') IS NOT NULL${o.status ? " AND status = ?" : ""} ORDER BY id`).all(...o.status ? [o.status] : []))
2560
+ for (const to of JSON.parse(c.uses))
2561
+ hops.push({
2562
+ id: `uses.${c.id}.${to}`,
2563
+ from: c.id,
2564
+ to,
2565
+ via: "uses",
2566
+ confidence: c.confidence,
2567
+ status: c.status
2568
+ });
2569
+ const nodeRows = ix.db.prepare("SELECT kind, id, name, status, system FROM records WHERE kind IN ('System', 'Component') ORDER BY kind DESC, id").all();
2570
+ const nodes = new Map(nodeRows.map((n) => [n.id, n]));
2571
+ const systemOf = (id) => nodes.get(id)?.system ?? void 0;
2572
+ let keep;
2573
+ if (o.focus) {
2574
+ const start = nodes.get(o.focus);
2575
+ if (!start)
2576
+ return { nodes: [], edges: [] };
2577
+ keep = new Set(start.kind === "System" ? [
2578
+ start.id,
2579
+ ...nodeRows.filter((n) => n.kind === "Component" && n.system === start.id).map((n) => n.id)
2580
+ ] : [start.id]);
2581
+ let frontier = [...keep];
2582
+ for (let d = 0; d < (o.depth ?? 1); d++) {
2583
+ const next = [];
2584
+ for (const h of hops) {
2585
+ if (frontier.includes(h.from) && !keep.has(h.to))
2586
+ next.push(h.to);
2587
+ if (frontier.includes(h.to) && !keep.has(h.from))
2588
+ next.push(h.from);
2589
+ }
2590
+ for (const n of next)
2591
+ keep.add(n);
2592
+ frontier = next;
2593
+ }
2594
+ } else
2595
+ keep = /* @__PURE__ */ new Set([...nodes.keys(), ...hops.flatMap((h) => [h.from, h.to])]);
2596
+ for (const id of [...keep]) {
2597
+ const s = systemOf(id);
2598
+ if (s && nodes.get(id)?.kind === "Component")
2599
+ keep.add(s);
2600
+ }
2601
+ const outNodes = [...keep].filter((id) => nodes.has(id)).map((id) => {
2602
+ const n = nodes.get(id);
2603
+ return {
2604
+ id,
2605
+ kind: n.kind,
2606
+ label: n.name,
2607
+ status: n.status,
2608
+ ...n.kind === "Component" && n.system ? { system: n.system } : {}
2609
+ };
2610
+ }).sort((a, b) => a.kind === b.kind ? a.id < b.id ? -1 : 1 : a.kind === "System" ? -1 : 1);
2611
+ const edges = hops.filter((h) => keep.has(h.from) && keep.has(h.to)).map(({ pattern, ...e }) => ({ ...e, ...pattern ? { pattern } : {} }));
2612
+ return { nodes: outNodes, edges };
2613
+ }
2614
+ function impact(ix, rootId, depth = 4) {
2615
+ const roots = ix.find(rootId);
2616
+ if (roots.length === 0)
2617
+ return void 0;
2618
+ const root = roots[0];
2619
+ const recordOf = (kind, id) => ix.get(kind, id);
2620
+ const anyRecord = (id) => ix.find(id)[0];
2621
+ const step = (r) => {
2622
+ const out = [];
2623
+ const id = String(r.id);
2624
+ if (r.kind === "Column" || r.kind === "IndexConstraint")
2625
+ out.push({ kind: "Table", id: String(r.table), reason: `${id} is part of it` });
2626
+ if (r.kind === "Table") {
2627
+ for (const c of r.readers ?? [])
2628
+ out.push({ kind: "", id: c, reason: `reads ${id}` });
2629
+ for (const c of r.writers ?? [])
2630
+ if (!(r.readers ?? []).includes(c))
2631
+ out.push({ kind: "", id: c, reason: `writes ${id}` });
2632
+ }
2633
+ if (r.kind === "Interface") {
2634
+ if (r.sync === "async")
2635
+ out.push({
2636
+ kind: "",
2637
+ id: String(r.to),
2638
+ reason: `receives ${String(r.normalizedPath)} from ${String(r.from)}`
2639
+ });
2640
+ else
2641
+ out.push({
2642
+ kind: "",
2643
+ id: String(r.from),
2644
+ reason: `calls ${String(r.to)}${r.method ? ` (${String(r.method)} ${String(r.normalizedPath)})` : ""}`
2645
+ });
2646
+ }
2647
+ const isAsync = (interfaceId) => recordOf("Interface", interfaceId)?.sync === "async";
2648
+ for (const ref of ix.referencesTo(id)) {
2649
+ const k = ref.kind;
2650
+ const f = ref.field;
2651
+ if (k === "Interface" && f === "endpoint")
2652
+ out.push({
2653
+ kind: k,
2654
+ id: ref.id,
2655
+ reason: isAsync(ref.id) ? `carries ${id}` : `calls ${id}`
2656
+ });
2657
+ else if (k === "Interface" && f === "to" && r.kind !== "Endpoint" && !isAsync(ref.id))
2658
+ out.push({ kind: k, id: ref.id, reason: `calls ${id}` });
2659
+ else if (k === "Interface" && f === "from" && r.kind !== "Endpoint" && isAsync(ref.id))
2660
+ out.push({ kind: k, id: ref.id, reason: `carries events published by ${id}` });
2661
+ else if (k === "Interface" && f === "route")
2662
+ out.push({ kind: k, id: ref.id, reason: `goes through ${id}` });
2663
+ else if (k === "Endpoint" && f === "schemaRefs[]")
2664
+ out.push({ kind: k, id: ref.id, reason: `uses schema ${id}` });
2665
+ else if (k === "Setting" && f === "target")
2666
+ out.push({ kind: k, id: ref.id, reason: `is a setting for calls to ${id}` });
2667
+ else if (k === "FieldMapping" && f === "interface")
2668
+ out.push({ kind: k, id: ref.id, reason: `maps fields of ${id}` });
2669
+ else if (k === "IndexConstraint" && f === "references.table")
2670
+ out.push({ kind: k, id: ref.id, reason: `is a foreign key into ${id}` });
2671
+ }
2672
+ if (r.kind === "System")
2673
+ for (const c of ix.referencesTo(id).filter((x) => x.kind === "Component" && x.field === "system"))
2674
+ out.push(...step(recordOf("Component", c.id) ?? {}).map((s) => ({ ...s })));
2675
+ return out;
2676
+ };
2677
+ const ownerOf = (kind, id) => {
2678
+ const s = ix.systemOf(kind, id);
2679
+ const sys = s ? recordOf("System", s) : void 0;
2680
+ return sys?.owner;
2681
+ };
2682
+ const seen = /* @__PURE__ */ new Set([`${root.kind}|${root.id}`]);
2683
+ const affected = [];
2684
+ let frontier = [
2685
+ { r: root, path: [root.id] }
2686
+ ];
2687
+ for (let d = 0; d < depth && frontier.length > 0; d++) {
2688
+ const next = [];
2689
+ for (const { r, path } of frontier) {
2690
+ for (const s of step(r)) {
2691
+ const rec = s.kind ? recordOf(s.kind, s.id) : anyRecord(s.id);
2692
+ const kind = rec?.kind ?? s.kind;
2693
+ const key = `${kind}|${s.id}`;
2694
+ if (seen.has(key) || !rec)
2695
+ continue;
2696
+ seen.add(key);
2697
+ const p = [...path, s.id];
2698
+ const owner = ownerOf(kind, s.id);
2699
+ affected.push({ id: s.id, kind, path: p, reason: s.reason, ...owner ? { owner } : {} });
2700
+ next.push({ r: rec, path: p });
2701
+ }
2702
+ }
2703
+ frontier = next;
2704
+ }
2705
+ const gaps = [];
2706
+ const gapKeys = /* @__PURE__ */ new Set();
2707
+ const addGap = (g) => {
2708
+ const k = `${g.record}|${g.field}`;
2709
+ if (gapKeys.has(k))
2710
+ return;
2711
+ gapKeys.add(k);
2712
+ gaps.push(g);
2713
+ };
2714
+ for (const a of affected) {
2715
+ const rec = recordOf(a.kind, a.id);
2716
+ if (a.kind === "Interface" && rec.confidence === "inferred")
2717
+ addGap({
2718
+ record: a.id,
2719
+ field: "confidence",
2720
+ question: `Only one end of this call was seen. Does ${String(rec.from)} really call ${String(rec.to)}?`
2721
+ });
2722
+ const s = ix.systemOf(a.kind, a.id);
2723
+ if (s && (a.kind === "Component" || a.kind === "System")) {
2724
+ const sys = recordOf("System", s);
2725
+ if (sys && !sys.owner)
2726
+ addGap({ record: s, field: "owner", question: `Who owns ${sys.name ?? s}?` });
2727
+ }
2728
+ if (a.kind === "Component")
2729
+ for (const u of ix.db.prepare("SELECT json FROM records WHERE kind = 'Unresolved' AND json_extract(json, '$.owner') = ? ORDER BY id").all(a.id)) {
2730
+ const x = JSON.parse(u.json);
2731
+ addGap({
2732
+ record: x.id,
2733
+ field: "raw",
2734
+ question: `${a.id} has a loose end Atticus couldn't follow (${x.raw}). Where does it go?`
2735
+ });
2736
+ }
2737
+ }
2738
+ return { root: root.id, affected, gaps };
2739
+ }
2740
+
2741
+ // packages/index/dist/ask.js
2742
+ var OUTSIDE = "Outside";
2743
+ var PLAIN = /^[a-z0-9][a-z0-9._-]*$/;
2744
+ function askLookup(ix) {
2745
+ return (word) => {
2746
+ const w = word.toLowerCase();
2747
+ const rec = ix.find(w)[0];
2748
+ if (rec)
2749
+ return { id: rec.id, kind: rec.kind };
2750
+ if (!PLAIN.test(w) || STOP2.has(w) || KIND_WORDS[w])
2751
+ return void 0;
2752
+ const has = (q) => {
2753
+ try {
2754
+ return ix.query(q, { limit: 1 }).rows.length > 0;
2755
+ } catch {
2756
+ return false;
2757
+ }
2758
+ };
2759
+ if (has(`kind:interface to:${w}`) || has(`kind:interface from:${w}`))
2760
+ return { id: w, kind: OUTSIDE };
2761
+ try {
2762
+ const t = ix.query(`kind:table name:${w}`, { limit: 1 }).rows[0];
2763
+ if (t)
2764
+ return { id: t.id, kind: t.kind };
2765
+ } catch {
2766
+ }
2767
+ return void 0;
2768
+ };
2769
+ }
2770
+ var KIND_WORDS = {
2771
+ interfaces: "interface",
2772
+ interface: "interface",
2773
+ calls: "interface",
2774
+ tables: "table",
2775
+ table: "table",
2776
+ endpoints: "endpoint",
2777
+ apis: "endpoint",
2778
+ components: "component",
2779
+ services: "component",
2780
+ systems: "system",
2781
+ settings: "setting",
2782
+ columns: "column",
2783
+ topics: "topic",
2784
+ features: "capability",
2785
+ migrations: "migration",
2786
+ deployments: "deployment"
2787
+ };
2788
+ var STOP2 = new Set("a an the of in on to for by is are was what which who whom how does do did if when me show list find all any with that this there it its and or please".split(" "));
2789
+ function named(words, lookup) {
2790
+ for (const w of [...words].sort((a, b) => b.length - a.length)) {
2791
+ const hit = lookup(w);
2792
+ if (hit)
2793
+ return hit;
2794
+ }
2795
+ return void 0;
2796
+ }
2797
+ function translate(question, lookup) {
2798
+ const q = question.trim();
2799
+ const lower = q.toLowerCase();
2800
+ const words = lower.replace(/[?!,;"'`()]/g, " ").split(/\s+/).map((w) => w.replace(/\.$/, "")).filter(Boolean);
2801
+ const rec = named(words, lookup);
2802
+ const has = (re) => re.test(lower);
2803
+ if (rec?.kind === OUTSIDE)
2804
+ return {
2805
+ action: "query",
2806
+ query: `kind:interface to:${rec.id}`,
2807
+ explanation: `Read as: the calls to ${rec.id}, a system outside the estate that the code calls.`
2808
+ };
2809
+ if (has(/\b(break|breaks|affected|impacted|impact|reach|reaches|depends? on)\b/)) {
2810
+ if (rec)
2811
+ return {
2812
+ action: "impact",
2813
+ id: rec.id,
2814
+ query: `impact ${rec.id}`,
2815
+ explanation: `Read as: what a change to ${rec.id} reaches, hop by hop, with owners.`
2816
+ };
2817
+ }
2818
+ if (rec && has(/\bwhat does\b.*\b(call|use|depend|talk)/))
2819
+ return {
2820
+ action: "query",
2821
+ query: `kind:interface from:${rec.id}`,
2822
+ explanation: `Read as: the interfaces ${rec.id} calls.`
2823
+ };
2824
+ if (rec && has(/\b(who|what|which)\b.*\b(calls?|uses?|consumes?|reads?|writes?)\b/)) {
2825
+ if (rec.kind === "Table" || rec.kind === "Column")
2826
+ return {
2827
+ action: "query",
2828
+ query: `refby:${rec.id} kind:component`,
2829
+ explanation: `Read as: the components that read or write ${rec.id}.`
2830
+ };
2831
+ return {
2832
+ action: "query",
2833
+ query: `kind:interface to:${rec.id}`,
2834
+ explanation: `Read as: the interfaces whose callee is ${rec.id}.`
2835
+ };
2836
+ }
2837
+ if (rec && has(/\bwho owns\b|\bowner of\b/))
2838
+ return {
2839
+ action: "owner",
2840
+ id: rec.id,
2841
+ query: rec.id,
2842
+ explanation: `Read as: the owner recorded for ${rec.id}.`
2843
+ };
2844
+ const terms = [];
2845
+ const system = rec?.kind === "System" ? rec.id : void 0;
2846
+ if (has(/\b(personal|sensitive|pii|confidential)\b/)) {
2847
+ terms.push("kind:column");
2848
+ if (system)
2849
+ terms.push(`system:${system}`);
2850
+ terms.push("classification:personal*");
2851
+ return {
2852
+ action: "query",
2853
+ query: terms.join(" "),
2854
+ explanation: `Read as: columns tagged as personal data${system ? ` in ${system}` : ""}. Untagged columns don't show.`
2855
+ };
2856
+ }
2857
+ const kindWord = words.find((w) => KIND_WORDS[w]);
2858
+ if (kindWord) {
2859
+ terms.push(`kind:${KIND_WORDS[kindWord]}`);
2860
+ if (has(/\bgateway\b/))
2861
+ terms.push("via:gateway");
2862
+ else if (has(/\b(event bus|event-bus|events?)\b/))
2863
+ terms.push("via:event-bus");
2864
+ if (system)
2865
+ terms.push(`system:${system}`);
2866
+ else if (rec)
2867
+ terms.push(`refs:${rec.id}`);
2868
+ if (has(/\b(without|no|missing) (a |an )?pattern\b/))
2869
+ terms.push("missing:pattern");
2870
+ if (has(/\b(inferred|unconfirmed|one end)\b/))
2871
+ terms.push("confidence:inferred");
2872
+ return {
2873
+ action: "query",
2874
+ query: terms.join(" "),
2875
+ explanation: `Read as: ${KIND_WORDS[kindWord]} records${terms.length > 1 ? " where " + terms.slice(1).join(", ") : ""}.`
2876
+ };
2877
+ }
2878
+ const text = words.filter((w) => !STOP2.has(w));
2879
+ const query = rec ? rec.id : text.join(" ");
2880
+ return {
2881
+ action: "query",
2882
+ query: query || q,
2883
+ explanation: rec ? `Read as: a search for ${rec.id}.` : `No record in the catalog is named in the question, so this is a word search for "${text.join(" ")}".`
2884
+ };
2885
+ }
2886
+
2887
+ // packages/index/dist/tools.js
2888
+ import { existsSync, readdirSync } from "node:fs";
2889
+ import { readdir as readdir2, readFile as readFile3, realpath, stat as stat2 } from "node:fs/promises";
2890
+ import { isAbsolute, join as join4, relative as relative2, resolve, sep as sep2 } from "node:path";
2891
+ var ContextToolError = class extends Error {
2892
+ constructor(message) {
2893
+ super(message);
2894
+ this.name = "ToolError";
2895
+ }
2896
+ };
2897
+ var CONTEXT_TOOL_NAMES = [
2898
+ "search",
2899
+ "show",
2900
+ "walk",
2901
+ "show_code",
2902
+ "grep",
2903
+ "files",
2904
+ "outline",
2905
+ "similar"
2906
+ ];
2907
+ var CODE_TOOL_NAMES = ["grep", "files", "outline"];
2908
+ var CONTEXT_TOOL_BOUNDS = {
2909
+ searchLimit: 20,
2910
+ walkHops: 2,
2911
+ walkLimit: 40,
2912
+ codeContext: 20,
2913
+ codeCites: 3,
2914
+ codeFileBytes: 2e6,
2915
+ /** grep (ADR 0118): hits a call shows at most, and by default. */
2916
+ grepLimit: 100,
2917
+ grepDefault: 30,
2918
+ /** The longest pattern grep takes, and the most of a hit's line it shows. */
2919
+ grepPattern: 200,
2920
+ grepLineChars: 200,
2921
+ /** Files one grep or files call walks at most, and bytes grep reads at most. */
2922
+ walkFiles: 2e4,
2923
+ grepBytes: 64e6,
2924
+ filesLimit: 500,
2925
+ filesDefault: 200,
2926
+ outlineLines: 300
2927
+ };
2928
+ var CONTEXT_TOOLS = [
2929
+ {
2930
+ name: "search",
2931
+ description: "Search everything Atticus knows by words and meaning: catalog records, knowledge items, flows and their steps, changes, answered questions, wiki sections and documents. Names and ids rank first; with a local embedding model, what is similar in meaning ranks too. Each hit gives its kind, id, name and the file it was read from.",
2932
+ inputSchema: {
2933
+ type: "object",
2934
+ properties: {
2935
+ text: { type: "string", description: "The words to look for." },
2936
+ by: {
2937
+ type: "string",
2938
+ enum: ["words", "meaning", "both"],
2939
+ description: "words: the words and the names they are made of; meaning: similar in meaning (needs the local model); both (the default)."
2940
+ },
2941
+ kinds: {
2942
+ type: "array",
2943
+ items: { type: "string" },
2944
+ description: "Only these kinds, for example System, Flow, ProcessDefinition, WikiSection."
2945
+ },
2946
+ limit: { type: "integer", minimum: 1, maximum: CONTEXT_TOOL_BOUNDS.searchLimit }
2947
+ },
2948
+ required: ["text"],
2949
+ additionalProperties: false
2950
+ }
2951
+ },
2952
+ {
2953
+ name: "show",
2954
+ description: "Show one record, flow, flow step (flow-id#n), knowledge item, change, answered question or wiki section in full, by its id, with the file it was read from and whether it was verified.",
2955
+ inputSchema: {
2956
+ type: "object",
2957
+ properties: {
2958
+ id: { type: "string", description: "The id, as the pack or a search names it." }
2959
+ },
2960
+ required: ["id"],
2961
+ additionalProperties: false
2962
+ }
2963
+ },
2964
+ {
2965
+ name: "walk",
2966
+ description: "Walk the links from one id: what it calls, contains, names or is named by, one line each with how the link was made, at most two hops.",
2967
+ inputSchema: {
2968
+ type: "object",
2969
+ properties: {
2970
+ id: { type: "string" },
2971
+ hops: { type: "integer", minimum: 1, maximum: CONTEXT_TOOL_BOUNDS.walkHops },
2972
+ kinds: { type: "array", items: { type: "string" }, description: "Only these kinds." },
2973
+ limit: { type: "integer", minimum: 1, maximum: CONTEXT_TOOL_BOUNDS.walkLimit }
2974
+ },
2975
+ required: ["id"],
2976
+ additionalProperties: false
2977
+ }
2978
+ },
2979
+ {
2980
+ name: "show_code",
2981
+ description: "Show the code a fact cites: a few lines around each file and line a record or flow step cites, read from the workspace checkout. Give the fact's id; or a repo, path and line that a fact cites or that a grep or outline hit of this run returned.",
2982
+ inputSchema: {
2983
+ type: "object",
2984
+ properties: {
2985
+ id: { type: "string", description: "A record or flow step (flow-id#n) id." },
2986
+ repo: { type: "string" },
2987
+ path: { type: "string" },
2988
+ line: { type: "integer", minimum: 1 },
2989
+ context: {
2990
+ type: "integer",
2991
+ minimum: 0,
2992
+ maximum: CONTEXT_TOOL_BOUNDS.codeContext,
2993
+ description: "Lines before and after the cited line (default 6)."
2994
+ }
2995
+ },
2996
+ additionalProperties: false
2997
+ }
2998
+ },
2999
+ {
3000
+ name: "grep",
3001
+ description: "Search the repos' code for the lines that match what you give: literal text, or a regular expression with regex true. Each hit is repo:path:line: the line. Use it to find code the material doesn't show, for example an in-house annotation, a wrapper or where a value is set. A hit is a pointer, not a fact: show_code shows the lines around it. Files .gitignore ignores are skipped.",
3002
+ inputSchema: {
3003
+ type: "object",
3004
+ properties: {
3005
+ pattern: {
3006
+ type: "string",
3007
+ maxLength: CONTEXT_TOOL_BOUNDS.grepPattern,
3008
+ description: "The text to find, or a regular expression (JavaScript syntax)."
3009
+ },
3010
+ regex: { type: "boolean", description: "The pattern is a regular expression." },
3011
+ ignoreCase: { type: "boolean" },
3012
+ repo: { type: "string", description: "Only this repo. Default: every cloned repo." },
3013
+ paths: {
3014
+ type: "string",
3015
+ description: "Only paths that match this glob, for example src/**/*.java; a glob with no / matches at any depth."
3016
+ },
3017
+ limit: { type: "integer", minimum: 1, maximum: CONTEXT_TOOL_BOUNDS.grepLimit }
3018
+ },
3019
+ required: ["pattern"],
3020
+ additionalProperties: false
3021
+ }
3022
+ },
3023
+ {
3024
+ name: "files",
3025
+ description: "List the repos' files whose paths match a glob (src/**/*.java; a glob with no / matches at any depth), each as repo:path. Files .gitignore ignores are left out.",
3026
+ inputSchema: {
3027
+ type: "object",
3028
+ properties: {
3029
+ glob: { type: "string" },
3030
+ repo: { type: "string", description: "Only this repo. Default: every cloned repo." },
3031
+ limit: { type: "integer", minimum: 1, maximum: CONTEXT_TOOL_BOUNDS.filesLimit }
3032
+ },
3033
+ required: ["glob"],
3034
+ additionalProperties: false
3035
+ }
3036
+ },
3037
+ {
3038
+ name: "outline",
3039
+ description: "Outline one file: its declarations (classes, functions, methods and the like, nested, each with the annotations or decorators written on it) from its syntax tree, each as line: what is declared there; for Markdown its headings, for YAML or properties its keys.",
3040
+ inputSchema: {
3041
+ type: "object",
3042
+ properties: {
3043
+ repo: { type: "string" },
3044
+ path: { type: "string", description: "The file's path in the repo." }
3045
+ },
3046
+ required: ["repo", "path"],
3047
+ additionalProperties: false
3048
+ }
3049
+ },
3050
+ {
3051
+ name: "similar",
3052
+ description: "What is nearest one id in meaning, by the local embedding model: records, items, sections and documents that read alike. A hint where to look, never a link or a fact: show one to read it.",
3053
+ inputSchema: {
3054
+ type: "object",
3055
+ properties: {
3056
+ id: { type: "string" },
3057
+ kinds: { type: "array", items: { type: "string" }, description: "Only these kinds." },
3058
+ limit: { type: "integer", minimum: 1, maximum: CONTEXT_TOOL_BOUNDS.searchLimit }
3059
+ },
3060
+ required: ["id"],
3061
+ additionalProperties: false
3062
+ }
3063
+ }
3064
+ ];
3065
+ var str2 = (v) => typeof v === "string" && v.trim() ? v.trim() : void 0;
3066
+ var int = (v, min, max, fallback) => typeof v === "number" && Number.isFinite(v) ? Math.min(max, Math.max(min, Math.floor(v))) : fallback;
3067
+ var strs = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && !!x.trim()) : void 0;
3068
+ function defaultCodeDir(root, repo) {
3069
+ if (!RecordId.safeParse(repo).success)
3070
+ return void 0;
3071
+ for (const base of [join4(root, "workspace"), join4(stateDirOf(root), "workspace")]) {
3072
+ const d = join4(base, repo);
3073
+ if (existsSync(d))
3074
+ return d;
3075
+ }
3076
+ return void 0;
3077
+ }
3078
+ async function runContextTool(ix, root, name, input, o = {}) {
3079
+ switch (name) {
3080
+ case "search":
3081
+ return searchTool(ix, input);
3082
+ case "show":
3083
+ return showTool(ix, input);
3084
+ case "walk":
3085
+ return walkTool(ix, input);
3086
+ case "show_code":
3087
+ return showCodeTool(ix, root, input, o);
3088
+ case "similar":
3089
+ return similarTool(ix, input);
3090
+ case "grep":
3091
+ case "files":
3092
+ case "outline":
3093
+ return runCodeTool(root, name, input, o);
3094
+ default:
3095
+ throw new ContextToolError(`There is no tool ${name}. The tools: ${CONTEXT_TOOL_NAMES.join(", ")}.`);
3096
+ }
3097
+ }
3098
+ function hitLine(ix, key, extra = "") {
3099
+ const r = ix.db.prepare("SELECT kind, id, name, file FROM nodes WHERE key = ?").get(key);
3100
+ return r ? `- ${r.kind} ${r.id}: ${r.name}${extra} [${r.file}]` : void 0;
3101
+ }
3102
+ async function searchTool(ix, input) {
3103
+ const text = str2(input.text);
3104
+ if (!text)
3105
+ throw new ContextToolError("search needs text: the words to look for.");
3106
+ const kinds = strs(input.kinds);
3107
+ const limit = int(input.limit, 1, CONTEXT_TOOL_BOUNDS.searchLimit, 10);
3108
+ const by = input.by === "words" || input.by === "meaning" ? input.by : "both";
3109
+ if (input.by !== void 0 && !["words", "meaning", "both"].includes(String(input.by)))
3110
+ throw new ContextToolError("search's by is words, meaning or both.");
3111
+ const k = { limit, ...kinds?.length ? { kinds } : {} };
3112
+ const words = by === "meaning" ? [] : ix.search(text, k);
3113
+ let near = [];
3114
+ let off;
3115
+ if (by !== "words") {
3116
+ await ix.prepare(text);
3117
+ const s = ix.similar(text, k);
3118
+ if ("off" in s)
3119
+ off = s.off;
3120
+ else
3121
+ near = s.hits;
3122
+ }
3123
+ if (by === "meaning" && off)
3124
+ return `Similar in meaning is off: ${off}. Search by words instead.`;
3125
+ const cos = new Map(near.map((h) => [h.key, h.cos]));
3126
+ const keys = near.length ? [
3127
+ ...fuse([
3128
+ { name: "words", keys: words.map((h) => h.key) },
3129
+ { name: "meaning", keys: near.map((h) => h.key) }
3130
+ ])
3131
+ ].sort((a, b) => b[1].score - a[1].score || (a[0] < b[0] ? -1 : 1)).map(([key]) => key).slice(0, limit) : words.map((h) => h.key);
3132
+ const lines = keys.map((key) => hitLine(ix, key, cos.has(key) ? ` (similar in meaning, cos ${cos.get(key).toFixed(2)})` : "")).filter((l) => !!l);
3133
+ const note = off && by === "both" ? [`(Similar in meaning is off: ${off}.)`] : [];
3134
+ if (!lines.length)
3135
+ return [`Nothing in the index matches "${text}".`, ...note].join("\n");
3136
+ return [
3137
+ `${lines.length} hit${lines.length === 1 ? "" : "s"} for "${text}", best first:`,
3138
+ ...lines,
3139
+ ...note,
3140
+ "Show one in full with show and its id."
3141
+ ].join("\n");
3142
+ }
3143
+ function similarTool(ix, input) {
3144
+ const id = str2(input.id);
3145
+ if (!id)
3146
+ throw new ContextToolError("similar needs an id.");
3147
+ const kinds = strs(input.kinds);
3148
+ const limit = int(input.limit, 1, CONTEXT_TOOL_BOUNDS.searchLimit, 10);
3149
+ const node = nodesOf(ix, id)[0];
3150
+ const s = ix.similar({ key: node.key }, { limit, ...kinds?.length ? { kinds } : {} });
3151
+ if ("off" in s)
3152
+ return `Similar in meaning is off: ${s.off}.`;
3153
+ if (!s.hits.length)
3154
+ return `Nothing reads like ${id}.`;
3155
+ return [
3156
+ `Nearest ${id} in meaning (a hint where to look, not a link or a fact):`,
3157
+ ...s.hits.map((h) => hitLine(ix, h.key, ` (cos ${h.cos.toFixed(2)})`)).filter((l) => !!l),
3158
+ "Show one in full with show and its id."
3159
+ ].join("\n");
3160
+ }
3161
+ function nodesOf(ix, id) {
3162
+ const nodes = ix.nodes(id);
3163
+ if (!nodes.length)
3164
+ throw new ContextToolError(`There is nothing called ${id} in the index. Search for it by words with search.`);
3165
+ return nodes;
3166
+ }
3167
+ function showTool(ix, input) {
3168
+ const id = str2(input.id);
3169
+ if (!id)
3170
+ throw new ContextToolError("show needs an id.");
3171
+ const nodes = nodesOf(ix, id).slice(0, 3);
3172
+ return nodes.map((n) => {
3173
+ const state = n.review ? `a person ${n.review} it` : n.verified ? "verified" : "not verified by a person or a check";
3174
+ const links = ix.links(n.key).length;
3175
+ return [
3176
+ `${n.kind} ${n.id} (${state}), read from ${n.file}:`,
3177
+ n.detail.trimEnd(),
3178
+ ...links ? [`It has ${links} link${links === 1 ? "" : "s"}; walk shows them.`] : []
3179
+ ].join("\n");
3180
+ }).join("\n\n");
3181
+ }
3182
+ function walkTool(ix, input) {
3183
+ const id = str2(input.id);
3184
+ if (!id)
3185
+ throw new ContextToolError("walk needs an id.");
3186
+ const hops = int(input.hops, 1, CONTEXT_TOOL_BOUNDS.walkHops, 1);
3187
+ const limit = int(input.limit, 1, CONTEXT_TOOL_BOUNDS.walkLimit, 25);
3188
+ const kinds = strs(input.kinds);
3189
+ const starts = nodesOf(ix, id);
3190
+ const seen = new Set(starts.map((n) => n.key));
3191
+ const lines = [];
3192
+ let more = 0;
3193
+ let frontier = starts.map((n) => ({ key: n.key, id: n.id }));
3194
+ for (let h = 1; h <= hops && frontier.length; h++) {
3195
+ const next = [];
3196
+ for (const f of frontier)
3197
+ for (const l of ix.links(f.key)) {
3198
+ if (seen.has(l.key))
3199
+ continue;
3200
+ seen.add(l.key);
3201
+ next.push({ key: l.key, id: l.id });
3202
+ if (kinds?.length && !kinds.includes(l.kind))
3203
+ continue;
3204
+ if (lines.length >= limit) {
3205
+ more++;
3206
+ continue;
3207
+ }
3208
+ const what = l.dir === "out" ? `${f.id} ${l.type} ${l.id}` : `${l.id} ${l.type} ${f.id}`;
3209
+ lines.push(`- hop ${h}: ${what} (${l.how}); ${l.kind} ${l.id}`);
3210
+ }
3211
+ frontier = next.sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0);
3212
+ }
3213
+ if (!lines.length)
3214
+ return `${id} has no links${kinds?.length ? ` to ${kinds.join(", ")}` : ""}.`;
3215
+ return [
3216
+ `Links from ${id}, ${hops} hop${hops === 1 ? "" : "s"}:`,
3217
+ ...lines,
3218
+ ...more ? [`\u2026 and ${more} more; ask for fewer kinds or one hop.`] : []
3219
+ ].join("\n");
3220
+ }
3221
+ function citesOf(ix, root, id) {
3222
+ const out = [];
3223
+ const rows = ix.db.prepare("SELECT kind, repo, path, line FROM evidence WHERE id = ? AND repo IS NOT NULL AND path IS NOT NULL AND line IS NOT NULL ORDER BY rowid").all(id);
3224
+ for (const r of rows)
3225
+ out.push({ repo: r.repo, path: r.path, line: r.line, what: `${r.kind} ${id}` });
3226
+ if (out.length)
3227
+ return out;
3228
+ const m = /^(.+)#(\d+)$/.exec(id);
3229
+ const flowId = m ? m[1] : id;
3230
+ for (const n of ix.nodes(flowId).filter((x) => x.kind === "Flow")) {
3231
+ const f = n.json;
3232
+ const specRel = specsRelOf(root);
3233
+ const specRepo = n.file.startsWith(`${specRel}/`) ? n.file.slice(specRel.length + 1).split("/")[0] : void 0;
3234
+ const parts = m ? [{ what: `step ${id}`, evidence: f.steps?.[Number(m[2]) - 1]?.evidence ?? [] }] : [
3235
+ { what: `trigger of ${flowId}`, evidence: f.trigger?.evidence ?? [] },
3236
+ ...(f.steps ?? []).map((s, i) => ({
3237
+ what: `step ${flowId}#${i + 1}`,
3238
+ evidence: s.evidence ?? []
3239
+ }))
3240
+ ];
3241
+ for (const p of parts)
3242
+ for (const e of p.evidence) {
3243
+ const repo = e.repo ?? specRepo;
3244
+ if (repo && e.path && e.line)
3245
+ out.push({ repo, path: e.path, line: e.line, what: p.what });
3246
+ }
3247
+ }
3248
+ return out;
3249
+ }
3250
+ function isCited(ix, root, c) {
3251
+ const hit = ix.db.prepare("SELECT 1 FROM evidence WHERE repo = ? AND path = ? AND line = ? LIMIT 1").get(c.repo, c.path, c.line);
3252
+ if (hit)
3253
+ return true;
3254
+ const flows = ix.db.prepare("SELECT id FROM nodes WHERE kind = 'Flow'").all();
3255
+ return flows.some((f) => citesOf(ix, root, f.id).some((x) => x.repo === c.repo && x.path === c.path && x.line === c.line));
3256
+ }
3257
+ async function insideClone(base, path) {
3258
+ if (!path || path.includes("\0") || isAbsolute(path) || /^[a-zA-Z]:/.test(path))
3259
+ throw new ContextToolError(`${path} isn't a path inside the repo.`);
3260
+ const full = resolve(base, ...path.split(/[\\/]+/));
3261
+ const rel2 = relative2(base, full);
3262
+ if (!rel2 || rel2.startsWith("..") || isAbsolute(rel2))
3263
+ throw new ContextToolError(`${path} leaves the repo's checkout; Atticus reads nothing outside it.`);
3264
+ let real;
3265
+ let realBase;
3266
+ try {
3267
+ [real, realBase] = await Promise.all([realpath(full), realpath(base)]);
3268
+ } catch {
3269
+ throw new ContextToolError(`${path} isn't in the workspace checkout (is the repo cloned?).`);
3270
+ }
3271
+ const r = relative2(realBase, real);
3272
+ if (!r || r.startsWith("..") || isAbsolute(r) || r.split(sep2).includes(".."))
3273
+ throw new ContextToolError(`${path} leads outside the repo's checkout; Atticus reads nothing outside it.`);
3274
+ return real;
3275
+ }
3276
+ async function excerpt(root, c, around, o) {
3277
+ const base = (o.codeDir ?? ((r) => defaultCodeDir(root, r)))(c.repo);
3278
+ if (!base)
3279
+ return `${c.what} ${c.verb ?? "cites"} ${c.repo}/${c.path}:${c.line}, but ${c.repo} isn't cloned in the workspace.`;
3280
+ let file;
3281
+ try {
3282
+ file = await insideClone(base, c.path);
3283
+ } catch (e) {
3284
+ return `${c.what} ${c.verb ?? "cites"} ${c.repo}/${c.path}:${c.line}: ${e.message}`;
3285
+ }
3286
+ const size = (await stat2(file)).size;
3287
+ if (size > CONTEXT_TOOL_BOUNDS.codeFileBytes)
3288
+ return `${c.what} ${c.verb ?? "cites"} ${c.repo}/${c.path}:${c.line}, a file too big to show (${size} bytes).`;
3289
+ const lines = (await readFile3(file, "utf8")).split(/\r?\n/);
3290
+ if (c.line > lines.length)
3291
+ return `${c.what} ${c.verb ?? "cites"} ${c.repo}/${c.path}:${c.line}, but the file has ${lines.length} lines now: the cite is stale.`;
3292
+ const from = Math.max(1, c.line - around);
3293
+ const to = Math.min(lines.length, c.line + around);
3294
+ const width = String(to).length;
3295
+ return [
3296
+ `${c.what} ${c.verb ?? "cites"} ${c.repo}/${c.path}:${c.line}:`,
3297
+ ...lines.slice(from - 1, to).map((l, i) => `${from + i === c.line ? ">" : " "}${String(from + i).padStart(width)} ${l}`)
3298
+ ].join("\n");
3299
+ }
3300
+ async function showCodeTool(ix, root, input, o) {
3301
+ const around = int(input.context, 0, CONTEXT_TOOL_BOUNDS.codeContext, 6);
3302
+ const id = str2(input.id);
3303
+ let cites2;
3304
+ if (id) {
3305
+ if (!ix)
3306
+ throw new ContextToolError("show_code by id needs the index.");
3307
+ nodesOf(ix, id.replace(/#\d+$/, ""));
3308
+ cites2 = citesOf(ix, root, id);
3309
+ if (!cites2.length)
3310
+ return `${id} cites no line of code (it may cite a wiki page or a document: show it).`;
3311
+ } else {
3312
+ const repo = str2(input.repo);
3313
+ const path = str2(input.path);
3314
+ const line = typeof input.line === "number" ? Math.floor(input.line) : void 0;
3315
+ if (!repo || !path || !line || line < 1)
3316
+ throw new ContextToolError("show_code needs an id, or a repo, path and line.");
3317
+ if (!RecordId.safeParse(repo).success)
3318
+ throw new ContextToolError(`${repo} isn't a repo id.`);
3319
+ if (o.pointers?.has(pointerKey(repo, path, line)))
3320
+ cites2 = [{ repo, path: posixPath(path), line, what: "A search hit", verb: "at" }];
3321
+ else {
3322
+ if (!ix || !isCited(ix, root, { repo, path, line }))
3323
+ throw new ContextToolError(`No fact cites ${repo}/${path}:${line}, and no grep or outline hit of this run returned it; show_code shows only those. Give a record's id, or find the line with grep first.`);
3324
+ cites2 = [{ repo, path, line, what: "A fact" }];
3325
+ }
3326
+ }
3327
+ const shown = cites2.slice(0, CONTEXT_TOOL_BOUNDS.codeCites);
3328
+ const parts = [];
3329
+ for (const c of shown)
3330
+ parts.push(await excerpt(root, c, around, o));
3331
+ if (cites2.length > shown.length)
3332
+ parts.push(`\u2026 and ${cites2.length - shown.length} more cites; ask for one by repo, path and line.`);
3333
+ return parts.join("\n\n");
3334
+ }
3335
+ function contextToolBox(root, open, o = {}) {
3336
+ const { calls, ...rest } = o;
3337
+ const pointers = o.pointers ?? /* @__PURE__ */ new Set();
3338
+ for (const c of calls ?? [])
3339
+ for (const k of pointersIn(c.name, c.output))
3340
+ pointers.add(k);
3341
+ const opts = { ...rest, pointers };
3342
+ return {
3343
+ definitions: CONTEXT_TOOLS,
3344
+ async call(name, input) {
3345
+ if (CODE_TOOL_NAMES.includes(name))
3346
+ return runCodeTool(root, name, input, opts);
3347
+ const line = typeof input.line === "number" ? Math.floor(input.line) : void 0;
3348
+ if (name === "show_code" && !str2(input.id) && str2(input.repo) && str2(input.path) && line && pointers.has(pointerKey(str2(input.repo), str2(input.path), line)))
3349
+ return showCodeTool(void 0, root, input, opts);
3350
+ const ix = await open();
3351
+ try {
3352
+ return await runContextTool(ix, root, name, input, opts);
3353
+ } finally {
3354
+ ix.close();
3355
+ }
3356
+ }
3357
+ };
3358
+ }
3359
+ var posixPath = (p) => p.replace(/\\/g, "/").replace(/^(\.\/)+/, "");
3360
+ var pointerKey = (repo, path, line) => `${repo}\0${posixPath(path)}\0${line}`;
3361
+ var HIT = /^([a-z0-9][a-z0-9._-]*):(.+?):(\d+): /;
3362
+ var OUTLINE_HEAD = /^Outline of ([a-z0-9][a-z0-9._-]*):(.+)$/;
3363
+ var OUTLINE_LINE = /^\s*(\d+): /;
3364
+ function pointersIn(name, output) {
3365
+ const out = [];
3366
+ if (name === "grep")
3367
+ for (const l of output.split(/\r?\n/)) {
3368
+ const m = HIT.exec(l);
3369
+ if (m)
3370
+ out.push(pointerKey(m[1], m[2], Number(m[3])));
3371
+ }
3372
+ if (name === "outline") {
3373
+ let at;
3374
+ for (const l of output.split(/\r?\n/)) {
3375
+ const h = OUTLINE_HEAD.exec(l);
3376
+ if (h) {
3377
+ at = { repo: h[1], path: h[2] };
3378
+ continue;
3379
+ }
3380
+ const m = at && OUTLINE_LINE.exec(l);
3381
+ if (at && m)
3382
+ out.push(pointerKey(at.repo, at.path, Number(m[1])));
3383
+ }
3384
+ }
3385
+ return out;
3386
+ }
3387
+ async function namesInCode(root, names, o = {}) {
3388
+ const B = CONTEXT_TOOL_BOUNDS;
3389
+ const found = /* @__PURE__ */ new Map();
3390
+ const want = [...new Set(names.filter((n) => n.trim().length >= 2))];
3391
+ const ids = o.repos ? o.repos() : workspaceRepos(root);
3392
+ const repos = [];
3393
+ for (const r of ids) {
3394
+ const dir = (o.codeDir ?? ((x) => defaultCodeDir(root, x)))(r);
3395
+ if (dir && RecordId.safeParse(r).success)
3396
+ repos.push({ repo: r, dir });
3397
+ }
3398
+ const how = /* @__PURE__ */ new Map();
3399
+ let walked = 0;
3400
+ let bytes = 0;
3401
+ let cut = false;
3402
+ for (const { repo, dir } of repos) {
3403
+ if (found.size === want.length)
3404
+ break;
3405
+ const w = await walkClone(dir, B.walkFiles - walked, async (p) => {
3406
+ walked++;
3407
+ const open = want.filter((n) => !found.has(n));
3408
+ for (const n of open)
3409
+ if (p === n || p.endsWith(`/${n.replace(/^\/+/, "")}`)) {
3410
+ found.set(n, `${repo}:${p}`);
3411
+ how.set(n, "file");
3412
+ }
3413
+ if (!o.outline || o.outlines && !o.outlines(p))
3414
+ return;
3415
+ const names2 = open.filter((n) => !found.has(n) && DECLARABLE.test(n));
3416
+ if (!names2.length)
3417
+ return;
3418
+ const buf = await readFile3(join4(dir, ...p.split("/"))).catch(() => void 0);
3419
+ if (!buf || buf.length > B.codeFileBytes)
3420
+ return;
3421
+ if (buf.subarray(0, 8e3).includes(0))
3422
+ return;
3423
+ bytes += buf.length;
3424
+ if (bytes > B.grepBytes) {
3425
+ cut = true;
3426
+ return false;
3427
+ }
3428
+ const text = buf.toString("utf8");
3429
+ if (!names2.some((n) => text.includes(n)))
3430
+ return;
3431
+ const decls = await o.outline(p, text).catch(() => void 0);
3432
+ for (const d of decls ?? []) {
3433
+ if (!isTypeDeclaration(d.kind))
3434
+ continue;
3435
+ const n = names2.find((x) => x === d.name);
3436
+ if (n && !found.has(n)) {
3437
+ found.set(n, `${repo}:${p}:${d.line}`);
3438
+ how.set(n, "declaration");
3439
+ }
3440
+ }
3441
+ if (found.size === want.length)
3442
+ return false;
3443
+ });
3444
+ if (w.cut)
3445
+ cut = true;
3446
+ if (cut)
3447
+ break;
3448
+ }
3449
+ return { found, how, repos: repos.map((r) => r.repo), cut };
3450
+ }
3451
+ var DECLARABLE = /^[A-Za-z_$][\w$]*$/;
3452
+ var isTypeDeclaration = (kind) => !/method|constructor|field|property|variable|parameter|accessor|getter|setter/.test(kind);
3453
+ function workspaceRepos(root) {
3454
+ const out = /* @__PURE__ */ new Set();
3455
+ for (const base of [join4(root, "workspace"), join4(stateDirOf(root), "workspace")]) {
3456
+ let names;
3457
+ try {
3458
+ names = readdirSync(base, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
3459
+ } catch {
3460
+ continue;
3461
+ }
3462
+ for (const n of names)
3463
+ if (RecordId.safeParse(n).success)
3464
+ out.add(n);
3465
+ }
3466
+ return [...out].sort();
3467
+ }
3468
+ function cloneOf(root, repo, o) {
3469
+ if (!RecordId.safeParse(repo).success)
3470
+ throw new ContextToolError(`${repo} isn't a repo id.`);
3471
+ const dir = (o.codeDir ?? ((r) => defaultCodeDir(root, r)))(repo);
3472
+ if (!dir)
3473
+ throw new ContextToolError(`${repo} isn't cloned in the workspace.`);
3474
+ return dir;
3475
+ }
3476
+ function reposOf(root, input, o) {
3477
+ const named2 = str2(input.repo);
3478
+ if (named2)
3479
+ return [{ repo: named2, dir: cloneOf(root, named2, o) }];
3480
+ const ids = o.repos ? o.repos() : workspaceRepos(root);
3481
+ const out = [];
3482
+ for (const r of ids) {
3483
+ const dir = (o.codeDir ?? ((x) => defaultCodeDir(root, x)))(r);
3484
+ if (dir && RecordId.safeParse(r).success)
3485
+ out.push({ repo: r, dir });
3486
+ }
3487
+ if (!out.length)
3488
+ throw new ContextToolError("No repo is cloned in the workspace.");
3489
+ return out;
3490
+ }
3491
+ function globRe(glob2, anyDepth = !glob2.replace(/\/$/, "").includes("/")) {
3492
+ let g = glob2.trim().replace(/\\/g, "/").replace(/^\.\//, "");
3493
+ if (g.startsWith("/")) {
3494
+ g = g.slice(1);
3495
+ anyDepth = false;
3496
+ }
3497
+ let re = "";
3498
+ for (let i = 0; i < g.length; i++) {
3499
+ const ch = g[i];
3500
+ if (ch === "*") {
3501
+ if (g[i + 1] === "*") {
3502
+ const slash = g[i + 2] === "/";
3503
+ re += slash ? "(?:.*/)?" : ".*";
3504
+ i += slash ? 2 : 1;
3505
+ } else
3506
+ re += "[^/]*";
3507
+ } else if (ch === "?")
3508
+ re += "[^/]";
3509
+ else if (ch === "[") {
3510
+ const end = g.indexOf("]", i + 2);
3511
+ if (end < 0)
3512
+ re += "\\[";
3513
+ else {
3514
+ const body = g.slice(i + 1, end).replace(/^!/, "^").replace(/\\/g, "\\\\");
3515
+ re += `[${body}]`;
3516
+ i = end;
3517
+ }
3518
+ } else if (ch === "{") {
3519
+ const end = g.indexOf("}", i);
3520
+ if (end < 0)
3521
+ re += "\\{";
3522
+ else {
3523
+ re += `(?:${g.slice(i + 1, end).split(",").map((p) => globRe(p, false).source.slice(1, -1)).join("|")})`;
3524
+ i = end;
3525
+ }
3526
+ } else
3527
+ re += ch.replace(/[.+^$()|\\]/g, "\\$&");
3528
+ }
3529
+ return new RegExp(`^${anyDepth ? "(?:.*/)?" : ""}${re}$`);
3530
+ }
3531
+ function ignoreRules(text, base) {
3532
+ const out = [];
3533
+ for (let line of text.split(/\r?\n/)) {
3534
+ line = line.replace(/(?<!\\)\s+$/, "");
3535
+ if (!line || line.startsWith("#"))
3536
+ continue;
3537
+ let negate = false;
3538
+ if (line.startsWith("!")) {
3539
+ negate = true;
3540
+ line = line.slice(1);
3541
+ } else if (line.startsWith("\\!") || line.startsWith("\\#"))
3542
+ line = line.slice(1);
3543
+ const dirOnly = line.endsWith("/");
3544
+ if (dirOnly)
3545
+ line = line.slice(0, -1);
3546
+ if (!line)
3547
+ continue;
3548
+ const anchored = line.includes("/");
3549
+ try {
3550
+ out.push({ base, re: globRe(line.replace(/^\//, ""), !anchored), negate, dirOnly });
3551
+ } catch {
3552
+ }
3553
+ }
3554
+ return out;
3555
+ }
3556
+ function ignored(rules, path, isDir) {
3557
+ let out = false;
3558
+ for (const r of rules) {
3559
+ if (r.dirOnly && !isDir)
3560
+ continue;
3561
+ if (r.base && !path.startsWith(`${r.base}/`))
3562
+ continue;
3563
+ const rel2 = r.base ? path.slice(r.base.length + 1) : path;
3564
+ if (r.re.test(rel2))
3565
+ out = !r.negate;
3566
+ }
3567
+ return out;
3568
+ }
3569
+ async function rulesFor(dir, path) {
3570
+ const parts = path.split("/").slice(0, -1);
3571
+ const rules = [];
3572
+ for (let i = 0; i <= parts.length; i++) {
3573
+ const base = parts.slice(0, i).join("/");
3574
+ const text = await readFile3(join4(dir, ...parts.slice(0, i), ".gitignore"), "utf8").catch(() => void 0);
3575
+ if (text)
3576
+ rules.push(...ignoreRules(text, base));
3577
+ }
3578
+ return rules;
3579
+ }
3580
+ async function isIgnored(dir, path) {
3581
+ const rules = await rulesFor(dir, path);
3582
+ const parts = path.split("/");
3583
+ for (let i = 1; i < parts.length; i++)
3584
+ if (ignored(rules, parts.slice(0, i).join("/"), true))
3585
+ return true;
3586
+ return ignored(rules, path, false);
3587
+ }
3588
+ async function walkClone(dir, max, visit) {
3589
+ let files = 0;
3590
+ let cut = false;
3591
+ const go = async (rel2, inherited) => {
3592
+ let entries;
3593
+ try {
3594
+ entries = await readdir2(rel2 ? join4(dir, ...rel2.split("/")) : dir, { withFileTypes: true });
3595
+ } catch {
3596
+ return true;
3597
+ }
3598
+ const here = entries.find((e) => e.name === ".gitignore" && e.isFile());
3599
+ const rules = here ? [
3600
+ ...inherited,
3601
+ ...ignoreRules(await readFile3(join4(dir, ...rel2.split("/"), ".gitignore"), "utf8"), rel2)
3602
+ ] : inherited;
3603
+ for (const e of entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
3604
+ if (e.isSymbolicLink() || e.name === ".git")
3605
+ continue;
3606
+ const p = rel2 ? `${rel2}/${e.name}` : e.name;
3607
+ if (e.isDirectory()) {
3608
+ if (ignored(rules, p, true))
3609
+ continue;
3610
+ if (!await go(p, rules))
3611
+ return false;
3612
+ } else if (e.isFile()) {
3613
+ if (ignored(rules, p, false))
3614
+ continue;
3615
+ if (files >= max) {
3616
+ cut = true;
3617
+ return false;
3618
+ }
3619
+ files++;
3620
+ if (await visit(p) === false)
3621
+ return false;
3622
+ }
3623
+ }
3624
+ return true;
3625
+ };
3626
+ await go("", []);
3627
+ return { files, cut };
3628
+ }
3629
+ var plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
3630
+ async function runCodeTool(root, name, input, o) {
3631
+ if (name === "grep")
3632
+ return grepTool(root, input, o);
3633
+ if (name === "files")
3634
+ return filesTool(root, input, o);
3635
+ return outlineTool(root, input, o);
3636
+ }
3637
+ async function grepTool(root, input, o) {
3638
+ const B = CONTEXT_TOOL_BOUNDS;
3639
+ const pattern = typeof input.pattern === "string" && input.pattern ? input.pattern : void 0;
3640
+ if (!pattern)
3641
+ throw new ContextToolError("grep needs a pattern: the text or expression to find.");
3642
+ if (pattern.length > B.grepPattern)
3643
+ throw new ContextToolError(`A grep pattern is at most ${B.grepPattern} characters.`);
3644
+ const ignoreCase = input.ignoreCase === true;
3645
+ let test;
3646
+ if (input.regex === true) {
3647
+ let re;
3648
+ try {
3649
+ re = new RegExp(pattern, ignoreCase ? "i" : "");
3650
+ } catch (e) {
3651
+ throw new ContextToolError(`${pattern} isn't a regular expression: ${e.message}.`);
3652
+ }
3653
+ test = (l) => re.test(l);
3654
+ } else {
3655
+ const p = ignoreCase ? pattern.toLowerCase() : pattern;
3656
+ test = ignoreCase ? (l) => l.toLowerCase().includes(p) : (l) => l.includes(p);
3657
+ }
3658
+ const limit = int(input.limit, 1, B.grepLimit, B.grepDefault);
3659
+ const paths = str2(input.paths);
3660
+ const only = paths ? globRe(paths) : void 0;
3661
+ const repos = reposOf(root, input, o);
3662
+ const hits = [];
3663
+ let more = false;
3664
+ let read = 0;
3665
+ let bytes = 0;
3666
+ let cut = false;
3667
+ for (const { repo, dir } of repos) {
3668
+ const walked = await walkClone(dir, B.walkFiles - read, async (p) => {
3669
+ if (only && !only.test(p))
3670
+ return;
3671
+ const buf = await readFile3(join4(dir, ...p.split("/"))).catch(() => void 0);
3672
+ if (!buf || buf.length > B.codeFileBytes)
3673
+ return;
3674
+ if (buf.subarray(0, 8e3).includes(0))
3675
+ return;
3676
+ bytes += buf.length;
3677
+ if (bytes > B.grepBytes) {
3678
+ cut = true;
3679
+ return false;
3680
+ }
3681
+ read++;
3682
+ const lines = buf.toString("utf8").split(/\r?\n/);
3683
+ for (let i = 0; i < lines.length; i++) {
3684
+ const line = lines[i].slice(0, 2e3);
3685
+ if (!test(line))
3686
+ continue;
3687
+ if (hits.length >= limit) {
3688
+ more = true;
3689
+ return false;
3690
+ }
3691
+ const text = line.trimEnd();
3692
+ const shown = text.length > B.grepLineChars ? `${text.slice(0, B.grepLineChars)} \u2026` : text;
3693
+ hits.push(`${repo}:${p}:${i + 1}: ${shown}`);
3694
+ o.pointers?.add(pointerKey(repo, p, i + 1));
3695
+ }
3696
+ });
3697
+ if (walked.cut)
3698
+ cut = true;
3699
+ if (more || cut)
3700
+ break;
3701
+ }
3702
+ const where = repos.length === 1 ? ` in ${repos[0].repo}` : "";
3703
+ const scope = paths ? `, paths ${paths}` : "";
3704
+ if (!hits.length)
3705
+ return `No line matches "${pattern}"${where}${scope} (${plural(read, "file")} read${cut ? "; the search stopped at its cap" : ""}).`;
3706
+ return [
3707
+ `${plural(hits.length, "hit")} for "${pattern}"${where}${scope} (${plural(read, "file")} read):`,
3708
+ ...hits,
3709
+ ...more ? [`\u2026 more hits past the ${limit} shown; narrow the pattern or the paths.`] : [],
3710
+ ...cut ? [`\u2026 the search stopped at its cap of files or bytes; name a repo or narrower paths.`] : [],
3711
+ "Show the lines around a hit with show_code (repo, path, line). A hit is a pointer, not a fact: a fact cites its file and line, and Atticus checks the value there."
3712
+ ].join("\n");
3713
+ }
3714
+ async function filesTool(root, input, o) {
3715
+ const B = CONTEXT_TOOL_BOUNDS;
3716
+ const glob2 = str2(input.glob);
3717
+ if (!glob2)
3718
+ throw new ContextToolError("files needs a glob, for example src/**/*.java.");
3719
+ const limit = int(input.limit, 1, B.filesLimit, B.filesDefault);
3720
+ const re = globRe(glob2);
3721
+ const repos = reposOf(root, input, o);
3722
+ const out = [];
3723
+ let more = false;
3724
+ let seen = 0;
3725
+ for (const { repo, dir } of repos) {
3726
+ const walked = await walkClone(dir, B.walkFiles - seen, async (p) => {
3727
+ seen++;
3728
+ if (!re.test(p))
3729
+ return;
3730
+ if (out.length >= limit) {
3731
+ more = true;
3732
+ return false;
3733
+ }
3734
+ out.push(`${repo}:${p}`);
3735
+ });
3736
+ if (more || walked.cut) {
3737
+ more = true;
3738
+ break;
3739
+ }
3740
+ }
3741
+ const where = repos.length === 1 ? ` in ${repos[0].repo}` : "";
3742
+ if (!out.length)
3743
+ return `No file matches ${glob2}${where}.`;
3744
+ return [
3745
+ `${plural(out.length, "file")} match ${glob2}${where}:`,
3746
+ ...out,
3747
+ ...more ? [`\u2026 more past the ${limit} shown; give a narrower glob.`] : []
3748
+ ].join("\n");
3749
+ }
3750
+ function textOutline(path, text) {
3751
+ const lines = text.split(/\r?\n/);
3752
+ const pick = (re, how) => ({
3753
+ how,
3754
+ lines: lines.flatMap((l, i) => re.test(l) ? [`${i + 1}: ${l.trimEnd()}`] : [])
3755
+ });
3756
+ if (/\.(md|markdown|mdx)$/i.test(path)) {
3757
+ let fence = false;
3758
+ return {
3759
+ how: "its headings",
3760
+ lines: lines.flatMap((l, i) => {
3761
+ if (/^\s*(```|~~~)/.test(l))
3762
+ fence = !fence;
3763
+ return !fence && /^#{1,6}\s/.test(l) ? [`${i + 1}: ${l.trimEnd()}`] : [];
3764
+ })
3765
+ };
3766
+ }
3767
+ if (/\.(adoc|asciidoc)$/i.test(path))
3768
+ return pick(/^={1,6}\s/, "its headings");
3769
+ if (/\.ya?ml$/i.test(path))
3770
+ return pick(/^[^\s#-][^:]*:/, "its top-level keys");
3771
+ if (/\.properties$/i.test(path)) {
3772
+ const o = pick(/^\s*[^#!\s][^=:]*[=:]/, "its keys");
3773
+ return {
3774
+ how: o.how,
3775
+ lines: o.lines.map((l) => l.replace(/^(\d+: )\s*([^=:]+?)\s*[=:].*$/, "$1$2"))
3776
+ };
3777
+ }
3778
+ return void 0;
3779
+ }
3780
+ async function outlineTool(root, input, o) {
3781
+ const repo = str2(input.repo);
3782
+ const path = str2(input.path);
3783
+ if (!repo || !path)
3784
+ throw new ContextToolError("outline needs a repo and the file's path in it.");
3785
+ const dir = cloneOf(root, repo, o);
3786
+ const file = await insideClone(dir, path);
3787
+ const rel2 = posixPath(relative2(await realpath(dir), file).split(sep2).join("/"));
3788
+ if (await isIgnored(dir, rel2))
3789
+ throw new ContextToolError(`${path} is a file the repo's .gitignore ignores; it isn't read.`);
3790
+ const size = (await stat2(file)).size;
3791
+ if (size > CONTEXT_TOOL_BOUNDS.codeFileBytes)
3792
+ throw new ContextToolError(`${path} is too big to outline (${size} bytes).`);
3793
+ const text = await readFile3(file, "utf8");
3794
+ const head = `Outline of ${repo}:${rel2}`;
3795
+ let how;
3796
+ let lines;
3797
+ const decls = o.outline ? await o.outline(rel2, text) : void 0;
3798
+ if (decls) {
3799
+ how = "From its syntax tree: each line is the line number, then what is declared there.";
3800
+ lines = decls.flatMap((d) => {
3801
+ const pad = " ".repeat(d.depth);
3802
+ return [
3803
+ ...d.marks.map((m) => `${m.line}: ${pad}${m.text}`),
3804
+ `${d.line}: ${pad}${d.kind} ${d.name}`
3805
+ ];
3806
+ });
3807
+ } else {
3808
+ const t = textOutline(rel2, text);
3809
+ if (!t)
3810
+ return `${repo}:${rel2} has no outline: no reader for this kind of file. grep it, or show_code a line a hit returned.`;
3811
+ how = `From ${t.how}: each line is the line number, then the line.`;
3812
+ lines = t.lines;
3813
+ }
3814
+ if (!lines.length)
3815
+ return `${head}
3816
+ ${how}
3817
+ (nothing)`;
3818
+ const shown = lines.slice(0, CONTEXT_TOOL_BOUNDS.outlineLines);
3819
+ for (const l of shown) {
3820
+ const m = OUTLINE_LINE.exec(l);
3821
+ if (m)
3822
+ o.pointers?.add(pointerKey(repo, rel2, Number(m[1])));
3823
+ }
3824
+ return [
3825
+ head,
3826
+ how,
3827
+ ...shown,
3828
+ ...lines.length > shown.length ? [`\u2026 and ${lines.length - shown.length} more lines; grep the file for what you need.`] : []
3829
+ ].join("\n");
3830
+ }
3831
+
3832
+ export {
3833
+ GAP_STEP,
3834
+ gapQuestion,
3835
+ gapNameOf,
3836
+ QueryError,
3837
+ searchIndex,
3838
+ nodesById,
3839
+ candidateRef,
3840
+ openIndex,
3841
+ graph,
3842
+ impact,
3843
+ askLookup,
3844
+ translate,
3845
+ CONTEXT_TOOLS,
3846
+ defaultCodeDir,
3847
+ runContextTool,
3848
+ contextToolBox,
3849
+ namesInCode,
3850
+ workspaceRepos
3851
+ };