cli-five 0.2.13 → 0.2.16
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 +144 -32
- package/package.json +14 -3
- package/plugin-agents/designer.agent.md +2 -2
- package/plugin-agents/planner.agent.md +1 -1
- package/src/addons/registry.mjs +96 -0
- package/src/cli.mjs +47 -4
- package/src/commands/add.mjs +55 -0
- package/src/commands/doctor.mjs +82 -15
- package/src/commands/init.mjs +164 -51
- package/src/commands/list-addons.mjs +40 -0
- package/src/steps/confirm.mjs +2 -0
- package/src/steps/detect.mjs +4 -0
- package/src/steps/interview.mjs +99 -34
- package/src/steps/platform.mjs +239 -0
- package/src/steps/scaffold.mjs +135 -34
- package/src/util/agents.mjs +63 -11
- package/src/util/merge.mjs +170 -0
- package/src/util/models.mjs +148 -0
- package/src/util/platforms.mjs +25 -0
- package/src/util/project.mjs +140 -0
- package/templates/AGENTS.md.tmpl +2 -0
- package/templates/opencode/agents/coder.md +74 -0
- package/templates/opencode/agents/designer.md +65 -0
- package/templates/opencode/agents/orchestrator.md +92 -0
- package/templates/opencode/agents/planner.md +76 -0
- package/templates/opencode/agents/reviewer.md +94 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import kleur from 'kleur';
|
|
2
|
+
import prompts from 'prompts';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import {
|
|
6
|
+
PLATFORM_COPILOT,
|
|
7
|
+
PLATFORM_OPENCODE,
|
|
8
|
+
PLATFORMS,
|
|
9
|
+
platformLabel,
|
|
10
|
+
} from '../util/platforms.mjs';
|
|
11
|
+
import { log } from '../util/log.mjs';
|
|
12
|
+
import {
|
|
13
|
+
agentNames,
|
|
14
|
+
getDefaultModelMap,
|
|
15
|
+
getModelCatalog,
|
|
16
|
+
normalizeProvider,
|
|
17
|
+
providerForPlatform,
|
|
18
|
+
providerLabel,
|
|
19
|
+
PROVIDER_COPILOT,
|
|
20
|
+
PROVIDER_ZEN,
|
|
21
|
+
PROVIDERS,
|
|
22
|
+
} from '../util/models.mjs';
|
|
23
|
+
|
|
24
|
+
const CUSTOM_SENTINEL = '__custom__';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Resolve whether CodeGraph should be enabled when no explicit `--codegraph`
|
|
28
|
+
* / `--no-codegraph` value was passed.
|
|
29
|
+
*
|
|
30
|
+
* The registration/opt-out mechanism is unchanged — this only decides the
|
|
31
|
+
* default: on for the full interview, off for minimal init.
|
|
32
|
+
*/
|
|
33
|
+
export function resolveCodegraphDefault(args, fullInterview) {
|
|
34
|
+
if (args.codegraph === true || args.codegraph === false) return args.codegraph;
|
|
35
|
+
return Boolean(fullInterview);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Ask the user to choose a target platform.
|
|
40
|
+
* If args.target is a valid platform, skip the prompt.
|
|
41
|
+
*
|
|
42
|
+
* Options:
|
|
43
|
+
* autoDetect — infer the platform from an existing scaffold before prompting
|
|
44
|
+
* askCodegraph — whether to ask the CodeGraph opt-out question (full interview)
|
|
45
|
+
* codegraphDefault — resolved default when no explicit flag was passed
|
|
46
|
+
*
|
|
47
|
+
* Note: CodeGraph registration and the `--no-codegraph` opt-out are unchanged;
|
|
48
|
+
* this only controls whether the question is asked / what the default is.
|
|
49
|
+
*/
|
|
50
|
+
export async function choosePlatform(args, { autoDetect = false, askCodegraph = true, codegraphDefault } = {}) {
|
|
51
|
+
if (codegraphDefault === undefined) codegraphDefault = args.codegraph !== false;
|
|
52
|
+
|
|
53
|
+
if (args.target) {
|
|
54
|
+
const t = String(args.target).toLowerCase();
|
|
55
|
+
if (PLATFORMS.includes(t)) {
|
|
56
|
+
return { platform: t, codegraph: codegraphDefault };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (autoDetect) {
|
|
61
|
+
const existing = detectExistingPlatform(args.cwd);
|
|
62
|
+
if (existing) {
|
|
63
|
+
log.dim(`Detected existing ${platformLabel(existing)} scaffold.`);
|
|
64
|
+
return { platform: existing, codegraph: codegraphDefault };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (args.yes) {
|
|
69
|
+
return { platform: PLATFORM_COPILOT, codegraph: codegraphDefault };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const { platform } = await prompts({
|
|
73
|
+
type: 'select',
|
|
74
|
+
name: 'platform',
|
|
75
|
+
message: 'Target platform',
|
|
76
|
+
choices: [
|
|
77
|
+
{
|
|
78
|
+
title: 'GitHub Copilot',
|
|
79
|
+
value: PLATFORM_COPILOT,
|
|
80
|
+
description: 'VS Code / Copilot Chat agent customization files',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
title: 'OpenCode',
|
|
84
|
+
value: PLATFORM_OPENCODE,
|
|
85
|
+
description: 'OpenCode project agents + opencode.json',
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
initial: 0,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (!platform) {
|
|
92
|
+
throw new Error('Platform selection cancelled. Nothing was written.');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!askCodegraph) {
|
|
96
|
+
return { platform, codegraph: codegraphDefault };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const { codegraph } = await prompts({
|
|
100
|
+
type: 'confirm',
|
|
101
|
+
name: 'codegraph',
|
|
102
|
+
message: `Add CodeGraph MCP + instructions for ${platformLabel(platform)}?`,
|
|
103
|
+
initial: true,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return { platform, codegraph: codegraph !== false };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Infer an existing scaffold's platform, or null when there is no scaffold. */
|
|
110
|
+
export function detectExistingPlatform(cwd) {
|
|
111
|
+
if (existsSync(join(cwd, '.opencode', 'agents'))) return PLATFORM_OPENCODE;
|
|
112
|
+
if (existsSync(join(cwd, '.github', 'agents', 'orchestrator.agent.md'))) return PLATFORM_COPILOT;
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Ask the user whether to customize models, pick a provider, and optionally
|
|
118
|
+
* override per-agent models.
|
|
119
|
+
*/
|
|
120
|
+
export async function chooseModels(platform, args) {
|
|
121
|
+
// Defaults for the platform.
|
|
122
|
+
const defaultProvider = providerForPlatform(platform, args.provider);
|
|
123
|
+
const defaults = getDefaultModelMap(defaultProvider);
|
|
124
|
+
|
|
125
|
+
if (args.yes) {
|
|
126
|
+
return {
|
|
127
|
+
provider: defaultProvider,
|
|
128
|
+
modelMap: defaults,
|
|
129
|
+
customized: false,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const { customize } = await prompts({
|
|
134
|
+
type: 'confirm',
|
|
135
|
+
name: 'customize',
|
|
136
|
+
message: 'Customize agent models?',
|
|
137
|
+
initial: false,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
if (!customize) {
|
|
141
|
+
return {
|
|
142
|
+
provider: defaultProvider,
|
|
143
|
+
modelMap: defaults,
|
|
144
|
+
customized: false,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const provider = await chooseProvider(platform, args.provider);
|
|
149
|
+
const baseMap = getDefaultModelMap(provider);
|
|
150
|
+
|
|
151
|
+
// Show defaults and ask which agents to override.
|
|
152
|
+
log.raw(kleur.dim('\n Default models:'));
|
|
153
|
+
for (const agent of agentNames()) {
|
|
154
|
+
log.raw(kleur.dim(` ${agent.padEnd(14)} → ${baseMap[agent]}`));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const { overrideAgents } = await prompts({
|
|
158
|
+
type: 'multiselect',
|
|
159
|
+
name: 'overrideAgents',
|
|
160
|
+
message: 'Select agents whose model you want to change',
|
|
161
|
+
choices: agentNames().map((name) => ({ title: name, value: name })),
|
|
162
|
+
hint: 'Space to toggle, Enter to confirm',
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const modelMap = { ...baseMap };
|
|
166
|
+
|
|
167
|
+
if (overrideAgents && overrideAgents.length > 0) {
|
|
168
|
+
const catalog = getModelCatalog(provider);
|
|
169
|
+
const choices = buildModelChoices(catalog);
|
|
170
|
+
|
|
171
|
+
for (const agent of overrideAgents) {
|
|
172
|
+
const { model } = await prompts({
|
|
173
|
+
type: 'select',
|
|
174
|
+
name: 'model',
|
|
175
|
+
message: `Model for ${agent}`,
|
|
176
|
+
choices,
|
|
177
|
+
initial: 0,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
if (model === CUSTOM_SENTINEL) {
|
|
181
|
+
const { custom } = await prompts({
|
|
182
|
+
type: 'text',
|
|
183
|
+
name: 'custom',
|
|
184
|
+
message: `Custom model for ${agent}`,
|
|
185
|
+
initial: baseMap[agent],
|
|
186
|
+
});
|
|
187
|
+
if (custom) modelMap[agent] = custom.trim();
|
|
188
|
+
} else if (model) {
|
|
189
|
+
modelMap[agent] = model;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return { provider, modelMap, customized: true };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function chooseProvider(platform, cliProvider) {
|
|
198
|
+
if (cliProvider) {
|
|
199
|
+
const normalized = normalizeProvider(cliProvider);
|
|
200
|
+
if (platform === PLATFORM_OPENCODE || normalized === PROVIDER_COPILOT) {
|
|
201
|
+
return normalized;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (platform === PLATFORM_COPILOT) {
|
|
206
|
+
return PROVIDER_COPILOT;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const { provider } = await prompts({
|
|
210
|
+
type: 'select',
|
|
211
|
+
name: 'provider',
|
|
212
|
+
message: 'OpenCode model provider',
|
|
213
|
+
choices: [
|
|
214
|
+
{
|
|
215
|
+
title: providerLabel(PROVIDER_ZEN),
|
|
216
|
+
value: PROVIDER_ZEN,
|
|
217
|
+
description: 'Curated, tested models via OpenCode Zen',
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
title: 'OpenCode Go',
|
|
221
|
+
value: 'opencode-go',
|
|
222
|
+
description: 'Low-cost open-coding model subscription',
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
initial: 0,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
if (!provider) {
|
|
229
|
+
throw new Error('Provider selection cancelled. Nothing was written.');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return provider;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function buildModelChoices(catalog) {
|
|
236
|
+
const choices = catalog.map((id) => ({ title: id, value: id }));
|
|
237
|
+
choices.push({ title: kleur.dim('Type custom model...'), value: CUSTOM_SENTINEL });
|
|
238
|
+
return choices;
|
|
239
|
+
}
|
package/src/steps/scaffold.mjs
CHANGED
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
2
|
import { log } from '../util/log.mjs';
|
|
3
|
-
import { readTemplate, render, writeFile, listFilesRecursive, relTo } from '../util/fs.mjs';
|
|
4
|
-
import { templatePath } from '../util/fs.mjs';
|
|
3
|
+
import { readTemplate, render, writeFile, listFilesRecursive, relTo, templatePath } from '../util/fs.mjs';
|
|
5
4
|
import { readFileSync } from 'node:fs';
|
|
5
|
+
import { PLATFORM_COPILOT, PLATFORM_OPENCODE, agentDirFor, agentFileFor } from '../util/platforms.mjs';
|
|
6
6
|
|
|
7
|
-
const
|
|
8
|
-
'orchestrator.agent.md',
|
|
9
|
-
'planner.agent.md',
|
|
10
|
-
'coder.agent.md',
|
|
11
|
-
'designer.agent.md',
|
|
12
|
-
'reviewer.agent.md',
|
|
13
|
-
];
|
|
14
|
-
|
|
7
|
+
const AGENT_NAMES = ['orchestrator', 'planner', 'coder', 'designer', 'reviewer'];
|
|
15
8
|
const HISTORY_FILES = ['orchestrator.md', 'planner.md', 'coder.md', 'designer.md', 'reviewer.md'];
|
|
16
9
|
|
|
17
|
-
|
|
10
|
+
// Legacy cost-mode maps for GitHub Copilot (used when models are not customized).
|
|
11
|
+
const COPILOT_COST_MODE_MAP = {
|
|
18
12
|
premium: {
|
|
19
13
|
Orchestrator: 'Claude Sonnet 4.6 (copilot)',
|
|
20
14
|
Planner: 'Claude Opus 4.6 (copilot)',
|
|
@@ -39,21 +33,55 @@ const MODEL_MAP = {
|
|
|
39
33
|
};
|
|
40
34
|
|
|
41
35
|
export function scaffold({ cwd, answers, args }) {
|
|
36
|
+
const platform = answers.platform || PLATFORM_COPILOT;
|
|
42
37
|
const vars = buildVars(answers);
|
|
43
38
|
const written = [];
|
|
44
39
|
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
if (platform === PLATFORM_OPENCODE) {
|
|
41
|
+
written.push(...scaffoldOpenCode({ cwd, answers, args, vars }));
|
|
42
|
+
} else {
|
|
43
|
+
written.push(...scaffoldCopilot({ cwd, answers, args, vars }));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Shared memory primitives
|
|
47
|
+
for (const tmpl of [
|
|
48
|
+
'AGENTS.md.tmpl',
|
|
49
|
+
'PROJECT.md.tmpl',
|
|
50
|
+
'STATE.md.tmpl',
|
|
51
|
+
'decisions.md.tmpl',
|
|
52
|
+
'agent-diary.md.tmpl',
|
|
53
|
+
]) {
|
|
54
|
+
const out = render(readTemplate(tmpl), vars);
|
|
55
|
+
const target = tmpl.replace(/\.tmpl$/, '');
|
|
56
|
+
written.push(writeFile(join(cwd, target), out, args));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Per-agent histories
|
|
60
|
+
for (const file of HISTORY_FILES) {
|
|
61
|
+
written.push(writeFile(join(cwd, 'histories', file), readTemplate('histories', file), args));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return written;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function scaffoldCopilot({ cwd, answers, args, vars }) {
|
|
68
|
+
const written = [];
|
|
69
|
+
const modelByAgent = answers.customizedModels
|
|
70
|
+
? answers.modelMap
|
|
71
|
+
: COPILOT_COST_MODE_MAP[answers.costMode] || COPILOT_COST_MODE_MAP.premium;
|
|
72
|
+
|
|
73
|
+
// Agents
|
|
74
|
+
for (const file of AGENT_NAMES.map((n) => `${n}.agent.md`)) {
|
|
47
75
|
const src = readFileSync(templatePath('.github', 'agents', file), 'utf8');
|
|
48
|
-
const swapped = swapModel(src,
|
|
76
|
+
const swapped = swapModel(src, modelByAgent);
|
|
49
77
|
written.push(writeFile(join(cwd, '.github', 'agents', file), swapped, args));
|
|
50
78
|
}
|
|
51
79
|
|
|
52
|
-
// copilot-instructions.md
|
|
80
|
+
// copilot-instructions.md
|
|
53
81
|
const ci = render(readTemplate('.github', 'copilot-instructions.md.tmpl'), vars);
|
|
54
82
|
written.push(writeFile(join(cwd, '.github', 'copilot-instructions.md'), ci, args));
|
|
55
83
|
|
|
56
|
-
// Empty containers
|
|
84
|
+
// Empty containers
|
|
57
85
|
written.push(
|
|
58
86
|
writeFile(
|
|
59
87
|
join(cwd, '.github', 'instructions', 'README.md'),
|
|
@@ -65,28 +93,85 @@ export function scaffold({ cwd, answers, args }) {
|
|
|
65
93
|
writeFile(join(cwd, '.github', 'skills', 'README.md'), readTemplate('.github', 'skills', 'README.md'), args),
|
|
66
94
|
);
|
|
67
95
|
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
'
|
|
71
|
-
'PROJECT.md.tmpl',
|
|
72
|
-
'STATE.md.tmpl',
|
|
73
|
-
'decisions.md.tmpl',
|
|
74
|
-
'agent-diary.md.tmpl',
|
|
75
|
-
]) {
|
|
76
|
-
const out = render(readTemplate(tmpl), vars);
|
|
77
|
-
const target = tmpl.replace(/\.tmpl$/, '');
|
|
78
|
-
written.push(writeFile(join(cwd, target), out, args));
|
|
96
|
+
// CodeGraph MCP for VS Code Copilot
|
|
97
|
+
if (answers.codegraph) {
|
|
98
|
+
written.push(writeFile(join(cwd, '.vscode', 'mcp.json'), JSON.stringify(codegraphMcpJson(), null, 2), args));
|
|
79
99
|
}
|
|
80
100
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
101
|
+
return written;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function scaffoldOpenCode({ cwd, answers, args, vars }) {
|
|
105
|
+
const written = [];
|
|
106
|
+
const modelByAgent = answers.modelMap || {};
|
|
107
|
+
const orchestratorModel = modelByAgent.Orchestrator || 'opencode/gpt-5.3-codex';
|
|
108
|
+
|
|
109
|
+
// Agents
|
|
110
|
+
for (const name of AGENT_NAMES) {
|
|
111
|
+
const src = readFileSync(templatePath('opencode', 'agents', `${name}.md`), 'utf8');
|
|
112
|
+
const swapped = swapModel(src, modelByAgent);
|
|
113
|
+
written.push(writeFile(join(cwd, '.opencode', 'agents', `${name}.md`), swapped, args));
|
|
84
114
|
}
|
|
85
115
|
|
|
116
|
+
// opencode.json
|
|
117
|
+
const opencodeConfig = buildOpencodeConfig({ answers, orchestratorModel });
|
|
118
|
+
written.push(writeFile(join(cwd, 'opencode.json'), JSON.stringify(opencodeConfig, null, 2) + '\n', args));
|
|
119
|
+
|
|
120
|
+
// Empty containers (still useful for OpenCode agents)
|
|
121
|
+
written.push(
|
|
122
|
+
writeFile(
|
|
123
|
+
join(cwd, '.github', 'instructions', 'README.md'),
|
|
124
|
+
readTemplate('.github', 'instructions', 'README.md'),
|
|
125
|
+
args,
|
|
126
|
+
),
|
|
127
|
+
);
|
|
128
|
+
written.push(
|
|
129
|
+
writeFile(join(cwd, '.github', 'skills', 'README.md'), readTemplate('.github', 'skills', 'README.md'), args),
|
|
130
|
+
);
|
|
131
|
+
|
|
86
132
|
return written;
|
|
87
133
|
}
|
|
88
134
|
|
|
135
|
+
function buildOpencodeConfig({ answers, orchestratorModel }) {
|
|
136
|
+
const smallModel = answers.provider === 'opencode-go'
|
|
137
|
+
? 'opencode-go/qwen3.8-flash'
|
|
138
|
+
: 'opencode/gpt-5-nano';
|
|
139
|
+
|
|
140
|
+
const config = {
|
|
141
|
+
$schema: 'https://opencode.ai/config.json',
|
|
142
|
+
model: orchestratorModel,
|
|
143
|
+
small_model: smallModel,
|
|
144
|
+
subagent_depth: 2,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
if (answers.codegraph) {
|
|
148
|
+
config.mcp = {
|
|
149
|
+
codegraph: {
|
|
150
|
+
type: 'local',
|
|
151
|
+
command: ['codegraph', 'serve', '--mcp'],
|
|
152
|
+
enabled: true,
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return config;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function codegraphMcpJson() {
|
|
161
|
+
return {
|
|
162
|
+
inputs: [],
|
|
163
|
+
servers: {
|
|
164
|
+
codegraph: {
|
|
165
|
+
command: 'codegraph',
|
|
166
|
+
args: ['serve', '--mcp'],
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
89
172
|
function buildVars(a) {
|
|
173
|
+
const codegraphBlock = a.codegraph ? CODEGRAPH_BLOCK : '';
|
|
174
|
+
|
|
90
175
|
return {
|
|
91
176
|
PROJECT_NAME: a.projectName,
|
|
92
177
|
ONE_LINER: a.oneLiner || 'TODO — write a one-line vision statement.',
|
|
@@ -105,17 +190,17 @@ ${a.docs}
|
|
|
105
190
|
` : '',
|
|
106
191
|
DATE: new Date().toISOString().slice(0, 10),
|
|
107
192
|
PERSONA_BLOCK: a.snark ? PERSONA_BLOCK : '',
|
|
193
|
+
CODEGRAPH_BLOCK: codegraphBlock,
|
|
108
194
|
};
|
|
109
195
|
}
|
|
110
196
|
|
|
111
197
|
function swapModel(src, modelByAgent) {
|
|
112
|
-
// Replace the YAML `model:` line based on the `name:` immediately above/around it.
|
|
113
198
|
const lines = src.split('\n');
|
|
114
199
|
let agentName = null;
|
|
115
200
|
for (let i = 0; i < lines.length; i++) {
|
|
116
|
-
const
|
|
117
|
-
if (
|
|
118
|
-
agentName =
|
|
201
|
+
const nameMatch = /^name:\s*(.+?)\s*$/.exec(lines[i]);
|
|
202
|
+
if (nameMatch) {
|
|
203
|
+
agentName = nameMatch[1];
|
|
119
204
|
continue;
|
|
120
205
|
}
|
|
121
206
|
if (agentName && /^model:\s*/.test(lines[i])) {
|
|
@@ -136,6 +221,22 @@ const PERSONA_BLOCK = `# Persona
|
|
|
136
221
|
|
|
137
222
|
`;
|
|
138
223
|
|
|
224
|
+
const CODEGRAPH_BLOCK = `
|
|
225
|
+
<!-- CODEGRAPH_START -->
|
|
226
|
+
## CodeGraph
|
|
227
|
+
|
|
228
|
+
This project is configured to use [CodeGraph](https://codegraph.ru) for graph-backed codebase context.
|
|
229
|
+
When you need to understand relationships, call paths, or impacts, use:
|
|
230
|
+
|
|
231
|
+
\`\`\`
|
|
232
|
+
codegraph explore "<your question>"
|
|
233
|
+
\`\`\`
|
|
234
|
+
|
|
235
|
+
The CodeGraph MCP server is registered in the project config. Run \`codegraph init\` in this directory
|
|
236
|
+
if the project has not been indexed yet.
|
|
237
|
+
<!-- CODEGRAPH_END -->
|
|
238
|
+
`;
|
|
239
|
+
|
|
139
240
|
export function summarize(written, cwd) {
|
|
140
241
|
const lines = [];
|
|
141
242
|
for (const w of written) {
|
package/src/util/agents.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
|
+
import YAML from 'yaml';
|
|
2
3
|
|
|
3
4
|
export const AGENT_FILES = [
|
|
4
5
|
'orchestrator.agent.md',
|
|
@@ -8,6 +9,14 @@ export const AGENT_FILES = [
|
|
|
8
9
|
'reviewer.agent.md',
|
|
9
10
|
];
|
|
10
11
|
|
|
12
|
+
export const OPENCODE_AGENT_FILES = [
|
|
13
|
+
'orchestrator.md',
|
|
14
|
+
'planner.md',
|
|
15
|
+
'coder.md',
|
|
16
|
+
'designer.md',
|
|
17
|
+
'reviewer.md',
|
|
18
|
+
];
|
|
19
|
+
|
|
11
20
|
export const EXPECTED_AGENT_NAMES = {
|
|
12
21
|
'orchestrator.agent.md': 'Orchestrator',
|
|
13
22
|
'planner.agent.md': 'Planner',
|
|
@@ -89,6 +98,44 @@ export function validateAgentSource(source, fileName, { requiredMarkers = [] } =
|
|
|
89
98
|
return errors;
|
|
90
99
|
}
|
|
91
100
|
|
|
101
|
+
export function validateOpenCodeAgentSource(source, fileName) {
|
|
102
|
+
const errors = [];
|
|
103
|
+
const { frontmatter, body } = parseAgentSource(source);
|
|
104
|
+
|
|
105
|
+
if (!frontmatter) {
|
|
106
|
+
return ['missing YAML frontmatter'];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (const key of ['description', 'mode', 'model']) {
|
|
110
|
+
if (!(key in frontmatter)) {
|
|
111
|
+
errors.push(`missing frontmatter key: ${key}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const mode = String(frontmatter.mode || '').toLowerCase();
|
|
116
|
+
if (mode && !['primary', 'subagent', 'all'].includes(mode)) {
|
|
117
|
+
errors.push(`invalid mode: ${frontmatter.mode}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const expectedMode = fileName === 'orchestrator.md' ? 'primary' : 'subagent';
|
|
121
|
+
if (mode && mode !== expectedMode && mode !== 'all') {
|
|
122
|
+
errors.push(`expected mode ${expectedMode}, found ${frontmatter.mode}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (fileName === 'orchestrator.md') {
|
|
126
|
+
const task = frontmatter.permission?.task || frontmatter.permissions?.task;
|
|
127
|
+
if (!task) {
|
|
128
|
+
errors.push('orchestrator must declare permission.task for subagents');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!body.trim()) {
|
|
133
|
+
errors.push('agent body must not be empty');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return errors;
|
|
137
|
+
}
|
|
138
|
+
|
|
92
139
|
function requiredFrontmatterKeys(fileName) {
|
|
93
140
|
return fileName === 'orchestrator.agent.md'
|
|
94
141
|
? ['name', 'description', 'model', 'tools', 'agents']
|
|
@@ -96,16 +143,21 @@ function requiredFrontmatterKeys(fileName) {
|
|
|
96
143
|
}
|
|
97
144
|
|
|
98
145
|
function parseFrontmatter(block) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
146
|
+
try {
|
|
147
|
+
return YAML.parse(block, { strict: false });
|
|
148
|
+
} catch {
|
|
149
|
+
// Fall back to the legacy line parser for simple Copilot frontmatter.
|
|
150
|
+
const out = {};
|
|
151
|
+
for (const rawLine of block.split('\n')) {
|
|
152
|
+
const line = rawLine.trim();
|
|
153
|
+
if (!line) continue;
|
|
154
|
+
const match = /^([A-Za-z-]+):\s*(.+)$/.exec(line);
|
|
155
|
+
if (!match) continue;
|
|
156
|
+
const [, key, rawValue] = match;
|
|
157
|
+
out[key] = parseFrontmatterValue(rawValue.trim());
|
|
158
|
+
}
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
109
161
|
}
|
|
110
162
|
|
|
111
163
|
function parseFrontmatterValue(rawValue) {
|
|
@@ -134,4 +186,4 @@ function parseArrayValue(rawValue) {
|
|
|
134
186
|
}
|
|
135
187
|
return part;
|
|
136
188
|
});
|
|
137
|
-
}
|
|
189
|
+
}
|