cli-five 0.2.15 → 0.2.17
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/README.md +95 -21
- package/package.json +1 -1
- package/src/addons/codegraph-command.mjs +47 -0
- package/src/addons/codegraph.mjs +124 -0
- package/src/addons/jev.mjs +98 -0
- package/src/addons/registry.mjs +126 -0
- package/src/cli.mjs +30 -5
- package/src/commands/add.mjs +55 -0
- package/src/commands/init.mjs +105 -41
- package/src/commands/list-addons.mjs +49 -0
- package/src/steps/interview.mjs +59 -0
- package/src/steps/platform.mjs +46 -3
- package/src/steps/scaffold.mjs +11 -46
- package/src/util/merge.mjs +170 -0
- package/src/util/project.mjs +140 -0
- package/templates/AGENTS.md.tmpl +0 -1
- package/templates/opencode/plugin/jev-tier-router/index.js +187 -0
- package/templates/opencode/plugin/jev-tier-router/package.json +9 -0
package/README.md
CHANGED
|
@@ -10,7 +10,14 @@
|
|
|
10
10
|
npx cli-five init
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
By default `init` is **minimal**: it scaffolds the 5-agent team and the tooling they need, asking only for the target platform and (when it can't find them) your project name and one-liner. Name and description are auto-pulled from `package.json` and `README.md`. Optional integrations — including CodeGraph, skills, and instruction files — are **not** installed unless you ask.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx cli-five init --full-interview # legacy: docs, goals, persona, model picker, CodeGraph, skills, instructions
|
|
17
|
+
npx cli-five init --codegraph # minimal, but add the CodeGraph MCP + AGENTS.md block
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`--full-interview` (or passing `--doc`) restores the complete guided setup: stack presets, goals, constraints, persona toggle, per-agent model overrides, Copilot cost modes, CodeGraph, skill discovery, and stack-specific instruction generation. Individual pieces can also be toggled with `--codegraph`, `--skills`, `--instructions`, and `--persona`.
|
|
14
21
|
|
|
15
22
|
### Quick plugin install (personal use, Copilot only)
|
|
16
23
|
|
|
@@ -44,9 +51,9 @@ your-repo/
|
|
|
44
51
|
│ │ ├── coder.agent.md
|
|
45
52
|
│ │ ├── designer.agent.md
|
|
46
53
|
│ │ └── reviewer.agent.md
|
|
47
|
-
│ ├── copilot-instructions.md #
|
|
48
|
-
│ ├── instructions/ #
|
|
49
|
-
│ └── skills/ #
|
|
54
|
+
│ ├── copilot-instructions.md # Project mandates (persona is opt-in)
|
|
55
|
+
│ ├── instructions/ # Container; stack guidelines via --instructions
|
|
56
|
+
│ └── skills/ # Container; skills via --skills
|
|
50
57
|
├── .vscode/mcp.json # CodeGraph MCP server (optional)
|
|
51
58
|
├── AGENTS.md # Tool-agnostic project context (agents.md standard)
|
|
52
59
|
├── PROJECT.md # Long-form vision (rarely changes)
|
|
@@ -68,8 +75,8 @@ your-repo/
|
|
|
68
75
|
│ ├── designer.md
|
|
69
76
|
│ └── reviewer.md
|
|
70
77
|
├── opencode.json # Project config, model defaults, CodeGraph MCP (optional)
|
|
71
|
-
├── .github/instructions/ #
|
|
72
|
-
├── .github/skills/ #
|
|
78
|
+
├── .github/instructions/ # Container; stack guidelines via --instructions
|
|
79
|
+
├── .github/skills/ # Container; skills via --skills
|
|
73
80
|
├── AGENTS.md # Project rules + optional CodeGraph block
|
|
74
81
|
├── PROJECT.md
|
|
75
82
|
├── STATE.md
|
|
@@ -94,7 +101,10 @@ The plugin agents and scaffolded `.github/agents/*.agent.md` templates are inten
|
|
|
94
101
|
## Commands
|
|
95
102
|
|
|
96
103
|
```bash
|
|
97
|
-
npx cli-five init #
|
|
104
|
+
npx cli-five init # minimal scaffold (5 agents + required tooling)
|
|
105
|
+
npx cli-five init --full-interview # full guided setup (interview, models, skills, instructions)
|
|
106
|
+
npx cli-five add <name> # install an optional add-on (dispatcher; targets land later)
|
|
107
|
+
npx cli-five list-addons # show installed vs. available add-ons
|
|
98
108
|
npx cli-five doctor # validate an existing cli-five setup
|
|
99
109
|
npx cli-five list-stacks # show detectable tech stacks
|
|
100
110
|
npx cli-five help
|
|
@@ -104,33 +114,97 @@ npx cli-five help
|
|
|
104
114
|
|
|
105
115
|
| Flag | Effect |
|
|
106
116
|
|---|---|
|
|
107
|
-
| `--yes`, `-y` | Accept
|
|
117
|
+
| `--yes`, `-y` | Accept defaults (overwrite gate still active) |
|
|
108
118
|
| `--force` | Overwrite without confirmation (use with `--yes`) |
|
|
109
119
|
| `--dry-run` | Print actions, write nothing |
|
|
110
|
-
| `--
|
|
111
|
-
| `--no-
|
|
120
|
+
| `--full-interview`, `--full` | Run the full legacy interview (docs, goals/constraints/persona, model picker). Also enables CodeGraph, skills + instructions |
|
|
121
|
+
| `--skills` / `--no-skills` | Force skill discovery on/off |
|
|
122
|
+
| `--instructions` / `--no-instructions` | Force stack-specific instruction generation on/off |
|
|
123
|
+
| `--persona` / `--no-persona` | Include / omit the snarky persona block |
|
|
124
|
+
| `--codegraph` / `--no-codegraph` | Force CodeGraph MCP + instructions on/off (default: on with `--full-interview`, off otherwise) |
|
|
112
125
|
| `--target <t>` | Target platform: `copilot` or `opencode` (default: `copilot`) |
|
|
113
126
|
| `--provider <p>` | Model provider: `copilot`, `opencode`, `opencode-go` (default: platform default) |
|
|
114
127
|
| `--cost-mode <m>` | Set cost mode for Copilot: `premium`, `cheap`, or `mixed` |
|
|
115
|
-
| `--doc <file>` | Read project docs to pre-fill interview (repeatable) |
|
|
128
|
+
| `--doc <file>` | Read project docs to pre-fill interview (repeatable; implies `--full-interview`) |
|
|
116
129
|
| `--cwd <path>` | Run against a different directory |
|
|
117
130
|
|
|
118
131
|
## What `init` does
|
|
119
132
|
|
|
133
|
+
Default (`npx cli-five init`):
|
|
134
|
+
|
|
120
135
|
1. **Detect** — fingerprints stack (Node/TS, Python, .NET, Kotlin, Rust, Go, etc.). Brownfield-aware.
|
|
121
|
-
2. **Choose platform** —
|
|
136
|
+
2. **Choose platform** — explicit `--target`, auto-detected from an existing scaffold, or prompted.
|
|
122
137
|
3. **git init** — if needed. Asks first.
|
|
123
138
|
4. **Overwrite gate** — double-confirms ("Proceed?" then "R U Sure?"). Only `--force --yes` bypasses.
|
|
124
|
-
5. **Project info** — name
|
|
125
|
-
6. **Model configuration** —
|
|
126
|
-
7. **Scaffold** — writes
|
|
127
|
-
8. **
|
|
128
|
-
|
|
129
|
-
|
|
139
|
+
5. **Project info** — name and one-liner auto-pulled from `package.json` / `README.md`; asks only when missing or ambiguous.
|
|
140
|
+
6. **Model configuration** — platform/provider defaults, no prompt.
|
|
141
|
+
7. **Scaffold** — writes the 5 agents + required project memory/tooling files. Swaps `model:` per provider defaults.
|
|
142
|
+
8. **Done** — prints platform-specific next steps.
|
|
143
|
+
|
|
144
|
+
`--full-interview` additionally runs:
|
|
145
|
+
|
|
146
|
+
- **Docs / manual interview** — project docs, stack preset, goals, constraints.
|
|
147
|
+
- **Model customization** — provider picker + per-agent model overrides.
|
|
148
|
+
- **Persona toggle** — snarky persona block.
|
|
149
|
+
- **CodeGraph** — MCP registration + `AGENTS.md` block (also available standalone via `--codegraph`).
|
|
150
|
+
- **Skill discovery** — multi-source discovery from **awesome-copilot** and **skills.sh**.
|
|
151
|
+
- **Custom instructions** — stack-specific `.instructions.md` files for detected languages.
|
|
152
|
+
|
|
153
|
+
## Add-ons
|
|
154
|
+
|
|
155
|
+
Optional integrations live outside the default scaffold and are installed with `cli-five add <name>`.
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
npx cli-five add # list known targets
|
|
159
|
+
npx cli-five add codegraph # CodeGraph MCP registration + AGENTS.md instructions
|
|
160
|
+
npx cli-five add jev # tier-routing tool for the Planner (OpenCode only)
|
|
161
|
+
npx cli-five list-addons # installed vs. available status
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Add-ons use `mergeBlock(file, markerFence, content)` to layer a fenced block into an existing JSON or Markdown file without touching the rest of it (distinct from init's blunt overwrite gate). Markdown gets `<!-- NAME_START -->` / `<!-- NAME_END -->` fences; JSON is deep-merged with existing keys preserved. Re-running an add-on is idempotent — no duplicate MCP entries or AGENTS.md sections.
|
|
165
|
+
|
|
166
|
+
### CodeGraph (`add codegraph`)
|
|
167
|
+
|
|
168
|
+
`add codegraph` registers the CodeGraph MCP server and adds the CodeGraph section to `AGENTS.md`. It works on **both platforms**:
|
|
169
|
+
|
|
170
|
+
| Target | MCP registration | Instructions |
|
|
171
|
+
|---|---|---|
|
|
172
|
+
| Copilot | `.vscode/mcp.json` → `servers.codegraph` | `AGENTS.md` block |
|
|
173
|
+
| OpenCode | `opencode.json` → `mcp.codegraph` | `AGENTS.md` block |
|
|
174
|
+
|
|
175
|
+
**The `init` flags are now thin wrappers.** `init --codegraph` and `init --no-codegraph` still behave exactly as before, but they call the *same* underlying `add codegraph` logic — one implementation, two entry points. CodeGraph remains off by default in minimal `init` and on with `--full-interview`; pass `--codegraph` to force it on, `--no-codegraph` to force it off.
|
|
176
|
+
|
|
177
|
+
cli-five still does **not** run `codegraph init` itself — it only registers the server and reminds you to index the project:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
npm i -g @colbymchenry/codegraph
|
|
181
|
+
codegraph init
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Jev (`add jev`) — tier-routing only, OpenCode only
|
|
185
|
+
|
|
186
|
+
`add jev` scaffolds an OpenCode plugin that adds a `local_tier_heuristic` tool. The Planner calls it once per task to classify the work as `trivial` / `minor` / `major`, then scales planning depth accordingly.
|
|
187
|
+
|
|
188
|
+
**Two things it deliberately does *not* do, stated plainly:**
|
|
189
|
+
|
|
190
|
+
1. **It does not call Jev.** `jev-harness` 0.2.0's `route` subcommand exposes no custom-criteria interface — it emits its own fixed tier vocabulary (`deterministic` / `lightweight_system2` / `heavy_system2`) and returns a constant confidence (`0.88`) under its offline/mock engine, so it cannot be thresholded on. The shipped tool is therefore a **local heuristic**, truthfully named `local_tier_heuristic`. The swap point for real Jev wiring is marked in `templates/opencode/plugin/jev-tier-router/index.js` (`JEVR_SWAP_POINT`).
|
|
191
|
+
2. **The test-gate is parked.** Gating Reviewer spawns via plugin interception (`tool.execute.before` / `permission.ask`) does not work: OpenCode plugin hooks do not fire under OpenChamber's embedded-server routing. Do not expect `add jev` to gate anything.
|
|
192
|
+
|
|
193
|
+
`list-addons` reports each add-on's honest capability rather than a bare "installed":
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
codegraph installed (MCP registration + AGENTS.md instructions) available MCP registration + AGENTS.md instructions
|
|
197
|
+
jev installed (tier-routing only) available local heuristic — …; test-gate parked — <issue link>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Fail-open is non-negotiable.** If the classifier is unavailable, errors, or returns malformed output, the tool returns `available: false` with tier `major` (the expensive tier) and never throws. The Planner falls back to its own judgment. cli-five and the scaffolded agents behave identically whether the plugin works, is missing, or is broken.
|
|
201
|
+
|
|
202
|
+
**Copilot has no equivalent.** `add jev` refuses cleanly on a Copilot target (exit 1, no files written) — there is no `tools.add`-style surface there.
|
|
203
|
+
|
|
130
204
|
|
|
131
205
|
## Model providers
|
|
132
206
|
|
|
133
|
-
During `init` you can choose the model provider and optionally customize each agent's model.
|
|
207
|
+
During `init --full-interview` you can choose the model provider and optionally customize each agent's model. The minimal `init` path uses provider defaults (honouring `--provider` and `--cost-mode`).
|
|
134
208
|
|
|
135
209
|
| Provider | Platform | Example model |
|
|
136
210
|
|---|---|---|
|
|
@@ -155,7 +229,7 @@ Change anytime by editing the `model:` line in the agent files.
|
|
|
155
229
|
|
|
156
230
|
## CodeGraph
|
|
157
231
|
|
|
158
|
-
CodeGraph adds a local, graph-backed codebase context server. When enabled, cli-five:
|
|
232
|
+
CodeGraph adds a local, graph-backed codebase context server. It is **off by default** in the minimal scaffold; enable it with `--codegraph` or `--full-interview`. When enabled, cli-five:
|
|
159
233
|
|
|
160
234
|
- Registers the CodeGraph MCP server in `opencode.json` (OpenCode) or `.vscode/mcp.json` (Copilot).
|
|
161
235
|
- Adds a marker-fenced CodeGraph section to `AGENTS.md`.
|
|
@@ -184,7 +258,7 @@ No extra settings required. Project agents live in `.opencode/agents/` and the p
|
|
|
184
258
|
|
|
185
259
|
## Skill discovery
|
|
186
260
|
|
|
187
|
-
cli-five searches **two sources** for skills matching your detected stack:
|
|
261
|
+
Skill discovery runs during `init --full-interview` (or when you pass `--skills`). cli-five searches **two sources** for skills matching your detected stack:
|
|
188
262
|
|
|
189
263
|
| Source | What it has | Stars |
|
|
190
264
|
|---|---|---|
|
package/package.json
CHANGED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import kleur from 'kleur';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { log } from '../util/log.mjs';
|
|
5
|
+
import { PLATFORM_COPILOT, PLATFORM_OPENCODE, platformLabel } from '../util/platforms.mjs';
|
|
6
|
+
import { addCodegraphTo, CODEGRAPH_INIT_REMINDER, mcpTargetFor } from './codegraph.mjs';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `add codegraph` — register the CodeGraph MCP server + AGENTS.md section.
|
|
10
|
+
*
|
|
11
|
+
* Works on both platforms. Same content and behavior as the previous inline
|
|
12
|
+
* init implementation; only the invocation path and write mechanism changed
|
|
13
|
+
* (mergeBlock instead of blunt overwrite).
|
|
14
|
+
*/
|
|
15
|
+
export async function runCodegraph({ cwd, args = {} }) {
|
|
16
|
+
const platform = detectPlatform(cwd);
|
|
17
|
+
|
|
18
|
+
if (platform === 'unknown') {
|
|
19
|
+
log.err('No cli-five scaffold detected (neither .opencode/agents nor .github/agents).');
|
|
20
|
+
log.dim('Run `npx cli-five init` first, then `npx cli-five add codegraph`.');
|
|
21
|
+
process.exitCode = 1;
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const dryRun = Boolean(args.dryRun);
|
|
26
|
+
const touched = addCodegraphTo({ cwd, platform, dryRun, track: true });
|
|
27
|
+
|
|
28
|
+
for (const t of touched) {
|
|
29
|
+
const rel = t.path.replace(cwd + '/', '');
|
|
30
|
+
const symbol = t.action === 'created' || t.action === 'updated' ? '+' : '~';
|
|
31
|
+
log.raw(` ${symbol} ${rel} ${kleur.dim(`(${t.action})`)}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
log.ok(`CodeGraph registered for ${platformLabel(platform)}`);
|
|
35
|
+
log.dim(`MCP: ${mcpTargetFor(platform)} · instructions: AGENTS.md`);
|
|
36
|
+
log.dim(CODEGRAPH_INIT_REMINDER.replace('CodeGraph is configured. ', ''));
|
|
37
|
+
|
|
38
|
+
return touched;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function detectPlatform(cwd) {
|
|
42
|
+
if (existsSync(join(cwd, '.opencode', 'agents'))) return PLATFORM_OPENCODE;
|
|
43
|
+
if (existsSync(join(cwd, '.github', 'agents', 'orchestrator.agent.md'))) return PLATFORM_COPILOT;
|
|
44
|
+
return 'unknown';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const __testables = { detectPlatform };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { mergeBlock } from '../util/merge.mjs';
|
|
4
|
+
import { PLATFORM_COPILOT, PLATFORM_OPENCODE } from '../util/platforms.mjs';
|
|
5
|
+
|
|
6
|
+
export const CODEGRAPH_STATUS = 'MCP registration + AGENTS.md instructions';
|
|
7
|
+
export const CODEGRAPH_BLOCK_NAME = 'codegraph';
|
|
8
|
+
export const CODEGRAPH_INIT_REMINDER =
|
|
9
|
+
'CodeGraph is configured. Remember to run `codegraph init` before asking agents to explore the codebase.';
|
|
10
|
+
|
|
11
|
+
// The AGENTS.md section is byte-identical to the pre-migration inline constant
|
|
12
|
+
// in scaffold.mjs. Do not change its content — only the write path changed.
|
|
13
|
+
export const CODEGRAPH_BLOCK = `## CodeGraph
|
|
14
|
+
|
|
15
|
+
This project is configured to use [CodeGraph](https://codegraph.ru) for graph-backed codebase context.
|
|
16
|
+
When you need to understand relationships, call paths, or impacts, use:
|
|
17
|
+
|
|
18
|
+
\`\`\`
|
|
19
|
+
codegraph explore "<your question>"
|
|
20
|
+
\`\`\`
|
|
21
|
+
|
|
22
|
+
The CodeGraph MCP server is registered in the project config. Run \`codegraph init\` in this directory
|
|
23
|
+
if the project has not been indexed yet.`;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The CodeGraph MCP server entries, per platform. Unchanged from the previous
|
|
27
|
+
* inline implementation — same server, same command, same shape.
|
|
28
|
+
*/
|
|
29
|
+
export function codegraphOpencodeConfig() {
|
|
30
|
+
return {
|
|
31
|
+
codegraph: {
|
|
32
|
+
type: 'local',
|
|
33
|
+
command: ['codegraph', 'serve', '--mcp'],
|
|
34
|
+
enabled: true,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function codegraphCopilotMcpJson() {
|
|
40
|
+
return {
|
|
41
|
+
inputs: [],
|
|
42
|
+
servers: {
|
|
43
|
+
codegraph: {
|
|
44
|
+
command: 'codegraph',
|
|
45
|
+
args: ['serve', '--mcp'],
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Which MCP file a given platform owns. Preserves the old per-platform split. */
|
|
52
|
+
export function mcpTargetFor(platform) {
|
|
53
|
+
return platform === PLATFORM_OPENCODE ? 'opencode.json' : join('.vscode', 'mcp.json');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Register CodeGraph in a workspace, using mergeBlock() for every write.
|
|
58
|
+
*
|
|
59
|
+
* Shared by `add codegraph` and init's legacy --codegraph flag — one
|
|
60
|
+
* implementation, two entry points.
|
|
61
|
+
*
|
|
62
|
+
* @returns {Array<{path:string, action:string}>} the files touched.
|
|
63
|
+
*/
|
|
64
|
+
export function addCodegraphTo({ cwd, platform, dryRun = false, track = false }) {
|
|
65
|
+
const touched = [];
|
|
66
|
+
const isOpenCode = platform === PLATFORM_OPENCODE;
|
|
67
|
+
|
|
68
|
+
// 1. MCP registration.
|
|
69
|
+
if (isOpenCode) {
|
|
70
|
+
const opencodePath = join(cwd, 'opencode.json');
|
|
71
|
+
// Preserve a pre-existing `plugins[]` (e.g. jev): deepMerge replaces arrays,
|
|
72
|
+
// so carry the existing value through untouched at the root.
|
|
73
|
+
const existing = readJson(opencodePath) || {};
|
|
74
|
+
const patch = { mcp: codegraphOpencodeConfig() };
|
|
75
|
+
if (Array.isArray(existing.plugins)) patch.plugins = existing.plugins;
|
|
76
|
+
touched.push(mergeBlock(opencodePath, 'codegraph', patch, { dryRun, track }));
|
|
77
|
+
} else {
|
|
78
|
+
const mcpPath = join(cwd, '.vscode', 'mcp.json');
|
|
79
|
+
const existing = readJson(mcpPath) || {};
|
|
80
|
+
// Preserve the old key order (`inputs` first) so output matches the
|
|
81
|
+
// pre-migration inline implementation byte-for-byte. `inputs` is an array;
|
|
82
|
+
// carry it through so deepMerge's array-replace is a no-op.
|
|
83
|
+
const patch = { inputs: Array.isArray(existing.inputs) ? existing.inputs : [] };
|
|
84
|
+
patch.servers = { codegraph: codegraphCopilotMcpJson().servers.codegraph };
|
|
85
|
+
touched.push(mergeBlock(mcpPath, 'codegraph', patch, { dryRun, track }));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 2. AGENTS.md section (fenced block; same marker name as the old template).
|
|
89
|
+
const agentsPath = join(cwd, 'AGENTS.md');
|
|
90
|
+
touched.push(mergeBlock(agentsPath, CODEGRAPH_BLOCK_NAME, CODEGRAPH_BLOCK, { dryRun }));
|
|
91
|
+
|
|
92
|
+
return touched;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Idempotent remove-of-duplicates is intentionally NOT implemented: mergeBlock
|
|
97
|
+
* already replaces an existing fenced block in place and deep-merges JSON, so
|
|
98
|
+
* re-running add codegraph does not duplicate anything.
|
|
99
|
+
*/
|
|
100
|
+
export function isCodegraphPresent(cwd, platform) {
|
|
101
|
+
const mcpPath = join(cwd, mcpTargetFor(platform));
|
|
102
|
+
const cfg = readJson(mcpPath);
|
|
103
|
+
if (platform === PLATFORM_OPENCODE) return Boolean(cfg?.mcp?.codegraph);
|
|
104
|
+
return Boolean(cfg?.servers?.codegraph);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function readJson(filePath) {
|
|
108
|
+
if (!existsSync(filePath)) return null;
|
|
109
|
+
try {
|
|
110
|
+
return JSON.parse(readFileSync(filePath, 'utf8'));
|
|
111
|
+
} catch {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Ensure a file exists with given content (used for a created-not-merged file). */
|
|
117
|
+
export function ensureFile(filePath, contents) {
|
|
118
|
+
if (existsSync(filePath)) return false;
|
|
119
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
120
|
+
writeFileSync(filePath, contents);
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export const __testables = { readJson, mcpTargetFor };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import kleur from 'kleur';
|
|
2
|
+
import { existsSync, readFileSync, cpSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { log } from '../util/log.mjs';
|
|
5
|
+
import { mergeBlock } from '../util/merge.mjs';
|
|
6
|
+
import { templatePath } from '../util/fs.mjs';
|
|
7
|
+
|
|
8
|
+
export const JEVR_STATUS =
|
|
9
|
+
'local heuristic — jev-harness lacks a custom-criteria interface as of 2026-09-26';
|
|
10
|
+
export const JEVR_TOOL = 'local_tier_heuristic';
|
|
11
|
+
export const TEST_GATE_ISSUE = 'https://github.com/idusortus/cli-five/issues';
|
|
12
|
+
|
|
13
|
+
const PLUGIN_REL = join('.opencode', 'plugin', 'jev-tier-router');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* `add jev` — tier-routing only.
|
|
17
|
+
*
|
|
18
|
+
* Ships a local tier-classification tool for the Planner. Does NOT wire the
|
|
19
|
+
* test-gate (parked: OpenCode plugin hooks don't fire under OpenChamber's
|
|
20
|
+
* embedded-server routing) and does NOT call Jev (no custom-criteria
|
|
21
|
+
* interface in jev-harness as of this release).
|
|
22
|
+
*/
|
|
23
|
+
export async function runJev({ cwd, args }) {
|
|
24
|
+
const platform = detectPlatform(cwd);
|
|
25
|
+
|
|
26
|
+
if (platform !== 'opencode') {
|
|
27
|
+
log.err('jev is OpenCode-only. No tools.add-equivalent surface exists for Copilot.');
|
|
28
|
+
log.dim('Re-run init with --target opencode, then `npx cli-five add jev`.');
|
|
29
|
+
process.exitCode = 1;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const written = [];
|
|
34
|
+
|
|
35
|
+
// 1. Plugin directory (package.json + index.js), confirmed convention.
|
|
36
|
+
const pluginDir = join(cwd, PLUGIN_REL);
|
|
37
|
+
mkdirSync(pluginDir, { recursive: true });
|
|
38
|
+
for (const file of ['package.json', 'index.js']) {
|
|
39
|
+
const src = templatePath('opencode', 'plugin', 'jev-tier-router', file);
|
|
40
|
+
const dest = join(pluginDir, file);
|
|
41
|
+
cpSync(src, dest);
|
|
42
|
+
written.push({ path: dest, written: true });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 2. Register the plugin path in opencode.json's `plugins` array.
|
|
46
|
+
// mergeBlock deep-merges JSON but replaces arrays, so read the existing
|
|
47
|
+
// array, union it, and hand the unioned value to mergeBlock (single
|
|
48
|
+
// write path — no second merge approach).
|
|
49
|
+
const opencodePath = join(cwd, 'opencode.json');
|
|
50
|
+
const plugins = readPlugins(opencodePath);
|
|
51
|
+
const pluginRef = toPosix(PLUGIN_REL);
|
|
52
|
+
if (!plugins.includes(pluginRef)) plugins.push(pluginRef);
|
|
53
|
+
mergeBlock(opencodePath, 'jev', { plugins }, { track: true, metaKey: '$cliFive' });
|
|
54
|
+
|
|
55
|
+
// 3. Planner instruction in AGENTS.md (fenced block, idempotent).
|
|
56
|
+
const agentsPath = join(cwd, 'AGENTS.md');
|
|
57
|
+
mergeBlock(agentsPath, 'jev-tier-routing', plannerInstruction());
|
|
58
|
+
|
|
59
|
+
log.ok(`Plugin written to ${kleur.bold(toPosix(PLUGIN_REL))}`);
|
|
60
|
+
log.ok(`Registered in opencode.json (plugins[])`);
|
|
61
|
+
log.ok('Planner instruction added to AGENTS.md');
|
|
62
|
+
log.raw('');
|
|
63
|
+
log.info(`Tool: ${kleur.bold(JEVR_TOOL)} (tier-routing only)`);
|
|
64
|
+
log.warn('Status: ' + JEVR_STATUS);
|
|
65
|
+
log.dim(`Test-gate is parked — see ${TEST_GATE_ISSUE}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function plannerInstruction() {
|
|
69
|
+
return `## Tier routing (local heuristic)
|
|
70
|
+
|
|
71
|
+
Before planning, call the \`${JEVR_TOOL}\` tool once with the task description.
|
|
72
|
+
|
|
73
|
+
- If it returns \`confidence\` >= 0.6, use its \`tier\` (trivial | minor | major) as your planning depth.
|
|
74
|
+
- If \`confidence\` < 0.6, or the tool is unavailable, use your own judgment and default to \`major\`.
|
|
75
|
+
- This is a local heuristic, not a Jev call. Never block or fail a turn because the tool is unavailable.`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function readPlugins(opencodePath) {
|
|
79
|
+
if (!existsSync(opencodePath)) return [];
|
|
80
|
+
try {
|
|
81
|
+
const cfg = JSON.parse(readFileSync(opencodePath, 'utf8'));
|
|
82
|
+
return Array.isArray(cfg.plugins) ? [...cfg.plugins] : [];
|
|
83
|
+
} catch {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function detectPlatform(cwd) {
|
|
89
|
+
if (existsSync(join(cwd, '.opencode', 'agents'))) return 'opencode';
|
|
90
|
+
if (existsSync(join(cwd, '.github', 'agents', 'orchestrator.agent.md'))) return 'copilot';
|
|
91
|
+
return 'unknown';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function toPosix(p) {
|
|
95
|
+
return p.split('\\').join('/');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const __testables = { plannerInstruction, readPlugins, detectPlatform };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { runJev, JEVR_STATUS, TEST_GATE_ISSUE } from './jev.mjs';
|
|
4
|
+
import { runCodegraph } from './codegraph-command.mjs';
|
|
5
|
+
import { CODEGRAPH_STATUS } from './codegraph.mjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Add-on registry for `cli-five add <name>`.
|
|
9
|
+
*
|
|
10
|
+
* An add-on describes an optional integration that can be layered onto an
|
|
11
|
+
* already-scaffolded repo. Entries without a `run` function are reserved
|
|
12
|
+
* (dispatcher/stub) targets — the mechanism is live, the integration is not.
|
|
13
|
+
*
|
|
14
|
+
* To wire a real target later: give it a `run({ cwd, args })` function (the
|
|
15
|
+
* `add` command will invoke it) and update `available`/`note` accordingly.
|
|
16
|
+
*/
|
|
17
|
+
export const ADDONS = {
|
|
18
|
+
codegraph: {
|
|
19
|
+
name: 'codegraph',
|
|
20
|
+
label: 'CodeGraph',
|
|
21
|
+
description: 'Graph-backed codebase context MCP server and agent instructions.',
|
|
22
|
+
// Matches jev's status-object pattern: explicit capability + status string,
|
|
23
|
+
// not a bare boolean.
|
|
24
|
+
available: true,
|
|
25
|
+
capability: 'MCP registration + AGENTS.md instructions',
|
|
26
|
+
status: CODEGRAPH_STATUS,
|
|
27
|
+
platforms: ['copilot', 'opencode'],
|
|
28
|
+
detect: detectCodeGraph,
|
|
29
|
+
run: runCodegraph,
|
|
30
|
+
},
|
|
31
|
+
jev: {
|
|
32
|
+
name: 'jev',
|
|
33
|
+
label: 'Jev',
|
|
34
|
+
description: 'Tier-routing tool for the Planner (local heuristic; test-gate parked).',
|
|
35
|
+
// Not a bare boolean: jev ships tier-routing ONLY. The test-gate half is
|
|
36
|
+
// parked because OpenCode plugin hooks don't fire under OpenChamber routing.
|
|
37
|
+
available: true,
|
|
38
|
+
capability: 'tier-routing only',
|
|
39
|
+
status: `${JEVR_STATUS}; test-gate parked — ${TEST_GATE_ISSUE}`,
|
|
40
|
+
platforms: ['opencode'],
|
|
41
|
+
detect: detectJev,
|
|
42
|
+
run: runJev,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const ADDON_NAMES = Object.keys(ADDONS);
|
|
47
|
+
|
|
48
|
+
export function listAddons() {
|
|
49
|
+
return Object.values(ADDONS);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function getAddon(name) {
|
|
53
|
+
if (!name) return undefined;
|
|
54
|
+
return ADDONS[String(name).toLowerCase()];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Inspect a workspace for evidence that an add-on is already present.
|
|
59
|
+
* Returns a list of human-readable signals (empty when not detected).
|
|
60
|
+
*/
|
|
61
|
+
export function detectAddon(addon, cwd) {
|
|
62
|
+
if (!addon || typeof addon.detect !== 'function') return [];
|
|
63
|
+
try {
|
|
64
|
+
return addon.detect(cwd) || [];
|
|
65
|
+
} catch {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ── Detectors ─────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Detect CodeGraph artifacts written by init today. Read-only: this inspects
|
|
74
|
+
* existing registration without modifying it.
|
|
75
|
+
*/
|
|
76
|
+
export function detectCodeGraph(cwd) {
|
|
77
|
+
const signals = [];
|
|
78
|
+
|
|
79
|
+
const opencodePath = join(cwd, 'opencode.json');
|
|
80
|
+
const opencodeCfg = readJson(opencodePath);
|
|
81
|
+
if (opencodeCfg?.mcp?.codegraph) signals.push('opencode.json mcp.codegraph');
|
|
82
|
+
|
|
83
|
+
const mcpPath = join(cwd, '.vscode', 'mcp.json');
|
|
84
|
+
const mcpCfg = readJson(mcpPath);
|
|
85
|
+
if (mcpCfg?.servers?.codegraph) signals.push('.vscode/mcp.json servers.codegraph');
|
|
86
|
+
|
|
87
|
+
const agentsPath = join(cwd, 'AGENTS.md');
|
|
88
|
+
if (existsSync(agentsPath)) {
|
|
89
|
+
try {
|
|
90
|
+
const body = readFileSync(agentsPath, 'utf8');
|
|
91
|
+
if (body.includes('<!-- CODEGRAPH_START -->')) signals.push('AGENTS.md CodeGraph section');
|
|
92
|
+
} catch {
|
|
93
|
+
/* ignore */
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return signals;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function readJson(filePath) {
|
|
101
|
+
if (!existsSync(filePath)) return null;
|
|
102
|
+
try {
|
|
103
|
+
return JSON.parse(readFileSync(filePath, 'utf8'));
|
|
104
|
+
} catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Detect the jev tier-router plugin: its directory on disk and/or its entry in
|
|
111
|
+
* opencode.json's `plugins` array. Read-only.
|
|
112
|
+
*/
|
|
113
|
+
export function detectJev(cwd) {
|
|
114
|
+
const signals = [];
|
|
115
|
+
|
|
116
|
+
if (existsSync(join(cwd, '.opencode', 'plugin', 'jev-tier-router', 'index.js'))) {
|
|
117
|
+
signals.push('.opencode/plugin/jev-tier-router');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const cfg = readJson(join(cwd, 'opencode.json'));
|
|
121
|
+
if (Array.isArray(cfg?.plugins) && cfg.plugins.some((p) => String(p).includes('jev-tier-router'))) {
|
|
122
|
+
signals.push('opencode.json plugins[]');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return signals;
|
|
126
|
+
}
|
package/src/cli.mjs
CHANGED
|
@@ -5,6 +5,8 @@ import { join } from 'node:path';
|
|
|
5
5
|
import { init } from './commands/init.mjs';
|
|
6
6
|
import { doctor } from './commands/doctor.mjs';
|
|
7
7
|
import { listStacks } from './commands/list-stacks.mjs';
|
|
8
|
+
import { add } from './commands/add.mjs';
|
|
9
|
+
import { listAddonsCommand } from './commands/list-addons.mjs';
|
|
8
10
|
import { STACK_SIGNATURES } from './steps/detect.mjs';
|
|
9
11
|
|
|
10
12
|
const HELP = `${kleur.bold('cli-five')} ${kleur.gray('— Code Like I\'m Five')}
|
|
@@ -14,7 +16,9 @@ ${kleur.bold('Usage')}
|
|
|
14
16
|
npx cli-five <command> [options]
|
|
15
17
|
|
|
16
18
|
${kleur.bold('Commands')}
|
|
17
|
-
init
|
|
19
|
+
init Scaffold the 5 agents + required tooling (minimal by default)
|
|
20
|
+
add <name> Install an optional add-on (dispatcher; targets land later)
|
|
21
|
+
list-addons Show installed vs. available add-ons
|
|
18
22
|
doctor Validate an existing cli-five setup
|
|
19
23
|
list-stacks Show detectable tech stacks
|
|
20
24
|
help Show this message
|
|
@@ -23,12 +27,19 @@ ${kleur.bold('Flags')}
|
|
|
23
27
|
--yes, -y Accept defaults and skip confirmations (still gated on overwrite)
|
|
24
28
|
--force Overwrite without confirmation. Dangerous. Use with --yes.
|
|
25
29
|
--dry-run Print actions without writing files
|
|
30
|
+
--full-interview Run the full legacy interview (docs, goals, persona, models). Enables CodeGraph by default
|
|
31
|
+
--skills Force skill discovery (default: full interview only)
|
|
26
32
|
--no-skills Skip the skills.sh discovery step
|
|
27
|
-
--
|
|
33
|
+
--instructions Force stack-specific instruction generation
|
|
34
|
+
--no-instructions Skip instruction generation
|
|
35
|
+
--persona Include the snarky persona block
|
|
36
|
+
--no-persona Omit the snarky persona block
|
|
37
|
+
--codegraph Add CodeGraph MCP + instructions (default: only with --full-interview)
|
|
38
|
+
--no-codegraph Skip CodeGraph MCP + instructions
|
|
39
|
+
--doc <file> Read project docs to pre-fill interview (repeatable; implies --full-interview)
|
|
28
40
|
--cost-mode <m> Override cost mode (premium, cheap, mixed) — skips interview question
|
|
29
41
|
--target <t> Target platform: copilot or opencode (default: copilot)
|
|
30
42
|
--provider <p> Model provider: copilot, opencode, opencode-go (default: platform default)
|
|
31
|
-
--no-codegraph Skip CodeGraph MCP + instructions
|
|
32
43
|
--cwd <path> Run against a directory other than the current one
|
|
33
44
|
--version, -v Print version and exit
|
|
34
45
|
`;
|
|
@@ -40,6 +51,10 @@ export async function run(argv) {
|
|
|
40
51
|
switch (cmd) {
|
|
41
52
|
case 'init':
|
|
42
53
|
return init(args);
|
|
54
|
+
case 'add':
|
|
55
|
+
return add(args);
|
|
56
|
+
case 'list-addons':
|
|
57
|
+
return listAddonsCommand(args);
|
|
43
58
|
case 'doctor':
|
|
44
59
|
return doctor(args);
|
|
45
60
|
case 'list-stacks':
|
|
@@ -68,8 +83,11 @@ function parse(argv) {
|
|
|
68
83
|
yes: false,
|
|
69
84
|
force: false,
|
|
70
85
|
dryRun: false,
|
|
71
|
-
|
|
72
|
-
|
|
86
|
+
fullInterview: false,
|
|
87
|
+
skills: null,
|
|
88
|
+
instructions: null,
|
|
89
|
+
persona: null,
|
|
90
|
+
codegraph: null,
|
|
73
91
|
docs: [],
|
|
74
92
|
costMode: null,
|
|
75
93
|
target: null,
|
|
@@ -81,7 +99,14 @@ function parse(argv) {
|
|
|
81
99
|
if (a === '--yes' || a === '-y') out.yes = true;
|
|
82
100
|
else if (a === '--force') out.force = true;
|
|
83
101
|
else if (a === '--dry-run') out.dryRun = true;
|
|
102
|
+
else if (a === '--full-interview' || a === '--full') out.fullInterview = true;
|
|
103
|
+
else if (a === '--skills') out.skills = true;
|
|
84
104
|
else if (a === '--no-skills') out.skills = false;
|
|
105
|
+
else if (a === '--instructions') out.instructions = true;
|
|
106
|
+
else if (a === '--no-instructions') out.instructions = false;
|
|
107
|
+
else if (a === '--persona') out.persona = true;
|
|
108
|
+
else if (a === '--no-persona') out.persona = false;
|
|
109
|
+
else if (a === '--codegraph') out.codegraph = true;
|
|
85
110
|
else if (a === '--no-codegraph') out.codegraph = false;
|
|
86
111
|
else if (a === '--doc') out.docs.push(argv[++i]);
|
|
87
112
|
else if (a === '--cost-mode') out.costMode = argv[++i];
|