brains-mcp 1.61.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 (350) hide show
  1. package/README.md +95 -0
  2. package/dist/access/projectShares.d.ts +48 -0
  3. package/dist/access/projectShares.d.ts.map +1 -0
  4. package/dist/access/projectShares.js +203 -0
  5. package/dist/access/projectShares.js.map +1 -0
  6. package/dist/access/sharedInstructionsNote.d.ts +37 -0
  7. package/dist/access/sharedInstructionsNote.d.ts.map +1 -0
  8. package/dist/access/sharedInstructionsNote.js +76 -0
  9. package/dist/access/sharedInstructionsNote.js.map +1 -0
  10. package/dist/admin/seedUsers.d.ts +23 -0
  11. package/dist/admin/seedUsers.d.ts.map +1 -0
  12. package/dist/admin/seedUsers.js +168 -0
  13. package/dist/admin/seedUsers.js.map +1 -0
  14. package/dist/api/openapi.d.ts +2 -0
  15. package/dist/api/openapi.d.ts.map +1 -0
  16. package/dist/api/openapi.js +942 -0
  17. package/dist/api/openapi.js.map +1 -0
  18. package/dist/auth/clientStore.d.ts +43 -0
  19. package/dist/auth/clientStore.d.ts.map +1 -0
  20. package/dist/auth/clientStore.js +168 -0
  21. package/dist/auth/clientStore.js.map +1 -0
  22. package/dist/auth/deleteAccount.d.ts +34 -0
  23. package/dist/auth/deleteAccount.d.ts.map +1 -0
  24. package/dist/auth/deleteAccount.js +203 -0
  25. package/dist/auth/deleteAccount.js.map +1 -0
  26. package/dist/auth/oauth.d.ts +118 -0
  27. package/dist/auth/oauth.d.ts.map +1 -0
  28. package/dist/auth/oauth.js +491 -0
  29. package/dist/auth/oauth.js.map +1 -0
  30. package/dist/auth/patStore.d.ts +87 -0
  31. package/dist/auth/patStore.d.ts.map +1 -0
  32. package/dist/auth/patStore.js +308 -0
  33. package/dist/auth/patStore.js.map +1 -0
  34. package/dist/auth/supabase.d.ts +15 -0
  35. package/dist/auth/supabase.d.ts.map +1 -0
  36. package/dist/auth/supabase.js +66 -0
  37. package/dist/auth/supabase.js.map +1 -0
  38. package/dist/auth/tokenStore.d.ts +73 -0
  39. package/dist/auth/tokenStore.d.ts.map +1 -0
  40. package/dist/auth/tokenStore.js +261 -0
  41. package/dist/auth/tokenStore.js.map +1 -0
  42. package/dist/backup/drive.d.ts +24 -0
  43. package/dist/backup/drive.d.ts.map +1 -0
  44. package/dist/backup/drive.js +70 -0
  45. package/dist/backup/drive.js.map +1 -0
  46. package/dist/billing/tierStore.d.ts +59 -0
  47. package/dist/billing/tierStore.d.ts.map +1 -0
  48. package/dist/billing/tierStore.js +152 -0
  49. package/dist/billing/tierStore.js.map +1 -0
  50. package/dist/digest/activityDigest.d.ts +32 -0
  51. package/dist/digest/activityDigest.d.ts.map +1 -0
  52. package/dist/digest/activityDigest.js +299 -0
  53. package/dist/digest/activityDigest.js.map +1 -0
  54. package/dist/digest/digestCron.d.ts +40 -0
  55. package/dist/digest/digestCron.d.ts.map +1 -0
  56. package/dist/digest/digestCron.js +146 -0
  57. package/dist/digest/digestCron.js.map +1 -0
  58. package/dist/digest/digestPreferences.d.ts +25 -0
  59. package/dist/digest/digestPreferences.d.ts.map +1 -0
  60. package/dist/digest/digestPreferences.js +79 -0
  61. package/dist/digest/digestPreferences.js.map +1 -0
  62. package/dist/digest/emailSender.d.ts +20 -0
  63. package/dist/digest/emailSender.d.ts.map +1 -0
  64. package/dist/digest/emailSender.js +35 -0
  65. package/dist/digest/emailSender.js.map +1 -0
  66. package/dist/drive/client.d.ts +43 -0
  67. package/dist/drive/client.d.ts.map +1 -0
  68. package/dist/drive/client.js +282 -0
  69. package/dist/drive/client.js.map +1 -0
  70. package/dist/drive/errors.d.ts +27 -0
  71. package/dist/drive/errors.d.ts.map +1 -0
  72. package/dist/drive/errors.js +51 -0
  73. package/dist/drive/errors.js.map +1 -0
  74. package/dist/drive/fileCache.d.ts +22 -0
  75. package/dist/drive/fileCache.d.ts.map +1 -0
  76. package/dist/drive/fileCache.js +57 -0
  77. package/dist/drive/fileCache.js.map +1 -0
  78. package/dist/governance/accessControl.d.ts +87 -0
  79. package/dist/governance/accessControl.d.ts.map +1 -0
  80. package/dist/governance/accessControl.js +179 -0
  81. package/dist/governance/accessControl.js.map +1 -0
  82. package/dist/html/components.d.ts +8 -0
  83. package/dist/html/components.d.ts.map +1 -0
  84. package/dist/html/components.js +19 -0
  85. package/dist/html/components.js.map +1 -0
  86. package/dist/html/layout.d.ts +7 -0
  87. package/dist/html/layout.d.ts.map +1 -0
  88. package/dist/html/layout.js +24 -0
  89. package/dist/html/layout.js.map +1 -0
  90. package/dist/html/metricsUi.d.ts +8 -0
  91. package/dist/html/metricsUi.d.ts.map +1 -0
  92. package/dist/html/metricsUi.js +271 -0
  93. package/dist/html/metricsUi.js.map +1 -0
  94. package/dist/httpServer.d.ts +19 -0
  95. package/dist/httpServer.d.ts.map +1 -0
  96. package/dist/httpServer.js +3913 -0
  97. package/dist/httpServer.js.map +1 -0
  98. package/dist/index.d.ts +3 -0
  99. package/dist/index.d.ts.map +1 -0
  100. package/dist/index.js +40 -0
  101. package/dist/index.js.map +1 -0
  102. package/dist/jobs/asyncJobRegistry.d.ts +30 -0
  103. package/dist/jobs/asyncJobRegistry.d.ts.map +1 -0
  104. package/dist/jobs/asyncJobRegistry.js +70 -0
  105. package/dist/jobs/asyncJobRegistry.js.map +1 -0
  106. package/dist/observability/metricsQuery.d.ts +41 -0
  107. package/dist/observability/metricsQuery.d.ts.map +1 -0
  108. package/dist/observability/metricsQuery.js +106 -0
  109. package/dist/observability/metricsQuery.js.map +1 -0
  110. package/dist/observability/relevanceSignal.d.ts +26 -0
  111. package/dist/observability/relevanceSignal.d.ts.map +1 -0
  112. package/dist/observability/relevanceSignal.js +95 -0
  113. package/dist/observability/relevanceSignal.js.map +1 -0
  114. package/dist/observability/requestEvents.d.ts +31 -0
  115. package/dist/observability/requestEvents.d.ts.map +1 -0
  116. package/dist/observability/requestEvents.js +54 -0
  117. package/dist/observability/requestEvents.js.map +1 -0
  118. package/dist/observability/slowQueryAlert.d.ts +25 -0
  119. package/dist/observability/slowQueryAlert.d.ts.map +1 -0
  120. package/dist/observability/slowQueryAlert.js +67 -0
  121. package/dist/observability/slowQueryAlert.js.map +1 -0
  122. package/dist/observability/zeroResultTriage.d.ts +22 -0
  123. package/dist/observability/zeroResultTriage.d.ts.map +1 -0
  124. package/dist/observability/zeroResultTriage.js +116 -0
  125. package/dist/observability/zeroResultTriage.js.map +1 -0
  126. package/dist/search/embeddings.d.ts +31 -0
  127. package/dist/search/embeddings.d.ts.map +1 -0
  128. package/dist/search/embeddings.js +91 -0
  129. package/dist/search/embeddings.js.map +1 -0
  130. package/dist/search/fileWatcher.d.ts +28 -0
  131. package/dist/search/fileWatcher.d.ts.map +1 -0
  132. package/dist/search/fileWatcher.js +103 -0
  133. package/dist/search/fileWatcher.js.map +1 -0
  134. package/dist/search/hotQueryCache.d.ts +31 -0
  135. package/dist/search/hotQueryCache.d.ts.map +1 -0
  136. package/dist/search/hotQueryCache.js +103 -0
  137. package/dist/search/hotQueryCache.js.map +1 -0
  138. package/dist/search/postgresIndex.d.ts +439 -0
  139. package/dist/search/postgresIndex.d.ts.map +1 -0
  140. package/dist/search/postgresIndex.js +1610 -0
  141. package/dist/search/postgresIndex.js.map +1 -0
  142. package/dist/search/sqliteIndex.d.ts +66 -0
  143. package/dist/search/sqliteIndex.d.ts.map +1 -0
  144. package/dist/search/sqliteIndex.js +342 -0
  145. package/dist/search/sqliteIndex.js.map +1 -0
  146. package/dist/server.d.ts +43 -0
  147. package/dist/server.d.ts.map +1 -0
  148. package/dist/server.js +532 -0
  149. package/dist/server.js.map +1 -0
  150. package/dist/session/bundle.d.ts +53 -0
  151. package/dist/session/bundle.d.ts.map +1 -0
  152. package/dist/session/bundle.js +97 -0
  153. package/dist/session/bundle.js.map +1 -0
  154. package/dist/storage/client.d.ts +157 -0
  155. package/dist/storage/client.d.ts.map +1 -0
  156. package/dist/storage/client.js +825 -0
  157. package/dist/storage/client.js.map +1 -0
  158. package/dist/tools/addWebhookPattern.d.ts +15 -0
  159. package/dist/tools/addWebhookPattern.d.ts.map +1 -0
  160. package/dist/tools/addWebhookPattern.js +15 -0
  161. package/dist/tools/addWebhookPattern.js.map +1 -0
  162. package/dist/tools/appendLog.d.ts +18 -0
  163. package/dist/tools/appendLog.d.ts.map +1 -0
  164. package/dist/tools/appendLog.js +18 -0
  165. package/dist/tools/appendLog.js.map +1 -0
  166. package/dist/tools/auditIndex.d.ts +15 -0
  167. package/dist/tools/auditIndex.d.ts.map +1 -0
  168. package/dist/tools/auditIndex.js +165 -0
  169. package/dist/tools/auditIndex.js.map +1 -0
  170. package/dist/tools/batchDeletePages.d.ts +18 -0
  171. package/dist/tools/batchDeletePages.d.ts.map +1 -0
  172. package/dist/tools/batchDeletePages.js +143 -0
  173. package/dist/tools/batchDeletePages.js.map +1 -0
  174. package/dist/tools/coerce.d.ts +3 -0
  175. package/dist/tools/coerce.d.ts.map +1 -0
  176. package/dist/tools/coerce.js +12 -0
  177. package/dist/tools/coerce.js.map +1 -0
  178. package/dist/tools/createPage.d.ts +24 -0
  179. package/dist/tools/createPage.d.ts.map +1 -0
  180. package/dist/tools/createPage.js +99 -0
  181. package/dist/tools/createPage.js.map +1 -0
  182. package/dist/tools/createPages.d.ts +51 -0
  183. package/dist/tools/createPages.d.ts.map +1 -0
  184. package/dist/tools/createPages.js +91 -0
  185. package/dist/tools/createPages.js.map +1 -0
  186. package/dist/tools/deletePage.d.ts +19 -0
  187. package/dist/tools/deletePage.d.ts.map +1 -0
  188. package/dist/tools/deletePage.js +127 -0
  189. package/dist/tools/deletePage.js.map +1 -0
  190. package/dist/tools/exportWiki.d.ts +48 -0
  191. package/dist/tools/exportWiki.d.ts.map +1 -0
  192. package/dist/tools/exportWiki.js +233 -0
  193. package/dist/tools/exportWiki.js.map +1 -0
  194. package/dist/tools/extractLinks.d.ts +14 -0
  195. package/dist/tools/extractLinks.d.ts.map +1 -0
  196. package/dist/tools/extractLinks.js +25 -0
  197. package/dist/tools/extractLinks.js.map +1 -0
  198. package/dist/tools/generateClaudeInstructions.d.ts +27 -0
  199. package/dist/tools/generateClaudeInstructions.d.ts.map +1 -0
  200. package/dist/tools/generateClaudeInstructions.js +263 -0
  201. package/dist/tools/generateClaudeInstructions.js.map +1 -0
  202. package/dist/tools/generateGptInstructions.d.ts +27 -0
  203. package/dist/tools/generateGptInstructions.d.ts.map +1 -0
  204. package/dist/tools/generateGptInstructions.js +208 -0
  205. package/dist/tools/generateGptInstructions.js.map +1 -0
  206. package/dist/tools/getBacklinks.d.ts +12 -0
  207. package/dist/tools/getBacklinks.d.ts.map +1 -0
  208. package/dist/tools/getBacklinks.js +13 -0
  209. package/dist/tools/getBacklinks.js.map +1 -0
  210. package/dist/tools/getRelated.d.ts +12 -0
  211. package/dist/tools/getRelated.d.ts.map +1 -0
  212. package/dist/tools/getRelated.js +13 -0
  213. package/dist/tools/getRelated.js.map +1 -0
  214. package/dist/tools/healthCheck.d.ts +5 -0
  215. package/dist/tools/healthCheck.d.ts.map +1 -0
  216. package/dist/tools/healthCheck.js +167 -0
  217. package/dist/tools/healthCheck.js.map +1 -0
  218. package/dist/tools/importBrainSource.d.ts +36 -0
  219. package/dist/tools/importBrainSource.d.ts.map +1 -0
  220. package/dist/tools/importBrainSource.js +314 -0
  221. package/dist/tools/importBrainSource.js.map +1 -0
  222. package/dist/tools/importWikiZip.d.ts +60 -0
  223. package/dist/tools/importWikiZip.d.ts.map +1 -0
  224. package/dist/tools/importWikiZip.js +379 -0
  225. package/dist/tools/importWikiZip.js.map +1 -0
  226. package/dist/tools/initProject.d.ts +34 -0
  227. package/dist/tools/initProject.d.ts.map +1 -0
  228. package/dist/tools/initProject.js +443 -0
  229. package/dist/tools/initProject.js.map +1 -0
  230. package/dist/tools/instructionContext.d.ts +60 -0
  231. package/dist/tools/instructionContext.d.ts.map +1 -0
  232. package/dist/tools/instructionContext.js +210 -0
  233. package/dist/tools/instructionContext.js.map +1 -0
  234. package/dist/tools/listArchivedPages.d.ts +17 -0
  235. package/dist/tools/listArchivedPages.d.ts.map +1 -0
  236. package/dist/tools/listArchivedPages.js +51 -0
  237. package/dist/tools/listArchivedPages.js.map +1 -0
  238. package/dist/tools/listPages.d.ts +28 -0
  239. package/dist/tools/listPages.d.ts.map +1 -0
  240. package/dist/tools/listPages.js +166 -0
  241. package/dist/tools/listPages.js.map +1 -0
  242. package/dist/tools/listWebhooks.d.ts +7 -0
  243. package/dist/tools/listWebhooks.d.ts.map +1 -0
  244. package/dist/tools/listWebhooks.js +10 -0
  245. package/dist/tools/listWebhooks.js.map +1 -0
  246. package/dist/tools/movePage.d.ts +18 -0
  247. package/dist/tools/movePage.d.ts.map +1 -0
  248. package/dist/tools/movePage.js +182 -0
  249. package/dist/tools/movePage.js.map +1 -0
  250. package/dist/tools/pageHistory.d.ts +36 -0
  251. package/dist/tools/pageHistory.d.ts.map +1 -0
  252. package/dist/tools/pageHistory.js +60 -0
  253. package/dist/tools/pageHistory.js.map +1 -0
  254. package/dist/tools/patchPage.d.ts +141 -0
  255. package/dist/tools/patchPage.d.ts.map +1 -0
  256. package/dist/tools/patchPage.js +287 -0
  257. package/dist/tools/patchPage.js.map +1 -0
  258. package/dist/tools/pullSessionBundle.d.ts +34 -0
  259. package/dist/tools/pullSessionBundle.d.ts.map +1 -0
  260. package/dist/tools/pullSessionBundle.js +432 -0
  261. package/dist/tools/pullSessionBundle.js.map +1 -0
  262. package/dist/tools/purgeWiki.d.ts +23 -0
  263. package/dist/tools/purgeWiki.d.ts.map +1 -0
  264. package/dist/tools/purgeWiki.js +113 -0
  265. package/dist/tools/purgeWiki.js.map +1 -0
  266. package/dist/tools/pushSessionBundle.d.ts +139 -0
  267. package/dist/tools/pushSessionBundle.d.ts.map +1 -0
  268. package/dist/tools/pushSessionBundle.js +282 -0
  269. package/dist/tools/pushSessionBundle.js.map +1 -0
  270. package/dist/tools/readGovernanceAudit.d.ts +17 -0
  271. package/dist/tools/readGovernanceAudit.d.ts.map +1 -0
  272. package/dist/tools/readGovernanceAudit.js +21 -0
  273. package/dist/tools/readGovernanceAudit.js.map +1 -0
  274. package/dist/tools/readPage.d.ts +13 -0
  275. package/dist/tools/readPage.d.ts.map +1 -0
  276. package/dist/tools/readPage.js +46 -0
  277. package/dist/tools/readPage.js.map +1 -0
  278. package/dist/tools/removeWebhook.d.ts +14 -0
  279. package/dist/tools/removeWebhook.d.ts.map +1 -0
  280. package/dist/tools/removeWebhook.js +12 -0
  281. package/dist/tools/removeWebhook.js.map +1 -0
  282. package/dist/tools/resolveProject.d.ts +58 -0
  283. package/dist/tools/resolveProject.d.ts.map +1 -0
  284. package/dist/tools/resolveProject.js +175 -0
  285. package/dist/tools/resolveProject.js.map +1 -0
  286. package/dist/tools/restorePage.d.ts +47 -0
  287. package/dist/tools/restorePage.d.ts.map +1 -0
  288. package/dist/tools/restorePage.js +111 -0
  289. package/dist/tools/restorePage.js.map +1 -0
  290. package/dist/tools/searchPages.d.ts +28 -0
  291. package/dist/tools/searchPages.d.ts.map +1 -0
  292. package/dist/tools/searchPages.js +372 -0
  293. package/dist/tools/searchPages.js.map +1 -0
  294. package/dist/tools/setWebhook.d.ts +30 -0
  295. package/dist/tools/setWebhook.d.ts.map +1 -0
  296. package/dist/tools/setWebhook.js +30 -0
  297. package/dist/tools/setWebhook.js.map +1 -0
  298. package/dist/tools/shareProject.d.ts +25 -0
  299. package/dist/tools/shareProject.d.ts.map +1 -0
  300. package/dist/tools/shareProject.js +56 -0
  301. package/dist/tools/shareProject.js.map +1 -0
  302. package/dist/tools/suggestLinks.d.ts +29 -0
  303. package/dist/tools/suggestLinks.d.ts.map +1 -0
  304. package/dist/tools/suggestLinks.js +214 -0
  305. package/dist/tools/suggestLinks.js.map +1 -0
  306. package/dist/tools/updatePage.d.ts +27 -0
  307. package/dist/tools/updatePage.d.ts.map +1 -0
  308. package/dist/tools/updatePage.js +118 -0
  309. package/dist/tools/updatePage.js.map +1 -0
  310. package/dist/webhooks/dispatch.d.ts +49 -0
  311. package/dist/webhooks/dispatch.d.ts.map +1 -0
  312. package/dist/webhooks/dispatch.js +159 -0
  313. package/dist/webhooks/dispatch.js.map +1 -0
  314. package/dist/webhooks/webhookRules.d.ts +34 -0
  315. package/dist/webhooks/webhookRules.d.ts.map +1 -0
  316. package/dist/webhooks/webhookRules.js +129 -0
  317. package/dist/webhooks/webhookRules.js.map +1 -0
  318. package/dist/wiki/frontmatter.d.ts +45 -0
  319. package/dist/wiki/frontmatter.d.ts.map +1 -0
  320. package/dist/wiki/frontmatter.js +68 -0
  321. package/dist/wiki/frontmatter.js.map +1 -0
  322. package/dist/wiki/index.d.ts +22 -0
  323. package/dist/wiki/index.d.ts.map +1 -0
  324. package/dist/wiki/index.js +203 -0
  325. package/dist/wiki/index.js.map +1 -0
  326. package/dist/wiki/ingestPolicy.d.ts +18 -0
  327. package/dist/wiki/ingestPolicy.d.ts.map +1 -0
  328. package/dist/wiki/ingestPolicy.js +64 -0
  329. package/dist/wiki/ingestPolicy.js.map +1 -0
  330. package/dist/wiki/layoutLint.d.ts +51 -0
  331. package/dist/wiki/layoutLint.d.ts.map +1 -0
  332. package/dist/wiki/layoutLint.js +146 -0
  333. package/dist/wiki/layoutLint.js.map +1 -0
  334. package/dist/wiki/links.d.ts +6 -0
  335. package/dist/wiki/links.d.ts.map +1 -0
  336. package/dist/wiki/links.js +45 -0
  337. package/dist/wiki/links.js.map +1 -0
  338. package/dist/wiki/log.d.ts +58 -0
  339. package/dist/wiki/log.d.ts.map +1 -0
  340. package/dist/wiki/log.js +126 -0
  341. package/dist/wiki/log.js.map +1 -0
  342. package/dist/wiki/path.d.ts +8 -0
  343. package/dist/wiki/path.d.ts.map +1 -0
  344. package/dist/wiki/path.js +25 -0
  345. package/dist/wiki/path.js.map +1 -0
  346. package/dist/wiki/storageLimits.d.ts +163 -0
  347. package/dist/wiki/storageLimits.d.ts.map +1 -0
  348. package/dist/wiki/storageLimits.js +281 -0
  349. package/dist/wiki/storageLimits.js.map +1 -0
  350. package/package.json +81 -0
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Write-time layout lint.
3
+ *
4
+ * `brain-structure.md` defines the layout invariants and `structure-audit.md`
5
+ * checks them periodically. Nothing checked them at the moment a page was
6
+ * written, so a write to a path the spec forbids succeeded silently and the
7
+ * violation was only found later — if anyone ran the audit at all.
8
+ *
9
+ * The cost of that gap is not tidiness. An instruction file naming a write-back
10
+ * target that does not exist does not fail: the target gets created, the write
11
+ * reports success, and the page everyone else reads goes stale. On one project
12
+ * that ran for four months — a video sat recorded as "in edit ~50%" ten weeks
13
+ * after it published, because state was being written to a phantom `active/`
14
+ * tree nothing else read (GH #458).
15
+ *
16
+ * Warnings only. Never blocks, never throws. Brains hold years of legacy layout
17
+ * and a hard failure mid-session is worse than a misfiled page — the point is to
18
+ * tell the agent while it still has the context to fix it.
19
+ *
20
+ * Path-only, no I/O: this runs on every write, and a round trip per write to
21
+ * check the spec would cost more than the spec is worth. That constraint shapes
22
+ * which rules are here — see the note on scope below.
23
+ */
24
+ export interface LayoutWarning {
25
+ /** Stable machine-readable rule id, so callers can filter. */
26
+ rule: string;
27
+ path: string;
28
+ message: string;
29
+ }
30
+ /**
31
+ * Check a write target against the layout invariants.
32
+ *
33
+ * Scope note: every rule here concerns the brain root or the top level of
34
+ * `projects/`. That is deliberate, and not only because those are the
35
+ * unambiguous cases. A project may declare its own filing system in
36
+ * `projects/<slug>/readme.md` frontmatter, and a declared project is audited
37
+ * against that skill's rules rather than the Operator invariants — but an
38
+ * override only ever governs the inside of its own folder. Keeping every rule
39
+ * outside project subtrees means no rule here can fire wrongly on a
40
+ * `mind-palace` project, without needing to read a readme to find out.
41
+ *
42
+ * Invariant 2 (thin project roots) is the notable omission. Its threshold is a
43
+ * count of loose files, which cannot be known from a path, and its allowed-list
44
+ * half would fire on the majority of legitimate writes in projects that predate
45
+ * the rule. It stays with the audit until there is data on how often it would
46
+ * actually fire.
47
+ */
48
+ export declare function lintLayout(name: string): LayoutWarning[];
49
+ /** Lint several targets at once, e.g. a bundle push. Order is preserved. */
50
+ export declare function lintLayoutMany(names: string[]): LayoutWarning[];
51
+ //# sourceMappingURL=layoutLint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layoutLint.d.ts","sourceRoot":"","sources":["../../src/wiki/layoutLint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,WAAW,aAAa;IAC5B,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAmCD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAmFxD;AAED,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,aAAa,EAAE,CAE/D"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Write-time layout lint.
3
+ *
4
+ * `brain-structure.md` defines the layout invariants and `structure-audit.md`
5
+ * checks them periodically. Nothing checked them at the moment a page was
6
+ * written, so a write to a path the spec forbids succeeded silently and the
7
+ * violation was only found later — if anyone ran the audit at all.
8
+ *
9
+ * The cost of that gap is not tidiness. An instruction file naming a write-back
10
+ * target that does not exist does not fail: the target gets created, the write
11
+ * reports success, and the page everyone else reads goes stale. On one project
12
+ * that ran for four months — a video sat recorded as "in edit ~50%" ten weeks
13
+ * after it published, because state was being written to a phantom `active/`
14
+ * tree nothing else read (GH #458).
15
+ *
16
+ * Warnings only. Never blocks, never throws. Brains hold years of legacy layout
17
+ * and a hard failure mid-session is worse than a misfiled page — the point is to
18
+ * tell the agent while it still has the context to fix it.
19
+ *
20
+ * Path-only, no I/O: this runs on every write, and a round trip per write to
21
+ * check the spec would cost more than the spec is worth. That constraint shapes
22
+ * which rules are here — see the note on scope below.
23
+ */
24
+ /**
25
+ * Directories permitted at the brain root, from `brain-structure.md`'s
26
+ * "cross-cutting root after a clean audit" list, plus the two system paths.
27
+ */
28
+ const SANCTIONED_ROOT_DIRS = new Set([
29
+ "projects",
30
+ "people",
31
+ "instructions",
32
+ "canonical",
33
+ "data",
34
+ "decisions",
35
+ "dumps",
36
+ "history",
37
+ "page-versions",
38
+ ]);
39
+ /** Files permitted loose at the brain root. `active-*.md` is matched separately. */
40
+ const SANCTIONED_ROOT_FILES = new Set(["open-loops.md", "readme.md", "index.md", "log.md"]);
41
+ /**
42
+ * Root taxonomies that hold cross-cutting content only, and where a subfolder is
43
+ * therefore always suspect.
44
+ *
45
+ * `canonical/` and `data/` are deliberately absent: the spec explicitly allows
46
+ * topic subfolders there (`data/watchlist/`, `data/shopping/`) and forbids only
47
+ * project-slug ones. Telling those apart needs to know the project list, which
48
+ * needs I/O, so that half of invariant 4 stays with the periodic audit.
49
+ */
50
+ const CROSS_CUTTING_DIRS_WITHOUT_SUBFOLDERS = new Set(["decisions", "history"]);
51
+ /** `<slug>-claude.md`, `<slug>-gpt.md`, `<slug>-cmo.md` — a project's agent instructions. */
52
+ const PROJECT_INSTRUCTION_FILE = /^instructions\/[^/]+-(claude|gpt|cmo)\.md$/;
53
+ /**
54
+ * Check a write target against the layout invariants.
55
+ *
56
+ * Scope note: every rule here concerns the brain root or the top level of
57
+ * `projects/`. That is deliberate, and not only because those are the
58
+ * unambiguous cases. A project may declare its own filing system in
59
+ * `projects/<slug>/readme.md` frontmatter, and a declared project is audited
60
+ * against that skill's rules rather than the Operator invariants — but an
61
+ * override only ever governs the inside of its own folder. Keeping every rule
62
+ * outside project subtrees means no rule here can fire wrongly on a
63
+ * `mind-palace` project, without needing to read a readme to find out.
64
+ *
65
+ * Invariant 2 (thin project roots) is the notable omission. Its threshold is a
66
+ * count of loose files, which cannot be known from a path, and its allowed-list
67
+ * half would fire on the majority of legitimate writes in projects that predate
68
+ * the rule. It stays with the audit until there is data on how often it would
69
+ * actually fire.
70
+ */
71
+ export function lintLayout(name) {
72
+ const warnings = [];
73
+ const path = name.replace(/^\/+/, "");
74
+ if (path === "")
75
+ return warnings;
76
+ const segments = path.split("/");
77
+ const [head, ...rest] = segments;
78
+ const isFileAtRoot = segments.length === 1;
79
+ // Invariant 1 — projects/ holds only <slug>/ folders plus a single readme.md.
80
+ if (head === "projects" && rest.length === 1 && rest[0].endsWith(".md")) {
81
+ if (rest[0] !== "readme.md") {
82
+ warnings.push({
83
+ rule: "projects-root-loose-file",
84
+ path,
85
+ message: `'${path}' puts a loose file directly in projects/, which holds only <slug>/ ` +
86
+ `folders plus a single readme.md (brain-structure.md invariant 1). A cross-project ` +
87
+ `tracker belongs at the brain root; project content belongs in projects/<slug>/.`,
88
+ });
89
+ }
90
+ }
91
+ // Invariant 4 — project instructions never live in global instructions/.
92
+ if (PROJECT_INSTRUCTION_FILE.test(path)) {
93
+ const slug = rest[0].replace(/-(claude|gpt|cmo)\.md$/, "");
94
+ const platform = rest[0].match(/-(claude|gpt|cmo)\.md$/)?.[1] ?? "claude";
95
+ warnings.push({
96
+ rule: "project-instructions-at-root",
97
+ path,
98
+ message: `'${path}' looks like a project's agent instructions in global instructions/, which ` +
99
+ `is reserved for operating rules and templates (brain-structure.md invariant 4). ` +
100
+ `Project instructions live at projects/${slug}/instructions/${platform}.md.`,
101
+ });
102
+ }
103
+ // Invariant 4 — no project-scoped subtree under root decisions/ or history/.
104
+ if (CROSS_CUTTING_DIRS_WITHOUT_SUBFOLDERS.has(head) &&
105
+ rest.length > 1) {
106
+ warnings.push({
107
+ rule: "project-subfolder-at-cross-cutting-root",
108
+ path,
109
+ message: `'${path}' nests a subfolder under root ${head}/, which holds cross-cutting ` +
110
+ `content only (brain-structure.md invariant 4). If '${rest[0]}' is a project, this ` +
111
+ `belongs at projects/${rest[0]}/${head}/.`,
112
+ });
113
+ }
114
+ // A write that invents a new top-level folder. This is the one that matters
115
+ // most: `active/` was born exactly this way, from an instruction file naming a
116
+ // write-back target nobody had created, and every write to it reported success.
117
+ if (!isFileAtRoot && !SANCTIONED_ROOT_DIRS.has(head)) {
118
+ warnings.push({
119
+ rule: "unsanctioned-root-folder",
120
+ path,
121
+ message: `'${path}' is under a top-level folder ('${head}/') outside the sanctioned set ` +
122
+ `(${[...SANCTIONED_ROOT_DIRS].filter((d) => d !== "page-versions").sort().join(", ")}). ` +
123
+ `If this is project content it belongs under projects/<slug>/. Nothing blocks this ` +
124
+ `write, so a mistyped or invented path will succeed and be read by nobody.`,
125
+ });
126
+ }
127
+ // A loose file at the brain root that is not one of the permitted few.
128
+ if (isFileAtRoot && path.endsWith(".md")) {
129
+ const permitted = SANCTIONED_ROOT_FILES.has(path) || /^active-[^/]+\.md$/.test(path);
130
+ if (!permitted) {
131
+ warnings.push({
132
+ rule: "unsanctioned-root-file",
133
+ path,
134
+ message: `'${path}' is a loose file at the brain root, which holds only open-loops.md, ` +
135
+ `active-*.md trackers, and the taxonomy folders (brain-structure.md). Project ` +
136
+ `content belongs under projects/<slug>/.`,
137
+ });
138
+ }
139
+ }
140
+ return warnings;
141
+ }
142
+ /** Lint several targets at once, e.g. a bundle push. Order is preserved. */
143
+ export function lintLayoutMany(names) {
144
+ return names.flatMap((name) => lintLayout(name));
145
+ }
146
+ //# sourceMappingURL=layoutLint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layoutLint.js","sourceRoot":"","sources":["../../src/wiki/layoutLint.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export interface ExtractedWikiLink {
2
+ target: string;
3
+ anchorText: string;
4
+ }
5
+ export declare function extractWikiLinks(source: string, markdown: string): ExtractedWikiLink[];
6
+ //# sourceMappingURL=links.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"links.d.ts","sourceRoot":"","sources":["../../src/wiki/links.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAID,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAkBtF"}
@@ -0,0 +1,45 @@
1
+ import path from "path";
2
+ const MARKDOWN_LINK_REGEX = /(?<!!)\[([^\]]+)\]\(([^)]+)\)/g;
3
+ export function extractWikiLinks(source, markdown) {
4
+ const unique = new Map();
5
+ const sourceDir = path.posix.dirname(source);
6
+ for (const match of markdown.matchAll(MARKDOWN_LINK_REGEX)) {
7
+ const anchorText = (match[1] ?? "").trim();
8
+ const hrefRaw = (match[2] ?? "").trim();
9
+ if (!anchorText || !hrefRaw)
10
+ continue;
11
+ const target = resolveWikiLinkTarget(sourceDir, hrefRaw);
12
+ if (!target)
13
+ continue;
14
+ if (!unique.has(target)) {
15
+ unique.set(target, anchorText);
16
+ }
17
+ }
18
+ return Array.from(unique.entries()).map(([target, anchorText]) => ({ target, anchorText }));
19
+ }
20
+ function resolveWikiLinkTarget(sourceDir, href) {
21
+ const token = href.split(/\s+/)[0]?.trim().replace(/^<|>$/g, "") ?? "";
22
+ if (!token)
23
+ return null;
24
+ if (token.startsWith("#"))
25
+ return null;
26
+ if (/^[a-z][a-z0-9+.-]*:/i.test(token) || token.startsWith("//"))
27
+ return null;
28
+ const [withoutQuery] = token.split("?");
29
+ const [withoutFragment] = withoutQuery.split("#");
30
+ if (!withoutFragment)
31
+ return null;
32
+ const normalized = withoutFragment.replace(/\\/g, "/");
33
+ const resolved = normalized.startsWith("/")
34
+ ? normalized.slice(1)
35
+ : path.posix.normalize(path.posix.join(sourceDir, normalized));
36
+ if (!resolved || resolved.startsWith("../"))
37
+ return null;
38
+ const ext = path.posix.extname(resolved);
39
+ if (ext && ext !== ".md")
40
+ return null;
41
+ if (!ext)
42
+ return `${resolved}.md`;
43
+ return resolved;
44
+ }
45
+ //# sourceMappingURL=links.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"links.js","sourceRoot":"","sources":["../../src/wiki/links.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAOxB,MAAM,mBAAmB,GAAG,gCAAgC,CAAC;AAE7D,MAAM,UAAU,gBAAgB,CAAC,MAAc,EAAE,QAAgB;IAC/D,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAE7C,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC;QAC3D,MAAM,UAAU,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3C,MAAM,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACxC,IAAI,CAAC,UAAU,IAAI,CAAC,OAAO;YAAE,SAAS;QAEtC,MAAM,MAAM,GAAG,qBAAqB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACzD,IAAI,CAAC,MAAM;YAAE,SAAS;QAEtB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACxB,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;AAC9F,CAAC;AAED,SAAS,qBAAqB,CAAC,SAAiB,EAAE,IAAY;IAC5D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;IACvE,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,IAAI,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAE9E,MAAM,CAAC,YAAY,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,CAAC,eAAe,CAAC,GAAG,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAClD,IAAI,CAAC,eAAe;QAAE,OAAO,IAAI,CAAC;IAElC,MAAM,UAAU,GAAG,eAAe,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACvD,MAAM,QAAQ,GAAG,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC;QACzC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;QACrB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC;IACjE,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEzD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzC,IAAI,GAAG,IAAI,GAAG,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IACtC,IAAI,CAAC,GAAG;QAAE,OAAO,GAAG,QAAQ,KAAK,CAAC;IAClC,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,58 @@
1
+ import type { StorageClient } from "../storage/client.js";
2
+ /**
3
+ * Hard ceiling on log.md, enforced on every append.
4
+ *
5
+ * Storage has no append primitive, so appendLogEntry is a read-modify-write of
6
+ * the whole file. Left unbounded it grows forever and eventually crosses
7
+ * MAX_FILE_SIZE_BYTES (2 MB), at which point updateFile throws — and because
8
+ * every caller invokes appendLogEntry fire-and-forget with a swallowed catch,
9
+ * logging would stop silently. Production was at 1.0 MB and growing ~30 KB/day
10
+ * when this was added (GH #417).
11
+ *
12
+ * 512 KB is a deliberate compromise, not a perf target. The activity digest
13
+ * parses log.md for the last 7 days, so the file cannot be trimmed below that
14
+ * window without breaking it. At the observed ~620 bytes/entry this holds
15
+ * roughly 845 entries (~17 days at the observed rate) — comfortable margin
16
+ * over the digest's 7 days, and a quarter of the hard cap.
17
+ *
18
+ * Getting the write path genuinely cheap needs the events moved out of a
19
+ * markdown file entirely; this bounds the damage until then.
20
+ */
21
+ export declare const LOG_MAX_BYTES: number;
22
+ /**
23
+ * Age-based trim, applied in addition to the byte ceiling. Note this is the
24
+ * weaker of the two bounds in practice — production had zero entries older
25
+ * than two months, so the byte ceiling is what actually does the work.
26
+ */
27
+ export declare const LOG_RETENTION_DAYS = 90;
28
+ /**
29
+ * Trim log.md to the configured bounds, oldest entries first.
30
+ *
31
+ * Preserves any preamble before the first entry and keeps entries in their
32
+ * original order. Returns the content unchanged when nothing needs dropping,
33
+ * so a bounded file costs only the scan.
34
+ */
35
+ export declare function rotateLog(content: string, now?: Date): string;
36
+ export type LogEventType = "ingest" | "query" | "update" | "create" | "move" | "delete" | "lint" | "error";
37
+ export interface LogEntry {
38
+ type: LogEventType;
39
+ title: string;
40
+ detail?: string;
41
+ }
42
+ /**
43
+ * Record an activity event.
44
+ *
45
+ * On Postgres-backed deployments this is a single indexed insert into
46
+ * `wiki_events`, and `log.md` is not touched at all. Storage has no append
47
+ * primitive, so the previous implementation read the whole file, concatenated,
48
+ * and wrote it back — ~920 KB of round-trip traffic per page write, measured
49
+ * at 2.6-3.6s against the production file, on every create/update/patch/move/
50
+ * restore/delete (GH #417, #419).
51
+ *
52
+ * Local and Drive backends keep appending to `log.md`, bounded by rotateLog().
53
+ *
54
+ * @param userId Storage owner subject. Empty for api-key / connector-key
55
+ * writes, which share the global root.
56
+ */
57
+ export declare function appendLogEntry(drive: StorageClient, entry: LogEntry, userId?: string): Promise<void>;
58
+ //# sourceMappingURL=log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../../src/wiki/log.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAK1D;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,aAAa,QAAa,CAAC;AAExC;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAUrC;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAa,GAAG,MAAM,CAwDnE;AAED,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAE3G,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,QAAQ,EACf,MAAM,SAAK,GACV,OAAO,CAAC,IAAI,CAAC,CAkBf"}
@@ -0,0 +1,126 @@
1
+ import { getPostgresSearchIndex } from "../search/postgresIndex.js";
2
+ const LOG_FILE = "log.md";
3
+ /**
4
+ * Hard ceiling on log.md, enforced on every append.
5
+ *
6
+ * Storage has no append primitive, so appendLogEntry is a read-modify-write of
7
+ * the whole file. Left unbounded it grows forever and eventually crosses
8
+ * MAX_FILE_SIZE_BYTES (2 MB), at which point updateFile throws — and because
9
+ * every caller invokes appendLogEntry fire-and-forget with a swallowed catch,
10
+ * logging would stop silently. Production was at 1.0 MB and growing ~30 KB/day
11
+ * when this was added (GH #417).
12
+ *
13
+ * 512 KB is a deliberate compromise, not a perf target. The activity digest
14
+ * parses log.md for the last 7 days, so the file cannot be trimmed below that
15
+ * window without breaking it. At the observed ~620 bytes/entry this holds
16
+ * roughly 845 entries (~17 days at the observed rate) — comfortable margin
17
+ * over the digest's 7 days, and a quarter of the hard cap.
18
+ *
19
+ * Getting the write path genuinely cheap needs the events moved out of a
20
+ * markdown file entirely; this bounds the damage until then.
21
+ */
22
+ export const LOG_MAX_BYTES = 512 * 1024;
23
+ /**
24
+ * Age-based trim, applied in addition to the byte ceiling. Note this is the
25
+ * weaker of the two bounds in practice — production had zero entries older
26
+ * than two months, so the byte ceiling is what actually does the work.
27
+ */
28
+ export const LOG_RETENTION_DAYS = 90;
29
+ const ENTRY_HEADING = /^##\s+\[(\d{4}-\d{2}-\d{2})\]\s+\w+\s+\|/;
30
+ const ROTATION_MARKER = /^<!-- log rotated:.*-->$/;
31
+ function envInt(name, fallback) {
32
+ const raw = parseInt(process.env[name] ?? "", 10);
33
+ return Number.isFinite(raw) && raw > 0 ? raw : fallback;
34
+ }
35
+ /**
36
+ * Trim log.md to the configured bounds, oldest entries first.
37
+ *
38
+ * Preserves any preamble before the first entry and keeps entries in their
39
+ * original order. Returns the content unchanged when nothing needs dropping,
40
+ * so a bounded file costs only the scan.
41
+ */
42
+ export function rotateLog(content, now = new Date()) {
43
+ const lines = content.split("\n");
44
+ const firstEntryIdx = lines.findIndex((l) => ENTRY_HEADING.test(l));
45
+ if (firstEntryIdx === -1)
46
+ return content;
47
+ const preamble = lines
48
+ .slice(0, firstEntryIdx)
49
+ .filter((l) => !ROTATION_MARKER.test(l));
50
+ // Group lines into entry blocks: a heading plus everything up to the next one.
51
+ const blocks = [];
52
+ let current = null;
53
+ for (const line of lines.slice(firstEntryIdx)) {
54
+ const m = line.match(ENTRY_HEADING);
55
+ if (m) {
56
+ if (current)
57
+ blocks.push({ date: current.date, text: current.buf.join("\n") });
58
+ current = { date: m[1], buf: [line] };
59
+ }
60
+ else if (current) {
61
+ current.buf.push(line);
62
+ }
63
+ }
64
+ if (current)
65
+ blocks.push({ date: current.date, text: current.buf.join("\n") });
66
+ const totalBefore = blocks.length;
67
+ const cutoff = new Date(now.getTime() - envInt("BRAINS_LOG_RETENTION_DAYS", LOG_RETENTION_DAYS) * 86_400_000)
68
+ .toISOString()
69
+ .slice(0, 10);
70
+ const kept = blocks.filter((b) => b.date >= cutoff);
71
+ // Byte ceiling — drop oldest until under. Always keep at least one entry so
72
+ // an oversized single entry cannot empty the log.
73
+ const maxBytes = envInt("BRAINS_LOG_MAX_BYTES", LOG_MAX_BYTES);
74
+ const noteFor = (dropped) => dropped === 0
75
+ ? []
76
+ : [
77
+ `<!-- log rotated: ${dropped} older ${dropped === 1 ? "entry" : "entries"} trimmed on ` +
78
+ `${now.toISOString().slice(0, 10)} (keeping <=${Math.round(maxBytes / 1024)} KB) -->`,
79
+ ];
80
+ const render = (bs, note) => [...preamble, ...note, ...bs.map((b) => b.text.trimEnd())].join("\n").trimEnd() + "\n";
81
+ // Measure the note too — it ships in the output, so excluding it here would
82
+ // let the result exceed maxBytes by the marker's length.
83
+ while (kept.length > 1) {
84
+ if (Buffer.byteLength(render(kept, noteFor(totalBefore - kept.length)), "utf8") <= maxBytes)
85
+ break;
86
+ kept.shift();
87
+ }
88
+ const dropped = totalBefore - kept.length;
89
+ if (dropped === 0)
90
+ return content;
91
+ return render(kept, noteFor(dropped));
92
+ }
93
+ /**
94
+ * Record an activity event.
95
+ *
96
+ * On Postgres-backed deployments this is a single indexed insert into
97
+ * `wiki_events`, and `log.md` is not touched at all. Storage has no append
98
+ * primitive, so the previous implementation read the whole file, concatenated,
99
+ * and wrote it back — ~920 KB of round-trip traffic per page write, measured
100
+ * at 2.6-3.6s against the production file, on every create/update/patch/move/
101
+ * restore/delete (GH #417, #419).
102
+ *
103
+ * Local and Drive backends keep appending to `log.md`, bounded by rotateLog().
104
+ *
105
+ * @param userId Storage owner subject. Empty for api-key / connector-key
106
+ * writes, which share the global root.
107
+ */
108
+ export async function appendLogEntry(drive, entry, userId = "") {
109
+ const pgIndex = getPostgresSearchIndex();
110
+ if (pgIndex.isConfigured()) {
111
+ await pgIndex.appendEvent({
112
+ userId,
113
+ type: entry.type,
114
+ title: entry.title,
115
+ detail: entry.detail,
116
+ });
117
+ return;
118
+ }
119
+ const file = await drive.readFile(LOG_FILE);
120
+ const date = new Date().toISOString().slice(0, 10);
121
+ const heading = `## [${date}] ${entry.type} | ${entry.title}`;
122
+ const body = entry.detail ? `\n${entry.detail}\n` : "";
123
+ const appended = `${file.content.trimEnd()}\n\n${heading}${body}\n`;
124
+ await drive.updateFile(file.fileId, rotateLog(appended), file.revision);
125
+ }
126
+ //# sourceMappingURL=log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.js","sourceRoot":"","sources":["../../src/wiki/log.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Normalize user-provided page paths to wiki-safe relative paths.
3
+ * Examples:
4
+ * - "Fitty Cent" -> "fitty-cent.md"
5
+ * - "projects/Fitty Cent" -> "projects/fitty-cent.md"
6
+ */
7
+ export declare function normalizeWikiPath(raw: string): string;
8
+ //# sourceMappingURL=path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/wiki/path.ts"],"names":[],"mappings":"AAWA;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CASrD"}
@@ -0,0 +1,25 @@
1
+ function slugSegment(value) {
2
+ const slug = value
3
+ .trim()
4
+ .toLowerCase()
5
+ .replace(/\.md$/i, "")
6
+ .replace(/[^a-z0-9-]+/g, "-")
7
+ .replace(/-+/g, "-")
8
+ .replace(/^-|-$/g, "");
9
+ return slug || "untitled";
10
+ }
11
+ /**
12
+ * Normalize user-provided page paths to wiki-safe relative paths.
13
+ * Examples:
14
+ * - "Fitty Cent" -> "fitty-cent.md"
15
+ * - "projects/Fitty Cent" -> "projects/fitty-cent.md"
16
+ */
17
+ export function normalizeWikiPath(raw) {
18
+ const normalized = raw.replace(/\\/g, "/").trim().replace(/^\/+|\/+$/g, "");
19
+ const parts = normalized.split("/").filter(Boolean);
20
+ const safeParts = (parts.length > 0 ? parts : ["untitled"]).map((p) => slugSegment(p));
21
+ const last = safeParts[safeParts.length - 1];
22
+ safeParts[safeParts.length - 1] = last.endsWith(".md") ? last : `${last}.md`;
23
+ return safeParts.join("/");
24
+ }
25
+ //# sourceMappingURL=path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.js","sourceRoot":"","sources":["../../src/wiki/path.ts"],"names":[],"mappings":"AAAA,SAAS,WAAW,CAAC,KAAa;IAChC,MAAM,IAAI,GAAG,KAAK;SACf,IAAI,EAAE;SACN,WAAW,EAAE;SACb,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;SACrB,OAAO,CAAC,cAAc,EAAE,GAAG,CAAC;SAC5B,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IACzB,OAAO,IAAI,IAAI,UAAU,CAAC;AAC5B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IAC5E,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACpD,MAAM,SAAS,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IAEvF,MAAM,IAAI,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC7C,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,CAAC;IAE7E,OAAO,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC7B,CAAC"}