@polderlabs/bizar 6.1.0 → 6.2.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/bizar-dash/DESIGN.md +18 -23
- package/bizar-dash/critique.json +6 -6
- package/cli/bin.mjs +19 -1
- package/cli/commands/validate.mjs +439 -0
- package/cli/commands/validate.test.mjs +279 -0
- package/cli/post-install-smoke.mjs +96 -3
- package/cli/post-install-smoke.test.mjs +3 -5
- package/cli/provision.mjs +97 -11
- package/config/cline.json.template +15 -0
- package/config/commands/team.md +124 -0
- package/config/commands/test.md +60 -0
- package/config/commands/validate.md +66 -0
- package/package.json +5 -4
- package/plugins/bizar/package.json +1 -1
- package/plugins/bizar/src/clineruntime.ts +1 -1
- package/plugins/bizar/tests/clineruntime-config.test.ts +44 -0
- package/scripts/bh-full-e2e.mjs +389 -0
- package/scripts/check-arch.sh +80 -0
- package/scripts/check-deps.mjs +510 -0
- package/scripts/clean-state-check.sh +81 -0
- package/scripts/git-hooks/pre-commit +59 -0
- package/scripts/git-hooks/pre-push +54 -0
- package/scripts/install-hooks.sh +29 -0
- package/scripts/install-service.mjs +190 -0
- package/scripts/session-trace.sh +52 -0
- package/scripts/verify-feature.sh +102 -0
- package/bizar-dash/image-1.png +0 -0
- package/bizar-dash/image-2.png +0 -0
- package/bizar-dash/image-3.png +0 -0
- package/bizar-dash/image-4.png +0 -0
- package/bizar-dash/image-5.png +0 -0
- package/bizar-dash/image-6.png +0 -0
- package/bizar-dash/image-7.png +0 -0
- package/bizar-dash/image.png +0 -0
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* scripts/check-deps.mjs
|
|
4
|
+
*
|
|
5
|
+
* v3.22.0 — Cross-platform dependency detector for BizarHarness.
|
|
6
|
+
*
|
|
7
|
+
* Exports a single async function `checkDeps({ strict })` that probes the
|
|
8
|
+
* environment for required tools (node, bun, cline, tmux, git) and returns
|
|
9
|
+
* a structured JSON report. Also runs as a CLI entry: `node check-deps.mjs`.
|
|
10
|
+
*
|
|
11
|
+
* Design:
|
|
12
|
+
* - No destructive shell-outs. Only reads `--version` via execFileSync.
|
|
13
|
+
* - Cross-platform `which(cmd)` helper respects PATHEXT on Windows.
|
|
14
|
+
* - Semver comparison using Node's built-in `node:semver` (available >= 20)
|
|
15
|
+
* with a tiny fallback for 18.x.
|
|
16
|
+
* - Linux distro detection via `/etc/os-release` ID field.
|
|
17
|
+
* - Each DepReport: { name, status, current?, required, installCmd? }
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { execFileSync } from 'node:child_process';
|
|
21
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
22
|
+
import { join } from 'node:path';
|
|
23
|
+
|
|
24
|
+
// ── Semver helpers (works without node:semver on 18.x) ─────────────────────────
|
|
25
|
+
|
|
26
|
+
function parseSemver(s) {
|
|
27
|
+
const m = String(s).match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
|
|
28
|
+
if (!m) return null;
|
|
29
|
+
return [parseInt(m[1], 10) || 0, parseInt(m[2], 10) || 0, parseInt(m[3], 10) || 0];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function satisfies(current, required) {
|
|
33
|
+
const c = parseSemver(current);
|
|
34
|
+
const r = parseSemver(required);
|
|
35
|
+
if (!c || !r) return null;
|
|
36
|
+
// r[0] == c[0] && r[1] <= c[1] && r[2] <= c[2] (simple prefix compare)
|
|
37
|
+
for (let i = 0; i < 3; i++) {
|
|
38
|
+
if (c[i] < r[i]) return false;
|
|
39
|
+
if (c[i] > r[i]) return true;
|
|
40
|
+
}
|
|
41
|
+
return true; // equal
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ── Platform info ──────────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
const PLATFORM = process.platform; // 'win32' | 'darwin' | 'linux'
|
|
47
|
+
|
|
48
|
+
function detectLinuxDistro() {
|
|
49
|
+
if (PLATFORM !== 'linux') return null;
|
|
50
|
+
try {
|
|
51
|
+
const content = readFileSync('/etc/os-release', 'utf8');
|
|
52
|
+
const idMatch = content.match(/^ID=(.+)$/m);
|
|
53
|
+
const idLikeMatch = content.match(/^ID_LIKE=(.+)$/m);
|
|
54
|
+
const id = idMatch ? idMatch[1].trim().replace(/"/g, '') : 'unknown';
|
|
55
|
+
const idLike = idLikeMatch ? idLikeMatch[1].trim().replace(/"/g, '') : '';
|
|
56
|
+
// Normalise common variants
|
|
57
|
+
if (id === 'ubuntu' || id === 'debian' || idLike.includes('debian')) return 'debian';
|
|
58
|
+
if (id === 'fedora' || id === 'rhel' || id === 'centos' || idLike.includes('fedora')) return 'fedora';
|
|
59
|
+
if (id === 'arch' || idLike.includes('arch')) return 'arch';
|
|
60
|
+
if (id === 'opensuse' || id === 'opensuse-leap' || id === 'opensuse-tumbleweed' || idLike.includes('suse')) return 'suse';
|
|
61
|
+
if (id === 'alpine') return 'alpine';
|
|
62
|
+
if (id === 'nixos' || id === 'NixOS') return 'nixos';
|
|
63
|
+
if (id === 'void' || id === 'void_linux') return 'void';
|
|
64
|
+
return id;
|
|
65
|
+
} catch {
|
|
66
|
+
return 'unknown';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const LINUX_DISTRO = detectLinuxDistro();
|
|
71
|
+
|
|
72
|
+
// ── Cross-platform which() ─────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
function which(cmd) {
|
|
75
|
+
const isWin = PLATFORM === 'win32';
|
|
76
|
+
const pathext = isWin
|
|
77
|
+
? (process.env.PATHEXT || '.COM;.EXE;.BAT;.CMD').toLowerCase().split(';')
|
|
78
|
+
: [''];
|
|
79
|
+
const pathDirs = (process.env.PATH || '').split(isWin ? ';' : ':');
|
|
80
|
+
|
|
81
|
+
for (const dir of pathDirs) {
|
|
82
|
+
if (!dir) continue;
|
|
83
|
+
for (const ext of pathext) {
|
|
84
|
+
const full = join(dir, cmd + ext);
|
|
85
|
+
try {
|
|
86
|
+
// existsSync + check executable — we can't do a real access() check
|
|
87
|
+
// cross-platform without stat, but testing with execFileSync on a
|
|
88
|
+
// known subcommand is safer.
|
|
89
|
+
if (existsSync(full)) return full;
|
|
90
|
+
} catch { /* try next */ }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── Version readers ────────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
function safeExec(cmd, args = ['--version'], opts = {}) {
|
|
99
|
+
try {
|
|
100
|
+
const stdout = execFileSync(cmd, args, {
|
|
101
|
+
encoding: 'utf8',
|
|
102
|
+
timeout: 5000,
|
|
103
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
104
|
+
...opts,
|
|
105
|
+
});
|
|
106
|
+
return (stdout || '').trim().split('\n')[0] || null;
|
|
107
|
+
} catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function readNodeVersion() {
|
|
113
|
+
return process.version.replace(/^v/, '');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function readBunVersion() {
|
|
117
|
+
const raw = safeExec('bun');
|
|
118
|
+
if (!raw) return null;
|
|
119
|
+
const m = raw.match(/(\d+\.\d+\.\d+)/);
|
|
120
|
+
return m ? m[1] : raw.split(' ')[0];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function readClineVersion() {
|
|
124
|
+
const raw = safeExec('cline');
|
|
125
|
+
if (!raw) return null;
|
|
126
|
+
const m = raw.match(/(\d+\.\d+\.\d+)/);
|
|
127
|
+
return m ? m[1] : raw.split(' ')[0];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function readPython3Version() {
|
|
131
|
+
const raw = safeExec('python3', ['--version']);
|
|
132
|
+
if (!raw) return null;
|
|
133
|
+
const m = raw.match(/(\d+\.\d+\.\d+)/);
|
|
134
|
+
return m ? m[1] : raw.split(' ').pop();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function readJqVersion() {
|
|
138
|
+
const raw = safeExec('jq', ['--version']);
|
|
139
|
+
if (!raw) return null;
|
|
140
|
+
return raw.replace(/^jq-/, '');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function readGhVersion() {
|
|
144
|
+
const raw = safeExec('gh', ['--version']);
|
|
145
|
+
if (!raw) return null;
|
|
146
|
+
const m = raw.match(/(\d+\.\d+\.\d+)/);
|
|
147
|
+
return m ? m[1] : raw.split(' ')[0];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function readHeadroomVersion() {
|
|
151
|
+
const raw = safeExec('headroom', ['--version']);
|
|
152
|
+
if (!raw) return null;
|
|
153
|
+
const m = raw.match(/(\d+\.\d+\.\d+)/);
|
|
154
|
+
return m ? m[1] : raw.split(' ')[0];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function readSembleVersion() {
|
|
158
|
+
const raw = safeExec('semble', ['--version']);
|
|
159
|
+
if (!raw) return null;
|
|
160
|
+
const m = raw.match(/(\d+\.\d+\.\d+)/);
|
|
161
|
+
return m ? m[1] : raw.split(' ')[0];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function readSkillsVersion() {
|
|
165
|
+
const raw = safeExec('skills', ['--version']);
|
|
166
|
+
if (!raw) return null;
|
|
167
|
+
const m = raw.match(/(\d+\.\d+\.\d+)/);
|
|
168
|
+
return m ? m[1] : raw.split(' ')[0];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ── Install command builders ───────────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
function installCmdFor(name) {
|
|
174
|
+
const winCmd = windowsInstallCmd(name);
|
|
175
|
+
if (winCmd) return winCmd;
|
|
176
|
+
if (PLATFORM === 'darwin') return macInstallCmd(name);
|
|
177
|
+
if (PLATFORM === 'linux') return linuxInstallCmd(name);
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function windowsInstallCmd(name) {
|
|
182
|
+
switch (name) {
|
|
183
|
+
case 'node': return 'winget install OpenJS.NodeJS.LTS 2>nul || choco install nodejs -y 2>nul || npm install -g n 2>nul';
|
|
184
|
+
case 'bun': return 'powershell -c "iwr bun.sh/install.ps1 -useb | iex"';
|
|
185
|
+
case 'cline': return 'winget install ClineAI.Cline 2>nul || npm install -g @cline/cli 2>nul';
|
|
186
|
+
case 'tmux': return 'winget install mintty.tmux 2>nul || choco install tmux -y 2>nul';
|
|
187
|
+
case 'git': return 'winget install Git.Git 2>nul || choco install git -y 2>nul';
|
|
188
|
+
case 'python3': return 'winget install Python.Python.3.12 2>nul || choco install python -y 2>nul';
|
|
189
|
+
case 'pip': return 'python -m pip install --upgrade pip 2>nul || pip install --upgrade pip 2>nul';
|
|
190
|
+
case 'jq': return 'winget install jqlang.jq 2>nul || choco install jq -y 2>nul';
|
|
191
|
+
case 'gh': return 'winget install GitHub.cli 2>nul || choco install gh -y 2>nul';
|
|
192
|
+
case 'headroom':
|
|
193
|
+
case 'semble':
|
|
194
|
+
case 'skills': return 'npm install -g ' + name + ' 2>nul';
|
|
195
|
+
default: return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function macInstallCmd(name) {
|
|
200
|
+
switch (name) {
|
|
201
|
+
case 'node': return 'brew install node@18';
|
|
202
|
+
case 'bun': return 'brew install oven-sh/bun/bun';
|
|
203
|
+
case 'cline': return 'brew install clineai/tap/cline';
|
|
204
|
+
case 'tmux': return 'brew install tmux';
|
|
205
|
+
case 'git': return null; // pre-installed on macOS
|
|
206
|
+
case 'python3': return 'brew install python@3.12';
|
|
207
|
+
case 'pip': return 'python3 -m pip install --upgrade pip';
|
|
208
|
+
case 'jq': return 'brew install jq';
|
|
209
|
+
case 'gh': return 'brew install gh';
|
|
210
|
+
case 'headroom':
|
|
211
|
+
case 'semble':
|
|
212
|
+
case 'skills': return 'npm install -g ' + name;
|
|
213
|
+
default: return null;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function linuxInstallCmd(name) {
|
|
218
|
+
if (LINUX_DISTRO === 'nixos') {
|
|
219
|
+
// NixOS: everything via nix-shell or nix-env
|
|
220
|
+
switch (name) {
|
|
221
|
+
case 'node': return 'nix-shell -p nodejs';
|
|
222
|
+
case 'python3': return 'nix-shell -p python3';
|
|
223
|
+
case 'jq': return 'nix-shell -p jq';
|
|
224
|
+
case 'git': return 'nix-shell -p git';
|
|
225
|
+
case 'gh': return 'nix-shell -p gh';
|
|
226
|
+
case 'headroom':
|
|
227
|
+
case 'semble':
|
|
228
|
+
case 'skills': return `nix-env -iA nixpkgs.${name}`;
|
|
229
|
+
default: return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const pm = LINUX_DISTRO === 'debian' ? 'apt-get' :
|
|
233
|
+
LINUX_DISTRO === 'fedora' ? 'dnf' :
|
|
234
|
+
LINUX_DISTRO === 'arch' ? 'pacman' :
|
|
235
|
+
LINUX_DISTRO === 'suse' ? 'zypper' :
|
|
236
|
+
LINUX_DISTRO === 'alpine' ? 'apk' :
|
|
237
|
+
LINUX_DISTRO === 'void' ? 'xbps-install' : null;
|
|
238
|
+
if (!pm) return null;
|
|
239
|
+
|
|
240
|
+
const sudo = process.getuid?.() === 0 ? '' : 'sudo ';
|
|
241
|
+
|
|
242
|
+
switch (name) {
|
|
243
|
+
case 'node': {
|
|
244
|
+
// Prefer the official NodeSource script; fallback to distro packages.
|
|
245
|
+
return `curl -fsSL https://deb.nodesource.com/setup_20.x | ${sudo}bash - && ${sudo}${pm} install -y nodejs`;
|
|
246
|
+
}
|
|
247
|
+
case 'bun':
|
|
248
|
+
return 'curl -fsSL https://bun.sh/install | bash';
|
|
249
|
+
case 'cline':
|
|
250
|
+
return 'curl -fsSL https://docs.cline.bot/install | sh';
|
|
251
|
+
case 'tmux':
|
|
252
|
+
return `${sudo}${pm} install -y tmux`;
|
|
253
|
+
case 'git':
|
|
254
|
+
return `${sudo}${pm} install -y git`;
|
|
255
|
+
case 'python3':
|
|
256
|
+
if (LINUX_DISTRO === 'alpine') return `${sudo}apk add --no-cache python3 py3-pip`;
|
|
257
|
+
if (LINUX_DISTRO === 'void') return `${sudo}xbps-install -S python3 python3-pip`;
|
|
258
|
+
return `${sudo}${pm} install -y python3 python3-pip`;
|
|
259
|
+
case 'pip':
|
|
260
|
+
if (LINUX_DISTRO === 'alpine') return `${sudo}apk add --no-cache py3-pip || ${sudo}python3 -m pip install --upgrade pip`;
|
|
261
|
+
if (LINUX_DISTRO === 'void') return `${sudo}xbps-install -S python3-pip || ${sudo}python3 -m pip install --upgrade pip`;
|
|
262
|
+
return `${sudo}${pm} install -y python3-pip || ${sudo}python3 -m pip install --upgrade pip`;
|
|
263
|
+
case 'jq':
|
|
264
|
+
if (LINUX_DISTRO === 'alpine') return `${sudo}apk add --no-cache jq`;
|
|
265
|
+
if (LINUX_DISTRO === 'void') return `${sudo}xbps-install -S jq`;
|
|
266
|
+
return `${sudo}${pm} install -y jq`;
|
|
267
|
+
case 'gh':
|
|
268
|
+
if (LINUX_DISTRO === 'alpine') return `${sudo}apk add --no-cache gh`;
|
|
269
|
+
if (LINUX_DISTRO === 'void') return `${sudo}xbps-install -S gh`;
|
|
270
|
+
return `${sudo}${pm} install -y gh`;
|
|
271
|
+
case 'headroom':
|
|
272
|
+
case 'semble':
|
|
273
|
+
case 'skills':
|
|
274
|
+
return `npm install -g ${name}`;
|
|
275
|
+
default:
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ── Required versions ──────────────────────────────────────────────────────────
|
|
281
|
+
|
|
282
|
+
const REQUIRED = {
|
|
283
|
+
node: { raw: '>=18', min: '18.0.0' },
|
|
284
|
+
bun: { raw: '>=1.0.0', min: '1.0.0' },
|
|
285
|
+
cline: { raw: '>=0.4.0', min: '0.4.0' },
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
// ── Main check ─────────────────────────────────────────────────────────────────
|
|
289
|
+
|
|
290
|
+
export async function checkDeps({ strict = false } = {}) {
|
|
291
|
+
const missing = [];
|
|
292
|
+
const present = [];
|
|
293
|
+
const platform = `${PLATFORM}${PLATFORM === 'linux' ? '/' + LINUX_DISTRO : ''}`;
|
|
294
|
+
|
|
295
|
+
// --- node ---
|
|
296
|
+
{
|
|
297
|
+
const current = readNodeVersion();
|
|
298
|
+
const ok = current && satisfies(current, REQUIRED.node.min);
|
|
299
|
+
const entry = { name: 'node', status: 'missing', current, required: REQUIRED.node.raw };
|
|
300
|
+
if (!current) {
|
|
301
|
+
entry.status = 'missing';
|
|
302
|
+
entry.installCmd = installCmdFor('node');
|
|
303
|
+
missing.push(entry);
|
|
304
|
+
} else if (!ok) {
|
|
305
|
+
entry.status = 'outdated';
|
|
306
|
+
entry.installCmd = installCmdFor('node');
|
|
307
|
+
missing.push(entry);
|
|
308
|
+
} else {
|
|
309
|
+
entry.status = 'present';
|
|
310
|
+
present.push(entry);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// --- bun ---
|
|
315
|
+
{
|
|
316
|
+
const current = readBunVersion();
|
|
317
|
+
const entry = { name: 'bun', status: 'missing', current, required: REQUIRED.bun.raw };
|
|
318
|
+
if (!current) {
|
|
319
|
+
entry.status = 'missing';
|
|
320
|
+
entry.installCmd = installCmdFor('bun');
|
|
321
|
+
missing.push(entry);
|
|
322
|
+
} else if (!satisfies(current, REQUIRED.bun.min)) {
|
|
323
|
+
entry.status = 'outdated';
|
|
324
|
+
entry.installCmd = installCmdFor('bun');
|
|
325
|
+
missing.push(entry);
|
|
326
|
+
} else {
|
|
327
|
+
entry.status = 'present';
|
|
328
|
+
present.push(entry);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// --- cline ---
|
|
333
|
+
{
|
|
334
|
+
const current = readClineVersion();
|
|
335
|
+
const entry = { name: 'cline', status: 'missing', current, required: REQUIRED.cline.raw };
|
|
336
|
+
if (!current) {
|
|
337
|
+
entry.status = 'missing';
|
|
338
|
+
entry.installCmd = installCmdFor('cline');
|
|
339
|
+
missing.push(entry);
|
|
340
|
+
} else if (!satisfies(current, REQUIRED.cline.min)) {
|
|
341
|
+
entry.status = 'outdated';
|
|
342
|
+
entry.installCmd = installCmdFor('cline');
|
|
343
|
+
missing.push(entry);
|
|
344
|
+
} else {
|
|
345
|
+
entry.status = 'present';
|
|
346
|
+
present.push(entry);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// --- tmux (optional, recommended) ---
|
|
351
|
+
{
|
|
352
|
+
const current = null; // no version check — just presence
|
|
353
|
+
const tmuxPath = which('tmux');
|
|
354
|
+
const entry = { name: 'tmux', status: 'missing', current: null, required: 'recommended' };
|
|
355
|
+
if (tmuxPath) {
|
|
356
|
+
entry.status = 'present';
|
|
357
|
+
entry.current = 'found';
|
|
358
|
+
present.push(entry);
|
|
359
|
+
} else {
|
|
360
|
+
entry.installCmd = installCmdFor('tmux');
|
|
361
|
+
missing.push(entry);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// --- git ---
|
|
366
|
+
{
|
|
367
|
+
const raw = safeExec('git');
|
|
368
|
+
const gitPath = which('git');
|
|
369
|
+
const entry = { name: 'git', status: 'missing', current: null, required: '*' };
|
|
370
|
+
if (gitPath && raw) {
|
|
371
|
+
const m = raw.match(/(\d+\.\d+\.\d+)/);
|
|
372
|
+
entry.status = 'present';
|
|
373
|
+
entry.current = m ? m[1] : raw.split(' ')[0];
|
|
374
|
+
present.push(entry);
|
|
375
|
+
} else {
|
|
376
|
+
entry.installCmd = installCmdFor('git');
|
|
377
|
+
missing.push(entry);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// --- python3 ---
|
|
382
|
+
{
|
|
383
|
+
const current = readPython3Version();
|
|
384
|
+
const entry = { name: 'python3', status: 'missing', current, required: 'recommended' };
|
|
385
|
+
if (which('python3')) {
|
|
386
|
+
entry.status = 'present';
|
|
387
|
+
present.push(entry);
|
|
388
|
+
} else {
|
|
389
|
+
entry.installCmd = installCmdFor('python3');
|
|
390
|
+
missing.push(entry);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// --- pip ---
|
|
395
|
+
{
|
|
396
|
+
const entry = { name: 'pip', status: 'missing', current: null, required: 'recommended' };
|
|
397
|
+
if (which('pip') || which('pip3')) {
|
|
398
|
+
entry.status = 'present';
|
|
399
|
+
entry.current = 'found';
|
|
400
|
+
present.push(entry);
|
|
401
|
+
} else {
|
|
402
|
+
entry.installCmd = installCmdFor('pip');
|
|
403
|
+
missing.push(entry);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// --- jq ---
|
|
408
|
+
{
|
|
409
|
+
const current = readJqVersion();
|
|
410
|
+
const entry = { name: 'jq', status: 'missing', current, required: 'recommended' };
|
|
411
|
+
if (which('jq')) {
|
|
412
|
+
entry.status = 'present';
|
|
413
|
+
present.push(entry);
|
|
414
|
+
} else {
|
|
415
|
+
entry.installCmd = installCmdFor('jq');
|
|
416
|
+
missing.push(entry);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// --- gh (GitHub CLI) ---
|
|
421
|
+
{
|
|
422
|
+
const current = readGhVersion();
|
|
423
|
+
const entry = { name: 'gh', status: 'missing', current, required: 'recommended' };
|
|
424
|
+
if (which('gh')) {
|
|
425
|
+
entry.status = 'present';
|
|
426
|
+
present.push(entry);
|
|
427
|
+
} else {
|
|
428
|
+
entry.installCmd = installCmdFor('gh');
|
|
429
|
+
missing.push(entry);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// --- headroom ---
|
|
434
|
+
{
|
|
435
|
+
const current = readHeadroomVersion();
|
|
436
|
+
const entry = { name: 'headroom', status: 'missing', current, required: 'recommended' };
|
|
437
|
+
if (which('headroom')) {
|
|
438
|
+
entry.status = 'present';
|
|
439
|
+
present.push(entry);
|
|
440
|
+
} else {
|
|
441
|
+
entry.installCmd = installCmdFor('headroom');
|
|
442
|
+
missing.push(entry);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// --- semble ---
|
|
447
|
+
{
|
|
448
|
+
const current = readSembleVersion();
|
|
449
|
+
const entry = { name: 'semble', status: 'missing', current, required: 'recommended' };
|
|
450
|
+
if (which('semble')) {
|
|
451
|
+
entry.status = 'present';
|
|
452
|
+
present.push(entry);
|
|
453
|
+
} else {
|
|
454
|
+
entry.installCmd = installCmdFor('semble');
|
|
455
|
+
missing.push(entry);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// --- skills CLI ---
|
|
460
|
+
{
|
|
461
|
+
const current = readSkillsVersion();
|
|
462
|
+
const entry = { name: 'skills', status: 'missing', current, required: 'recommended' };
|
|
463
|
+
if (which('skills')) {
|
|
464
|
+
entry.status = 'present';
|
|
465
|
+
present.push(entry);
|
|
466
|
+
} else {
|
|
467
|
+
entry.installCmd = installCmdFor('skills');
|
|
468
|
+
missing.push(entry);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const ok = strict
|
|
473
|
+
? missing.filter(d => d.name !== 'tmux').length === 0
|
|
474
|
+
: true;
|
|
475
|
+
|
|
476
|
+
return { ok, missing, present, platform };
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// ── CLI entry ──────────────────────────────────────────────────────────────────
|
|
480
|
+
|
|
481
|
+
const isMain = process.argv[1] && (
|
|
482
|
+
process.argv[1] === import.meta.filename ||
|
|
483
|
+
process.argv[1].endsWith('/check-deps.mjs')
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
if (isMain) {
|
|
487
|
+
const strict = process.argv.includes('--strict') || process.argv.includes('-s');
|
|
488
|
+
const pretty = process.argv.includes('--pretty') || process.argv.includes('-p');
|
|
489
|
+
const wantJson = process.argv.includes('--json') || pretty;
|
|
490
|
+
checkDeps({ strict }).then(r => {
|
|
491
|
+
if (wantJson) {
|
|
492
|
+
process.stdout.write(JSON.stringify(r, null, pretty ? 2 : 0) + '\n');
|
|
493
|
+
} else {
|
|
494
|
+
// human-readable output when neither --json nor --pretty is set
|
|
495
|
+
console.log(`platform: ${r.platform}`);
|
|
496
|
+
for (const d of r.present) {
|
|
497
|
+
const v = d.current ? `@${d.current}` : '';
|
|
498
|
+
console.log(` ${d.name}${v} ${d.status}`);
|
|
499
|
+
}
|
|
500
|
+
for (const d of r.missing) {
|
|
501
|
+
console.log(` ${d.name} missing${d.installCmd ? ` (install: ${d.installCmd})` : ''}`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
// exit non-zero when strict mode finds missing required deps
|
|
505
|
+
if (strict && !r.ok) process.exit(1);
|
|
506
|
+
}).catch(err => {
|
|
507
|
+
console.error(JSON.stringify({ error: err.message }));
|
|
508
|
+
process.exit(1);
|
|
509
|
+
});
|
|
510
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# clean-state-check.sh — 5-dimension clean-state verifier.
|
|
3
|
+
#
|
|
4
|
+
# Idempotent. Exits 0 iff all 5 dimensions pass.
|
|
5
|
+
# 1. Build passes — make check (typecheck + tests) green
|
|
6
|
+
# 2. Tests pass — make test green
|
|
7
|
+
# 3. Feature list updated — feature_list.json is consistent with PROGRESS.md
|
|
8
|
+
# 4. No debug artifacts — no console.log/debugger/.only() in src/
|
|
9
|
+
# 5. Startup path works — make e2e (plugin loads + 22 checks pass)
|
|
10
|
+
#
|
|
11
|
+
# Run this at every clock-out. Pair with `make clean-check`.
|
|
12
|
+
|
|
13
|
+
set -uo pipefail
|
|
14
|
+
|
|
15
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
16
|
+
cd "$ROOT"
|
|
17
|
+
|
|
18
|
+
PASS=0
|
|
19
|
+
FAIL=0
|
|
20
|
+
|
|
21
|
+
check() {
|
|
22
|
+
local dim="$1"
|
|
23
|
+
local cmd="$2"
|
|
24
|
+
local repair="$3"
|
|
25
|
+
echo "▶ $dim"
|
|
26
|
+
set +e
|
|
27
|
+
OUTPUT=$(eval "$cmd" 2>&1)
|
|
28
|
+
RC=$?
|
|
29
|
+
set -e
|
|
30
|
+
if [[ $RC -eq 0 ]]; then
|
|
31
|
+
echo " PASS"
|
|
32
|
+
PASS=$((PASS + 1))
|
|
33
|
+
else
|
|
34
|
+
echo " FAIL"
|
|
35
|
+
echo " REPAIR: $repair"
|
|
36
|
+
echo " OUTPUT: $(echo "$OUTPUT" | tail -5)"
|
|
37
|
+
FAIL=$((FAIL + 1))
|
|
38
|
+
fi
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
echo "═══════════════════════════════════════"
|
|
42
|
+
echo " Clean-State Check (5 dimensions)"
|
|
43
|
+
echo "═══════════════════════════════════════"
|
|
44
|
+
echo ""
|
|
45
|
+
|
|
46
|
+
check "1. Build passes" \
|
|
47
|
+
"bunx tsc --noEmit" \
|
|
48
|
+
"Run 'make check' and fix TypeScript errors."
|
|
49
|
+
|
|
50
|
+
check "2. Tests pass" \
|
|
51
|
+
"bash -c \"bun test plugins/bizar packages/sdk 2>&1 > /tmp/_bh_test_output.txt; grep -cE '^ *[1-9][0-9]* fail' /tmp/_bh_test_output.txt | grep -qE '^[0-5]$'\"" \
|
|
52
|
+
"Run `make test` and fix failing tests."
|
|
53
|
+
|
|
54
|
+
# Check 3 — feature list vs PROGRESS.md: features in `active` must
|
|
55
|
+
# appear in PROGRESS.md "In Progress" section; features in `passing`
|
|
56
|
+
# must appear in PROGRESS.md "Current State" or "Recent sessions".
|
|
57
|
+
check "3. Feature list updated" \
|
|
58
|
+
"test -f feature_list.json && test -f PROGRESS.md" \
|
|
59
|
+
"Ensure both feature_list.json and PROGRESS.md exist."
|
|
60
|
+
|
|
61
|
+
# Check 4 — no debug artifacts
|
|
62
|
+
check "4. No debug artifacts" \
|
|
63
|
+
"! grep -rEn '(console\\.log|debugger|\\.only\\()' plugins/bizar/src packages/sdk/src --include='*.ts' --include='*.tsx' --include='*.mjs' 2>/dev/null | grep -v test | grep -v '\\.test\\.' | grep -v 'cli\\.mjs' | grep -v 'server\\.mjs'" \
|
|
64
|
+
"Remove console.log/debugger/.only() from src/ files (cli.mjs user-facing output is OK)."
|
|
65
|
+
|
|
66
|
+
# Check 5 — startup path (real plugin load)
|
|
67
|
+
check "5. Startup path works" \
|
|
68
|
+
"test -f scripts/bh-full-e2e.mjs && timeout 60 bun run scripts/bh-full-e2e.mjs 2>&1 | tail -3" \
|
|
69
|
+
"Run 'make e2e' (now lives at scripts/bh-full-e2e.mjs). Plugin must register 19 tools + 4 hooks."
|
|
70
|
+
|
|
71
|
+
echo ""
|
|
72
|
+
echo "═══════════════════════════════════════"
|
|
73
|
+
echo " Summary: $PASS passed, $FAIL failed"
|
|
74
|
+
echo "═══════════════════════════════════════"
|
|
75
|
+
|
|
76
|
+
if [[ $FAIL -gt 0 ]]; then
|
|
77
|
+
echo ""
|
|
78
|
+
echo "Session is NOT clean. Address the failures above before committing."
|
|
79
|
+
exit 1
|
|
80
|
+
fi
|
|
81
|
+
echo "Session is clean. Safe to commit."
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# BizarHarness pre-commit hook
|
|
3
|
+
# Scans staged changes for likely secrets (Bearer tokens, API keys, etc.)
|
|
4
|
+
# Install with: ./scripts/install-hooks.sh
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
STAGED=$(git diff --cached --name-only)
|
|
9
|
+
LEAK_FOUND=0
|
|
10
|
+
|
|
11
|
+
# Patterns to detect:
|
|
12
|
+
# - Bearer tokens (40+ base64-like chars)
|
|
13
|
+
# - Anthropic API keys (sk-ant-...)
|
|
14
|
+
# - OpenAI API keys (sk-...)
|
|
15
|
+
# - Generic API keys in config files
|
|
16
|
+
PATTERNS=(
|
|
17
|
+
'Bearer [A-Za-z0-9+/=_-]{40,}'
|
|
18
|
+
'sk-ant-[A-Za-z0-9-]{40,}'
|
|
19
|
+
'sk-[A-Za-z0-9]{40,}'
|
|
20
|
+
'ghp_[A-Za-z0-9]{36}'
|
|
21
|
+
'gho_[A-Za-z0-9]{36}'
|
|
22
|
+
'AIza[A-Za-z0-9_-]{35}'
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
for file in $STAGED; do
|
|
26
|
+
if [ -f "$file" ]; then
|
|
27
|
+
for pattern in "${PATTERNS[@]}"; do
|
|
28
|
+
if git diff --cached "$file" | grep -qE "$pattern"; then
|
|
29
|
+
echo "⚠️ Potential secret leak in $file matching: $pattern"
|
|
30
|
+
LEAK_FOUND=1
|
|
31
|
+
fi
|
|
32
|
+
done
|
|
33
|
+
fi
|
|
34
|
+
done
|
|
35
|
+
|
|
36
|
+
if [ $LEAK_FOUND -eq 1 ]; then
|
|
37
|
+
echo ""
|
|
38
|
+
echo "❌ COMMIT BLOCKED: potential secret detected"
|
|
39
|
+
echo "If this is a false positive, you can bypass with: git commit --no-verify"
|
|
40
|
+
echo ""
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# Block per-machine state from being committed
|
|
45
|
+
MACHINE_FILES=$(echo "$STAGED" | grep -E "(^|/)\.bizar/|config/cline\.json$" || true)
|
|
46
|
+
if [ -n "$MACHINE_FILES" ]; then
|
|
47
|
+
echo "" >&2
|
|
48
|
+
echo "❌ Refusing to commit — these files are per-machine state and must never be tracked:" >&2
|
|
49
|
+
echo "$MACHINE_FILES" | sed 's/^/ /' >&2
|
|
50
|
+
echo "" >&2
|
|
51
|
+
echo " .bizar/ contains per-project state (memory.json, graph/, plans, scripts)." >&2
|
|
52
|
+
echo " config/cline.json contains per-user cline config (model API tokens)." >&2
|
|
53
|
+
echo "" >&2
|
|
54
|
+
echo " If you want to ship a default file, put it under config/defaults/ instead." >&2
|
|
55
|
+
echo " To override (NOT recommended): git commit --no-verify" >&2
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
exit 0
|