@aiwg/cli 2026.8.0 → 2026.8.2
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 +33 -0
- package/agentic/code/providers/capability-matrix.yaml +511 -0
- package/agentic/code/providers/model-capabilities.v1.json +120 -0
- package/agentic/code/providers/model-catalog.v1.json +96 -0
- package/agentic/code/providers/model-policy-evaluations.v1.json +50 -0
- package/agentic/code/providers/premium-model-allowlist.v1.json +36 -0
- package/bin/aiwg.mjs +14 -10
- package/dist/src/api/index.d.ts +1 -0
- package/dist/src/api/index.js +1 -0
- package/dist/src/artifacts/cli.js +2 -0
- package/dist/src/artifacts/types.js +4 -0
- package/dist/src/auth/client.js +209 -0
- package/dist/src/auth/config.js +38 -0
- package/dist/src/auth/credential-store.js +141 -0
- package/dist/src/auth/resource-credentials.js +25 -0
- package/dist/src/auth/types.js +2 -0
- package/dist/src/channel/manager.mjs +5 -5
- package/dist/src/cli/handlers/auth.js +125 -0
- package/dist/src/cli/handlers/help.js +1 -0
- package/dist/src/cli/handlers/index.js +3 -1
- package/dist/src/cli/handlers/install.js +42 -4
- package/dist/src/cli/handlers/marketplace.js +375 -122
- package/dist/src/cli/handlers/resource-versions.js +2 -0
- package/dist/src/cli/handlers/sessions.js +23 -5
- package/dist/src/cli/handlers/subcommands.js +10 -1
- package/dist/src/cli/handlers/use.js +342 -43
- package/dist/src/config/gitignore.js +1 -0
- package/dist/src/extensions/commands/definitions.js +19 -0
- package/dist/src/marketplace/exchange.js +602 -0
- package/dist/src/marketplace/provenance-types.js +19 -0
- package/dist/src/marketplace/provenance.js +834 -0
- package/dist/src/memory/canonical-context.js +342 -0
- package/dist/src/memory/context-pack.js +282 -0
- package/dist/src/memory/index.js +4 -0
- package/dist/src/memory/intake.js +118 -0
- package/dist/src/packages/adapters/git.js +79 -29
- package/dist/src/packages/package-discovery.js +81 -0
- package/dist/src/packages/package-registry.js +2 -0
- package/dist/src/packages/registry.js +119 -20
- package/dist/src/resources/resolver.js +1 -0
- package/dist/src/resources/web-release.d.ts +3 -1
- package/dist/src/resources/web-release.js +14 -6
- package/dist/src/serve/agentic-sandbox-fleet-client.js +213 -0
- package/dist/src/serve/fleet-mission-conductor.js +293 -0
- package/dist/src/sessions/index.js +1 -0
- package/dist/src/sessions/output-registration.js +338 -0
- package/dist/src/sessions/promotion.js +73 -2
- package/dist/src/sessions/repository.js +2 -1
- package/dist/src/update/notifier.mjs +13 -2
- package/package.json +8 -1
- package/tools/_resolve-impl.mjs +74 -0
- package/tools/agents/deploy-agents.mjs +962 -0
- package/tools/agents/providers/base.mjs +2954 -0
- package/tools/agents/providers/claude.mjs +711 -0
- package/tools/agents/providers/codex.mjs +699 -0
- package/tools/agents/providers/copilot.mjs +659 -0
- package/tools/agents/providers/cursor.mjs +714 -0
- package/tools/agents/providers/factory.mjs +1130 -0
- package/tools/agents/providers/hermes.mjs +663 -0
- package/tools/agents/providers/hook-capabilities.mjs +85 -0
- package/tools/agents/providers/model-role.mjs +56 -0
- package/tools/agents/providers/openclaw-translator.mjs +348 -0
- package/tools/agents/providers/openclaw.mjs +680 -0
- package/tools/agents/providers/opencode.mjs +675 -0
- package/tools/agents/providers/openhuman.mjs +292 -0
- package/tools/agents/providers/warp.mjs +413 -0
- package/tools/agents/providers/windsurf.mjs +748 -0
- package/tools/commands/deploy-prompts-codex.mjs +336 -0
- package/tools/plugin/package-plugins.mjs +1013 -0
- package/tools/skills/deploy-skills-codex.mjs +571 -0
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode Provider
|
|
3
|
+
*
|
|
4
|
+
* Deploys skills and rules in OpenCode format with mode, temperature,
|
|
5
|
+
* tools, and permission configurations based on agent category.
|
|
6
|
+
*
|
|
7
|
+
* Deployment paths:
|
|
8
|
+
* - Agents: .opencode/agent/ (discovered via agent glob pattern)
|
|
9
|
+
* - Commands: .opencode/command/ (generated wrappers for operator workflows)
|
|
10
|
+
* - Skills: .opencode/skill/ (discovered via skill glob: SKILL.md)
|
|
11
|
+
* - Rules: .opencode/rule/ (loaded via `instructions` array in opencode.json)
|
|
12
|
+
*
|
|
13
|
+
* Special features:
|
|
14
|
+
* - Category-based configuration (analysis, documentation, planning, implementation)
|
|
15
|
+
* - Permission system with bash command whitelist
|
|
16
|
+
* - Temperature and steps per category
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import realFs from 'fs';
|
|
20
|
+
import { createRequire } from 'module';
|
|
21
|
+
const _require = createRequire(import.meta.url);
|
|
22
|
+
const staticModelCatalog = _require('../../../agentic/code/providers/model-catalog.v1.json');
|
|
23
|
+
let fs;
|
|
24
|
+
try { const gfs = _require('graceful-fs'); gfs.gracefulify(realFs); fs = realFs; } catch { fs = realFs; }
|
|
25
|
+
import path from 'path';
|
|
26
|
+
import {
|
|
27
|
+
ensureDir,
|
|
28
|
+
listMdFiles,
|
|
29
|
+
listMdFilesRecursive,
|
|
30
|
+
deployFiles,
|
|
31
|
+
inferAgentCategory,
|
|
32
|
+
createAgentsMdFromTemplate,
|
|
33
|
+
initializeFrameworkWorkspace,
|
|
34
|
+
getAddonAgentFiles,
|
|
35
|
+
getAddonCommandFiles,
|
|
36
|
+
getAddonRuleFiles,
|
|
37
|
+
getAddonSkillDirs,
|
|
38
|
+
listSkillDirs,
|
|
39
|
+
deploySkillDir,
|
|
40
|
+
deploySkillsWithKernelRouting,
|
|
41
|
+
isKernelSkill,
|
|
42
|
+
pruneStaleAiwgSkills,
|
|
43
|
+
computeAllKernelNames,
|
|
44
|
+
normalizeDeploymentMode,
|
|
45
|
+
collectFrameworkArtifacts,
|
|
46
|
+
listOnDemandRuleFiles,
|
|
47
|
+
writeOnDemandRuleIndex,
|
|
48
|
+
cleanupOldRuleFiles,
|
|
49
|
+
loadRuntimeModelCatalog,
|
|
50
|
+
filterCommandsAgainstSkills,
|
|
51
|
+
deploySoulCompanions
|
|
52
|
+
} from './base.mjs';
|
|
53
|
+
const modelCatalog = loadRuntimeModelCatalog(staticModelCatalog);
|
|
54
|
+
|
|
55
|
+
// ============================================================================
|
|
56
|
+
// Provider Configuration
|
|
57
|
+
// ============================================================================
|
|
58
|
+
|
|
59
|
+
export const name = 'opencode';
|
|
60
|
+
export const aliases = [];
|
|
61
|
+
|
|
62
|
+
export const paths = {
|
|
63
|
+
agents: '.opencode/agent/', // Discovered via {agent,agents}/**/*.md glob (#773)
|
|
64
|
+
commands: '.opencode/command/',
|
|
65
|
+
// Skills sequestered under .opencode/.aiwg/skill/ — index-driven discovery (#1212).
|
|
66
|
+
skills: '.opencode/.aiwg/skill/',
|
|
67
|
+
rules: '.opencode/rule/',
|
|
68
|
+
modes: '.opencode/mode/' // PUW-035 (#1136) — TUI-selectable primary modes
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Kernel skills (always-loaded) deploy to the platform-native dir.
|
|
72
|
+
export const kernelSkillsPath = '.opencode/skill/';
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* SDLC primary roles emitted as OpenCode TUI modes per PUW-035 (#1136).
|
|
76
|
+
*
|
|
77
|
+
* OpenCode scans `.opencode/{mode,modes}/*.md` for selectable primary modes.
|
|
78
|
+
* AIWG SDLC role agents map naturally onto modes — operators pick a "mode"
|
|
79
|
+
* when starting an OpenCode session and get the role's persona + tool set
|
|
80
|
+
* loaded automatically.
|
|
81
|
+
*
|
|
82
|
+
* The list is curated rather than auto-derived. Auto-promoting all 190+
|
|
83
|
+
* AIWG agents would clutter the TUI mode picker. The selected roles are
|
|
84
|
+
* the ones most often invoked as session-level primary personas.
|
|
85
|
+
*/
|
|
86
|
+
const SDLC_PRIMARY_ROLES = [
|
|
87
|
+
'architecture-designer',
|
|
88
|
+
'security-architect',
|
|
89
|
+
'test-architect',
|
|
90
|
+
'requirements-analyst',
|
|
91
|
+
'product-strategist',
|
|
92
|
+
'documentation-synthesizer',
|
|
93
|
+
'code-reviewer',
|
|
94
|
+
'debugger',
|
|
95
|
+
'devops-engineer',
|
|
96
|
+
'incident-responder',
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
export const support = {
|
|
100
|
+
agents: 'native', // Discovered via {agent,agents}/**/*.md glob
|
|
101
|
+
commands: 'native', // Generated wrappers live in .opencode/command/
|
|
102
|
+
skills: 'native', // Discovered via {skill,skills}/**/SKILL.md
|
|
103
|
+
rules: 'conventional', // Requires instructions[] entry in opencode.json
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const capabilities = {
|
|
107
|
+
skills: true,
|
|
108
|
+
rules: true,
|
|
109
|
+
aggregatedOutput: false,
|
|
110
|
+
yamlFormat: false
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// ============================================================================
|
|
114
|
+
// Model Mapping
|
|
115
|
+
// ============================================================================
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Map model shorthand to OpenCode format (full provider/model path)
|
|
119
|
+
*/
|
|
120
|
+
export function mapModel(originalModel, modelCfg, modelsConfig) {
|
|
121
|
+
const opencodeModels = {
|
|
122
|
+
'opus': modelCatalog.providers.opencode.roles.reasoning.id,
|
|
123
|
+
'sonnet': modelCatalog.providers.opencode.roles.coding.id,
|
|
124
|
+
'haiku': modelCatalog.providers.opencode.roles.efficiency.id
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Handle override models first
|
|
128
|
+
if (modelCfg.reasoningModel || modelCfg.codingModel || modelCfg.efficiencyModel) {
|
|
129
|
+
const clean = (originalModel || 'sonnet').toLowerCase().replace(/['"]/g, '');
|
|
130
|
+
if (/opus/i.test(clean)) return modelCfg.reasoningModel || opencodeModels.opus;
|
|
131
|
+
if (/haiku/i.test(clean)) return modelCfg.efficiencyModel || opencodeModels.haiku;
|
|
132
|
+
return modelCfg.codingModel || opencodeModels.sonnet;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const clean = (originalModel || 'sonnet').toLowerCase().replace(/['"]/g, '');
|
|
136
|
+
|
|
137
|
+
for (const [key, value] of Object.entries(opencodeModels)) {
|
|
138
|
+
if (clean.includes(key)) return value;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return opencodeModels.sonnet; // default
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ============================================================================
|
|
145
|
+
// Category Configuration
|
|
146
|
+
// ============================================================================
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Get OpenCode agent configuration based on category
|
|
150
|
+
*/
|
|
151
|
+
export function getAgentConfig(category, name) {
|
|
152
|
+
const configs = {
|
|
153
|
+
analysis: {
|
|
154
|
+
permission: {
|
|
155
|
+
bash: {
|
|
156
|
+
'git *': 'allow',
|
|
157
|
+
'npm audit': 'allow',
|
|
158
|
+
'npm test': 'allow',
|
|
159
|
+
'*': 'ask'
|
|
160
|
+
},
|
|
161
|
+
edit: 'ask'
|
|
162
|
+
},
|
|
163
|
+
temperature: 0.2,
|
|
164
|
+
steps: 30
|
|
165
|
+
},
|
|
166
|
+
documentation: {
|
|
167
|
+
permission: {},
|
|
168
|
+
temperature: 0.4,
|
|
169
|
+
steps: 50
|
|
170
|
+
},
|
|
171
|
+
planning: {
|
|
172
|
+
permission: {
|
|
173
|
+
bash: 'ask',
|
|
174
|
+
edit: 'ask'
|
|
175
|
+
},
|
|
176
|
+
temperature: 0.3,
|
|
177
|
+
steps: 40
|
|
178
|
+
},
|
|
179
|
+
implementation: {
|
|
180
|
+
permission: {
|
|
181
|
+
bash: {
|
|
182
|
+
'aiwg *': 'allow',
|
|
183
|
+
'git status': 'allow',
|
|
184
|
+
'git diff': 'allow',
|
|
185
|
+
'git log*': 'allow',
|
|
186
|
+
'npm test': 'allow',
|
|
187
|
+
'npm run *': 'allow',
|
|
188
|
+
'git push': 'ask',
|
|
189
|
+
'rm -rf': 'deny',
|
|
190
|
+
'*': 'ask'
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
temperature: 0.3,
|
|
194
|
+
steps: 100
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
return configs[category] || configs.implementation;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ============================================================================
|
|
202
|
+
// Content Transformation
|
|
203
|
+
// ============================================================================
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Transform AIWG agent to OpenCode agent format
|
|
207
|
+
*/
|
|
208
|
+
export function transformAgent(srcPath, content, opts) {
|
|
209
|
+
const { modelsConfig = {} } = opts;
|
|
210
|
+
|
|
211
|
+
// Parse existing frontmatter
|
|
212
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
213
|
+
if (!fmMatch) return content;
|
|
214
|
+
|
|
215
|
+
const [, frontmatter, body] = fmMatch;
|
|
216
|
+
|
|
217
|
+
// Extract metadata
|
|
218
|
+
const name = frontmatter.match(/name:\s*(.+)/)?.[1]?.trim();
|
|
219
|
+
const description = frontmatter.match(/description:\s*(.+)/)?.[1]?.trim();
|
|
220
|
+
const modelMatch = frontmatter.match(/model:\s*(.+)/)?.[1]?.trim();
|
|
221
|
+
const categoryMatch = frontmatter.match(/category:\s*(.+)/)?.[1]?.trim();
|
|
222
|
+
const orchestrationMatch = frontmatter.match(/orchestration:\s*(.+)/)?.[1]?.trim();
|
|
223
|
+
|
|
224
|
+
// Map model to OpenCode format
|
|
225
|
+
const opencodeModel = mapModel(modelMatch, opts, modelsConfig);
|
|
226
|
+
|
|
227
|
+
// Determine agent category
|
|
228
|
+
const category = categoryMatch || inferAgentCategory(name, body);
|
|
229
|
+
|
|
230
|
+
// Get configuration based on category
|
|
231
|
+
const { permission, temperature, steps } = getAgentConfig(category, name);
|
|
232
|
+
|
|
233
|
+
// Mode: primary for orchestration agents, subagent for others
|
|
234
|
+
const mode = orchestrationMatch === 'true' ? 'primary' : 'subagent';
|
|
235
|
+
|
|
236
|
+
// Generate OpenCode agent frontmatter
|
|
237
|
+
// Valid fields: description, mode, model, temperature, topP, steps, color, hidden, permission, options
|
|
238
|
+
let opencodeFrontmatter = `---
|
|
239
|
+
description: ${description || 'AIWG SDLC agent'}
|
|
240
|
+
mode: ${mode}
|
|
241
|
+
model: ${opencodeModel}
|
|
242
|
+
temperature: ${temperature}
|
|
243
|
+
steps: ${steps}`;
|
|
244
|
+
|
|
245
|
+
// Add permission configuration (controls tool access — opencode has no separate tools block)
|
|
246
|
+
if (Object.keys(permission).length > 0) {
|
|
247
|
+
opencodeFrontmatter += `\npermission:`;
|
|
248
|
+
for (const [perm, value] of Object.entries(permission)) {
|
|
249
|
+
if (typeof value === 'object') {
|
|
250
|
+
opencodeFrontmatter += `\n ${perm}:`;
|
|
251
|
+
for (const [cmd, action] of Object.entries(value)) {
|
|
252
|
+
opencodeFrontmatter += `\n "${cmd}": ${action}`;
|
|
253
|
+
}
|
|
254
|
+
} else {
|
|
255
|
+
opencodeFrontmatter += `\n ${perm}: ${value}`;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
opencodeFrontmatter += `\n---`;
|
|
261
|
+
|
|
262
|
+
return `${opencodeFrontmatter}\n\n${body.trim()}`;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Transform AIWG command to OpenCode command format
|
|
267
|
+
*/
|
|
268
|
+
export function transformCommand(srcPath, content, opts) {
|
|
269
|
+
const { modelsConfig = {} } = opts;
|
|
270
|
+
|
|
271
|
+
// Parse existing frontmatter
|
|
272
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
273
|
+
if (!fmMatch) {
|
|
274
|
+
// No frontmatter, add minimal OpenCode frontmatter
|
|
275
|
+
const firstLine = content.split('\n')[0];
|
|
276
|
+
const description = firstLine.replace(/^#\s*/, '').trim() || 'AIWG command';
|
|
277
|
+
return `---\ndescription: ${description}\nsubtask: true\n---\n\n${content}`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const [, frontmatter, body] = fmMatch;
|
|
281
|
+
|
|
282
|
+
// Extract metadata
|
|
283
|
+
const description = frontmatter.match(/description:\s*(.+)/)?.[1]?.trim();
|
|
284
|
+
const agentMatch = frontmatter.match(/agent:\s*(.+)/)?.[1]?.trim();
|
|
285
|
+
const modelMatch = frontmatter.match(/model:\s*(.+)/)?.[1]?.trim();
|
|
286
|
+
|
|
287
|
+
// Build OpenCode command frontmatter
|
|
288
|
+
let opencodeFrontmatter = `---\ndescription: ${description || 'AIWG command'}`;
|
|
289
|
+
|
|
290
|
+
if (agentMatch) {
|
|
291
|
+
opencodeFrontmatter += `\nagent: ${agentMatch}`;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (modelMatch) {
|
|
295
|
+
const opencodeModel = mapModel(modelMatch, opts, modelsConfig);
|
|
296
|
+
opencodeFrontmatter += `\nmodel: ${opencodeModel}`;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
opencodeFrontmatter += `\nsubtask: true\n---`;
|
|
300
|
+
|
|
301
|
+
return `${opencodeFrontmatter}\n\n${body.trim()}`;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// ============================================================================
|
|
305
|
+
// Deployment Functions
|
|
306
|
+
// ============================================================================
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Deploy agents to .opencode/agent/
|
|
310
|
+
*
|
|
311
|
+
* OpenCode discovers agents via glob within each .opencode directory.
|
|
312
|
+
* Agent files use YAML frontmatter (description, mode, model, temperature, steps, permission)
|
|
313
|
+
* with Markdown body as the system prompt.
|
|
314
|
+
*
|
|
315
|
+
* See: packages/opencode/src/config/config.ts loadAgent()
|
|
316
|
+
*/
|
|
317
|
+
export function deployAgents(agentFiles, targetDir, opts) {
|
|
318
|
+
const destDir = path.join(targetDir, paths.agents);
|
|
319
|
+
ensureDir(destDir, opts.dryRun);
|
|
320
|
+
return deployFiles(agentFiles, destDir, opts, transformAgent);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Deploy commands — no-op here for OpenCode.
|
|
325
|
+
*
|
|
326
|
+
* The top-level deploy orchestrator mirrors selected skill workflows into
|
|
327
|
+
* `.opencode/command/` after provider deployment, using the same
|
|
328
|
+
* skill→command translation path shared by other command-surface providers.
|
|
329
|
+
* See: packages/opencode/src/command/index.ts
|
|
330
|
+
*/
|
|
331
|
+
export function deployCommands(_commandFiles, _targetDir, _opts) {
|
|
332
|
+
// No-op here: deploy-agents.mjs mirrors selected skills to .opencode/command/.
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Deploy skills to .opencode/skill/ (primary) and .agents/skills/ (cross-agent
|
|
337
|
+
* compatibility, PUW-012 #1113).
|
|
338
|
+
*
|
|
339
|
+
* The .agents/skills/ path is an interop convention for projects using
|
|
340
|
+
* multiple AI coding tools. OpenCode's skill walker scans the project tree
|
|
341
|
+
* and picks up `.agents/skills/<name>/SKILL.md` as a secondary discovery
|
|
342
|
+
* location, so this is purely additive — does not replace the primary
|
|
343
|
+
* `.opencode/skill/` deploy.
|
|
344
|
+
*/
|
|
345
|
+
export function deploySkills(skillDirs, targetDir, opts) {
|
|
346
|
+
// Primary: kernel-vs-standard routing (#1212/#1216)
|
|
347
|
+
// - kernel skills → .opencode/skill/ (platform-native, always-loaded)
|
|
348
|
+
// - standard → .opencode/.aiwg/skill/ (index-discoverable)
|
|
349
|
+
const standardDestDir = path.join(targetDir, paths.skills);
|
|
350
|
+
const kernelDestDir = path.join(targetDir, kernelSkillsPath);
|
|
351
|
+
deploySkillsWithKernelRouting(skillDirs, standardDestDir, kernelDestDir, opts);
|
|
352
|
+
|
|
353
|
+
// Cross-agent compatibility: .agents/skills/ — honors #1217 no-copy
|
|
354
|
+
// default. Filter to kernel-only unless operator opts in via env var so
|
|
355
|
+
// standard skills stay at $AIWG_ROOT and are reached via `aiwg discover`.
|
|
356
|
+
const copyStandardSkills = opts?.copyStandardSkills === true;
|
|
357
|
+
const crossAgentSkills = copyStandardSkills
|
|
358
|
+
? skillDirs
|
|
359
|
+
: skillDirs.filter(d => isKernelSkill(d));
|
|
360
|
+
if (crossAgentSkills.length > 0) {
|
|
361
|
+
const crossAgentDir = path.join(targetDir, '.agents', 'skills');
|
|
362
|
+
ensureDir(crossAgentDir, opts.dryRun);
|
|
363
|
+
if (!opts.dryRun) {
|
|
364
|
+
console.log(`Deploying cross-agent skills to ${path.relative(process.cwd(), crossAgentDir)}...`);
|
|
365
|
+
} else {
|
|
366
|
+
console.log(`[dry-run] Would deploy cross-agent skills to .agents/skills/`);
|
|
367
|
+
}
|
|
368
|
+
for (const skillDir of crossAgentSkills) {
|
|
369
|
+
deploySkillDir(skillDir, crossAgentDir, opts);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Deploy rules to .opencode/rule/
|
|
376
|
+
*/
|
|
377
|
+
export function deployRules(ruleFiles, targetDir, opts) {
|
|
378
|
+
const destDir = path.join(targetDir, paths.rules);
|
|
379
|
+
ensureDir(destDir, opts.dryRun);
|
|
380
|
+
cleanupOldRuleFiles(destDir, opts);
|
|
381
|
+
return deployFiles(ruleFiles, destDir, opts, transformAgent);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Wire deployed rules into opencode.json `instructions[]` (#1548).
|
|
386
|
+
*
|
|
387
|
+
* OpenCode loads rule/instruction content via the `instructions` array in
|
|
388
|
+
* `opencode.json` — files under `.opencode/rule/` are written to disk but never
|
|
389
|
+
* loaded into the agent's system prompt unless referenced there. This merges a
|
|
390
|
+
* glob for the rule directory plus `AGENTS.md` into `instructions[]` WITHOUT
|
|
391
|
+
* clobbering operator-authored config: existing keys and existing instructions
|
|
392
|
+
* entries are preserved and de-duplicated.
|
|
393
|
+
*
|
|
394
|
+
* Non-destructive: a malformed existing opencode.json is left untouched (warn,
|
|
395
|
+
* skip) rather than overwritten.
|
|
396
|
+
*
|
|
397
|
+
* @returns {boolean} true if the config was changed (or would be, in dry-run)
|
|
398
|
+
*/
|
|
399
|
+
export function mergeOpenCodeInstructions(targetDir, opts = {}) {
|
|
400
|
+
const RULE_GLOB = `${paths.rules}*.md`; // .opencode/rule/*.md — opencode supports globs in instructions (verified in sst/opencode session/instruction.ts)
|
|
401
|
+
// opencode auto-loads AGENTS.md/CLAUDE.md/CONTEXT.md but NOT AIWG.md, and the
|
|
402
|
+
// generated AGENTS.md is a thin link-index that points to AIWG.md. The
|
|
403
|
+
// strengthened discover-first / directive-classification guidance lives in
|
|
404
|
+
// AIWG.md, so reference it explicitly here so opencode loads it directly
|
|
405
|
+
// rather than relying on the agent to follow AGENTS.md's link (#1542).
|
|
406
|
+
const AGENTS = 'AGENTS.md';
|
|
407
|
+
const AIWG = 'AIWG.md';
|
|
408
|
+
const wanted = [RULE_GLOB, AGENTS, AIWG];
|
|
409
|
+
const configPath = path.join(targetDir, 'opencode.json');
|
|
410
|
+
|
|
411
|
+
let config = {};
|
|
412
|
+
if (fs.existsSync(configPath)) {
|
|
413
|
+
try {
|
|
414
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
415
|
+
if (config === null || typeof config !== 'object' || Array.isArray(config)) {
|
|
416
|
+
console.warn(`Warning: ${configPath} is not a JSON object — leaving it untouched (rules not wired).`);
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
} catch (err) {
|
|
420
|
+
console.warn(`Warning: ${configPath} is not valid JSON (${err && err.message ? err.message : err}) — leaving it untouched (rules not wired).`);
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const existing = Array.isArray(config.instructions) ? config.instructions : [];
|
|
426
|
+
const missing = wanted.filter((w) => !existing.includes(w));
|
|
427
|
+
if (missing.length === 0) return false; // already wired — nothing to do
|
|
428
|
+
|
|
429
|
+
if (opts.dryRun) {
|
|
430
|
+
console.log(`[dry-run] Would add to opencode.json instructions[]: ${missing.join(', ')}`);
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Preserve operator-authored entries; append only what's missing, deduped.
|
|
435
|
+
config.instructions = [...existing, ...missing];
|
|
436
|
+
fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
|
437
|
+
if (opts.verbose) {
|
|
438
|
+
console.log(`Wired ${missing.length} entr${missing.length === 1 ? 'y' : 'ies'} into opencode.json instructions[]: ${missing.join(', ')}`);
|
|
439
|
+
}
|
|
440
|
+
return true;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// ============================================================================
|
|
444
|
+
// AGENTS.md
|
|
445
|
+
// ============================================================================
|
|
446
|
+
|
|
447
|
+
export function createAgentsMd(target, srcRoot, dryRun) {
|
|
448
|
+
createAgentsMdFromTemplate(target, srcRoot, 'opencode/AGENTS.md.aiwg-template', dryRun);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// ============================================================================
|
|
452
|
+
// Post-Deployment
|
|
453
|
+
// ============================================================================
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Emit selected SDLC primary-role agents as OpenCode TUI modes
|
|
457
|
+
* (PUW-035 / #1136). Uses already-deployed agent files at
|
|
458
|
+
* `.opencode/agent/<role>.md` as the source — copies them to
|
|
459
|
+
* `.opencode/mode/<role>.md` for the TUI mode picker.
|
|
460
|
+
*
|
|
461
|
+
* Per ADR-1 §0.6 always-deploy invariant: agents/ files keep deploying
|
|
462
|
+
* unchanged; the mode/ files are an additive layer.
|
|
463
|
+
*/
|
|
464
|
+
export function deployModes(targetDir, opts) {
|
|
465
|
+
const agentsDir = path.join(targetDir, paths.agents);
|
|
466
|
+
const modesDir = path.join(targetDir, paths.modes);
|
|
467
|
+
|
|
468
|
+
if (!fs.existsSync(agentsDir)) {
|
|
469
|
+
return 0;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
ensureDir(modesDir, opts.dryRun);
|
|
473
|
+
let count = 0;
|
|
474
|
+
|
|
475
|
+
for (const roleId of SDLC_PRIMARY_ROLES) {
|
|
476
|
+
const agentSrc = path.join(agentsDir, `${roleId}.md`);
|
|
477
|
+
const modeDest = path.join(modesDir, `${roleId}.md`);
|
|
478
|
+
|
|
479
|
+
if (!fs.existsSync(agentSrc)) {
|
|
480
|
+
// Role's agent file isn't deployed (e.g., pruned framework or
|
|
481
|
+
// stale-list entry). Skip silently; not an error.
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (opts.dryRun) {
|
|
486
|
+
console.log(`[dry-run] Would emit OpenCode mode: ${roleId}`);
|
|
487
|
+
count++;
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
fs.copyFileSync(agentSrc, modeDest);
|
|
492
|
+
count++;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (opts.verbose && count > 0) {
|
|
496
|
+
console.log(`Deployed ${count} OpenCode primary modes to ${modesDir}`);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return count;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
export async function postDeploy(targetDir, opts) {
|
|
503
|
+
initializeFrameworkWorkspace(targetDir, opts.mode, opts.dryRun, opts.srcRoot);
|
|
504
|
+
|
|
505
|
+
if (opts.createAgentsMd) {
|
|
506
|
+
createAgentsMd(targetDir, opts.srcRoot, opts.dryRun);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// PUW-035 (#1136): emit SDLC primary roles as TUI modes.
|
|
510
|
+
try {
|
|
511
|
+
deployModes(targetDir, opts || {});
|
|
512
|
+
} catch (err) {
|
|
513
|
+
console.warn(`Warning: OpenCode mode deploy failed: ${err && err.message ? err.message : err}`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// ============================================================================
|
|
518
|
+
// File Extension
|
|
519
|
+
// ============================================================================
|
|
520
|
+
|
|
521
|
+
export function getFileExtension(type) {
|
|
522
|
+
return '.md';
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// ============================================================================
|
|
526
|
+
// Main Deploy Function
|
|
527
|
+
// ============================================================================
|
|
528
|
+
|
|
529
|
+
export async function deploy(opts) {
|
|
530
|
+
const {
|
|
531
|
+
srcRoot,
|
|
532
|
+
target,
|
|
533
|
+
mode,
|
|
534
|
+
deployCommands: shouldDeployCommands,
|
|
535
|
+
deploySkills: shouldDeploySkills,
|
|
536
|
+
deployRules: shouldDeployRules,
|
|
537
|
+
commandsOnly,
|
|
538
|
+
skillsOnly,
|
|
539
|
+
rulesOnly,
|
|
540
|
+
dryRun,
|
|
541
|
+
createAgentsMd: shouldCreateAgentsMd
|
|
542
|
+
} = opts;
|
|
543
|
+
|
|
544
|
+
console.log(`\n=== OpenCode Provider ===`);
|
|
545
|
+
console.log(`Target: ${target}`);
|
|
546
|
+
console.log(`Mode: ${mode}`);
|
|
547
|
+
|
|
548
|
+
const agentFiles = [];
|
|
549
|
+
const commandFiles = [];
|
|
550
|
+
const skillDirs = [];
|
|
551
|
+
const ruleFiles = [];
|
|
552
|
+
const normalizedMode = normalizeDeploymentMode(mode);
|
|
553
|
+
|
|
554
|
+
// All addons (dynamically discovered)
|
|
555
|
+
if (normalizedMode === 'general' || normalizedMode === 'sdlc' || normalizedMode === 'both' || normalizedMode === 'all') {
|
|
556
|
+
agentFiles.push(...getAddonAgentFiles(srcRoot));
|
|
557
|
+
|
|
558
|
+
if (shouldDeployCommands || commandsOnly) {
|
|
559
|
+
commandFiles.push(...getAddonCommandFiles(srcRoot));
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if (shouldDeploySkills || skillsOnly) {
|
|
563
|
+
skillDirs.push(...getAddonSkillDirs(srcRoot));
|
|
564
|
+
|
|
565
|
+
// Holistic post-deploy cleanup of stale AIWG-managed kernel
|
|
566
|
+
// skills (renamed/removed sources). Uses the global kernel set
|
|
567
|
+
// (computeAllKernelNames walks all source frameworks/addons),
|
|
568
|
+
// not just this-call's skillDirs, because aiwg use invokes
|
|
569
|
+
// deploy-agents.mjs multiple times.
|
|
570
|
+
{
|
|
571
|
+
const _kernelDestDir = path.isAbsolute(kernelSkillsPath)
|
|
572
|
+
? kernelSkillsPath
|
|
573
|
+
: path.join(target, kernelSkillsPath);
|
|
574
|
+
pruneStaleAiwgSkills(_kernelDestDir, computeAllKernelNames(srcRoot), opts);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (shouldDeployRules || rulesOnly) {
|
|
579
|
+
ruleFiles.push(...getAddonRuleFiles(srcRoot));
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const frameworkArtifacts = collectFrameworkArtifacts(srcRoot, normalizedMode, {
|
|
584
|
+
includeAgents: true,
|
|
585
|
+
includeCommands: shouldDeployCommands || commandsOnly,
|
|
586
|
+
includeSkills: shouldDeploySkills || skillsOnly,
|
|
587
|
+
includeRules: shouldDeployRules || rulesOnly,
|
|
588
|
+
recursiveCommands: true,
|
|
589
|
+
consolidatedSdlcRules: true
|
|
590
|
+
});
|
|
591
|
+
agentFiles.push(...frameworkArtifacts.agents);
|
|
592
|
+
const soulFiles = [...(frameworkArtifacts.souls || [])];
|
|
593
|
+
commandFiles.push(...frameworkArtifacts.commands);
|
|
594
|
+
skillDirs.push(...frameworkArtifacts.skills);
|
|
595
|
+
ruleFiles.push(...frameworkArtifacts.rules);
|
|
596
|
+
|
|
597
|
+
// Deploy agents to .opencode/agent/
|
|
598
|
+
if (!commandsOnly && !skillsOnly && !rulesOnly) {
|
|
599
|
+
console.log(`\nDeploying ${agentFiles.length} agents to .opencode/agent/...`);
|
|
600
|
+
deployAgents(agentFiles, target, opts);
|
|
601
|
+
// Deploy soul companion files alongside agents
|
|
602
|
+
if (soulFiles.length > 0) {
|
|
603
|
+
deploySoulCompanions(soulFiles, path.join(target, paths.agents), opts);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// Filter commands that collide with skills (skills take precedence)
|
|
608
|
+
const filteredCommands = (shouldDeploySkills || skillsOnly)
|
|
609
|
+
? filterCommandsAgainstSkills(commandFiles, skillDirs)
|
|
610
|
+
: commandFiles;
|
|
611
|
+
|
|
612
|
+
if (shouldDeployCommands || commandsOnly) {
|
|
613
|
+
console.log(`\nDeploying ${filteredCommands.length} commands...`);
|
|
614
|
+
deployCommands(filteredCommands, target, opts);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (shouldDeploySkills || skillsOnly) {
|
|
618
|
+
console.log(`\nDeploying ${skillDirs.length} skills...`);
|
|
619
|
+
deploySkills(skillDirs, target, opts);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
if (shouldDeployRules || rulesOnly) {
|
|
623
|
+
console.log(`\nDeploying ${ruleFiles.length} rules...`);
|
|
624
|
+
deployRules(ruleFiles, target, opts);
|
|
625
|
+
|
|
626
|
+
// On-demand index (#1675): list the MEDIUM/LOW rules tier-gated out of the
|
|
627
|
+
// always-on set so agents can fetch them via `aiwg show rule`. Written into
|
|
628
|
+
// the rule dir before instruction wiring so the `*.md` glob picks it up.
|
|
629
|
+
const onDemandCount = writeOnDemandRuleIndex(
|
|
630
|
+
path.join(target, paths.rules),
|
|
631
|
+
listOnDemandRuleFiles(srcRoot),
|
|
632
|
+
opts,
|
|
633
|
+
);
|
|
634
|
+
if (onDemandCount > 0) {
|
|
635
|
+
console.log(` On-demand rules (not inlined): ${onDemandCount} → RULES-ONDEMAND.md`);
|
|
636
|
+
}
|
|
637
|
+
// #1548: rules on disk are inert unless referenced from opencode.json
|
|
638
|
+
// instructions[]. Wire them so opencode actually loads them.
|
|
639
|
+
try {
|
|
640
|
+
mergeOpenCodeInstructions(target, opts);
|
|
641
|
+
} catch (err) {
|
|
642
|
+
console.warn(`Warning: could not wire opencode.json instructions[]: ${err && err.message ? err.message : err}`);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
await postDeploy(target, { ...opts, createAgentsMd: shouldCreateAgentsMd });
|
|
647
|
+
|
|
648
|
+
console.log('\n=== OpenCode deployment complete ===\n');
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// ============================================================================
|
|
652
|
+
// Default Export
|
|
653
|
+
// ============================================================================
|
|
654
|
+
|
|
655
|
+
export default {
|
|
656
|
+
name,
|
|
657
|
+
aliases,
|
|
658
|
+
paths,
|
|
659
|
+
kernelSkillsPath,
|
|
660
|
+
support,
|
|
661
|
+
capabilities,
|
|
662
|
+
transformAgent,
|
|
663
|
+
transformCommand,
|
|
664
|
+
mapModel,
|
|
665
|
+
getAgentConfig,
|
|
666
|
+
deployAgents,
|
|
667
|
+
deployCommands,
|
|
668
|
+
deploySkills,
|
|
669
|
+
deployRules,
|
|
670
|
+
mergeOpenCodeInstructions,
|
|
671
|
+
createAgentsMd,
|
|
672
|
+
postDeploy,
|
|
673
|
+
getFileExtension,
|
|
674
|
+
deploy
|
|
675
|
+
};
|