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.
- package/README.md +95 -0
- package/dist/access/projectShares.d.ts +48 -0
- package/dist/access/projectShares.d.ts.map +1 -0
- package/dist/access/projectShares.js +203 -0
- package/dist/access/projectShares.js.map +1 -0
- package/dist/access/sharedInstructionsNote.d.ts +37 -0
- package/dist/access/sharedInstructionsNote.d.ts.map +1 -0
- package/dist/access/sharedInstructionsNote.js +76 -0
- package/dist/access/sharedInstructionsNote.js.map +1 -0
- package/dist/admin/seedUsers.d.ts +23 -0
- package/dist/admin/seedUsers.d.ts.map +1 -0
- package/dist/admin/seedUsers.js +168 -0
- package/dist/admin/seedUsers.js.map +1 -0
- package/dist/api/openapi.d.ts +2 -0
- package/dist/api/openapi.d.ts.map +1 -0
- package/dist/api/openapi.js +942 -0
- package/dist/api/openapi.js.map +1 -0
- package/dist/auth/clientStore.d.ts +43 -0
- package/dist/auth/clientStore.d.ts.map +1 -0
- package/dist/auth/clientStore.js +168 -0
- package/dist/auth/clientStore.js.map +1 -0
- package/dist/auth/deleteAccount.d.ts +34 -0
- package/dist/auth/deleteAccount.d.ts.map +1 -0
- package/dist/auth/deleteAccount.js +203 -0
- package/dist/auth/deleteAccount.js.map +1 -0
- package/dist/auth/oauth.d.ts +118 -0
- package/dist/auth/oauth.d.ts.map +1 -0
- package/dist/auth/oauth.js +491 -0
- package/dist/auth/oauth.js.map +1 -0
- package/dist/auth/patStore.d.ts +87 -0
- package/dist/auth/patStore.d.ts.map +1 -0
- package/dist/auth/patStore.js +308 -0
- package/dist/auth/patStore.js.map +1 -0
- package/dist/auth/supabase.d.ts +15 -0
- package/dist/auth/supabase.d.ts.map +1 -0
- package/dist/auth/supabase.js +66 -0
- package/dist/auth/supabase.js.map +1 -0
- package/dist/auth/tokenStore.d.ts +73 -0
- package/dist/auth/tokenStore.d.ts.map +1 -0
- package/dist/auth/tokenStore.js +261 -0
- package/dist/auth/tokenStore.js.map +1 -0
- package/dist/backup/drive.d.ts +24 -0
- package/dist/backup/drive.d.ts.map +1 -0
- package/dist/backup/drive.js +70 -0
- package/dist/backup/drive.js.map +1 -0
- package/dist/billing/tierStore.d.ts +59 -0
- package/dist/billing/tierStore.d.ts.map +1 -0
- package/dist/billing/tierStore.js +152 -0
- package/dist/billing/tierStore.js.map +1 -0
- package/dist/digest/activityDigest.d.ts +32 -0
- package/dist/digest/activityDigest.d.ts.map +1 -0
- package/dist/digest/activityDigest.js +299 -0
- package/dist/digest/activityDigest.js.map +1 -0
- package/dist/digest/digestCron.d.ts +40 -0
- package/dist/digest/digestCron.d.ts.map +1 -0
- package/dist/digest/digestCron.js +146 -0
- package/dist/digest/digestCron.js.map +1 -0
- package/dist/digest/digestPreferences.d.ts +25 -0
- package/dist/digest/digestPreferences.d.ts.map +1 -0
- package/dist/digest/digestPreferences.js +79 -0
- package/dist/digest/digestPreferences.js.map +1 -0
- package/dist/digest/emailSender.d.ts +20 -0
- package/dist/digest/emailSender.d.ts.map +1 -0
- package/dist/digest/emailSender.js +35 -0
- package/dist/digest/emailSender.js.map +1 -0
- package/dist/drive/client.d.ts +43 -0
- package/dist/drive/client.d.ts.map +1 -0
- package/dist/drive/client.js +282 -0
- package/dist/drive/client.js.map +1 -0
- package/dist/drive/errors.d.ts +27 -0
- package/dist/drive/errors.d.ts.map +1 -0
- package/dist/drive/errors.js +51 -0
- package/dist/drive/errors.js.map +1 -0
- package/dist/drive/fileCache.d.ts +22 -0
- package/dist/drive/fileCache.d.ts.map +1 -0
- package/dist/drive/fileCache.js +57 -0
- package/dist/drive/fileCache.js.map +1 -0
- package/dist/governance/accessControl.d.ts +87 -0
- package/dist/governance/accessControl.d.ts.map +1 -0
- package/dist/governance/accessControl.js +179 -0
- package/dist/governance/accessControl.js.map +1 -0
- package/dist/html/components.d.ts +8 -0
- package/dist/html/components.d.ts.map +1 -0
- package/dist/html/components.js +19 -0
- package/dist/html/components.js.map +1 -0
- package/dist/html/layout.d.ts +7 -0
- package/dist/html/layout.d.ts.map +1 -0
- package/dist/html/layout.js +24 -0
- package/dist/html/layout.js.map +1 -0
- package/dist/html/metricsUi.d.ts +8 -0
- package/dist/html/metricsUi.d.ts.map +1 -0
- package/dist/html/metricsUi.js +271 -0
- package/dist/html/metricsUi.js.map +1 -0
- package/dist/httpServer.d.ts +19 -0
- package/dist/httpServer.d.ts.map +1 -0
- package/dist/httpServer.js +3913 -0
- package/dist/httpServer.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/jobs/asyncJobRegistry.d.ts +30 -0
- package/dist/jobs/asyncJobRegistry.d.ts.map +1 -0
- package/dist/jobs/asyncJobRegistry.js +70 -0
- package/dist/jobs/asyncJobRegistry.js.map +1 -0
- package/dist/observability/metricsQuery.d.ts +41 -0
- package/dist/observability/metricsQuery.d.ts.map +1 -0
- package/dist/observability/metricsQuery.js +106 -0
- package/dist/observability/metricsQuery.js.map +1 -0
- package/dist/observability/relevanceSignal.d.ts +26 -0
- package/dist/observability/relevanceSignal.d.ts.map +1 -0
- package/dist/observability/relevanceSignal.js +95 -0
- package/dist/observability/relevanceSignal.js.map +1 -0
- package/dist/observability/requestEvents.d.ts +31 -0
- package/dist/observability/requestEvents.d.ts.map +1 -0
- package/dist/observability/requestEvents.js +54 -0
- package/dist/observability/requestEvents.js.map +1 -0
- package/dist/observability/slowQueryAlert.d.ts +25 -0
- package/dist/observability/slowQueryAlert.d.ts.map +1 -0
- package/dist/observability/slowQueryAlert.js +67 -0
- package/dist/observability/slowQueryAlert.js.map +1 -0
- package/dist/observability/zeroResultTriage.d.ts +22 -0
- package/dist/observability/zeroResultTriage.d.ts.map +1 -0
- package/dist/observability/zeroResultTriage.js +116 -0
- package/dist/observability/zeroResultTriage.js.map +1 -0
- package/dist/search/embeddings.d.ts +31 -0
- package/dist/search/embeddings.d.ts.map +1 -0
- package/dist/search/embeddings.js +91 -0
- package/dist/search/embeddings.js.map +1 -0
- package/dist/search/fileWatcher.d.ts +28 -0
- package/dist/search/fileWatcher.d.ts.map +1 -0
- package/dist/search/fileWatcher.js +103 -0
- package/dist/search/fileWatcher.js.map +1 -0
- package/dist/search/hotQueryCache.d.ts +31 -0
- package/dist/search/hotQueryCache.d.ts.map +1 -0
- package/dist/search/hotQueryCache.js +103 -0
- package/dist/search/hotQueryCache.js.map +1 -0
- package/dist/search/postgresIndex.d.ts +439 -0
- package/dist/search/postgresIndex.d.ts.map +1 -0
- package/dist/search/postgresIndex.js +1610 -0
- package/dist/search/postgresIndex.js.map +1 -0
- package/dist/search/sqliteIndex.d.ts +66 -0
- package/dist/search/sqliteIndex.d.ts.map +1 -0
- package/dist/search/sqliteIndex.js +342 -0
- package/dist/search/sqliteIndex.js.map +1 -0
- package/dist/server.d.ts +43 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +532 -0
- package/dist/server.js.map +1 -0
- package/dist/session/bundle.d.ts +53 -0
- package/dist/session/bundle.d.ts.map +1 -0
- package/dist/session/bundle.js +97 -0
- package/dist/session/bundle.js.map +1 -0
- package/dist/storage/client.d.ts +157 -0
- package/dist/storage/client.d.ts.map +1 -0
- package/dist/storage/client.js +825 -0
- package/dist/storage/client.js.map +1 -0
- package/dist/tools/addWebhookPattern.d.ts +15 -0
- package/dist/tools/addWebhookPattern.d.ts.map +1 -0
- package/dist/tools/addWebhookPattern.js +15 -0
- package/dist/tools/addWebhookPattern.js.map +1 -0
- package/dist/tools/appendLog.d.ts +18 -0
- package/dist/tools/appendLog.d.ts.map +1 -0
- package/dist/tools/appendLog.js +18 -0
- package/dist/tools/appendLog.js.map +1 -0
- package/dist/tools/auditIndex.d.ts +15 -0
- package/dist/tools/auditIndex.d.ts.map +1 -0
- package/dist/tools/auditIndex.js +165 -0
- package/dist/tools/auditIndex.js.map +1 -0
- package/dist/tools/batchDeletePages.d.ts +18 -0
- package/dist/tools/batchDeletePages.d.ts.map +1 -0
- package/dist/tools/batchDeletePages.js +143 -0
- package/dist/tools/batchDeletePages.js.map +1 -0
- package/dist/tools/coerce.d.ts +3 -0
- package/dist/tools/coerce.d.ts.map +1 -0
- package/dist/tools/coerce.js +12 -0
- package/dist/tools/coerce.js.map +1 -0
- package/dist/tools/createPage.d.ts +24 -0
- package/dist/tools/createPage.d.ts.map +1 -0
- package/dist/tools/createPage.js +99 -0
- package/dist/tools/createPage.js.map +1 -0
- package/dist/tools/createPages.d.ts +51 -0
- package/dist/tools/createPages.d.ts.map +1 -0
- package/dist/tools/createPages.js +91 -0
- package/dist/tools/createPages.js.map +1 -0
- package/dist/tools/deletePage.d.ts +19 -0
- package/dist/tools/deletePage.d.ts.map +1 -0
- package/dist/tools/deletePage.js +127 -0
- package/dist/tools/deletePage.js.map +1 -0
- package/dist/tools/exportWiki.d.ts +48 -0
- package/dist/tools/exportWiki.d.ts.map +1 -0
- package/dist/tools/exportWiki.js +233 -0
- package/dist/tools/exportWiki.js.map +1 -0
- package/dist/tools/extractLinks.d.ts +14 -0
- package/dist/tools/extractLinks.d.ts.map +1 -0
- package/dist/tools/extractLinks.js +25 -0
- package/dist/tools/extractLinks.js.map +1 -0
- package/dist/tools/generateClaudeInstructions.d.ts +27 -0
- package/dist/tools/generateClaudeInstructions.d.ts.map +1 -0
- package/dist/tools/generateClaudeInstructions.js +263 -0
- package/dist/tools/generateClaudeInstructions.js.map +1 -0
- package/dist/tools/generateGptInstructions.d.ts +27 -0
- package/dist/tools/generateGptInstructions.d.ts.map +1 -0
- package/dist/tools/generateGptInstructions.js +208 -0
- package/dist/tools/generateGptInstructions.js.map +1 -0
- package/dist/tools/getBacklinks.d.ts +12 -0
- package/dist/tools/getBacklinks.d.ts.map +1 -0
- package/dist/tools/getBacklinks.js +13 -0
- package/dist/tools/getBacklinks.js.map +1 -0
- package/dist/tools/getRelated.d.ts +12 -0
- package/dist/tools/getRelated.d.ts.map +1 -0
- package/dist/tools/getRelated.js +13 -0
- package/dist/tools/getRelated.js.map +1 -0
- package/dist/tools/healthCheck.d.ts +5 -0
- package/dist/tools/healthCheck.d.ts.map +1 -0
- package/dist/tools/healthCheck.js +167 -0
- package/dist/tools/healthCheck.js.map +1 -0
- package/dist/tools/importBrainSource.d.ts +36 -0
- package/dist/tools/importBrainSource.d.ts.map +1 -0
- package/dist/tools/importBrainSource.js +314 -0
- package/dist/tools/importBrainSource.js.map +1 -0
- package/dist/tools/importWikiZip.d.ts +60 -0
- package/dist/tools/importWikiZip.d.ts.map +1 -0
- package/dist/tools/importWikiZip.js +379 -0
- package/dist/tools/importWikiZip.js.map +1 -0
- package/dist/tools/initProject.d.ts +34 -0
- package/dist/tools/initProject.d.ts.map +1 -0
- package/dist/tools/initProject.js +443 -0
- package/dist/tools/initProject.js.map +1 -0
- package/dist/tools/instructionContext.d.ts +60 -0
- package/dist/tools/instructionContext.d.ts.map +1 -0
- package/dist/tools/instructionContext.js +210 -0
- package/dist/tools/instructionContext.js.map +1 -0
- package/dist/tools/listArchivedPages.d.ts +17 -0
- package/dist/tools/listArchivedPages.d.ts.map +1 -0
- package/dist/tools/listArchivedPages.js +51 -0
- package/dist/tools/listArchivedPages.js.map +1 -0
- package/dist/tools/listPages.d.ts +28 -0
- package/dist/tools/listPages.d.ts.map +1 -0
- package/dist/tools/listPages.js +166 -0
- package/dist/tools/listPages.js.map +1 -0
- package/dist/tools/listWebhooks.d.ts +7 -0
- package/dist/tools/listWebhooks.d.ts.map +1 -0
- package/dist/tools/listWebhooks.js +10 -0
- package/dist/tools/listWebhooks.js.map +1 -0
- package/dist/tools/movePage.d.ts +18 -0
- package/dist/tools/movePage.d.ts.map +1 -0
- package/dist/tools/movePage.js +182 -0
- package/dist/tools/movePage.js.map +1 -0
- package/dist/tools/pageHistory.d.ts +36 -0
- package/dist/tools/pageHistory.d.ts.map +1 -0
- package/dist/tools/pageHistory.js +60 -0
- package/dist/tools/pageHistory.js.map +1 -0
- package/dist/tools/patchPage.d.ts +141 -0
- package/dist/tools/patchPage.d.ts.map +1 -0
- package/dist/tools/patchPage.js +287 -0
- package/dist/tools/patchPage.js.map +1 -0
- package/dist/tools/pullSessionBundle.d.ts +34 -0
- package/dist/tools/pullSessionBundle.d.ts.map +1 -0
- package/dist/tools/pullSessionBundle.js +432 -0
- package/dist/tools/pullSessionBundle.js.map +1 -0
- package/dist/tools/purgeWiki.d.ts +23 -0
- package/dist/tools/purgeWiki.d.ts.map +1 -0
- package/dist/tools/purgeWiki.js +113 -0
- package/dist/tools/purgeWiki.js.map +1 -0
- package/dist/tools/pushSessionBundle.d.ts +139 -0
- package/dist/tools/pushSessionBundle.d.ts.map +1 -0
- package/dist/tools/pushSessionBundle.js +282 -0
- package/dist/tools/pushSessionBundle.js.map +1 -0
- package/dist/tools/readGovernanceAudit.d.ts +17 -0
- package/dist/tools/readGovernanceAudit.d.ts.map +1 -0
- package/dist/tools/readGovernanceAudit.js +21 -0
- package/dist/tools/readGovernanceAudit.js.map +1 -0
- package/dist/tools/readPage.d.ts +13 -0
- package/dist/tools/readPage.d.ts.map +1 -0
- package/dist/tools/readPage.js +46 -0
- package/dist/tools/readPage.js.map +1 -0
- package/dist/tools/removeWebhook.d.ts +14 -0
- package/dist/tools/removeWebhook.d.ts.map +1 -0
- package/dist/tools/removeWebhook.js +12 -0
- package/dist/tools/removeWebhook.js.map +1 -0
- package/dist/tools/resolveProject.d.ts +58 -0
- package/dist/tools/resolveProject.d.ts.map +1 -0
- package/dist/tools/resolveProject.js +175 -0
- package/dist/tools/resolveProject.js.map +1 -0
- package/dist/tools/restorePage.d.ts +47 -0
- package/dist/tools/restorePage.d.ts.map +1 -0
- package/dist/tools/restorePage.js +111 -0
- package/dist/tools/restorePage.js.map +1 -0
- package/dist/tools/searchPages.d.ts +28 -0
- package/dist/tools/searchPages.d.ts.map +1 -0
- package/dist/tools/searchPages.js +372 -0
- package/dist/tools/searchPages.js.map +1 -0
- package/dist/tools/setWebhook.d.ts +30 -0
- package/dist/tools/setWebhook.d.ts.map +1 -0
- package/dist/tools/setWebhook.js +30 -0
- package/dist/tools/setWebhook.js.map +1 -0
- package/dist/tools/shareProject.d.ts +25 -0
- package/dist/tools/shareProject.d.ts.map +1 -0
- package/dist/tools/shareProject.js +56 -0
- package/dist/tools/shareProject.js.map +1 -0
- package/dist/tools/suggestLinks.d.ts +29 -0
- package/dist/tools/suggestLinks.d.ts.map +1 -0
- package/dist/tools/suggestLinks.js +214 -0
- package/dist/tools/suggestLinks.js.map +1 -0
- package/dist/tools/updatePage.d.ts +27 -0
- package/dist/tools/updatePage.d.ts.map +1 -0
- package/dist/tools/updatePage.js +118 -0
- package/dist/tools/updatePage.js.map +1 -0
- package/dist/webhooks/dispatch.d.ts +49 -0
- package/dist/webhooks/dispatch.d.ts.map +1 -0
- package/dist/webhooks/dispatch.js +159 -0
- package/dist/webhooks/dispatch.js.map +1 -0
- package/dist/webhooks/webhookRules.d.ts +34 -0
- package/dist/webhooks/webhookRules.d.ts.map +1 -0
- package/dist/webhooks/webhookRules.js +129 -0
- package/dist/webhooks/webhookRules.js.map +1 -0
- package/dist/wiki/frontmatter.d.ts +45 -0
- package/dist/wiki/frontmatter.d.ts.map +1 -0
- package/dist/wiki/frontmatter.js +68 -0
- package/dist/wiki/frontmatter.js.map +1 -0
- package/dist/wiki/index.d.ts +22 -0
- package/dist/wiki/index.d.ts.map +1 -0
- package/dist/wiki/index.js +203 -0
- package/dist/wiki/index.js.map +1 -0
- package/dist/wiki/ingestPolicy.d.ts +18 -0
- package/dist/wiki/ingestPolicy.d.ts.map +1 -0
- package/dist/wiki/ingestPolicy.js +64 -0
- package/dist/wiki/ingestPolicy.js.map +1 -0
- package/dist/wiki/layoutLint.d.ts +51 -0
- package/dist/wiki/layoutLint.d.ts.map +1 -0
- package/dist/wiki/layoutLint.js +146 -0
- package/dist/wiki/layoutLint.js.map +1 -0
- package/dist/wiki/links.d.ts +6 -0
- package/dist/wiki/links.d.ts.map +1 -0
- package/dist/wiki/links.js +45 -0
- package/dist/wiki/links.js.map +1 -0
- package/dist/wiki/log.d.ts +58 -0
- package/dist/wiki/log.d.ts.map +1 -0
- package/dist/wiki/log.js +126 -0
- package/dist/wiki/log.js.map +1 -0
- package/dist/wiki/path.d.ts +8 -0
- package/dist/wiki/path.d.ts.map +1 -0
- package/dist/wiki/path.js +25 -0
- package/dist/wiki/path.js.map +1 -0
- package/dist/wiki/storageLimits.d.ts +163 -0
- package/dist/wiki/storageLimits.d.ts.map +1 -0
- package/dist/wiki/storageLimits.js +281 -0
- package/dist/wiki/storageLimits.js.map +1 -0
- 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 @@
|
|
|
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"}
|
package/dist/wiki/log.js
ADDED
|
@@ -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"}
|