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.
- package/LICENSE +21 -21
- package/README.md +256 -456
- package/SECURITY.md +135 -8
- package/bin/crbro.mjs +555 -299
- package/dist/build-id.json +10 -0
- package/dist/cli/common.js +211 -0
- package/dist/cli/export.js +522 -0
- package/dist/cli/import.js +683 -0
- package/dist/cli/markdown.js +313 -0
- package/dist/cli/mcpclient.js +107 -0
- package/dist/cli/serve.js +453 -0
- package/dist/cli/view.js +358 -0
- package/dist/cli/viewer-page.js +378 -0
- package/dist/daemon/daemon.js +119 -11
- package/dist/daemon/endpoint.js +222 -21
- package/dist/daemon/lines.js +102 -13
- package/dist/daemon/proxy.js +480 -125
- package/dist/daemon/surface.js +116 -0
- package/dist/daemon/trace.js +73 -0
- package/dist/engine/atrest.js +132 -0
- package/dist/engine/backup.js +143 -19
- package/dist/engine/brain.js +22 -6
- package/dist/engine/codexconfig.js +476 -0
- package/dist/engine/cortex.js +762 -311
- package/dist/engine/doctor.js +676 -0
- package/dist/engine/evidence.js +171 -0
- package/dist/engine/heat.js +81 -37
- package/dist/engine/hippocampus.js +50 -36
- package/dist/engine/hookrefresh.js +296 -0
- package/dist/engine/hookverify.js +15 -3
- package/dist/engine/installer.js +1022 -0
- package/dist/engine/installio.js +279 -0
- package/dist/engine/keychain.js +735 -144
- package/dist/engine/maintenance.js +355 -87
- package/dist/engine/modinstall.js +98 -45
- package/dist/engine/prefrontal.js +126 -69
- package/dist/engine/projects.js +80 -0
- package/dist/engine/quarantine.js +204 -0
- package/dist/engine/secrets.js +41 -5
- package/dist/engine/shelf.js +34 -11
- package/dist/engine/synapses.js +183 -80
- package/dist/engine/triggers.js +141 -11
- package/dist/index.js +37 -3
- package/dist/mcp-surface.json +1 -0
- package/dist/miner/extractor.js +64 -17
- package/dist/miner/scheduler.js +144 -68
- package/dist/search/index.js +810 -176
- package/dist/search/quality.js +31 -0
- package/dist/search/semantic.js +208 -18
- package/dist/search/tokenize.js +44 -0
- package/dist/server.js +649 -343
- package/dist/sync/git.js +203 -72
- package/dist/sync/materialize.js +33 -8
- package/dist/sync/space.js +286 -101
- package/dist/sync/validate.js +204 -0
- package/dist/utils/budget.js +15 -2
- package/dist/utils/fs.js +40 -6
- package/dist/utils/ids.js +128 -14
- package/dist/utils/lock.js +219 -52
- package/hooks/crbro-codex.mjs +284 -0
- package/hooks/crbro-guard.mjs +281 -173
- package/hooks/crbro-hooks.shipped.json +20 -0
- package/hooks/crbro-lifecycle.mjs +16 -4
- package/hooks/crbro-subagent.mjs +180 -180
- package/package.json +12 -8
- package/dist/daemon/daemon.d.ts +0 -25
- package/dist/daemon/daemon.d.ts.map +0 -1
- package/dist/daemon/daemon.js.map +0 -1
- package/dist/daemon/endpoint.d.ts +0 -66
- package/dist/daemon/endpoint.d.ts.map +0 -1
- package/dist/daemon/endpoint.js.map +0 -1
- package/dist/daemon/lines.d.ts +0 -55
- package/dist/daemon/lines.d.ts.map +0 -1
- package/dist/daemon/lines.js.map +0 -1
- package/dist/daemon/proxy.d.ts +0 -55
- package/dist/daemon/proxy.d.ts.map +0 -1
- package/dist/daemon/proxy.js.map +0 -1
- package/dist/engine/backup.d.ts +0 -68
- package/dist/engine/backup.d.ts.map +0 -1
- package/dist/engine/backup.js.map +0 -1
- package/dist/engine/brain.d.ts +0 -79
- package/dist/engine/brain.d.ts.map +0 -1
- package/dist/engine/brain.js.map +0 -1
- package/dist/engine/cortex.d.ts +0 -670
- package/dist/engine/cortex.d.ts.map +0 -1
- package/dist/engine/cortex.js.map +0 -1
- package/dist/engine/dates.d.ts +0 -28
- package/dist/engine/dates.d.ts.map +0 -1
- package/dist/engine/dates.js.map +0 -1
- package/dist/engine/heat.d.ts +0 -28
- package/dist/engine/heat.d.ts.map +0 -1
- package/dist/engine/heat.js.map +0 -1
- package/dist/engine/hippocampus.d.ts +0 -60
- package/dist/engine/hippocampus.d.ts.map +0 -1
- package/dist/engine/hippocampus.js.map +0 -1
- package/dist/engine/hookverify.d.ts +0 -42
- package/dist/engine/hookverify.d.ts.map +0 -1
- package/dist/engine/hookverify.js.map +0 -1
- package/dist/engine/keychain.d.ts +0 -32
- package/dist/engine/keychain.d.ts.map +0 -1
- package/dist/engine/keychain.js.map +0 -1
- package/dist/engine/maintenance.d.ts +0 -218
- package/dist/engine/maintenance.d.ts.map +0 -1
- package/dist/engine/maintenance.js.map +0 -1
- package/dist/engine/modinstall.d.ts +0 -215
- package/dist/engine/modinstall.d.ts.map +0 -1
- package/dist/engine/modinstall.js.map +0 -1
- package/dist/engine/postmortem.d.ts +0 -81
- package/dist/engine/postmortem.d.ts.map +0 -1
- package/dist/engine/postmortem.js.map +0 -1
- package/dist/engine/prefrontal.d.ts +0 -61
- package/dist/engine/prefrontal.d.ts.map +0 -1
- package/dist/engine/prefrontal.js.map +0 -1
- package/dist/engine/secrets.d.ts +0 -30
- package/dist/engine/secrets.d.ts.map +0 -1
- package/dist/engine/secrets.js.map +0 -1
- package/dist/engine/shelf.d.ts +0 -156
- package/dist/engine/shelf.d.ts.map +0 -1
- package/dist/engine/shelf.js.map +0 -1
- package/dist/engine/source.d.ts +0 -6
- package/dist/engine/source.d.ts.map +0 -1
- package/dist/engine/source.js.map +0 -1
- package/dist/engine/synapses.d.ts +0 -82
- package/dist/engine/synapses.d.ts.map +0 -1
- package/dist/engine/synapses.js.map +0 -1
- package/dist/engine/triggers.d.ts +0 -53
- package/dist/engine/triggers.d.ts.map +0 -1
- package/dist/engine/triggers.js.map +0 -1
- package/dist/engine/usage.d.ts +0 -61
- package/dist/engine/usage.d.ts.map +0 -1
- package/dist/engine/usage.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/miner/extractor.d.ts +0 -12
- package/dist/miner/extractor.d.ts.map +0 -1
- package/dist/miner/extractor.js.map +0 -1
- package/dist/miner/index.d.ts +0 -61
- package/dist/miner/index.d.ts.map +0 -1
- package/dist/miner/index.js.map +0 -1
- package/dist/miner/scheduler.d.ts +0 -30
- package/dist/miner/scheduler.d.ts.map +0 -1
- package/dist/miner/scheduler.js.map +0 -1
- package/dist/miner/types.d.ts +0 -11
- package/dist/miner/types.d.ts.map +0 -1
- package/dist/miner/types.js.map +0 -1
- package/dist/search/index.d.ts +0 -323
- package/dist/search/index.d.ts.map +0 -1
- package/dist/search/index.js.map +0 -1
- package/dist/search/semantic.d.ts +0 -80
- package/dist/search/semantic.d.ts.map +0 -1
- package/dist/search/semantic.js.map +0 -1
- package/dist/search/synonyms.d.ts +0 -2
- package/dist/search/synonyms.d.ts.map +0 -1
- package/dist/search/synonyms.js.map +0 -1
- package/dist/search/tokenize.d.ts +0 -39
- package/dist/search/tokenize.d.ts.map +0 -1
- package/dist/search/tokenize.js.map +0 -1
- package/dist/server.d.ts +0 -41
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/sync/git.d.ts +0 -36
- package/dist/sync/git.d.ts.map +0 -1
- package/dist/sync/git.js.map +0 -1
- package/dist/sync/materialize.d.ts +0 -35
- package/dist/sync/materialize.d.ts.map +0 -1
- package/dist/sync/materialize.js.map +0 -1
- package/dist/sync/ops.d.ts +0 -142
- package/dist/sync/ops.d.ts.map +0 -1
- package/dist/sync/ops.js.map +0 -1
- package/dist/sync/space.d.ts +0 -116
- package/dist/sync/space.d.ts.map +0 -1
- package/dist/sync/space.js.map +0 -1
- package/dist/types/index.d.ts +0 -346
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js.map +0 -1
- package/dist/utils/budget.d.ts +0 -44
- package/dist/utils/budget.d.ts.map +0 -1
- package/dist/utils/budget.js.map +0 -1
- package/dist/utils/fs.d.ts +0 -59
- package/dist/utils/fs.d.ts.map +0 -1
- package/dist/utils/fs.js.map +0 -1
- package/dist/utils/hash.d.ts +0 -20
- package/dist/utils/hash.d.ts.map +0 -1
- package/dist/utils/hash.js.map +0 -1
- package/dist/utils/ids.d.ts +0 -47
- package/dist/utils/ids.d.ts.map +0 -1
- package/dist/utils/ids.js.map +0 -1
- package/dist/utils/lock.d.ts +0 -11
- package/dist/utils/lock.d.ts.map +0 -1
- package/dist/utils/lock.js.map +0 -1
- package/dist/utils/transcripts.d.ts +0 -63
- package/dist/utils/transcripts.d.ts.map +0 -1
- package/dist/utils/transcripts.js.map +0 -1
- package/dist/version.d.ts +0 -26
- package/dist/version.d.ts.map +0 -1
- 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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
53
|
-
|
|
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:
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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 (!
|
|
98
|
-
|
|
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 (!
|
|
101
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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 =
|
|
222
|
+
const legacy = yield { args: ['init'], cwd: dir };
|
|
117
223
|
if (!legacy.ok)
|
|
118
224
|
return legacy;
|
|
119
|
-
|
|
225
|
+
yield { args: ['checkout', '-b', branch], cwd: dir };
|
|
120
226
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
129
|
-
|
|
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
|
|
240
|
+
return yield { args: ['push', '-u', 'origin', `HEAD:${branch}`], cwd: dir, timeoutMs: 30_000 };
|
|
134
241
|
}
|
|
135
|
-
/**
|
|
136
|
-
function
|
|
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 =
|
|
252
|
+
const r = yield { args: ['clone', '--branch', branch, '--', remote, name], cwd: parent, timeoutMs: 60_000 };
|
|
140
253
|
if (r.ok)
|
|
141
|
-
|
|
254
|
+
yield* hardenGen(dir);
|
|
142
255
|
return r;
|
|
143
256
|
}
|
|
144
|
-
/**
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
268
|
+
yield { args: ['commit', '-q', '-m', `crbro ${author} ${new Date().toISOString()}`], cwd: dir };
|
|
154
269
|
}
|
|
155
|
-
const fetched =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 (!
|
|
283
|
+
if (!(yield { args: ['fetch', 'origin', '--', branch], cwd: dir, timeoutMs }).ok)
|
|
169
284
|
break;
|
|
170
|
-
if (!
|
|
171
|
-
|
|
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 =
|
|
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
|
package/dist/sync/materialize.js
CHANGED
|
@@ -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:
|
|
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
|
|
237
|
-
if (
|
|
238
|
-
|
|
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
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|