brainjar 0.3.976 → 0.3.977
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/brainjar.js +465 -88
- package/package.json +1 -1
package/brainjar.js
CHANGED
|
@@ -107,7 +107,7 @@ var CLI_VERSION, CACHE, DAY;
|
|
|
107
107
|
var init_version_check = __esm({
|
|
108
108
|
"packages/cli/src/version-check.ts"() {
|
|
109
109
|
"use strict";
|
|
110
|
-
CLI_VERSION = "0.3.
|
|
110
|
+
CLI_VERSION = "0.3.977";
|
|
111
111
|
CACHE = path.join(os.homedir(), ".brainjar", "version-check.json");
|
|
112
112
|
DAY = 864e5;
|
|
113
113
|
}
|
|
@@ -5926,7 +5926,9 @@ var init_config = __esm({
|
|
|
5926
5926
|
// Launch pricing made permanent (official pricing page, checked 2026-08-18).
|
|
5927
5927
|
"claude-sonnet-5": { input: 2, cachedInput: 0.2, output: 10 },
|
|
5928
5928
|
"claude-haiku-4-5": { input: 1, cachedInput: 0.1, output: 5 },
|
|
5929
|
-
"gpt-5.6-luna": { input: 0.2, cachedInput: 0.02, output: 1.2 }
|
|
5929
|
+
"gpt-5.6-luna": { input: 0.2, cachedInput: 0.02, output: 1.2 },
|
|
5930
|
+
// Released 2026-09-22 (official pricing page, checked 2026-09-25).
|
|
5931
|
+
"gpt-6-luna": { input: 0.1, cachedInput: 0.01, output: 0.5 }
|
|
5930
5932
|
};
|
|
5931
5933
|
DEFAULT_TENANT_ID = "00000000-0000-0000-0000-000000000002";
|
|
5932
5934
|
SECRETS = {
|
|
@@ -6083,7 +6085,7 @@ function validateFlowDiagram(d) {
|
|
|
6083
6085
|
if (n2.evidence.length > 12)
|
|
6084
6086
|
errs.push(`nodes: ${n2.id} too many evidence entries`);
|
|
6085
6087
|
for (const ev of n2.evidence)
|
|
6086
|
-
if (!short(ev, 200) || /[
|
|
6088
|
+
if (!short(ev, 200) || /[\x00-\x1f]/.test(ev))
|
|
6087
6089
|
errs.push(`nodes: ${n2.id} evidence entry is not a short string`);
|
|
6088
6090
|
}
|
|
6089
6091
|
if (n2.description !== void 0 && !short(n2.description, 600))
|
|
@@ -13182,42 +13184,280 @@ function resolveSpecifier(fromFile, spec, known) {
|
|
|
13182
13184
|
for (const probe of [
|
|
13183
13185
|
clean,
|
|
13184
13186
|
c,
|
|
13185
|
-
...
|
|
13186
|
-
...
|
|
13187
|
+
...JS_EXTS.map((e) => clean + e),
|
|
13188
|
+
...JS_EXTS.map((e) => `${clean}/index${e}`)
|
|
13187
13189
|
]) {
|
|
13188
13190
|
if (known.has(probe)) return probe;
|
|
13189
13191
|
}
|
|
13190
13192
|
}
|
|
13191
13193
|
return null;
|
|
13192
13194
|
}
|
|
13195
|
+
function resolveBySuffix(segments, exts, fromFile, idx, inits = []) {
|
|
13196
|
+
if (segments.length === 0) return null;
|
|
13197
|
+
const want = segments.join("/");
|
|
13198
|
+
const last = segments[segments.length - 1];
|
|
13199
|
+
const hits = [
|
|
13200
|
+
...(idx.byName.get(last) ?? []).filter(
|
|
13201
|
+
(f) => exts.includes(ext(f)) && (stem(f) === want || stem(f).endsWith(`/${want}`))
|
|
13202
|
+
),
|
|
13203
|
+
...inits.flatMap(
|
|
13204
|
+
(init2) => (idx.byName.get(stem(init2)) ?? []).filter(
|
|
13205
|
+
(f) => f.endsWith(`/${want}/${init2}`) || f === `${want}/${init2}`
|
|
13206
|
+
)
|
|
13207
|
+
)
|
|
13208
|
+
];
|
|
13209
|
+
if (hits.length === 0) return null;
|
|
13210
|
+
return hits.sort(
|
|
13211
|
+
(a, b) => nearness(b, fromFile) - nearness(a, fromFile) || a.length - b.length
|
|
13212
|
+
)[0];
|
|
13213
|
+
}
|
|
13214
|
+
function pythonTargets(file, src, idx) {
|
|
13215
|
+
const out = [];
|
|
13216
|
+
const mod = (base, segs) => resolveBySuffix([...base, ...segs], [".py"], file, idx, ["__init__.py"]);
|
|
13217
|
+
for (const m of src.matchAll(PY_FROM)) {
|
|
13218
|
+
const dots = m[1].length;
|
|
13219
|
+
const segs = m[2] ? m[2].split(".") : [];
|
|
13220
|
+
const names = m[3].split(",").map((n2) => n2.trim().split(/\s+/)[0]);
|
|
13221
|
+
if (dots > 0) {
|
|
13222
|
+
let dir = path4.posix.dirname(file);
|
|
13223
|
+
for (let i = 1; i < dots; i++) dir = path4.posix.dirname(dir);
|
|
13224
|
+
const base = dir === "." ? [] : dir.split("/");
|
|
13225
|
+
const exact = (s) => {
|
|
13226
|
+
const p = [...base, ...s].join("/");
|
|
13227
|
+
return idx.known.has(`${p}.py`) ? `${p}.py` : idx.known.has(`${p}/__init__.py`) ? `${p}/__init__.py` : null;
|
|
13228
|
+
};
|
|
13229
|
+
const target = segs.length ? exact(segs) : null;
|
|
13230
|
+
if (target) out.push(target);
|
|
13231
|
+
for (const n2 of names) {
|
|
13232
|
+
const sub = exact([...segs, n2]);
|
|
13233
|
+
if (sub) out.push(sub);
|
|
13234
|
+
}
|
|
13235
|
+
} else if (segs.length) {
|
|
13236
|
+
const sub = names.map((n2) => mod([], [...segs, n2])).filter(Boolean);
|
|
13237
|
+
const target = resolveLongest(segs, (s) => mod([], s));
|
|
13238
|
+
out.push(...sub, ...target ? [target] : []);
|
|
13239
|
+
}
|
|
13240
|
+
}
|
|
13241
|
+
for (const m of src.matchAll(PY_IMPORT))
|
|
13242
|
+
for (const spec of m[1].split(",")) {
|
|
13243
|
+
const target = resolveLongest(spec.trim().split("."), (s) => mod([], s));
|
|
13244
|
+
if (target) out.push(target);
|
|
13245
|
+
}
|
|
13246
|
+
return out;
|
|
13247
|
+
}
|
|
13248
|
+
function rustTargets(file, src, idx) {
|
|
13249
|
+
const out = [];
|
|
13250
|
+
const dir = path4.posix.dirname(file);
|
|
13251
|
+
const base = path4.posix.basename(file, ".rs");
|
|
13252
|
+
const own = ["lib", "main", "mod"].includes(base) ? dir : `${dir}/${base}`;
|
|
13253
|
+
const crateSrc = [...idx.crates.values()].filter((s) => file.startsWith(`${s}/`)).sort((a, b) => b.length - a.length)[0];
|
|
13254
|
+
const inDir = (d, segs) => {
|
|
13255
|
+
const p = [d, ...segs].join("/");
|
|
13256
|
+
return idx.known.has(`${p}.rs`) ? `${p}.rs` : idx.known.has(`${p}/mod.rs`) ? `${p}/mod.rs` : null;
|
|
13257
|
+
};
|
|
13258
|
+
for (const m of src.matchAll(RS_MOD)) {
|
|
13259
|
+
const hit = inDir(own, [m[1]]);
|
|
13260
|
+
if (hit) out.push(hit);
|
|
13261
|
+
}
|
|
13262
|
+
for (const m of src.matchAll(RS_USE))
|
|
13263
|
+
for (const use of expandUse(m[1])) {
|
|
13264
|
+
const [head, ...rest] = use.replace(/^::/, "").split("::");
|
|
13265
|
+
let root;
|
|
13266
|
+
if (head === "crate") root = crateSrc;
|
|
13267
|
+
else if (head === "self") root = own;
|
|
13268
|
+
else if (head === "super")
|
|
13269
|
+
root = ["lib", "main", "mod"].includes(base) ? path4.posix.dirname(dir) : dir;
|
|
13270
|
+
else if (head && idx.crates.has(head)) {
|
|
13271
|
+
root = idx.crates.get(head);
|
|
13272
|
+
if (rest.length === 0) {
|
|
13273
|
+
const lib = `${root}/lib.rs`;
|
|
13274
|
+
if (idx.known.has(lib)) out.push(lib);
|
|
13275
|
+
continue;
|
|
13276
|
+
}
|
|
13277
|
+
}
|
|
13278
|
+
if (!root) continue;
|
|
13279
|
+
const hit = resolveLongest(rest, (s) => inDir(root, s));
|
|
13280
|
+
if (hit) out.push(hit);
|
|
13281
|
+
else if (head !== "self" && head !== "super") {
|
|
13282
|
+
const lib = `${root}/lib.rs`;
|
|
13283
|
+
if (idx.known.has(lib)) out.push(lib);
|
|
13284
|
+
}
|
|
13285
|
+
}
|
|
13286
|
+
return out;
|
|
13287
|
+
}
|
|
13288
|
+
function goTargets(file, src, idx) {
|
|
13289
|
+
const specs = [
|
|
13290
|
+
...[...src.matchAll(GO_BLOCK)].flatMap(
|
|
13291
|
+
(m) => [...m[1].matchAll(/"([^"]+)"/g)].map((x) => x[1])
|
|
13292
|
+
),
|
|
13293
|
+
...[...src.matchAll(GO_ONE)].map((m) => m[1])
|
|
13294
|
+
];
|
|
13295
|
+
const out = [];
|
|
13296
|
+
for (const spec of specs)
|
|
13297
|
+
for (const [mod, dir] of idx.goModules) {
|
|
13298
|
+
if (spec !== mod && !spec.startsWith(`${mod}/`)) continue;
|
|
13299
|
+
const pkgDir = [dir === "." ? "" : dir, spec.slice(mod.length + 1)].filter(Boolean).join("/");
|
|
13300
|
+
const rep = (idx.byDir.get(pkgDir) ?? []).find(
|
|
13301
|
+
(f) => f.endsWith(".go") && !f.endsWith("_test.go")
|
|
13302
|
+
);
|
|
13303
|
+
if (rep) out.push(rep);
|
|
13304
|
+
}
|
|
13305
|
+
return out;
|
|
13306
|
+
}
|
|
13307
|
+
function swiftTargets(src, idx) {
|
|
13308
|
+
const out = [];
|
|
13309
|
+
for (const m of src.matchAll(
|
|
13310
|
+
/^[ \t]*(?:@\w+[ \t]+)*import[ \t]+(?:\w+[ \t]+)?(\w+)/gm
|
|
13311
|
+
)) {
|
|
13312
|
+
for (const [dir, files] of idx.byDir) {
|
|
13313
|
+
if (!(dir === `Sources/${m[1]}` || dir.endsWith(`/Sources/${m[1]}`) || dir.includes(`/Sources/${m[1]}/`)))
|
|
13314
|
+
continue;
|
|
13315
|
+
const rep = files.find((f) => f.endsWith(".swift") || f.endsWith(".h"));
|
|
13316
|
+
if (rep) {
|
|
13317
|
+
out.push(rep);
|
|
13318
|
+
break;
|
|
13319
|
+
}
|
|
13320
|
+
}
|
|
13321
|
+
}
|
|
13322
|
+
return out;
|
|
13323
|
+
}
|
|
13324
|
+
function jvmTargets(file, src, idx) {
|
|
13325
|
+
const out = [];
|
|
13326
|
+
for (const m of src.matchAll(
|
|
13327
|
+
/^[ \t]*import[ \t]+(?:static[ \t]+)?([\w.]+)/gm
|
|
13328
|
+
)) {
|
|
13329
|
+
const hit = resolveLongest(
|
|
13330
|
+
m[1].split("."),
|
|
13331
|
+
(s) => resolveBySuffix(s, [".java", ".kt"], file, idx)
|
|
13332
|
+
);
|
|
13333
|
+
if (hit) out.push(hit);
|
|
13334
|
+
}
|
|
13335
|
+
return out;
|
|
13336
|
+
}
|
|
13337
|
+
function cTargets(file, src, idx) {
|
|
13338
|
+
const out = [];
|
|
13339
|
+
for (const m of src.matchAll(
|
|
13340
|
+
/^[ \t]*#[ \t]*(?:include|import)[ \t]*"([^"]+)"/gm
|
|
13341
|
+
)) {
|
|
13342
|
+
const rel = path4.posix.normalize(
|
|
13343
|
+
path4.posix.join(path4.posix.dirname(file), m[1])
|
|
13344
|
+
);
|
|
13345
|
+
if (idx.known.has(rel)) out.push(rel);
|
|
13346
|
+
else {
|
|
13347
|
+
const hit = resolveBySuffix(
|
|
13348
|
+
stem(m[1]).split("/"),
|
|
13349
|
+
[ext(m[1])],
|
|
13350
|
+
file,
|
|
13351
|
+
idx
|
|
13352
|
+
);
|
|
13353
|
+
if (hit) out.push(hit);
|
|
13354
|
+
}
|
|
13355
|
+
}
|
|
13356
|
+
return out;
|
|
13357
|
+
}
|
|
13193
13358
|
async function scanStructure(root) {
|
|
13194
13359
|
const entries = await readdir(root, { recursive: true, withFileTypes: true });
|
|
13195
13360
|
const files = [];
|
|
13361
|
+
const cargo = [];
|
|
13362
|
+
const gomod = [];
|
|
13196
13363
|
for (const e of entries) {
|
|
13197
13364
|
if (!e.isFile()) continue;
|
|
13198
13365
|
const parent = e.parentPath ?? e.path ?? root;
|
|
13199
13366
|
const rel = toPosix(path4.relative(root, path4.join(parent, e.name)));
|
|
13200
13367
|
if (rel.split("/").some((seg) => SKIP_DIRS.has(seg))) continue;
|
|
13201
|
-
if (SOURCE_EXTS.some((
|
|
13368
|
+
if (SOURCE_EXTS.some((x) => rel.endsWith(x))) files.push(rel);
|
|
13369
|
+
else if (e.name === "Cargo.toml") cargo.push(rel);
|
|
13370
|
+
else if (e.name === "go.mod") gomod.push(rel);
|
|
13371
|
+
}
|
|
13372
|
+
const read = (f) => readFile(path4.join(root, f), "utf8");
|
|
13373
|
+
const idx = {
|
|
13374
|
+
known: new Set(files),
|
|
13375
|
+
byName: /* @__PURE__ */ new Map(),
|
|
13376
|
+
byDir: /* @__PURE__ */ new Map(),
|
|
13377
|
+
crates: /* @__PURE__ */ new Map(),
|
|
13378
|
+
goModules: /* @__PURE__ */ new Map()
|
|
13379
|
+
};
|
|
13380
|
+
for (const f of [...files].sort()) {
|
|
13381
|
+
const name = path4.posix.basename(stem(f));
|
|
13382
|
+
idx.byName.set(name, [...idx.byName.get(name) ?? [], f]);
|
|
13383
|
+
const d = path4.posix.dirname(f);
|
|
13384
|
+
idx.byDir.set(d, [...idx.byDir.get(d) ?? [], f]);
|
|
13385
|
+
}
|
|
13386
|
+
for (const c of cargo) {
|
|
13387
|
+
const pkg = (await read(c)).match(
|
|
13388
|
+
/\[package\][^[]*?\bname\s*=\s*"([^"]+)"/
|
|
13389
|
+
);
|
|
13390
|
+
const dir = path4.posix.dirname(c);
|
|
13391
|
+
if (pkg)
|
|
13392
|
+
idx.crates.set(
|
|
13393
|
+
pkg[1].replace(/-/g, "_"),
|
|
13394
|
+
`${dir === "." ? "" : `${dir}/`}src`.replace(/^\//, "")
|
|
13395
|
+
);
|
|
13202
13396
|
}
|
|
13203
|
-
const
|
|
13397
|
+
for (const g of gomod) {
|
|
13398
|
+
const mod = (await read(g)).match(/^module\s+(\S+)/m);
|
|
13399
|
+
if (mod) idx.goModules.set(mod[1], path4.posix.dirname(g));
|
|
13400
|
+
}
|
|
13401
|
+
const exported = /* @__PURE__ */ new Map();
|
|
13402
|
+
for (const f of files.filter((x) => x.endsWith(".rs")))
|
|
13403
|
+
for (const m of (await read(f)).matchAll(RS_EXPORT))
|
|
13404
|
+
if (!exported.has(m[1])) exported.set(m[1], f);
|
|
13405
|
+
const names = [...exported.keys()].slice(0, 2e3);
|
|
13406
|
+
const callRe = names.length ? new RegExp(
|
|
13407
|
+
`\\b(${names.map((n2) => n2.replace(/[^\w]/g, "")).join("|")})\\s*\\(`,
|
|
13408
|
+
"g"
|
|
13409
|
+
) : null;
|
|
13410
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13204
13411
|
const edges = [];
|
|
13412
|
+
const add = (from, to) => {
|
|
13413
|
+
if (!to || to === from || !idx.known.has(to)) return;
|
|
13414
|
+
const k = JSON.stringify([from, to]);
|
|
13415
|
+
if (seen.has(k)) return;
|
|
13416
|
+
seen.add(k);
|
|
13417
|
+
edges.push({ from, to });
|
|
13418
|
+
};
|
|
13205
13419
|
for (const file of files) {
|
|
13206
|
-
const
|
|
13207
|
-
|
|
13208
|
-
|
|
13209
|
-
|
|
13210
|
-
|
|
13420
|
+
const src = await read(file);
|
|
13421
|
+
const x = ext(file);
|
|
13422
|
+
if (JS_EXTS.includes(x))
|
|
13423
|
+
for (const spec of extractImports(src))
|
|
13424
|
+
add(file, resolveSpecifier(file, spec, idx.known));
|
|
13425
|
+
else if (x === ".py")
|
|
13426
|
+
for (const t of pythonTargets(file, src, idx)) add(file, t);
|
|
13427
|
+
else if (x === ".rs")
|
|
13428
|
+
for (const t of rustTargets(file, src, idx)) add(file, t);
|
|
13429
|
+
else if (x === ".go")
|
|
13430
|
+
for (const t of goTargets(file, src, idx)) add(file, t);
|
|
13431
|
+
else if (x === ".swift")
|
|
13432
|
+
for (const t of swiftTargets(src, idx)) add(file, t);
|
|
13433
|
+
else if (x === ".java" || x === ".kt")
|
|
13434
|
+
for (const t of jvmTargets(file, src, idx)) add(file, t);
|
|
13435
|
+
else if (C_EXTS.includes(x))
|
|
13436
|
+
for (const t of cTargets(file, src, idx)) add(file, t);
|
|
13437
|
+
if (callRe && x !== ".rs")
|
|
13438
|
+
for (const m of src.matchAll(callRe))
|
|
13439
|
+
add(file, exported.get(m[1]) ?? null);
|
|
13211
13440
|
}
|
|
13212
13441
|
return { modules: files.sort(), edges };
|
|
13213
13442
|
}
|
|
13214
|
-
var SOURCE_EXTS, SKIP_DIRS, IMPORT_RE, toPosix;
|
|
13443
|
+
var JS_EXTS, C_EXTS, SOURCE_EXTS, SKIP_DIRS, IMPORT_RE, toPosix, ext, stem, nearness, resolveLongest, PY_FROM, PY_IMPORT, RS_MOD, RS_USE, expandUse, GO_BLOCK, GO_ONE, RS_EXPORT;
|
|
13215
13444
|
var init_structure = __esm({
|
|
13216
13445
|
"packages/ingest/src/structure.ts"() {
|
|
13217
13446
|
"use strict";
|
|
13218
13447
|
init_src2();
|
|
13219
13448
|
init_entities();
|
|
13220
|
-
|
|
13449
|
+
JS_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
13450
|
+
C_EXTS = [".c", ".h", ".cc", ".cpp", ".hpp", ".m", ".mm"];
|
|
13451
|
+
SOURCE_EXTS = [
|
|
13452
|
+
...JS_EXTS,
|
|
13453
|
+
".py",
|
|
13454
|
+
".rs",
|
|
13455
|
+
".go",
|
|
13456
|
+
".swift",
|
|
13457
|
+
".java",
|
|
13458
|
+
".kt",
|
|
13459
|
+
...C_EXTS
|
|
13460
|
+
];
|
|
13221
13461
|
SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
13222
13462
|
"node_modules",
|
|
13223
13463
|
".git",
|
|
@@ -13227,10 +13467,51 @@ var init_structure = __esm({
|
|
|
13227
13467
|
".next",
|
|
13228
13468
|
"dist",
|
|
13229
13469
|
"build",
|
|
13230
|
-
"coverage"
|
|
13470
|
+
"coverage",
|
|
13471
|
+
// Build output and dependency caches of the other languages.
|
|
13472
|
+
"target",
|
|
13473
|
+
".build",
|
|
13474
|
+
"__pycache__",
|
|
13475
|
+
".venv",
|
|
13476
|
+
"venv",
|
|
13477
|
+
"vendor",
|
|
13478
|
+
"Pods",
|
|
13479
|
+
"DerivedData",
|
|
13480
|
+
".gradle"
|
|
13231
13481
|
]);
|
|
13232
13482
|
IMPORT_RE = /(?:import|export)\s+[^'"]*?from\s+['"]([^'"]+)['"]|import\s*\(\s*['"]([^'"]+)['"]\s*\)|require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
13233
13483
|
toPosix = (p) => p.split(path4.sep).join("/");
|
|
13484
|
+
ext = (f) => path4.posix.extname(f);
|
|
13485
|
+
stem = (f) => f.slice(0, f.length - ext(f).length);
|
|
13486
|
+
nearness = (a, b) => {
|
|
13487
|
+
const x = a.split("/");
|
|
13488
|
+
const y = b.split("/");
|
|
13489
|
+
let i = 0;
|
|
13490
|
+
while (i < x.length && i < y.length && x[i] === y[i]) i++;
|
|
13491
|
+
return i;
|
|
13492
|
+
};
|
|
13493
|
+
resolveLongest = (segments, resolve) => {
|
|
13494
|
+
for (let n2 = segments.length; n2 > 0; n2--) {
|
|
13495
|
+
const hit = resolve(segments.slice(0, n2));
|
|
13496
|
+
if (hit) return hit;
|
|
13497
|
+
}
|
|
13498
|
+
return null;
|
|
13499
|
+
};
|
|
13500
|
+
PY_FROM = /^[ \t]*from[ \t]+(\.*)([\w.]*)[ \t]+import[ \t]+\(?([^\n#)]+)/gm;
|
|
13501
|
+
PY_IMPORT = /^[ \t]*import[ \t]+([\w.]+(?:[ \t]*,[ \t]*[\w.]+)*)/gm;
|
|
13502
|
+
RS_MOD = /^[ \t]*(?:pub(?:\([^)]*\))?[ \t]+)?mod[ \t]+(\w+)[ \t]*;/gm;
|
|
13503
|
+
RS_USE = /\buse[ \t]+([^;]+);/g;
|
|
13504
|
+
expandUse = (use) => {
|
|
13505
|
+
const clean = use.replace(/\s+/g, "");
|
|
13506
|
+
const brace = clean.match(/^(.*?)::\{(.*)\}$/);
|
|
13507
|
+
if (!brace) return [clean.replace(/::\*$/, "")];
|
|
13508
|
+
return brace[2].split(",").filter(Boolean).map(
|
|
13509
|
+
(part) => part === "self" ? brace[1] : `${brace[1]}::${part.replace(/::\*$/, "")}`
|
|
13510
|
+
);
|
|
13511
|
+
};
|
|
13512
|
+
GO_BLOCK = /\bimport[ \t]*\(([\s\S]*?)\)/g;
|
|
13513
|
+
GO_ONE = /\bimport[ \t]+(?:[\w.]+[ \t]+)?"([^"]+)"/g;
|
|
13514
|
+
RS_EXPORT = /extern[ \t]+"C"[ \t]+fn[ \t]+([A-Za-z_]\w{2,})/g;
|
|
13234
13515
|
}
|
|
13235
13516
|
});
|
|
13236
13517
|
|
|
@@ -15456,6 +15737,45 @@ function aiState(m) {
|
|
|
15456
15737
|
aiAttempts: typeof m?.aiAttempts === "number" ? m.aiAttempts : 0
|
|
15457
15738
|
};
|
|
15458
15739
|
}
|
|
15740
|
+
function aiFacts(kind, m) {
|
|
15741
|
+
if (!m) return null;
|
|
15742
|
+
if (kind === "feature") {
|
|
15743
|
+
const f = m;
|
|
15744
|
+
return JSON.stringify([
|
|
15745
|
+
f.title,
|
|
15746
|
+
f.what,
|
|
15747
|
+
f.behavior ?? null,
|
|
15748
|
+
f.config ?? [],
|
|
15749
|
+
f.locations ?? [],
|
|
15750
|
+
f.why ?? null
|
|
15751
|
+
]);
|
|
15752
|
+
}
|
|
15753
|
+
if (kind === "entry") {
|
|
15754
|
+
const e = m;
|
|
15755
|
+
return JSON.stringify([
|
|
15756
|
+
e.brainName,
|
|
15757
|
+
e.description ?? null,
|
|
15758
|
+
(e.architecture?.groups ?? []).map((g) => g.name),
|
|
15759
|
+
// Tuples, never objects: a stored model comes back from jsonb with its keys reordered.
|
|
15760
|
+
(e.architecture?.edges ?? []).map((x) => [x.from, x.to]),
|
|
15761
|
+
(e.features ?? []).map((f) => [f.slug, f.title, f.area, f.what])
|
|
15762
|
+
]);
|
|
15763
|
+
}
|
|
15764
|
+
return null;
|
|
15765
|
+
}
|
|
15766
|
+
function withStoredAi(model, stored) {
|
|
15767
|
+
const s = stored;
|
|
15768
|
+
const fresh = model;
|
|
15769
|
+
return {
|
|
15770
|
+
...model,
|
|
15771
|
+
...s.presentation ? { presentation: s.presentation } : {},
|
|
15772
|
+
body: s.body ?? null,
|
|
15773
|
+
docTitle: s.docTitle ?? null,
|
|
15774
|
+
category: s.category ?? null,
|
|
15775
|
+
...!fresh.flow && s.flow ? { flow: s.flow } : {},
|
|
15776
|
+
...!fresh.flowDiagram && s.flowDiagram ? { flowDiagram: s.flowDiagram } : {}
|
|
15777
|
+
};
|
|
15778
|
+
}
|
|
15459
15779
|
function aiComplete(kind, m) {
|
|
15460
15780
|
if (!m) return false;
|
|
15461
15781
|
const text = (v) => typeof v === "string" && v.trim().length > 0;
|
|
@@ -15471,6 +15791,28 @@ function aiComplete(kind, m) {
|
|
|
15471
15791
|
return true;
|
|
15472
15792
|
}
|
|
15473
15793
|
}
|
|
15794
|
+
function pagePrompt(input) {
|
|
15795
|
+
const { categories, flowDiagramTitle, hasFlow, sharedLines = [] } = input;
|
|
15796
|
+
return [
|
|
15797
|
+
"Categories (pick exactly one key):",
|
|
15798
|
+
...categories.length ? categories.map((c) => `- ${c.key}: ${c.name} (${c.description})`) : ["- general: General (everything)"],
|
|
15799
|
+
"",
|
|
15800
|
+
"Available diagrams (the only allowed flow block sources):",
|
|
15801
|
+
...flowDiagramTitle ? [
|
|
15802
|
+
`- flowDiagram: "${flowDiagramTitle}" (the feature's runtime flow, lanes and stations)`
|
|
15803
|
+
] : [],
|
|
15804
|
+
...hasFlow && !flowDiagramTitle ? ["- flow: the feature's runtime steps as a simple chain"] : [],
|
|
15805
|
+
...!flowDiagramTitle && !hasFlow ? ["- (none)"] : [],
|
|
15806
|
+
"",
|
|
15807
|
+
...sharedLines.length ? [
|
|
15808
|
+
"Documented elsewhere for the same endpoint (proven facts; reuse them, link the page):",
|
|
15809
|
+
...sharedLines,
|
|
15810
|
+
""
|
|
15811
|
+
] : [],
|
|
15812
|
+
"Fact sheet:",
|
|
15813
|
+
input.factSheet
|
|
15814
|
+
].join("\n");
|
|
15815
|
+
}
|
|
15474
15816
|
function parseDocPage(raw, categoryKeys, ctx) {
|
|
15475
15817
|
const bad = (error2) => ({ page: null, error: error2, dropped: [] });
|
|
15476
15818
|
const match = raw.match(/\{[\s\S]*\}/);
|
|
@@ -15614,16 +15956,32 @@ function parseArchNotes(raw, groups) {
|
|
|
15614
15956
|
};
|
|
15615
15957
|
}
|
|
15616
15958
|
function archFlowsInput(model) {
|
|
15959
|
+
const modules = (model.raw?.modules ?? []).filter(
|
|
15960
|
+
(m) => !/\.test\.[a-z]+$/.test(m)
|
|
15961
|
+
);
|
|
15962
|
+
const cited = new Set(model.features.flatMap((f) => f.locations));
|
|
15617
15963
|
const files = /* @__PURE__ */ new Map();
|
|
15618
|
-
for (const m of
|
|
15619
|
-
|
|
15964
|
+
for (const m of [
|
|
15965
|
+
...modules.filter((m2) => cited.has(m2)),
|
|
15966
|
+
...modules.filter((m2) => !cited.has(m2))
|
|
15967
|
+
]) {
|
|
15620
15968
|
const g = moduleGroup(m);
|
|
15621
15969
|
const list = files.get(g) ?? [];
|
|
15622
|
-
if (list.length <
|
|
15970
|
+
if (list.length < 12) list.push(m);
|
|
15623
15971
|
files.set(g, list);
|
|
15624
15972
|
}
|
|
15973
|
+
const spanning = model.features.flatMap((f) => {
|
|
15974
|
+
const groups = [...new Set(f.locations.map(moduleGroup))];
|
|
15975
|
+
return groups.length >= 2 ? [`- ${f.title}: ${groups.join(", ")}`] : [];
|
|
15976
|
+
});
|
|
15625
15977
|
return [
|
|
15626
15978
|
archNotesInput(model),
|
|
15979
|
+
...spanning.length ? [
|
|
15980
|
+
"",
|
|
15981
|
+
"## Features spanning groups (each feature's code lives in all of these groups)",
|
|
15982
|
+
"",
|
|
15983
|
+
...spanning
|
|
15984
|
+
] : [],
|
|
15627
15985
|
"",
|
|
15628
15986
|
"## Files (the ONLY paths you may cite as evidence, copied exactly)",
|
|
15629
15987
|
...[...files.entries()].flatMap(([g, list]) => [
|
|
@@ -15888,45 +16246,69 @@ async function generateDokuInner(brainId, opts = {}) {
|
|
|
15888
16246
|
}
|
|
15889
16247
|
continue;
|
|
15890
16248
|
}
|
|
15891
|
-
const
|
|
16249
|
+
const facts = aiFacts(page.kind, page.model);
|
|
16250
|
+
const sameFacts = stored !== void 0 && (facts !== null ? aiFacts(page.kind, stored) === facts : state.factHash === factHash);
|
|
15892
16251
|
let markdown = page.markdown;
|
|
15893
16252
|
let model = page.model;
|
|
15894
16253
|
let icon = null;
|
|
15895
16254
|
let ok2 = !required;
|
|
15896
|
-
if (
|
|
15897
|
-
|
|
15898
|
-
|
|
15899
|
-
|
|
15900
|
-
|
|
15901
|
-
|
|
15902
|
-
|
|
15903
|
-
|
|
15904
|
-
|
|
15905
|
-
|
|
15906
|
-
|
|
15907
|
-
|
|
15908
|
-
|
|
15909
|
-
|
|
16255
|
+
if (page.polishable && page.kind === "architecture") {
|
|
16256
|
+
const inputs = {
|
|
16257
|
+
notes: sha1(archNotesInput(doku)),
|
|
16258
|
+
flows: sha1(archFlowsInput(doku))
|
|
16259
|
+
};
|
|
16260
|
+
const s = stored ?? {};
|
|
16261
|
+
let notesFor;
|
|
16262
|
+
let flowsFor;
|
|
16263
|
+
if (typeof s.narrative === "string" && s.aiInputs?.notes === inputs.notes) {
|
|
16264
|
+
model = { ...model, narrative: s.narrative, roles: s.roles };
|
|
16265
|
+
notesFor = inputs.notes;
|
|
16266
|
+
ok2 = true;
|
|
16267
|
+
} else if (opts.llm) {
|
|
16268
|
+
try {
|
|
16269
|
+
const { notes } = parseArchNotes(
|
|
16270
|
+
await opts.llm.complete({
|
|
16271
|
+
system: ARCH_SYSTEM,
|
|
16272
|
+
prompt: archNotesInput(doku),
|
|
16273
|
+
maxTokens: 800
|
|
16274
|
+
}),
|
|
16275
|
+
doku.architecture.groups.map((g) => g.name)
|
|
16276
|
+
);
|
|
16277
|
+
if (notes) {
|
|
16278
|
+
model = {
|
|
16279
|
+
...model,
|
|
16280
|
+
narrative: notes.narrative,
|
|
16281
|
+
roles: notes.roles
|
|
16282
|
+
};
|
|
16283
|
+
notesFor = inputs.notes;
|
|
16284
|
+
polished = true;
|
|
16285
|
+
ok2 = true;
|
|
16286
|
+
}
|
|
16287
|
+
} catch {
|
|
15910
16288
|
}
|
|
15911
|
-
} catch {
|
|
15912
16289
|
}
|
|
15913
|
-
|
|
15914
|
-
|
|
15915
|
-
|
|
15916
|
-
|
|
15917
|
-
|
|
15918
|
-
|
|
15919
|
-
|
|
15920
|
-
|
|
15921
|
-
|
|
15922
|
-
|
|
15923
|
-
|
|
15924
|
-
|
|
16290
|
+
if (Array.isArray(s.flows) && s.flows.length > 0 && s.aiInputs?.flows === inputs.flows) {
|
|
16291
|
+
model = { ...model, flows: s.flows };
|
|
16292
|
+
flowsFor = inputs.flows;
|
|
16293
|
+
} else if (opts.llm) {
|
|
16294
|
+
try {
|
|
16295
|
+
const { flows } = parseArchFlows(
|
|
16296
|
+
await opts.llm.complete({
|
|
16297
|
+
system: ARCH_FLOWS_SYSTEM,
|
|
16298
|
+
prompt: archFlowsInput(doku),
|
|
16299
|
+
maxTokens: 4500
|
|
16300
|
+
}),
|
|
16301
|
+
new Set(doku.raw?.modules ?? [])
|
|
16302
|
+
);
|
|
16303
|
+
if (flows.length > 0) {
|
|
16304
|
+
model = { ...model, flows };
|
|
16305
|
+
flowsFor = inputs.flows;
|
|
16306
|
+
polished = true;
|
|
16307
|
+
}
|
|
16308
|
+
} catch {
|
|
15925
16309
|
}
|
|
15926
|
-
} catch {
|
|
15927
16310
|
}
|
|
15928
16311
|
if (!ok2 && sameFacts) {
|
|
15929
|
-
const s = stored;
|
|
15930
16312
|
const kept = model;
|
|
15931
16313
|
model = {
|
|
15932
16314
|
...model,
|
|
@@ -15934,6 +16316,14 @@ async function generateDokuInner(brainId, opts = {}) {
|
|
|
15934
16316
|
...!kept.flows && Array.isArray(s.flows) ? { flows: s.flows } : {}
|
|
15935
16317
|
};
|
|
15936
16318
|
}
|
|
16319
|
+
const present = model;
|
|
16320
|
+
model = {
|
|
16321
|
+
...model,
|
|
16322
|
+
aiInputs: {
|
|
16323
|
+
...typeof present.narrative === "string" ? { notes: notesFor ?? s.aiInputs?.notes } : {},
|
|
16324
|
+
...Array.isArray(present.flows) ? { flows: flowsFor ?? s.aiInputs?.flows } : {}
|
|
16325
|
+
}
|
|
16326
|
+
};
|
|
15937
16327
|
const arch = model;
|
|
15938
16328
|
if (arch.narrative || arch.flows?.length)
|
|
15939
16329
|
markdown = renderArchitecturePage(doku, {
|
|
@@ -15942,7 +16332,11 @@ async function generateDokuInner(brainId, opts = {}) {
|
|
|
15942
16332
|
});
|
|
15943
16333
|
} else if (page.kind === "entry") {
|
|
15944
16334
|
model = { ...page.model, categories };
|
|
15945
|
-
|
|
16335
|
+
const s = stored;
|
|
16336
|
+
if (sameFacts && typeof s?.narrative === "string" && s.narrative.trim()) {
|
|
16337
|
+
model = { ...model, narrative: s.narrative };
|
|
16338
|
+
ok2 = true;
|
|
16339
|
+
} else if (opts.llm && page.polishable) {
|
|
15946
16340
|
try {
|
|
15947
16341
|
const narrative2 = (await opts.llm.complete({
|
|
15948
16342
|
system: ENTRY_NARRATIVE_SYSTEM,
|
|
@@ -15956,15 +16350,16 @@ async function generateDokuInner(brainId, opts = {}) {
|
|
|
15956
16350
|
}
|
|
15957
16351
|
} catch {
|
|
15958
16352
|
}
|
|
15959
|
-
if (!ok2 && sameFacts) {
|
|
15960
|
-
const s = stored;
|
|
15961
|
-
if (typeof s.narrative === "string" && s.narrative.trim())
|
|
15962
|
-
model = { ...model, narrative: s.narrative };
|
|
15963
|
-
}
|
|
15964
16353
|
}
|
|
15965
16354
|
const narrative = model.narrative;
|
|
15966
16355
|
if (narrative)
|
|
15967
16356
|
markdown = renderEntryPage(doku, "brainjar-doku/", narrative);
|
|
16357
|
+
} else if (page.kind === "feature" && sameFacts && !storedCollisions.has(page.slug) && aiComplete("feature", stored)) {
|
|
16358
|
+
const f = withStoredAi(page.model, stored);
|
|
16359
|
+
model = f;
|
|
16360
|
+
markdown = renderFeaturePage(f);
|
|
16361
|
+
if (f.docTitle) page.title = f.docTitle;
|
|
16362
|
+
ok2 = true;
|
|
15968
16363
|
} else if (opts.llm && page.polishable) {
|
|
15969
16364
|
try {
|
|
15970
16365
|
const { flow } = parseFlow(
|
|
@@ -16018,25 +16413,13 @@ async function generateDokuInner(brainId, opts = {}) {
|
|
|
16018
16413
|
hasFlowDiagram: Boolean(diagrams.flowDiagram),
|
|
16019
16414
|
hasFlow: Boolean(diagrams.flow)
|
|
16020
16415
|
};
|
|
16021
|
-
const prompt =
|
|
16022
|
-
|
|
16023
|
-
|
|
16024
|
-
|
|
16025
|
-
|
|
16026
|
-
|
|
16027
|
-
|
|
16028
|
-
] : [],
|
|
16029
|
-
...ctx.hasFlow && !ctx.hasFlowDiagram ? ["- flow: the feature's runtime steps as a simple chain"] : [],
|
|
16030
|
-
...!ctx.hasFlowDiagram && !ctx.hasFlow ? ["- (none)"] : [],
|
|
16031
|
-
"",
|
|
16032
|
-
...sharedLines.length ? [
|
|
16033
|
-
"Documented elsewhere for the same endpoint (proven facts; reuse them, link the page):",
|
|
16034
|
-
...sharedLines,
|
|
16035
|
-
""
|
|
16036
|
-
] : [],
|
|
16037
|
-
"Fact sheet:",
|
|
16038
|
-
page.markdown
|
|
16039
|
-
].join("\n");
|
|
16416
|
+
const prompt = pagePrompt({
|
|
16417
|
+
categories,
|
|
16418
|
+
factSheet: page.markdown,
|
|
16419
|
+
flowDiagramTitle: diagrams.flowDiagram?.title ?? null,
|
|
16420
|
+
hasFlow: ctx.hasFlow,
|
|
16421
|
+
sharedLines
|
|
16422
|
+
});
|
|
16040
16423
|
const keys = categoryKeys.length ? categoryKeys : ["general"];
|
|
16041
16424
|
let synth = parseDocPage(
|
|
16042
16425
|
await opts.llm.complete({
|
|
@@ -16066,17 +16449,20 @@ async function generateDokuInner(brainId, opts = {}) {
|
|
|
16066
16449
|
`doku: blocks dropped (${page.slug}): ${synth.dropped.join("; ")}`
|
|
16067
16450
|
);
|
|
16068
16451
|
if (synth.page) {
|
|
16069
|
-
const
|
|
16452
|
+
const s = stored;
|
|
16453
|
+
const same = s?.title === page.model.title && !storedCollisions.has(page.slug);
|
|
16454
|
+
const title = same && s?.docTitle ? s.docTitle : pickDocTitle(
|
|
16070
16455
|
synth.page.title,
|
|
16071
16456
|
page.title,
|
|
16072
16457
|
page.slug,
|
|
16073
16458
|
takenTitle
|
|
16074
16459
|
);
|
|
16460
|
+
const category = same && s?.category && categoryKeys.includes(s.category) ? s.category : synth.page.category;
|
|
16075
16461
|
const f = {
|
|
16076
16462
|
...model,
|
|
16077
16463
|
presentation: synth.page.presentation,
|
|
16078
16464
|
docTitle: title,
|
|
16079
|
-
category
|
|
16465
|
+
category
|
|
16080
16466
|
};
|
|
16081
16467
|
f.body = pageBody(f);
|
|
16082
16468
|
model = f;
|
|
@@ -16094,17 +16480,7 @@ async function generateDokuInner(brainId, opts = {}) {
|
|
|
16094
16480
|
console.warn(`doku: page synthesis failed (${page.slug}):`, e);
|
|
16095
16481
|
}
|
|
16096
16482
|
if (!ok2 && sameFacts && aiComplete("feature", stored)) {
|
|
16097
|
-
const
|
|
16098
|
-
const fresh = model;
|
|
16099
|
-
const f = {
|
|
16100
|
-
...model,
|
|
16101
|
-
...s.presentation ? { presentation: s.presentation } : {},
|
|
16102
|
-
body: s.body ?? null,
|
|
16103
|
-
docTitle: s.docTitle ?? null,
|
|
16104
|
-
category: s.category ?? null,
|
|
16105
|
-
...!fresh.flow && s.flow ? { flow: s.flow } : {},
|
|
16106
|
-
...!fresh.flowDiagram && s.flowDiagram ? { flowDiagram: s.flowDiagram } : {}
|
|
16107
|
-
};
|
|
16483
|
+
const f = withStoredAi(model, stored);
|
|
16108
16484
|
model = f;
|
|
16109
16485
|
markdown = renderFeaturePage(f);
|
|
16110
16486
|
if (f.docTitle) page.title = f.docTitle;
|
|
@@ -16377,7 +16753,7 @@ var init_doku = __esm({
|
|
|
16377
16753
|
`{"kind":"flow","title":"...","source":"flowDiagram"}: places the feature's existing runtime diagram where it helps the explanation; allowed only when the input lists that diagram as available, at most once.`,
|
|
16378
16754
|
"Composition: explanation and outcome before technical detail. A feature exposing an API documents what it is, why you need it, and each endpoint with its parameters, request and response shape, and error cases, all from the facts only. A user-facing feature explains what is shown and how to use it, with steps only for a real action sequence. A pipeline explains what goes in, what comes out, and when it runs. A configuration-heavy feature tabulates its settings and explains precedence and effects. A feature containing several of these covers each. Mix kinds freely; there is no fixed template and no fixed number of blocks.",
|
|
16379
16755
|
"Length follows the subject: an extensive feature gets several text sections with subheadings, the relationships, prerequisites, special cases, and reasons explained, and a concrete example; a small feature stays short. Never break a coherent explanation into bullet points for layout reasons and never cut it short. Explain a term at its first use; state the concrete consequence for the reader.",
|
|
16380
|
-
"Keep every fact; invent nothing. Missing facts stay missing: leave optional blocks out, and when an indispensable piece of information is not available, say so in one plain sentence addressed to the reader (for example: The exact request path is not documented yet.), never mentioning a fact sheet, an input, or a model.
|
|
16756
|
+
"Keep every fact; invent nothing. Missing facts stay missing: leave optional blocks out, and when an indispensable piece of information is not available, say so in one plain sentence addressed to the reader (for example: The exact request path is not documented yet.), never mentioning a fact sheet, an input, or a model. The change history and the decision list are shown separately below the page: never reproduce them as a list or table. Their content still counts as facts. The page describes the CURRENT state: where a newer history entry or decision added, changed, replaced, or removed something, the text states the current state (for example that a capability no longer exists, or which mechanism replaced another), and the reasons that explain the current design belong in the explanation. When facts conflict, the newer one wins. Never replace a stated fact with what is usual elsewhere. No H1 heading (the title renders above).",
|
|
16381
16757
|
"When the input lists facts documented elsewhere for the SAME endpoint or interface, use them: they are proven, so the endpoint may be shown here as well, and the other page may be linked with a short readable link text in the form [text](page:slug), copying the slug exactly. Never merge pages because their titles look alike.",
|
|
16382
16758
|
PROSE_STYLE
|
|
16383
16759
|
].join(" ");
|
|
@@ -16433,11 +16809,12 @@ var init_doku = __esm({
|
|
|
16433
16809
|
"You read one architecture overview of a software system (module groups with their features and dependencies, plus real file paths) and produce the system's main RUNTIME flows: how a request, session, or piece of data actually moves through the system.",
|
|
16434
16810
|
'Respond ONLY with JSON of exactly this shape: {"flows": [{"slug": "...", "title": "...", "summary": "...", "explanation": "...", "lanes": [{"key": "...", "title": "..."}], "nodes": [{"id": "...", "lane": "...", "title": "...", "subtitle": "...", "kind": "...", "evidence": ["path"]}], "edges": [{"from": "...", "to": "...", "label": "...", "kind": "..."}]}]}. Nothing outside the JSON.',
|
|
16435
16811
|
'"explanation" walks a reader through THIS flow in markdown (2-5 short paragraphs, ### subheadings where they help, at most 3500 characters): what starts it, what each phase does, what comes out. Describe an order only where the edges show it; where paths run in parallel or depend on a gate, say so instead of inventing a sequence. Name the same stations as the nodes.',
|
|
16436
|
-
|
|
16812
|
+
'Produce up to 4 flows, one per MAIN runtime path a newcomer must understand (for example: how input becomes stored data, how a query becomes an answer, how an external agent connects, how background verification runs). Cover every major path the listed features describe and no path they do not: when they describe fewer paths, produce fewer flows, and when no feature is documented at all, respond {"flows": []}.',
|
|
16437
16813
|
"lanes are 3-5 PHASES of the flow in order (intake, processing, storage, ...), titled in plain words. Never folder names.",
|
|
16438
16814
|
"nodes are the runtime components in those phases: 4-10 per flow. kind is exactly one of: actor (human or external agent), surface (UI or endpoint), logic (application work), policy (a gate: auth, quota, validation), action (an outward call such as an LLM or external API), data (a store). title at most 3 words; subtitle at most 3 words (under 24 characters).",
|
|
16439
16815
|
"evidence lists 1-3 file paths copied EXACTLY from the Files section that implement the node. Never invent or modify a path; omit evidence rather than guess.",
|
|
16440
16816
|
'edges carry meaning: label with WHAT flows (at most 3 words, under 20 characters, never counts). kind is "primary" for the main path, "return" for a reply going back, "policy" for a gate outcome, "async" for background work.',
|
|
16817
|
+
"Draw an edge only where the input shows the connection: a listed dependency between the two groups, a feature whose code spans both groups (Features spanning groups), or one feature describing both steps. Never connect components because such systems usually are connected; a path the input does not show stays out of the flow.",
|
|
16441
16818
|
"Ground everything in the listed features and dependencies. Invent nothing."
|
|
16442
16819
|
].join(" ");
|
|
16443
16820
|
FEATURE_FLOW_SYSTEM = [
|