crbro-memory 2.9.1 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +256 -456
  3. package/SECURITY.md +135 -8
  4. package/bin/crbro.mjs +555 -299
  5. package/dist/build-id.json +10 -0
  6. package/dist/cli/common.js +211 -0
  7. package/dist/cli/export.js +522 -0
  8. package/dist/cli/import.js +683 -0
  9. package/dist/cli/markdown.js +313 -0
  10. package/dist/cli/mcpclient.js +107 -0
  11. package/dist/cli/serve.js +453 -0
  12. package/dist/cli/view.js +358 -0
  13. package/dist/cli/viewer-page.js +378 -0
  14. package/dist/daemon/daemon.js +119 -11
  15. package/dist/daemon/endpoint.js +222 -21
  16. package/dist/daemon/lines.js +102 -13
  17. package/dist/daemon/proxy.js +480 -125
  18. package/dist/daemon/surface.js +116 -0
  19. package/dist/daemon/trace.js +73 -0
  20. package/dist/engine/atrest.js +132 -0
  21. package/dist/engine/backup.js +143 -19
  22. package/dist/engine/brain.js +22 -6
  23. package/dist/engine/codexconfig.js +476 -0
  24. package/dist/engine/cortex.js +762 -311
  25. package/dist/engine/doctor.js +676 -0
  26. package/dist/engine/evidence.js +171 -0
  27. package/dist/engine/heat.js +81 -37
  28. package/dist/engine/hippocampus.js +50 -36
  29. package/dist/engine/hookrefresh.js +296 -0
  30. package/dist/engine/hookverify.js +15 -3
  31. package/dist/engine/installer.js +1022 -0
  32. package/dist/engine/installio.js +279 -0
  33. package/dist/engine/keychain.js +735 -144
  34. package/dist/engine/maintenance.js +355 -87
  35. package/dist/engine/modinstall.js +98 -45
  36. package/dist/engine/prefrontal.js +126 -69
  37. package/dist/engine/projects.js +80 -0
  38. package/dist/engine/quarantine.js +204 -0
  39. package/dist/engine/secrets.js +41 -5
  40. package/dist/engine/shelf.js +34 -11
  41. package/dist/engine/synapses.js +183 -80
  42. package/dist/engine/triggers.js +141 -11
  43. package/dist/index.js +37 -3
  44. package/dist/mcp-surface.json +1 -0
  45. package/dist/miner/extractor.js +64 -17
  46. package/dist/miner/scheduler.js +144 -68
  47. package/dist/search/index.js +810 -176
  48. package/dist/search/quality.js +31 -0
  49. package/dist/search/semantic.js +208 -18
  50. package/dist/search/tokenize.js +44 -0
  51. package/dist/server.js +649 -343
  52. package/dist/sync/git.js +203 -72
  53. package/dist/sync/materialize.js +33 -8
  54. package/dist/sync/space.js +286 -101
  55. package/dist/sync/validate.js +204 -0
  56. package/dist/utils/budget.js +15 -2
  57. package/dist/utils/fs.js +40 -6
  58. package/dist/utils/ids.js +128 -14
  59. package/dist/utils/lock.js +219 -52
  60. package/hooks/crbro-codex.mjs +284 -0
  61. package/hooks/crbro-guard.mjs +281 -173
  62. package/hooks/crbro-hooks.shipped.json +20 -0
  63. package/hooks/crbro-lifecycle.mjs +16 -4
  64. package/hooks/crbro-subagent.mjs +180 -180
  65. package/package.json +12 -8
  66. package/dist/daemon/daemon.d.ts +0 -25
  67. package/dist/daemon/daemon.d.ts.map +0 -1
  68. package/dist/daemon/daemon.js.map +0 -1
  69. package/dist/daemon/endpoint.d.ts +0 -66
  70. package/dist/daemon/endpoint.d.ts.map +0 -1
  71. package/dist/daemon/endpoint.js.map +0 -1
  72. package/dist/daemon/lines.d.ts +0 -55
  73. package/dist/daemon/lines.d.ts.map +0 -1
  74. package/dist/daemon/lines.js.map +0 -1
  75. package/dist/daemon/proxy.d.ts +0 -55
  76. package/dist/daemon/proxy.d.ts.map +0 -1
  77. package/dist/daemon/proxy.js.map +0 -1
  78. package/dist/engine/backup.d.ts +0 -68
  79. package/dist/engine/backup.d.ts.map +0 -1
  80. package/dist/engine/backup.js.map +0 -1
  81. package/dist/engine/brain.d.ts +0 -79
  82. package/dist/engine/brain.d.ts.map +0 -1
  83. package/dist/engine/brain.js.map +0 -1
  84. package/dist/engine/cortex.d.ts +0 -670
  85. package/dist/engine/cortex.d.ts.map +0 -1
  86. package/dist/engine/cortex.js.map +0 -1
  87. package/dist/engine/dates.d.ts +0 -28
  88. package/dist/engine/dates.d.ts.map +0 -1
  89. package/dist/engine/dates.js.map +0 -1
  90. package/dist/engine/heat.d.ts +0 -28
  91. package/dist/engine/heat.d.ts.map +0 -1
  92. package/dist/engine/heat.js.map +0 -1
  93. package/dist/engine/hippocampus.d.ts +0 -60
  94. package/dist/engine/hippocampus.d.ts.map +0 -1
  95. package/dist/engine/hippocampus.js.map +0 -1
  96. package/dist/engine/hookverify.d.ts +0 -42
  97. package/dist/engine/hookverify.d.ts.map +0 -1
  98. package/dist/engine/hookverify.js.map +0 -1
  99. package/dist/engine/keychain.d.ts +0 -32
  100. package/dist/engine/keychain.d.ts.map +0 -1
  101. package/dist/engine/keychain.js.map +0 -1
  102. package/dist/engine/maintenance.d.ts +0 -218
  103. package/dist/engine/maintenance.d.ts.map +0 -1
  104. package/dist/engine/maintenance.js.map +0 -1
  105. package/dist/engine/modinstall.d.ts +0 -215
  106. package/dist/engine/modinstall.d.ts.map +0 -1
  107. package/dist/engine/modinstall.js.map +0 -1
  108. package/dist/engine/postmortem.d.ts +0 -81
  109. package/dist/engine/postmortem.d.ts.map +0 -1
  110. package/dist/engine/postmortem.js.map +0 -1
  111. package/dist/engine/prefrontal.d.ts +0 -61
  112. package/dist/engine/prefrontal.d.ts.map +0 -1
  113. package/dist/engine/prefrontal.js.map +0 -1
  114. package/dist/engine/secrets.d.ts +0 -30
  115. package/dist/engine/secrets.d.ts.map +0 -1
  116. package/dist/engine/secrets.js.map +0 -1
  117. package/dist/engine/shelf.d.ts +0 -156
  118. package/dist/engine/shelf.d.ts.map +0 -1
  119. package/dist/engine/shelf.js.map +0 -1
  120. package/dist/engine/source.d.ts +0 -6
  121. package/dist/engine/source.d.ts.map +0 -1
  122. package/dist/engine/source.js.map +0 -1
  123. package/dist/engine/synapses.d.ts +0 -82
  124. package/dist/engine/synapses.d.ts.map +0 -1
  125. package/dist/engine/synapses.js.map +0 -1
  126. package/dist/engine/triggers.d.ts +0 -53
  127. package/dist/engine/triggers.d.ts.map +0 -1
  128. package/dist/engine/triggers.js.map +0 -1
  129. package/dist/engine/usage.d.ts +0 -61
  130. package/dist/engine/usage.d.ts.map +0 -1
  131. package/dist/engine/usage.js.map +0 -1
  132. package/dist/index.d.ts +0 -2
  133. package/dist/index.d.ts.map +0 -1
  134. package/dist/index.js.map +0 -1
  135. package/dist/miner/extractor.d.ts +0 -12
  136. package/dist/miner/extractor.d.ts.map +0 -1
  137. package/dist/miner/extractor.js.map +0 -1
  138. package/dist/miner/index.d.ts +0 -61
  139. package/dist/miner/index.d.ts.map +0 -1
  140. package/dist/miner/index.js.map +0 -1
  141. package/dist/miner/scheduler.d.ts +0 -30
  142. package/dist/miner/scheduler.d.ts.map +0 -1
  143. package/dist/miner/scheduler.js.map +0 -1
  144. package/dist/miner/types.d.ts +0 -11
  145. package/dist/miner/types.d.ts.map +0 -1
  146. package/dist/miner/types.js.map +0 -1
  147. package/dist/search/index.d.ts +0 -323
  148. package/dist/search/index.d.ts.map +0 -1
  149. package/dist/search/index.js.map +0 -1
  150. package/dist/search/semantic.d.ts +0 -80
  151. package/dist/search/semantic.d.ts.map +0 -1
  152. package/dist/search/semantic.js.map +0 -1
  153. package/dist/search/synonyms.d.ts +0 -2
  154. package/dist/search/synonyms.d.ts.map +0 -1
  155. package/dist/search/synonyms.js.map +0 -1
  156. package/dist/search/tokenize.d.ts +0 -39
  157. package/dist/search/tokenize.d.ts.map +0 -1
  158. package/dist/search/tokenize.js.map +0 -1
  159. package/dist/server.d.ts +0 -41
  160. package/dist/server.d.ts.map +0 -1
  161. package/dist/server.js.map +0 -1
  162. package/dist/sync/git.d.ts +0 -36
  163. package/dist/sync/git.d.ts.map +0 -1
  164. package/dist/sync/git.js.map +0 -1
  165. package/dist/sync/materialize.d.ts +0 -35
  166. package/dist/sync/materialize.d.ts.map +0 -1
  167. package/dist/sync/materialize.js.map +0 -1
  168. package/dist/sync/ops.d.ts +0 -142
  169. package/dist/sync/ops.d.ts.map +0 -1
  170. package/dist/sync/ops.js.map +0 -1
  171. package/dist/sync/space.d.ts +0 -116
  172. package/dist/sync/space.d.ts.map +0 -1
  173. package/dist/sync/space.js.map +0 -1
  174. package/dist/types/index.d.ts +0 -346
  175. package/dist/types/index.d.ts.map +0 -1
  176. package/dist/types/index.js.map +0 -1
  177. package/dist/utils/budget.d.ts +0 -44
  178. package/dist/utils/budget.d.ts.map +0 -1
  179. package/dist/utils/budget.js.map +0 -1
  180. package/dist/utils/fs.d.ts +0 -59
  181. package/dist/utils/fs.d.ts.map +0 -1
  182. package/dist/utils/fs.js.map +0 -1
  183. package/dist/utils/hash.d.ts +0 -20
  184. package/dist/utils/hash.d.ts.map +0 -1
  185. package/dist/utils/hash.js.map +0 -1
  186. package/dist/utils/ids.d.ts +0 -47
  187. package/dist/utils/ids.d.ts.map +0 -1
  188. package/dist/utils/ids.js.map +0 -1
  189. package/dist/utils/lock.d.ts +0 -11
  190. package/dist/utils/lock.d.ts.map +0 -1
  191. package/dist/utils/lock.js.map +0 -1
  192. package/dist/utils/transcripts.d.ts +0 -63
  193. package/dist/utils/transcripts.d.ts.map +0 -1
  194. package/dist/utils/transcripts.js.map +0 -1
  195. package/dist/version.d.ts +0 -26
  196. package/dist/version.d.ts.map +0 -1
  197. package/dist/version.js.map +0 -1
package/SECURITY.md CHANGED
@@ -16,7 +16,8 @@ like friction, and nobody trusts one for more than it does.
16
16
  | | Claude Code session logs, read by `crbro usage`, `crbro postmortem` and the lifecycle hook | `~/.claude/projects/` |
17
17
  | | Compaction checkpoints written by the lifecycle hook (requests, task list, folder, remote) | `<brain>/checkpoints/`, seven days |
18
18
  | **Code CRBRO runs in Claude Code without a command** | The open-items mod, installed and updated by `crbro_boot` | `~/.claude/crbro-mods/`, one entry in `env.CLAUDE_CODE_PLUGIN_DIRS` of `~/.claude/settings.json` |
19
- | **Communication out** | Team-space sync | A git remote you configure |
19
+ | **Communication out** | MCP tool results, including explicit secret reads | Your AI client and, depending on it, its model provider |
20
+ | | Team-space sync | A git remote you configure |
20
21
  | | The optional semantic model | Downloaded once from Hugging Face by `semantic install` |
21
22
 
22
23
  There is no account, no server of ours and no telemetry. Nothing is sent to
@@ -41,10 +42,20 @@ applies the same filter to everything it prints. Text is always redacted whole
41
42
  and cut to length afterwards: a cut first could leave half a token the filter
42
43
  no longer recognises.
43
44
 
45
+ Since 2.11 it also covers the decision rationale and the fact aliases
46
+ (`keywords`) of `crbro_learn`, and the notes of `crbro_revise`; `crbro_audit`
47
+ counts a credential found in any of them with its entry. A private key is
48
+ redacted whole — through its `END` line, or every base64 line after the
49
+ header when there is none — not just its header. Labels written without a
50
+ separator (`PASS value`, `la contraseña value (…`) are caught when the value
51
+ mixes character classes; plain prose after the same words is not.
52
+
44
53
  It is tuned for precision, not recall: it matches shapes that are credentials
45
54
  and essentially nothing else, because a false positive silently corrupts real
46
55
  knowledge. The consequence is that a credential in a shape it does not know
47
- passes. Treat it as a net, not as a guarantee.
56
+ passes. Treat it as a net, not as a guarantee. `npm run bench` measures it:
57
+ every synthetic value must be gone from the redacted text (not merely the text
58
+ changed), and the release gate allows no false positive on its innocent set.
48
59
 
49
60
  Sharing is stricter than storing: a neuron that contains a credential is
50
61
  **refused** for sharing, not redacted, because sending a teammate a mangled
@@ -65,13 +76,89 @@ it, and it goes back into a config file in plain text. `crbro_secret` and
65
76
  - **Linux** — the Secret Service, through `secret-tool(1)`.
66
77
 
67
78
  CRBRO invents no cryptography. The store lives outside the brain on purpose,
68
- so no sync, share or backup can reach it even by mistake. The value is read
69
- from stdin by the CLI, never from arguments, so it stays out of shell history
70
- and the process table.
79
+ so normal memory sync, share and backups do not include that store. Keep custom
80
+ `CRBRO_KEYS_DIR` locations outside the brain, repositories and synced folders.
81
+ Never add the encrypted store to Git or a release package. The CLI reads the
82
+ value from stdin, never from its arguments, so it stays out of shell history.
83
+ How it then reaches the store depends on the platform: on Windows and Linux it
84
+ goes to PowerShell and `secret-tool` through stdin; on **macOS** `security(1)`
85
+ has no stdin form, so the value (base64-encoded, which is not encryption) is
86
+ on that command's argument list for the few milliseconds the call lasts, where
87
+ another process of the same machine could read it with `ps`.
71
88
 
72
89
  What it does not do: protect against code running as your own user. DPAPI,
73
90
  the login keychain and the Secret Service all open for the logged-in account.
74
91
 
92
+ `crbro_secret action=get` deliberately returns the value to the MCP client.
93
+ Local encryption does not stop that client from putting it into model context
94
+ or conversation logs. Prefer a credential-aware CLI that reads it internally
95
+ and uses it only to authenticate to the intended service; never print it for
96
+ convenience. There is no promise that a secret used for authentication never
97
+ leaves the computer.
98
+
99
+ **The environment is not a keychain (2.11).** Until 2.10 `get` returned any
100
+ variable of the server's environment with the requested name — `USERNAME`,
101
+ `ANTHROPIC_API_KEY`, whatever the client was started with — before looking at
102
+ the store. Now an environment variable counts only when it opts in: a variable
103
+ named `CRBRO_SECRET_<NAME>`, or a plain `<NAME>` that `CRBRO_SECRET_ENV`
104
+ (a comma-separated list) names. That keeps CI and one-off overrides working.
105
+ The answer says `source: env` or `source: keychain`. Every action checks the
106
+ name (`SCREAMING_SNAKE_CASE`).
107
+
108
+ **Destructive writes ask first (2.11).** Through MCP, `set` on a name that
109
+ already holds a different value and `remove` change nothing on the first call:
110
+ they return a `confirm_token` to show the user, and only a second call with
111
+ that token writes. The token is an HMAC under a key that never leaves the
112
+ server process, bound to the name, the state of the store and (for `set`) the
113
+ new value, so a stale token, another value or a restarted server is refused.
114
+ The CLI asks for `--yes` on `remove` as before; a person typing at a terminal
115
+ is the confirmation for `set`.
116
+
117
+ **A missing store is not an empty one (2.11).** On Windows a plain marker
118
+ (`keys.dpapi.marker`, dates only) is written next to the store on every write,
119
+ and every successful write keeps a byte copy of the sealed file in
120
+ `sealed-copies/` (the last `CRBRO_KEYS_COPIES`, default 5; copies are never
121
+ opened except to validate a restore, and they open for the same Windows
122
+ account only). If `keys.dpapi` is missing while the marker, a sealed copy or a
123
+ leftover `*.tmp` says it existed, every operation fails with recovery steps
124
+ instead of answering "no credentials", and `set` refuses to create a new store
125
+ over it unless `--force` is given. `npx crbro-memory secret copies` lists the
126
+ copies and leftovers; `npx crbro-memory secret restore <id> --yes` validates
127
+ one with DPAPI, keeps the current store as a copy and puts it back. A removal
128
+ or an overwrite can therefore be undone until five more writes rotate the copy
129
+ out. Stale temporary files of this release are swept after an hour; the fixed
130
+ `keys.dpapi.tmp` of releases up to 2.9 is never touched, because it may be the
131
+ only copy left. macOS and Linux keep no copies: their stores are the
132
+ operating system's.
133
+
134
+ On macOS and Linux the names live in an index item of their own. Only "not
135
+ found" reads as an empty index; a locked keychain, a denied prompt or an
136
+ unreadable index is an error, and nothing is written. On Linux the index
137
+ replaced `secret-tool search --all`, which printed every value just to list
138
+ names (a store from 2.10 is migrated once, keeping names and descriptions).
139
+
140
+ On Windows, read-modify-write operations are serialized across processes and
141
+ ciphertext changes are checked before serving cached values; the decrypted
142
+ cache is dropped by a timer after 30 seconds. PowerShell is started from its
143
+ absolute System32 path, not looked up from the client's folder. The server
144
+ runs every keychain operation asynchronously, so a slow PowerShell start no
145
+ longer stalls the other conversations of a shared daemon; the store probe is
146
+ remembered for five minutes. Lock timeouts fail closed and say which process
147
+ holds the lock and for how long. Async lock holders refresh a heartbeat, so a
148
+ lock whose owner died is reclaimed even when Windows has reused its PID. A
149
+ crashed short-lived `.lock.guard` is not stolen while waiting; maintenance
150
+ removes one only when it is more than ten minutes old. Stop all writers before
151
+ deleting locks by hand.
152
+
153
+ ### Project and evidence boundaries (2.10)
154
+
155
+ Project scope is a retrieval/task-organization filter, not authorization or
156
+ tenant isolation. A caller with this brain's MCP access can request `scope=all`.
157
+ References name a file, URL or person but are never fetched or executed by the
158
+ memory server. `verification.method=source` records the caller's claim to have
159
+ checked that reference, not an independently audited or signed result. A strong
160
+ search match, a repeated statement, and a true statement are different things.
161
+
75
162
  ### Quarantine before forgetting
76
163
 
77
164
  `crbro_forget` (and every merge, split and compaction that removes a neuron)
@@ -117,9 +204,10 @@ What it does not prove:
117
204
  teammate's or the miner's line cannot be rewritten as your own lesson
118
205
  through `promotion_candidates`.
119
206
 
120
- Provenance labels; it does not sanitise. Content arriving through a shared
121
- space is not filtered for secrets or for instructions on the way in. Treat any
122
- line with an `origin` as data, never as an order.
207
+ Provenance labels; it does not sanitise instructions. Since 2.11 content
208
+ arriving through a shared space passes through the secret filter and size and
209
+ shape checks on the way in (see below), but nothing filters what it tells the
210
+ model to do. Treat any line with an `origin` as data, never as an order.
123
211
 
124
212
  ### Team sync over git
125
213
 
@@ -132,6 +220,45 @@ a changed one afterwards. Incoming logs written by a newer, unknown format are
132
220
  skipped rather than half-understood, and an unreadable line is skipped without
133
221
  costing the rest.
134
222
 
223
+ What 2.11 checks, because each of these was accepted as it came:
224
+
225
+ - **Space names** are slugs (letters, digits, `-`, `_`; at most 64) and must
226
+ resolve to a folder directly inside `shared/`. A name like `..` used to make
227
+ `create` run `git add -A` and push the whole brain to the given remote.
228
+ - **Remotes** that start with `-`, contain control characters or carry
229
+ credentials (`https://user:password@…`, `https://token@…`) are refused before
230
+ git runs, so a credential never lands in `.git/config`; use SSH or a
231
+ credential helper. Branches follow git's ref-name rules, including the one
232
+ read from a cloned `space.json`. Positional arguments follow `--`, and git's
233
+ error output passes through the secret filter before it is shown.
234
+ - **No adoption.** A sync merges a folder of the space only into a neuron you
235
+ already share in that space, or into one this machine does not have. A
236
+ folder with the id of a private neuron of yours is left alone and reported
237
+ (`left_alone`): a teammate can no longer turn your neuron into a shared one,
238
+ which would have sent everything you learn about it to the team. Share it
239
+ with `crbro_share` to merge it.
240
+ - **Incoming notes** must name the neuron of their folder (a note that names
241
+ another one is dropped, so it cannot steer where a neuron is written); the
242
+ author label is cut to the charset and length of a local identity; ids,
243
+ kinds and confidences are checked; text is capped and redacted; logs over
244
+ 32 MB are skipped.
245
+ - **Sharing checks every field that travels**: rationales, aliases, revision
246
+ notes, the neuron's name, summary and tags, not only the entry texts. A
247
+ credential in any of them blocks the share.
248
+ - On Windows git is started from its absolute path on `PATH`, never looked up
249
+ in the space folder, whose content comes from teammates. The server runs git
250
+ asynchronously, so a slow push no longer stalls the shared daemon.
251
+
252
+ ### The miner's schedule
253
+
254
+ `setup-miner` and `remove-miner` run every command as an argument array,
255
+ never through a shell. The crontab is read with `crontab -l`, your own lines
256
+ are kept byte for byte, the previous crontab is saved next to the brain as
257
+ `crontab.before-crbro-*.txt`, and the new one is written through stdin.
258
+ Until 2.10 it went through `echo "…" | crontab -`, which expanded `$(…)` and
259
+ backticks in your existing cron lines. On Windows the task runs the absolute
260
+ `npx.cmd`, and the registered command is checked after creation.
261
+
135
262
  ### Hooks never block
136
263
 
137
264
  The guard (`PreToolUse`), the subagent hook (`SubagentStart`) and the