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
@@ -0,0 +1,296 @@
1
+ "use strict";
2
+ // ─── Installed hooks stay current on their own ───────────────────
3
+ //
4
+ // install-hooks and install-boot copy hooks/*.mjs out of the package into a
5
+ // stable folder — <Claude config>/crbro-hooks, <CODEX_HOME>/crbro-hooks —
6
+ // because the npx cache moves with every release. Until 2.11 nothing ever
7
+ // updated those copies: an update of CRBRO left the old guard (silent once the
8
+ // trigger index format moved) and the old lifecycle hook (an older copy of the
9
+ // secret filter) running for good.
10
+ //
11
+ // Each of those folders now keeps a manifest of what CRBRO put there: the
12
+ // version and the LF SHA-256 of every file. When the running package is a
13
+ // different build, a copy that is still byte for byte what CRBRO installed —
14
+ // by the manifest, or by the list of every hook CRBRO ever shipped for copies
15
+ // installed before manifests existed — is replaced by the package's. A copy
16
+ // that matches neither was edited by someone: it is left alone and reported,
17
+ // by doctor always and by the boot once (hooks_notice).
18
+ //
19
+ // Runs from crbro_boot (beside the mod's automatic install) and from every
20
+ // install command. Never creates a folder, never adds a hook to a client's
21
+ // settings: it only keeps current what is already there.
22
+ var __importDefault = (this && this.__importDefault) || function (mod) {
23
+ return (mod && mod.__esModule) ? mod : { "default": mod };
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.SHIPPED_HASHES = exports.HOOK_MANIFEST = void 0;
27
+ exports.compareVersions = compareVersions;
28
+ exports.hookDirs = hookDirs;
29
+ exports.packageHooksDir = packageHooksDir;
30
+ exports.readManifest = readManifest;
31
+ exports.shippedHashes = shippedHashes;
32
+ exports.writtenBy = writtenBy;
33
+ exports.installHookFile = installHookFile;
34
+ exports.refreshHooks = refreshHooks;
35
+ exports.hooksNotice = hooksNotice;
36
+ const fs_1 = require("fs");
37
+ const path_1 = __importDefault(require("path"));
38
+ const installio_js_1 = require("./installio.js");
39
+ exports.HOOK_MANIFEST = 'crbro-hooks.manifest.json';
40
+ exports.SHIPPED_HASHES = 'crbro-hooks.shipped.json';
41
+ /**
42
+ * -1, 0 or 1 on x.y.z; a prerelease is below its release; anything that is
43
+ * not x.y.z compares equal, which never counts as newer (same as modinstall).
44
+ */
45
+ function compareVersions(a, b) {
46
+ const parse = (v) => /^(\d+)\.(\d+)\.(\d+)(-)?/.exec(v);
47
+ const x = parse(a);
48
+ const y = parse(b);
49
+ if (!x || !y)
50
+ return 0;
51
+ for (let i = 1; i <= 3; i++)
52
+ if (Number(x[i]) !== Number(y[i]))
53
+ return Number(x[i]) < Number(y[i]) ? -1 : 1;
54
+ if (!!x[4] !== !!y[4])
55
+ return x[4] ? -1 : 1;
56
+ return 0;
57
+ }
58
+ function hookDirs(home, env = process.env) {
59
+ return [
60
+ { client: 'claude-code', dir: path_1.default.join((0, installio_js_1.claudeDir)(home, env), 'crbro-hooks') },
61
+ { client: 'codex', dir: path_1.default.join((0, installio_js_1.codexHome)(home, env), 'crbro-hooks') },
62
+ ];
63
+ }
64
+ /** The hooks/ folder of the package this code runs from. */
65
+ function packageHooksDir() {
66
+ return path_1.default.join(__dirname, '..', '..', 'hooks');
67
+ }
68
+ function readManifest(dir) {
69
+ try {
70
+ const m = JSON.parse((0, fs_1.readFileSync)(path_1.default.join(dir, exports.HOOK_MANIFEST), 'utf8'));
71
+ if (m && typeof m === 'object' && m.files && typeof m.files === 'object') {
72
+ const files = {};
73
+ for (const [k, v] of Object.entries(m.files)) {
74
+ if (v && typeof v.sha256 === 'string')
75
+ files[k] = { sha256: v.sha256, version: String(v.version ?? '') };
76
+ }
77
+ return { version: String(m.version ?? ''), files, ...(Array.isArray(m.noticed) ? { noticed: m.noticed } : {}) };
78
+ }
79
+ }
80
+ catch { /* none yet */ }
81
+ return null;
82
+ }
83
+ function writeManifest(dir, m) {
84
+ const file = path_1.default.join(dir, exports.HOOK_MANIFEST);
85
+ const tmp = `${file}.${process.pid}.tmp`;
86
+ (0, fs_1.writeFileSync)(tmp, JSON.stringify(m, null, 2) + '\n', 'utf8');
87
+ (0, fs_1.renameSync)(tmp, file);
88
+ }
89
+ /** Every LF hash CRBRO shipped for a hook before manifests existed, with the last version that shipped it. */
90
+ function shippedHashes(pkgHooks) {
91
+ try {
92
+ const raw = JSON.parse((0, fs_1.readFileSync)(path_1.default.join(pkgHooks, exports.SHIPPED_HASHES), 'utf8'));
93
+ const out = {};
94
+ for (const [k, v] of Object.entries(raw)) {
95
+ if (!v || typeof v !== 'object' || Array.isArray(v))
96
+ continue;
97
+ out[k] = Object.fromEntries(Object.entries(v).filter(([, x]) => typeof x === 'string'));
98
+ }
99
+ return out;
100
+ }
101
+ catch {
102
+ return {};
103
+ }
104
+ }
105
+ /**
106
+ * Which CRBRO version wrote this copy, when it is one CRBRO wrote unmodified:
107
+ * by the folder's manifest, or by the list of hooks shipped before manifests.
108
+ * Null when it matches neither: somebody edited it.
109
+ */
110
+ function writtenBy(name, sha, manifest, shipped) {
111
+ const m = manifest?.files[name];
112
+ if (m && m.sha256 === sha)
113
+ return m.version || '0.0.0';
114
+ return shipped[name]?.[sha] ?? null;
115
+ }
116
+ function pkgVersion(pkgHooks) {
117
+ try {
118
+ return String(JSON.parse((0, fs_1.readFileSync)(path_1.default.join(pkgHooks, '..', 'package.json'), 'utf8')).version || 'unknown');
119
+ }
120
+ catch {
121
+ return 'unknown';
122
+ }
123
+ }
124
+ function mjsIn(dir) {
125
+ try {
126
+ return (0, fs_1.readdirSync)(dir).filter(f => f.endsWith('.mjs') && (0, fs_1.statSync)(path_1.default.join(dir, f)).isFile()).sort();
127
+ }
128
+ catch {
129
+ return [];
130
+ }
131
+ }
132
+ /** Copies one packaged hook into a folder (tmp + rename) and records it in that folder's manifest. */
133
+ function installHookFile(pkgHooks, name, targetDir) {
134
+ (0, fs_1.mkdirSync)(targetDir, { recursive: true });
135
+ const src = (0, fs_1.readFileSync)(path_1.default.join(pkgHooks, name));
136
+ const target = path_1.default.join(targetDir, name);
137
+ const version = pkgVersion(pkgHooks);
138
+ const before = readManifest(targetDir);
139
+ // A copy somebody edited is kept aside before it is replaced.
140
+ try {
141
+ const mine = (0, installio_js_1.lfSha)((0, fs_1.readFileSync)(target));
142
+ if (mine !== (0, installio_js_1.lfSha)(src) && writtenBy(name, mine, before, shippedHashes(pkgHooks)) === null)
143
+ (0, installio_js_1.backupFile)(target);
144
+ }
145
+ catch { /* not there yet */ }
146
+ let same = false;
147
+ try {
148
+ same = (0, fs_1.readFileSync)(target).equals(src);
149
+ }
150
+ catch { /* not there */ }
151
+ const entry = before?.files[name];
152
+ if (same && entry?.sha256 === (0, installio_js_1.lfSha)(src) && entry.version === version)
153
+ return target; // idempotent
154
+ if (!same) {
155
+ const tmp = `${target}.${process.pid}.tmp`;
156
+ try {
157
+ (0, fs_1.writeFileSync)(tmp, src);
158
+ (0, fs_1.renameSync)(tmp, target);
159
+ }
160
+ catch (e) {
161
+ (0, fs_1.rmSync)(tmp, { force: true });
162
+ throw e;
163
+ }
164
+ }
165
+ const m = before ?? { version, files: {} };
166
+ m.version = version;
167
+ m.files[name] = { sha256: (0, installio_js_1.lfSha)(src), version };
168
+ writeManifest(targetDir, m);
169
+ return target;
170
+ }
171
+ /**
172
+ * Checks every installed CRBRO hook against the package; refreshes the
173
+ * unmodified ones unless dryRun. Never throws: a copy it cannot write is
174
+ * reported with its error and tried again next time.
175
+ */
176
+ function refreshHooks(opts) {
177
+ const pkgHooks = opts.pkgHooks ?? packageHooksDir();
178
+ const shipped = shippedHashes(pkgHooks);
179
+ const version = pkgVersion(pkgHooks);
180
+ const out = [];
181
+ for (const { client, dir } of hookDirs(opts.home, opts.env ?? process.env)) {
182
+ if (!(0, fs_1.existsSync)(dir))
183
+ continue;
184
+ const manifest = readManifest(dir);
185
+ let manifestChanged = false;
186
+ const next = manifest ? { ...manifest, files: { ...manifest.files } } : { version, files: {} };
187
+ for (const name of mjsIn(dir)) {
188
+ let pkg;
189
+ try {
190
+ pkg = (0, fs_1.readFileSync)(path_1.default.join(pkgHooks, name));
191
+ }
192
+ catch {
193
+ continue;
194
+ } // not a hook of this package
195
+ let mine;
196
+ try {
197
+ mine = (0, fs_1.readFileSync)(path_1.default.join(dir, name));
198
+ }
199
+ catch {
200
+ continue;
201
+ }
202
+ const pkgSha = (0, installio_js_1.lfSha)(pkg);
203
+ const mineSha = (0, installio_js_1.lfSha)(mine);
204
+ if (mineSha === pkgSha) {
205
+ out.push({ client, dir, name, state: 'current' });
206
+ if (next.files[name]?.sha256 !== pkgSha) {
207
+ next.files[name] = { sha256: pkgSha, version };
208
+ manifestChanged = true;
209
+ }
210
+ continue;
211
+ }
212
+ const by = writtenBy(name, mineSha, manifest, shipped);
213
+ if (by === null) {
214
+ out.push({ client, dir, name, state: 'modified' });
215
+ continue;
216
+ }
217
+ // Only a newer release replaces it.
218
+ if (compareVersions(version, by) <= 0) {
219
+ out.push({ client, dir, name, state: 'other_build' });
220
+ continue;
221
+ }
222
+ const row = { client, dir, name, state: 'stale' };
223
+ if (!opts.dryRun) {
224
+ const target = path_1.default.join(dir, name);
225
+ const tmp = `${target}.${process.pid}.tmp`;
226
+ try {
227
+ // Checked once more right before: an edit made since the read is not overwritten.
228
+ if ((0, installio_js_1.lfSha)((0, fs_1.readFileSync)(target)) !== mineSha)
229
+ throw new Error('changed while being refreshed');
230
+ (0, fs_1.writeFileSync)(tmp, pkg);
231
+ (0, fs_1.renameSync)(tmp, target);
232
+ row.refreshed = true;
233
+ next.files[name] = { sha256: pkgSha, version };
234
+ manifestChanged = true;
235
+ }
236
+ catch (e) {
237
+ (0, fs_1.rmSync)(tmp, { force: true });
238
+ row.error = e.message;
239
+ }
240
+ }
241
+ out.push(row);
242
+ }
243
+ if (!opts.dryRun && manifestChanged) {
244
+ try {
245
+ next.version = version;
246
+ writeManifest(dir, next);
247
+ }
248
+ catch { /* the next run writes it */ }
249
+ }
250
+ }
251
+ return out;
252
+ }
253
+ /**
254
+ * The sentence for the boot, once per edited copy: the copies CRBRO did not
255
+ * refresh because someone changed them, and refreshes that failed. Null when
256
+ * there is nothing new to say. Marks what it said in the manifests.
257
+ */
258
+ function hooksNotice(copies) {
259
+ const edited = copies.filter(c => c.state === 'modified');
260
+ const failed = copies.filter(c => c.error);
261
+ const fresh = [];
262
+ for (const c of edited) {
263
+ let sha = '';
264
+ try {
265
+ sha = (0, installio_js_1.lfSha)((0, fs_1.readFileSync)(path_1.default.join(c.dir, c.name)));
266
+ }
267
+ catch {
268
+ continue;
269
+ }
270
+ const m = readManifest(c.dir);
271
+ const key = `${c.name}:${sha}`;
272
+ if (m?.noticed?.includes(key))
273
+ continue;
274
+ fresh.push(c);
275
+ try {
276
+ const next = m ?? { version: '', files: {} };
277
+ next.noticed = [...(next.noticed ?? []), key].slice(-50);
278
+ writeManifest(c.dir, next);
279
+ }
280
+ catch { /* said again next time */ }
281
+ }
282
+ if (!fresh.length && !failed.length)
283
+ return null;
284
+ const parts = [];
285
+ if (fresh.length) {
286
+ parts.push(`CRBRO did not update ${fresh.length} installed hook file(s) because they differ from what CRBRO installed (edited by hand?): ` +
287
+ fresh.map(c => path_1.default.join(c.dir, c.name)).join(', ') + '.');
288
+ parts.push('They keep running as they are. To take this version\'s copy, run the install command again (npx crbro-memory install-hooks …, or install), which replaces them after a backup; npx crbro-memory doctor lists them.');
289
+ }
290
+ if (failed.length) {
291
+ parts.push(`CRBRO could not refresh ${failed.map(c => path_1.default.join(c.dir, c.name)).join(', ')} (${failed[0].error}); it tries again at the next start.`);
292
+ }
293
+ parts.push('Tell the user this once, briefly, in their language.');
294
+ return parts.join(' ');
295
+ }
296
+ //# sourceMappingURL=hookrefresh.js.map
@@ -24,6 +24,7 @@ const crypto_1 = require("crypto");
24
24
  const fs_1 = require("fs");
25
25
  const path_1 = __importDefault(require("path"));
26
26
  const os_1 = __importDefault(require("os"));
27
+ const hookrefresh_js_1 = require("./hookrefresh.js");
27
28
  function sha256(buf) {
28
29
  return (0, crypto_1.createHash)('sha256').update(buf).digest('hex');
29
30
  }
@@ -89,6 +90,13 @@ function verifyHooks(opts) {
89
90
  .flatMap(scriptsIn);
90
91
  const referenced = new Set(crbroScripts.map(p => path_1.default.basename(p)));
91
92
  const hooks = [];
93
+ const known = (0, hookrefresh_js_1.shippedHashes)(opts.packageDir);
94
+ const manifest = (0, hookrefresh_js_1.readManifest)(opts.installedDir);
95
+ let version = '';
96
+ try {
97
+ version = String(JSON.parse((0, fs_1.readFileSync)(path_1.default.join(opts.packageDir, '..', 'package.json'), 'utf8')).version || '');
98
+ }
99
+ catch { /* unknown */ }
92
100
  for (const name of shipped) {
93
101
  const pkg = (0, fs_1.readFileSync)(path_1.default.join(opts.packageDir, name));
94
102
  const check = { name, status: 'not_installed', package_sha256: sha256(pkg), in_settings: referenced.has(name) };
@@ -100,8 +108,10 @@ function verifyHooks(opts) {
100
108
  check.status = 'same';
101
109
  else if (lfSha(mine) === lfSha(pkg))
102
110
  check.status = 'line_endings_only';
103
- else
104
- check.status = 'different';
111
+ else {
112
+ const by = (0, hookrefresh_js_1.writtenBy)(name, lfSha(mine), manifest, known);
113
+ check.status = by === null ? 'different' : (0, hookrefresh_js_1.compareVersions)(version, by) > 0 ? 'older_release' : 'other_build';
114
+ }
105
115
  }
106
116
  hooks.push(check);
107
117
  }
@@ -113,7 +123,7 @@ function verifyHooks(opts) {
113
123
  }
114
124
  const missing = [...new Set(crbroScripts)].filter(p => !(0, fs_1.existsSync)(p));
115
125
  const ok = !settingsUnreadable && missing.length === 0 &&
116
- hooks.every(h => h.status === 'same' || h.status === 'line_endings_only' || h.status === 'not_installed');
126
+ hooks.every(h => h.status === 'same' || h.status === 'line_endings_only' || h.status === 'not_installed' || h.status === 'older_release' || h.status === 'other_build');
117
127
  return {
118
128
  package_dir: opts.packageDir,
119
129
  installed_dir: opts.installedDir,
@@ -127,6 +137,8 @@ function verifyHooks(opts) {
127
137
  const STATUS_TEXT = {
128
138
  same: '✅ same as the package',
129
139
  line_endings_only: '✅ same except line endings (CRLF/LF)',
140
+ older_release: '⚪ from an older CRBRO release, unmodified: refreshed at the next crbro_boot',
141
+ other_build: '⚪ unmodified, from another CRBRO build of this version or a newer one',
130
142
  different: '⚠️ DIFFERENT from the package',
131
143
  not_installed: '⚪ not installed',
132
144
  unknown: '⚠️ not a hook of this package',