@polderlabs/bizar 4.2.0 → 4.2.2

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/memory.mjs CHANGED
@@ -104,6 +104,10 @@ async function cmdWrite(args) {
104
104
  (default: verified)
105
105
  --tag <tag> Tag to attach (may be passed multiple times)
106
106
  --title <title> Frontmatter title field
107
+ --memory-id <id> Custom memory_id (default: <type>_<timestamp>)
108
+ Must match [a-zA-Z0-9._-]+
109
+ --scope <scope> Frontmatter scope field (e.g. project, team)
110
+ --source-agent <name> Frontmatter source_agent field
107
111
  --body <text> Note body as a string
108
112
  --body-file <path> Path to a file containing the body
109
113
  (exactly one of --body / --body-file is allowed)
@@ -177,6 +181,30 @@ async function cmdWrite(args) {
177
181
  process.exit(1);
178
182
  }
179
183
 
184
+ // --memory-id (kebab/snake-case only)
185
+ const memoryId = optVal('--memory-id');
186
+ if (memoryId !== undefined) {
187
+ if (!/^[a-zA-Z0-9._-]+$/.test(memoryId)) {
188
+ error(`invalid --memory-id: '${memoryId}' (must be kebab/snake-case)`);
189
+ info('expected pattern: mem_<path>, e.g. mem_api_memory_schema_md');
190
+ process.exit(1);
191
+ }
192
+ }
193
+
194
+ // --scope (free-form, but non-empty when provided)
195
+ const scope = optVal('--scope');
196
+ if (scope !== undefined && scope.length === 0) {
197
+ error('--scope must be a non-empty string');
198
+ process.exit(1);
199
+ }
200
+
201
+ // --source-agent (free-form, but non-empty when provided)
202
+ const sourceAgent = optVal('--source-agent');
203
+ if (sourceAgent !== undefined && sourceAgent.length === 0) {
204
+ error('--source-agent must be a non-empty string');
205
+ process.exit(1);
206
+ }
207
+
180
208
  let body = '';
181
209
  if (bodyFile) {
182
210
  try {
@@ -201,6 +229,9 @@ async function cmdWrite(args) {
201
229
  tags,
202
230
  });
203
231
  if (title) frontmatter.title = title;
232
+ if (memoryId) frontmatter.memory_id = memoryId;
233
+ if (scope) frontmatter.scope = scope;
234
+ if (sourceAgent) frontmatter.source_agent = sourceAgent;
204
235
 
205
236
  // ── Write ───────────────────────────────────────────────────────────────
206
237
  let result;
@@ -254,7 +285,25 @@ async function cmdInit(args) {
254
285
  const projectRoot = getProjectRoot();
255
286
  const { loadConfig, saveConfig } = memoryStore;
256
287
 
257
- const yesFlag = args.includes('--yes');
288
+ // ── Help (must come before any work — fixes gap in cmdInit --help coverage)
289
+ if (args.includes('--help') || args.includes('-h')) {
290
+ console.log(`
291
+ Usage: bizar memory init [options]
292
+
293
+ Initialize memory config for this project.
294
+
295
+ Options:
296
+ --memory-mode <managed|local-only> Vault mode (default: managed)
297
+ --memory-repo-name <name> Vault directory name (managed mode)
298
+ --yes Skip prompts; non-interactive
299
+ --non-interactive Alias for --yes
300
+ --help, -h Show this help
301
+ `.trim());
302
+ return;
303
+ }
304
+
305
+ // --non-interactive is the canonical alias; --yes kept for backward compat.
306
+ const yesFlag = args.includes('--yes') || args.includes('--non-interactive');
258
307
 
259
308
  const existing = loadConfig(projectRoot);
260
309
  if (existing.exists) {
@@ -879,7 +928,7 @@ async function cmdUnlink(_args) {
879
928
  async function cmdPull(_args) {
880
929
  const projectRoot = getProjectRoot();
881
930
  const { loadConfig, resolveVault } = memoryStore;
882
- const { pull, isGitInstalled } = memoryGit;
931
+ const { pull, isGitInstalled, ensureUpstream } = memoryGit;
883
932
 
884
933
  const { config } = loadConfig(projectRoot);
885
934
  if (config.mode === 'local-only') {
@@ -892,7 +941,18 @@ async function cmdPull(_args) {
892
941
  process.exit(1);
893
942
  }
894
943
 
895
- const { vaultRoot } = resolveVault(projectRoot);
944
+ const { vaultRoot, branch } = resolveVault(projectRoot);
945
+ const remoteName = config.gitRemote ? 'origin' : 'origin';
946
+
947
+ // Pre-flight: ensure upstream is set. Without this, `git pull` fails with
948
+ // "no tracking information" on freshly-cloned or locally-initialized vaults.
949
+ const upstreamResult = ensureUpstream(vaultRoot, config.branch || branch || 'main', remoteName);
950
+ if (!upstreamResult.ok) {
951
+ info(`upstream not set (${upstreamResult.error}); pull may fail`);
952
+ } else if (upstreamResult.action === 'set') {
953
+ success(`set upstream to ${remoteName}/${config.branch || branch || 'main'}`);
954
+ }
955
+
896
956
  info(`pulling into ${vaultRoot}`);
897
957
  const result = pull(vaultRoot);
898
958
  if (!result.ok) {
@@ -985,7 +1045,7 @@ async function cmdPush(_args) {
985
1045
  async function cmdSync(args) {
986
1046
  const projectRoot = getProjectRoot();
987
1047
  const { loadConfig, resolveVault, validateAll, scanForSecrets, listNotes } = memoryStore;
988
- const { pull, addAll, commit: gitCommit, push: gitPush, status: gitStatus, acquireLock, isGitInstalled } = memoryGit;
1048
+ const { pull, addAll, commit: gitCommit, push: gitPush, status: gitStatus, acquireLock, isGitInstalled, ensureUpstream } = memoryGit;
989
1049
 
990
1050
  const { config } = loadConfig(projectRoot);
991
1051
  if (config.mode === 'local-only') {
@@ -998,7 +1058,19 @@ async function cmdSync(args) {
998
1058
  process.exit(1);
999
1059
  }
1000
1060
 
1001
- const { vaultRoot } = resolveVault(projectRoot);
1061
+ const { vaultRoot, branch } = resolveVault(projectRoot);
1062
+ const branchName = config.branch || branch || 'main';
1063
+
1064
+ // Pre-flight: ensure upstream is set. Some workflows create a vault before
1065
+ // origin exists; without upstream, `git pull` fails with "no tracking".
1066
+ if (config.gitRemote) {
1067
+ const upstreamResult = ensureUpstream(vaultRoot, branchName, 'origin');
1068
+ if (!upstreamResult.ok) {
1069
+ info(`upstream not set (${upstreamResult.error}); pull may fail`);
1070
+ } else if (upstreamResult.action === 'set') {
1071
+ success(`set upstream to origin/${branchName}`);
1072
+ }
1073
+ }
1002
1074
 
1003
1075
  // Acquire lock
1004
1076
  const lock = acquireLock(vaultRoot);
@@ -1237,7 +1309,7 @@ async function cmdConflicts(_args) {
1237
1309
  const filePath = join(vaultRoot, note.relPath);
1238
1310
  try {
1239
1311
  const content = rf(filePath, 'utf8');
1240
- if (/^<{7}\s|^={7}\s|>{7}\s/.test(content)) {
1312
+ if (/^<{7}\s|^={7}\s|^>{7}\s/m.test(content)) {
1241
1313
  conflicts.push({ relPath: note.relPath, reason: 'git conflict markers detected' });
1242
1314
  }
1243
1315
  } catch { /* skip */ }
@@ -1346,6 +1418,189 @@ async function cmdDoctor(_args) {
1346
1418
  console.log();
1347
1419
  }
1348
1420
 
1421
+ // ─── read / list / delete (Fix 4 + Fix 5 plumbing) ────────────────────────────
1422
+
1423
+ /**
1424
+ * Parse `--namespace <ns>` and return the chosen namespace plus the remaining
1425
+ * positional args. Defaults to 'project' when the flag is absent.
1426
+ */
1427
+ function parseNamespaceFlag(args) {
1428
+ const idx = args.indexOf('--namespace');
1429
+ if (idx === -1) return { namespace: 'project', rest: args };
1430
+ const v = args[idx + 1];
1431
+ if (!v || v.startsWith('-')) return { namespace: 'project', rest: args };
1432
+ // Remove the flag and its value from the rest
1433
+ const rest = args.filter((a, i) => i !== idx && i !== idx + 1);
1434
+ return { namespace: v, rest };
1435
+ }
1436
+
1437
+ /**
1438
+ * `bizar memory read [--namespace <ns>] <relpath>`
1439
+ *
1440
+ * Read a single note and print its raw content (frontmatter + body) to stdout.
1441
+ */
1442
+ async function cmdRead(args) {
1443
+ const projectRoot = getProjectRoot();
1444
+ const { resolveVault, resolveNamespaceRoot } = memoryStore;
1445
+
1446
+ if (args.includes('--help') || args.includes('-h')) {
1447
+ console.log(`
1448
+ Usage: bizar memory read [--namespace <ns>] <relpath>
1449
+
1450
+ Read a single note and print it to stdout (YAML frontmatter + body).
1451
+
1452
+ Options:
1453
+ --namespace <project|global|user> Namespace to read from (default: project)
1454
+ --help, -h Show this help
1455
+ `.trim());
1456
+ return;
1457
+ }
1458
+
1459
+ const { namespace, rest } = parseNamespaceFlag(args);
1460
+ const relpath = rest.filter((a) => !a.startsWith('-'))[0];
1461
+ if (!relpath) {
1462
+ error('relpath is required: `bizar memory read <relpath>`');
1463
+ info('run `bizar memory read --help` for usage');
1464
+ process.exit(1);
1465
+ }
1466
+
1467
+ let note;
1468
+ if (namespace === 'project') {
1469
+ note = memoryStore.readNote(projectRoot, relpath);
1470
+ } else {
1471
+ const vi = resolveVault(projectRoot);
1472
+ const root = resolveNamespaceRoot(vi, namespace);
1473
+ if (!root) {
1474
+ error(`unknown namespace: ${namespace}`);
1475
+ process.exit(1);
1476
+ }
1477
+ note = memoryStore.readNote(projectRoot, relpath, { root });
1478
+ }
1479
+
1480
+ if (!note) {
1481
+ error(`note not found: ${relpath}`);
1482
+ process.exit(1);
1483
+ }
1484
+ process.stdout.write(note.raw);
1485
+ if (!note.raw.endsWith('\n')) process.stdout.write('\n');
1486
+ }
1487
+
1488
+ /**
1489
+ * `bizar memory list [--namespace <ns>] [--json] [dir]`
1490
+ *
1491
+ * List notes under a directory. Default: walk the project namespace root.
1492
+ * Pass `dir` (e.g. `decisions/`) to scope to a subdirectory. The dir is
1493
+ * resolved RELATIVE TO the namespace root.
1494
+ */
1495
+ async function cmdList(args) {
1496
+ const projectRoot = getProjectRoot();
1497
+ const { resolveVault, resolveNamespaceRoot } = memoryStore;
1498
+
1499
+ if (args.includes('--help') || args.includes('-h')) {
1500
+ console.log(`
1501
+ Usage: bizar memory list [--namespace <ns>] [dir]
1502
+
1503
+ List notes under a directory. Default: the project namespace root.
1504
+
1505
+ Options:
1506
+ --namespace <project|global|user> Namespace to list (default: project)
1507
+ --json Emit JSON array
1508
+ [dir] Subdirectory under the namespace (e.g. decisions/)
1509
+ --help, -h Show this help
1510
+ `.trim());
1511
+ return;
1512
+ }
1513
+
1514
+ const { namespace, rest } = parseNamespaceFlag(args);
1515
+ const asJson = rest.includes('--json');
1516
+ const dir = rest.filter((a) => !a.startsWith('-'))[0] || '';
1517
+
1518
+ let notes;
1519
+ if (namespace === 'project') {
1520
+ notes = memoryStore.listNotes(projectRoot, dir ? { namespace: dir } : {});
1521
+ } else {
1522
+ const vi = resolveVault(projectRoot);
1523
+ const root = resolveNamespaceRoot(vi, namespace);
1524
+ if (!root) {
1525
+ error(`unknown namespace: ${namespace}`);
1526
+ process.exit(1);
1527
+ }
1528
+ notes = memoryStore.listNotes(projectRoot, dir ? { root, namespace: dir } : { root });
1529
+ }
1530
+
1531
+ // listNotes returns relPaths RELATIVE to the search root, so when we
1532
+ // filter by `dir` we need to re-attach the dir prefix for display —
1533
+ // otherwise the user sees `0001-foo.md` instead of the more useful
1534
+ // `decisions/0001-foo.md`. Strip a trailing slash for cleanliness.
1535
+ const displayPrefix = dir ? dir.replace(/\/+$/, '') + '/' : '';
1536
+
1537
+ if (asJson) {
1538
+ console.log(JSON.stringify(notes.map((n) => ({
1539
+ relPath: displayPrefix + n.relPath,
1540
+ size: n.size,
1541
+ mtime: n.mtime,
1542
+ })), null, 2));
1543
+ return;
1544
+ }
1545
+ if (notes.length === 0) {
1546
+ console.log(chalk.dim(' (no notes)'));
1547
+ return;
1548
+ }
1549
+ for (const n of notes) {
1550
+ console.log(` ${displayPrefix}${n.relPath}`);
1551
+ }
1552
+ }
1553
+
1554
+ /**
1555
+ * `bizar memory delete [--namespace <ns>] <relpath>`
1556
+ *
1557
+ * Delete a single note from the vault.
1558
+ */
1559
+ async function cmdDelete(args) {
1560
+ const projectRoot = getProjectRoot();
1561
+ const { resolveVault, resolveNamespaceRoot } = memoryStore;
1562
+
1563
+ if (args.includes('--help') || args.includes('-h')) {
1564
+ console.log(`
1565
+ Usage: bizar memory delete [--namespace <ns>] <relpath>
1566
+
1567
+ Delete a single note from the vault.
1568
+
1569
+ Options:
1570
+ --namespace <project|global|user> Namespace to delete from (default: project)
1571
+ --help, -h Show this help
1572
+ `.trim());
1573
+ return;
1574
+ }
1575
+
1576
+ const { namespace, rest } = parseNamespaceFlag(args);
1577
+ const relpath = rest.filter((a) => !a.startsWith('-'))[0];
1578
+ if (!relpath) {
1579
+ error('relpath is required: `bizar memory delete <relpath>`');
1580
+ info('run `bizar memory delete --help` for usage');
1581
+ process.exit(1);
1582
+ }
1583
+
1584
+ let ok;
1585
+ if (namespace === 'project') {
1586
+ ok = memoryStore.deleteNote(projectRoot, relpath);
1587
+ } else {
1588
+ const vi = resolveVault(projectRoot);
1589
+ const root = resolveNamespaceRoot(vi, namespace);
1590
+ if (!root) {
1591
+ error(`unknown namespace: ${namespace}`);
1592
+ process.exit(1);
1593
+ }
1594
+ ok = memoryStore.deleteNote(projectRoot, relpath, { root });
1595
+ }
1596
+
1597
+ if (!ok) {
1598
+ error(`note not found: ${relpath}`);
1599
+ process.exit(1);
1600
+ }
1601
+ success(`deleted ${relpath}`);
1602
+ }
1603
+
1349
1604
  // ─── Dispatcher ──────────────────────────────────────────────────────────────
1350
1605
 
1351
1606
  /**
@@ -1372,6 +1627,15 @@ export async function runMemory(subcommand, args) {
1372
1627
  case 'write':
1373
1628
  await cmdWrite(args);
1374
1629
  break;
1630
+ case 'read':
1631
+ await cmdRead(args);
1632
+ break;
1633
+ case 'list':
1634
+ await cmdList(args);
1635
+ break;
1636
+ case 'delete':
1637
+ await cmdDelete(args);
1638
+ break;
1375
1639
  case 'pull':
1376
1640
  await cmdPull(args);
1377
1641
  break;
@@ -1422,6 +1686,9 @@ function showHelp() {
1422
1686
  link <path> Link to a shared memory repo (clone if URL)
1423
1687
  unlink Revert to local-only mode
1424
1688
  write Write a single note to the vault
1689
+ read Read a single note and print it to stdout
1690
+ list List notes (optionally filtered by namespace + dir)
1691
+ delete Delete a single note from the vault
1425
1692
  pull Git pull from the shared repo
1426
1693
  commit [-m] Git commit staged changes
1427
1694
  push Git push to the shared repo
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar",
3
- "version": "4.2.0",
3
+ "version": "4.2.2",
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. v4 ships as a single npm package bundling the dashboard server, opencode plugin, and typed SDK.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,7 +21,7 @@
21
21
  "build:sdk": "tsc -p packages/sdk/tsconfig.json",
22
22
  "test:sdk": "npm test --prefix packages/sdk",
23
23
  "test:sdk:watch": "npm run test:watch --prefix packages/sdk",
24
- "test": "npm run typecheck && npm run test:sdk && bun test plugins/bizar/tests/loop.test.ts plugins/bizar/tests/block.test.ts plugins/bizar/tests/stall-think.test.ts plugins/bizar/tests/tools/bg-get-comments.test.ts plugins/bizar/tests/tools/bg-spawn-delegation.test.ts plugins/bizar/tests/tools/opencode-runner.test.ts plugins/bizar/tests/settings.test.ts plugins/bizar/tests/commands.test.ts plugins/bizar/tests/commands-impl.test.ts plugins/bizar/tests/tools/plan-action.test.ts plugins/bizar/tests/tools/wait-for-feedback.test.ts plugins/bizar/tests/tools/read-glyph-feedback.test.ts plugins/bizar/tests/reasoning-clean.test.ts plugins/bizar/tests/key-rotation.test.ts && node bizar-dash/tests/smoke-v2.mjs && node --test bizar-dash/tests/path-safe.test.mjs bizar-dash/tests/tmux-wrap.test.mjs bizar-dash/tests/opencode-runner.test.mjs bizar-dash/tests/mod-instructions.node.test.mjs bizar-dash/tests/mod-upgrade.node.test.mjs bizar-dash/tests/graphify-mod-spawn.node.test.mjs bizar-dash/tests/no-agent-browser.node.test.mjs bizar-dash/tests/providers-store-backup-keys.node.test.mjs bizar-dash/tests/dashboard-ports.test.mjs bizar-dash/tests/submit-feedback.test.mjs bizar-dash/tests/yaml.test.mjs bizar-dash/tests/memory-store.test.mjs bizar-dash/tests/memory-schema.test.mjs bizar-dash/tests/memory-secrets.test.mjs bizar-dash/tests/memory-git.test.mjs bizar-dash/tests/memory-sync.test.mjs bizar-dash/tests/obsidian-back-compat.test.mjs bizar-dash/tests/memory-lightrag.test.mjs bizar-dash/tests/memory-config.test.mjs bizar-dash/tests/memory-cli.test.mjs",
24
+ "test": "npm run typecheck && npm run test:sdk && bun test plugins/bizar/tests/loop.test.ts plugins/bizar/tests/block.test.ts plugins/bizar/tests/stall-think.test.ts plugins/bizar/tests/tools/bg-get-comments.test.ts plugins/bizar/tests/tools/bg-spawn-delegation.test.ts plugins/bizar/tests/tools/opencode-runner.test.ts plugins/bizar/tests/settings.test.ts plugins/bizar/tests/commands.test.ts plugins/bizar/tests/commands-impl.test.ts plugins/bizar/tests/tools/plan-action.test.ts plugins/bizar/tests/tools/wait-for-feedback.test.ts plugins/bizar/tests/tools/read-glyph-feedback.test.ts plugins/bizar/tests/reasoning-clean.test.ts plugins/bizar/tests/key-rotation.test.ts && node bizar-dash/tests/smoke-v2.mjs && node --test bizar-dash/tests/path-safe.test.mjs bizar-dash/tests/tmux-wrap.test.mjs bizar-dash/tests/opencode-runner.test.mjs bizar-dash/tests/mod-instructions.node.test.mjs bizar-dash/tests/mod-upgrade.node.test.mjs bizar-dash/tests/graphify-mod-spawn.node.test.mjs bizar-dash/tests/no-agent-browser.node.test.mjs bizar-dash/tests/providers-store-backup-keys.node.test.mjs bizar-dash/tests/dashboard-ports.test.mjs bizar-dash/tests/submit-feedback.test.mjs bizar-dash/tests/yaml.test.mjs bizar-dash/tests/memory-store.test.mjs bizar-dash/tests/memory-schema.test.mjs bizar-dash/tests/memory-secrets.test.mjs bizar-dash/tests/memory-git.test.mjs bizar-dash/tests/memory-sync.test.mjs bizar-dash/tests/obsidian-back-compat.test.mjs bizar-dash/tests/memory-lightrag.test.mjs bizar-dash/tests/memory-config.test.mjs bizar-dash/tests/memory-cli.test.mjs bizar-dash/tests/memory-cli-readlistdelete.test.mjs bizar-dash/tests/memory-cli-setup.test.mjs bizar-dash/tests/memory-conflicts.test.mjs bizar-dash/tests/memory-namespace.test.mjs bizar-dash/tests/memory-path-safety.test.mjs bizar-dash/tests/memory-protocol-drift.test.mjs bizar-dash/tests/memory-roundtrip.test.mjs",
25
25
  "build": "npm run build:sdk"
26
26
  },
27
27
  "keywords": [