@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,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
|
+
};
|