@aiwg/cli 2026.7.20 → 2026.7.23

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 (62) hide show
  1. package/README.md +18 -7
  2. package/dist/src/api/index.d.ts +2 -0
  3. package/dist/src/api/index.js +2 -0
  4. package/dist/src/artifacts/browser-export.js +7 -0
  5. package/dist/src/artifacts/citation-parser.js +96 -35
  6. package/dist/src/artifacts/index-builder.js +54 -17
  7. package/dist/src/artifacts/state-transfer.js +27 -0
  8. package/dist/src/artifacts/stats.js +8 -0
  9. package/dist/src/cli/cli-extension-loader.js +73 -0
  10. package/dist/src/cli/handlers/index.js +3 -1
  11. package/dist/src/cli/handlers/sessions.js +1265 -0
  12. package/dist/src/cli/handlers/skill-lint.js +49 -45
  13. package/dist/src/cli/handlers/use.js +143 -60
  14. package/dist/src/cli/handlers/utilities.js +22 -8
  15. package/dist/src/cli/skill-usage.js +146 -24
  16. package/dist/src/config/aiwg-config.js +12 -0
  17. package/dist/src/config/cli.js +16 -3
  18. package/dist/src/extensions/commands/definitions.js +29 -0
  19. package/dist/src/extensions/manifest.js +29 -0
  20. package/dist/src/security/threat-assessment-config.js +296 -0
  21. package/dist/src/sessions/adapters/claude.js +385 -0
  22. package/dist/src/sessions/adapters/codex.js +548 -0
  23. package/dist/src/sessions/adapters/copilot.js +226 -0
  24. package/dist/src/sessions/adapters/cursor.js +528 -0
  25. package/dist/src/sessions/adapters/factory.js +386 -0
  26. package/dist/src/sessions/adapters/generic.js +225 -0
  27. package/dist/src/sessions/adapters/hermes.js +341 -0
  28. package/dist/src/sessions/adapters/openclaw.js +381 -0
  29. package/dist/src/sessions/adapters/opencode.js +454 -0
  30. package/dist/src/sessions/adapters/openhuman.js +315 -0
  31. package/dist/src/sessions/adapters/warp.js +160 -0
  32. package/dist/src/sessions/adapters/windsurf.js +212 -0
  33. package/dist/src/sessions/batch-contracts.js +121 -0
  34. package/dist/src/sessions/batch-import.js +265 -0
  35. package/dist/src/sessions/candidates.js +210 -0
  36. package/dist/src/sessions/contracts.js +337 -0
  37. package/dist/src/sessions/discovery.js +51 -0
  38. package/dist/src/sessions/fixtures.js +12 -0
  39. package/dist/src/sessions/import-lease.js +152 -0
  40. package/dist/src/sessions/importer.js +464 -0
  41. package/dist/src/sessions/index.js +31 -0
  42. package/dist/src/sessions/knowledge-shard.js +61 -0
  43. package/dist/src/sessions/optional-backends.js +238 -0
  44. package/dist/src/sessions/origin.js +117 -0
  45. package/dist/src/sessions/policy.js +192 -0
  46. package/dist/src/sessions/ports.js +2 -0
  47. package/dist/src/sessions/promotion.js +367 -0
  48. package/dist/src/sessions/readers.js +176 -0
  49. package/dist/src/sessions/repository.js +1892 -0
  50. package/dist/src/sessions/timeline.js +148 -0
  51. package/dist/src/sessions/workspace-discovery.js +319 -0
  52. package/dist/src/skills/adapters/agent-skills.js +59 -0
  53. package/dist/src/skills/adapters/local.js +19 -1
  54. package/dist/src/skills/agent-skills.js +249 -0
  55. package/dist/src/skills/cli.js +463 -7
  56. package/dist/src/skills/deployer.js +554 -0
  57. package/dist/src/skills/doctor.js +105 -0
  58. package/dist/src/skills/exporter.js +382 -0
  59. package/dist/src/skills/importer.js +921 -0
  60. package/dist/src/skills/registry.js +19 -0
  61. package/dist/src/skills/validator.js +323 -0
  62. package/package.json +2 -2
@@ -11,8 +11,8 @@
11
11
  */
12
12
  import * as fs from 'fs/promises';
13
13
  import * as path from 'path';
14
- import * as yaml from 'js-yaml';
15
14
  import { validateSkillFrontmatter } from '../../extensions/validation.js';
15
+ import { validateAgentSkillFile } from '../../skills/validator.js';
16
16
  const THRESHOLDS = {
17
17
  strict: 80,
18
18
  standard: 60,
@@ -36,7 +36,14 @@ function partialDimension(score, notes) {
36
36
  * because the cleanup work in #1015 made schema correctness a baseline
37
37
  * expectation across the corpus.
38
38
  */
39
- function scoreSchema(frontmatter) {
39
+ function scoreSchema(frontmatter, diagnostics) {
40
+ const conformanceErrors = diagnostics.filter((item) => item.severity === 'error');
41
+ if (conformanceErrors.length > 0) {
42
+ const notes = conformanceErrors.map((item) => (item.code === 'AS_YAML_PARSE'
43
+ ? `YAML parse error: ${item.message}`
44
+ : `${item.yamlPath}: ${item.message} [${item.code}]`));
45
+ return scoreDimension(false, notes);
46
+ }
40
47
  const result = validateSkillFrontmatter(frontmatter);
41
48
  if (result.success)
42
49
  return scoreDimension(true);
@@ -190,38 +197,12 @@ function inventoryCompanionCli(filePath, body) {
190
197
  * @param filePath - Absolute or relative path to the SKILL.md
191
198
  * @param rubric - Strictness level (drives the pass threshold)
192
199
  */
193
- export async function lintSkillFile(filePath, rubric = 'standard') {
194
- const content = await fs.readFile(filePath, 'utf-8');
195
- const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
196
- if (!fmMatch) {
197
- return {
198
- file: filePath,
199
- score: 0,
200
- passes: false,
201
- dimensions: {
202
- schema: scoreDimension(false, ['no YAML frontmatter found']),
203
- description: scoreDimension(false, ['no frontmatter to derive description from']),
204
- discoverability: scoreDimension(false, ['no frontmatter']),
205
- body: scoreBody(content),
206
- },
207
- };
208
- }
209
- const [, fmText, body = ''] = fmMatch;
210
- let frontmatter = {};
211
- let yamlError = null;
212
- try {
213
- const parsed = yaml.load(fmText);
214
- if (parsed && typeof parsed === 'object') {
215
- frontmatter = parsed;
216
- }
217
- }
218
- catch (e) {
219
- yamlError = e.message.split('\n')[0];
220
- }
200
+ export async function lintSkillFile(filePath, rubric = 'standard', profile = 'compatible') {
201
+ const validation = validateAgentSkillFile(filePath, { profile });
202
+ const frontmatter = validation.frontmatter ?? {};
203
+ const body = validation.body;
221
204
  const dimensions = {
222
- schema: yamlError
223
- ? scoreDimension(false, [`YAML parse error: ${yamlError}`])
224
- : scoreSchema(frontmatter),
205
+ schema: scoreSchema(frontmatter, validation.diagnostics),
225
206
  description: scoreDescription(frontmatter),
226
207
  discoverability: scoreDiscoverability(frontmatter),
227
208
  body: scoreBody(body),
@@ -230,7 +211,12 @@ export async function lintSkillFile(filePath, rubric = 'standard') {
230
211
  return {
231
212
  file: filePath,
232
213
  score,
233
- passes: score >= THRESHOLDS[rubric],
214
+ conformance: {
215
+ profile,
216
+ state: validation.state,
217
+ diagnostics: validation.diagnostics,
218
+ },
219
+ passes: validation.valid && score >= THRESHOLDS[rubric],
234
220
  dimensions,
235
221
  };
236
222
  }
@@ -243,7 +229,8 @@ async function* walkSkillFiles(rootPath) {
243
229
  }
244
230
  if (!stat.isDirectory())
245
231
  return;
246
- const entries = await fs.readdir(rootPath, { withFileTypes: true });
232
+ const entries = (await fs.readdir(rootPath, { withFileTypes: true }))
233
+ .sort((left, right) => left.name.localeCompare(right.name));
247
234
  for (const e of entries) {
248
235
  if (e.name.startsWith('.') || e.name === 'node_modules')
249
236
  continue;
@@ -265,7 +252,7 @@ async function* walkSkillFiles(rootPath) {
265
252
  * Pure function — does not print or exit. The CLI handler renders
266
253
  * output and translates the report into an exit code.
267
254
  */
268
- export async function lintSkills(targetPaths, rubric = 'standard') {
255
+ export async function lintSkills(targetPaths, rubric = 'standard', profile = 'compatible') {
269
256
  const targets = Array.isArray(targetPaths) ? targetPaths : [targetPaths];
270
257
  const seen = new Set();
271
258
  const files = [];
@@ -275,7 +262,7 @@ export async function lintSkills(targetPaths, rubric = 'standard') {
275
262
  if (seen.has(resolved))
276
263
  continue;
277
264
  seen.add(resolved);
278
- files.push(await lintSkillFile(f, rubric));
265
+ files.push(await lintSkillFile(f, rubric, profile));
279
266
  }
280
267
  }
281
268
  const total = files.reduce((sum, f) => sum + f.score, 0);
@@ -289,6 +276,7 @@ export async function lintSkills(targetPaths, rubric = 'standard') {
289
276
  }
290
277
  return {
291
278
  rubric,
279
+ profile,
292
280
  threshold: THRESHOLDS[rubric],
293
281
  files,
294
282
  averageScore: files.length > 0 ? Math.round(total / files.length) : 0,
@@ -303,6 +291,7 @@ export async function lintSkills(targetPaths, rubric = 'standard') {
303
291
  function parseArgs(args) {
304
292
  const targets = [];
305
293
  let rubric = 'standard';
294
+ let profile = 'compatible';
306
295
  let json = false;
307
296
  for (let i = 0; i < args.length; i++) {
308
297
  const a = args[i];
@@ -315,27 +304,42 @@ function parseArgs(args) {
315
304
  rubric = next;
316
305
  }
317
306
  }
307
+ else if (a === '--profile' && i + 1 < args.length) {
308
+ const next = args[++i];
309
+ if (next === 'strict' || next === 'compatible' || next === 'discovery') {
310
+ profile = next;
311
+ }
312
+ }
318
313
  else if (!a.startsWith('-')) {
319
314
  targets.push(a);
320
315
  }
321
316
  }
322
317
  if (targets.length === 0)
323
318
  targets.push('agentic/code');
324
- return { targets, rubric, json };
319
+ return { targets, rubric, profile, json };
325
320
  }
326
321
  function renderTextReport(report) {
327
- const { files, threshold, rubric, averageScore, failedCount } = report;
322
+ const { files, threshold, rubric, profile, averageScore, failedCount } = report;
328
323
  for (const f of files) {
329
- if (f.passes)
324
+ if (f.passes && f.conformance.diagnostics.length === 0)
330
325
  continue;
331
- console.log(`✗ ${f.file} (${f.score}/100)`);
326
+ const mark = !f.passes
327
+ ? '✗'
328
+ : f.conformance.diagnostics.some((item) => item.severity === 'warning')
329
+ ? '⚠'
330
+ : '✓';
331
+ console.log(`${mark} ${f.file} (${f.score}/100, conformance=${f.conformance.state})`);
332
+ for (const item of f.conformance.diagnostics) {
333
+ console.log(` ${item.severity} ${item.code} ${item.yamlPath}: ${item.message}; `
334
+ + `fix: ${item.remediation}`);
335
+ }
332
336
  for (const [name, dim] of Object.entries(f.dimensions)) {
333
- if (dim.score < 100) {
337
+ if (!f.passes && dim.score < 100) {
334
338
  console.log(` ${name} (${dim.score}): ${dim.notes.join('; ')}`);
335
339
  }
336
340
  }
337
341
  }
338
- console.log(`\nskill-lint (rubric=${rubric}, threshold=${threshold}):`);
342
+ console.log(`\nskill-lint (rubric=${rubric}, profile=${profile}, threshold=${threshold}):`);
339
343
  console.log(` ${files.length} file(s) scanned`);
340
344
  console.log(` ${failedCount} below threshold`);
341
345
  console.log(` average score: ${averageScore}/100`);
@@ -351,8 +355,8 @@ export const skillLintHandler = {
351
355
  category: 'utility',
352
356
  aliases: ['-skill-lint', '--skill-lint'],
353
357
  async execute(ctx) {
354
- const { targets, rubric, json } = parseArgs(ctx.args);
355
- const report = await lintSkills(targets, rubric);
358
+ const { targets, rubric, profile, json } = parseArgs(ctx.args);
359
+ const report = await lintSkills(targets, rubric, profile);
356
360
  if (json) {
357
361
  console.log(JSON.stringify(report, null, 2));
358
362
  }
@@ -17,7 +17,7 @@ import { createScriptRunner } from './script-runner.js';
17
17
  import { getFrameworkRoot, getVersionInfo } from '../../channel/manager.mjs';
18
18
  import { getRegistry } from '../../extensions/registry.js';
19
19
  import { registerDeployedExtensions } from '../../extensions/deployment-registration.js';
20
- import { registerCliCommands, registerHooks } from '../cli-extension-loader.js';
20
+ import { loadCliCommandsContribution, registerCliCommands, registerHooks, } from '../cli-extension-loader.js';
21
21
  import { translateSkillsToCommands, providerNeedsCommands } from '../../plugin/skill-command-translator.js';
22
22
  import * as ui from '../ui.js';
23
23
  import { readAiwgConfig, writeAiwgConfig, updateInstalled, hashManifest, emptyConfig, getProjectDir } from '../../config/aiwg-config.js';
@@ -270,6 +270,25 @@ export function addonPath(frameworkRoot, name) {
270
270
  const folderName = resolveAddonFolderName(name);
271
271
  return path.join(frameworkRoot, 'agentic/code/addons', folderName);
272
272
  }
273
+ async function registerSourceCliCommands(opts) {
274
+ const contribution = await loadCliCommandsContribution(opts.source);
275
+ if (!contribution)
276
+ return 0;
277
+ const { manifest, commandsSource } = contribution;
278
+ const count = Object.keys(manifest.subcommands).length;
279
+ if (opts.dryRun) {
280
+ ui.dim(` [dry-run] Would register CLI namespace '${manifest.namespace}' (${count} subcommands)`);
281
+ return count;
282
+ }
283
+ await registerCliCommands(opts.target, manifest.namespace, manifest.description || opts.fallbackDescription, commandsSource, manifest.subcommands);
284
+ ui.success(`CLI namespace '${manifest.namespace}' registered (${count} subcommands)`);
285
+ if (opts.provider === 'claude') {
286
+ const registeredHooks = await registerHooks(opts.target, manifest.namespace, manifest.subcommands);
287
+ for (const hook of registeredHooks)
288
+ ui.success(`Hook registered: ${hook}`);
289
+ }
290
+ return count;
291
+ }
273
292
  function getProviderPaths(provider) {
274
293
  const paths = getProviderArtifactPathStrings(provider) ?? getProviderArtifactPathStrings('claude');
275
294
  if (!paths)
@@ -777,54 +796,82 @@ async function deployOneProjectLocalBundle(opts) {
777
796
  const { bundle, ctx, frameworkRoot, provider, target, dryRun, verbose, quiet, modelArgs } = opts;
778
797
  const counts = await countBundleSourceArtifacts(bundle.artifactPath);
779
798
  const artifactTotal = counts.agents + counts.commands + counts.skills + counts.rules;
799
+ let cliCommandCount = 0;
800
+ try {
801
+ const contribution = await loadCliCommandsContribution(bundle.artifactPath);
802
+ cliCommandCount = contribution ? Object.keys(contribution.manifest.subcommands).length : 0;
803
+ }
804
+ catch (error) {
805
+ ui.warn(`Invalid CLI contribution for project-local '${bundle.id}': ${error.message}`);
806
+ return { exitCode: 1, counts };
807
+ }
780
808
  if (verbose || dryRun) {
781
- ui.dim(` Artifacts: agents=${counts.agents} commands=${counts.commands} skills=${counts.skills} rules=${counts.rules}`);
809
+ ui.dim(` Artifacts: agents=${counts.agents} commands=${counts.commands} skills=${counts.skills} rules=${counts.rules} cli=${cliCommandCount}`);
782
810
  }
783
- if (artifactTotal === 0) {
784
- ui.warn(`Project-local ${bundle.type} '${bundle.id}' has no deployable agents, commands, skills, or rules at ${bundle.artifactPath}`);
811
+ if (artifactTotal === 0 && cliCommandCount === 0) {
812
+ ui.warn(`Project-local ${bundle.type} '${bundle.id}' has no deployable agents, commands, skills, rules, or CLI commands at ${bundle.artifactPath}`);
785
813
  return { exitCode: 1, counts };
786
814
  }
787
- const runner = createScriptRunner(frameworkRoot);
788
- const args = [
789
- '--source', bundle.artifactPath,
790
- '--deploy-commands', '--deploy-skills', '--deploy-rules',
791
- '--provider', provider,
792
- '--target', target,
793
- // Project-local skills MUST land in the per-project skills tier
794
- // (#1228 follow-up). Default deploy mode after #1217 is no-copy +
795
- // index-driven discovery, but that model assumes upstream skills at
796
- // $AIWG_ROOT — project-local bundles live under the project's .aiwg/
797
- // tree and aren't reachable via `aiwg discover` of the framework
798
- // graph. Without --copy-all, the bundle's rules deploy but its skills
799
- // never reach <provider>/.aiwg/skills/, leaving them invisible to
800
- // both the platform and the index.
801
- '--copy-all',
802
- ...modelArgs,
803
- ];
804
- if (dryRun)
805
- args.push('--dry-run');
806
- if (verbose)
807
- args.push('--verbose');
808
- if (quiet && !verbose)
809
- args.push('--quiet');
810
- // Project-local bundles are addon-shaped — never trigger the legacy commands
811
- // migration prompt (which is only relevant for full-framework deploys).
812
- args.push('--skip-commands-migration');
813
- const captureOpts = quiet && !verbose ? { capture: true } : {};
814
- // Inject AIWG_ROOT so the deploy subprocess can resolve the upstream AIWG
815
- // install root. The bundle's `--source` is its project-local path, so
816
- // `computeAllKernelNames`/`computeAllArtifactBasenames` (which walk up from
817
- // srcRoot looking for agentic/code/{frameworks,addons}) would otherwise fail
818
- // and prune the provider's kernel skill directory with an empty desired set
819
- // (#123). `frameworkRoot` is the AIWG install root that owns these trees.
820
- const result = await runner.run('tools/agents/deploy-agents.mjs', args, {
821
- ...captureOpts,
822
- env: { AIWG_ROOT: frameworkRoot },
823
- });
815
+ let exitCode = 0;
816
+ if (artifactTotal > 0) {
817
+ const runner = createScriptRunner(frameworkRoot);
818
+ const args = [
819
+ '--source', bundle.artifactPath,
820
+ '--deploy-commands', '--deploy-skills', '--deploy-rules',
821
+ '--provider', provider,
822
+ '--target', target,
823
+ // Project-local skills MUST land in the per-project skills tier
824
+ // (#1228 follow-up). Default deploy mode after #1217 is no-copy +
825
+ // index-driven discovery, but that model assumes upstream skills at
826
+ // $AIWG_ROOT project-local bundles live under the project's .aiwg/
827
+ // tree and aren't reachable via `aiwg discover` of the framework
828
+ // graph. Without --copy-all, the bundle's rules deploy but its skills
829
+ // never reach <provider>/.aiwg/skills/, leaving them invisible to
830
+ // both the platform and the index.
831
+ '--copy-all',
832
+ ...modelArgs,
833
+ ];
834
+ if (dryRun)
835
+ args.push('--dry-run');
836
+ if (verbose)
837
+ args.push('--verbose');
838
+ if (quiet && !verbose)
839
+ args.push('--quiet');
840
+ // Project-local bundles are addon-shaped — never trigger the legacy commands
841
+ // migration prompt (which is only relevant for full-framework deploys).
842
+ args.push('--skip-commands-migration');
843
+ const captureOpts = quiet && !verbose ? { capture: true } : {};
844
+ // Inject AIWG_ROOT so the deploy subprocess can resolve the upstream AIWG
845
+ // install root. The bundle's `--source` is its project-local path, so
846
+ // `computeAllKernelNames`/`computeAllArtifactBasenames` (which walk up from
847
+ // srcRoot looking for agentic/code/{frameworks,addons}) would otherwise fail
848
+ // and prune the provider's kernel skill directory with an empty desired set
849
+ // (#123). `frameworkRoot` is the AIWG install root that owns these trees.
850
+ const result = await runner.run('tools/agents/deploy-agents.mjs', args, {
851
+ ...captureOpts,
852
+ env: { AIWG_ROOT: frameworkRoot },
853
+ });
854
+ exitCode = result.exitCode;
855
+ }
856
+ if (exitCode === 0 && cliCommandCount > 0) {
857
+ try {
858
+ await registerSourceCliCommands({
859
+ source: bundle.artifactPath,
860
+ target,
861
+ provider,
862
+ dryRun,
863
+ fallbackDescription: `${bundle.id} project-local commands`,
864
+ });
865
+ }
866
+ catch (error) {
867
+ ui.warn(`Failed to register CLI commands for project-local '${bundle.id}': ${error.message}`);
868
+ exitCode = 1;
869
+ }
870
+ }
824
871
  // Approximate counts from the bundle's source dirs (deploy-agents.mjs is
825
872
  // idempotent and copies file-for-file from these dirs)
826
873
  void ctx;
827
- return { exitCode: result.exitCode, counts };
874
+ return { exitCode, counts };
828
875
  }
829
876
  /**
830
877
  * Discover and deploy artifact-bearing project-local bundles from
@@ -1461,7 +1508,25 @@ async function deploySourceDirectory(opts) {
1461
1508
  if (opts.quiet)
1462
1509
  args.unshift('--quiet');
1463
1510
  const runner = createScriptRunner(opts.frameworkRoot);
1464
- return runner.run('tools/agents/deploy-agents.mjs', args, opts.quiet ? { capture: true } : {});
1511
+ const result = await runner.run('tools/agents/deploy-agents.mjs', args, opts.quiet ? { capture: true } : {});
1512
+ if (result.exitCode === 0) {
1513
+ try {
1514
+ await registerSourceCliCommands({
1515
+ source: opts.source,
1516
+ target: opts.target,
1517
+ provider: opts.provider,
1518
+ dryRun: opts.dryRun,
1519
+ fallbackDescription: `${path.basename(opts.source)} addon commands`,
1520
+ });
1521
+ }
1522
+ catch (error) {
1523
+ return {
1524
+ exitCode: 1,
1525
+ message: `Failed to register addon CLI commands: ${error.message}`,
1526
+ };
1527
+ }
1528
+ }
1529
+ return result;
1465
1530
  }
1466
1531
  /**
1467
1532
  * Use command handler
@@ -1940,25 +2005,19 @@ export class UseHandler {
1940
2005
  }
1941
2006
  // Register CLI commands if addon declares them
1942
2007
  try {
1943
- const manifestPath = path.join(addonSource, 'manifest.json');
1944
- const manifestContent = await fs.readFile(manifestPath, 'utf-8');
1945
- const manifest = JSON.parse(manifestContent);
1946
- if (manifest.cli_commands?.namespace && manifest.cli_commands?.subcommands) {
1947
- const cmds = manifest.cli_commands;
1948
- const commandsSource = path.join(addonSource, cmds.entry || 'commands/');
1949
- await registerCliCommands(target, cmds.namespace, cmds.description || `${framework} addon commands`, commandsSource, cmds.subcommands);
1950
- ui.success(`CLI namespace '${cmds.namespace}' registered (${Object.keys(cmds.subcommands).length} subcommands)`);
1951
- // Register Claude Code hooks for subcommands with hook_event
1952
- if (provider === 'claude') {
1953
- const registeredHooks = await registerHooks(target, cmds.namespace, cmds.subcommands);
1954
- for (const hook of registeredHooks) {
1955
- ui.success(`Hook registered: ${hook}`);
1956
- }
1957
- }
1958
- }
2008
+ await registerSourceCliCommands({
2009
+ source: addonSource,
2010
+ target,
2011
+ provider,
2012
+ dryRun: false,
2013
+ fallbackDescription: `${framework} addon commands`,
2014
+ });
1959
2015
  }
1960
2016
  catch (error) {
1961
- ui.warn(`Failed to register CLI commands: ${error instanceof Error ? error.message : String(error)}`);
2017
+ return {
2018
+ exitCode: 1,
2019
+ message: `Failed to register CLI commands: ${error instanceof Error ? error.message : String(error)}`,
2020
+ };
1962
2021
  }
1963
2022
  // Profile picker for addons with memory topology and multiple templates
1964
2023
  try {
@@ -2247,6 +2306,18 @@ export class UseHandler {
2247
2306
  if (result.exitCode !== 0) {
2248
2307
  return result;
2249
2308
  }
2309
+ try {
2310
+ await registerSourceCliCommands({
2311
+ source,
2312
+ target,
2313
+ provider,
2314
+ dryRun,
2315
+ fallbackDescription: `${addon} addon commands`,
2316
+ });
2317
+ }
2318
+ catch (error) {
2319
+ ui.warn(`Failed to register CLI commands for '${addon}': ${error.message}`);
2320
+ }
2250
2321
  }
2251
2322
  // Deploy all extensions from agentic/code/extensions/* (#1222).
2252
2323
  // Extensions are addon-shaped bundles (manifest type: "addon") that live
@@ -2267,6 +2338,18 @@ export class UseHandler {
2267
2338
  if (result.exitCode !== 0) {
2268
2339
  return result;
2269
2340
  }
2341
+ try {
2342
+ await registerSourceCliCommands({
2343
+ source,
2344
+ target,
2345
+ provider,
2346
+ dryRun,
2347
+ fallbackDescription: `${ext} extension commands`,
2348
+ });
2349
+ }
2350
+ catch (error) {
2351
+ ui.warn(`Failed to register CLI commands for '${ext}': ${error.message}`);
2352
+ }
2270
2353
  }
2271
2354
  }
2272
2355
  // Deploy project-local bundles (#1035). Auto-runs after upstream addons unless
@@ -130,8 +130,8 @@ function countSentences(s) {
130
130
  return matches.length + (endsWithTerminator ? 0 : 1);
131
131
  }
132
132
  /**
133
- * Scan source SKILL.md files in `agentic/code/` for namespace issues:
134
- * - Missing `namespace: aiwg`
133
+ * Scan canonical AIWG source SKILL.md files in `agentic/code/` for policy issues:
134
+ * - Missing the AIWG-only `namespace: aiwg` source convention
135
135
  * - Slug (`aiwg-{name}`) that would shadow an AIWG CLI command
136
136
  *
137
137
  * Returns lines suitable for console output, or empty array if clean.
@@ -263,12 +263,14 @@ export const validateMetadataHandler = {
263
263
  const result = await runner.run('tools/cli/validate-metadata.mjs', scriptArgs, {
264
264
  cwd: ctx.cwd,
265
265
  });
266
- // Append namespace validation: scan source SKILL.md files
266
+ // Append canonical AIWG source policy. `namespace` is an AIWG extension,
267
+ // not an Agent Skills standard requirement; shared conformance is emitted
268
+ // by validate-metadata.mjs before this source-only convention.
267
269
  try {
268
270
  const issues = await scanSourceNamespaceIssues(frameworkRoot);
269
271
  if (issues.length > 0) {
270
- console.log('\n── Namespace validation ──');
271
- console.log(` ${issues.length} skill(s) missing namespace field:`);
272
+ console.log('\n── AIWG source conventions ──');
273
+ console.log(` ${issues.length} canonical source convention issue(s):`);
272
274
  // Show first 20 to avoid flooding output
273
275
  issues.slice(0, 20).forEach(l => console.log(l));
274
276
  if (issues.length > 20) {
@@ -276,11 +278,11 @@ export const validateMetadataHandler = {
276
278
  }
277
279
  // Non-zero exit only in strict mode
278
280
  if (ctx.args.includes('--strict') && result.exitCode === 0) {
279
- return { exitCode: 1, message: `Namespace validation failed: ${issues.length} skill(s) missing namespace field` };
281
+ return { exitCode: 1, message: `AIWG source convention validation failed: ${issues.length} issue(s)` };
280
282
  }
281
283
  }
282
284
  else {
283
- console.log('\n── Namespace validation: all skills have namespace field ✓');
285
+ console.log('\n── AIWG source conventions: all canonical skills include the AIWG namespace extension ✓');
284
286
  }
285
287
  }
286
288
  catch {
@@ -460,6 +462,16 @@ export const doctorHandler = {
460
462
  const runner = createScriptRunner(frameworkRoot);
461
463
  // Run core doctor diagnostics
462
464
  const result = await runner.run('tools/cli/doctor.mjs', ctx.args, { cwd: ctx.cwd });
465
+ let agentSkillsFailure = false;
466
+ try {
467
+ const { buildAgentSkillsDoctorSection } = await import('../../skills/doctor.js');
468
+ const section = buildAgentSkillsDoctorSection(ctx.cwd || process.cwd());
469
+ console.log(section.output);
470
+ agentSkillsFailure = section.hasFailures;
471
+ }
472
+ catch (error) {
473
+ console.log(`\n── Agent Skills conformance ──\n ⚠ unable to audit: ${error instanceof Error ? error.message : String(error)}`);
474
+ }
463
475
  // Surface feedback escape hatch when doctor finds issues
464
476
  if (result.exitCode !== 0) {
465
477
  console.log(`
@@ -567,7 +579,9 @@ export const doctorHandler = {
567
579
  catch (error) {
568
580
  console.log(`\n── Workspace context graph ──\n ⚠ unable to audit: ${error instanceof Error ? error.message : String(error)}`);
569
581
  }
570
- return result;
582
+ return agentSkillsFailure
583
+ ? { exitCode: 1, message: '' }
584
+ : result;
571
585
  },
572
586
  };
573
587
  /**