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/dist/sync/git.js CHANGED
@@ -11,31 +11,48 @@
11
11
  // forever. And it must never rewrite what it stores: git on Windows converts
12
12
  // line endings by default, which turns one appended line into two different
13
13
  // lines on two machines, and the logs here are append-only.
14
+ //
15
+ // 2.11: a third. Inside the shared daemon a synchronous git call blocks every
16
+ // client for as long as a push takes (up to 30 s). Each multi-step operation is
17
+ // written once as a generator of git calls and driven either synchronously or
18
+ // asynchronously; the server path uses the async drivers.
14
19
  var __importDefault = (this && this.__importDefault) || function (mod) {
15
20
  return (mod && mod.__esModule) ? mod : { "default": mod };
16
21
  };
17
22
  Object.defineProperty(exports, "__esModule", { value: true });
18
23
  exports.GITIGNORE = exports.GITATTRIBUTES = void 0;
24
+ exports.resolveGit = resolveGit;
19
25
  exports.git = git;
26
+ exports.gitAsync = gitAsync;
20
27
  exports.gitAvailable = gitAvailable;
28
+ exports.gitAvailableAsync = gitAvailableAsync;
21
29
  exports.hardenRepo = hardenRepo;
22
30
  exports.initSpace = initSpace;
31
+ exports.initSpaceAsync = initSpaceAsync;
23
32
  exports.cloneSpace = cloneSpace;
33
+ exports.cloneSpaceAsync = cloneSpaceAsync;
24
34
  exports.syncSpace = syncSpace;
35
+ exports.syncSpaceAsync = syncSpaceAsync;
36
+ exports._resetGitMemoForTests = _resetGitMemoForTests;
25
37
  const node_child_process_1 = require("node:child_process");
26
38
  const node_fs_1 = require("node:fs");
27
39
  const node_path_1 = __importDefault(require("node:path"));
28
40
  /**
29
41
  * Everything that could make git stop and ask a person something, turned off.
30
42
  * Without this the server hangs on the first private repository it meets.
43
+ * A GIT_SSH_COMMAND the user set is kept: replacing it silently overrode their
44
+ * own ssh setup (G16).
31
45
  */
32
- const SILENT_ENV = {
33
- GIT_TERMINAL_PROMPT: '0',
34
- GIT_ASKPASS: '',
35
- SSH_ASKPASS: '',
36
- GIT_SSH_COMMAND: 'ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new',
37
- GCM_INTERACTIVE: 'never',
38
- };
46
+ function silentEnv() {
47
+ return {
48
+ ...process.env,
49
+ GIT_TERMINAL_PROMPT: '0',
50
+ GIT_ASKPASS: '',
51
+ SSH_ASKPASS: '',
52
+ GIT_SSH_COMMAND: process.env['GIT_SSH_COMMAND'] || 'ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new',
53
+ GCM_INTERACTIVE: 'never',
54
+ };
55
+ }
39
56
  function classify(stderr) {
40
57
  const s = stderr.toLowerCase();
41
58
  if (s.includes('could not resolve host') || s.includes('unable to access') ||
@@ -48,38 +65,125 @@ function classify(stderr) {
48
65
  return 'conflict';
49
66
  return undefined;
50
67
  }
68
+ // ─── Which git ───────────────────────────────────────────────────
69
+ //
70
+ // On Windows a bare "git" is looked up in the child's working directory before
71
+ // PATH. Every call here runs with cwd = a space directory, whose contents come
72
+ // from teammates: a git.exe committed at its root would run as the user (G10).
73
+ // So the executable is resolved once, from absolute PATH entries only.
74
+ let resolved = null;
75
+ function resolveGit() {
76
+ if (process.platform !== 'win32')
77
+ return 'git';
78
+ if (resolved && (0, node_fs_1.existsSync)(resolved))
79
+ return resolved;
80
+ resolved = null;
81
+ for (const entry of (process.env['PATH'] || process.env['Path'] || '').split(node_path_1.default.delimiter)) {
82
+ const d = entry.trim().replace(/^"|"$/g, '');
83
+ if (!d || !node_path_1.default.isAbsolute(d))
84
+ continue;
85
+ const exe = node_path_1.default.join(d, 'git.exe');
86
+ try {
87
+ if ((0, node_fs_1.statSync)(exe).isFile()) {
88
+ resolved = exe;
89
+ return exe;
90
+ }
91
+ }
92
+ catch { /* not here */ }
93
+ }
94
+ // Not on PATH: the bare name fails with ENOENT from a neutral directory
95
+ // (see git()), which reports no_git rather than running a planted binary.
96
+ return 'git';
97
+ }
98
+ function neutralCwd(cwd) {
99
+ // With an absolute executable the space directory is a safe cwd. Without one
100
+ // (git not found), run from the system directory and pass -C instead.
101
+ if (process.platform === 'win32' && resolveGit() === 'git') {
102
+ const root = process.env['SystemRoot'] || 'C:\\Windows';
103
+ return { cwd: root, args: ['-C', cwd] };
104
+ }
105
+ return { cwd, args: [] };
106
+ }
107
+ function toResult(status, signal, stdout, stderr, error) {
108
+ if (error) {
109
+ const code = error.code;
110
+ return { ok: false, stdout: '', stderr: String(error.message || ''), reason: code === 'ENOENT' ? 'no_git' : 'timeout' };
111
+ }
112
+ if (signal)
113
+ return { ok: false, stdout, stderr, reason: 'timeout' };
114
+ return { ok: status === 0, stdout, stderr, reason: status === 0 ? undefined : classify(stderr) };
115
+ }
51
116
  function git(args, cwd, timeoutMs = 20_000) {
52
- const r = (0, node_child_process_1.spawnSync)('git', args, {
53
- cwd,
117
+ const where = neutralCwd(cwd);
118
+ const r = (0, node_child_process_1.spawnSync)(resolveGit(), [...where.args, ...args], {
119
+ cwd: where.cwd,
54
120
  encoding: 'utf-8',
55
121
  timeout: timeoutMs,
56
122
  windowsHide: true,
57
- env: { ...process.env, ...SILENT_ENV },
123
+ env: silentEnv(),
124
+ });
125
+ return toResult(r.status, r.signal, r.stdout || '', r.stderr || '', r.error);
126
+ }
127
+ /** The same call without blocking the event loop. */
128
+ function gitAsync(args, cwd, timeoutMs = 20_000) {
129
+ return new Promise(resolve => {
130
+ const where = neutralCwd(cwd);
131
+ let stdout = '';
132
+ let stderr = '';
133
+ let settled = false;
134
+ let timedOut = false;
135
+ const done = (r) => { if (!settled) {
136
+ settled = true;
137
+ clearTimeout(timer);
138
+ resolve(r);
139
+ } };
140
+ let child;
141
+ try {
142
+ child = (0, node_child_process_1.spawn)(resolveGit(), [...where.args, ...args], {
143
+ cwd: where.cwd, windowsHide: true, env: silentEnv(), stdio: ['ignore', 'pipe', 'pipe'],
144
+ });
145
+ }
146
+ catch (err) {
147
+ done(toResult(null, null, '', '', err));
148
+ return;
149
+ }
150
+ const timer = setTimeout(() => { timedOut = true; child.kill(); }, timeoutMs);
151
+ child.stdout.setEncoding('utf8');
152
+ child.stderr.setEncoding('utf8');
153
+ child.stdout.on('data', d => { stdout += d; });
154
+ child.stderr.on('data', d => { stderr += d; });
155
+ child.on('error', err => done(toResult(null, null, stdout, stderr, err)));
156
+ child.on('close', (code, signal) => done(timedOut
157
+ ? { ok: false, stdout, stderr, reason: 'timeout' }
158
+ : toResult(code, signal, stdout, stderr)));
58
159
  });
59
- if (r.error) {
60
- const code = r.error.code;
61
- return {
62
- ok: false,
63
- stdout: '',
64
- stderr: String(r.error.message || ''),
65
- reason: code === 'ENOENT' ? 'no_git' : 'timeout',
66
- };
67
- }
68
- if (r.signal) {
69
- return { ok: false, stdout: r.stdout || '', stderr: r.stderr || '', reason: 'timeout' };
70
- }
71
- const stderr = r.stderr || '';
72
- return {
73
- ok: r.status === 0,
74
- stdout: r.stdout || '',
75
- stderr,
76
- reason: r.status === 0 ? undefined : classify(stderr),
77
- };
160
+ }
161
+ function driveSync(g) {
162
+ let step = g.next();
163
+ while (!step.done)
164
+ step = g.next(git(step.value.args, step.value.cwd, step.value.timeoutMs));
165
+ return step.value;
166
+ }
167
+ async function driveAsync(g) {
168
+ let step = g.next();
169
+ while (!step.done)
170
+ step = g.next(await gitAsync(step.value.args, step.value.cwd, step.value.timeoutMs));
171
+ return step.value;
78
172
  }
79
173
  /** Is git installed at all? */
80
174
  function gitAvailable() {
81
175
  return git(['--version'], process.cwd(), 5_000).ok;
82
176
  }
177
+ // Whether git is installed does not change between two syncs; asking costs a
178
+ // process start. Kept for a minute, failures included.
179
+ let availability = null;
180
+ async function gitAvailableAsync() {
181
+ if (availability && Date.now() - availability.at < 60_000)
182
+ return availability.ok;
183
+ const ok = (await gitAsync(['--version'], process.cwd(), 5_000)).ok;
184
+ availability = { ok, at: Date.now() };
185
+ return ok;
186
+ }
83
187
  /**
84
188
  * Settings that have to be on the repository itself, not on the machine.
85
189
  *
@@ -89,18 +193,21 @@ function gitAvailable() {
89
193
  * becomes two lines, and the memory quietly duplicates itself. `* -text` in
90
194
  * .gitattributes plus this setting stops it at both ends.
91
195
  */
92
- function hardenRepo(dir) {
93
- git(['config', 'core.autocrlf', 'false'], dir);
94
- git(['config', 'core.safecrlf', 'false'], dir);
95
- git(['config', 'merge.ours.driver', 'true'], dir);
196
+ function* hardenGen(dir) {
197
+ yield { args: ['config', 'core.autocrlf', 'false'], cwd: dir };
198
+ yield { args: ['config', 'core.safecrlf', 'false'], cwd: dir };
199
+ yield { args: ['config', 'merge.ours.driver', 'true'], cwd: dir };
96
200
  // Identity, so committing works on a machine with no global git config.
97
- if (!git(['config', 'user.email'], dir).stdout.trim()) {
98
- git(['config', 'user.email', 'crbro@localhost'], dir);
201
+ if (!(yield { args: ['config', 'user.email'], cwd: dir }).stdout.trim()) {
202
+ yield { args: ['config', 'user.email', 'crbro@localhost'], cwd: dir };
99
203
  }
100
- if (!git(['config', 'user.name'], dir).stdout.trim()) {
101
- git(['config', 'user.name', 'CRBRO'], dir);
204
+ if (!(yield { args: ['config', 'user.name'], cwd: dir }).stdout.trim()) {
205
+ yield { args: ['config', 'user.name', 'CRBRO'], cwd: dir };
102
206
  }
103
207
  }
208
+ function hardenRepo(dir) {
209
+ driveSync(hardenGen(dir));
210
+ }
104
211
  exports.GITATTRIBUTES = '# CRBRO shared memory. Do not edit.\n' +
105
212
  '# -text keeps git from rewriting line endings: these logs are append-only\n' +
106
213
  '# and a rewritten ending turns one line into two on the next merge.\n' +
@@ -108,71 +215,95 @@ exports.GITATTRIBUTES = '# CRBRO shared memory. Do not edit.\n' +
108
215
  '*.jsonl merge=union\n';
109
216
  exports.GITIGNORE = '# Local bookkeeping. Never shared.\n' +
110
217
  '.local/\n';
111
- /** Prepare a brand-new space so the first person to clone finds real history. */
112
- async function initSpace(dir, remote, branch = 'main') {
113
- await node_fs_1.promises.mkdir(dir, { recursive: true });
114
- const init = git(['init', '-b', branch], dir);
218
+ function* initGen(dir, remote, branch) {
219
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true });
220
+ const init = yield { args: ['init', '-b', branch], cwd: dir };
115
221
  if (!init.ok && !init.stdout.includes('Reinitialized')) {
116
- const legacy = git(['init'], dir);
222
+ const legacy = yield { args: ['init'], cwd: dir };
117
223
  if (!legacy.ok)
118
224
  return legacy;
119
- git(['checkout', '-b', branch], dir);
225
+ yield { args: ['checkout', '-b', branch], cwd: dir };
120
226
  }
121
- hardenRepo(dir);
122
- await node_fs_1.promises.writeFile(node_path_1.default.join(dir, '.gitattributes'), exports.GITATTRIBUTES, 'utf-8');
123
- await node_fs_1.promises.writeFile(node_path_1.default.join(dir, '.gitignore'), exports.GITIGNORE, 'utf-8');
124
- git(['remote', 'remove', 'origin'], dir);
125
- const add = git(['remote', 'add', 'origin', remote], dir);
227
+ yield* hardenGen(dir);
228
+ (0, node_fs_1.writeFileSync)(node_path_1.default.join(dir, '.gitattributes'), exports.GITATTRIBUTES, 'utf-8');
229
+ (0, node_fs_1.writeFileSync)(node_path_1.default.join(dir, '.gitignore'), exports.GITIGNORE, 'utf-8');
230
+ yield { args: ['remote', 'remove', 'origin'], cwd: dir };
231
+ // "--": a remote that starts with "-" is a URL, never an option.
232
+ const add = yield { args: ['remote', 'add', '--', 'origin', remote], cwd: dir };
126
233
  if (!add.ok)
127
234
  return add;
128
- git(['add', '-A', '.'], dir);
129
- git(['commit', '-q', '-m', 'CRBRO shared space'], dir);
235
+ yield { args: ['add', '-A', '.'], cwd: dir };
236
+ yield { args: ['commit', '-q', '-m', 'CRBRO shared space'], cwd: dir };
130
237
  // Push the first commit before anyone clones. Skipping this is how two
131
238
  // people end up with unrelated histories and a merge that refuses to run —
132
239
  // each of them keeping their own half of the memory without noticing.
133
- return git(['push', '-u', 'origin', `HEAD:${branch}`], dir, 30_000);
240
+ return yield { args: ['push', '-u', 'origin', `HEAD:${branch}`], cwd: dir, timeoutMs: 30_000 };
134
241
  }
135
- /** Clone an existing space. */
136
- function cloneSpace(dir, remote, branch = 'main') {
242
+ /** Prepare a brand-new space so the first person to clone finds real history. */
243
+ async function initSpace(dir, remote, branch = 'main') {
244
+ return driveSync(initGen(dir, remote, branch));
245
+ }
246
+ function initSpaceAsync(dir, remote, branch = 'main') {
247
+ return driveAsync(initGen(dir, remote, branch));
248
+ }
249
+ function* cloneGen(dir, remote, branch) {
137
250
  const parent = node_path_1.default.dirname(dir);
138
251
  const name = node_path_1.default.basename(dir);
139
- const r = git(['clone', '--branch', branch, remote, name], parent, 60_000);
252
+ const r = yield { args: ['clone', '--branch', branch, '--', remote, name], cwd: parent, timeoutMs: 60_000 };
140
253
  if (r.ok)
141
- hardenRepo(dir);
254
+ yield* hardenGen(dir);
142
255
  return r;
143
256
  }
144
- /**
145
- * Bring in everyone else's notes and send ours.
146
- * Offline is a normal outcome, not an error: the work stays local and goes out
147
- * next time. Nothing here can lose a note, because nobody edits anyone's file.
148
- */
149
- function syncSpace(dir, author, branch = 'main', timeoutMs = 30_000) {
150
- git(['add', '-A', '.'], dir);
151
- const hayCambios = !git(['diff', '--cached', '--quiet'], dir).ok;
257
+ /** Clone an existing space. */
258
+ function cloneSpace(dir, remote, branch = 'main') {
259
+ return driveSync(cloneGen(dir, remote, branch));
260
+ }
261
+ function cloneSpaceAsync(dir, remote, branch = 'main') {
262
+ return driveAsync(cloneGen(dir, remote, branch));
263
+ }
264
+ function* syncGen(dir, author, branch, timeoutMs) {
265
+ yield { args: ['add', '-A', '.'], cwd: dir };
266
+ const hayCambios = !(yield { args: ['diff', '--cached', '--quiet'], cwd: dir }).ok;
152
267
  if (hayCambios) {
153
- git(['commit', '-q', '-m', `crbro ${author} ${new Date().toISOString()}`], dir);
268
+ yield { args: ['commit', '-q', '-m', `crbro ${author} ${new Date().toISOString()}`], cwd: dir };
154
269
  }
155
- const fetched = git(['fetch', 'origin', branch], dir, timeoutMs);
270
+ const fetched = yield { args: ['fetch', 'origin', '--', branch], cwd: dir, timeoutMs };
156
271
  if (!fetched.ok)
157
272
  return { pulled: fetched, pushed: null };
158
- const merged = git(['merge', '--no-edit', '--allow-unrelated-histories', `origin/${branch}`], dir, timeoutMs);
273
+ const merged = yield { args: ['merge', '--no-edit', '--allow-unrelated-histories', `origin/${branch}`], cwd: dir, timeoutMs };
159
274
  if (!merged.ok) {
160
275
  // Union merging makes this rare, but a hand-edited file could still clash.
161
276
  // Keeping both sides and stopping beats guessing.
162
- git(['merge', '--abort'], dir);
277
+ yield { args: ['merge', '--abort'], cwd: dir };
163
278
  return { pulled: { ...merged, reason: 'conflict' }, pushed: null };
164
279
  }
165
280
  // Push, and if someone landed first, take their work and try once more.
166
- let pushed = git(['push', 'origin', `HEAD:${branch}`], dir, timeoutMs);
281
+ let pushed = yield { args: ['push', 'origin', `HEAD:${branch}`], cwd: dir, timeoutMs };
167
282
  for (let intento = 0; intento < 2 && !pushed.ok && pushed.reason !== 'auth'; intento++) {
168
- if (!git(['fetch', 'origin', branch], dir, timeoutMs).ok)
283
+ if (!(yield { args: ['fetch', 'origin', '--', branch], cwd: dir, timeoutMs }).ok)
169
284
  break;
170
- if (!git(['merge', '--no-edit', `origin/${branch}`], dir, timeoutMs).ok) {
171
- git(['merge', '--abort'], dir);
285
+ if (!(yield { args: ['merge', '--no-edit', `origin/${branch}`], cwd: dir, timeoutMs }).ok) {
286
+ yield { args: ['merge', '--abort'], cwd: dir };
172
287
  break;
173
288
  }
174
- pushed = git(['push', 'origin', `HEAD:${branch}`], dir, timeoutMs);
289
+ pushed = yield { args: ['push', 'origin', `HEAD:${branch}`], cwd: dir, timeoutMs };
175
290
  }
176
291
  return { pulled: merged, pushed };
177
292
  }
293
+ /**
294
+ * Bring in everyone else's notes and send ours.
295
+ * Offline is a normal outcome, not an error: the work stays local and goes out
296
+ * next time. Nothing here can lose a note, because nobody edits anyone's file.
297
+ */
298
+ function syncSpace(dir, author, branch = 'main', timeoutMs = 30_000) {
299
+ return driveSync(syncGen(dir, author, branch, timeoutMs));
300
+ }
301
+ function syncSpaceAsync(dir, author, branch = 'main', timeoutMs = 30_000) {
302
+ return driveAsync(syncGen(dir, author, branch, timeoutMs));
303
+ }
304
+ /** Tests: forget the resolved executable and the availability memo. */
305
+ function _resetGitMemoForTests() {
306
+ resolved = null;
307
+ availability = null;
308
+ }
178
309
  //# sourceMappingURL=git.js.map
@@ -18,6 +18,7 @@ const fs_js_1 = require("../utils/fs.js");
18
18
  const ops_js_1 = require("./ops.js");
19
19
  const hash_js_1 = require("../utils/hash.js");
20
20
  const shelf_js_1 = require("../engine/shelf.js");
21
+ const evidence_js_1 = require("../engine/evidence.js");
21
22
  /**
22
23
  * How far along a fact's life it is. Merging takes the maximum, so a fact
23
24
  * someone retracted can never come back to life because a stale log still
@@ -50,6 +51,7 @@ function applyOps(base, ops, fallback) {
50
51
  tags_added: 0,
51
52
  verifications_updated: 0,
52
53
  shelf_updated: 0,
54
+ metadata_updated: 0,
53
55
  authors: [],
54
56
  divergence: [],
55
57
  };
@@ -60,7 +62,7 @@ function applyOps(base, ops, fallback) {
60
62
  // reported rather than resolved. Overwriting someone's local name because a
61
63
  // teammate spelled it differently is not a merge, it is a stomp.
62
64
  const neuron = base
63
- ? { ...base, facts: [...base.facts], decisions: [...base.decisions], patterns: [...base.patterns], preferences: [...base.preferences], tags: [...base.tags], connections: [...base.connections], errors: [...(base.errors || [])], debts: [...(base.debts || [])], entry_dates: { ...(base.entry_dates || {}) }, entry_status: { ...(base.entry_status || {}) }, ...(base.entry_source ? { entry_source: { ...base.entry_source } } : {}), ...(base.entry_verified ? { entry_verified: { ...base.entry_verified } } : {}), map: base.map ? { ...base.map } : undefined }
65
+ ? { ...base, facts: base.facts.map(f => structuredClone(f)), decisions: [...base.decisions], patterns: [...base.patterns], preferences: [...base.preferences], tags: [...base.tags], connections: [...base.connections], errors: [...(base.errors || [])], debts: [...(base.debts || [])], entry_dates: { ...(base.entry_dates || {}) }, entry_status: { ...(base.entry_status || {}) }, ...(base.entry_source ? { entry_source: { ...base.entry_source } } : {}), ...(base.entry_verified ? { entry_verified: { ...base.entry_verified } } : {}), ...(base.entry_verification ? { entry_verification: structuredClone(base.entry_verification) } : {}), map: base.map ? { ...base.map } : undefined }
64
66
  : {
65
67
  id,
66
68
  name: neuronOp && neuronOp.op === 'neuron' ? neuronOp.name : id,
@@ -157,6 +159,10 @@ function applyOps(base, ops, fallback) {
157
159
  const fo = op;
158
160
  const existing = byFid.get(fo.fid) || byText.get((0, ops_js_1.normalizeText)(fo.text));
159
161
  if (existing) {
162
+ const before = JSON.stringify([existing.reference, existing.temporal, existing.verification_invalidated_at]);
163
+ (0, evidence_js_1.mergeFactEvidence)(existing, { reference: fo.reference, temporal: fo.temporal, verification_invalidated_at: fo.verification_invalidated_at });
164
+ if (JSON.stringify([existing.reference, existing.temporal, existing.verification_invalidated_at]) !== before)
165
+ report.metadata_updated++;
160
166
  // Same fact from two people. Keep the earliest date so provenance
161
167
  // reflects who knew it first, and the higher confidence.
162
168
  existing.id = existing.id || fo.fid;
@@ -190,6 +196,7 @@ function applyOps(base, ops, fallback) {
190
196
  fact.keys = fo.keys.slice(0, 8);
191
197
  if ((0, shelf_js_1.isShelfLife)(fo.shelf))
192
198
  fact.shelf_life = fo.shelf;
199
+ (0, evidence_js_1.mergeFactEvidence)(fact, { reference: fo.reference, temporal: fo.temporal, verification_invalidated_at: fo.verification_invalidated_at });
193
200
  neuron.facts.push(fact);
194
201
  byFid.set(fo.fid, fact);
195
202
  alias(fact);
@@ -233,9 +240,16 @@ function applyOps(base, ops, fallback) {
233
240
  if (!target)
234
241
  continue;
235
242
  const at = typeof vo.at === 'string' ? vo.at : undefined;
236
- const nuevo = (0, shelf_js_1.latestOf)(target.verified, at);
237
- if (nuevo && nuevo !== target.verified) {
238
- target.verified = nuevo;
243
+ const verification = (0, evidence_js_1.readVerification)(vo.verification);
244
+ if (vo.verification !== undefined && (!verification || verification.at !== at))
245
+ continue;
246
+ const nuevo = (0, evidence_js_1.mergeCheck)(target, { verified: at, verification });
247
+ if (nuevo.verified && (nuevo.verified !== target.verified || JSON.stringify(nuevo.verification) !== JSON.stringify(target.verification))) {
248
+ target.verified = nuevo.verified;
249
+ if (nuevo.verification)
250
+ target.verification = nuevo.verification;
251
+ else
252
+ delete target.verification;
239
253
  report.verifications_updated++;
240
254
  }
241
255
  }
@@ -348,10 +362,17 @@ function applyOps(base, ops, fallback) {
348
362
  if (vo.ekind !== 'entry' || typeof vo.eid !== 'string' || !vo.eid)
349
363
  continue;
350
364
  const at = typeof vo.at === 'string' ? vo.at : undefined;
351
- const previo = neuron.entry_verified?.[vo.eid];
352
- const nuevo = (0, shelf_js_1.latestOf)(previo, at);
353
- if (nuevo && nuevo !== previo) {
354
- (neuron.entry_verified ||= {})[vo.eid] = nuevo;
365
+ const verification = (0, evidence_js_1.readVerification)(vo.verification);
366
+ if (vo.verification !== undefined && (!verification || verification.at !== at))
367
+ continue;
368
+ const previo = { verified: neuron.entry_verified?.[vo.eid], verification: neuron.entry_verification?.[vo.eid] };
369
+ const nuevo = (0, evidence_js_1.mergeCheck)(previo, { verified: at, verification });
370
+ if (nuevo.verified && (nuevo.verified !== previo.verified || JSON.stringify(nuevo.verification) !== JSON.stringify(previo.verification))) {
371
+ (neuron.entry_verified ||= {})[vo.eid] = nuevo.verified;
372
+ if (nuevo.verification)
373
+ (neuron.entry_verification ||= {})[vo.eid] = nuevo.verification;
374
+ else if (neuron.entry_verification)
375
+ delete neuron.entry_verification[vo.eid];
355
376
  report.verifications_updated++;
356
377
  }
357
378
  }
@@ -483,6 +504,10 @@ function applyOps(base, ops, fallback) {
483
504
  delete neuron.entry_verified;
484
505
  }
485
506
  report.authors = [...new Set(ops.map(o => o.by).filter(Boolean))].sort();
507
+ (0, evidence_js_1.mergeEntryEvidence)(neuron, neuron, new Set([
508
+ ...neuron.decisions.map(d => d.text), ...neuron.patterns, ...neuron.preferences,
509
+ ...(neuron.errors || []), ...(neuron.debts || []),
510
+ ].map(ops_js_1.entryId)));
486
511
  return { neuron, report };
487
512
  }
488
513
  //# sourceMappingURL=materialize.js.map