@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,711 @@
1
+ /**
2
+ * Claude Code Provider
3
+ *
4
+ * The default/primary provider for AIWG. Claude Code is the most feature-rich
5
+ * provider with full support for agents, commands, skills, and rules.
6
+ *
7
+ * Deployment paths:
8
+ * Skills are sequestered under `.claude/.aiwg/skills/` so the
9
+ * platform's flat-namespace skill-listing budget doesn't truncate
10
+ * them. Discovery is index-driven (epic #1212). The kernel of
11
+ * always-loaded skills deploys to `.claude/skills/` so Claude Code's
12
+ * native loader picks them up. Agents, commands, and rules continue
13
+ * to deploy to their platform-native paths.
14
+ *
15
+ * - Agents: .claude/agents/ (platform-native)
16
+ * - Commands: .claude/commands/ (platform-native)
17
+ * - AIWG skills: .claude/.aiwg/skills/ (index-driven discovery)
18
+ * - Kernel skills: .claude/skills/ (platform-native, always-loaded)
19
+ * - Rules: .claude/rules/ (platform-native)
20
+ * - Hooks/settings: .claude/ (platform-native)
21
+ */
22
+
23
+ import realFs from 'fs';
24
+ import { createRequire } from 'module';
25
+ const _require = createRequire(import.meta.url);
26
+ let fs;
27
+ try { const gfs = _require('graceful-fs'); gfs.gracefulify(realFs); fs = realFs; } catch { fs = realFs; }
28
+ import path from 'path';
29
+ import { tmpdir } from 'os';
30
+ import { classifyModelRole, modelForRole } from './model-role.mjs';
31
+ import {
32
+ ensureDir,
33
+ listMdFiles,
34
+ listMdFilesRecursive,
35
+ listSkillDirs,
36
+ writeFile,
37
+ deployFiles,
38
+ deploySkillDir,
39
+ deploySkillsWithKernelRouting,
40
+ isKernelSkill,
41
+ pruneStaleAiwgSkills,
42
+ computeAllKernelNames,
43
+ parseFrontmatter,
44
+ initializeFrameworkWorkspace,
45
+ filterAgentFiles,
46
+ getAddonAgentFiles,
47
+ getAddonCommandFiles,
48
+ getAddonSkillDirs,
49
+ getAddonRuleFiles,
50
+ listOnDemandRuleFiles,
51
+ writeOnDemandRuleIndex,
52
+ assembleRulesIndex,
53
+ normalizeDeploymentMode,
54
+ collectFrameworkArtifacts,
55
+ cleanupOldRuleFiles,
56
+ filterCommandsAgainstSkills,
57
+ deploySoulCompanions,
58
+ buildRemotesTopologyBlock,
59
+ interpolateContextTokens
60
+ } from './base.mjs';
61
+
62
+ // ============================================================================
63
+ // Provider Configuration
64
+ // ============================================================================
65
+
66
+ export const name = 'claude';
67
+ export const aliases = [];
68
+
69
+ export const paths = {
70
+ agents: '.claude/agents/',
71
+ commands: '.claude/commands/',
72
+ // Skills are hidden under `.claude/.aiwg/skills/` so the platform's
73
+ // flat-namespace skill-listing budget doesn't truncate them.
74
+ // Discovery is index-driven (#1212). The kernel set deploys to
75
+ // `kernelSkills` separately so Claude Code natively loads it.
76
+ skills: '.claude/.aiwg/skills/',
77
+ rules: '.claude/rules/',
78
+ };
79
+
80
+ // Kernel skills path: always-loaded set the platform sees natively.
81
+ // The rest of AIWG's skills sit at `paths.skills` and are reached
82
+ // through the artifact index (epic #1212).
83
+ export const kernelSkillsPath = '.claude/skills/';
84
+
85
+ export const support = {
86
+ agents: 'native',
87
+ commands: 'native',
88
+ skills: 'native',
89
+ rules: 'native'
90
+ };
91
+
92
+ export const capabilities = {
93
+ skills: true,
94
+ rules: true,
95
+ aggregatedOutput: false,
96
+ yamlFormat: false,
97
+ mdcFormat: false,
98
+ homeDirectoryDeploy: false,
99
+ projectLocalMirror: false
100
+ };
101
+
102
+ // ============================================================================
103
+ // Model Handling
104
+ // ============================================================================
105
+
106
+ /**
107
+ * Replace model in frontmatter based on role classification
108
+ * opus -> reasoning, sonnet -> coding, haiku -> efficiency
109
+ */
110
+ export function replaceModelFrontmatter(content, models) {
111
+ const fmStart = content.indexOf('---');
112
+ if (fmStart !== 0) return content;
113
+ const fmEnd = content.indexOf('\n---', 3);
114
+ if (fmEnd === -1) return content;
115
+
116
+ const header = content.slice(0, fmEnd + 4);
117
+ const body = content.slice(fmEnd + 4);
118
+
119
+ const modelMatch = header.match(/^model:\s*([^\n]+)$/m);
120
+ let newModel = null;
121
+
122
+ if (modelMatch) {
123
+ const orig = modelMatch[1].trim();
124
+ const role = classifyModelRole(orig);
125
+
126
+ if (role === 'reasoning') newModel = models.reasoning;
127
+ else if (role === 'efficiency') newModel = models.efficiency;
128
+ else if (role === 'coding') newModel = models.coding;
129
+ }
130
+
131
+ if (!newModel) return content;
132
+ const updatedHeader = header.replace(/^model:\s*[^\n]+$/m, `model: ${newModel}`);
133
+ return updatedHeader + body;
134
+ }
135
+
136
+ /**
137
+ * Map model shorthand to Claude format
138
+ * For Claude, we keep opus/sonnet/haiku unless overridden
139
+ */
140
+ export function mapModel(shorthand, modelCfg, modelsConfig) {
141
+ // If overrides specified, use them
142
+ if (modelCfg.reasoningModel || modelCfg.codingModel || modelCfg.efficiencyModel) {
143
+ return modelForRole(shorthand, {
144
+ reasoning: modelCfg.reasoningModel || 'opus',
145
+ coding: modelCfg.codingModel || 'sonnet',
146
+ efficiency: modelCfg.efficiencyModel || 'haiku',
147
+ }, { defaultRole: 'coding' }) ?? shorthand;
148
+ }
149
+
150
+ // No transformation needed for Claude - keep shorthand
151
+ return shorthand || 'sonnet';
152
+ }
153
+
154
+ // ============================================================================
155
+ // Content Transformation
156
+ // ============================================================================
157
+
158
+ /**
159
+ * Transform agent content for Claude
160
+ * Claude is the native format - minimal transformation needed
161
+ */
162
+ export function transformAgent(srcPath, content, opts) {
163
+ const { reasoningModel, codingModel, efficiencyModel } = opts;
164
+
165
+ // Only transform if model overrides specified
166
+ if (reasoningModel || codingModel || efficiencyModel) {
167
+ const models = {
168
+ reasoning: reasoningModel || 'opus',
169
+ coding: codingModel || 'sonnet',
170
+ efficiency: efficiencyModel || 'haiku'
171
+ };
172
+ return replaceModelFrontmatter(content, models);
173
+ }
174
+
175
+ return content;
176
+ }
177
+
178
+ /**
179
+ * Transform command content for Claude
180
+ * Commands use same format as agents - minimal transformation
181
+ */
182
+ export function transformCommand(srcPath, content, opts) {
183
+ return transformAgent(srcPath, content, opts);
184
+ }
185
+
186
+ // ============================================================================
187
+ // Legacy Skill Cleanup
188
+ // ============================================================================
189
+
190
+ /**
191
+ * Claude built-in command names that must never be used as bare skill slugs.
192
+ * Mirrors the list in src/smiths/skillsmith/collision-detector.ts.
193
+ */
194
+ const CLAUDE_BUILTINS = new Set([
195
+ 'help', 'clear', 'compact', 'review', 'init', 'doctor',
196
+ 'memory', 'settings', 'logout', 'login', 'mcp', 'migrate',
197
+ ]);
198
+
199
+ /**
200
+ * After deploying skills, remove stale bare-named skills that collide with
201
+ * Claude built-ins, provided all three conditions hold:
202
+ * 1. The skill directory is owned by the aiwg namespace (namespace: aiwg in SKILL.md)
203
+ * 2. The skill name is in the CLAUDE_BUILTINS set
204
+ * 3. A namespaced replacement (aiwg-{name}) already exists in the same directory
205
+ *
206
+ * This implements the "+1 release" auto-cleanup milestone from the skill namespace
207
+ * migration guide (docs/migration/skill-namespace-migration.md).
208
+ */
209
+ function cleanupLegacyBuiltinCollisions(destDir, opts) {
210
+ if (opts.dryRun || !fs.existsSync(destDir)) return;
211
+
212
+ let entries;
213
+ try { entries = fs.readdirSync(destDir, { withFileTypes: true }); } catch { return; }
214
+
215
+ for (const entry of entries) {
216
+ if (!entry.isDirectory()) continue;
217
+ const name = entry.name;
218
+ if (!CLAUDE_BUILTINS.has(name)) continue;
219
+
220
+ const skillPath = path.join(destDir, name);
221
+ const namespacedPath = path.join(destDir, `aiwg-${name}`);
222
+ const skillMd = path.join(skillPath, 'SKILL.md');
223
+
224
+ // Only remove when the namespaced replacement has already been deployed
225
+ if (!fs.existsSync(skillMd) || !fs.existsSync(namespacedPath)) continue;
226
+
227
+ let content = '';
228
+ try { content = fs.readFileSync(skillMd, 'utf8'); } catch { continue; }
229
+ // Guard: only remove if the skill is owned by the aiwg namespace
230
+ if (!/^namespace:\s*aiwg\s*$/m.test(content)) continue;
231
+
232
+ try {
233
+ fs.rmSync(skillPath, { recursive: true, force: true });
234
+ if (opts.verbose) console.log(`removed legacy skill: ${name} (superseded by aiwg-${name})`);
235
+ } catch { /* non-fatal */ }
236
+ }
237
+ }
238
+
239
+ // ============================================================================
240
+ // Deployment Functions
241
+ // ============================================================================
242
+
243
+ /**
244
+ * Deploy agents to .claude/agents/
245
+ */
246
+ export function deployAgents(agentFiles, targetDir, opts) {
247
+ const destDir = path.join(targetDir, paths.agents);
248
+ ensureDir(destDir, opts.dryRun);
249
+ return deployFiles(agentFiles, destDir, { ...opts, injectPlatform: true }, transformAgent);
250
+ }
251
+
252
+ /**
253
+ * Deploy commands to .claude/commands/
254
+ */
255
+ export function deployCommands(commandFiles, targetDir, opts) {
256
+ const destDir = path.join(targetDir, paths.commands);
257
+ ensureDir(destDir, opts.dryRun);
258
+ return deployFiles(commandFiles, destDir, opts, transformCommand);
259
+ }
260
+
261
+ /**
262
+ * Deploy skills.
263
+ *
264
+ * Skills are directories containing SKILL.md and supporting files. Two
265
+ * deploy targets per epic #1212:
266
+ *
267
+ * - **Kernel skills** (frontmatter `kernel: true`) → `.claude/skills/`
268
+ * (platform-native, always-loaded). These are the always-on
269
+ * quickref / utility skills that frame the agent's interaction with
270
+ * the rest of AIWG. Kept small (~10-15 entries) to fit within the
271
+ * platform's flat-namespace skill-listing budget.
272
+ *
273
+ * - **Standard skills** → `.claude/.aiwg/skills/`. The bulk of AIWG's
274
+ * skills, hidden from the platform's flat listing and discoverable
275
+ * via the artifact index.
276
+ */
277
+ export function deploySkills(skillDirs, targetDir, opts) {
278
+ const standardDestDir = path.join(targetDir, paths.skills);
279
+ const kernelDestDir = path.join(targetDir, kernelSkillsPath);
280
+ // copyStandardSkills resolution: opts.copyStandardSkills (set by
281
+ // `--copy-all` CLI flag, #1219). Default (#1217) is no-copy +
282
+ // index-driven discovery. The deploySkillsWithKernelRouting helper
283
+ // does the actual partition + cleanup.
284
+ // does the same priority resolution centrally.
285
+ deploySkillsWithKernelRouting(skillDirs, standardDestDir, kernelDestDir, {
286
+ ...opts,
287
+ transformSkillMd: transformSkillModelPolicy,
288
+ });
289
+ // Remove legacy bare-named skills superseded by their aiwg- prefixed replacements
290
+ cleanupLegacyBuiltinCollisions(standardDestDir, opts);
291
+ }
292
+
293
+ function resolveSkillModelPolicy(commandHint = {}) {
294
+ const legacy = String(commandHint.model || '').trim().toLowerCase();
295
+ const legacyRole = legacy === 'opus' ? 'reasoning'
296
+ : legacy === 'haiku' ? 'efficiency'
297
+ : legacy ? 'coding' : null;
298
+ const role = commandHint.modelRole || legacyRole;
299
+ if (!role) return null;
300
+ return {
301
+ model: role === 'reasoning' ? 'opus' : role === 'efficiency' ? 'haiku' : 'sonnet',
302
+ effort: commandHint.modelEffort,
303
+ };
304
+ }
305
+
306
+ /** Compile portable commandHint policy to Claude's native skill fields. */
307
+ export function transformSkillModelPolicy(content) {
308
+ const { frontmatter: rawFrontmatter } = parseFrontmatter(content);
309
+ if (!rawFrontmatter) return content;
310
+ const hintBlock = rawFrontmatter.match(/^commandHint:\s*\n((?:[ \t]+[^\n]*\n?)*)/m)?.[1] || '';
311
+ const commandHint = {};
312
+ for (const line of hintBlock.split('\n')) {
313
+ const match = line.trim().match(/^(model|modelRole|modelTier|modelEffort):\s*(.+)$/);
314
+ if (match) commandHint[match[1]] = match[2].trim().replace(/^['"]|['"]$/g, '');
315
+ }
316
+ const policy = resolveSkillModelPolicy(commandHint);
317
+ if (!policy) return content;
318
+ const match = content.match(/^([\s\S]*?---\n)([\s\S]*?)(\n---\n[\s\S]*)$/);
319
+ if (!match) return content;
320
+ let frontmatter = match[2]
321
+ .replace(/^model:\s*.*\n?/m, '')
322
+ .replace(/^effort:\s*.*\n?/m, '');
323
+ frontmatter += `\nmodel: ${policy.model}`;
324
+ if (policy.effort) {
325
+ const effort = policy.effort === 'medium' ? 2
326
+ : policy.effort === 'high' || policy.effort === 'xhigh' ? 3 : 1;
327
+ frontmatter += `\neffort: ${effort}`;
328
+ }
329
+ return `${match[1]}${frontmatter}${match[3]}`;
330
+ }
331
+
332
+ /**
333
+ * Deploy rules to .claude/rules/
334
+ * Deploys consolidated RULES-INDEX.md instead of individual rule files.
335
+ * Cleans up old individual rule files from previous deployments.
336
+ */
337
+ export function deployRules(ruleFiles, targetDir, opts) {
338
+ const destDir = path.join(targetDir, paths.rules);
339
+ ensureDir(destDir, opts.dryRun);
340
+ // Pass incomingFiles so addon deploys with 0 rules don't wipe the main
341
+ // framework's rules (#1143 mitigation; also fixes #1117 PUW-016).
342
+ cleanupOldRuleFiles(destDir, { ...opts, incomingFiles: ruleFiles });
343
+ return deployFiles(ruleFiles, destDir, opts, transformCommand);
344
+ }
345
+
346
+ // ============================================================================
347
+ // AGENTS.md (Not typically used for Claude, but supported)
348
+ // ============================================================================
349
+
350
+ export function createAgentsMd(target, srcRoot, dryRun) {
351
+ // Claude Code doesn't typically use AGENTS.md since it has native agent support
352
+ // But we can create one for documentation purposes if needed
353
+ console.log('Claude Code uses native .claude/agents/ - AGENTS.md not required');
354
+ }
355
+
356
+ // ============================================================================
357
+ // Post-Deployment
358
+ // ============================================================================
359
+
360
+ export async function postDeploy(targetDir, opts) {
361
+ // Initialize framework workspace structure
362
+ initializeFrameworkWorkspace(targetDir, opts.mode, opts.dryRun, opts.srcRoot);
363
+
364
+ // Claude-specific post-deployment (settings.json, etc.)
365
+ const claudeDir = path.join(targetDir, '.claude');
366
+ const settingsPath = path.join(claudeDir, 'settings.json');
367
+
368
+ // If settings.json doesn't exist, create a minimal one
369
+ if (!fs.existsSync(settingsPath) && !opts.dryRun) {
370
+ ensureDir(claudeDir);
371
+ const settings = {
372
+ version: '1.0',
373
+ created: new Date().toISOString(),
374
+ aiwg: {
375
+ enabled: true,
376
+ mode: opts.mode || 'all'
377
+ }
378
+ };
379
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
380
+ console.log('Created .claude/settings.json');
381
+ }
382
+
383
+ // Hook file architecture: write AIWG.md and wire @AIWG.md into CLAUDE.md
384
+ if (opts.srcRoot) {
385
+ deployHookFile(targetDir, opts);
386
+ }
387
+ }
388
+
389
+ /**
390
+ * Deploy the AIWG.md hook file and add @AIWG.md directive to CLAUDE.md.
391
+ * Hook file approach: AIWG content lives in AIWG.md; CLAUDE.md stays minimal
392
+ * with a single @AIWG.md directive that Claude Code loads at session start.
393
+ * Falls back gracefully if template is missing (older installs).
394
+ */
395
+ /**
396
+ * Substitute {{TOKEN}} placeholders in hook file content with deployment counts
397
+ * and the resolved remotes topology (#998).
398
+ *
399
+ * Delegates to the shared interpolateContextTokens helper in base.mjs so the
400
+ * Claude hook file and the AGENTS.md template path render the same tokens.
401
+ */
402
+ function interpolateHookTokens(content, counts, targetDir) {
403
+ return interpolateContextTokens(content, {
404
+ counts,
405
+ topology: targetDir ? buildRemotesTopologyBlock(targetDir) : '',
406
+ });
407
+ }
408
+
409
+ function deployHookFile(targetDir, opts) {
410
+ const { srcRoot, dryRun, counts } = opts;
411
+ const templatePath = path.join(srcRoot, 'agentic', 'code', 'frameworks', 'sdlc-complete', 'templates', 'project', 'AIWG.md');
412
+ const hookDest = path.join(targetDir, 'AIWG.md');
413
+ const claudeDest = path.join(targetDir, 'CLAUDE.md');
414
+ const directive = '@AIWG.md';
415
+
416
+ if (!fs.existsSync(templatePath)) return;
417
+
418
+ // Build effective counts: use current-run counts where > 0, otherwise read the
419
+ // actual deployed count from the filesystem. This prevents a partial deploy
420
+ // (e.g. agents-only, no --skills flag) from resetting skill/command counts to
421
+ // "0" in the generated AIWG.md.
422
+ const effectiveCounts = { ...counts };
423
+ function countMdFiles(dir) {
424
+ try { return fs.readdirSync(dir).filter(f => f.endsWith('.md')).length; } catch { return 0; }
425
+ }
426
+ function countDirs(dir) {
427
+ try { return fs.readdirSync(dir, { withFileTypes: true }).filter(e => e.isDirectory()).length; } catch { return 0; }
428
+ }
429
+ if (!effectiveCounts.agents) effectiveCounts.agents = countMdFiles(path.join(targetDir, '.claude', 'agents'));
430
+ if (!effectiveCounts.skills) effectiveCounts.skills = countDirs(path.join(targetDir, '.claude', 'skills'));
431
+ if (!effectiveCounts.commands) effectiveCounts.commands = countMdFiles(path.join(targetDir, '.claude', 'commands'));
432
+ if (!effectiveCounts.rules) effectiveCounts.rules = countMdFiles(path.join(targetDir, '.claude', 'rules'));
433
+
434
+ // Write AIWG.md (always overwrite — it's generated content)
435
+ if (dryRun) {
436
+ console.log('[dry-run] Would write AIWG.md from template');
437
+ } else {
438
+ let content = fs.readFileSync(templatePath, 'utf8');
439
+ content = interpolateHookTokens(content, effectiveCounts, targetDir);
440
+ fs.writeFileSync(hookDest, content, 'utf8');
441
+ console.log('Created AIWG.md (hook file)');
442
+ }
443
+
444
+ // Add @AIWG.md directive to CLAUDE.md if present but missing the directive
445
+ if (fs.existsSync(claudeDest)) {
446
+ const existing = fs.readFileSync(claudeDest, 'utf8');
447
+ if (!existing.includes(directive)) {
448
+ if (dryRun) {
449
+ console.log('[dry-run] Would add @AIWG.md directive to CLAUDE.md');
450
+ } else {
451
+ // Insert directive after the first heading or at end of first paragraph
452
+ const lines = existing.split('\n');
453
+ let insertAt = lines.length;
454
+ for (let i = 0; i < lines.length; i++) {
455
+ if (lines[i].startsWith('#')) {
456
+ // Insert after the first heading block (skip blank lines after heading)
457
+ let j = i + 1;
458
+ while (j < lines.length && lines[j].trim() === '') j++;
459
+ insertAt = j;
460
+ break;
461
+ }
462
+ }
463
+ lines.splice(insertAt, 0, '', directive, '');
464
+ fs.writeFileSync(claudeDest, lines.join('\n'), 'utf8');
465
+ console.log('Added @AIWG.md directive to CLAUDE.md');
466
+ }
467
+ }
468
+ }
469
+ }
470
+
471
+ // ============================================================================
472
+ // File Extension
473
+ // ============================================================================
474
+
475
+ export function getFileExtension(type) {
476
+ return '.md';
477
+ }
478
+
479
+ // ============================================================================
480
+ // Main Deploy Function
481
+ // ============================================================================
482
+
483
+ /**
484
+ * Main deployment function for Claude provider
485
+ * Orchestrates deployment of agents, commands, skills, and rules
486
+ */
487
+ export async function deploy(opts) {
488
+ const {
489
+ srcRoot,
490
+ target,
491
+ mode,
492
+ deployCommands: shouldDeployCommands,
493
+ deploySkills: shouldDeploySkills,
494
+ deployRules: shouldDeployRules,
495
+ commandsOnly,
496
+ skillsOnly,
497
+ rulesOnly,
498
+ dryRun
499
+ } = opts;
500
+
501
+ const verbose = opts.verbose || false;
502
+
503
+ if (verbose) {
504
+ console.log(`\n=== Claude Code Provider ===`);
505
+ console.log(`Target: ${target}`);
506
+ console.log(`Mode: ${mode}`);
507
+ }
508
+
509
+ // Collect source files based on mode
510
+ const agentFiles = [];
511
+ const commandFiles = [];
512
+ const skillDirs = [];
513
+ const ruleFiles = [];
514
+
515
+ // Check for addon-style directory structure (direct agents/, commands/, skills/ subdirs)
516
+ // This handles deployment when --source points to an addon directory
517
+ const isAddonSource = fs.existsSync(path.join(srcRoot, 'agents')) ||
518
+ fs.existsSync(path.join(srcRoot, 'commands')) ||
519
+ fs.existsSync(path.join(srcRoot, 'skills'));
520
+
521
+ if (isAddonSource) {
522
+ // Deploy from addon-style directory structure
523
+ const addonAgentsDir = path.join(srcRoot, 'agents');
524
+ if (fs.existsSync(addonAgentsDir)) {
525
+ agentFiles.push(...listMdFiles(addonAgentsDir));
526
+ }
527
+
528
+ if (shouldDeployCommands || commandsOnly) {
529
+ const addonCommandsDir = path.join(srcRoot, 'commands');
530
+ if (fs.existsSync(addonCommandsDir)) {
531
+ commandFiles.push(...listMdFiles(addonCommandsDir));
532
+ }
533
+ }
534
+
535
+ if (shouldDeploySkills || skillsOnly) {
536
+ const addonSkillsDir = path.join(srcRoot, 'skills');
537
+ if (fs.existsSync(addonSkillsDir)) {
538
+ skillDirs.push(...listSkillDirs(addonSkillsDir));
539
+ }
540
+ }
541
+
542
+ if (shouldDeployRules || rulesOnly) {
543
+ const addonRulesDir = path.join(srcRoot, 'rules');
544
+ if (fs.existsSync(addonRulesDir)) {
545
+ ruleFiles.push(...listMdFiles(addonRulesDir));
546
+ }
547
+ }
548
+ }
549
+
550
+ const normalizedMode = normalizeDeploymentMode(mode);
551
+
552
+ // All addons (dynamically discovered)
553
+ if (normalizedMode === 'general' || normalizedMode === 'sdlc' || normalizedMode === 'both' || normalizedMode === 'all') {
554
+ agentFiles.push(...getAddonAgentFiles(srcRoot));
555
+
556
+ if (shouldDeployCommands || commandsOnly) {
557
+ commandFiles.push(...getAddonCommandFiles(srcRoot));
558
+ }
559
+
560
+ if (shouldDeploySkills || skillsOnly) {
561
+ skillDirs.push(...getAddonSkillDirs(srcRoot));
562
+ }
563
+
564
+ if (shouldDeployRules || rulesOnly) {
565
+ ruleFiles.push(...getAddonRuleFiles(srcRoot));
566
+ }
567
+ }
568
+
569
+ const frameworkArtifacts = collectFrameworkArtifacts(srcRoot, normalizedMode, {
570
+ includeAgents: true,
571
+ includeCommands: shouldDeployCommands || commandsOnly,
572
+ includeSkills: shouldDeploySkills || skillsOnly,
573
+ includeRules: shouldDeployRules || rulesOnly,
574
+ recursiveCommands: true,
575
+ consolidatedSdlcRules: true
576
+ });
577
+ agentFiles.push(...frameworkArtifacts.agents);
578
+ const soulFiles = [...(frameworkArtifacts.souls || [])];
579
+ commandFiles.push(...frameworkArtifacts.commands);
580
+ skillDirs.push(...frameworkArtifacts.skills);
581
+ ruleFiles.push(...frameworkArtifacts.rules);
582
+
583
+ // Deploy based on flags — track counts for summary
584
+ const counts = { agents: 0, commands: 0, skills: 0, rules: 0, souls: 0 };
585
+
586
+ if (!commandsOnly && !skillsOnly) {
587
+ // Apply filters if specified
588
+ const filteredAgents = filterAgentFiles(agentFiles, opts);
589
+ if (verbose && (opts.filter || opts.filterRole)) {
590
+ console.log(`\nFiltered from ${agentFiles.length} to ${filteredAgents.length} agents`);
591
+ }
592
+ if (verbose) console.log(`\nDeploying ${filteredAgents.length} agents...`);
593
+ deployAgents(filteredAgents, target, opts);
594
+ counts.agents = filteredAgents.length;
595
+
596
+ // Deploy soul companion files alongside agents
597
+ if (soulFiles.length > 0) {
598
+ const destDir = path.join(target, paths.agents);
599
+ if (verbose) console.log(`\nDeploying ${soulFiles.length} soul files...`);
600
+ deploySoulCompanions(soulFiles, destDir, opts);
601
+ counts.souls = soulFiles.length;
602
+ }
603
+ }
604
+
605
+ // Filter commands that collide with skills (skills take precedence)
606
+ const filteredCommands = (shouldDeploySkills || skillsOnly)
607
+ ? filterCommandsAgainstSkills(commandFiles, skillDirs)
608
+ : commandFiles;
609
+
610
+ if (shouldDeployCommands || commandsOnly) {
611
+ if (verbose) console.log(`\nDeploying ${filteredCommands.length} commands...`);
612
+ deployCommands(filteredCommands, target, opts);
613
+ counts.commands = filteredCommands.length;
614
+ }
615
+
616
+ if (shouldDeploySkills || skillsOnly) {
617
+ if (verbose) console.log(`\nDeploying ${skillDirs.length} skills...`);
618
+ deploySkills(skillDirs, target, opts);
619
+ counts.skills = skillDirs.length;
620
+
621
+ // Holistic cleanup of stale AIWG-managed kernel skills (renamed or
622
+ // removed sources). Builds the desired-kernel set by walking the
623
+ // ENTIRE source tree, not just this-call's skillDirs, because
624
+ // `deploy-agents.mjs` is invoked multiple times by `aiwg use`
625
+ // (once per framework + once per addon batch). Per-call cleanup
626
+ // would prune kernel skills owned by a sibling call.
627
+ const kernelDestDir = path.join(target, kernelSkillsPath);
628
+ pruneStaleAiwgSkills(kernelDestDir, computeAllKernelNames(srcRoot), opts);
629
+ }
630
+
631
+ if (shouldDeployRules || rulesOnly) {
632
+ // Try assembled rules index (combines all component indexes)
633
+ const assembled = assembleRulesIndex(srcRoot);
634
+ if (assembled) {
635
+ // Write assembled index to a unique temp dir to avoid races when
636
+ // multiple deployments run concurrently (e.g., parallel test workers)
637
+ const tmpDir = fs.mkdtempSync(path.join(tmpdir(), 'aiwg-rules-assembly-'));
638
+ const assembledPath = path.join(tmpDir, 'RULES-INDEX.md');
639
+ fs.writeFileSync(assembledPath, assembled);
640
+
641
+ // Replace the sdlc RULES-INDEX.md with the assembled one;
642
+ // keep any non-RULES-INDEX files (non-consolidated addon rules)
643
+ const finalRuleFiles = [
644
+ assembledPath,
645
+ ...ruleFiles.filter(f => path.basename(f) !== 'RULES-INDEX.md')
646
+ ];
647
+
648
+ if (verbose) console.log(`\nDeploying assembled RULES-INDEX.md + ${finalRuleFiles.length - 1} additional rule files...`);
649
+ deployRules(finalRuleFiles, target, opts);
650
+ counts.rules = finalRuleFiles.length;
651
+
652
+ // Cleanup temp directory
653
+ try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch (e) { /* ignore */ }
654
+ } else {
655
+ // Fallback: deploy individual files
656
+ if (verbose) console.log(`\nDeploying ${ruleFiles.length} rules...`);
657
+ deployRules(ruleFiles, target, opts);
658
+ counts.rules = ruleFiles.length;
659
+ }
660
+
661
+ // On-demand index (#1673): list the MEDIUM/LOW rules that were tier-gated
662
+ // out of the always-on set so agents can fetch them via `aiwg show rule`.
663
+ const rulesDestDir = path.join(target, paths.rules);
664
+ const onDemandCount = writeOnDemandRuleIndex(rulesDestDir, listOnDemandRuleFiles(srcRoot), opts);
665
+ if (verbose && onDemandCount > 0) {
666
+ console.log(` On-demand rules (not inlined): ${onDemandCount} → RULES-ONDEMAND.md`);
667
+ }
668
+ }
669
+
670
+ // Post-deployment (pass counts for hook file token substitution)
671
+ await postDeploy(target, { ...opts, counts });
672
+
673
+ if (verbose) {
674
+ console.log('\n=== Claude deployment complete ===\n');
675
+ } else {
676
+ // Clean summary output
677
+ const parts = [];
678
+ if (counts.agents > 0) parts.push(`${counts.agents} agents`);
679
+ if (counts.souls > 0) parts.push(`${counts.souls} souls`);
680
+ if (counts.commands > 0) parts.push(`${counts.commands} commands`);
681
+ if (counts.skills > 0) parts.push(`${counts.skills} skills`);
682
+ if (counts.rules > 0) parts.push(`${counts.rules} rules`);
683
+ if (parts.length > 0) {
684
+ console.log(` Deployed: ${parts.join(' ')}`);
685
+ }
686
+ }
687
+ }
688
+
689
+ // ============================================================================
690
+ // Default Export
691
+ // ============================================================================
692
+
693
+ export default {
694
+ name,
695
+ aliases,
696
+ paths,
697
+ kernelSkillsPath,
698
+ support,
699
+ capabilities,
700
+ transformAgent,
701
+ transformCommand,
702
+ mapModel,
703
+ deployAgents,
704
+ deployCommands,
705
+ deploySkills,
706
+ deployRules,
707
+ createAgentsMd,
708
+ postDeploy,
709
+ getFileExtension,
710
+ deploy
711
+ };