@polderlabs/bizar 6.2.4 → 6.3.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/.claude/CLAUDE.md +15 -0
- package/.claude/agents/_shared/AGENT_BASELINE.md +168 -0
- package/.claude/agents/_shared/CLAUDE_TOOLS.md +412 -0
- package/.claude/agents/_shared/SKILLS.md +109 -0
- package/.claude/agents/agent-browser.md +80 -0
- package/.claude/agents/baldr.md +49 -0
- package/.claude/agents/forseti.md +51 -0
- package/.claude/agents/frigg.md +42 -0
- package/.claude/agents/heimdall.md +33 -0
- package/.claude/agents/hermod.md +53 -0
- package/.claude/agents/mimir.md +49 -0
- package/.claude/agents/odin.md +287 -0
- package/.claude/agents/quick.md +34 -0
- package/.claude/agents/semble-search.md +50 -0
- package/.claude/agents/thor.md +53 -0
- package/.claude/agents/tyr.md +56 -0
- package/.claude/agents/vidarr.md +54 -0
- package/.claude/agents/vor.md +53 -0
- package/.claude/commands/audit.md +25 -0
- package/.claude/commands/bizar.md +22 -0
- package/.claude/commands/explain.md +17 -0
- package/.claude/commands/init.md +29 -0
- package/.claude/commands/learn.md +44 -0
- package/.claude/commands/plan.md +35 -0
- package/.claude/commands/plow-through.md +50 -0
- package/.claude/commands/pr-review.md +49 -0
- package/.claude/commands/setup-provider.md +96 -0
- package/.claude/commands/tailscale-serve.md +100 -0
- package/.claude/commands/team.md +132 -0
- package/.claude/commands/test.md +62 -0
- package/.claude/commands/validate.md +68 -0
- package/.claude/commands/visual-plan.md +24 -0
- package/.claude/hooks/README.md +92 -0
- package/.claude/hooks/posttooluse-editwrite.mjs +91 -0
- package/.claude/hooks/pretooluse-bash.mjs +81 -0
- package/.claude/hooks/pretooluse-editwrite.mjs +139 -0
- package/.claude/hooks/sessionend-recall.mjs +74 -0
- package/.claude/hooks/sessionstart-prime.mjs +80 -0
- package/.claude/hooks/userpromptsubmit-tag.mjs +80 -0
- package/.claude/settings.json +116 -0
- package/.claude/skills/9router/SKILL.md +80 -0
- package/.claude/skills/9router-chat/SKILL.md +73 -0
- package/.claude/skills/9router-embeddings/SKILL.md +69 -0
- package/.claude/skills/9router-image/SKILL.md +86 -0
- package/.claude/skills/9router-stt/SKILL.md +79 -0
- package/.claude/skills/9router-tts/SKILL.md +80 -0
- package/.claude/skills/9router-web-fetch/SKILL.md +99 -0
- package/.claude/skills/9router-web-search/SKILL.md +91 -0
- package/.claude/skills/bizar/README.md +9 -0
- package/.claude/skills/bizar/SKILL.md +450 -0
- package/.claude/skills/cpp-coding-standards/README.md +28 -0
- package/.claude/skills/cpp-coding-standards/SKILL.md +634 -0
- package/.claude/skills/cpp-coding-standards/references/concurrency.md +320 -0
- package/.claude/skills/cpp-coding-standards/references/error-handling.md +229 -0
- package/.claude/skills/cpp-coding-standards/references/memory-safety.md +216 -0
- package/.claude/skills/cpp-coding-standards/references/modern-idioms.md +282 -0
- package/.claude/skills/cpp-coding-standards/references/review-checklist.md +96 -0
- package/.claude/skills/cpp-testing/README.md +28 -0
- package/.claude/skills/cpp-testing/SKILL.md +304 -0
- package/.claude/skills/cpp-testing/references/coverage.md +370 -0
- package/.claude/skills/cpp-testing/references/framework-compare.md +175 -0
- package/.claude/skills/cpp-testing/references/host-test-for-embedded.md +499 -0
- package/.claude/skills/cpp-testing/references/mocking.md +364 -0
- package/.claude/skills/cpp-testing/references/tdd-workflow.md +308 -0
- package/.claude/skills/cubesandbox/SKILL.md +148 -0
- package/.claude/skills/embedded-esp-idf/README.md +41 -0
- package/.claude/skills/embedded-esp-idf/SKILL.md +439 -0
- package/.claude/skills/embedded-esp-idf/references/freertos-patterns.md +214 -0
- package/.claude/skills/embedded-esp-idf/references/host-tests.md +164 -0
- package/.claude/skills/embedded-esp-idf/references/idf-py-commands.md +157 -0
- package/.claude/skills/embedded-esp-idf/references/kconfig.md +159 -0
- package/.claude/skills/embedded-esp-idf/references/logging-discipline.md +118 -0
- package/.claude/skills/embedded-esp-idf/references/memory-and-iram.md +137 -0
- package/.claude/skills/embedded-esp-idf/references/nvs.md +121 -0
- package/.claude/skills/embedded-esp-idf/references/packed-structs.md +192 -0
- package/.claude/skills/embedded-esp-idf/scripts/idf_env.sh +47 -0
- package/.claude/skills/embedded-esp-idf/scripts/size_check.sh +77 -0
- package/.claude/skills/glyph/SKILL.md +163 -0
- package/.claude/skills/harness-engineering/SKILL.md +143 -0
- package/.claude/skills/lightrag/SKILL.md +81 -0
- package/.claude/skills/memory-protocol/SKILL.md +105 -0
- package/.claude/skills/obsidian/SKILL.md +306 -0
- package/.claude/skills/read-the-damn-docs/SKILL.md +113 -0
- package/.claude/skills/self-improvement/SKILL.md +64 -0
- package/README.md +87 -59
- package/bizar-dash/dist/assets/{EnvVarsSection-DXM8gRm_.js → EnvVarsSection-B58aiJiE.js} +1 -1
- package/bizar-dash/dist/assets/{EnvVarsSection-DXM8gRm_.js.map → EnvVarsSection-B58aiJiE.js.map} +1 -1
- package/bizar-dash/dist/assets/{MobileChat-BnKN_Ks_.js → MobileChat-BJrqwVDd.js} +1 -1
- package/bizar-dash/dist/assets/{MobileChat-BnKN_Ks_.js.map → MobileChat-BJrqwVDd.js.map} +1 -1
- package/bizar-dash/dist/assets/{MobileSettings-DjCPxC-Q.js → MobileSettings-CEQNJNLJ.js} +1 -1
- package/bizar-dash/dist/assets/{MobileSettings-DjCPxC-Q.js.map → MobileSettings-CEQNJNLJ.js.map} +1 -1
- package/bizar-dash/dist/assets/{main-DYiZqMrn.js → main-IvfQAOfy.js} +1 -1
- package/bizar-dash/dist/assets/{main-DYiZqMrn.js.map → main-IvfQAOfy.js.map} +1 -1
- package/bizar-dash/dist/assets/{markdown-C6mXtQxD.js → markdown-tOLaD6nm.js} +1 -1
- package/bizar-dash/dist/assets/{markdown-C6mXtQxD.js.map → markdown-tOLaD6nm.js.map} +1 -1
- package/bizar-dash/dist/assets/{mobile-CMHqtLV2.js → mobile-DYCHcUpq.js} +1 -1
- package/bizar-dash/dist/assets/{mobile-CMHqtLV2.js.map → mobile-DYCHcUpq.js.map} +1 -1
- package/bizar-dash/dist/assets/{mobile-layout-3jIhHX_p.js → mobile-layout-CBHjpwsb.js} +2 -2
- package/bizar-dash/dist/assets/{mobile-layout-3jIhHX_p.js.map → mobile-layout-CBHjpwsb.js.map} +1 -1
- package/bizar-dash/dist/assets/{useSlashCommands-DtITw8Xv.js → useSlashCommands-Bd7_FA6U.js} +2 -2
- package/bizar-dash/dist/assets/{useSlashCommands-DtITw8Xv.js.map → useSlashCommands-Bd7_FA6U.js.map} +1 -1
- package/bizar-dash/dist/assets/{vendor-CeHGtduv.js → vendor-C843201K.js} +12 -12
- package/bizar-dash/dist/assets/vendor-C843201K.js.map +1 -0
- package/bizar-dash/dist/index.html +6 -6
- package/bizar-dash/dist/mobile.html +2 -2
- package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/bizar-dash/src/server/api.mjs +4 -4
- package/bizar-dash/src/server/background-store.mjs +15 -16
- package/bizar-dash/src/server/bg-poller.mjs +25 -35
- package/bizar-dash/src/server/bg-retry.mjs +66 -194
- package/bizar-dash/src/server/claude-bg-spawner.mjs +390 -0
- package/bizar-dash/src/server/claude-info.mjs +412 -0
- package/bizar-dash/src/server/{cline-runner.mjs → claude-runner.mjs} +53 -48
- package/bizar-dash/src/server/claude-sdk.mjs +191 -0
- package/bizar-dash/src/server/providers-store.mjs +22 -49
- package/bizar-dash/src/server/routes/background.mjs +24 -20
- package/bizar-dash/src/server/routes/chat.mjs +223 -326
- package/bizar-dash/src/server/routes/claude-session-detail.mjs +329 -0
- package/bizar-dash/src/server/routes/claude-sessions.mjs +259 -0
- package/bizar-dash/src/server/routes/tasks.mjs +20 -28
- package/bizar-dash/src/server/task-delegator.mjs +76 -77
- package/bizar-dash/src/web/components/chat/useChat.ts +23 -27
- package/bizar-dash/src/web/views/Chat.tsx +8 -8
- package/cli/bin.mjs +31 -18
- package/cli/commands/claude-cmd.mjs +348 -0
- package/cli/commands/install.mjs +12 -11
- package/cli/commands/sandbox.mjs +220 -0
- package/cli/commands/validate.mjs +252 -288
- package/cli/dev-link.test.mjs +11 -11
- package/cli/doctor.mjs +131 -213
- package/cli/install.mjs +33 -37
- package/cli/install.test.mjs +9 -9
- package/cli/provision-claude.mjs +893 -0
- package/cli/provision.mjs +330 -226
- package/cli/provision.test.mjs +102 -0
- package/cli/utils.mjs +72 -58
- package/config/AGENTS.md +28 -28
- package/config/agents/_shared/AGENT_BASELINE.md +104 -684
- package/config/skills/bizar/SKILL.md +197 -0
- package/config/skills/cubesandbox/SKILL.md +148 -0
- package/config/skills/harness-engineering/SKILL.md +142 -0
- package/install.sh +66 -44
- package/package.json +19 -11
- package/packages/sdk/ARCHITECTURE.md +40 -36
- package/packages/sdk/package-lock.json +32 -0
- package/packages/sdk/package.json +15 -5
- package/{plugins/bizar → packages/sdk}/src/dangerous-patterns.ts +6 -28
- package/packages/sdk/src/fingerprint.ts +0 -0
- package/packages/sdk/src/index.ts +38 -73
- package/packages/sdk/src/mcp/bin.ts +51 -0
- package/packages/sdk/src/mcp/server.ts +498 -0
- package/{plugins/bizar/src/memory-vault.ts → packages/sdk/src/memory/index.ts} +18 -21
- package/packages/sdk/tests/sdk.test.mjs +148 -0
- package/packages/sdk/vitest.config.ts +1 -1
- package/scripts/bh-full-e2e.mjs +166 -363
- package/scripts/check-deps.mjs +28 -46
- package/scripts/mirror-claude-md.sh +78 -0
- package/scripts/test-in-container.sh +157 -0
- package/templates/clean-state-checklist.md +3 -3
- package/templates/sprint-contract.md +2 -2
- package/bizar-dash/.bizar/activity.log +0 -3
- package/bizar-dash/.omx/logs/omx-2026-07-07.jsonl +0 -1
- package/bizar-dash/.omx/state/session.json +0 -10
- package/bizar-dash/bizar-design/EXPLANATION.md +0 -307
- package/bizar-dash/bizar-design/INSTRUCTIONS.md +0 -149
- package/bizar-dash/bizar-design/canvas.html +0 -281
- package/bizar-dash/bizar-design/canvas.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/components.css +0 -1665
- package/bizar-dash/bizar-design/components.html +0 -557
- package/bizar-dash/bizar-design/components.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/desktop/agents.html +0 -74
- package/bizar-dash/bizar-design/desktop/agents.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/desktop/memory.html +0 -75
- package/bizar-dash/bizar-design/desktop/memory.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/desktop/metrics.html +0 -63
- package/bizar-dash/bizar-design/desktop/metrics.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/desktop/overview.html +0 -95
- package/bizar-dash/bizar-design/desktop/overview.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/desktop/settings.html +0 -70
- package/bizar-dash/bizar-design/desktop/settings.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/icons.svg +0 -44
- package/bizar-dash/bizar-design/index.html +0 -192
- package/bizar-dash/bizar-design/index.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/mobile/agents.html +0 -29
- package/bizar-dash/bizar-design/mobile/agents.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/mobile/memory.html +0 -28
- package/bizar-dash/bizar-design/mobile/memory.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/mobile/metrics.html +0 -29
- package/bizar-dash/bizar-design/mobile/metrics.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/mobile/overview.html +0 -33
- package/bizar-dash/bizar-design/mobile/overview.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/mobile/settings.html +0 -28
- package/bizar-dash/bizar-design/mobile/settings.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/prototype.js +0 -114
- package/bizar-dash/bizar-design/tokens.css +0 -106
- package/bizar-dash/dist/assets/vendor-CeHGtduv.js.map +0 -1
- package/bizar-dash/node_modules/.package-lock.json +0 -6
- package/bizar-dash/package-lock.json +0 -6
- package/bizar-dash/src/server/cline-sdk.mjs +0 -132
- package/bizar-dash/src/server/routes/cline-session-detail.mjs +0 -559
- package/bizar-dash/src/server/routes/cline-sessions.mjs +0 -291
- package/cli/commands/cline-cmd.mjs +0 -289
- package/cli/commands/validate.test.mjs +0 -344
- package/cli/doctor.test.mjs +0 -350
- package/config/cline.json.template +0 -342
- package/packages/sdk/src/client.ts +0 -188
- package/packages/sdk/src/cline-events.ts +0 -134
- package/packages/sdk/src/cline-types.ts +0 -66
- package/packages/sdk/src/cline.ts +0 -339
- package/packages/sdk/src/errors.ts +0 -129
- package/packages/sdk/src/events.ts +0 -153
- package/packages/sdk/src/types.ts +0 -176
- package/packages/sdk/tests/client.test.ts +0 -217
- package/packages/sdk/tests/errors.test.ts +0 -108
- package/packages/sdk/tests/events.test.ts +0 -139
- package/packages/sdk/tests/fixtures/fetch-mock.ts +0 -152
- package/packages/sdk/tests/fixtures/sse-mock.ts +0 -30
- package/plugins/bizar/ARCHITECTURE.md +0 -142
- package/plugins/bizar/CONSTRAINTS.md +0 -67
- package/plugins/bizar/LICENSE +0 -21
- package/plugins/bizar/README.md +0 -448
- package/plugins/bizar/index.ts +0 -872
- package/plugins/bizar/package.json +0 -39
- package/plugins/bizar/scripts/check-forbidden-imports.sh +0 -33
- package/plugins/bizar/src/background-state.ts +0 -641
- package/plugins/bizar/src/background.ts +0 -1806
- package/plugins/bizar/src/cline-runner.ts +0 -203
- package/plugins/bizar/src/clineruntime.ts +0 -227
- package/plugins/bizar/src/commands-impl.ts +0 -151
- package/plugins/bizar/src/commands.ts +0 -1799
- package/plugins/bizar/src/compaction.d.mts +0 -48
- package/plugins/bizar/src/compaction.mjs +0 -192
- package/plugins/bizar/src/dashboard-client.ts +0 -233
- package/plugins/bizar/src/event-stream.ts +0 -606
- package/plugins/bizar/src/fingerprint.ts +0 -120
- package/plugins/bizar/src/handoff.ts +0 -79
- package/plugins/bizar/src/hooks/memory-flush-on-compact.ts +0 -123
- package/plugins/bizar/src/hooks/memory-inject.ts +0 -247
- package/plugins/bizar/src/hooks/memory-write-on-end.ts +0 -188
- package/plugins/bizar/src/hooks/skill-curator.ts +0 -180
- package/plugins/bizar/src/http-client.ts +0 -467
- package/plugins/bizar/src/key-rotation.ts +0 -218
- package/plugins/bizar/src/logger.ts +0 -144
- package/plugins/bizar/src/loop-engineering.ts +0 -241
- package/plugins/bizar/src/loop.ts +0 -176
- package/plugins/bizar/src/mistake-recovery.ts +0 -98
- package/plugins/bizar/src/odin.ts +0 -227
- package/plugins/bizar/src/options.ts +0 -470
- package/plugins/bizar/src/plan-fs.ts +0 -323
- package/plugins/bizar/src/reasoning-clean.ts +0 -454
- package/plugins/bizar/src/report.ts +0 -178
- package/plugins/bizar/src/research-prompt.ts +0 -35
- package/plugins/bizar/src/serve-info.ts +0 -228
- package/plugins/bizar/src/serve.ts +0 -496
- package/plugins/bizar/src/settings.ts +0 -349
- package/plugins/bizar/src/state.ts +0 -298
- package/plugins/bizar/src/tool-discipline.ts +0 -105
- package/plugins/bizar/src/tools/agent-browser.ts +0 -315
- package/plugins/bizar/src/tools/bg-collect.ts +0 -131
- package/plugins/bizar/src/tools/bg-get-comments.ts +0 -266
- package/plugins/bizar/src/tools/bg-kill.ts +0 -116
- package/plugins/bizar/src/tools/bg-pause.ts +0 -99
- package/plugins/bizar/src/tools/bg-report-progress.ts +0 -115
- package/plugins/bizar/src/tools/bg-resume.ts +0 -94
- package/plugins/bizar/src/tools/bg-send-message.ts +0 -223
- package/plugins/bizar/src/tools/bg-spawn.ts +0 -502
- package/plugins/bizar/src/tools/bg-status.ts +0 -130
- package/plugins/bizar/src/tools/graph-query.ts +0 -278
- package/plugins/bizar/src/tools/loop-engineering.ts +0 -193
- package/plugins/bizar/src/tools/memory-list.ts +0 -43
- package/plugins/bizar/src/tools/memory-read.ts +0 -69
- package/plugins/bizar/src/tools/memory-search.ts +0 -47
- package/plugins/bizar/src/tools/memory-write.ts +0 -54
- package/plugins/bizar/src/tools/open-kb.ts +0 -198
- package/plugins/bizar/src/tools/plan-action.ts +0 -785
- package/plugins/bizar/src/tools/read-glyph-feedback.ts +0 -191
- package/plugins/bizar/src/tools/team-spawn.ts +0 -73
- package/plugins/bizar/src/tools/team-status.ts +0 -76
- package/plugins/bizar/src/tools/wait-for-feedback.ts +0 -415
- package/plugins/bizar/src/trajectory.ts +0 -104
- package/plugins/bizar/tests/README.md +0 -99
- package/plugins/bizar/tests/attach-handler-bug.test.ts +0 -169
- package/plugins/bizar/tests/background-state.test.ts +0 -277
- package/plugins/bizar/tests/background.test.ts +0 -402
- package/plugins/bizar/tests/block.test.ts +0 -195
- package/plugins/bizar/tests/canonical-key-order.test.ts +0 -75
- package/plugins/bizar/tests/clineruntime-config.test.ts +0 -283
- package/plugins/bizar/tests/commands-impl.test.ts +0 -316
- package/plugins/bizar/tests/commands.test.ts +0 -584
- package/plugins/bizar/tests/compaction.test.ts +0 -264
- package/plugins/bizar/tests/config.test.ts +0 -128
- package/plugins/bizar/tests/dashboard-client.test.ts +0 -159
- package/plugins/bizar/tests/dispose.test.ts +0 -336
- package/plugins/bizar/tests/event-stream.test.ts +0 -409
- package/plugins/bizar/tests/event.test.ts +0 -262
- package/plugins/bizar/tests/fingerprint.test.ts +0 -162
- package/plugins/bizar/tests/http-client.test.ts +0 -404
- package/plugins/bizar/tests/init-helpers.test.ts +0 -203
- package/plugins/bizar/tests/integration/slash-command.test.ts +0 -349
- package/plugins/bizar/tests/integration/tool-routing.test.ts +0 -98
- package/plugins/bizar/tests/key-rotation.test.ts +0 -396
- package/plugins/bizar/tests/loop-engineering.test.ts +0 -168
- package/plugins/bizar/tests/loop.test.ts +0 -397
- package/plugins/bizar/tests/memory-write-on-end.test.ts +0 -92
- package/plugins/bizar/tests/mistake-recovery.test.ts +0 -116
- package/plugins/bizar/tests/odin.test.ts +0 -125
- package/plugins/bizar/tests/options.test.ts +0 -329
- package/plugins/bizar/tests/reasoning-clean.test.ts +0 -422
- package/plugins/bizar/tests/safety.test.ts +0 -256
- package/plugins/bizar/tests/serve.test.ts +0 -339
- package/plugins/bizar/tests/settings.test.ts +0 -351
- package/plugins/bizar/tests/stall-think.test.ts +0 -750
- package/plugins/bizar/tests/state.test.ts +0 -276
- package/plugins/bizar/tests/tool-discipline.test.ts +0 -77
- package/plugins/bizar/tests/tools/agent-browser.test.ts +0 -98
- package/plugins/bizar/tests/tools/bg-collect.test.ts +0 -337
- package/plugins/bizar/tests/tools/bg-get-comments.test.ts +0 -485
- package/plugins/bizar/tests/tools/bg-kill.test.ts +0 -235
- package/plugins/bizar/tests/tools/bg-pause.test.ts +0 -61
- package/plugins/bizar/tests/tools/bg-report-progress.test.ts +0 -79
- package/plugins/bizar/tests/tools/bg-resume.test.ts +0 -40
- package/plugins/bizar/tests/tools/bg-send-message.test.ts +0 -116
- package/plugins/bizar/tests/tools/bg-spawn-delegation.test.ts +0 -147
- package/plugins/bizar/tests/tools/bg-spawn-http.test.ts +0 -233
- package/plugins/bizar/tests/tools/bg-spawn.test.ts +0 -311
- package/plugins/bizar/tests/tools/bg-status.test.ts +0 -217
- package/plugins/bizar/tests/tools/cline-runner.test.ts +0 -115
- package/plugins/bizar/tests/tools/plan-action.test.ts +0 -599
- package/plugins/bizar/tests/tools/read-glyph-feedback.test.ts +0 -253
- package/plugins/bizar/tests/tools/wait-for-feedback.test.ts +0 -390
- package/plugins/bizar/tests/update-deadlock.test.ts +0 -151
- package/plugins/bizar/tsconfig.json +0 -29
|
@@ -1,344 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* cli/commands/validate.test.mjs
|
|
3
|
-
*
|
|
4
|
-
* v6.2.0 — Unit tests for the new `bizar validate` subcommand.
|
|
5
|
-
*
|
|
6
|
-
* We exercise `runValidate()` directly. CLINE_DIR is mocked via
|
|
7
|
-
* `process.env.CLINE_DIR`; a minimal-but-complete Bizar install is
|
|
8
|
-
* staged inside a tmpdir. We then progressively delete files to
|
|
9
|
-
* verify each check fails with a sensible message.
|
|
10
|
-
*/
|
|
11
|
-
import { test, describe, beforeEach, afterEach } from 'node:test';
|
|
12
|
-
import assert from 'node:assert/strict';
|
|
13
|
-
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync, readFileSync, chmodSync } from 'node:fs';
|
|
14
|
-
import { tmpdir } from 'node:os';
|
|
15
|
-
import { join } from 'node:path';
|
|
16
|
-
import { Writable } from 'node:stream';
|
|
17
|
-
|
|
18
|
-
const { runValidate } = await import('./validate.mjs');
|
|
19
|
-
|
|
20
|
-
const ORIG_CLINE_DIR = process.env.CLINE_DIR;
|
|
21
|
-
const ORIG_HOME = process.env.HOME;
|
|
22
|
-
|
|
23
|
-
let workDir;
|
|
24
|
-
|
|
25
|
-
function freshWorkDir() {
|
|
26
|
-
const root = mkdtempSync(join(tmpdir(), 'bizar-validate-'));
|
|
27
|
-
process.env.CLINE_DIR = root;
|
|
28
|
-
return root;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function makeFakeClineInstall(root) {
|
|
32
|
-
writeFileSync(
|
|
33
|
-
join(root, 'cline.json'),
|
|
34
|
-
JSON.stringify({
|
|
35
|
-
$schema: 'https://docs.cline.bot/config.json',
|
|
36
|
-
plugin: [[
|
|
37
|
-
'./plugins/bizar',
|
|
38
|
-
{ loopThresholdWarn: 5, loopThresholdEscalate: 8, loopThresholdBlock: 12, loopWindowSize: 10 },
|
|
39
|
-
]],
|
|
40
|
-
default_agent: 'odin',
|
|
41
|
-
permission: 'allow',
|
|
42
|
-
snapshot: false,
|
|
43
|
-
instructions: ['.cline/instructions/bizar-tools.md'],
|
|
44
|
-
provider: {
|
|
45
|
-
'9router': {
|
|
46
|
-
baseUrl: 'http://localhost:20128/v1',
|
|
47
|
-
apiKey: '${env:NINEROUTER_KEY}',
|
|
48
|
-
models: { 'minimax/MiniMax-M3': { reasoning: true } },
|
|
49
|
-
},
|
|
50
|
-
minimax: { models: {} },
|
|
51
|
-
},
|
|
52
|
-
}, null, 2),
|
|
53
|
-
);
|
|
54
|
-
const agentsDir = join(root, 'agents');
|
|
55
|
-
mkdirSync(agentsDir, { recursive: true });
|
|
56
|
-
for (const f of [
|
|
57
|
-
'odin.md', 'vor.md', 'frigg.md', 'quick.md',
|
|
58
|
-
'mimir.md', 'heimdall.md', 'hermod.md', 'thor.md', 'baldr.md',
|
|
59
|
-
'tyr.md', 'vidarr.md', 'forseti.md',
|
|
60
|
-
'semble-search.md', 'agent-browser.md',
|
|
61
|
-
]) {
|
|
62
|
-
writeFileSync(join(agentsDir, f), '# fake agent');
|
|
63
|
-
}
|
|
64
|
-
writeFileSync(join(agentsDir, 'odin.yaml'), 'name: odin\ndescription: odin\n');
|
|
65
|
-
const cmdsDir = join(root, 'commands');
|
|
66
|
-
mkdirSync(cmdsDir, { recursive: true });
|
|
67
|
-
for (const f of [
|
|
68
|
-
'audit.md', 'bizar.md', 'explain.md', 'init.md', 'learn.md',
|
|
69
|
-
'plan.md', 'plow-through.md', 'pr-review.md', 'tailscale-serve.md',
|
|
70
|
-
'visual-plan.md',
|
|
71
|
-
'team.md', 'test.md', 'validate.md', 'setup-provider.md',
|
|
72
|
-
]) {
|
|
73
|
-
writeFileSync(join(cmdsDir, f), '# fake command');
|
|
74
|
-
}
|
|
75
|
-
const skillsDir = join(root, 'skills');
|
|
76
|
-
mkdirSync(skillsDir, { recursive: true });
|
|
77
|
-
for (const skill of ['9router', 'bizar', 'obsidian']) {
|
|
78
|
-
mkdirSync(join(skillsDir, skill), { recursive: true });
|
|
79
|
-
writeFileSync(join(skillsDir, skill, 'SKILL.md'), '# fake skill');
|
|
80
|
-
}
|
|
81
|
-
const rulesDir = join(root, 'rules');
|
|
82
|
-
mkdirSync(rulesDir, { recursive: true });
|
|
83
|
-
for (const r of ['general.md', 'git.md', 'javascript.md', 'python.md', 'testing.md', 'thinking.md', 'uncertainty.md']) {
|
|
84
|
-
writeFileSync(join(rulesDir, r), '# fake rule');
|
|
85
|
-
}
|
|
86
|
-
const hooksDir = join(root, 'hooks');
|
|
87
|
-
mkdirSync(hooksDir, { recursive: true });
|
|
88
|
-
for (const h of ['PreToolUse', 'PostToolUse', 'TaskStart', 'TaskResume', 'UserPromptSubmit']) {
|
|
89
|
-
// Cline hooks must be executable scripts with a shebang line.
|
|
90
|
-
writeFileSync(join(hooksDir, h), '#!/usr/bin/env node\n// fake hook\n');
|
|
91
|
-
chmodSync(join(hooksDir, h), 0o755);
|
|
92
|
-
}
|
|
93
|
-
const pluginDir = join(root, 'plugins', 'bizar');
|
|
94
|
-
mkdirSync(pluginDir, { recursive: true });
|
|
95
|
-
writeFileSync(join(pluginDir, 'index.ts'), '// fake plugin entry — >100 bytes to pass size check\n'.repeat(10));
|
|
96
|
-
mkdirSync(join(pluginDir, 'src'), { recursive: true });
|
|
97
|
-
writeFileSync(
|
|
98
|
-
join(pluginDir, 'src', 'clineruntime.ts'),
|
|
99
|
-
'export class ClineRuntime { config = { enableAgentTeams: true }; }\n',
|
|
100
|
-
);
|
|
101
|
-
// node_modules with the required runtime deps
|
|
102
|
-
const nmDir = join(pluginDir, 'node_modules');
|
|
103
|
-
mkdirSync(nmDir, { recursive: true });
|
|
104
|
-
mkdirSync(join(nmDir, 'zod'), { recursive: true });
|
|
105
|
-
writeFileSync(join(nmDir, 'zod', 'package.json'), '{}');
|
|
106
|
-
mkdirSync(join(nmDir, '@cline'), { recursive: true });
|
|
107
|
-
for (const dep of ['@cline/sdk', '@cline/core', '@cline/shared']) {
|
|
108
|
-
mkdirSync(join(nmDir, dep), { recursive: true });
|
|
109
|
-
writeFileSync(join(nmDir, dep, 'package.json'), '{}');
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
beforeEach(() => {
|
|
114
|
-
workDir = freshWorkDir();
|
|
115
|
-
makeFakeClineInstall(workDir);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
afterEach(() => {
|
|
119
|
-
if (workDir && existsSync(workDir)) rmSync(workDir, { recursive: true, force: true });
|
|
120
|
-
if (ORIG_CLINE_DIR === undefined) delete process.env.CLINE_DIR;
|
|
121
|
-
else process.env.CLINE_DIR = ORIG_CLINE_DIR;
|
|
122
|
-
if (ORIG_HOME === undefined) delete process.env.HOME;
|
|
123
|
-
else process.env.HOME = ORIG_HOME;
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Capture stdout + exit code for a single runValidate() call.
|
|
128
|
-
* The test runner writes its own progress to stdout, so we swap
|
|
129
|
-
* process.stdout for a sink Writable during the call.
|
|
130
|
-
*/
|
|
131
|
-
async function captureRun(opts) {
|
|
132
|
-
const captured = [];
|
|
133
|
-
const sink = new Writable({
|
|
134
|
-
write(chunk, _enc, cb) {
|
|
135
|
-
captured.push(Buffer.from(chunk));
|
|
136
|
-
cb();
|
|
137
|
-
},
|
|
138
|
-
});
|
|
139
|
-
const origStdoutDescriptor = Object.getOwnPropertyDescriptor(process, 'stdout');
|
|
140
|
-
const origExit = process.exit;
|
|
141
|
-
let exitCode = null;
|
|
142
|
-
Object.defineProperty(process, 'stdout', { value: sink, configurable: true, writable: true });
|
|
143
|
-
process.exit = (code) => { exitCode = code; };
|
|
144
|
-
try {
|
|
145
|
-
await runValidate(opts);
|
|
146
|
-
} finally {
|
|
147
|
-
if (origStdoutDescriptor) Object.defineProperty(process, 'stdout', origStdoutDescriptor);
|
|
148
|
-
process.exit = origExit;
|
|
149
|
-
}
|
|
150
|
-
return { stdout: Buffer.concat(captured).toString('utf8'), exitCode };
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
describe('runValidate() — JSON output', () => {
|
|
154
|
-
test('returns structured JSON with passed/failed/results', async () => {
|
|
155
|
-
const { stdout, exitCode } = await captureRun({ json: true });
|
|
156
|
-
assert.equal(exitCode, 0, 'should exit 0 on success');
|
|
157
|
-
const parsed = JSON.parse(stdout);
|
|
158
|
-
assert.equal(typeof parsed.passed, 'number');
|
|
159
|
-
assert.equal(typeof parsed.failed, 'number');
|
|
160
|
-
assert.ok(Array.isArray(parsed.results));
|
|
161
|
-
assert.ok(parsed.results.length >= 18, 'expected at least 18 checks');
|
|
162
|
-
for (const r of parsed.results) {
|
|
163
|
-
assert.ok(typeof r.name === 'string');
|
|
164
|
-
assert.ok(typeof r.ok === 'boolean');
|
|
165
|
-
assert.ok(typeof r.message === 'string');
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
test('fails on missing team command', async () => {
|
|
170
|
-
rmSync(join(workDir, 'commands', 'team.md'));
|
|
171
|
-
const { exitCode } = await captureRun({ json: true });
|
|
172
|
-
assert.equal(exitCode, 1, 'should exit 1 on team-command-present failure');
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
test('fails on missing test command', async () => {
|
|
176
|
-
rmSync(join(workDir, 'commands', 'test.md'));
|
|
177
|
-
const { exitCode } = await captureRun({ json: true });
|
|
178
|
-
assert.equal(exitCode, 1);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
test('fails on missing validate command', async () => {
|
|
182
|
-
rmSync(join(workDir, 'commands', 'validate.md'));
|
|
183
|
-
const { exitCode } = await captureRun({ json: true });
|
|
184
|
-
assert.equal(exitCode, 1);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
test('fails on missing agent file', async () => {
|
|
188
|
-
rmSync(join(workDir, 'agents', 'mimir.md'));
|
|
189
|
-
const { exitCode } = await captureRun({ json: true });
|
|
190
|
-
assert.equal(exitCode, 1);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
test('fails when cline.json missing', async () => {
|
|
194
|
-
rmSync(join(workDir, 'cline.json'));
|
|
195
|
-
const { exitCode } = await captureRun({ json: true });
|
|
196
|
-
assert.equal(exitCode, 1);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
test('fails when enableAgentTeams is false in clineruntime.ts', async () => {
|
|
200
|
-
writeFileSync(
|
|
201
|
-
join(workDir, 'plugins', 'bizar', 'src', 'clineruntime.ts'),
|
|
202
|
-
'export class ClineRuntime { config = { enableAgentTeams: false }; }\n',
|
|
203
|
-
);
|
|
204
|
-
const { exitCode } = await captureRun({ json: true });
|
|
205
|
-
assert.equal(exitCode, 1, 'should fail when enableAgentTeams is false');
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
test('passes when no provider is configured (v6.2.2+ — user must configure)', async () => {
|
|
209
|
-
// v6.2.2: the installer no longer touches provider config. The
|
|
210
|
-
// user must add their own provider. The provider-config check
|
|
211
|
-
// is therefore ALWAYS lenient — it just reports state.
|
|
212
|
-
const cfg = JSON.parse(readFileSync(join(workDir, 'cline.json'), 'utf8'));
|
|
213
|
-
delete cfg.provider;
|
|
214
|
-
writeFileSync(join(workDir, 'cline.json'), JSON.stringify(cfg, null, 2));
|
|
215
|
-
const { exitCode, stdout } = await captureRun({ json: true });
|
|
216
|
-
assert.equal(exitCode, 0, 'no-provider must not fail validation (v6.2.2+ contract)');
|
|
217
|
-
const parsed = JSON.parse(stdout);
|
|
218
|
-
const p = parsed.results.find((r) => r.name === 'provider-config');
|
|
219
|
-
assert.ok(p, 'provider-config check should be present');
|
|
220
|
-
assert.equal(p.ok, true, 'provider-config always returns ok (lenient)');
|
|
221
|
-
assert.match(p.message, /no provider configured/);
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
test('reports user-configured providers when present', async () => {
|
|
225
|
-
// User has set up their own provider (anything goes).
|
|
226
|
-
const cfg = JSON.parse(readFileSync(join(workDir, 'cline.json'), 'utf8'));
|
|
227
|
-
cfg.provider = {
|
|
228
|
-
'my-custom-provider': {
|
|
229
|
-
baseUrl: 'https://my-llm.example/v1',
|
|
230
|
-
apiKey: 'sk-test',
|
|
231
|
-
models: { 'my-model-1': {}, 'my-model-2': {} },
|
|
232
|
-
},
|
|
233
|
-
};
|
|
234
|
-
writeFileSync(join(workDir, 'cline.json'), JSON.stringify(cfg, null, 2));
|
|
235
|
-
const { exitCode, stdout } = await captureRun({ json: true });
|
|
236
|
-
assert.equal(exitCode, 0);
|
|
237
|
-
const parsed = JSON.parse(stdout);
|
|
238
|
-
const p = parsed.results.find((r) => r.name === 'provider-config');
|
|
239
|
-
assert.match(p.message, /my-custom-provider/);
|
|
240
|
-
assert.match(p.message, /2 models/);
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
test('reports both providers if user has multiple', async () => {
|
|
244
|
-
const cfg = JSON.parse(readFileSync(join(workDir, 'cline.json'), 'utf8'));
|
|
245
|
-
cfg.provider = {
|
|
246
|
-
'a': { baseUrl: 'https://a.example/v1', models: { 'm1': {} } },
|
|
247
|
-
'b': { baseUrl: 'https://b.example/v1', models: { 'm2': {}, 'm3': {} } },
|
|
248
|
-
};
|
|
249
|
-
writeFileSync(join(workDir, 'cline.json'), JSON.stringify(cfg, null, 2));
|
|
250
|
-
const { exitCode, stdout } = await captureRun({ json: true });
|
|
251
|
-
assert.equal(exitCode, 0);
|
|
252
|
-
const parsed = JSON.parse(stdout);
|
|
253
|
-
const p = parsed.results.find((r) => r.name === 'provider-config');
|
|
254
|
-
assert.match(p.message, /\ba\b/);
|
|
255
|
-
assert.match(p.message, /\bb\b/);
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
test('default mode is lenient on 9router-unreachable', async () => {
|
|
259
|
-
const { stdout, exitCode } = await captureRun({ json: true });
|
|
260
|
-
const parsed = JSON.parse(stdout);
|
|
261
|
-
const nineR = parsed.results.find((r) => r.name === '9router-reachable');
|
|
262
|
-
assert.ok(nineR, '9router-reachable check should be present');
|
|
263
|
-
if (!nineR.ok) {
|
|
264
|
-
// 9router is not running. Default mode must be lenient.
|
|
265
|
-
assert.equal(exitCode, 0, 'default mode should be lenient when 9router is down');
|
|
266
|
-
} else {
|
|
267
|
-
// 9router is running (some test envs have it). Just confirm the
|
|
268
|
-
// check is in the list and exit code is sane.
|
|
269
|
-
assert.equal(exitCode, 0, 'expected exit 0 when all critical checks pass');
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
test('--strict fails when 9router is unreachable', async () => {
|
|
274
|
-
// We can't easily kill 9router in a test, but we can prove that
|
|
275
|
-
// --strict mode would fail if 9router were down: set NINEROUTER_URL
|
|
276
|
-
// to a port nothing is listening on.
|
|
277
|
-
const origUrl = process.env.NINEROUTER_URL;
|
|
278
|
-
process.env.NINEROUTER_URL = 'http://127.0.0.1:1'; // nothing here
|
|
279
|
-
try {
|
|
280
|
-
const { exitCode } = await captureRun({ json: true, strict: true });
|
|
281
|
-
assert.equal(exitCode, 1, 'strict mode should fail when 9router is unreachable');
|
|
282
|
-
} finally {
|
|
283
|
-
if (origUrl === undefined) delete process.env.NINEROUTER_URL;
|
|
284
|
-
else process.env.NINEROUTER_URL = origUrl;
|
|
285
|
-
}
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
test('--only filters to a single check', async () => {
|
|
289
|
-
const { stdout, exitCode } = await captureRun({ json: true, only: 'team-command-present' });
|
|
290
|
-
assert.equal(exitCode, 0);
|
|
291
|
-
const parsed = JSON.parse(stdout);
|
|
292
|
-
assert.equal(parsed.results.length, 1);
|
|
293
|
-
assert.equal(parsed.results[0].name, 'team-command-present');
|
|
294
|
-
assert.equal(parsed.results[0].ok, true);
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
test('--only on a missing file fails with exit 1', async () => {
|
|
298
|
-
rmSync(join(workDir, 'commands', 'team.md'));
|
|
299
|
-
const { exitCode } = await captureRun({ json: true, only: 'team-command-present' });
|
|
300
|
-
assert.equal(exitCode, 1);
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
test('--only with an unknown name exits 2', async () => {
|
|
304
|
-
const { exitCode } = await captureRun({ json: true, only: 'this-does-not-exist' });
|
|
305
|
-
assert.equal(exitCode, 2);
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
test('v6.2.4 — mistake-limit-floor warns when clineruntimeMaxConsecutiveMistakes is < 10', async () => {
|
|
309
|
-
const cfg = JSON.parse(readFileSync(join(workDir, 'cline.json'), 'utf8'));
|
|
310
|
-
cfg.plugin = [['./plugins/bizar', { clineruntimeMaxConsecutiveMistakes: 3 }]];
|
|
311
|
-
writeFileSync(join(workDir, 'cline.json'), JSON.stringify(cfg, null, 2));
|
|
312
|
-
const { stdout, exitCode } = await captureRun({ json: true });
|
|
313
|
-
assert.equal(exitCode, 0, 'mistake-limit-floor is lenient so exit stays 0');
|
|
314
|
-
const parsed = JSON.parse(stdout);
|
|
315
|
-
const check = parsed.results.find((r) => r.name === 'mistake-limit-floor');
|
|
316
|
-
assert.ok(check, 'mistake-limit-floor check should be present');
|
|
317
|
-
assert.equal(check.ok, false, 'low mistake limit should warn');
|
|
318
|
-
assert.match(check.message, /BELOW the recommended minimum/);
|
|
319
|
-
assert.match(check.message, /clineruntimeMaxConsecutiveMistakes=3/);
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
test('v6.2.4 — mistake-limit-floor OK when clineruntimeMaxConsecutiveMistakes >= 10', async () => {
|
|
323
|
-
const cfg = JSON.parse(readFileSync(join(workDir, 'cline.json'), 'utf8'));
|
|
324
|
-
cfg.plugin = [['./plugins/bizar', { clineruntimeMaxConsecutiveMistakes: 15 }]];
|
|
325
|
-
writeFileSync(join(workDir, 'cline.json'), JSON.stringify(cfg, null, 2));
|
|
326
|
-
const { stdout, exitCode } = await captureRun({ json: true });
|
|
327
|
-
assert.equal(exitCode, 0);
|
|
328
|
-
const parsed = JSON.parse(stdout);
|
|
329
|
-
const check = parsed.results.find((r) => r.name === 'mistake-limit-floor');
|
|
330
|
-
assert.equal(check.ok, true);
|
|
331
|
-
assert.match(check.message, /15/);
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
test('v6.2.4 — mistake-limit-floor OK when no plugin entry (legacy cline.json)', async () => {
|
|
335
|
-
const cfg = JSON.parse(readFileSync(join(workDir, 'cline.json'), 'utf8'));
|
|
336
|
-
delete cfg.plugin;
|
|
337
|
-
writeFileSync(join(workDir, 'cline.json'), JSON.stringify(cfg, null, 2));
|
|
338
|
-
const { stdout } = await captureRun({ json: true });
|
|
339
|
-
const parsed = JSON.parse(stdout);
|
|
340
|
-
const check = parsed.results.find((r) => r.name === 'mistake-limit-floor');
|
|
341
|
-
assert.match(check.message, /no plugin entries/);
|
|
342
|
-
assert.equal(check.ok, true);
|
|
343
|
-
});
|
|
344
|
-
});
|
package/cli/doctor.test.mjs
DELETED
|
@@ -1,350 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* cli/doctor.test.mjs
|
|
3
|
-
*
|
|
4
|
-
* Tests for the `bizar doctor` subcommand. Uses Node's built-in
|
|
5
|
-
* node:test (no external test framework).
|
|
6
|
-
*
|
|
7
|
-
* Strategy: mock HOME so clineConfigDir() resolves inside a
|
|
8
|
-
* tmpdir, and craft the cline.json + agents/ directory to drive
|
|
9
|
-
* specific pass/fail outcomes. For checks that depend on global
|
|
10
|
-
* npm-installed packages or external CLIs (e.g. cline --version,
|
|
11
|
-
* npm root -g, which headroom), we accept that they may pass or fail
|
|
12
|
-
* depending on the test environment and just verify the framework
|
|
13
|
-
* returns the right shape.
|
|
14
|
-
*/
|
|
15
|
-
import { test, describe, beforeEach, afterEach } from 'node:test';
|
|
16
|
-
import assert from 'node:assert/strict';
|
|
17
|
-
import {
|
|
18
|
-
mkdtempSync,
|
|
19
|
-
mkdirSync,
|
|
20
|
-
writeFileSync,
|
|
21
|
-
rmSync,
|
|
22
|
-
existsSync,
|
|
23
|
-
utimesSync,
|
|
24
|
-
} from 'node:fs';
|
|
25
|
-
import { tmpdir } from 'node:os';
|
|
26
|
-
import { join, dirname } from 'node:path';
|
|
27
|
-
import { fileURLToPath } from 'node:url';
|
|
28
|
-
|
|
29
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
30
|
-
const PROJECT_ROOT = join(__dirname, '..');
|
|
31
|
-
|
|
32
|
-
const { runDoctor } = await import('./doctor.mjs');
|
|
33
|
-
|
|
34
|
-
// ── HOME mocking ────────────────────────────────────────────────────────────
|
|
35
|
-
|
|
36
|
-
const ORIG_HOME = process.env.HOME;
|
|
37
|
-
const ORIG_XDG = process.env.XDG_CONFIG_HOME;
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Mock HOME so clineConfigDir() resolves to `<tmpdir>/.cline`.
|
|
41
|
-
* Returns the tmpdir path.
|
|
42
|
-
*
|
|
43
|
-
* We don't set XDG_CONFIG_HOME here for the same reason as dev-link.test.mjs:
|
|
44
|
-
* the helper treats a set XDG_CONFIG_HOME as the direct parent (so
|
|
45
|
-
* `XDG_CONFIG_HOME=~/.config` → `~/.cline`).
|
|
46
|
-
*/
|
|
47
|
-
function freshHome() {
|
|
48
|
-
const home = mkdtempSync(join(tmpdir(), 'bizar-doctor-'));
|
|
49
|
-
process.env.HOME = home;
|
|
50
|
-
delete process.env.CLINE_DIR;
|
|
51
|
-
delete process.env.BIZAR_LEGACY_CLINE_DIR;
|
|
52
|
-
return home;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
afterEach(() => {
|
|
56
|
-
if (ORIG_HOME === undefined) delete process.env.HOME;
|
|
57
|
-
else process.env.HOME = ORIG_HOME;
|
|
58
|
-
if (ORIG_XDG === undefined) {
|
|
59
|
-
delete process.env.CLINE_DIR;
|
|
60
|
-
delete process.env.BIZAR_LEGACY_CLINE_DIR;
|
|
61
|
-
} else {
|
|
62
|
-
process.env.XDG_CONFIG_HOME = ORIG_XDG;
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
// ── Shape & silent-mode tests ───────────────────────────────────────────────
|
|
67
|
-
|
|
68
|
-
describe('runDoctor() shape', () => {
|
|
69
|
-
let home;
|
|
70
|
-
|
|
71
|
-
beforeEach(() => {
|
|
72
|
-
home = freshHome();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
afterEach(() => {
|
|
76
|
-
if (home && existsSync(home)) rmSync(home, { recursive: true, force: true });
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test('returns { passed, failed, results }', async () => {
|
|
80
|
-
const result = await runDoctor({ silent: true });
|
|
81
|
-
assert.equal(typeof result.passed, 'number');
|
|
82
|
-
assert.equal(typeof result.failed, 'number');
|
|
83
|
-
assert.ok(Array.isArray(result.results));
|
|
84
|
-
// results should have one entry per check
|
|
85
|
-
assert.equal(result.results.length, 9);
|
|
86
|
-
assert.equal(result.passed + result.failed, 9);
|
|
87
|
-
// Each result has the expected fields
|
|
88
|
-
for (const r of result.results) {
|
|
89
|
-
assert.equal(typeof r.name, 'string');
|
|
90
|
-
assert.equal(typeof r.ok, 'boolean');
|
|
91
|
-
assert.equal(typeof r.message, 'string');
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test('silent mode suppresses per-check output but still prints summary on failure', async () => {
|
|
96
|
-
// Capture stdout by overriding console.log.
|
|
97
|
-
const lines = [];
|
|
98
|
-
const origLog = console.log;
|
|
99
|
-
console.log = (...args) => {
|
|
100
|
-
lines.push(args.join(' '));
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
try {
|
|
104
|
-
// Empty home → many checks will fail → summary should print.
|
|
105
|
-
const result = await runDoctor({ silent: true });
|
|
106
|
-
assert.ok(result.failed > 0, 'precondition: some checks should fail');
|
|
107
|
-
} finally {
|
|
108
|
-
console.log = origLog;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// In silent mode with failures, only the summary line should print
|
|
112
|
-
// (an empty leading line is also fine — it's a visual separator).
|
|
113
|
-
const summaryLines = lines.filter((l) => /checks passed.*failed/.test(l));
|
|
114
|
-
assert.equal(
|
|
115
|
-
summaryLines.length,
|
|
116
|
-
1,
|
|
117
|
-
`expected exactly 1 summary line in silent+failed mode, got ${summaryLines.length}:\n${lines.join('\n')}`,
|
|
118
|
-
);
|
|
119
|
-
assert.match(summaryLines[0], /checks passed.*failed/);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test('silent mode suppresses ALL output when nothing fails', async () => {
|
|
123
|
-
// Build a fully healthy environment. We can't make cline reachable
|
|
124
|
-
// or headroom installed without modifying PATH, so we settle for: no failures
|
|
125
|
-
// means no output at all. Since we know some checks will fail in a
|
|
126
|
-
// bare tmpdir, this test is structured to assert the negative case
|
|
127
|
-
// differently: we verify silent+no-fail produces 0 lines.
|
|
128
|
-
//
|
|
129
|
-
// For this we mock nothing — the test just confirms the framework
|
|
130
|
-
// doesn't print per-check lines when silent is true.
|
|
131
|
-
const lines = [];
|
|
132
|
-
const origLog = console.log;
|
|
133
|
-
console.log = (...args) => {
|
|
134
|
-
lines.push(args.join(' '));
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
try {
|
|
138
|
-
await runDoctor({ silent: true });
|
|
139
|
-
// Some checks failed (because the env isn't fully healthy).
|
|
140
|
-
// What we want to verify is that per-check lines did NOT print.
|
|
141
|
-
const perCheckLines = lines.filter((l) =>
|
|
142
|
-
/^\s+[✓✗]\s/.test(l) // chalk check markers
|
|
143
|
-
);
|
|
144
|
-
assert.equal(
|
|
145
|
-
perCheckLines.length,
|
|
146
|
-
0,
|
|
147
|
-
`silent mode should suppress per-check output, got:\n${perCheckLines.join('\n')}`,
|
|
148
|
-
);
|
|
149
|
-
} finally {
|
|
150
|
-
console.log = origLog;
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
test('non-silent mode prints per-check output', async () => {
|
|
155
|
-
const lines = [];
|
|
156
|
-
const origLog = console.log;
|
|
157
|
-
console.log = (...args) => {
|
|
158
|
-
lines.push(args.join(' '));
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
try {
|
|
162
|
-
await runDoctor();
|
|
163
|
-
} finally {
|
|
164
|
-
console.log = origLog;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Should have at least 9 per-check lines plus the summary.
|
|
168
|
-
const perCheckLines = lines.filter((l) => /^\s+[✓✗]\s/.test(l));
|
|
169
|
-
assert.ok(
|
|
170
|
-
perCheckLines.length >= 9,
|
|
171
|
-
`expected ≥9 per-check lines, got ${perCheckLines.length}`,
|
|
172
|
-
);
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
// ── Fixture-driven pass/fail tests ──────────────────────────────────────────
|
|
177
|
-
|
|
178
|
-
describe('runDoctor() with fixture HOME', () => {
|
|
179
|
-
let home;
|
|
180
|
-
|
|
181
|
-
beforeEach(() => {
|
|
182
|
-
home = freshHome();
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
afterEach(() => {
|
|
186
|
-
if (home && existsSync(home)) rmSync(home, { recursive: true, force: true });
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
function writeClineConfig(json) {
|
|
190
|
-
const cfgDir = join(home, '.cline');
|
|
191
|
-
mkdirSync(cfgDir, { recursive: true });
|
|
192
|
-
writeFileSync(join(cfgDir, 'cline.json'), JSON.stringify(json), 'utf8');
|
|
193
|
-
return cfgDir;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function writeAgents(...files) {
|
|
197
|
-
const agentsDir = join(home, '.cline', 'agents');
|
|
198
|
-
mkdirSync(agentsDir, { recursive: true });
|
|
199
|
-
for (const f of files) {
|
|
200
|
-
writeFileSync(join(agentsDir, f), `# ${f}`, 'utf8');
|
|
201
|
-
}
|
|
202
|
-
return agentsDir;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function findCheck(result, name) {
|
|
206
|
-
return result.results.find((r) => r.name === name);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
test('config-valid passes for parseable JSON', async () => {
|
|
210
|
-
writeClineConfig({ provider: {} });
|
|
211
|
-
const result = await runDoctor({ silent: true });
|
|
212
|
-
const r = findCheck(result, 'cline-config-valid');
|
|
213
|
-
assert.equal(r.ok, true, `expected config-valid to pass: ${r.message}`);
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
test('config-valid fails for missing cline.json', async () => {
|
|
217
|
-
// No cline.json written.
|
|
218
|
-
const result = await runDoctor({ silent: true });
|
|
219
|
-
const r = findCheck(result, 'cline-config-valid');
|
|
220
|
-
assert.equal(r.ok, false);
|
|
221
|
-
assert.match(r.message, /not found/);
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
test('config-valid fails for invalid JSON', async () => {
|
|
225
|
-
const cfgDir = writeClineConfig({});
|
|
226
|
-
// Overwrite with garbage
|
|
227
|
-
writeFileSync(join(cfgDir, 'cline.json'), '{ this is not json', 'utf8');
|
|
228
|
-
const result = await runDoctor({ silent: true });
|
|
229
|
-
const r = findCheck(result, 'cline-config-valid');
|
|
230
|
-
assert.equal(r.ok, false);
|
|
231
|
-
assert.match(r.message, /invalid JSON/);
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
test('plugin-entry-present fails when plugin[] is empty', async () => {
|
|
235
|
-
writeClineConfig({ plugin: [] });
|
|
236
|
-
const result = await runDoctor({ silent: true });
|
|
237
|
-
const r = findCheck(result, 'plugin-entry-present');
|
|
238
|
-
assert.equal(r.ok, false);
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
test('plugin-entry-present fails when plugin[] has no bizar', async () => {
|
|
242
|
-
writeClineConfig({ plugin: ['something-else'] });
|
|
243
|
-
const result = await runDoctor({ silent: true });
|
|
244
|
-
const r = findCheck(result, 'plugin-entry-present');
|
|
245
|
-
assert.equal(r.ok, false);
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
test('plugin-entry-present passes when plugin[] has bizar string', async () => {
|
|
249
|
-
writeClineConfig({ plugin: ['bizar'] });
|
|
250
|
-
const result = await runDoctor({ silent: true });
|
|
251
|
-
const r = findCheck(result, 'plugin-entry-present');
|
|
252
|
-
assert.equal(r.ok, true, r.message);
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
test('plugin-entry-present passes when plugin[] has bizar object', async () => {
|
|
256
|
-
writeClineConfig({ plugin: [{ name: 'bizar', path: './plugins/bizar' }] });
|
|
257
|
-
const result = await runDoctor({ silent: true });
|
|
258
|
-
const r = findCheck(result, 'plugin-entry-present');
|
|
259
|
-
assert.equal(r.ok, true, r.message);
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
test('plugin-entry-present passes when plugin[] has [path, options] tuple', async () => {
|
|
263
|
-
writeClineConfig({ plugin: [['./plugins/bizar/index.ts', { loopThresholdWarn: 5 }]] });
|
|
264
|
-
const result = await runDoctor({ silent: true });
|
|
265
|
-
const r = findCheck(result, 'plugin-entry-present');
|
|
266
|
-
assert.equal(r.ok, true, r.message);
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
test('plugin-entry-present fails when tuple path does not contain "bizar"', async () => {
|
|
270
|
-
writeClineConfig({ plugin: [['./plugins/other/index.ts', {}]] });
|
|
271
|
-
const result = await runDoctor({ silent: true });
|
|
272
|
-
const r = findCheck(result, 'plugin-entry-present');
|
|
273
|
-
assert.equal(r.ok, false);
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
test('plugin-path-resolves passes when tuple path resolves', async () => {
|
|
277
|
-
const home = process.env.HOME;
|
|
278
|
-
const pluginsDir = join(home, '.cline', 'plugins', 'bizar');
|
|
279
|
-
mkdirSync(pluginsDir, { recursive: true });
|
|
280
|
-
writeFileSync(join(pluginsDir, 'index.ts'), '// fake plugin\n', 'utf8');
|
|
281
|
-
writeClineConfig({ plugin: [['./plugins/bizar/index.ts', {}]] });
|
|
282
|
-
const result = await runDoctor({ silent: true });
|
|
283
|
-
const r = findCheck(result, 'plugin-path-resolves');
|
|
284
|
-
assert.equal(r.ok, true, r.message);
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
test('agent-files-installed fails when core agents missing', async () => {
|
|
288
|
-
writeClineConfig({});
|
|
289
|
-
writeAgents('odin.md'); // missing the other 13
|
|
290
|
-
const result = await runDoctor({ silent: true });
|
|
291
|
-
const r = findCheck(result, 'agent-files-installed');
|
|
292
|
-
assert.equal(r.ok, false);
|
|
293
|
-
assert.match(r.message, /missing/);
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
test('agent-files-installed passes when all 14 agents present', async () => {
|
|
297
|
-
writeClineConfig({});
|
|
298
|
-
// v3.20.11: doctor now expects all 14 agents (was 4 in v3.20.10).
|
|
299
|
-
writeAgents(
|
|
300
|
-
'odin.md', 'vor.md', 'frigg.md', 'quick.md',
|
|
301
|
-
'mimir.md', 'heimdall.md', 'hermod.md', 'thor.md', 'baldr.md',
|
|
302
|
-
'tyr.md', 'vidarr.md', 'forseti.md',
|
|
303
|
-
'semble-search.md', 'agent-browser.md',
|
|
304
|
-
);
|
|
305
|
-
const result = await runDoctor({ silent: true });
|
|
306
|
-
const r = findCheck(result, 'agent-files-installed');
|
|
307
|
-
assert.equal(r.ok, true, r.message);
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
test('provider-config-sanity warns (not fails) without minimax block', async () => {
|
|
311
|
-
// v5.x: checkProviderConfigSanity warns instead of throwing when the
|
|
312
|
-
// provider.minimax block is missing, since provision.mjs auto-adds it.
|
|
313
|
-
writeClineConfig({ provider: {} });
|
|
314
|
-
const result = await runDoctor({ silent: true });
|
|
315
|
-
const r = findCheck(result, 'provider-config-sanity');
|
|
316
|
-
assert.equal(r.ok, true, 'should pass with warning, not throw');
|
|
317
|
-
assert.match(r.message, /warn.*minimax|minimax.*missing/i);
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
test('provider-config-sanity fails when models lack interleaved+reasoning', async () => {
|
|
321
|
-
writeClineConfig({
|
|
322
|
-
provider: {
|
|
323
|
-
minimax: {
|
|
324
|
-
models: { 'some-model': { id: 'foo' } },
|
|
325
|
-
},
|
|
326
|
-
},
|
|
327
|
-
});
|
|
328
|
-
const result = await runDoctor({ silent: true });
|
|
329
|
-
const r = findCheck(result, 'provider-config-sanity');
|
|
330
|
-
assert.equal(r.ok, false);
|
|
331
|
-
assert.match(r.message, /interleaved.*reasoning/);
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
test('provider-config-sanity passes with interleaved+reasoning', async () => {
|
|
335
|
-
writeClineConfig({
|
|
336
|
-
provider: {
|
|
337
|
-
minimax: {
|
|
338
|
-
models: {
|
|
339
|
-
'MiniMax/MiniMax-M3': { interleaved: true, reasoning: true },
|
|
340
|
-
},
|
|
341
|
-
},
|
|
342
|
-
},
|
|
343
|
-
});
|
|
344
|
-
const result = await runDoctor({ silent: true });
|
|
345
|
-
const r = findCheck(result, 'provider-config-sanity');
|
|
346
|
-
assert.equal(r.ok, true, r.message);
|
|
347
|
-
});
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
console.log(' doctor.mjs tests loaded — run with: node --test cli/doctor.test.mjs');
|