@bongos/core 1.19.379 → 1.19.381
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/.bongos-core.json +149 -14
- package/docs/design/apex-pass-2-direction.md +104 -0
- package/docs/design/mocks/world/blue-hud/_tokens.css +246 -0
- package/docs/design/mocks/world/blue-hud/hall.html +595 -0
- package/docs/design/mocks/world/blue-hud/landing.html +424 -0
- package/docs/design/mocks/world/blue-hud/status.html +410 -0
- package/docs/design/mocks/world/chrome-blobs/_tokens.css +479 -0
- package/docs/design/mocks/world/chrome-blobs/hall.html +553 -0
- package/docs/design/mocks/world/chrome-blobs/landing-b.html +581 -0
- package/docs/design/mocks/world/chrome-blobs/landing.html +328 -0
- package/docs/design/mocks/world/chrome-blobs/status.html +416 -0
- package/docs/design/mocks/world/natgeo-editorial/_tokens.css +377 -0
- package/docs/design/mocks/world/natgeo-editorial/hall.html +649 -0
- package/docs/design/mocks/world/natgeo-editorial/landing-b.html +743 -0
- package/docs/design/mocks/world/natgeo-editorial/landing.html +475 -0
- package/docs/design/mocks/world/natgeo-editorial/status.html +437 -0
- package/docs/design/mocks/world/organic-cards/_tokens.css +462 -0
- package/docs/design/mocks/world/organic-cards/hall.html +625 -0
- package/docs/design/mocks/world/organic-cards/landing-b.html +736 -0
- package/docs/design/mocks/world/organic-cards/landing.html +420 -0
- package/docs/design/mocks/world/organic-cards/status.html +458 -0
- package/docs/design/mocks/world/world-sheet.html +156 -0
- package/docs/design/references/2026-08-26/hall-1-light-mono-label-dashboard.webp +0 -0
- package/docs/design/references/2026-08-26/hall-2-dark-glass-dashboard-forest.webp +0 -0
- package/docs/design/references/2026-08-26/site-1-natgeo-dark-editorial-planets-expedition-map.jpg +0 -0
- package/docs/design/references/2026-08-26/site-2-iridescent-chrome-blobs-warm-cta.jpg +0 -0
- package/docs/design/references/2026-08-26/site-3-blue-accent-hud-portfolio.jpg +0 -0
- package/docs/design/references/2026-08-26/site-4-green-brown-organic-card-system.jpg +0 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/ship-memory-sync.js +62 -5
- package/scripts/gds/ship-session-upload.js +12 -10
- package/src/module-api.js +5 -1
- package/tests/session_records.mjs +122 -5
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.381",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.381",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.381",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
|
@@ -56,21 +56,78 @@ function encodeProjectPath(absProjectPath) {
|
|
|
56
56
|
return String(absProjectPath).replace(/[^a-zA-Z0-9]/g, '-');
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
// listProjectDirs — the default directory lister for the scan fallback below.
|
|
60
|
+
// Returns the immediate subdirectory NAMES of ~/.claude/projects, sorted so a
|
|
61
|
+
// scan is deterministic. Injectable (opts.readdirFn) so the fallback is unit
|
|
62
|
+
// testable without a real home dir.
|
|
63
|
+
function listProjectDirs(projectsRoot) {
|
|
64
|
+
return fs
|
|
65
|
+
.readdirSync(projectsRoot, { withFileTypes: true })
|
|
66
|
+
.filter((d) => d.isDirectory())
|
|
67
|
+
.map((d) => d.name)
|
|
68
|
+
.sort();
|
|
69
|
+
}
|
|
70
|
+
|
|
59
71
|
// resolveTranscriptPath — find this session's transcript .jsonl. Claude Code
|
|
60
72
|
// keys the transcript to the project dir of the CWD where the session RUNS, so
|
|
61
73
|
// a session in a git worktree stores it under the WORKTREE-encoded dir, not the
|
|
62
74
|
// main checkout. (6D.1 #540 shipped resolving only REPO_ROOT, which silently
|
|
63
75
|
// missed every worktree session — #551.) We try the session-local dir
|
|
64
|
-
// (process.cwd()) first, then REPO_ROOT
|
|
65
|
-
//
|
|
76
|
+
// (process.cwd()) first, then REPO_ROOT.
|
|
77
|
+
//
|
|
78
|
+
// task 1002505: those two fast paths NARROW the miss, they do not seal it. The
|
|
79
|
+
// session driving a ship need not be rooted in EITHER dir — a session rooted in
|
|
80
|
+
// another repo can file a task here and ship it (proven on task 1002475, whose
|
|
81
|
+
// orchestrating session lived under a different project's encoded dir, so both
|
|
82
|
+
// probed paths missed, no session_record was written, and the cost-plus reward
|
|
83
|
+
// — the only stream this instance pays, ADR 0146 — never booked). So after the
|
|
84
|
+
// fast paths we SCAN every dir under ~/.claude/projects for <sessionId>.jsonl.
|
|
85
|
+
// A session id is a UUID, so a filename hit is unambiguous and cannot
|
|
86
|
+
// mis-attribute; the scan is one stat per project dir, so it stays cheap.
|
|
87
|
+
//
|
|
88
|
+
// Pure-ish (exists + readdir + base injectable for tests). Pass opts.tried an
|
|
89
|
+
// array and every probed path is appended to it — the caller reports that in a
|
|
90
|
+
// transcript_not_found skip, so a genuine miss still names where it looked. The
|
|
91
|
+
// scan contributes ONE summary line, not one line per dir, so the reason stays
|
|
92
|
+
// readable. Returns the first existing path, or null.
|
|
66
93
|
function resolveTranscriptPath(sessionId, projectDirs, opts = {}) {
|
|
67
94
|
const exists = opts.existsFn || fs.existsSync;
|
|
95
|
+
const readdir = opts.readdirFn || listProjectDirs;
|
|
68
96
|
const base = opts.base || claudeConfigBase();
|
|
69
|
-
|
|
97
|
+
const tried = Array.isArray(opts.tried) ? opts.tried : null;
|
|
98
|
+
const projectsRoot = path.join(base, 'projects');
|
|
99
|
+
const seen = new Set();
|
|
100
|
+
|
|
101
|
+
const probe = (p, { record = true } = {}) => {
|
|
102
|
+
if (seen.has(p)) return false;
|
|
103
|
+
seen.add(p);
|
|
104
|
+
if (tried && record) tried.push(p);
|
|
105
|
+
return exists(p);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
for (const dir of projectDirs || []) {
|
|
70
109
|
if (!dir) continue;
|
|
71
|
-
const p = path.join(
|
|
72
|
-
if (
|
|
110
|
+
const p = path.join(projectsRoot, encodeProjectPath(path.resolve(dir)), `${sessionId}.jsonl`);
|
|
111
|
+
if (probe(p)) return p;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Fallback scan. Best-effort: an unreadable/absent projects root is not an
|
|
115
|
+
// error here — it just means the scan finds nothing, same as the fast paths.
|
|
116
|
+
let names;
|
|
117
|
+
try {
|
|
118
|
+
names = readdir(projectsRoot);
|
|
119
|
+
} catch (_) {
|
|
120
|
+
names = null;
|
|
121
|
+
}
|
|
122
|
+
if (!Array.isArray(names)) {
|
|
123
|
+
if (tried) tried.push(`${path.join(projectsRoot, '*')} (could not read the projects dir)`);
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
for (const name of names) {
|
|
127
|
+
const p = path.join(projectsRoot, name, `${sessionId}.jsonl`);
|
|
128
|
+
if (probe(p, { record: false })) return p;
|
|
73
129
|
}
|
|
130
|
+
if (tried) tried.push(`${path.join(projectsRoot, '*', `${sessionId}.jsonl`)} (scanned ${names.length} project dirs)`);
|
|
74
131
|
return null;
|
|
75
132
|
}
|
|
76
133
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
const fs = require('node:fs');
|
|
12
12
|
const path = require('node:path');
|
|
13
13
|
const { cliClient, loadSession } = require('./cli-lib');
|
|
14
|
-
const {
|
|
14
|
+
const { resolveTranscriptPath } = require('./ship-memory-sync.js');
|
|
15
15
|
const { apiErrorLine } = require('./ship-io.js');
|
|
16
16
|
const { REPO_ROOT } = require('./ship-state.js');
|
|
17
17
|
|
|
@@ -117,16 +117,18 @@ async function uploadSessionOnShip(taskId) {
|
|
|
117
117
|
// hostile env value with `../` could otherwise point the read elsewhere.
|
|
118
118
|
const sessionIdSafe = !!sessionId && /^[A-Za-z0-9_-]+$/.test(sessionId);
|
|
119
119
|
// The transcript is keyed to the session's CWD (the worktree when shipping
|
|
120
|
-
// from one), so try that before REPO_ROOT (#551)
|
|
121
|
-
//
|
|
122
|
-
//
|
|
120
|
+
// from one), so try that before REPO_ROOT (#551), then let the resolver scan
|
|
121
|
+
// every ~/.claude/projects dir — a session rooted in ANOTHER repo can drive a
|
|
122
|
+
// ship here, and before task 1002505 that missed both fast paths and silently
|
|
123
|
+
// wrote no session_record at all. resolveTranscriptPath fills pathsTried with
|
|
124
|
+
// exactly what it probed, so a transcript-not-found skip names precisely where
|
|
125
|
+
// it looked (actionable, not a bare "not found") without this file having to
|
|
126
|
+
// re-derive the encoding and drift from it.
|
|
123
127
|
const dirsTried = [process.cwd(), REPO_ROOT].filter(Boolean);
|
|
124
|
-
const pathsTried =
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
: [];
|
|
129
|
-
const transcriptPath = sessionIdSafe ? resolveTranscriptPath(sessionId, dirsTried) : null;
|
|
128
|
+
const pathsTried = [];
|
|
129
|
+
const transcriptPath = sessionIdSafe
|
|
130
|
+
? resolveTranscriptPath(sessionId, dirsTried, { tried: pathsTried })
|
|
131
|
+
: null;
|
|
130
132
|
|
|
131
133
|
const decision = classifyUploadSkip({ sessionId, sessionIdSafe, transcriptPath, pathsTried });
|
|
132
134
|
if (decision.skip) {
|
package/src/module-api.js
CHANGED
|
@@ -1198,8 +1198,12 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
1198
1198
|
// landed since 1.19.377 with no explicit bump. run 33025512093. (task 1002620)
|
|
1199
1199
|
// 1.19.379 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1200
1200
|
// landed since 1.19.378 with no explicit bump. run 33026467245. (task 1002620)
|
|
1201
|
+
// 1.19.380 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1202
|
+
// landed since 1.19.379 with no explicit bump. run 33085516721. (task 1002620)
|
|
1203
|
+
// 1.19.381 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1204
|
+
// landed since 1.19.380 with no explicit bump. run 33088290961. (task 1002620)
|
|
1201
1205
|
// ---------------------------------------------------------------------------
|
|
1202
|
-
const CORE_VERSION = '1.19.
|
|
1206
|
+
const CORE_VERSION = '1.19.381'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
1203
1207
|
|
|
1204
1208
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
1205
1209
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -112,27 +112,144 @@ test('snippetOf flattens whitespace, caps length, and scrubs', () => {
|
|
|
112
112
|
});
|
|
113
113
|
|
|
114
114
|
// ---------- resolveTranscriptPath (#551: worktree sessions) ----------
|
|
115
|
+
//
|
|
116
|
+
// FIXTURE NOTE: the expected transcript path is DERIVED via encodeProjectPath(
|
|
117
|
+
// path.resolve(dir)) rather than hard-coded as its POSIX encoding. path.resolve
|
|
118
|
+
// is platform-dependent — on win32 '/proj' resolves to 'C:\\\\proj', which encodes
|
|
119
|
+
// to 'C--proj', not '-proj' — so hard-coded encodings made these two tests fail
|
|
120
|
+
// on Windows while passing in Linux CI. Deriving them asserts the same behaviour
|
|
121
|
+
// on both. (POSIX encoding itself is pinned separately below.)
|
|
122
|
+
const sidPath = (base, dir, sid) =>
|
|
123
|
+
path.join(base, 'projects', encodeProjectPath(path.resolve(dir)), `${sid}.jsonl`);
|
|
115
124
|
|
|
116
125
|
test('resolveTranscriptPath prefers the session CWD (worktree) over REPO_ROOT', () => {
|
|
117
126
|
const sid = 'abc-123';
|
|
118
127
|
const base = '/fake/.claude';
|
|
119
128
|
const worktree = '/proj/.claude/worktrees/wt1';
|
|
120
129
|
const mainRepo = '/proj';
|
|
121
|
-
const wtPath =
|
|
130
|
+
const wtPath = sidPath(base, worktree, sid);
|
|
122
131
|
// Only the worktree-encoded path exists — the bug looked at REPO_ROOT first.
|
|
123
|
-
const got = resolveTranscriptPath(sid, [worktree, mainRepo], {
|
|
132
|
+
const got = resolveTranscriptPath(sid, [worktree, mainRepo], {
|
|
133
|
+
base,
|
|
134
|
+
existsFn: (p) => p === wtPath,
|
|
135
|
+
readdirFn: () => [],
|
|
136
|
+
});
|
|
124
137
|
assert.equal(got, wtPath);
|
|
125
138
|
});
|
|
126
139
|
|
|
127
140
|
test('resolveTranscriptPath falls back to REPO_ROOT, else null', () => {
|
|
128
141
|
const sid = 'abc-123';
|
|
129
142
|
const base = '/fake/.claude';
|
|
130
|
-
const mainPath =
|
|
143
|
+
const mainPath = sidPath(base, '/proj', sid);
|
|
131
144
|
assert.equal(
|
|
132
|
-
resolveTranscriptPath(sid, ['/proj/.claude/worktrees/wt1', '/proj'], {
|
|
145
|
+
resolveTranscriptPath(sid, ['/proj/.claude/worktrees/wt1', '/proj'], {
|
|
146
|
+
base,
|
|
147
|
+
existsFn: (p) => p === mainPath,
|
|
148
|
+
readdirFn: () => [],
|
|
149
|
+
}),
|
|
133
150
|
mainPath
|
|
134
151
|
);
|
|
135
|
-
assert.equal(
|
|
152
|
+
assert.equal(
|
|
153
|
+
resolveTranscriptPath(sid, ['/a', '/b'], { base, existsFn: () => false, readdirFn: () => [] }),
|
|
154
|
+
null
|
|
155
|
+
);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// ---------- resolveTranscriptPath scan fallback (task 1002505) ----------
|
|
159
|
+
//
|
|
160
|
+
// The two fast paths above NARROW the miss, they do not seal it: the session
|
|
161
|
+
// driving a ship need not be rooted in process.cwd() OR REPO_ROOT. A session
|
|
162
|
+
// rooted in ANOTHER repo can file a task here and ship it (proven on task
|
|
163
|
+
// 1002475), and before this fallback both probes missed, classifyUploadSkip
|
|
164
|
+
// returned transcript_not_found, no session_record was written, and the
|
|
165
|
+
// cost-plus reward — the only stream this instance pays (ADR 0146) — never
|
|
166
|
+
// booked. The fallback scans every dir under <base>/projects for
|
|
167
|
+
// <sessionId>.jsonl; a session id is a UUID, so a filename hit is unambiguous.
|
|
168
|
+
|
|
169
|
+
test('resolveTranscriptPath finds a transcript rooted in a DIFFERENT project dir (the 1002475 shape)', () => {
|
|
170
|
+
const sid = 'e53f445b-308a-454e-9ac1-9c22d0188a37';
|
|
171
|
+
const base = '/fake/.claude';
|
|
172
|
+
// The orchestrating session lived under an unrelated repo's encoded dir, so
|
|
173
|
+
// neither the claim worktree nor REPO_ROOT could ever match it.
|
|
174
|
+
const foreign = path.join(base, 'projects', 'C--Users-x-Git-repos-other-project', `${sid}.jsonl`);
|
|
175
|
+
const got = resolveTranscriptPath(sid, ['/proj/.claude/worktrees/wt1', '/proj'], {
|
|
176
|
+
base,
|
|
177
|
+
existsFn: (p) => p === foreign,
|
|
178
|
+
readdirFn: () => ['C--Users-x-Git-repos-other-project', '-proj', '-proj--claude-worktrees-wt1'],
|
|
179
|
+
});
|
|
180
|
+
assert.equal(got, foreign);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test('resolveTranscriptPath scan only matches the exact session id — no mis-attribution', () => {
|
|
184
|
+
const sid = 'abc-123';
|
|
185
|
+
const base = '/fake/.claude';
|
|
186
|
+
const other = path.join(base, 'projects', '-somewhere-else', 'def-456.jsonl');
|
|
187
|
+
assert.equal(
|
|
188
|
+
resolveTranscriptPath(sid, ['/proj'], {
|
|
189
|
+
base,
|
|
190
|
+
existsFn: (p) => p === other,
|
|
191
|
+
readdirFn: () => ['-somewhere-else'],
|
|
192
|
+
}),
|
|
193
|
+
null
|
|
194
|
+
);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('resolveTranscriptPath does not scan when a fast path hits (precedence unchanged)', () => {
|
|
198
|
+
const sid = 'abc-123';
|
|
199
|
+
const base = '/fake/.claude';
|
|
200
|
+
const wtPath = sidPath(base, '/proj/.claude/worktrees/wt1', sid);
|
|
201
|
+
let scanned = 0;
|
|
202
|
+
const got = resolveTranscriptPath(sid, ['/proj/.claude/worktrees/wt1', '/proj'], {
|
|
203
|
+
base,
|
|
204
|
+
existsFn: (p) => p === wtPath,
|
|
205
|
+
readdirFn: () => { scanned++; return []; },
|
|
206
|
+
});
|
|
207
|
+
assert.equal(got, wtPath);
|
|
208
|
+
assert.equal(scanned, 0, 'the scan must not run once a fast path has matched');
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test('resolveTranscriptPath fills opts.tried: the fast paths, plus ONE scan summary', () => {
|
|
212
|
+
const sid = 'abc-123';
|
|
213
|
+
const base = '/fake/.claude';
|
|
214
|
+
const tried = [];
|
|
215
|
+
const got = resolveTranscriptPath(sid, ['/proj/.claude/worktrees/wt1', '/proj'], {
|
|
216
|
+
base,
|
|
217
|
+
existsFn: () => false,
|
|
218
|
+
readdirFn: () => ['-a', '-b', '-c'],
|
|
219
|
+
tried,
|
|
220
|
+
});
|
|
221
|
+
assert.equal(got, null);
|
|
222
|
+
// Two exact fast paths...
|
|
223
|
+
assert.equal(tried[0], sidPath(base, '/proj/.claude/worktrees/wt1', sid));
|
|
224
|
+
assert.equal(tried[1], sidPath(base, '/proj', sid));
|
|
225
|
+
// ...then a single bounded summary, not one line per scanned dir, so the
|
|
226
|
+
// transcript_not_found reason stays readable however many projects exist.
|
|
227
|
+
assert.equal(tried.length, 3);
|
|
228
|
+
assert.ok(tried[2].includes('scanned 3 project dirs'), tried[2]);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('resolveTranscriptPath survives an unreadable projects dir and says so', () => {
|
|
232
|
+
const sid = 'abc-123';
|
|
233
|
+
const base = '/fake/.claude';
|
|
234
|
+
const tried = [];
|
|
235
|
+
const got = resolveTranscriptPath(sid, ['/proj'], {
|
|
236
|
+
base,
|
|
237
|
+
existsFn: () => false,
|
|
238
|
+
readdirFn: () => { throw new Error('ENOENT'); },
|
|
239
|
+
tried,
|
|
240
|
+
});
|
|
241
|
+
assert.equal(got, null, 'a missing/unreadable projects dir must not throw into the ship path');
|
|
242
|
+
assert.ok(tried[tried.length - 1].includes('could not read the projects dir'), tried.join(' ; '));
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test('resolveTranscriptPath tolerates a null/absent projectDirs list', () => {
|
|
246
|
+
const sid = 'abc-123';
|
|
247
|
+
const base = '/fake/.claude';
|
|
248
|
+
const hit = path.join(base, 'projects', '-only-dir', `${sid}.jsonl`);
|
|
249
|
+
assert.equal(
|
|
250
|
+
resolveTranscriptPath(sid, null, { base, existsFn: (p) => p === hit, readdirFn: () => ['-only-dir'] }),
|
|
251
|
+
hit
|
|
252
|
+
);
|
|
136
253
|
});
|
|
137
254
|
|
|
138
255
|
// ---------- encodeProjectPath (task 1002471: win32 separators) ----------
|