@polderlabs/bizar 3.14.1 → 3.15.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/cli/graph-build-from-cache.mjs +124 -0
- package/cli/graph.mjs +239 -2
- package/package.json +1 -1
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* cli/graph-build-from-cache.mjs
|
|
4
|
+
*
|
|
5
|
+
* One-off script for environments where graphify's semantic-extraction
|
|
6
|
+
* step fails (e.g. no LLM API key + remaining non-code docs that the
|
|
7
|
+
* `.graphifyignore` can't filter) but the AST cache is intact.
|
|
8
|
+
*
|
|
9
|
+
* graphify's pipeline has two stages:
|
|
10
|
+
* 1. AST extraction (local, tree-sitter) — writes per-file JSON to
|
|
11
|
+
* `.bizar/graph/cache/ast/v0.8.46/<hash>.json` regardless of LLM status.
|
|
12
|
+
* 2. Semantic extraction (LLM, optional) — merges per-file nodes into a
|
|
13
|
+
* project-wide graph.
|
|
14
|
+
*
|
|
15
|
+
* If stage 2 aborts, `graph.json` is never written and the dashboard has
|
|
16
|
+
* nothing to render. This script reconstructs `graph.json` directly from
|
|
17
|
+
* the per-file AST cache, then calls `graphify cluster-only` to generate
|
|
18
|
+
* `graph.html` + `GRAPH_REPORT.md`.
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* node cli/graph-build-from-cache.mjs # use .bizar/graph/
|
|
22
|
+
* node cli/graph-build-from-cache.mjs /tmp/other/graph # custom dir
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync } from 'node:fs';
|
|
26
|
+
import { join, resolve } from 'node:path';
|
|
27
|
+
|
|
28
|
+
const graphDirArg = process.argv[2] || '.bizar/graph';
|
|
29
|
+
const graphDir = resolve(graphDirArg);
|
|
30
|
+
|
|
31
|
+
// The cache version directory inside graphify's cache layout. Bumping the
|
|
32
|
+
// graphify version moves the cache; we discover the actual subdir at
|
|
33
|
+
// runtime so we don't hardcode "v0.8.46".
|
|
34
|
+
const cacheAstRoot = join(graphDir, 'cache', 'ast');
|
|
35
|
+
|
|
36
|
+
function findCacheDir() {
|
|
37
|
+
if (!existsSync(cacheAstRoot)) {
|
|
38
|
+
throw new Error(`No AST cache found at ${cacheAstRoot}. Run \`bizar graph build\` first to populate it.`);
|
|
39
|
+
}
|
|
40
|
+
const entries = readdirSync(cacheAstRoot);
|
|
41
|
+
if (entries.length === 0) {
|
|
42
|
+
throw new Error(`AST cache directory ${cacheAstRoot} is empty.`);
|
|
43
|
+
}
|
|
44
|
+
// graphify uses one version directory like v0.8.46. Pick the first.
|
|
45
|
+
return join(cacheAstRoot, entries[0]);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function buildGraphFromCache(cacheDir) {
|
|
49
|
+
const files = readdirSync(cacheDir).filter((f) => f.endsWith('.json'));
|
|
50
|
+
console.log(` Reading ${files.length} AST cache files from ${cacheDir}...`);
|
|
51
|
+
|
|
52
|
+
const nodesById = new Map();
|
|
53
|
+
const edges = [];
|
|
54
|
+
const rawCalls = [];
|
|
55
|
+
|
|
56
|
+
let fileIdx = 0;
|
|
57
|
+
for (const f of files) {
|
|
58
|
+
fileIdx++;
|
|
59
|
+
const raw = JSON.parse(readFileSync(join(cacheDir, f), 'utf8'));
|
|
60
|
+
for (const n of raw.nodes || []) {
|
|
61
|
+
// Later files win on duplicate id (consistent with graphify's
|
|
62
|
+
// dedup behavior — first write keeps the canonical location).
|
|
63
|
+
if (!nodesById.has(n.id)) nodesById.set(n.id, n);
|
|
64
|
+
}
|
|
65
|
+
for (const e of raw.edges || []) edges.push(e);
|
|
66
|
+
for (const rc of raw.raw_calls || []) rawCalls.push(rc);
|
|
67
|
+
if (fileIdx % 50 === 0) {
|
|
68
|
+
console.log(` merged ${fileIdx}/${files.length} files`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Re-key node.community so it matches the dashboard's expected schema.
|
|
73
|
+
// The AST cache uses `community` as a numeric cluster id; we keep it as-is.
|
|
74
|
+
const nodes = Array.from(nodesById.values());
|
|
75
|
+
|
|
76
|
+
const graph = {
|
|
77
|
+
directed: false,
|
|
78
|
+
multigraph: false,
|
|
79
|
+
graph: {},
|
|
80
|
+
nodes,
|
|
81
|
+
links: edges,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
console.log(` Built graph: ${nodes.length} nodes, ${edges.length} edges (raw_calls=${rawCalls.length})`);
|
|
85
|
+
return { graph, rawCalls };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function writeOutputs(graphDir, graph) {
|
|
89
|
+
// graph.json — NetworkX node-link format
|
|
90
|
+
const graphJsonPath = join(graphDir, 'graph.json');
|
|
91
|
+
writeFileSync(graphJsonPath, JSON.stringify(graph, null, 2), 'utf8');
|
|
92
|
+
console.log(` ✓ Wrote ${graphJsonPath}`);
|
|
93
|
+
|
|
94
|
+
// .graphify_analysis.json — graphify's secondary output. cluster-only
|
|
95
|
+
// doesn't strictly require this, but writing it keeps the directory
|
|
96
|
+
// shape consistent with what `graphify .` would have produced.
|
|
97
|
+
const analysis = {
|
|
98
|
+
schema_version: 1,
|
|
99
|
+
generated_at: new Date().toISOString(),
|
|
100
|
+
clusters: [],
|
|
101
|
+
};
|
|
102
|
+
const analysisPath = join(graphDir, '.graphify_analysis.json');
|
|
103
|
+
writeFileSync(analysisPath, JSON.stringify(analysis, null, 2), 'utf8');
|
|
104
|
+
console.log(` ✓ Wrote ${analysisPath}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function main() {
|
|
108
|
+
console.log(` Graph dir: ${graphDir}`);
|
|
109
|
+
if (!existsSync(graphDir)) {
|
|
110
|
+
throw new Error(`Graph dir does not exist: ${graphDir}`);
|
|
111
|
+
}
|
|
112
|
+
const cacheDir = findCacheDir();
|
|
113
|
+
const { graph } = buildGraphFromCache(cacheDir);
|
|
114
|
+
writeOutputs(graphDir, graph);
|
|
115
|
+
console.log('');
|
|
116
|
+
console.log(' Next step: run `bizar graph cluster-only` to generate graph.html.');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
main();
|
|
121
|
+
} catch (err) {
|
|
122
|
+
console.error(`\n Error: ${err.message}\n`);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
package/cli/graph.mjs
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import chalk from 'chalk';
|
|
9
9
|
import { spawnSync } from 'node:child_process';
|
|
10
|
-
import { existsSync, readFileSync, mkdirSync, writeFileSync, statSync } from 'node:fs';
|
|
10
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync, statSync, copyFileSync } from 'node:fs';
|
|
11
11
|
import { join } from 'node:path';
|
|
12
12
|
|
|
13
13
|
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
@@ -15,6 +15,82 @@ import { join } from 'node:path';
|
|
|
15
15
|
export const GRAPH_DIR = '.bizar/graph';
|
|
16
16
|
export const GRAPHIFY_OUT_ENV = 'GRAPHIFY_OUT';
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Env vars graphify recognises for semantic extraction of docs/papers/images.
|
|
20
|
+
* If NONE of these are set, graphify refuses to run on a corpus that contains
|
|
21
|
+
* any non-code file ("A code-only corpus needs no key."). We auto-detect this
|
|
22
|
+
* and write a comprehensive .graphifyignore so a fresh checkout can build
|
|
23
|
+
* the graph offline with no LLM key.
|
|
24
|
+
*/
|
|
25
|
+
const GRAPHIFY_LLM_KEY_ENVS = [
|
|
26
|
+
'GEMINI_API_KEY', 'GOOGLE_API_KEY',
|
|
27
|
+
'MOONSHOT_API_KEY',
|
|
28
|
+
'ANTHROPIC_API_KEY',
|
|
29
|
+
'OPENAI_API_KEY',
|
|
30
|
+
'DEEPSEEK_API_KEY',
|
|
31
|
+
'AZURE_OPENAI_API_KEY',
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Minimal .graphifyignore that restricts graphify to code-only extraction.
|
|
36
|
+
* Written automatically by `bizar graph build` when no LLM key is set.
|
|
37
|
+
* The user can override this file with their own .graphifyignore — we
|
|
38
|
+
* never overwrite a pre-existing one.
|
|
39
|
+
*/
|
|
40
|
+
const CODE_ONLY_GRAPHIFYIGNORE = [
|
|
41
|
+
'# Auto-generated by `bizar graph build` — code-only mode.',
|
|
42
|
+
'# Bizar detected no LLM key, so docs/papers/images cannot be semantically',
|
|
43
|
+
'# extracted. We restrict extraction to code files (processed locally via',
|
|
44
|
+
"# tree-sitter — no API key needed). Delete this file and re-run once you've",
|
|
45
|
+
'# set one of: GEMINI_API_KEY, GOOGLE_API_KEY, MOONSHOT_API_KEY,',
|
|
46
|
+
'# ANTHROPIC_API_KEY, OPENAI_API_KEY, DEEPSEEK_API_KEY, AZURE_OPENAI_API_KEY.',
|
|
47
|
+
'',
|
|
48
|
+
'# Heavy / vendored dirs to skip entirely',
|
|
49
|
+
'node_modules/',
|
|
50
|
+
'.git/',
|
|
51
|
+
'.bizar/',
|
|
52
|
+
'.agents/',
|
|
53
|
+
'dist/',
|
|
54
|
+
'build/',
|
|
55
|
+
'coverage/',
|
|
56
|
+
'',
|
|
57
|
+
'# Agent system prompts — they\'re Markdown but contain YAML frontmatter',
|
|
58
|
+
'# + system instructions, not project documentation. They\'re noise for',
|
|
59
|
+
'# a structural graph and would balloon the doc count to ~1100.',
|
|
60
|
+
'config/agents/',
|
|
61
|
+
'',
|
|
62
|
+
'# Non-code file types — graphify would need an LLM for these',
|
|
63
|
+
'*.md',
|
|
64
|
+
'*.mdx',
|
|
65
|
+
'*.qmd',
|
|
66
|
+
'*.html',
|
|
67
|
+
'*.txt',
|
|
68
|
+
'*.rst',
|
|
69
|
+
'*.pdf',
|
|
70
|
+
'*.png',
|
|
71
|
+
'*.jpg',
|
|
72
|
+
'*.jpeg',
|
|
73
|
+
'*.webp',
|
|
74
|
+
'*.gif',
|
|
75
|
+
'*.mp4',
|
|
76
|
+
'*.mov',
|
|
77
|
+
'*.mp3',
|
|
78
|
+
'*.wav',
|
|
79
|
+
'',
|
|
80
|
+
].join('\n');
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Detect whether any graphify-recognised LLM API key is set in the
|
|
84
|
+
* current process env. Used by `cmdBuild` to decide whether to write
|
|
85
|
+
* the code-only .graphifyignore.
|
|
86
|
+
*/
|
|
87
|
+
function hasLlmKey(env = process.env) {
|
|
88
|
+
return GRAPHIFY_LLM_KEY_ENVS.some((k) => {
|
|
89
|
+
const v = env[k];
|
|
90
|
+
return typeof v === 'string' && v.trim().length > 0;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
18
94
|
// ── Python detection ───────────────────────────────────────────────────────────
|
|
19
95
|
|
|
20
96
|
/**
|
|
@@ -190,15 +266,176 @@ function runGraphify(python, subArgs, extraEnv = {}) {
|
|
|
190
266
|
// ── Subcommand handlers ────────────────────────────────────────────────────────
|
|
191
267
|
|
|
192
268
|
async function cmdBuild(python) {
|
|
269
|
+
// If no LLM key is set, fall back to a code-only build by writing a
|
|
270
|
+
// comprehensive .graphifyignore. We never overwrite a user-provided
|
|
271
|
+
// .graphifyignore — only create the file when it doesn't exist.
|
|
272
|
+
let llmKeyPresent = hasLlmKey();
|
|
273
|
+
if (!llmKeyPresent) {
|
|
274
|
+
const ignorePath = '.graphifyignore';
|
|
275
|
+
if (!existsSync(ignorePath)) {
|
|
276
|
+
writeFileSync(ignorePath, CODE_ONLY_GRAPHIFYIGNORE, 'utf8');
|
|
277
|
+
console.log(chalk.dim(' No LLM API key detected — switching to code-only build.'));
|
|
278
|
+
console.log(chalk.dim(` Wrote ${ignorePath} restricting extraction to code files.`));
|
|
279
|
+
console.log(chalk.dim(' Set GEMINI_API_KEY / OPENAI_API_KEY / etc. and re-run for full docs extraction.\n'));
|
|
280
|
+
} else {
|
|
281
|
+
console.log(chalk.dim(' No LLM API key detected — using existing .graphifyignore for code-only build.\n'));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
193
285
|
console.log(chalk.dim(' Running full graphify pipeline...'));
|
|
194
|
-
|
|
286
|
+
let code = runGraphify(python, ['.']);
|
|
287
|
+
|
|
288
|
+
// Offline code-only fallback — when no LLM key is set, graphify's
|
|
289
|
+
// pre-flight check rejects the build before it can write any output.
|
|
290
|
+
// Even with a dummy key set, the semantic step fails for any non-code
|
|
291
|
+
// docs that escaped the .graphifyignore filter, and graphify refuses
|
|
292
|
+
// to write graph.json. To unblock this case we:
|
|
293
|
+
// 1. Run graphify with a dummy ANTHROPIC_API_KEY so the AST cache
|
|
294
|
+
// gets populated (semantic extraction will fail, that's fine).
|
|
295
|
+
// 2. Reconstruct graph.json from the per-file AST cache.
|
|
296
|
+
// 3. Run cluster-only to generate graph.html + GRAPH_REPORT.md.
|
|
297
|
+
if (code !== 0 && !llmKeyPresent) {
|
|
298
|
+
const cacheAstRoot = join(GRAPH_DIR, 'cache', 'ast');
|
|
299
|
+
const cacheIsPopulated = existsSync(cacheAstRoot) && (
|
|
300
|
+
// graphify creates a versioned subdir on first extract.
|
|
301
|
+
// Check if any version dir contains JSON files.
|
|
302
|
+
(await import('node:fs/promises'))
|
|
303
|
+
.readdir(cacheAstRoot, { withFileTypes: true })
|
|
304
|
+
.then((entries) =>
|
|
305
|
+
entries.some(async (e) => {
|
|
306
|
+
if (!e.isDirectory()) return false;
|
|
307
|
+
const files = await (await import('node:fs/promises')).readdir(join(cacheAstRoot, e.name));
|
|
308
|
+
return files.some((f) => f.endsWith('.json'));
|
|
309
|
+
}),
|
|
310
|
+
)
|
|
311
|
+
.catch(() => false)
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
if (!cacheIsPopulated) {
|
|
315
|
+
console.log(chalk.yellow(' ⚠ No AST cache yet. Running graphify with a dummy key to populate the cache (semantic extraction will fail; that\'s OK)...'));
|
|
316
|
+
// Set a dummy key so graphify gets past its pre-flight check and
|
|
317
|
+
// populates the AST cache. We invoke the graphify binary directly
|
|
318
|
+
// (the user's python3 -m graphify may not have graphify installed;
|
|
319
|
+
// uv-tool installs put a shim at ~/.local/bin/graphify).
|
|
320
|
+
const dummyRes = spawnSync('graphify', ['.'], {
|
|
321
|
+
stdio: 'inherit',
|
|
322
|
+
env: { ...process.env, ANTHROPIC_API_KEY: 'sk-bizar-graph-dummy', GRAPHIFY_OUT: GRAPH_DIR },
|
|
323
|
+
timeout: 0,
|
|
324
|
+
});
|
|
325
|
+
void dummyRes; // exit code doesn't matter — we want the cache, not the merge
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const cacheStillEmpty = !(await (async () => {
|
|
329
|
+
try {
|
|
330
|
+
const e = await (await import('node:fs/promises')).readdir(cacheAstRoot, { withFileTypes: true });
|
|
331
|
+
for (const d of e) {
|
|
332
|
+
if (!d.isDirectory()) continue;
|
|
333
|
+
const f = await (await import('node:fs/promises')).readdir(join(cacheAstRoot, d.name));
|
|
334
|
+
if (f.some((x) => x.endsWith('.json'))) return true;
|
|
335
|
+
}
|
|
336
|
+
return false;
|
|
337
|
+
} catch {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
})());
|
|
341
|
+
|
|
342
|
+
if (!cacheStillEmpty) {
|
|
343
|
+
console.log(chalk.yellow(' ⚠ graphify exited non-zero. Falling back to AST-cache build...'));
|
|
344
|
+
const fb = runCacheFallbackBuild();
|
|
345
|
+
if (fb === 0) code = 0;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
195
349
|
if (code === 0) {
|
|
196
350
|
ensureGitignore(GRAPH_DIR);
|
|
351
|
+
// After extract, run cluster-only to write graph.html + GRAPH_REPORT.md
|
|
352
|
+
// (extract only writes graph.json; cluster-only adds viz + report).
|
|
353
|
+
const graphJson = join(GRAPH_DIR, 'graph.json');
|
|
354
|
+
if (existsSync(graphJson)) {
|
|
355
|
+
console.log(chalk.dim(' Generating graph.html + GRAPH_REPORT.md via cluster-only...'));
|
|
356
|
+
// Pass "." as the cluster-only path so it writes graph.html +
|
|
357
|
+
// GRAPH_REPORT.md to the current directory's graphify-out/.
|
|
358
|
+
// (Passing `.bizar/graph` directly nests the output as
|
|
359
|
+
// `.bizar/graph/.bizar/graph/...` which is wrong.) The explicit
|
|
360
|
+
// --graph overrides the auto-discovery to find graph.json.
|
|
361
|
+
const clusterArgs = ['cluster-only', '.', '--graph', graphJson];
|
|
362
|
+
const clusterCode = runGraphify(python, clusterArgs);
|
|
363
|
+
if (clusterCode !== 0) {
|
|
364
|
+
console.log(chalk.yellow(` ⚠ cluster-only returned ${clusterCode} — graph.json was built but graph.html may be stale.`));
|
|
365
|
+
} else {
|
|
366
|
+
// cluster-only writes to ./graphify-out/ (relative to CWD).
|
|
367
|
+
// Promote the artifacts up to .bizar/graph/ paths the dashboard
|
|
368
|
+
// serves directly.
|
|
369
|
+
const out = join(GRAPH_DIR, 'graphify-out');
|
|
370
|
+
if (!existsSync(out)) {
|
|
371
|
+
// Fallback: graphify may have written to ./graphify-out/ at CWD.
|
|
372
|
+
const cwdOut = join(process.cwd(), 'graphify-out');
|
|
373
|
+
if (existsSync(cwdOut)) {
|
|
374
|
+
promoteFromDir(cwdOut, GRAPH_DIR);
|
|
375
|
+
}
|
|
376
|
+
} else {
|
|
377
|
+
promoteClusterOutputs(GRAPH_DIR);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
197
381
|
console.log(chalk.green(`\n ✓ Graph built → ${GRAPH_DIR}/\n`));
|
|
198
382
|
}
|
|
199
383
|
return code;
|
|
200
384
|
}
|
|
201
385
|
|
|
386
|
+
/**
|
|
387
|
+
* Reconstruct graph.json from graphify's per-file AST cache and run
|
|
388
|
+
* cluster-only to produce graph.html + GRAPH_REPORT.md. Used when the
|
|
389
|
+
* full graphify pipeline fails because some docs need an LLM that we
|
|
390
|
+
* don't have, but the AST cache for code-only files was successfully
|
|
391
|
+
* extracted.
|
|
392
|
+
*
|
|
393
|
+
* Returns the exit code of the overall sequence (0 on success).
|
|
394
|
+
*/
|
|
395
|
+
function runCacheFallbackBuild() {
|
|
396
|
+
// Step 1: merge the per-file AST cache into a single graph.json.
|
|
397
|
+
const scriptPath = new URL(import.meta.url).pathname;
|
|
398
|
+
// cli/graph.mjs sits next to cli/graph-build-from-cache.mjs.
|
|
399
|
+
const cacheScript = scriptPath.replace(/graph\.mjs$/, 'graph-build-from-cache.mjs');
|
|
400
|
+
if (!existsSync(cacheScript)) {
|
|
401
|
+
console.log(chalk.red(` ✗ Cache fallback script not found at ${cacheScript}`));
|
|
402
|
+
return 1;
|
|
403
|
+
}
|
|
404
|
+
const buildRes = spawnSync(process.execPath, [cacheScript, GRAPH_DIR], {
|
|
405
|
+
stdio: 'inherit',
|
|
406
|
+
});
|
|
407
|
+
if (buildRes.status !== 0) {
|
|
408
|
+
return buildRes.status ?? 1;
|
|
409
|
+
}
|
|
410
|
+
// Step 2: cluster-only regenerates graph.html from the merged graph.json.
|
|
411
|
+
return 0;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Copy the named files from `srcDir` up to `dstDir`. Used to hoist
|
|
416
|
+
* cluster-only's graph.html / GRAPH_REPORT.md / graph.json up to the
|
|
417
|
+
* canonical .bizar/graph/ paths the dashboard serves.
|
|
418
|
+
*/
|
|
419
|
+
function promoteFromDir(srcDir, dstDir) {
|
|
420
|
+
for (const name of ['graph.html', 'GRAPH_REPORT.md', 'graph.json']) {
|
|
421
|
+
const src = join(srcDir, name);
|
|
422
|
+
const dst = join(dstDir, name);
|
|
423
|
+
if (existsSync(src)) {
|
|
424
|
+
copyFileSync(src, dst);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* cluster-only writes its outputs to a nested graphify-out/ directory
|
|
431
|
+
* by default. This hoists them up to the standard paths the dashboard
|
|
432
|
+
* serves (.bizar/graph/graph.html + .bizar/graph/GRAPH_REPORT.md).
|
|
433
|
+
*/
|
|
434
|
+
function promoteClusterOutputs(graphDir) {
|
|
435
|
+
const out = join(graphDir, 'graphify-out');
|
|
436
|
+
if (existsSync(out)) promoteFromDir(out, graphDir);
|
|
437
|
+
}
|
|
438
|
+
|
|
202
439
|
async function cmdUpdate(python) {
|
|
203
440
|
console.log(chalk.dim(' Running incremental graph update...'));
|
|
204
441
|
return runGraphify(python, ['.', '--update']);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polderlabs/bizar",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.15.0",
|
|
4
4
|
"description": "Norse-pantheon multi-agent system for opencode — 13 agents across 4 cost tiers with cost-aware routing, plans, and a configurable agent harness.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|