crbro-memory 2.10.0 → 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 -465
- package/SECURITY.md +114 -12
- package/bin/crbro.mjs +539 -300
- 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 +139 -19
- package/dist/engine/brain.js +22 -6
- package/dist/engine/codexconfig.js +476 -0
- package/dist/engine/cortex.js +667 -232
- package/dist/engine/doctor.js +312 -14
- package/dist/engine/heat.js +81 -37
- package/dist/engine/hippocampus.js +18 -7
- 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 +721 -162
- package/dist/engine/maintenance.js +355 -87
- package/dist/engine/modinstall.js +98 -45
- package/dist/engine/prefrontal.js +29 -6
- package/dist/engine/quarantine.js +204 -0
- package/dist/engine/secrets.js +41 -5
- package/dist/engine/shelf.js +25 -3
- 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 +797 -176
- package/dist/search/semantic.js +208 -18
- package/dist/search/tokenize.js +44 -0
- package/dist/server.js +583 -373
- package/dist/sync/git.js +203 -72
- package/dist/sync/space.js +100 -25
- 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 +94 -7
- 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 +9 -5
- 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 -682
- 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/doctor.d.ts +0 -93
- package/dist/engine/doctor.d.ts.map +0 -1
- package/dist/engine/doctor.js.map +0 -1
- package/dist/engine/evidence.d.ts +0 -26
- package/dist/engine/evidence.d.ts.map +0 -1
- package/dist/engine/evidence.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 -64
- package/dist/engine/prefrontal.d.ts.map +0 -1
- package/dist/engine/prefrontal.js.map +0 -1
- package/dist/engine/projects.d.ts +0 -20
- package/dist/engine/projects.d.ts.map +0 -1
- package/dist/engine/projects.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 -155
- 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 -328
- package/dist/search/index.d.ts.map +0 -1
- package/dist/search/index.js.map +0 -1
- package/dist/search/quality.d.ts +0 -18
- package/dist/search/quality.d.ts.map +0 -1
- package/dist/search/quality.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 -36
- package/dist/sync/materialize.d.ts.map +0 -1
- package/dist/sync/materialize.js.map +0 -1
- package/dist/sync/ops.d.ts +0 -147
- package/dist/sync/ops.d.ts.map +0 -1
- package/dist/sync/ops.js.map +0 -1
- package/dist/sync/space.d.ts +0 -118
- package/dist/sync/space.d.ts.map +0 -1
- package/dist/sync/space.js.map +0 -1
- package/dist/types/index.d.ts +0 -369
- 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 -13
- 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/space.js
CHANGED
|
@@ -37,6 +37,7 @@ const secrets_js_1 = require("../engine/secrets.js");
|
|
|
37
37
|
const evidence_js_1 = require("../engine/evidence.js");
|
|
38
38
|
const ops_js_1 = require("./ops.js");
|
|
39
39
|
const git_js_1 = require("./git.js");
|
|
40
|
+
const validate_js_1 = require("./validate.js");
|
|
40
41
|
// Fuera de shared/: el bloqueo debe sobrevivir al traslado de un espacio.
|
|
41
42
|
// Orden: shared-state -> shared-outbox-state -> JSON. Nunca a la inversa.
|
|
42
43
|
// Git retiene shared-state, pero NUNCA shared-outbox-state. Los emisores de
|
|
@@ -97,16 +98,24 @@ async function getIdentity(brain, author) {
|
|
|
97
98
|
await (0, fs_js_1.writeJSON)(p, id);
|
|
98
99
|
return id;
|
|
99
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* The folder of a space. Throws for a name that is not a plain slug or that
|
|
103
|
+
* would resolve outside shared/: with "..", create used to run `git add -A`
|
|
104
|
+
* and push the whole brain to whatever remote it was given (G1).
|
|
105
|
+
*/
|
|
100
106
|
function spaceDir(brain, name) {
|
|
101
|
-
return
|
|
107
|
+
return (0, validate_js_1.safeSpaceDir)(brain.paths.shared, name);
|
|
102
108
|
}
|
|
109
|
+
/** The space's config, or null — also for a name that cannot be a space. */
|
|
103
110
|
async function readSpace(brain, name) {
|
|
111
|
+
if ((0, validate_js_1.spaceNameProblem)(name))
|
|
112
|
+
return null;
|
|
104
113
|
return (0, fs_js_1.readJSON)(node_path_1.default.join(spaceDir(brain, name), 'space.json'));
|
|
105
114
|
}
|
|
106
115
|
async function listSpaces(brain) {
|
|
107
116
|
try {
|
|
108
117
|
const entries = await node_fs_1.promises.readdir(brain.paths.shared, { withFileTypes: true });
|
|
109
|
-
return entries.filter(e => e.isDirectory()).map(e => e.name);
|
|
118
|
+
return entries.filter(e => e.isDirectory() && !(0, validate_js_1.spaceNameProblem)(e.name)).map(e => e.name);
|
|
110
119
|
}
|
|
111
120
|
catch {
|
|
112
121
|
return [];
|
|
@@ -116,8 +125,16 @@ async function listSpaces(brain) {
|
|
|
116
125
|
async function createSpace(brain, name, remote, author, branch = 'main') {
|
|
117
126
|
return withSharedState(brain, () => createSpaceUnlocked(brain, name, remote, author, branch));
|
|
118
127
|
}
|
|
128
|
+
/** One message for a name, remote or branch that cannot be used. */
|
|
129
|
+
function invalidInput(name, remote, branch) {
|
|
130
|
+
const problem = (0, validate_js_1.spaceNameProblem)(name) || (0, validate_js_1.remoteProblem)(remote) || (0, validate_js_1.branchProblem)(branch);
|
|
131
|
+
return problem ? { ok: false, message: `Nothing was created or cloned. ${problem}` } : null;
|
|
132
|
+
}
|
|
119
133
|
async function createSpaceUnlocked(brain, name, remote, author, branch) {
|
|
120
|
-
|
|
134
|
+
const invalido = invalidInput(name, remote, branch);
|
|
135
|
+
if (invalido)
|
|
136
|
+
return invalido;
|
|
137
|
+
if (!(await (0, git_js_1.gitAvailableAsync)())) {
|
|
121
138
|
return { ok: false, message: 'git is not installed, and it is what carries the shared memory. Install it and try again.' };
|
|
122
139
|
}
|
|
123
140
|
const dir = spaceDir(brain, name);
|
|
@@ -125,30 +142,33 @@ async function createSpaceUnlocked(brain, name, remote, author, branch) {
|
|
|
125
142
|
return { ok: false, message: `The space "${name}" already exists on this machine.` };
|
|
126
143
|
}
|
|
127
144
|
const id = await getIdentity(brain, author);
|
|
128
|
-
const pushed = await (0, git_js_1.
|
|
145
|
+
const pushed = await (0, git_js_1.initSpaceAsync)(dir, remote, branch);
|
|
129
146
|
const cfg = { v: 1, name, created_by: id.author, created: (0, fs_js_1.now)(), branch };
|
|
130
147
|
await (0, fs_js_1.writeJSON)(node_path_1.default.join(dir, 'space.json'), cfg);
|
|
131
148
|
await node_fs_1.promises.mkdir(node_path_1.default.join(dir, '.local'), { recursive: true });
|
|
132
|
-
(0, git_js_1.
|
|
133
|
-
(0, git_js_1.
|
|
134
|
-
const again = (0, git_js_1.
|
|
149
|
+
await (0, git_js_1.gitAsync)(['add', '-A', '.'], dir);
|
|
150
|
+
await (0, git_js_1.gitAsync)(['commit', '-q', '-m', `CRBRO space "${name}"`], dir);
|
|
151
|
+
const again = await (0, git_js_1.gitAsync)(['push', 'origin', `HEAD:${branch}`], dir, 30_000);
|
|
135
152
|
if (!pushed.ok && !again.ok) {
|
|
136
153
|
return {
|
|
137
154
|
ok: false,
|
|
138
155
|
message: 'The space was created locally but could not reach the remote. Check the URL and that you have push access.',
|
|
139
|
-
detail: (again.stderr || pushed.stderr).slice(0, 300),
|
|
156
|
+
detail: (0, secrets_js_1.redact)((again.stderr || pushed.stderr).slice(0, 300)).text,
|
|
140
157
|
};
|
|
141
158
|
}
|
|
142
159
|
return {
|
|
143
160
|
ok: true,
|
|
144
|
-
message: `Space "${name}" created and pushed. Anyone you invite to ${remote} can now join it
|
|
161
|
+
message: (0, secrets_js_1.redact)(`Space "${name}" created and pushed. Anyone you invite to ${remote} can now join it.`).text,
|
|
145
162
|
};
|
|
146
163
|
}
|
|
147
164
|
async function joinSpace(brain, name, remote, author, branch = 'main') {
|
|
148
165
|
return withSharedState(brain, () => joinSpaceUnlocked(brain, name, remote, author, branch));
|
|
149
166
|
}
|
|
150
167
|
async function joinSpaceUnlocked(brain, name, remote, author, branch) {
|
|
151
|
-
|
|
168
|
+
const invalido = invalidInput(name, remote, branch);
|
|
169
|
+
if (invalido)
|
|
170
|
+
return invalido;
|
|
171
|
+
if (!(await (0, git_js_1.gitAvailableAsync)())) {
|
|
152
172
|
return { ok: false, message: 'git is not installed, and it is what carries the shared memory. Install it and try again.' };
|
|
153
173
|
}
|
|
154
174
|
const dir = spaceDir(brain, name);
|
|
@@ -157,14 +177,14 @@ async function joinSpaceUnlocked(brain, name, remote, author, branch) {
|
|
|
157
177
|
}
|
|
158
178
|
await getIdentity(brain, author);
|
|
159
179
|
await node_fs_1.promises.mkdir(brain.paths.shared, { recursive: true });
|
|
160
|
-
const r = (0, git_js_1.
|
|
180
|
+
const r = await (0, git_js_1.cloneSpaceAsync)(dir, remote, branch);
|
|
161
181
|
if (!r.ok) {
|
|
162
182
|
return {
|
|
163
183
|
ok: false,
|
|
164
184
|
message: r.reason === 'auth'
|
|
165
185
|
? 'Cloned nothing: no access to that repository. Ask to be added as a collaborator.'
|
|
166
186
|
: 'Could not clone the space. Check the URL and your connection.',
|
|
167
|
-
detail: r.stderr.slice(0, 300),
|
|
187
|
+
detail: (0, secrets_js_1.redact)(r.stderr.slice(0, 300)).text,
|
|
168
188
|
};
|
|
169
189
|
}
|
|
170
190
|
await node_fs_1.promises.mkdir(node_path_1.default.join(dir, '.local'), { recursive: true });
|
|
@@ -175,7 +195,7 @@ function neuronOps(neuron, id) {
|
|
|
175
195
|
const at = (0, fs_js_1.now)();
|
|
176
196
|
const ops = [
|
|
177
197
|
{ v: ops_js_1.OPS_VERSION, op: 'neuron', nid: neuron.id, by: id.author, at,
|
|
178
|
-
name: neuron.name, ntype: neuron.type, domain: neuron.domain },
|
|
198
|
+
name: (0, secrets_js_1.redact)(neuron.name || '').text, ntype: neuron.type, domain: neuron.domain },
|
|
179
199
|
];
|
|
180
200
|
for (const f of neuron.facts || []) {
|
|
181
201
|
if (!f.text)
|
|
@@ -186,7 +206,7 @@ function neuronOps(neuron, id) {
|
|
|
186
206
|
...(f.reference ? { reference: f.reference } : {}),
|
|
187
207
|
...(f.temporal ? { temporal: f.temporal } : {}),
|
|
188
208
|
...(f.verification_invalidated_at ? { verification_invalidated_at: f.verification_invalidated_at } : {}),
|
|
189
|
-
...(f.keys?.length ? { keys: f.keys } : {}),
|
|
209
|
+
...(f.keys?.length ? { keys: f.keys.map(k => (0, secrets_js_1.redact)(k).text) } : {}),
|
|
190
210
|
...(f.shelf_life ? { shelf: f.shelf_life } : {}) });
|
|
191
211
|
if (f.verified) {
|
|
192
212
|
ops.push({ v: ops_js_1.OPS_VERSION, op: 'verify', nid: neuron.id, by: id.author, at: f.verified, eid: fid, ekind: 'fact',
|
|
@@ -194,14 +214,14 @@ function neuronOps(neuron, id) {
|
|
|
194
214
|
}
|
|
195
215
|
if (f.status === 'superseded' || f.status === 'retracted') {
|
|
196
216
|
ops.push({ v: ops_js_1.OPS_VERSION, op: 'status', nid: neuron.id, by: id.author,
|
|
197
|
-
at: f.revised || at, fid, to: f.status, why: f.revision_note });
|
|
217
|
+
at: f.revised || at, fid, to: f.status, why: f.revision_note ? (0, secrets_js_1.redact)(f.revision_note).text : undefined });
|
|
198
218
|
}
|
|
199
219
|
}
|
|
200
220
|
for (const d of neuron.decisions || []) {
|
|
201
221
|
if (!d.text)
|
|
202
222
|
continue;
|
|
203
223
|
ops.push({ v: ops_js_1.OPS_VERSION, op: 'decision', nid: neuron.id, by: id.author,
|
|
204
|
-
at: d.date || at, did: (0, ops_js_1.entryId)(d.text), text: d.text, why: d.rationale });
|
|
224
|
+
at: d.date || at, did: (0, ops_js_1.entryId)(d.text), text: d.text, why: d.rationale ? (0, secrets_js_1.redact)(d.rationale).text : d.rationale });
|
|
205
225
|
}
|
|
206
226
|
// Dated entries carry their own date in `at`, so the team sees when a
|
|
207
227
|
// pattern, error or debt was really recorded, not when it was shared.
|
|
@@ -273,7 +293,21 @@ async function prepareShare(brain, cortex, neuronRef, spaceName) {
|
|
|
273
293
|
for (const [k, v] of Object.entries(neuron.entry_verification || {})) {
|
|
274
294
|
revisarReferencia(`entry_verification[${k}].reference`, v.reference);
|
|
275
295
|
}
|
|
276
|
-
(neuron.decisions || []).forEach((d, i) =>
|
|
296
|
+
(neuron.decisions || []).forEach((d, i) => {
|
|
297
|
+
revisar(`decisions[${i}]`, d.text);
|
|
298
|
+
revisar(`decisions[${i}].rationale`, d.rationale);
|
|
299
|
+
});
|
|
300
|
+
// Every other field that travels in an op, not only the entry texts (G8):
|
|
301
|
+
// a credential in a rationale, an alias or a revision note went out while
|
|
302
|
+
// the dry run reported nothing blocked.
|
|
303
|
+
(neuron.facts || []).forEach((f, i) => {
|
|
304
|
+
(f.keys || []).forEach((k, j) => revisar(`facts[${i}].keys[${j}]`, k));
|
|
305
|
+
if (f.revision_note)
|
|
306
|
+
revisar(`facts[${i}].revision_note`, f.revision_note);
|
|
307
|
+
});
|
|
308
|
+
revisar('name', neuron.name);
|
|
309
|
+
revisar('summary', neuron.summary);
|
|
310
|
+
(neuron.tags || []).forEach((t, i) => revisar(`tags[${i}]`, t));
|
|
277
311
|
(neuron.patterns || []).forEach((p, i) => revisar(`patterns[${i}]`, p));
|
|
278
312
|
(neuron.errors || []).forEach((e, i) => revisar(`errors[${i}]`, e));
|
|
279
313
|
(neuron.debts || []).forEach((d, i) => revisar(`debts[${i}]`, d));
|
|
@@ -471,6 +505,7 @@ async function emitOps(brain, neuronId, make) {
|
|
|
471
505
|
});
|
|
472
506
|
}
|
|
473
507
|
// ─── Sync ────────────────────────────────────────────────────────
|
|
508
|
+
const MAX_LOG_BYTES = 32 * 1024 * 1024;
|
|
474
509
|
async function readAllOps(dir, neuronId) {
|
|
475
510
|
const opsDir = node_path_1.default.join(dir, 'neurons', neuronId, 'ops');
|
|
476
511
|
let files;
|
|
@@ -485,7 +520,11 @@ async function readAllOps(dir, neuronId) {
|
|
|
485
520
|
if (!f.endsWith('.jsonl'))
|
|
486
521
|
continue;
|
|
487
522
|
try {
|
|
488
|
-
const
|
|
523
|
+
const p = node_path_1.default.join(opsDir, f);
|
|
524
|
+
// A log that size is not notes; reading it whole would only cost memory.
|
|
525
|
+
if ((await node_fs_1.promises.stat(p)).size > MAX_LOG_BYTES)
|
|
526
|
+
continue;
|
|
527
|
+
const raw = await node_fs_1.promises.readFile(p, 'utf-8');
|
|
489
528
|
all.push(...(0, ops_js_1.decodeOps)(raw).ops);
|
|
490
529
|
}
|
|
491
530
|
catch { /* unreadable log: skip it, keep the rest */ }
|
|
@@ -503,11 +542,16 @@ async function syncSpaceUnlocked(brain, cortex, spaceName, timeoutMs) {
|
|
|
503
542
|
if (!cfg)
|
|
504
543
|
return { ...base, state: 'not_joined', message: `You are not in a space called "${spaceName}".` };
|
|
505
544
|
await withOutboxState(brain, () => drainOutboxUnlocked(brain, spaceName));
|
|
506
|
-
if (!(0, git_js_1.
|
|
545
|
+
if (!(await (0, git_js_1.gitAvailableAsync)()))
|
|
507
546
|
return { ...base, state: 'no_git', message: 'git is not installed on this machine.' };
|
|
547
|
+
// space.json arrives from the remote: its branch is checked like any input.
|
|
548
|
+
const branch = cfg.branch || 'main';
|
|
549
|
+
if ((0, validate_js_1.branchProblem)(branch)) {
|
|
550
|
+
return { ...base, state: 'conflict', message: 'The space.json of this space names an invalid branch. Nothing was synced.' };
|
|
551
|
+
}
|
|
508
552
|
const dir = spaceDir(brain, spaceName);
|
|
509
553
|
const id = await getIdentity(brain);
|
|
510
|
-
const { pulled, pushed } = (0, git_js_1.
|
|
554
|
+
const { pulled, pushed } = await (0, git_js_1.syncSpaceAsync)(dir, id.author, branch, timeoutMs);
|
|
511
555
|
if (!pulled.ok) {
|
|
512
556
|
const state = (pulled.reason === 'offline' || pulled.reason === 'timeout') ? 'offline'
|
|
513
557
|
: pulled.reason === 'auth' ? 'auth'
|
|
@@ -531,10 +575,30 @@ async function syncSpaceUnlocked(brain, cortex, spaceName, timeoutMs) {
|
|
|
531
575
|
catch { /* nothing shared yet */ }
|
|
532
576
|
// Neurons this machine stopped following: no merge, no markShared.
|
|
533
577
|
const fuera = new Set(await unsharedList(brain));
|
|
578
|
+
const mapa = await sharedMap(brain);
|
|
579
|
+
const intactas = [];
|
|
580
|
+
let descartadas = 0;
|
|
534
581
|
for (const nid of neuronIds) {
|
|
535
582
|
if (fuera.has(nid))
|
|
536
583
|
continue;
|
|
537
|
-
|
|
584
|
+
// A folder name is what becomes a file name in the cortex: only what
|
|
585
|
+
// CRBRO itself would have written.
|
|
586
|
+
if (!validate_js_1.NEURON_DIR_RE.test(nid))
|
|
587
|
+
continue;
|
|
588
|
+
const owner = mapa[nid];
|
|
589
|
+
// Followed in another space: that space's sync owns it.
|
|
590
|
+
if (owner && owner !== spaceName)
|
|
591
|
+
continue;
|
|
592
|
+
// A local neuron you never shared here stays yours. Until 2.10 a
|
|
593
|
+
// teammate could create neurons/<your id>/ and the next sync merged into
|
|
594
|
+
// it and marked it shared, so everything you learned there went out (G2).
|
|
595
|
+
if (!owner && await (0, fs_js_1.fileExists)(brain.paths.neuron(nid))) {
|
|
596
|
+
intactas.push(nid);
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
const leidas = await readAllOps(dir, nid);
|
|
600
|
+
const { ops, dropped } = (0, validate_js_1.sanitizeIncomingOps)(nid, leidas);
|
|
601
|
+
descartadas += dropped;
|
|
538
602
|
if (ops.length === 0)
|
|
539
603
|
continue;
|
|
540
604
|
const { report, created } = await cortex.mergeFromSync(nid, ops);
|
|
@@ -547,8 +611,13 @@ async function syncSpaceUnlocked(brain, cortex, spaceName, timeoutMs) {
|
|
|
547
611
|
base.neurons_touched.push(nid);
|
|
548
612
|
base.merged.push(report);
|
|
549
613
|
}
|
|
550
|
-
|
|
614
|
+
if (!owner)
|
|
615
|
+
await markSharedUnlocked(brain, nid, spaceName);
|
|
551
616
|
}
|
|
617
|
+
if (intactas.length)
|
|
618
|
+
base.left_alone = intactas;
|
|
619
|
+
if (descartadas)
|
|
620
|
+
base.ops_dropped = descartadas;
|
|
552
621
|
const pending = await withOutboxState(brain, () => drainOutboxUnlocked(brain, spaceName));
|
|
553
622
|
// Git solo envió lo que había antes: lo añadido después espera el próximo sync.
|
|
554
623
|
base.pushed = !!pushed?.ok && pending === 0;
|
|
@@ -559,6 +628,12 @@ async function syncSpaceUnlocked(brain, cortex, spaceName, timeoutMs) {
|
|
|
559
628
|
if (!base.pushed && pushed) {
|
|
560
629
|
base.message += ' Your own notes could not be pushed this time; they will go with the next sync.';
|
|
561
630
|
}
|
|
631
|
+
if (intactas.length) {
|
|
632
|
+
base.message += ` ${intactas.length} neuron(s) in the space have the id of a private neuron of yours and were left ` +
|
|
633
|
+
`alone (${intactas.slice(0, 5).join(', ')}${intactas.length > 5 ? ', …' : ''}). To merge one, crbro_share it into "${spaceName}".`;
|
|
634
|
+
}
|
|
635
|
+
if (descartadas)
|
|
636
|
+
base.message += ` ${descartadas} malformed incoming note(s) were ignored.`;
|
|
562
637
|
return base;
|
|
563
638
|
}
|
|
564
639
|
/** Sync every space. Used at boot and at consolidation, with a short budget. */
|
|
@@ -593,7 +668,7 @@ function attachSync(brain, cortex) {
|
|
|
593
668
|
...(change.reference ? { reference: change.reference } : {}),
|
|
594
669
|
...(change.temporal ? { temporal: change.temporal } : {}),
|
|
595
670
|
...(change.verification_invalidated_at ? { verification_invalidated_at: change.verification_invalidated_at } : {}),
|
|
596
|
-
...(change.keys?.length ? { keys: change.keys } : {}),
|
|
671
|
+
...(change.keys?.length ? { keys: change.keys.map(k => (0, secrets_js_1.redact)(k).text) } : {}),
|
|
597
672
|
...(change.shelf ? { shelf: change.shelf } : {}) }];
|
|
598
673
|
}
|
|
599
674
|
if (change.kind === 'verify') {
|
|
@@ -601,11 +676,11 @@ function attachSync(brain, cortex) {
|
|
|
601
676
|
...(change.verification ? { verification: change.verification } : {}) }];
|
|
602
677
|
}
|
|
603
678
|
if (change.kind === 'status') {
|
|
604
|
-
return [{ ...comun, op: 'status', fid: change.fid, to: change.to, why: change.why }];
|
|
679
|
+
return [{ ...comun, op: 'status', fid: change.fid, to: change.to, why: change.why ? (0, secrets_js_1.redact)(change.why).text : change.why }];
|
|
605
680
|
}
|
|
606
681
|
if (change.kind === 'decision') {
|
|
607
682
|
return [{ ...comun, op: 'decision', did: (0, ops_js_1.entryId)(change.text),
|
|
608
|
-
text: change.text, why: change.why }];
|
|
683
|
+
text: change.text, why: change.why ? (0, secrets_js_1.redact)(change.why).text : change.why }];
|
|
609
684
|
}
|
|
610
685
|
if (change.kind === 'error') {
|
|
611
686
|
return [{ ...comun, op: 'error', text: change.text }];
|