@polderlabs/bizar 3.7.3 → 3.8.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/bin.mjs CHANGED
@@ -28,6 +28,7 @@ import { runInit } from './init.mjs';
28
28
  import { runExport } from './export.mjs';
29
29
  import runPlan from './plan.mjs';
30
30
  import { runUpdate } from './update.mjs';
31
+ import { runGraph } from './graph.mjs';
31
32
  import { ensureSetup, checkSetupStatus } from './bootstrap.mjs';
32
33
 
33
34
  const args = process.argv.slice(2);
@@ -86,6 +87,7 @@ function showHelp() {
86
87
  bizar init Initialize .bizar/ in current project
87
88
  bizar export [target] Export agents/rules to another harness
88
89
  bizar plan <subcommand> Manage visual plans
90
+ bizar graph Per-project knowledge graph (powered by graphify)
89
91
  bizar test-gate Detect & run the project's test suite
90
92
  bizar update Update opencode, bizar, and/or bizar-plugin
91
93
  bizar service Manage the background service daemon
@@ -126,7 +128,29 @@ function showInitHelp() {
126
128
 
127
129
  Description:
128
130
  Detects the project stack, creates .bizar/PROJECT.md and
129
- .bizar/AGENTS_SELF_IMPROVEMENT.md, and installs relevant skills.
131
+ .bizar/AGENTS_SELF_IMPROVEMENT.md, installs relevant skills,
132
+ and builds the per-project knowledge graph in .bizar/graph/
133
+ (requires graphify — pip install graphifyy — otherwise the
134
+ graph step is skipped gracefully).
135
+ `);
136
+ }
137
+
138
+ function showGraphHelp() {
139
+ console.log(`
140
+ bizar graph — Per-project knowledge graph (powered by graphify)
141
+
142
+ Usage:
143
+ bizar graph build # full build of the project graph
144
+ bizar graph update # incremental rebuild
145
+ bizar graph query "<text>" # query the graph
146
+ bizar graph path "<A>" "<B>" # shortest path between concepts
147
+ bizar graph explain "<X>" # all nodes related to a concept
148
+ bizar graph watch # watch for changes and rebuild
149
+ bizar graph status # show graph path, size, node/edge/community counts
150
+ bizar graph install # install graphify + drop OpenCode skill/plugin
151
+
152
+ Requires Python 3.10+ and \`graphify\` (pip install graphifyy).
153
+ Graph data lives in .bizar/graph/ inside this project.
130
154
  `);
131
155
  }
132
156
 
@@ -376,6 +400,9 @@ async function main() {
376
400
  } else if (args[0] === 'init') {
377
401
  if (isHelpRequest) showInitHelp();
378
402
  else await runInit(process.cwd());
403
+ } else if (args[0] === 'graph') {
404
+ if (isHelpRequest) showGraphHelp();
405
+ else await runGraph(args.slice(1));
379
406
  } else if (args[0] === 'export') {
380
407
  if (isHelpRequest) showExportHelp();
381
408
  else await runExport(parseFlag('--target'));
package/cli/graph.mjs ADDED
@@ -0,0 +1,337 @@
1
+ /**
2
+ * cli/graph.mjs
3
+ *
4
+ * `bizar graph` — Per-project knowledge graph powered by graphify.
5
+ * Graph data lives in .bizar/graph/ inside the project.
6
+ */
7
+
8
+ import chalk from 'chalk';
9
+ import { spawnSync } from 'node:child_process';
10
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, statSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+
13
+ // ── Constants ─────────────────────────────────────────────────────────────────
14
+
15
+ export const GRAPH_DIR = '.bizar/graph';
16
+ export const GRAPHIFY_OUT_ENV = 'GRAPHIFY_OUT';
17
+
18
+ // ── Python detection ───────────────────────────────────────────────────────────
19
+
20
+ /**
21
+ * Find a Python 3 interpreter on PATH.
22
+ * Returns the absolute path string, or null if none found.
23
+ */
24
+ export function findPython() {
25
+ // Prefer python3, fall back to python
26
+ for (const name of ['python3', 'python']) {
27
+ try {
28
+ const result = spawnSync(name, ['--version'], { encoding: 'utf8', timeout: 5000 });
29
+ if (result.status === 0 && result.stdout.includes('Python 3')) {
30
+ return name;
31
+ }
32
+ } catch {
33
+ // try next
34
+ }
35
+ }
36
+ return null;
37
+ }
38
+
39
+ /**
40
+ * Check whether graphify is available via `python3 -m graphify --version`.
41
+ * Returns true if available, false otherwise.
42
+ */
43
+ function checkGraphify(python) {
44
+ try {
45
+ const result = spawnSync(python, ['-m', 'graphify', '--version'], {
46
+ encoding: 'utf8',
47
+ timeout: 10000,
48
+ env: { ...process.env },
49
+ });
50
+ return result.status === 0;
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
55
+
56
+ // ── Graph stats parsing ────────────────────────────────────────────────────────
57
+
58
+ /**
59
+ * Parse .bizar/graph/graph.json and return stats.
60
+ * Returns null if the file does not exist or cannot be parsed.
61
+ *
62
+ * Expected NetworkX node-link JSON shape:
63
+ * {
64
+ * "directed": false,
65
+ * "multigraph": false,
66
+ * "graph": {},
67
+ * "nodes": [{ "id": "...", "community": 0, ... }],
68
+ * "links": [{ "source": "...", "target": "...", ... }]
69
+ * }
70
+ */
71
+ export function parseGraphStats(jsonPath) {
72
+ if (!existsSync(jsonPath)) return null;
73
+ try {
74
+ const content = readFileSync(jsonPath, 'utf8');
75
+ const graph = JSON.parse(content);
76
+
77
+ const nodes = Array.isArray(graph.nodes) ? graph.nodes : [];
78
+ const links = Array.isArray(graph.links) ? graph.links : [];
79
+
80
+ // Count unique community values among nodes
81
+ const communitySet = new Set();
82
+ for (const node of nodes) {
83
+ if (node.community !== undefined && node.community !== null) {
84
+ communitySet.add(node.community);
85
+ }
86
+ }
87
+
88
+ const stats = statSync(jsonPath);
89
+
90
+ return {
91
+ nodes: nodes.length,
92
+ edges: links.length,
93
+ communities: communitySet.size,
94
+ lastModified: stats.mtime.toISOString(),
95
+ sizeBytes: stats.size,
96
+ };
97
+ } catch {
98
+ return null;
99
+ }
100
+ }
101
+
102
+ // ── Gitignore guard ────────────────────────────────────────────────────────────
103
+
104
+ const GRAPH_GITIGNORE = `cache/
105
+ .graphify_python
106
+ cost.json
107
+ `;
108
+
109
+ /**
110
+ * Ensure .bizar/graph/.gitignore exists so heavy cache artefacts are not committed.
111
+ * Called after a successful first build.
112
+ */
113
+ function ensureGitignore(graphDir) {
114
+ const gitignorePath = join(graphDir, '.gitignore');
115
+ if (!existsSync(gitignorePath)) {
116
+ writeFileSync(gitignorePath, GRAPH_GITIGNORE, 'utf8');
117
+ }
118
+ }
119
+
120
+ // ── Run helpers ───────────────────────────────────────────────────────────────
121
+
122
+ /**
123
+ * Spawn a graphify command with GRAPHIFY_OUT set to GRAPH_DIR.
124
+ * Streams stdout/stderr to the terminal. Returns the exit code.
125
+ */
126
+ function runGraphify(python, subArgs, extraEnv = {}) {
127
+ const env = { ...process.env, [GRAPHIFY_OUT_ENV]: GRAPH_DIR };
128
+ const result = spawnSync(python, ['-m', 'graphify', ...subArgs], {
129
+ stdio: 'inherit',
130
+ env,
131
+ timeout: 0, // no timeout — user may run long builds
132
+ });
133
+ return result.status ?? 1;
134
+ }
135
+
136
+ // ── Subcommand handlers ────────────────────────────────────────────────────────
137
+
138
+ async function cmdBuild(python) {
139
+ console.log(chalk.dim(' Running full graphify pipeline...'));
140
+ const code = runGraphify(python, ['.']);
141
+ if (code === 0) {
142
+ ensureGitignore(GRAPH_DIR);
143
+ console.log(chalk.green(`\n ✓ Graph built → ${GRAPH_DIR}/\n`));
144
+ }
145
+ return code;
146
+ }
147
+
148
+ async function cmdUpdate(python) {
149
+ console.log(chalk.dim(' Running incremental graph update...'));
150
+ return runGraphify(python, ['.', '--update']);
151
+ }
152
+
153
+ async function cmdQuery(python, query) {
154
+ if (!query) {
155
+ console.log(chalk.red(' Error: query text required.'));
156
+ console.log(chalk.dim(' Usage: bizar graph query "<search term>"\n'));
157
+ return 1;
158
+ }
159
+ return runGraphify(python, ['query', query]);
160
+ }
161
+
162
+ async function cmdPath(python, a, b) {
163
+ if (!a || !b) {
164
+ console.log(chalk.red(' Error: two concept names required.'));
165
+ console.log(chalk.dim(' Usage: bizar graph path "<A>" "<B>"\n'));
166
+ return 1;
167
+ }
168
+ return runGraphify(python, ['path', a, b]);
169
+ }
170
+
171
+ async function cmdExplain(python, concept) {
172
+ if (!concept) {
173
+ console.log(chalk.red(' Error: concept name required.'));
174
+ console.log(chalk.dim(' Usage: bizar graph explain "<X>"\n'));
175
+ return 1;
176
+ }
177
+ return runGraphify(python, ['explain', concept]);
178
+ }
179
+
180
+ async function cmdWatch(python) {
181
+ console.log(chalk.dim(' Starting graphify watch mode (Ctrl-C to stop)...'));
182
+ return runGraphify(python, ['.', '--watch']);
183
+ }
184
+
185
+ async function cmdStatus() {
186
+ const graphJsonPath = join(GRAPH_DIR, 'graph.json');
187
+ const stats = parseGraphStats(graphJsonPath);
188
+
189
+ console.log(chalk.bold.hex('#10b981')('\n ᛗ BIZAR GRAPH STATUS ᛗ\n'));
190
+ console.log(` Path: ${GRAPH_DIR}/`);
191
+
192
+ if (!existsSync(GRAPH_DIR)) {
193
+ console.log(` Exists: ${chalk.yellow('no')} (run \`bizar graph build\` first)`);
194
+ console.log();
195
+ return 0;
196
+ }
197
+
198
+ if (!stats) {
199
+ console.log(` Exists: ${chalk.yellow('no graph.json found')}`);
200
+ console.log();
201
+ return 0;
202
+ }
203
+
204
+ console.log(` Exists: ${chalk.green('yes')}`);
205
+ console.log(` Size: ${(stats.sizeBytes / 1024).toFixed(1)} KB`);
206
+ console.log(` Last built: ${stats.lastModified}`);
207
+ console.log(` Nodes: ${stats.nodes}`);
208
+ console.log(` Edges: ${stats.edges}`);
209
+ console.log(` Communities:${stats.communities}`);
210
+ console.log();
211
+ return 0;
212
+ }
213
+
214
+ async function cmdInstall() {
215
+ const python = findPython();
216
+ if (!python) {
217
+ console.error(chalk.red(' Error: graphify requires Python 3.10+.'));
218
+ console.error(chalk.dim(' Install Python from https://python.org or via your package manager, then re-run.\n'));
219
+ return 1;
220
+ }
221
+
222
+ console.log(chalk.dim(' Installing graphify via pip...\n'));
223
+
224
+ // pip install graphifyy
225
+ console.log(chalk.dim(' $ pip install graphifyy'));
226
+ let result = spawnSync('pip', ['install', 'graphifyy'], { stdio: 'inherit' });
227
+ if (result.status !== 0) {
228
+ console.log(chalk.red(' ✗ pip install graphifyy failed.\n'));
229
+ return 1;
230
+ }
231
+
232
+ // graphify install --platform opencode --project
233
+ console.log(chalk.dim('\n $ graphify install --platform opencode --project'));
234
+ result = spawnSync('graphify', ['install', '--platform', 'opencode', '--project'], {
235
+ stdio: 'inherit',
236
+ });
237
+ if (result.status !== 0) {
238
+ console.log(chalk.red(' ✗ graphify install failed.\n'));
239
+ return 1;
240
+ }
241
+
242
+ console.log(chalk.green('\n ✓ graphify installed and OpenCode plugin configured.\n'));
243
+ return 0;
244
+ }
245
+
246
+ // ── Help ──────────────────────────────────────────────────────────────────────
247
+
248
+ export function showGraphHelp() {
249
+ console.log(`
250
+ ${chalk.bold.hex('#10b981')('bizar graph')} — Per-project knowledge graph (powered by graphify)
251
+
252
+ ${chalk.dim('Usage:')}
253
+ ${chalk.cyan('bizar graph build')} # full build of the project graph
254
+ ${chalk.cyan('bizar graph update')} # incremental rebuild
255
+ ${chalk.cyan('bizar graph query "<text>"')} # query the graph
256
+ ${chalk.cyan('bizar graph path "<A>" "<B>"')} # shortest path between concepts
257
+ ${chalk.cyan('bizar graph explain "<X>"')} # all nodes related to a concept
258
+ ${chalk.cyan('bizar graph watch')} # watch for changes and rebuild
259
+ ${chalk.cyan('bizar graph status')} # show graph path, size, node/edge/community counts
260
+ ${chalk.cyan('bizar graph install')} # install graphify + drop OpenCode skill/plugin
261
+
262
+ ${chalk.dim('Requires:')} Python 3.10+ and \`graphify\` (${chalk.cyan('pip install graphifyy')})
263
+
264
+ ${chalk.dim('Graph data lives in')} ${chalk.cyan('.bizar/graph/')} ${chalk.dim('inside this project.')}
265
+ `);
266
+ }
267
+
268
+ // ── Main entry point ───────────────────────────────────────────────────────────
269
+
270
+ /**
271
+ * `bizar graph` entry point.
272
+ *
273
+ * @param {string[]} args — subcommand + args after 'graph'
274
+ */
275
+ export async function runGraph(args) {
276
+ // Show help when called with no args or --help / -h
277
+ if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
278
+ showGraphHelp();
279
+ return 0;
280
+ }
281
+
282
+ const sub = args[0];
283
+
284
+ // status and install are the only subcommands that don't need graphify
285
+ if (sub !== 'status' && sub !== 'install') {
286
+ // Detect Python
287
+ const python = findPython();
288
+ if (!python) {
289
+ console.error(chalk.red(' Error: graphify requires Python 3.10+.'));
290
+ console.error(chalk.dim(' Install Python from https://python.org or via your package manager, then re-run.\n'));
291
+ return 1;
292
+ }
293
+
294
+ // Detect graphify
295
+ if (!checkGraphify(python)) {
296
+ console.error(chalk.red(' Error: graphify is not installed.'));
297
+ console.error(chalk.dim(' Install it with:'));
298
+ console.error(chalk.cyan(' pip install graphifyy\n'));
299
+ console.error(chalk.dim(' Or run:'));
300
+ console.error(chalk.cyan(' bizar graph install\n'));
301
+ return 1;
302
+ }
303
+
304
+ // Ensure .bizar/ directory exists (graphify will create graph/ inside it)
305
+ mkdirSync(GRAPH_DIR, { recursive: true });
306
+
307
+ switch (sub) {
308
+ case 'build':
309
+ return await cmdBuild(python);
310
+ case 'update':
311
+ return await cmdUpdate(python);
312
+ case 'query':
313
+ return await cmdQuery(python, args.slice(1).join(' '));
314
+ case 'path':
315
+ return await cmdPath(python, args[1], args[2]);
316
+ case 'explain':
317
+ return await cmdExplain(python, args[1]);
318
+ case 'watch':
319
+ return await cmdWatch(python);
320
+ default:
321
+ console.error(chalk.red(` Error: unknown subcommand '${sub}'.\n`));
322
+ showGraphHelp();
323
+ return 1;
324
+ }
325
+ }
326
+
327
+ // subcommands that don't need graphify
328
+ switch (sub) {
329
+ case 'status':
330
+ return await cmdStatus();
331
+ case 'install':
332
+ return await cmdInstall();
333
+ default:
334
+ // unreachable — already caught unknown subcommands above
335
+ return 1;
336
+ }
337
+ }
@@ -0,0 +1,218 @@
1
+ /**
2
+ * cli/graph.test.mjs
3
+ *
4
+ * Tests for the `bizar graph` subcommand.
5
+ * Uses Node's built-in node:test (no external test framework).
6
+ *
7
+ * Covered:
8
+ * - findPython() returns a string when Python is on PATH (or null)
9
+ * - parseGraphStats() returns null for nonexistent files
10
+ * - parseGraphStats() returns correct {nodes, edges, communities} from a fixture
11
+ * - GRAPH_DIR constant is '.bizar/graph'
12
+ * - help output contains all subcommand names
13
+ */
14
+
15
+ import { test, describe } from 'node:test';
16
+ import assert from 'node:assert/strict';
17
+ import { existsSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
18
+ import { join, dirname } from 'node:path';
19
+ import { fileURLToPath } from 'node:url';
20
+
21
+ // Resolve graph.mjs from this test file's location
22
+ const __dirname = dirname(fileURLToPath(import.meta.url));
23
+ const PROJECT_ROOT = join(__dirname, '..');
24
+
25
+ const {
26
+ findPython,
27
+ parseGraphStats,
28
+ showGraphHelp,
29
+ GRAPH_DIR,
30
+ } = await import('./graph.mjs');
31
+
32
+ // ── Constants ─────────────────────────────────────────────────────────────────
33
+
34
+ test('GRAPH_DIR equals ".bizar/graph"', () => {
35
+ assert.equal(GRAPH_DIR, '.bizar/graph');
36
+ });
37
+
38
+ // ── findPython ────────────────────────────────────────────────────────────────
39
+
40
+ describe('findPython()', () => {
41
+ test('returns a string when python3 or python is on PATH', () => {
42
+ const result = findPython();
43
+ // On most dev machines Python is installed; the test documents the expected shape
44
+ if (result !== null) {
45
+ assert.equal(typeof result, 'string');
46
+ assert.ok(result.length > 0);
47
+ }
48
+ });
49
+
50
+ test('returns null when neither python3 nor python is found', () => {
51
+ // We can't easily stub PATH in this test runner without副作用,
52
+ // so we verify the function is deterministic and returns the same value
53
+ // across two calls (proving it doesn't throw or return garbage).
54
+ const first = findPython();
55
+ const second = findPython();
56
+ assert.equal(first, second, 'findPython should be deterministic');
57
+ });
58
+ });
59
+
60
+ // ── parseGraphStats ────────────────────────────────────────────────────────────
61
+
62
+ describe('parseGraphStats()', () => {
63
+ test('returns null for a nonexistent path', () => {
64
+ const result = parseGraphStats('/nonexistent/path/graph.json');
65
+ assert.equal(result, null);
66
+ });
67
+
68
+ test('returns null for a file that is not valid JSON', () => {
69
+ const tmpDir = join(PROJECT_ROOT, '.tmp_graph_test');
70
+ mkdirSync(tmpDir, { recursive: true });
71
+ try {
72
+ const badPath = join(tmpDir, 'graph.json');
73
+ writeFileSync(badPath, '{ not json', 'utf8');
74
+ const result = parseGraphStats(badPath);
75
+ assert.equal(result, null);
76
+ } finally {
77
+ rmSync(tmpDir, { recursive: true });
78
+ }
79
+ });
80
+
81
+ test('returns zeroed stats when graph.json has null nodes/links arrays', () => {
82
+ const tmpDir = join(PROJECT_ROOT, '.tmp_graph_test2');
83
+ mkdirSync(tmpDir, { recursive: true });
84
+ try {
85
+ const graphPath = join(tmpDir, 'graph.json');
86
+ writeFileSync(graphPath, JSON.stringify({ graph: {}, nodes: null, links: null }), 'utf8');
87
+ const result = parseGraphStats(graphPath);
88
+ // null arrays are treated as 0-length — parseGraphStats guards with Array.isArray
89
+ assert.notEqual(result, null);
90
+ assert.equal(result.nodes, 0);
91
+ assert.equal(result.edges, 0);
92
+ assert.equal(result.communities, 0);
93
+ } finally {
94
+ rmSync(tmpDir, { recursive: true });
95
+ }
96
+ });
97
+
98
+ test('returns correct stats from a minimal NetworkX node-link fixture', () => {
99
+ const tmpDir = join(PROJECT_ROOT, '.tmp_graph_test3');
100
+ mkdirSync(tmpDir, { recursive: true });
101
+ try {
102
+ const graphPath = join(tmpDir, 'graph.json');
103
+
104
+ // NetworkX node-link JSON with 4 nodes, 3 links, 2 communities
105
+ const fixture = {
106
+ directed: false,
107
+ multigraph: false,
108
+ graph: {},
109
+ nodes: [
110
+ { id: 'alpha', community: 0 },
111
+ { id: 'beta', community: 0 },
112
+ { id: 'gamma', community: 1 },
113
+ { id: 'delta', community: 1 },
114
+ ],
115
+ links: [
116
+ { source: 'alpha', target: 'beta' },
117
+ { source: 'beta', target: 'gamma' },
118
+ { source: 'gamma', target: 'delta' },
119
+ ],
120
+ };
121
+
122
+ writeFileSync(graphPath, JSON.stringify(fixture), 'utf8');
123
+
124
+ const stats = parseGraphStats(graphPath);
125
+
126
+ assert.notEqual(stats, null);
127
+ assert.equal(stats.nodes, 4, 'should count 4 nodes');
128
+ assert.equal(stats.edges, 3, 'should count 3 links');
129
+ assert.equal(stats.communities, 2, 'should detect 2 unique communities (0 and 1)');
130
+ assert.ok(typeof stats.lastModified === 'string', 'lastModified should be ISO string');
131
+ assert.ok(typeof stats.sizeBytes === 'number', 'sizeBytes should be a number');
132
+ assert.ok(stats.sizeBytes > 0, 'sizeBytes should be > 0 for a real file');
133
+ } finally {
134
+ rmSync(tmpDir, { recursive: true });
135
+ }
136
+ });
137
+
138
+ test('handles nodes without a community field gracefully', () => {
139
+ const tmpDir = join(PROJECT_ROOT, '.tmp_graph_test4');
140
+ mkdirSync(tmpDir, { recursive: true });
141
+ try {
142
+ const graphPath = join(tmpDir, 'graph.json');
143
+ const fixture = {
144
+ nodes: [
145
+ { id: 'orphan' }, // no community field
146
+ { id: 'solo', community: 0 },
147
+ { id: 'alone', community: 1 },
148
+ ],
149
+ links: [],
150
+ };
151
+ writeFileSync(graphPath, JSON.stringify(fixture), 'utf8');
152
+ const stats = parseGraphStats(graphPath);
153
+ assert.notEqual(stats, null);
154
+ assert.equal(stats.communities, 2, 'should count communities 0 and 1 only');
155
+ } finally {
156
+ rmSync(tmpDir, { recursive: true });
157
+ }
158
+ });
159
+ });
160
+
161
+ // ── Help output ───────────────────────────────────────────────────────────────
162
+
163
+ describe('showGraphHelp()', () => {
164
+ test('outputs all subcommand names', () => {
165
+ // Capture stdout
166
+ let output = '';
167
+ const originalLog = console.log;
168
+ console.log = (msg) => { output += msg + '\n'; };
169
+
170
+ try {
171
+ showGraphHelp();
172
+ } finally {
173
+ console.log = originalLog;
174
+ }
175
+
176
+ // Assert each expected subcommand keyword appears in the help text
177
+ const subcommands = ['build', 'update', 'query', 'path', 'explain', 'watch', 'status', 'install'];
178
+ for (const cmd of subcommands) {
179
+ assert.ok(
180
+ output.includes(cmd),
181
+ `help output should contain subcommand "${cmd}"`
182
+ );
183
+ }
184
+ });
185
+
186
+ test('mentions the GRAPH_DIR path', () => {
187
+ let output = '';
188
+ const originalLog = console.log;
189
+ console.log = (msg) => { output += msg + '\n'; };
190
+
191
+ try {
192
+ showGraphHelp();
193
+ } finally {
194
+ console.log = originalLog;
195
+ }
196
+
197
+ assert.ok(output.includes('.bizar/graph'), 'help should mention the graph directory');
198
+ });
199
+
200
+ test('mentions graphify pip install command', () => {
201
+ let output = '';
202
+ const originalLog = console.log;
203
+ console.log = (msg) => { output += msg + '\n'; };
204
+
205
+ try {
206
+ showGraphHelp();
207
+ } finally {
208
+ console.log = originalLog;
209
+ }
210
+
211
+ assert.ok(
212
+ output.includes('pip install graphifyy'),
213
+ 'help should mention pip install graphifyy'
214
+ );
215
+ });
216
+ });
217
+
218
+ console.log(' graph.mjs tests loaded — run with: node --test cli/graph.test.mjs');
package/cli/init.mjs CHANGED
@@ -150,6 +150,41 @@ ${stack.runner ? `- Dev: \`${stack.runner}\`` : ''}
150
150
  console.log(chalk.green(` ✓ Created ${siPath}`));
151
151
  }
152
152
 
153
+ // Build per-project knowledge graph (graphify -> .bizar/graph/)
154
+ // Soft step: never fails init. If graphify is missing or build errors,
155
+ // the user can retry manually with `bizar graph build`.
156
+ console.log(chalk.bold('\n--- Graph ---\n'));
157
+ const detectGraphify = spawnSync('python3', ['-c', 'import graphify; print(graphify.__version__)'], {
158
+ cwd,
159
+ encoding: 'utf8',
160
+ timeout: 5000,
161
+ });
162
+ const graphifyAvailable = detectGraphify.status === 0 && (detectGraphify.stdout || '').trim().length > 0;
163
+
164
+ if (!graphifyAvailable) {
165
+ console.log(chalk.yellow(' graphify not detected — skipping project graph build.'));
166
+ console.log(chalk.dim(' Install with: pip install graphifyy (or pipx install graphifyy)'));
167
+ console.log(chalk.dim(' Then re-run: bizar graph build'));
168
+ console.log(chalk.dim(' The graph will land in .bizar/graph/ inside this project.'));
169
+ } else {
170
+ console.log(chalk.dim(' Building project knowledge graph (.bizar/graph/)...'));
171
+ // npx resolves "bizar" via local package.json bin field (or global install).
172
+ // Fallback for environments without global bizar: node <repo>/cli/bin.mjs graph build
173
+ const buildResult = spawnSync('npx', ['bizar', 'graph', 'build'], {
174
+ cwd,
175
+ stdio: 'inherit',
176
+ timeout: 5 * 60 * 1000,
177
+ });
178
+ if (buildResult.status === 0) {
179
+ console.log(chalk.green(' ✓ Graph built at .bizar/graph/ — query with: bizar graph query "<concept>"'));
180
+ } else {
181
+ const code = buildResult.status !== null ? buildResult.status : (buildResult.signal || '?');
182
+ console.log(chalk.yellow(` Graph build failed (exit ${code}). You can retry manually:`));
183
+ console.log(chalk.dim(' bizar graph build'));
184
+ console.log(chalk.dim(' The graph will land in .bizar/graph/ inside this project.'));
185
+ }
186
+ }
187
+
153
188
  console.log(chalk.dim('\n Project initialized. Run `@frigg` to ask questions about the codebase.\n'));
154
189
  return true;
155
190
  }
package/cli/install.mjs CHANGED
@@ -292,6 +292,9 @@ export async function runInstaller() {
292
292
  }
293
293
  }
294
294
 
295
+ // ── Post-install: graphify ──
296
+ await promptGraphifyInstall();
297
+
295
298
  // ── Post-install ──
296
299
  console.log(chalk.dim('\n Odin watches. The Pantheon awaits. ᛟ\n'));
297
300
  }
@@ -386,6 +389,61 @@ async function promptAndInstallOptional() {
386
389
  }
387
390
  }
388
391
 
392
+ async function promptGraphifyInstall() {
393
+ const { spawnSync } = await import('node:child_process');
394
+
395
+ console.log();
396
+ sectionHeading('Knowledge Graph (graphify)');
397
+
398
+ const detect = spawnSync('python3', ['-c', 'import graphify; print(graphify.__version__)'], {
399
+ cwd: process.cwd(),
400
+ encoding: 'utf8',
401
+ timeout: 5000,
402
+ });
403
+ const graphifyInstalled = detect.status === 0 && (detect.stdout || '').trim().length > 0;
404
+
405
+ if (graphifyInstalled) {
406
+ console.log(chalk.green(' graphify already installed — skipping.'));
407
+ return;
408
+ }
409
+
410
+ // Non-interactive: no TTY means we can't ask, so just print the hint
411
+ if (!stdin.isTTY || !stdout.isTTY) {
412
+ console.log(chalk.dim(' graphify not detected. Install later with:'));
413
+ console.log(chalk.dim(' pip install graphifyy'));
414
+ console.log(chalk.dim(' pipx install graphifyy'));
415
+ console.log(chalk.dim(' uv tool install graphifyy'));
416
+ console.log(chalk.dim(' Then run `bizar graph build` to populate .bizar/graph/.'));
417
+ return;
418
+ }
419
+
420
+ console.log(chalk.dim(' graphify not detected. graphify powers per-project knowledge graphs (bizar graph build).'));
421
+ console.log(chalk.dim(' Install with one of:'));
422
+ console.log(chalk.dim(' pip install graphifyy # user-site or venv'));
423
+ console.log(chalk.dim(' pipx install graphifyy # isolated CLI tool (recommended on macOS)'));
424
+ console.log(chalk.dim(' uv tool install graphifyy # via uv'));
425
+
426
+ const rl = createInterface({ input: stdin, output: stdout });
427
+ try {
428
+ const answer = (await rl.question(' Install graphify now via pip? [Y/n]: ')).trim().toLowerCase();
429
+ rl.close();
430
+
431
+ if (answer === '' || answer.startsWith('y')) {
432
+ const result = spawnSync('pip', ['install', 'graphifyy'], { stdio: 'inherit', timeout: 120000 });
433
+ if (result.status === 0) {
434
+ console.log(chalk.green(' graphify installed. Try: bizar graph build'));
435
+ } else {
436
+ console.log(chalk.yellow(` pip install failed (exit ${result.status}). You can install manually later — see options above.`));
437
+ }
438
+ } else {
439
+ console.log(chalk.dim(' Skipped. Install manually with one of the commands above when ready.'));
440
+ }
441
+ } catch {
442
+ rl.close();
443
+ console.log(chalk.dim(' Skipped.'));
444
+ }
445
+ }
446
+
389
447
  export async function runPostInstall() {
390
448
  // Skip interactive prompts in CI / non-TTY environments
391
449
  if (!process.env.BIZAR_SKIP_OPTIONAL_INSTALLS) {
package/config/AGENTS.md CHANGED
@@ -283,6 +283,36 @@ The Hindsight MCP server is already configured. All agents interact with it thro
283
283
 
284
284
  ---
285
285
 
286
+ ## Graph Query (bizar graph)
287
+
288
+ Bizar integrates [graphify](https://github.com/safishamsi/graphify) for per-project knowledge graphs. When investigating a Bizar project, **query the graph before grepping raw files** — it's faster and surfaces structural relationships grep can't see.
289
+
290
+ ### Where the graph lives
291
+ `.bizar/graph/` inside the project (git-trackable JSON + Markdown; cache and per-machine interpreter path are gitignored).
292
+
293
+ ### How to query
294
+ From the project root, the user (or heimdall via `/init` or any other agent prompted by Odin) can run:
295
+ - `bizar graph status` — confirm the graph exists; print node/edge/community counts
296
+ - `bizar graph query "<concept>"` — find nodes related to a concept (BFS traversal)
297
+ - `bizar graph path "<A>" "<B>"` — shortest path between two concepts
298
+ - `bizar graph explain "<X>"` — all nodes related to X
299
+ - `bizar graph update` — incremental rebuild after editing source files
300
+ - `bizar graph build` — full rebuild (overwrites existing graph)
301
+ - `bizar graph watch` — foreground watcher (Ctrl-C to stop)
302
+
303
+ ### When to use the graph
304
+ - Before reading a large file: `bizar graph explain "<module-name>"` to see what calls/uses it
305
+ - When mapping unfamiliar code: `bizar graph query "<feature>"` to find related concepts
306
+ - When debugging cross-module interactions: `bizar graph path "<symptom>" "<root-cause>"`
307
+ - Before grep: `bizar graph query "<term>"` first — the graph may already point you to the right file
308
+
309
+ ### When NOT to use the graph
310
+ - The graph is stale (run `bizar graph update` first)
311
+ - graphify is not installed (init skipped graph step; user can install with `pip install graphifyy` then `bizar graph build`)
312
+ - The question is about runtime behavior, not source structure
313
+
314
+ ---
315
+
286
316
  ---
287
317
 
288
318
  ## General Agent Baseline — Always-On Behavior
@@ -548,3 +578,26 @@ For Bizar-internal claims (citing files, lines, tool results), use file:line ref
548
578
 
549
579
  This baseline covers: identity, refusal, tone, formatting, lists, user wellbeing, evenhandedness, mistakes, knowledge cutoff and research-first, MCP servers and skills, mandatory skill-read, file creation, file handling, search, copyright, harmful content, citations, images, memory privacy, execution, clarification, and communication. Every Bizar agent — Odin, Frigg, Vör, Mimir, Heimdall, Hermod, Thor, Baldr, Tyr, Vidarr, Forseti — must follow it.
550
580
 
581
+ ---
582
+
583
+ ## Parallel Execution Awareness
584
+
585
+ You may be dispatched by Odin as one of several agents running concurrently against the same working directory and the same git repository. Your sibling agents **cannot see you** and you **cannot see them**. Without discipline this leads to silent file overwrites, `.git/index.lock` collisions, lockfile corruption, and lost work.
586
+
587
+ ### Hard rules when you have siblings (Odin tells you in the prompt)
588
+
589
+ 1. **File scope is sacred.** Odin assigns you a scope. Only modify files inside it. If you need to touch something outside, STOP and report — do not improvise.
590
+ 2. **No write-level git.** `git commit`, `push`, `merge`, `rebase`, `reset`, `clean`, `stash`, branch-switching `checkout`, and `pull --rebase` are FORBIDDEN for every agent except @hermod. Use `git status`, `git diff`, `git log`, and `git add` (scope files only) for context.
591
+ 3. **Detect conflicts before they happen.** Before writing a file, run `git diff --name-only` and confirm the file is not in a sibling's scope. If it has changed since you started, STOP and report.
592
+ 4. **`.git/index.lock` is a sibling's signal.** If you see it, wait 2-3 seconds and retry. If it persists, STOP and report. Do not delete the lock file.
593
+ 5. **Lockfiles and root configs are shared.** `package.json`, `package-lock.json`, `tsconfig.json`, `vite.config.*`, `Dockerfile`, CI configs — only ONE agent in a batch should touch these. If Odin did not assign them to you, treat as READ-ONLY.
594
+ 6. **Report parallel context in your final summary.** State "siblings: ..." and any conflicts observed.
595
+
596
+ ### Default behavior when Odin does NOT mention siblings
597
+
598
+ - You may work normally.
599
+ - Still avoid `git commit`/`push`/`merge`/`rebase`/`reset`/`clean`/`stash` unless explicitly asked. Default to read-only git unless the user/Odin explicitly requests a write operation. When in doubt, leave git work to @hermod.
600
+
601
+ ### Why this exists
602
+ The harness shares one `.git/` directory across all parallel sessions. Two simultaneous `git commit` calls race on the index lock. Two agents writing the same file = silent last-writer-wins data loss. Discipline now is cheaper than recovery later.
603
+
@@ -160,6 +160,30 @@ The injected message you will see is exactly one of:
160
160
  - `[loop guard: 8 identical calls to <tool>]. Consider using the task tool to report back to your parent with what you've learned and what you need.`
161
161
  - An error containing: `Loop protection: 12 identical calls to <tool>. Use task to escalate.`
162
162
 
163
+ ## Parallel Execution
164
+
165
+ You may be dispatched alongside sibling agents working on the same repository at the same time. The shared `AGENTS.md` baseline contains the universal rules — read those first. This section adds role-specific guidance.
166
+
167
+ ### When Odin tells you about siblings in your prompt
168
+ - You will receive a `## PARALLEL EXECUTION CONTEXT` block listing your siblings and your file scope.
169
+ - Treat your scope as a hard boundary. Files outside your scope are READ-ONLY.
170
+ - If Odin did not give you a scope, default to: write nothing, return a clarifying question to Odin.
171
+
172
+ ### Git — your specific rules
173
+ - ALLOWED: `git status`, `git diff`, `git log`, `git branch --list`, `git add` (scope files only)
174
+ - FORBIDDEN: `git commit`, `git push`, `git merge`, `git rebase`, `git reset`, `git clean`, `git stash`, branch-switching `checkout`, `pull --rebase`
175
+ - If a task seems to require a forbidden operation, report it back to Odin in your final summary — do not improvise. Only @hermod performs write-level git.
176
+ - If you hit `.git/index.lock`, wait 2-3s and retry. If it persists, STOP and report.
177
+
178
+ ### Pre-write checklist (before every `write` / `edit` call)
179
+ 1. Is the file inside the scope Odin gave me? If not, STOP.
180
+ 2. Has this file changed since I started? (`git diff --name-only <file>`) If yes, STOP — a sibling may have written it.
181
+ 3. Is this a lockfile or root config (`package.json`, `package-lock.json`, `tsconfig.json`, `vite.config.*`, `Dockerfile`, CI)? If yes, only proceed if Odin explicitly assigned it to you.
182
+ 4. Proceed.
183
+
184
+ ### Reporting
185
+ End your final summary with: `Siblings: <list>. Conflicts: <list or "none">. Git ops performed: <list or "none">.`
186
+
163
187
  ---
164
188
 
165
189
  ## Always-On Behavior Baseline
@@ -124,6 +124,15 @@ Be professional and concise. Do not write long essays for every action.
124
124
  - One sentence of context beats three paragraphs of preamble.
125
125
  - Match the user's register: if they write briefly, reply briefly. If they want depth, they will ask.
126
126
 
127
+ ## Parallel Execution
128
+
129
+ You may be invoked alongside other audit agents (parallel reviews of different files) or alongside implementation agents. The shared `AGENTS.md` baseline rules apply.
130
+
131
+ ### Your rules
132
+ - You are AUDIT-ONLY. You MUST NOT modify source files, write to `.bizar/`, or run write-level git.
133
+ - If running alongside an implementation agent and you need to read a file it is currently editing, do not block on `.git/index.lock` — just read the file directly.
134
+ - Report any active sibling agents in your final summary so Odin knows the audit was concurrent.
135
+
127
136
  ---
128
137
 
129
138
  ## Always-On Behavior Baseline
@@ -169,6 +169,30 @@ Be professional and concise. Do not write long essays for every action.
169
169
  - One sentence of context beats three paragraphs of preamble.
170
170
  - Match the user's register: if they write briefly, reply briefly. If they want depth, they will ask.
171
171
 
172
+ ## Parallel Execution
173
+
174
+ You may be dispatched alongside sibling agents working on the same repository at the same time. The shared `AGENTS.md` baseline contains the universal rules — read those first. This section adds role-specific guidance.
175
+
176
+ ### When Odin tells you about siblings in your prompt
177
+ - You will receive a `## PARALLEL EXECUTION CONTEXT` block listing your siblings and your file scope.
178
+ - Treat your scope as a hard boundary. Files outside your scope are READ-ONLY.
179
+ - If Odin did not give you a scope, default to: write nothing, return a clarifying question to Odin.
180
+
181
+ ### Git — your specific rules
182
+ - ALLOWED: `git status`, `git diff`, `git log`, `git branch --list`, `git add` (scope files only)
183
+ - FORBIDDEN: `git commit`, `git push`, `git merge`, `git rebase`, `git reset`, `git clean`, `git stash`, branch-switching `checkout`, `pull --rebase`
184
+ - If a task seems to require a forbidden operation, report it back to Odin in your final summary — do not improvise. Only @hermod performs write-level git.
185
+ - If you hit `.git/index.lock`, wait 2-3s and retry. If it persists, STOP and report.
186
+
187
+ ### Pre-write checklist (before every `write` / `edit` call)
188
+ 1. Is the file inside the scope Odin gave me? If not, STOP.
189
+ 2. Has this file changed since I started? (`git diff --name-only <file>`) If yes, STOP — a sibling may have written it.
190
+ 3. Is this a lockfile or root config (`package.json`, `package-lock.json`, `tsconfig.json`, `vite.config.*`, `Dockerfile`, CI)? If yes, only proceed if Odin explicitly assigned it to you.
191
+ 4. Proceed.
192
+
193
+ ### Reporting
194
+ End your final summary with: `Siblings: <list>. Conflicts: <list or "none">. Git ops performed: <list or "none">.`
195
+
172
196
  ---
173
197
 
174
198
  ## Always-On Behavior Baseline
@@ -156,6 +156,26 @@ When dispatched for a `/pr-review`:
156
156
 
157
157
  You have `gh` access — use it to fetch PR diffs and post comments.
158
158
 
159
+ ## Parallel Execution — Multi-Agent Integration
160
+
161
+ You may run while implementation agents (Thor, Tyr, Heimdall, Vidarr, Mimir, Baldr) are mid-task. Your job is to integrate their work safely.
162
+
163
+ ### Before any write-level git operation (commit, merge, rebase, push, PR)
164
+ 1. Run `git status` and `git diff --stat` to see the working tree state.
165
+ 2. Identify which files are staged/modified and which agent likely owns each (Odin's prompt told you, or infer from `chore:`, `feat(scope):`, file paths).
166
+ 3. If uncommitted work spans multiple agents' scopes, stage deliberately — `git add <specific files>` not `git add .`. Never `git add -A`.
167
+ 4. If `git status` shows work that does NOT match the scope Odin assigned to you, STOP and report — that work belongs to a sibling agent and you must integrate it deliberately, not roll it into your commit.
168
+ 5. If `.git/index.lock` exists, wait 2-3s and retry. If it persists, STOP and report — a sibling is mid-write.
169
+
170
+ ### Commit discipline for parallel work
171
+ - Commit messages should reference contributing agents: `feat(scope): description [co-authored-by: @thor, @tyr]` or use a multi-line body listing the agent contributions.
172
+ - Use a single commit per logical unit. Do NOT batch unrelated agents' work into one mega-commit.
173
+ - Never force-push to a branch a sibling may also be pushing to.
174
+
175
+ ### Conflict handling
176
+ - If a rebase or merge encounters conflicts on a file that was modified by a parallel agent (check the file path against the scope list Odin gave you), STOP and report — that resolution is Odin's call, not yours.
177
+ - If you find `.git/index.lock` held by a sibling (waiting did not help), report the conflict and stop.
178
+
159
179
  ---
160
180
 
161
181
  ## Always-On Behavior Baseline
@@ -127,6 +127,30 @@ Be professional and concise. Do not write long essays for every action.
127
127
  - One sentence of context beats three paragraphs of preamble.
128
128
  - Match the user's register: if they write briefly, reply briefly. If they want depth, they will ask.
129
129
 
130
+ ## Parallel Execution
131
+
132
+ You may be dispatched alongside sibling agents working on the same repository at the same time. The shared `AGENTS.md` baseline contains the universal rules — read those first. This section adds role-specific guidance.
133
+
134
+ ### When Odin tells you about siblings in your prompt
135
+ - You will receive a `## PARALLEL EXECUTION CONTEXT` block listing your siblings and your file scope.
136
+ - Treat your scope as a hard boundary. Files outside your scope are READ-ONLY.
137
+ - If Odin did not give you a scope, default to: write nothing, return a clarifying question to Odin.
138
+
139
+ ### Git — your specific rules
140
+ - ALLOWED: `git status`, `git diff`, `git log`, `git branch --list`, `git add` (scope files only)
141
+ - FORBIDDEN: `git commit`, `git push`, `git merge`, `git rebase`, `git reset`, `git clean`, `git stash`, branch-switching `checkout`, `pull --rebase`
142
+ - If a task seems to require a forbidden operation, report it back to Odin in your final summary — do not improvise. Only @hermod performs write-level git.
143
+ - If you hit `.git/index.lock`, wait 2-3s and retry. If it persists, STOP and report.
144
+
145
+ ### Pre-write checklist (before every `write` / `edit` call)
146
+ 1. Is the file inside the scope Odin gave me? If not, STOP.
147
+ 2. Has this file changed since I started? (`git diff --name-only <file>`) If yes, STOP — a sibling may have written it.
148
+ 3. Is this a lockfile or root config (`package.json`, `package-lock.json`, `tsconfig.json`, `vite.config.*`, `Dockerfile`, CI)? If yes, only proceed if Odin explicitly assigned it to you.
149
+ 4. Proceed.
150
+
151
+ ### Reporting
152
+ End your final summary with: `Siblings: <list>. Conflicts: <list or "none">. Git ops performed: <list or "none">.`
153
+
130
154
  ---
131
155
 
132
156
  ## Always-On Behavior Baseline
@@ -243,6 +243,51 @@ You MUST use **per-project banks** — never the default bank for project work.
243
243
  - Tag memories with `project:<repo-name>`
244
244
  - Create or update mental models for sustained project context
245
245
 
246
+ ## Parallel Dispatch Coordination
247
+
248
+ When you dispatch 2+ agents in parallel via `task` or `bizar_spawn_background`, each subagent opens its own session but **shares the same working directory and `.git/` directory**. They cannot see each other. Without explicit context they will collide on file writes and git operations.
249
+
250
+ ### Pre-dispatch checklist (MANDATORY before any parallel `task` call)
251
+ - [ ] Each subagent's **file scope is disjoint** — no two agents edit the same file or directory
252
+ - [ ] Lockfiles, `package.json`, root configs, and shared infra files (`tsconfig.json`, `vite.config.*`, `Dockerfile`, CI files) are assigned to ONE agent or marked READ-ONLY for everyone else
253
+ - [ ] You have not assigned any subagent `bash: allow` PLUS a write-level git task in the same batch (Hermod is the only git writer)
254
+ - [ ] You have named each subagent's scope in plain English (e.g. "Thor owns `src/api/`, Tyr owns `src/core/`")
255
+
256
+ ### Sibling-awareness block (PREPEND to every parallel subagent prompt)
257
+
258
+ Every prompt you send to a parallel subagent must start with this block, with the `{...}` placeholders filled in:
259
+
260
+ ```
261
+ ## PARALLEL EXECUTION CONTEXT
262
+
263
+ You are running alongside sibling agents in the same working directory and the same git repository. They cannot see you. You cannot see them. Follow these rules strictly.
264
+
265
+ ### Your siblings (running concurrently)
266
+ - **{sibling_agent_1}** ({sibling_1_scope})
267
+ - **{sibling_agent_2}** ({sibling_2_scope})
268
+ - ... (add lines as needed)
269
+
270
+ ### Your scope (files you MAY create or modify)
271
+ {comma_separated_paths_or_globs}
272
+
273
+ ### Sibling scopes (READ-ONLY for you — do NOT modify, even if you think they need it)
274
+ {comma_separated_paths_or_globs_for_each_sibling}
275
+
276
+ ### Git coordination
277
+ - ALLOWED: `git status`, `git diff`, `git log`, `git branch --list`, `git add` (only for files inside YOUR scope)
278
+ - FORBIDDEN: `git commit`, `git push`, `git merge`, `git rebase`, `git reset`, `git clean`, `git stash`, `git checkout` to switch branches, `git pull --rebase`
279
+ - If you need a forbidden operation, STOP and report back to Odin in your final summary. Only @hermod performs write-level git operations.
280
+ - If you encounter `.git/index.lock` existing, wait briefly and retry — a sibling is mid-write. If it persists, STOP and report.
281
+
282
+ ### Conflict detection
283
+ - Before each `write` or `edit`, if the target file is in a sibling's scope, STOP and report.
284
+ - If a file in your scope has been modified by another agent since you started (check `git diff --name-only` against your starting state), STOP and report — do not overwrite.
285
+ - Use the shared `AGENTS.md` baseline "Parallel Execution Awareness" section for full rules.
286
+ ```
287
+
288
+ ### Sequential fallback
289
+ If you cannot decompose into disjoint file scopes (e.g. the task is genuinely monolithic), do NOT parallelize — dispatch a single agent. Parallelism is a tool, not a religion.
290
+
246
291
  ## Background Agents (Asynchronous Work)
247
292
 
248
293
  When a sub-task can run independently, spawn it as a **background agent** instead of using the synchronous `task` tool. The main conversation continues while the background work progresses.
@@ -109,6 +109,30 @@ Be professional and concise. Do not write long essays for every action.
109
109
  - One sentence of context beats three paragraphs of preamble.
110
110
  - Match the user's register: if they write briefly, reply briefly. If they want depth, they will ask.
111
111
 
112
+ ## Parallel Execution
113
+
114
+ You may be dispatched alongside sibling agents working on the same repository at the same time. The shared `AGENTS.md` baseline contains the universal rules — read those first. This section adds role-specific guidance.
115
+
116
+ ### When Odin tells you about siblings in your prompt
117
+ - You will receive a `## PARALLEL EXECUTION CONTEXT` block listing your siblings and your file scope.
118
+ - Treat your scope as a hard boundary. Files outside your scope are READ-ONLY.
119
+ - If Odin did not give you a scope, default to: write nothing, return a clarifying question to Odin.
120
+
121
+ ### Git — your specific rules
122
+ - ALLOWED: `git status`, `git diff`, `git log`, `git branch --list`, `git add` (scope files only)
123
+ - FORBIDDEN: `git commit`, `git push`, `git merge`, `git rebase`, `git reset`, `git clean`, `git stash`, branch-switching `checkout`, `pull --rebase`
124
+ - If a task seems to require a forbidden operation, report it back to Odin in your final summary — do not improvise. Only @hermod performs write-level git.
125
+ - If you hit `.git/index.lock`, wait 2-3s and retry. If it persists, STOP and report.
126
+
127
+ ### Pre-write checklist (before every `write` / `edit` call)
128
+ 1. Is the file inside the scope Odin gave me? If not, STOP.
129
+ 2. Has this file changed since I started? (`git diff --name-only <file>`) If yes, STOP — a sibling may have written it.
130
+ 3. Is this a lockfile or root config (`package.json`, `package-lock.json`, `tsconfig.json`, `vite.config.*`, `Dockerfile`, CI)? If yes, only proceed if Odin explicitly assigned it to you.
131
+ 4. Proceed.
132
+
133
+ ### Reporting
134
+ End your final summary with: `Siblings: <list>. Conflicts: <list or "none">. Git ops performed: <list or "none">.`
135
+
112
136
  ---
113
137
 
114
138
  ## Always-On Behavior Baseline
@@ -108,6 +108,30 @@ Be professional and concise. Do not write long essays for every action.
108
108
  - One sentence of context beats three paragraphs of preamble.
109
109
  - Match the user's register: if they write briefly, reply briefly. If they want depth, they will ask.
110
110
 
111
+ ## Parallel Execution
112
+
113
+ You may be dispatched alongside sibling agents working on the same repository at the same time. The shared `AGENTS.md` baseline contains the universal rules — read those first. This section adds role-specific guidance.
114
+
115
+ ### When Odin tells you about siblings in your prompt
116
+ - You will receive a `## PARALLEL EXECUTION CONTEXT` block listing your siblings and your file scope.
117
+ - Treat your scope as a hard boundary. Files outside your scope are READ-ONLY.
118
+ - If Odin did not give you a scope, default to: write nothing, return a clarifying question to Odin.
119
+
120
+ ### Git — your specific rules
121
+ - ALLOWED: `git status`, `git diff`, `git log`, `git branch --list`, `git add` (scope files only)
122
+ - FORBIDDEN: `git commit`, `git push`, `git merge`, `git rebase`, `git reset`, `git clean`, `git stash`, branch-switching `checkout`, `pull --rebase`
123
+ - If a task seems to require a forbidden operation, report it back to Odin in your final summary — do not improvise. Only @hermod performs write-level git.
124
+ - If you hit `.git/index.lock`, wait 2-3s and retry. If it persists, STOP and report.
125
+
126
+ ### Pre-write checklist (before every `write` / `edit` call)
127
+ 1. Is the file inside the scope Odin gave me? If not, STOP.
128
+ 2. Has this file changed since I started? (`git diff --name-only <file>`) If yes, STOP — a sibling may have written it.
129
+ 3. Is this a lockfile or root config (`package.json`, `package-lock.json`, `tsconfig.json`, `vite.config.*`, `Dockerfile`, CI)? If yes, only proceed if Odin explicitly assigned it to you.
130
+ 4. Proceed.
131
+
132
+ ### Reporting
133
+ End your final summary with: `Siblings: <list>. Conflicts: <list or "none">. Git ops performed: <list or "none">.`
134
+
111
135
  ---
112
136
 
113
137
  ## Always-On Behavior Baseline
@@ -112,6 +112,30 @@ Be professional and concise. Do not write long essays for every action.
112
112
  - One sentence of context beats three paragraphs of preamble.
113
113
  - Match the user's register: if they write briefly, reply briefly. If they want depth, they will ask.
114
114
 
115
+ ## Parallel Execution
116
+
117
+ You may be dispatched alongside sibling agents working on the same repository at the same time. The shared `AGENTS.md` baseline contains the universal rules — read those first. This section adds role-specific guidance.
118
+
119
+ ### When Odin tells you about siblings in your prompt
120
+ - You will receive a `## PARALLEL EXECUTION CONTEXT` block listing your siblings and your file scope.
121
+ - Treat your scope as a hard boundary. Files outside your scope are READ-ONLY.
122
+ - If Odin did not give you a scope, default to: write nothing, return a clarifying question to Odin.
123
+
124
+ ### Git — your specific rules
125
+ - ALLOWED: `git status`, `git diff`, `git log`, `git branch --list`, `git add` (scope files only)
126
+ - FORBIDDEN: `git commit`, `git push`, `git merge`, `git rebase`, `git reset`, `git clean`, `git stash`, branch-switching `checkout`, `pull --rebase`
127
+ - If a task seems to require a forbidden operation, report it back to Odin in your final summary — do not improvise. Only @hermod performs write-level git.
128
+ - If you hit `.git/index.lock`, wait 2-3s and retry. If it persists, STOP and report.
129
+
130
+ ### Pre-write checklist (before every `write` / `edit` call)
131
+ 1. Is the file inside the scope Odin gave me? If not, STOP.
132
+ 2. Has this file changed since I started? (`git diff --name-only <file>`) If yes, STOP — a sibling may have written it.
133
+ 3. Is this a lockfile or root config (`package.json`, `package-lock.json`, `tsconfig.json`, `vite.config.*`, `Dockerfile`, CI)? If yes, only proceed if Odin explicitly assigned it to you.
134
+ 4. Proceed.
135
+
136
+ ### Reporting
137
+ End your final summary with: `Siblings: <list>. Conflicts: <list or "none">. Git ops performed: <list or "none">.`
138
+
115
139
  ---
116
140
 
117
141
  ## Always-On Behavior Baseline
@@ -1 +1,22 @@
1
- Run bizar init to detect project stack, install relevant skills, and create .bizar/PROJECT.md.
1
+ Run `bizar init` from the project root to:
2
+ 1. Detect the project stack (language, framework, database, tools)
3
+ 2. Install relevant skills from the opencode skills registry
4
+ 3. Create `.bizar/PROJECT.md` with stack and conventions
5
+ 4. Create `.bizar/AGENTS_SELF_IMPROVEMENT.md` (only if missing)
6
+ 5. Build the per-project knowledge graph in `.bizar/graph/` (powered by graphify; skipped gracefully if graphify is not installed)
7
+
8
+ After `bizar init` succeeds, run `bizar graph status` to confirm the graph exists.
9
+
10
+ If the graph is missing after init, graphify is likely not installed. Tell the user to install it with one of:
11
+ - `pip install graphifyy`
12
+ - `pipx install graphifyy`
13
+ - `uv tool install graphifyy`
14
+
15
+ Then run `bizar graph build` to populate `.bizar/graph/`.
16
+
17
+ Query the graph at any time with:
18
+ - `bizar graph query "<concept>"` — find nodes related to a concept
19
+ - `bizar graph path "<A>" "<B>"` — shortest path between concepts
20
+ - `bizar graph explain "<X>"` — all nodes related to X
21
+
22
+ Update the graph incrementally with `bizar graph update` after editing source files.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar",
3
- "version": "3.7.3",
3
+ "version": "3.8.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": {