muonroi-cli 1.4.1 → 1.6.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/LICENSE +21 -21
- package/README.md +122 -122
- package/dist/packages/agent-harness-core/src/predicate.d.ts +1 -1
- package/dist/src/agent-harness/__tests__/mock-model.spec.js +48 -1
- package/dist/src/agent-harness/mock-model.d.ts +11 -0
- package/dist/src/agent-harness/mock-model.js +21 -0
- package/dist/src/cli/cost-forensics.js +12 -12
- package/dist/src/council/__tests__/clarification-prompt.test.js +51 -0
- package/dist/src/council/__tests__/clarifier-ready-gate.test.js +32 -0
- package/dist/src/council/__tests__/decisions-lock.test.js +17 -1
- package/dist/src/council/__tests__/oauth-reachable.test.d.ts +1 -0
- package/dist/src/council/__tests__/oauth-reachable.test.js +31 -0
- package/dist/src/council/__tests__/parse-outcome-fallback.test.js +11 -0
- package/dist/src/council/clarifier.js +9 -1
- package/dist/src/council/debate.js +5 -1
- package/dist/src/council/decisions-lock.js +3 -3
- package/dist/src/council/index.js +12 -5
- package/dist/src/council/leader.d.ts +0 -17
- package/dist/src/council/leader.js +22 -15
- package/dist/src/council/planner.js +1 -1
- package/dist/src/council/prompts.js +63 -57
- package/dist/src/council/types.d.ts +7 -0
- package/dist/src/ee/__tests__/ee-onboarding.test.d.ts +1 -0
- package/dist/src/ee/__tests__/ee-onboarding.test.js +32 -0
- package/dist/src/ee/artifact-cache.d.ts +56 -0
- package/dist/src/ee/artifact-cache.js +155 -0
- package/dist/src/ee/artifact-cache.test.d.ts +1 -0
- package/dist/src/ee/artifact-cache.test.js +69 -0
- package/dist/src/ee/auth.d.ts +9 -0
- package/dist/src/ee/auth.js +19 -0
- package/dist/src/ee/ee-onboarding.d.ts +5 -0
- package/dist/src/ee/ee-onboarding.js +76 -0
- package/dist/src/ee/search.js +7 -5
- package/dist/src/ee/search.test.d.ts +1 -0
- package/dist/src/ee/search.test.js +23 -0
- package/dist/src/generated/version.d.ts +1 -1
- package/dist/src/generated/version.js +1 -1
- package/dist/src/headless/output.js +6 -4
- package/dist/src/headless/output.test.js +4 -3
- package/dist/src/index.js +20 -1
- package/dist/src/mcp/__tests__/auto-setup.test.js +74 -0
- package/dist/src/mcp/__tests__/client-pool.spec.d.ts +1 -0
- package/dist/src/mcp/__tests__/client-pool.spec.js +98 -0
- package/dist/src/mcp/__tests__/parallel-build.spec.d.ts +1 -0
- package/dist/src/mcp/__tests__/parallel-build.spec.js +67 -0
- package/dist/src/mcp/__tests__/smart-filter.test.js +56 -0
- package/dist/src/mcp/auto-setup.js +56 -2
- package/dist/src/mcp/client-pool.d.ts +46 -0
- package/dist/src/mcp/client-pool.js +212 -0
- package/dist/src/mcp/oauth-callback.js +2 -2
- package/dist/src/mcp/parse-headers.test.js +14 -14
- package/dist/src/mcp/runtime.d.ts +28 -0
- package/dist/src/mcp/runtime.js +117 -51
- package/dist/src/mcp/self-verify-runner.d.ts +14 -0
- package/dist/src/mcp/self-verify-runner.js +38 -0
- package/dist/src/mcp/setup-guide-text.d.ts +9 -0
- package/dist/src/mcp/setup-guide-text.js +84 -0
- package/dist/src/mcp/smart-filter.js +49 -0
- package/dist/src/mcp/smoke.test.js +43 -43
- package/dist/src/mcp/tools-server.d.ts +7 -0
- package/dist/src/mcp/tools-server.js +19 -22
- package/dist/src/models/catalog.json +349 -349
- package/dist/src/ops/__tests__/doctor-ee-health.test.js +21 -0
- package/dist/src/ops/doctor.d.ts +3 -2
- package/dist/src/ops/doctor.js +47 -11
- package/dist/src/ops/doctor.test.js +4 -3
- package/dist/src/orchestrator/__tests__/mcp-capability-block.test.d.ts +1 -0
- package/dist/src/orchestrator/__tests__/mcp-capability-block.test.js +39 -0
- package/dist/src/orchestrator/__tests__/project-stack.test.d.ts +1 -0
- package/dist/src/orchestrator/__tests__/project-stack.test.js +65 -0
- package/dist/src/orchestrator/batch-turn-runner.js +7 -11
- package/dist/src/orchestrator/compaction.d.ts +2 -0
- package/dist/src/orchestrator/compaction.js +14 -1
- package/dist/src/orchestrator/compaction.test.js +25 -1
- package/dist/src/orchestrator/message-processor.js +72 -32
- package/dist/src/orchestrator/orchestrator.js +26 -0
- package/dist/src/orchestrator/prompts.d.ts +51 -0
- package/dist/src/orchestrator/prompts.js +257 -134
- package/dist/src/orchestrator/scope-ceiling.js +6 -1
- package/dist/src/orchestrator/scope-reminder.d.ts +12 -0
- package/dist/src/orchestrator/scope-reminder.js +16 -0
- package/dist/src/orchestrator/scope-reminder.test.js +22 -1
- package/dist/src/orchestrator/stream-runner.js +23 -15
- package/dist/src/orchestrator/subagent-compactor.d.ts +14 -5
- package/dist/src/orchestrator/subagent-compactor.js +30 -8
- package/dist/src/orchestrator/subagent-compactor.spec.js +18 -0
- package/dist/src/orchestrator/text-tool-call-detector.test.js +13 -13
- package/dist/src/pil/__tests__/clarity-gate.test.js +24 -215
- package/dist/src/pil/__tests__/config.test.js +1 -17
- package/dist/src/pil/__tests__/discovery.test.js +144 -11
- package/dist/src/pil/__tests__/layer1-intent-trace.test.js +7 -2
- package/dist/src/pil/__tests__/layer1-intent.test.js +3 -0
- package/dist/src/pil/__tests__/layer16-clarity.test.js +32 -116
- package/dist/src/pil/__tests__/layer4-gsd.test.js +37 -0
- package/dist/src/pil/__tests__/layer6-output.test.js +158 -18
- package/dist/src/pil/__tests__/llm-classify.test.js +49 -2
- package/dist/src/pil/__tests__/surface-compaction-artifacts.test.d.ts +1 -0
- package/dist/src/pil/__tests__/surface-compaction-artifacts.test.js +112 -0
- package/dist/src/pil/agent-operating-contract.d.ts +1 -1
- package/dist/src/pil/agent-operating-contract.js +2 -0
- package/dist/src/pil/agent-operating-contract.test.js +7 -2
- package/dist/src/pil/cheap-model-playbook.js +35 -35
- package/dist/src/pil/cheap-model-workbooks.js +16 -13
- package/dist/src/pil/clarity-gate.d.ts +21 -19
- package/dist/src/pil/clarity-gate.js +26 -153
- package/dist/src/pil/config.d.ts +9 -1
- package/dist/src/pil/config.js +15 -4
- package/dist/src/pil/discovery.js +211 -136
- package/dist/src/pil/layer1-intent.d.ts +12 -0
- package/dist/src/pil/layer1-intent.js +283 -38
- package/dist/src/pil/layer1-intent.test.js +210 -4
- package/dist/src/pil/layer16-clarity.d.ts +25 -11
- package/dist/src/pil/layer16-clarity.js +19 -306
- package/dist/src/pil/layer3-ee-injection.d.ts +19 -0
- package/dist/src/pil/layer3-ee-injection.js +96 -4
- package/dist/src/pil/layer4-gsd.js +18 -6
- package/dist/src/pil/layer6-output.d.ts +2 -0
- package/dist/src/pil/layer6-output.js +151 -25
- package/dist/src/pil/llm-classify.d.ts +26 -0
- package/dist/src/pil/llm-classify.js +34 -5
- package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
- package/dist/src/pil/native-capabilities-workbook.js +82 -76
- package/dist/src/pil/pipeline.js +15 -9
- package/dist/src/pil/schema.d.ts +8 -0
- package/dist/src/pil/schema.js +12 -1
- package/dist/src/pil/task-tier-map.js +4 -0
- package/dist/src/pil/types.d.ts +11 -1
- package/dist/src/product-loop/done-gate.js +3 -3
- package/dist/src/product-loop/loop-driver.js +18 -18
- package/dist/src/product-loop/progress-snapshot.js +4 -4
- package/dist/src/providers/auth/gemini-oauth.js +6 -15
- package/dist/src/providers/auth/grok-oauth.js +6 -15
- package/dist/src/providers/auth/openai-oauth.js +6 -15
- package/dist/src/providers/mcp-vision-bridge.js +48 -48
- package/dist/src/reporter/index.js +1 -1
- package/dist/src/scaffold/bb-ecosystem-apply.js +47 -47
- package/dist/src/scaffold/bb-quality-gate.js +5 -5
- package/dist/src/scaffold/continuation-prompt.js +60 -60
- package/dist/src/scaffold/init-new.js +453 -453
- package/dist/src/self-qa/__tests__/scenario-planner.test.js +3 -3
- package/dist/src/self-qa/agentic-loop.js +24 -19
- package/dist/src/self-qa/spec-emitter.js +26 -23
- package/dist/src/storage/__tests__/migrations.test.js +2 -2
- package/dist/src/storage/interaction-log.js +5 -5
- package/dist/src/storage/migrations.js +122 -122
- package/dist/src/storage/sessions.js +42 -42
- package/dist/src/storage/transcript.js +91 -84
- package/dist/src/storage/usage.js +14 -14
- package/dist/src/storage/workspaces.js +12 -12
- package/dist/src/tools/__tests__/native-tools.test.d.ts +1 -0
- package/dist/src/tools/__tests__/native-tools.test.js +53 -0
- package/dist/src/tools/git-safety.d.ts +61 -0
- package/dist/src/tools/git-safety.js +141 -0
- package/dist/src/tools/git-safety.test.d.ts +1 -0
- package/dist/src/tools/git-safety.test.js +111 -0
- package/dist/src/tools/native-tools.d.ts +31 -0
- package/dist/src/tools/native-tools.js +273 -0
- package/dist/src/tools/registry-ee-query.test.js +18 -1
- package/dist/src/tools/registry-git-safety.test.d.ts +7 -0
- package/dist/src/tools/registry-git-safety.test.js +92 -0
- package/dist/src/tools/registry.js +52 -6
- package/dist/src/ui/__tests__/markdown-render.test.d.ts +1 -0
- package/dist/src/ui/__tests__/markdown-render.test.js +48 -0
- package/dist/src/ui/app.js +0 -0
- package/dist/src/ui/components/message-view.js +4 -1
- package/dist/src/ui/components/structured-response-view.js +7 -3
- package/dist/src/ui/components/tool-group.js +7 -1
- package/dist/src/ui/markdown-render.d.ts +41 -0
- package/dist/src/ui/markdown-render.js +223 -0
- package/dist/src/ui/markdown.d.ts +10 -0
- package/dist/src/ui/markdown.js +12 -35
- package/dist/src/ui/slash/council-inspect.js +4 -4
- package/dist/src/ui/slash/export.js +4 -4
- package/dist/src/ui/utils/text.d.ts +8 -0
- package/dist/src/ui/utils/text.js +16 -0
- package/dist/src/ui/utils/text.test.d.ts +1 -0
- package/dist/src/ui/utils/text.test.js +23 -0
- package/dist/src/usage/ledger.js +48 -15
- package/dist/src/utils/__tests__/footprint-gitignore.test.d.ts +1 -0
- package/dist/src/utils/__tests__/footprint-gitignore.test.js +50 -0
- package/dist/src/utils/clipboard-image.js +23 -23
- package/dist/src/utils/open-url.d.ts +56 -0
- package/dist/src/utils/open-url.js +58 -0
- package/dist/src/utils/open-url.test.d.ts +1 -0
- package/dist/src/utils/open-url.test.js +86 -0
- package/dist/src/utils/settings.d.ts +12 -0
- package/dist/src/utils/settings.js +48 -0
- package/dist/src/utils/side-question.js +2 -2
- package/dist/src/utils/skills.js +3 -3
- package/dist/src/verify/__tests__/coverage-parsers.test.js +30 -30
- package/dist/src/verify/environment.js +2 -1
- package/package.json +1 -1
- package/dist/src/pil/layer16-clarity.test.js +0 -31
- /package/dist/src/{pil/layer16-clarity.test.d.ts → council/__tests__/clarification-prompt.test.d.ts} +0 -0
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 muonroi
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 muonroi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,122 +1,122 @@
|
|
|
1
|
-
<p align="center">
|
|
2
|
-
<h1 align="center">muonroi-cli</h1>
|
|
3
|
-
<p align="center">
|
|
4
|
-
<em>An AI coding agent where models argue with each other before answering.</em>
|
|
5
|
-
</p>
|
|
6
|
-
<p align="center">
|
|
7
|
-
<a href="https://github.com/muonroi/muonroi-cli/actions/workflows/ci-matrix.yml"><img alt="CI" src="https://github.com/muonroi/muonroi-cli/actions/workflows/ci-matrix.yml/badge.svg"></a>
|
|
8
|
-
<a href="https://www.npmjs.com/package/muonroi-cli"><img alt="npm" src="https://img.shields.io/npm/v/muonroi-cli.svg"></a>
|
|
9
|
-
<img alt="Providers" src="https://img.shields.io/badge/providers-7%20supported-blue">
|
|
10
|
-
<img alt="License" src="https://img.shields.io/badge/license-MIT-yellow">
|
|
11
|
-
<img alt="Runtime" src="https://img.shields.io/badge/runtime-Bun%201.3%2B-orange">
|
|
12
|
-
</p>
|
|
13
|
-
</p>
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
> Routes each task to the optimal model, runs adversarial multi-model debates for high-stakes decisions, and persists behavioral memory across sessions. Bring your own API keys. Total cost: ~$5/month.
|
|
18
|
-
|
|
19
|
-
<p align="center">
|
|
20
|
-
<img src="https://raw.githubusercontent.com/muonroi/muonroi-cli/master/docs/demo.gif" alt="Council debate — REST vs gRPC decision" width="840" />
|
|
21
|
-
</p>
|
|
22
|
-
|
|
23
|
-
## Quick Start
|
|
24
|
-
|
|
25
|
-
### Install
|
|
26
|
-
|
|
27
|
-
**Recommended — prebuilt standalone binary (zero runtime deps, no prereqs):**
|
|
28
|
-
|
|
29
|
-
Linux / macOS:
|
|
30
|
-
```bash
|
|
31
|
-
curl -fsSL https://raw.githubusercontent.com/muonroi/muonroi-cli/master/install.sh | bash
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
Windows PowerShell:
|
|
35
|
-
```powershell
|
|
36
|
-
irm https://raw.githubusercontent.com/muonroi/muonroi-cli/master/install.ps1 | iex
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
The installers download a `bun --compile` binary from GitHub Releases — single executable, all native deps bundled, no Node/Bun/build tools required.
|
|
40
|
-
|
|
41
|
-
**Bun runtime (requires Bun ≥ 1.x):**
|
|
42
|
-
```bash
|
|
43
|
-
bun add -g muonroi-cli
|
|
44
|
-
muonroi-cli
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
> **⚠ Why no `npm install -g`?** The TUI engine (`@opentui/core`) uses Bun-only `import ... with { type: "file" }` for Tree-sitter syntax highlight queries (`.scm` files). Node ESM cannot load these, so running under bare Node throws `ERR_UNKNOWN_FILE_EXTENSION`. Use the standalone installer (above) or `bun add -g`.
|
|
48
|
-
|
|
49
|
-
### First run
|
|
50
|
-
|
|
51
|
-
On first launch the CLI shows a wizard that:
|
|
52
|
-
1. Lists supported providers (currently **DeepSeek** and **SiliconFlow**).
|
|
53
|
-
2. Offers four ways to add credentials: paste an API key, import an encrypted bundle (`keys export`/`keys import`), sync from Bitwarden, or skip and add later via `/providers` inside the TUI.
|
|
54
|
-
|
|
55
|
-
After setup, role routing auto-balances across enabled providers:
|
|
56
|
-
|
|
57
|
-
```json
|
|
58
|
-
// ~/.muonroi-cli/user-settings.json (generated by the wizard — edit if needed)
|
|
59
|
-
{
|
|
60
|
-
"defaultProvider": "deepseek",
|
|
61
|
-
"providers": {
|
|
62
|
-
"deepseek": { "enabled": true },
|
|
63
|
-
"siliconflow": { "enabled": true }
|
|
64
|
-
},
|
|
65
|
-
"roleModels": {
|
|
66
|
-
"leader": "deepseek-v4-pro",
|
|
67
|
-
"implement": "deepseek-v4-flash",
|
|
68
|
-
"verify": "deepseek-v4-pro",
|
|
69
|
-
"research": "deepseek-v4-flash"
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### Moving keys between devices
|
|
75
|
-
|
|
76
|
-
```bash
|
|
77
|
-
# Source device — encrypts every stored key into one passphrase-protected file
|
|
78
|
-
muonroi-cli keys export ~/muonroi-keys.json
|
|
79
|
-
|
|
80
|
-
# Move the file via any channel (USB, Drive, AirDrop, email...)
|
|
81
|
-
|
|
82
|
-
# Target device — same passphrase rehydrates the OS keychain
|
|
83
|
-
muonroi-cli keys import ~/muonroi-keys.json
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
Inside the TUI press `/providers` then `B` to sync directly from a Bitwarden vault instead.
|
|
87
|
-
|
|
88
|
-
### Updates
|
|
89
|
-
|
|
90
|
-
The CLI checks npm once per day on startup and prompts when a newer version is available. Set `autoUpdate: true` in `user-settings.json` to skip the prompt and update silently. Manual: `muonroi-cli update`.
|
|
91
|
-
|
|
92
|
-
## Documentation
|
|
93
|
-
|
|
94
|
-
Full documentation at **[docs.muonroi.com/docs/cli](https://docs.muonroi.com/docs/cli/overview)**
|
|
95
|
-
|
|
96
|
-
| Topic | Link |
|
|
97
|
-
|---|---|
|
|
98
|
-
| Overview & architecture | [CLI Overview](https://docs.muonroi.com/docs/cli/overview) |
|
|
99
|
-
| Multi-Model Council | [Council Debate Guide](https://docs.muonroi.com/docs/cli/guides/council-debate) |
|
|
100
|
-
| Prompt Intelligence Layer | [PIL Pipeline Guide](https://docs.muonroi.com/docs/cli/guides/pil-pipeline) |
|
|
101
|
-
| Experience Engine | [Experience Engine Guide](https://docs.muonroi.com/docs/cli/guides/experience-engine) |
|
|
102
|
-
| Agent Harness | [Agent Harness Guide](https://docs.muonroi.com/docs/cli/guides/agent-harness) |
|
|
103
|
-
| Settings reference | [CLI Settings Reference](https://docs.muonroi.com/docs/cli/reference/cli-settings-reference) |
|
|
104
|
-
| Commands reference | [Commands Reference](https://docs.muonroi.com/docs/cli/reference/commands-reference) |
|
|
105
|
-
| Providers reference | [Providers Reference](https://docs.muonroi.com/docs/cli/reference/providers-reference) |
|
|
106
|
-
|
|
107
|
-
## Development
|
|
108
|
-
|
|
109
|
-
```bash
|
|
110
|
-
git clone https://github.com/muonroi/muonroi-cli.git
|
|
111
|
-
cd muonroi-cli && bun install
|
|
112
|
-
|
|
113
|
-
bun run dev # run from source
|
|
114
|
-
bun run typecheck # type check
|
|
115
|
-
bun run test # vitest
|
|
116
|
-
bun run lint # biome check
|
|
117
|
-
bun run build:binary # standalone binary
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
## License
|
|
121
|
-
|
|
122
|
-
MIT
|
|
1
|
+
<p align="center">
|
|
2
|
+
<h1 align="center">muonroi-cli</h1>
|
|
3
|
+
<p align="center">
|
|
4
|
+
<em>An AI coding agent where models argue with each other before answering.</em>
|
|
5
|
+
</p>
|
|
6
|
+
<p align="center">
|
|
7
|
+
<a href="https://github.com/muonroi/muonroi-cli/actions/workflows/ci-matrix.yml"><img alt="CI" src="https://github.com/muonroi/muonroi-cli/actions/workflows/ci-matrix.yml/badge.svg"></a>
|
|
8
|
+
<a href="https://www.npmjs.com/package/muonroi-cli"><img alt="npm" src="https://img.shields.io/npm/v/muonroi-cli.svg"></a>
|
|
9
|
+
<img alt="Providers" src="https://img.shields.io/badge/providers-7%20supported-blue">
|
|
10
|
+
<img alt="License" src="https://img.shields.io/badge/license-MIT-yellow">
|
|
11
|
+
<img alt="Runtime" src="https://img.shields.io/badge/runtime-Bun%201.3%2B-orange">
|
|
12
|
+
</p>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
> Routes each task to the optimal model, runs adversarial multi-model debates for high-stakes decisions, and persists behavioral memory across sessions. Bring your own API keys. Total cost: ~$5/month.
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<img src="https://raw.githubusercontent.com/muonroi/muonroi-cli/master/docs/demo.gif" alt="Council debate — REST vs gRPC decision" width="840" />
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
### Install
|
|
26
|
+
|
|
27
|
+
**Recommended — prebuilt standalone binary (zero runtime deps, no prereqs):**
|
|
28
|
+
|
|
29
|
+
Linux / macOS:
|
|
30
|
+
```bash
|
|
31
|
+
curl -fsSL https://raw.githubusercontent.com/muonroi/muonroi-cli/master/install.sh | bash
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Windows PowerShell:
|
|
35
|
+
```powershell
|
|
36
|
+
irm https://raw.githubusercontent.com/muonroi/muonroi-cli/master/install.ps1 | iex
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The installers download a `bun --compile` binary from GitHub Releases — single executable, all native deps bundled, no Node/Bun/build tools required.
|
|
40
|
+
|
|
41
|
+
**Bun runtime (requires Bun ≥ 1.x):**
|
|
42
|
+
```bash
|
|
43
|
+
bun add -g muonroi-cli
|
|
44
|
+
muonroi-cli
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
> **⚠ Why no `npm install -g`?** The TUI engine (`@opentui/core`) uses Bun-only `import ... with { type: "file" }` for Tree-sitter syntax highlight queries (`.scm` files). Node ESM cannot load these, so running under bare Node throws `ERR_UNKNOWN_FILE_EXTENSION`. Use the standalone installer (above) or `bun add -g`.
|
|
48
|
+
|
|
49
|
+
### First run
|
|
50
|
+
|
|
51
|
+
On first launch the CLI shows a wizard that:
|
|
52
|
+
1. Lists supported providers (currently **DeepSeek** and **SiliconFlow**).
|
|
53
|
+
2. Offers four ways to add credentials: paste an API key, import an encrypted bundle (`keys export`/`keys import`), sync from Bitwarden, or skip and add later via `/providers` inside the TUI.
|
|
54
|
+
|
|
55
|
+
After setup, role routing auto-balances across enabled providers:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
// ~/.muonroi-cli/user-settings.json (generated by the wizard — edit if needed)
|
|
59
|
+
{
|
|
60
|
+
"defaultProvider": "deepseek",
|
|
61
|
+
"providers": {
|
|
62
|
+
"deepseek": { "enabled": true },
|
|
63
|
+
"siliconflow": { "enabled": true }
|
|
64
|
+
},
|
|
65
|
+
"roleModels": {
|
|
66
|
+
"leader": "deepseek-v4-pro",
|
|
67
|
+
"implement": "deepseek-v4-flash",
|
|
68
|
+
"verify": "deepseek-v4-pro",
|
|
69
|
+
"research": "deepseek-v4-flash"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Moving keys between devices
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# Source device — encrypts every stored key into one passphrase-protected file
|
|
78
|
+
muonroi-cli keys export ~/muonroi-keys.json
|
|
79
|
+
|
|
80
|
+
# Move the file via any channel (USB, Drive, AirDrop, email...)
|
|
81
|
+
|
|
82
|
+
# Target device — same passphrase rehydrates the OS keychain
|
|
83
|
+
muonroi-cli keys import ~/muonroi-keys.json
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Inside the TUI press `/providers` then `B` to sync directly from a Bitwarden vault instead.
|
|
87
|
+
|
|
88
|
+
### Updates
|
|
89
|
+
|
|
90
|
+
The CLI checks npm once per day on startup and prompts when a newer version is available. Set `autoUpdate: true` in `user-settings.json` to skip the prompt and update silently. Manual: `muonroi-cli update`.
|
|
91
|
+
|
|
92
|
+
## Documentation
|
|
93
|
+
|
|
94
|
+
Full documentation at **[docs.muonroi.com/docs/cli](https://docs.muonroi.com/docs/cli/overview)**
|
|
95
|
+
|
|
96
|
+
| Topic | Link |
|
|
97
|
+
|---|---|
|
|
98
|
+
| Overview & architecture | [CLI Overview](https://docs.muonroi.com/docs/cli/overview) |
|
|
99
|
+
| Multi-Model Council | [Council Debate Guide](https://docs.muonroi.com/docs/cli/guides/council-debate) |
|
|
100
|
+
| Prompt Intelligence Layer | [PIL Pipeline Guide](https://docs.muonroi.com/docs/cli/guides/pil-pipeline) |
|
|
101
|
+
| Experience Engine | [Experience Engine Guide](https://docs.muonroi.com/docs/cli/guides/experience-engine) |
|
|
102
|
+
| Agent Harness | [Agent Harness Guide](https://docs.muonroi.com/docs/cli/guides/agent-harness) |
|
|
103
|
+
| Settings reference | [CLI Settings Reference](https://docs.muonroi.com/docs/cli/reference/cli-settings-reference) |
|
|
104
|
+
| Commands reference | [Commands Reference](https://docs.muonroi.com/docs/cli/reference/commands-reference) |
|
|
105
|
+
| Providers reference | [Providers Reference](https://docs.muonroi.com/docs/cli/reference/providers-reference) |
|
|
106
|
+
|
|
107
|
+
## Development
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
git clone https://github.com/muonroi/muonroi-cli.git
|
|
111
|
+
cd muonroi-cli && bun install
|
|
112
|
+
|
|
113
|
+
bun run dev # run from source
|
|
114
|
+
bun run typecheck # type check
|
|
115
|
+
bun run test # vitest
|
|
116
|
+
bun run lint # biome check
|
|
117
|
+
bun run build:binary # standalone binary
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
MIT
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { join } from "node:path";
|
|
8
|
-
import { stepCountIs, streamText, tool } from "ai";
|
|
8
|
+
import { generateObject, stepCountIs, streamText, tool } from "ai";
|
|
9
9
|
import { afterAll, describe, expect, it } from "vitest";
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
import { createMockModel, loadMockModelFromDir, textOnlyStream, toolCallStream } from "../mock-model.js";
|
|
@@ -84,6 +84,37 @@ describe("createMockModel", () => {
|
|
|
84
84
|
await drainStream(r2);
|
|
85
85
|
expect(handle.calls.length).toBe(1);
|
|
86
86
|
});
|
|
87
|
+
it("doGenerate backs generateObject with the configured JSON (council debate-planner path)", async () => {
|
|
88
|
+
const handle = createMockModel({
|
|
89
|
+
stream: textOnlyStream("unused"),
|
|
90
|
+
generate: JSON.stringify({ name: "counter", count: 3 }),
|
|
91
|
+
});
|
|
92
|
+
const { object } = await generateObject({
|
|
93
|
+
model: handle.model,
|
|
94
|
+
schema: z.object({ name: z.string(), count: z.number() }),
|
|
95
|
+
prompt: "plan the build",
|
|
96
|
+
});
|
|
97
|
+
expect(object).toEqual({ name: "counter", count: 3 });
|
|
98
|
+
});
|
|
99
|
+
it("doGenerate sequences across calls and repeats the last entry when exhausted", async () => {
|
|
100
|
+
const handle = createMockModel({
|
|
101
|
+
stream: textOnlyStream("unused"),
|
|
102
|
+
generate: [JSON.stringify({ n: 1 }), JSON.stringify({ n: 2 })],
|
|
103
|
+
});
|
|
104
|
+
const schema = z.object({ n: z.number() });
|
|
105
|
+
const a = await generateObject({ model: handle.model, schema, prompt: "1" });
|
|
106
|
+
const b = await generateObject({ model: handle.model, schema, prompt: "2" });
|
|
107
|
+
const c = await generateObject({ model: handle.model, schema, prompt: "3" });
|
|
108
|
+
expect(a.object.n).toBe(1);
|
|
109
|
+
expect(b.object.n).toBe(2);
|
|
110
|
+
expect(c.object.n).toBe(2); // exhausted → last entry repeats
|
|
111
|
+
});
|
|
112
|
+
it("doGenerate defaults to {} when no generate fixture is supplied (caller retry/fallback runs)", async () => {
|
|
113
|
+
const handle = createMockModel({ stream: textOnlyStream("unused") });
|
|
114
|
+
// An empty object fails a required-field schema → generateObject rejects,
|
|
115
|
+
// which is exactly what lets debate-planner fall through to its retry path.
|
|
116
|
+
await expect(generateObject({ model: handle.model, schema: z.object({ required: z.string() }), prompt: "x" })).rejects.toBeTruthy();
|
|
117
|
+
});
|
|
87
118
|
it("textOnlyStream emits a well-formed finish chunk", () => {
|
|
88
119
|
const chunks = textOnlyStream("hi");
|
|
89
120
|
const finish = chunks.find((c) => c.type === "finish");
|
|
@@ -141,6 +172,22 @@ describe("loadMockModelFromDir", () => {
|
|
|
141
172
|
expect(handle).not.toBeNull();
|
|
142
173
|
expect(handle?.defaultProviderOptions).toEqual({ openai: { store: false } });
|
|
143
174
|
});
|
|
175
|
+
it("propagates generate (doGenerate JSON) from the fixture file for generateObject", async () => {
|
|
176
|
+
const dir = mkFixtureDir({
|
|
177
|
+
provider: "mock",
|
|
178
|
+
modelId: "mock-gpt",
|
|
179
|
+
stream: textOnlyStream("unused"),
|
|
180
|
+
generate: JSON.stringify({ ok: true, label: "built" }),
|
|
181
|
+
});
|
|
182
|
+
const handle = await loadMockModelFromDir(dir);
|
|
183
|
+
expect(handle).not.toBeNull();
|
|
184
|
+
const { object } = await generateObject({
|
|
185
|
+
model: handle.model,
|
|
186
|
+
schema: z.object({ ok: z.boolean(), label: z.string() }),
|
|
187
|
+
prompt: "go",
|
|
188
|
+
});
|
|
189
|
+
expect(object).toEqual({ ok: true, label: "built" });
|
|
190
|
+
});
|
|
144
191
|
it("supports multi-round stream arrays from the fixture file", async () => {
|
|
145
192
|
const dir = mkFixtureDir({
|
|
146
193
|
provider: "mock",
|
|
@@ -33,6 +33,17 @@ export interface MockModelFixture {
|
|
|
33
33
|
* entry repeats (so multi-round loops don't crash if the fixture is short).
|
|
34
34
|
*/
|
|
35
35
|
stream: StreamChunks | StreamChunks[];
|
|
36
|
+
/**
|
|
37
|
+
* JSON text returned per `doGenerate` call — the path `generateObject` uses
|
|
38
|
+
* (council `debate-planner` plans the debate via `generateObject`, research
|
|
39
|
+
* classifiers via `generateText`, etc.). Without this the mock's `doGenerate`
|
|
40
|
+
* returns `"{}"`, which `generateObject` schema-validates → throws → the
|
|
41
|
+
* caller's retry/fallback path runs. Supply the exact object JSON to exercise
|
|
42
|
+
* the happy path.
|
|
43
|
+
* - Single string → same JSON on every call.
|
|
44
|
+
* - Array → one entry consumed per call; last entry repeats when exhausted.
|
|
45
|
+
*/
|
|
46
|
+
generate?: string | string[];
|
|
36
47
|
/** Reported provider id. Default "mock". */
|
|
37
48
|
provider?: string;
|
|
38
49
|
/** Reported model id. Default "mock-model". */
|
|
@@ -38,7 +38,9 @@ function isNestedArray(v) {
|
|
|
38
38
|
*/
|
|
39
39
|
export function createMockModel(fx) {
|
|
40
40
|
const streams = isNestedArray(fx.stream) ? fx.stream : [fx.stream];
|
|
41
|
+
const generates = fx.generate === undefined ? [] : Array.isArray(fx.generate) ? fx.generate : [fx.generate];
|
|
41
42
|
let callIdx = 0;
|
|
43
|
+
let genIdx = 0;
|
|
42
44
|
const provider = fx.provider ?? "mock";
|
|
43
45
|
const modelId = fx.modelId ?? "mock-model";
|
|
44
46
|
const model = new MockLanguageModelV3({
|
|
@@ -60,6 +62,24 @@ export function createMockModel(fx) {
|
|
|
60
62
|
}),
|
|
61
63
|
};
|
|
62
64
|
},
|
|
65
|
+
// doGenerate backs `generateObject` / non-streaming `generateText`. The AI
|
|
66
|
+
// SDK reads the first text content part as the object JSON (generateObject)
|
|
67
|
+
// or the completion text (generateText). Default "{}" keeps the mock from
|
|
68
|
+
// throwing "Not implemented" — generateObject then schema-rejects it and the
|
|
69
|
+
// caller's retry/fallback runs. Supply `generate` to drive the happy path.
|
|
70
|
+
doGenerate: async () => {
|
|
71
|
+
const text = generates.length > 0 ? generates[Math.min(genIdx, generates.length - 1)] : "{}";
|
|
72
|
+
genIdx += 1;
|
|
73
|
+
if (process.env.MUONROI_DEBUG_MOCK_MODEL === "1") {
|
|
74
|
+
process.stderr.write(`[mock-model] doGenerate #${genIdx} → ${text.length} chars\n`);
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
content: [{ type: "text", text }],
|
|
78
|
+
finishReason: { unified: "stop", raw: undefined },
|
|
79
|
+
usage: buildUsage(10, text.length),
|
|
80
|
+
warnings: [],
|
|
81
|
+
};
|
|
82
|
+
},
|
|
63
83
|
});
|
|
64
84
|
return {
|
|
65
85
|
model,
|
|
@@ -69,6 +89,7 @@ export function createMockModel(fx) {
|
|
|
69
89
|
reset() {
|
|
70
90
|
model.doStreamCalls.length = 0;
|
|
71
91
|
callIdx = 0;
|
|
92
|
+
genIdx = 0;
|
|
72
93
|
},
|
|
73
94
|
};
|
|
74
95
|
}
|
|
@@ -42,21 +42,21 @@ export function collectCostForensics(sessionId) {
|
|
|
42
42
|
if (!session)
|
|
43
43
|
throw new Error(`Session not found: ${sessionId}`);
|
|
44
44
|
const rows = db
|
|
45
|
-
.prepare(`
|
|
46
|
-
SELECT id, source, model, message_seq, input_tokens, output_tokens,
|
|
47
|
-
cache_read_tokens, cache_creation_tokens, cost_micros, created_at,
|
|
48
|
-
provider_options_shape
|
|
49
|
-
FROM usage_events
|
|
50
|
-
WHERE session_id = ?
|
|
51
|
-
ORDER BY id ASC
|
|
45
|
+
.prepare(`
|
|
46
|
+
SELECT id, source, model, message_seq, input_tokens, output_tokens,
|
|
47
|
+
cache_read_tokens, cache_creation_tokens, cost_micros, created_at,
|
|
48
|
+
provider_options_shape
|
|
49
|
+
FROM usage_events
|
|
50
|
+
WHERE session_id = ?
|
|
51
|
+
ORDER BY id ASC
|
|
52
52
|
`)
|
|
53
53
|
.all(sessionId);
|
|
54
54
|
const counts = db
|
|
55
|
-
.prepare(`
|
|
56
|
-
SELECT event_type, COUNT(*) AS c
|
|
57
|
-
FROM interaction_logs
|
|
58
|
-
WHERE session_id = ?
|
|
59
|
-
GROUP BY event_type
|
|
55
|
+
.prepare(`
|
|
56
|
+
SELECT event_type, COUNT(*) AS c
|
|
57
|
+
FROM interaction_logs
|
|
58
|
+
WHERE session_id = ?
|
|
59
|
+
GROUP BY event_type
|
|
60
60
|
`)
|
|
61
61
|
.all(sessionId);
|
|
62
62
|
const countMap = new Map(counts.map((r) => [r.event_type, r.c]));
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { buildClarificationPrompt, buildReadinessJudgePrompt } from "../prompts.js";
|
|
3
|
+
// Guards the de-robotized askcard contract (2026-06-14). The council clarifier
|
|
4
|
+
// used to (a) force "AT LEAST 2 questions" → rambling/over-asking, (b) tell the
|
|
5
|
+
// model to OMIT a recommendation unless certain → unranked option lists, and
|
|
6
|
+
// (c) carry no existing-repo grounding → generic greenfield questions on a
|
|
7
|
+
// brownfield repo. These assertions lock in the fixed behaviour so a future
|
|
8
|
+
// edit can't silently regress it.
|
|
9
|
+
describe("buildClarificationPrompt — de-robotized askcard", () => {
|
|
10
|
+
const { system } = buildClarificationPrompt("Add a retry to the EE bridge call", "## Current Project\nmuonroi-cli — TypeScript CLI. Stack: bun, vitest.\n");
|
|
11
|
+
it("does NOT force a minimum question quota", () => {
|
|
12
|
+
expect(system).not.toMatch(/AT LEAST 2 questions/i);
|
|
13
|
+
expect(system).not.toMatch(/MUST ask AT LEAST/i);
|
|
14
|
+
expect(system).not.toMatch(/Minimum-question rule/i);
|
|
15
|
+
});
|
|
16
|
+
it("explicitly allows zero questions / returning []", () => {
|
|
17
|
+
expect(system).toMatch(/return \[\]/i);
|
|
18
|
+
expect(system).toMatch(/ZERO questions/i);
|
|
19
|
+
});
|
|
20
|
+
it("makes a recommendation MANDATORY (decisive), not optional", () => {
|
|
21
|
+
expect(system).toMatch(/ALWAYS include "recommended"/);
|
|
22
|
+
// The old "OMIT the field entirely" default must be gone.
|
|
23
|
+
expect(system).not.toMatch(/OMIT the field entirely/i);
|
|
24
|
+
expect(system).toMatch(/never face an unranked list/i);
|
|
25
|
+
});
|
|
26
|
+
it("grounds questions in the existing repo, not greenfield", () => {
|
|
27
|
+
expect(system).toMatch(/Current Project/);
|
|
28
|
+
expect(system).toMatch(/EXISTING repository/);
|
|
29
|
+
expect(system).toMatch(/do NOT ask generic greenfield/i);
|
|
30
|
+
});
|
|
31
|
+
it("still emits the structured JSON contract (question/suggestions/recommended)", () => {
|
|
32
|
+
expect(system).toMatch(/"suggestions"/);
|
|
33
|
+
expect(system).toMatch(/"recommended"/);
|
|
34
|
+
expect(system).toMatch(/Output ONLY a JSON array/);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
describe("buildReadinessJudgePrompt — no JS string-concat leak", () => {
|
|
38
|
+
const { system } = buildReadinessJudgePrompt("Optimize queries", [], {
|
|
39
|
+
problemStatement: "p",
|
|
40
|
+
constraints: [],
|
|
41
|
+
successCriteria: ["c"],
|
|
42
|
+
scope: "s",
|
|
43
|
+
});
|
|
44
|
+
it("does not leak JS concatenation syntax into the prompt text", () => {
|
|
45
|
+
// Was: `...means "probably " +\n "ready but some ambiguity remains"` — the
|
|
46
|
+
// `" +` and the leading ` "` leaked verbatim into the model-facing string.
|
|
47
|
+
expect(system).not.toContain('" +');
|
|
48
|
+
expect(system).toContain("probably ready but some ambiguity remains");
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
//# sourceMappingURL=clarification-prompt.test.js.map
|
|
@@ -151,6 +151,38 @@ describe("P5 ready-gate: Test C — hard cap at MAX_CLARIFY_ROUNDS", () => {
|
|
|
151
151
|
});
|
|
152
152
|
});
|
|
153
153
|
// ---------------------------------------------------------------------------
|
|
154
|
+
// Test E: clarifier asks ZERO questions → spec.ready=true, judge NOT called
|
|
155
|
+
// (de-robotized prompt now commonly returns [] — the gate must reflect that,
|
|
156
|
+
// not stay at its not-ready default, and must not pay for a judge LLM call.)
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
describe("P5 ready-gate: Test E — zero questions ⇒ ready without a judge call", () => {
|
|
159
|
+
it("marks spec.ready=true and skips the readiness judge when the clarifier asks nothing", async () => {
|
|
160
|
+
let callCount = 0;
|
|
161
|
+
const generate = vi.fn().mockImplementation(async () => {
|
|
162
|
+
callCount++;
|
|
163
|
+
if (callCount === 1)
|
|
164
|
+
return "[]"; // clarify round 0: nothing to ask
|
|
165
|
+
// spec synthesis (if reached) — no judge call should occur on this path
|
|
166
|
+
return JSON.stringify({
|
|
167
|
+
problemStatement: "Add a retry to the EE bridge",
|
|
168
|
+
constraints: [],
|
|
169
|
+
successCriteria: ["Retries once on transient failure"],
|
|
170
|
+
scope: "EE bridge only",
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
const mockLLM = { generate };
|
|
174
|
+
const gen = runClarification("Add a retry to the EE bridge", "leader-model", "## Current Project\nTypeScript CLI", alwaysAnswer, mockLLM);
|
|
175
|
+
const spec = await _drain(gen);
|
|
176
|
+
expect(spec.ready).toBe(true);
|
|
177
|
+
expect(spec.confidenceScore).toBe(1);
|
|
178
|
+
expect(spec.remainingGaps).toEqual([]);
|
|
179
|
+
expect(spec.clarifyHistory).toEqual([]);
|
|
180
|
+
// The readiness judge ("debate facilitator" system prompt) must NOT fire.
|
|
181
|
+
const judgeCalled = generate.mock.calls.some(([, system]) => typeof system === "string" && system.includes("debate facilitator"));
|
|
182
|
+
expect(judgeCalled).toBe(false);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
154
186
|
// Test D: judgeReadiness unit tests
|
|
155
187
|
// ---------------------------------------------------------------------------
|
|
156
188
|
describe("P5 ready-gate: Test D — judgeReadiness unit", () => {
|
|
@@ -7,7 +7,7 @@ import * as fs from "node:fs";
|
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
10
|
-
import { buildStackLockSection, detectOutOfStackProposals, prependDecisionsLock, readDecisionsLock, renderDecisionsLock, writeDecisionsLock, } from "../decisions-lock.js";
|
|
10
|
+
import { buildStackLockSection, detectOutOfStackProposals, extractStackFromSpec, prependDecisionsLock, readDecisionsLock, renderDecisionsLock, writeDecisionsLock, } from "../decisions-lock.js";
|
|
11
11
|
import { buildFollowupPrompt, buildLeaderEvaluationPrompt, buildOpeningPrompt, buildResponsePrompt, } from "../prompts.js";
|
|
12
12
|
// ── Fixtures ──────────────────────────────────────────────────────────────────
|
|
13
13
|
function makeSpec(overrides = {}) {
|
|
@@ -72,6 +72,22 @@ describe("C1: buildStackLockSection", () => {
|
|
|
72
72
|
expect(section).toBe("");
|
|
73
73
|
});
|
|
74
74
|
});
|
|
75
|
+
// ── extractStackFromSpec: mediatr keyword (Cyrillic-char regression) ──────────
|
|
76
|
+
describe("extractStackFromSpec — mediatr keyword", () => {
|
|
77
|
+
it("detects the BB/.NET backend from a 'MediatR' mention alone", () => {
|
|
78
|
+
// Pre-fix the keyword used a Cyrillic 'р' (U+0440), so ASCII "mediatr" could
|
|
79
|
+
// never match. A spec mentioning ONLY MediatR (no BaseTemplate / building-block)
|
|
80
|
+
// must still resolve the backend.
|
|
81
|
+
const spec = makeSpec({ problemStatement: "Wire up CQRS handlers with MediatR", constraints: [], scope: "" });
|
|
82
|
+
const stack = extractStackFromSpec(spec);
|
|
83
|
+
expect(stack).not.toBeNull();
|
|
84
|
+
expect(stack?.backend ?? "").toContain("MediatR");
|
|
85
|
+
});
|
|
86
|
+
it("returns null when no BB/.NET/Muonroi keyword is present", () => {
|
|
87
|
+
const spec = makeSpec({ problemStatement: "Build a plain Express API", constraints: [], scope: "" });
|
|
88
|
+
expect(extractStackFromSpec(spec)).toBeNull();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
75
91
|
// ── C1: STACK LOCK injected into debate system prompts ────────────────────────
|
|
76
92
|
describe("C1: STACK LOCK injected into debate system prompts", () => {
|
|
77
93
|
const bbSpec = makeSpecWithBBStack();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { beforeAll, describe, expect, it, vi } from "vitest";
|
|
2
|
+
// F15 regression: council participant resolution must treat OAuth-authed
|
|
3
|
+
// providers (no API key) as reachable. getConfiguredProviders() is the
|
|
4
|
+
// authoritative cred check; the old loadKeyForProvider-only path threw for
|
|
5
|
+
// OAuth-only providers → 0 participants → council bailed "No reachable
|
|
6
|
+
// provider" even though the model (e.g. grok via xAI OAuth) answers fine.
|
|
7
|
+
// xai is configured via OAuth only (present in getConfiguredProviders, but
|
|
8
|
+
// loadKeyForProvider would have thrown — no API key).
|
|
9
|
+
vi.mock("../../providers/keychain.js", () => ({
|
|
10
|
+
getConfiguredProviders: vi.fn(async () => ["xai"]),
|
|
11
|
+
}));
|
|
12
|
+
// Hermetic settings: no explicit role models, nothing disabled.
|
|
13
|
+
vi.mock("../../utils/settings.js", () => ({
|
|
14
|
+
getRoleModels: () => ({}),
|
|
15
|
+
getRoleModel: () => undefined,
|
|
16
|
+
isProviderDisabled: () => false,
|
|
17
|
+
}));
|
|
18
|
+
import { loadCatalog } from "../../models/registry.js";
|
|
19
|
+
import { resolveParticipants } from "../leader.js";
|
|
20
|
+
describe("F15 — council reachability counts OAuth-only providers", () => {
|
|
21
|
+
beforeAll(async () => {
|
|
22
|
+
await loadCatalog();
|
|
23
|
+
});
|
|
24
|
+
it("resolves >=2 participants when the session provider is OAuth-only (xai, no API key)", async () => {
|
|
25
|
+
const participants = await resolveParticipants("grok-build-0.1", false);
|
|
26
|
+
// Pre-fix this returned [] (loadKeyForProvider('xai') threw) → council bailed.
|
|
27
|
+
expect(participants.length).toBeGreaterThanOrEqual(2);
|
|
28
|
+
expect(participants.every((p) => p.model.startsWith("grok"))).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
//# sourceMappingURL=oauth-reachable.test.js.map
|
|
@@ -99,5 +99,16 @@ describe("parseOutcome — raw log + shape-based fallback (CQ-20)", () => {
|
|
|
99
99
|
expect(result?.outcome?.summary).toBe("We should go with option A because it is simpler.");
|
|
100
100
|
expect(errorSpy).not.toHaveBeenCalled();
|
|
101
101
|
});
|
|
102
|
+
it("Test 6: shape fallback EXTRACTS section content from markdown headings (regex regression)", async () => {
|
|
103
|
+
// Pre-fix the heading regex used a literal "s+" instead of "\\s+" (and
|
|
104
|
+
// replaced spaces with "s+"), so it never matched a real "## Heading" line —
|
|
105
|
+
// every section came back empty even when the synthesis clearly contained them.
|
|
106
|
+
const md = "Here is the evaluation summary line that is plenty long enough.\n\n" +
|
|
107
|
+
"## Strengths\n- Fast startup\n- Low cost\n\n" +
|
|
108
|
+
"## Summary\nThe approach is solid overall.";
|
|
109
|
+
const result = await runPlanningWith(md, sampleDebatePlan);
|
|
110
|
+
expect(result?.outcome?.sections?.strengths).toEqual(["- Fast startup", "- Low cost"]);
|
|
111
|
+
expect(result?.outcome?.sections?.summary_text).toContain("The approach is solid overall.");
|
|
112
|
+
});
|
|
102
113
|
});
|
|
103
114
|
//# sourceMappingURL=parse-outcome-fallback.test.js.map
|