@polderlabs/bizar 4.2.1 → 4.2.3
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.
|
@@ -244,6 +244,60 @@ describe('bizar memory write CLI', () => {
|
|
|
244
244
|
assert.strictEqual(r.code, 0, `expected exit 0, got ${r.code}; stderr=${r.stderr}`);
|
|
245
245
|
assert.ok(existsSync(join(projectRoot, '.obsidian', 'med.md')), 'medium-severity note should be written');
|
|
246
246
|
});
|
|
247
|
+
|
|
248
|
+
// ─── v4.2.2 — custom frontmatter flags ─────────────────────────────────
|
|
249
|
+
test('--memory-id flag sets a custom memory_id in frontmatter', () => {
|
|
250
|
+
const relPath = 'flags/custom-id.md';
|
|
251
|
+
const r = runBizarMemoryWrite(projectRoot, [
|
|
252
|
+
relPath,
|
|
253
|
+
'--type', 'coding_convention',
|
|
254
|
+
'--memory-id', 'mem_test_custom_id',
|
|
255
|
+
'--body', 'custom id test',
|
|
256
|
+
]);
|
|
257
|
+
assert.strictEqual(r.code, 0, `expected exit 0, got ${r.code}; stderr=${r.stderr}; stdout=${r.stdout}`);
|
|
258
|
+
const filePath = join(projectRoot, '.obsidian', relPath);
|
|
259
|
+
assert.ok(existsSync(filePath), `note file should exist at ${filePath}`);
|
|
260
|
+
const raw = readFileSync(filePath, 'utf8');
|
|
261
|
+
assert.ok(/memory_id:\s*mem_test_custom_id\b/.test(raw),
|
|
262
|
+
`frontmatter should contain memory_id: mem_test_custom_id; got:\n${raw}`);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test('--scope and --source-agent flags pass through to frontmatter', () => {
|
|
266
|
+
const relPath = 'flags/scope-and-agent.md';
|
|
267
|
+
const r = runBizarMemoryWrite(projectRoot, [
|
|
268
|
+
relPath,
|
|
269
|
+
'--type', 'coding_convention',
|
|
270
|
+
'--scope', 'project',
|
|
271
|
+
'--source-agent', 'mimir',
|
|
272
|
+
'--body', 'scope/agent test',
|
|
273
|
+
]);
|
|
274
|
+
assert.strictEqual(r.code, 0, `expected exit 0, got ${r.code}; stderr=${r.stderr}; stdout=${r.stdout}`);
|
|
275
|
+
const filePath = join(projectRoot, '.obsidian', relPath);
|
|
276
|
+
assert.ok(existsSync(filePath), `note file should exist at ${filePath}`);
|
|
277
|
+
const raw = readFileSync(filePath, 'utf8');
|
|
278
|
+
assert.ok(/scope:\s*project\b/.test(raw),
|
|
279
|
+
`frontmatter should contain scope: project; got:\n${raw}`);
|
|
280
|
+
assert.ok(/source_agent:\s*mimir\b/.test(raw),
|
|
281
|
+
`frontmatter should contain source_agent: mimir; got:\n${raw}`);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test('invalid --memory-id format is rejected with kebab/snake-case error', () => {
|
|
285
|
+
const r = runBizarMemoryWrite(projectRoot, [
|
|
286
|
+
'flags/bad-id.md',
|
|
287
|
+
'--type', 'coding_convention',
|
|
288
|
+
'--memory-id', 'has spaces and !@#',
|
|
289
|
+
'--body', 'should fail',
|
|
290
|
+
]);
|
|
291
|
+
assert.strictEqual(r.code, 1, `expected exit 1, got ${r.code}; stderr=${r.stderr}`);
|
|
292
|
+
const out = `${r.stdout}\n${r.stderr}`;
|
|
293
|
+
assert.ok(/invalid --memory-id/i.test(out),
|
|
294
|
+
`expected invalid --memory-id error, got: ${out}`);
|
|
295
|
+
assert.ok(/kebab|snake/i.test(out),
|
|
296
|
+
`expected error to mention kebab/snake-case, got: ${out}`);
|
|
297
|
+
// File should NOT exist
|
|
298
|
+
assert.ok(!existsSync(join(projectRoot, '.obsidian', 'flags', 'bad-id.md')),
|
|
299
|
+
'note file must not be written when --memory-id is invalid');
|
|
300
|
+
});
|
|
247
301
|
});
|
|
248
302
|
|
|
249
303
|
// ─── bizar memory setup ──────────────────────────────────────────────────────
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tests/memory-install-bootstrap.test.mjs
|
|
3
|
+
*
|
|
4
|
+
* Regression test for v4.2.2: the `bizar` CLI bootstrap path crashed with
|
|
5
|
+
* `ERR_AMBIGUOUS_MODULE_SYNTAX` on a fresh install because `promptAndInstallOptional`
|
|
6
|
+
* in `cli/install.mjs` referenced `__dirname` (a CommonJS global) without defining
|
|
7
|
+
* the ESM polyfill. This was only triggered by the bootstrap path, which the
|
|
8
|
+
* existing test suite never exercised.
|
|
9
|
+
*
|
|
10
|
+
* Test: spawn the CLI in a fresh temp project, with BIZAR_SKIP_INSTALL unset.
|
|
11
|
+
* The bootstrap should run and exit cleanly (rc 0 or rc 1 for missing auth, but
|
|
12
|
+
* NOT crash with ERR_AMBIGUOUS_MODULE_SYNTAX).
|
|
13
|
+
*
|
|
14
|
+
* Fix: move the `__dirname` polyfill to module scope in cli/install.mjs.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { describe, it, beforeEach, afterEach } from 'node:test';
|
|
18
|
+
import assert from 'node:assert/strict';
|
|
19
|
+
import { tmpdir } from 'node:os';
|
|
20
|
+
import { join, dirname } from 'node:path';
|
|
21
|
+
import { mkdirSync, rmSync } from 'node:fs';
|
|
22
|
+
import { execFileSync } from 'node:child_process';
|
|
23
|
+
import { fileURLToPath } from 'node:url';
|
|
24
|
+
|
|
25
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
26
|
+
const CLI_BIN = join(dirname(__filename), '..', '..', 'cli', 'bin.mjs');
|
|
27
|
+
|
|
28
|
+
const uid = () => `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Spawn `bizar <subcmd>` in the given cwd, with the given env vars.
|
|
32
|
+
* Returns { code, stdout, stderr }.
|
|
33
|
+
*
|
|
34
|
+
* The bootstrap path is checked by NOT setting BIZAR_SKIP_INSTALL.
|
|
35
|
+
*/
|
|
36
|
+
function runBizarWithoutSkip(args, cwd, extraEnv = {}) {
|
|
37
|
+
try {
|
|
38
|
+
const stdout = execFileSync('node', [CLI_BIN, ...args], {
|
|
39
|
+
cwd,
|
|
40
|
+
encoding: 'utf8',
|
|
41
|
+
stdio: 'pipe',
|
|
42
|
+
timeout: 30_000,
|
|
43
|
+
env: { ...process.env, ...extraEnv }, // no BIZAR_SKIP_INSTALL
|
|
44
|
+
});
|
|
45
|
+
return { code: 0, stdout, stderr: '' };
|
|
46
|
+
} catch (err) {
|
|
47
|
+
return {
|
|
48
|
+
code: err.status ?? 1,
|
|
49
|
+
stdout: err.stdout ? err.stdout.toString() : '',
|
|
50
|
+
stderr: err.stderr ? err.stderr.toString() : (err.message ?? ''),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
describe('bizar CLI bootstrap (v4.2.3 regression)', () => {
|
|
56
|
+
let projectRoot;
|
|
57
|
+
let homeDir;
|
|
58
|
+
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
projectRoot = join(tmpdir(), `bizar-bootstrap-${uid()}`);
|
|
61
|
+
homeDir = join(tmpdir(), `bizar-bootstrap-home-${uid()}`);
|
|
62
|
+
mkdirSync(projectRoot, { recursive: true });
|
|
63
|
+
mkdirSync(homeDir, { recursive: true });
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
afterEach(() => {
|
|
67
|
+
try { rmSync(projectRoot, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
68
|
+
try { rmSync(homeDir, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('bizar --version does not crash with ERR_AMBIGUOUS_MODULE_SYNTAX', () => {
|
|
72
|
+
const r = runBizarWithoutSkip(['--version'], projectRoot, { HOME: homeDir });
|
|
73
|
+
// Whatever exit code, the key thing is the bootstrap doesn't crash
|
|
74
|
+
assert.ok(
|
|
75
|
+
!r.stderr.includes('ERR_AMBIGUOUS_MODULE_SYNTAX'),
|
|
76
|
+
`bootstrap crashed with ERR_AMBIGUOUS_MODULE_SYNTAX. stderr=${r.stderr}`,
|
|
77
|
+
);
|
|
78
|
+
assert.ok(
|
|
79
|
+
!r.stderr.includes('Cannot determine intended module format'),
|
|
80
|
+
`bootstrap module-format error. stderr=${r.stderr}`,
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('bizar memory status does not crash with ERR_AMBIGUOUS_MODULE_SYNTAX', () => {
|
|
85
|
+
const r = runBizarWithoutSkip(['memory', 'status'], projectRoot, { HOME: homeDir });
|
|
86
|
+
assert.ok(
|
|
87
|
+
!r.stderr.includes('ERR_AMBIGUOUS_MODULE_SYNTAX'),
|
|
88
|
+
`bootstrap crashed with ERR_AMBIGUOUS_MODULE_SYNTAX. stderr=${r.stderr}`,
|
|
89
|
+
);
|
|
90
|
+
assert.ok(
|
|
91
|
+
!r.stderr.includes('Cannot determine intended module format'),
|
|
92
|
+
`bootstrap module-format error. stderr=${r.stderr}`,
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('bizar memory doctor does not crash with ERR_AMBIGUOUS_MODULE_SYNTAX', () => {
|
|
97
|
+
const r = runBizarWithoutSkip(['memory', 'doctor'], projectRoot, { HOME: homeDir });
|
|
98
|
+
assert.ok(
|
|
99
|
+
!r.stderr.includes('ERR_AMBIGUOUS_MODULE_SYNTAX'),
|
|
100
|
+
`bootstrap crashed with ERR_AMBIGUOUS_MODULE_SYNTAX. stderr=${r.stderr}`,
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('install.mjs source defines __dirname at module scope (regression guard)', () => {
|
|
105
|
+
// Read the source of cli/install.mjs and check that __dirname is
|
|
106
|
+
// defined at module scope (not only inside runInstaller).
|
|
107
|
+
const installSrc = execFileSync('node', ['-e', `
|
|
108
|
+
const fs = require('fs');
|
|
109
|
+
const src = fs.readFileSync(${JSON.stringify(join(dirname(CLI_BIN), 'install.mjs'))}, 'utf8');
|
|
110
|
+
// Find first __dirname definition (should be at module scope, not inside a function)
|
|
111
|
+
const lines = src.split('\\n');
|
|
112
|
+
let firstDefine = -1;
|
|
113
|
+
for (let i = 0; i < lines.length; i++) {
|
|
114
|
+
if (lines[i].includes('__dirname = dirname(fileURLToPath')) {
|
|
115
|
+
firstDefine = i;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// The first definition must be BEFORE any function definition that uses __dirname
|
|
120
|
+
// Simple heuristic: it must be within the first 20 lines
|
|
121
|
+
process.stdout.write(JSON.stringify({ firstDefine, totalLines: lines.length }));
|
|
122
|
+
`], { encoding: 'utf8' });
|
|
123
|
+
const { firstDefine, totalLines } = JSON.parse(installSrc);
|
|
124
|
+
assert.ok(
|
|
125
|
+
firstDefine >= 0 && firstDefine < 20,
|
|
126
|
+
`__dirname polyfill should be at module scope (first 20 lines). Found at line ${firstDefine + 1} of ${totalLines}.`,
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
});
|
package/cli/install.mjs
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import boxen from 'boxen';
|
|
3
3
|
import { existsSync, lstatSync, rmSync } from 'node:fs';
|
|
4
|
-
import { join } from 'node:path';
|
|
4
|
+
import { join, dirname } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
// ── ESM `__dirname` polyfill ────────────────────────────────────────────────
|
|
8
|
+
// `__dirname` is a CommonJS global. ESM modules don't have it. We define it
|
|
9
|
+
// at module scope so all functions in this file can use it without each
|
|
10
|
+
// having to recreate the polyfill. (v4.2.3 — previously only `runInstaller`
|
|
11
|
+
// had it, which caused `promptAndInstallOptional` to crash with
|
|
12
|
+
// `ERR_AMBIGUOUS_MODULE_SYNTAX` when the bootstrap path fired on a fresh
|
|
13
|
+
// install.)
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
15
|
|
|
6
16
|
import { showBanner, showPantheon, sectionHeading } from './banner.mjs';
|
|
7
17
|
import { promptComponents, promptInstallMode, promptAgents, promptSkillPacks, promptApiKeys, promptConfirmInstall, promptRestartOpenCode } from './prompts.mjs';
|
|
@@ -232,11 +242,10 @@ export async function installPluginFromGlobal(opts = {}) {
|
|
|
232
242
|
*/
|
|
233
243
|
export async function runInstaller() {
|
|
234
244
|
const { existsSync } = await import('node:fs');
|
|
235
|
-
const { join
|
|
236
|
-
const { fileURLToPath } = await import('node:url');
|
|
245
|
+
const { join } = await import('node:path');
|
|
237
246
|
const { spawnSync } = await import('node:child_process');
|
|
238
247
|
|
|
239
|
-
|
|
248
|
+
// __dirname is defined at module scope (see top of file)
|
|
240
249
|
// cli/install.mjs → ../install.sh
|
|
241
250
|
const installSh = join(__dirname, '..', 'install.sh');
|
|
242
251
|
|
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;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polderlabs/bizar",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.3",
|
|
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": {
|