@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.
Files changed (70) hide show
  1. package/README.md +33 -0
  2. package/agentic/code/providers/capability-matrix.yaml +511 -0
  3. package/agentic/code/providers/model-capabilities.v1.json +120 -0
  4. package/agentic/code/providers/model-catalog.v1.json +96 -0
  5. package/agentic/code/providers/model-policy-evaluations.v1.json +50 -0
  6. package/agentic/code/providers/premium-model-allowlist.v1.json +36 -0
  7. package/bin/aiwg.mjs +14 -10
  8. package/dist/src/api/index.d.ts +1 -0
  9. package/dist/src/api/index.js +1 -0
  10. package/dist/src/artifacts/cli.js +2 -0
  11. package/dist/src/artifacts/types.js +4 -0
  12. package/dist/src/auth/client.js +209 -0
  13. package/dist/src/auth/config.js +38 -0
  14. package/dist/src/auth/credential-store.js +141 -0
  15. package/dist/src/auth/resource-credentials.js +25 -0
  16. package/dist/src/auth/types.js +2 -0
  17. package/dist/src/channel/manager.mjs +5 -5
  18. package/dist/src/cli/handlers/auth.js +125 -0
  19. package/dist/src/cli/handlers/help.js +1 -0
  20. package/dist/src/cli/handlers/index.js +3 -1
  21. package/dist/src/cli/handlers/install.js +42 -4
  22. package/dist/src/cli/handlers/marketplace.js +375 -122
  23. package/dist/src/cli/handlers/resource-versions.js +2 -0
  24. package/dist/src/cli/handlers/sessions.js +23 -5
  25. package/dist/src/cli/handlers/subcommands.js +10 -1
  26. package/dist/src/cli/handlers/use.js +342 -43
  27. package/dist/src/config/gitignore.js +1 -0
  28. package/dist/src/extensions/commands/definitions.js +19 -0
  29. package/dist/src/marketplace/exchange.js +602 -0
  30. package/dist/src/marketplace/provenance-types.js +19 -0
  31. package/dist/src/marketplace/provenance.js +834 -0
  32. package/dist/src/memory/canonical-context.js +342 -0
  33. package/dist/src/memory/context-pack.js +282 -0
  34. package/dist/src/memory/index.js +4 -0
  35. package/dist/src/memory/intake.js +118 -0
  36. package/dist/src/packages/adapters/git.js +79 -29
  37. package/dist/src/packages/package-discovery.js +81 -0
  38. package/dist/src/packages/package-registry.js +2 -0
  39. package/dist/src/packages/registry.js +119 -20
  40. package/dist/src/resources/resolver.js +1 -0
  41. package/dist/src/resources/web-release.d.ts +3 -1
  42. package/dist/src/resources/web-release.js +14 -6
  43. package/dist/src/serve/agentic-sandbox-fleet-client.js +213 -0
  44. package/dist/src/serve/fleet-mission-conductor.js +293 -0
  45. package/dist/src/sessions/index.js +1 -0
  46. package/dist/src/sessions/output-registration.js +338 -0
  47. package/dist/src/sessions/promotion.js +73 -2
  48. package/dist/src/sessions/repository.js +2 -1
  49. package/dist/src/update/notifier.mjs +13 -2
  50. package/package.json +8 -1
  51. package/tools/_resolve-impl.mjs +74 -0
  52. package/tools/agents/deploy-agents.mjs +962 -0
  53. package/tools/agents/providers/base.mjs +2954 -0
  54. package/tools/agents/providers/claude.mjs +711 -0
  55. package/tools/agents/providers/codex.mjs +699 -0
  56. package/tools/agents/providers/copilot.mjs +659 -0
  57. package/tools/agents/providers/cursor.mjs +714 -0
  58. package/tools/agents/providers/factory.mjs +1130 -0
  59. package/tools/agents/providers/hermes.mjs +663 -0
  60. package/tools/agents/providers/hook-capabilities.mjs +85 -0
  61. package/tools/agents/providers/model-role.mjs +56 -0
  62. package/tools/agents/providers/openclaw-translator.mjs +348 -0
  63. package/tools/agents/providers/openclaw.mjs +680 -0
  64. package/tools/agents/providers/opencode.mjs +675 -0
  65. package/tools/agents/providers/openhuman.mjs +292 -0
  66. package/tools/agents/providers/warp.mjs +413 -0
  67. package/tools/agents/providers/windsurf.mjs +748 -0
  68. package/tools/commands/deploy-prompts-codex.mjs +336 -0
  69. package/tools/plugin/package-plugins.mjs +1013 -0
  70. 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
+ };