@aiwg/cli 2026.7.20 → 2026.7.21

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 (53) hide show
  1. package/README.md +4 -4
  2. package/dist/src/api/index.d.ts +1 -0
  3. package/dist/src/api/index.js +1 -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 +966 -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/extensions/commands/definitions.js +29 -0
  17. package/dist/src/extensions/manifest.js +29 -0
  18. package/dist/src/sessions/adapters/claude.js +357 -0
  19. package/dist/src/sessions/adapters/codex.js +521 -0
  20. package/dist/src/sessions/adapters/copilot.js +226 -0
  21. package/dist/src/sessions/adapters/cursor.js +372 -0
  22. package/dist/src/sessions/adapters/factory.js +345 -0
  23. package/dist/src/sessions/adapters/generic.js +225 -0
  24. package/dist/src/sessions/adapters/hermes.js +341 -0
  25. package/dist/src/sessions/adapters/openclaw.js +381 -0
  26. package/dist/src/sessions/adapters/opencode.js +454 -0
  27. package/dist/src/sessions/adapters/openhuman.js +315 -0
  28. package/dist/src/sessions/adapters/warp.js +160 -0
  29. package/dist/src/sessions/adapters/windsurf.js +212 -0
  30. package/dist/src/sessions/candidates.js +210 -0
  31. package/dist/src/sessions/contracts.js +310 -0
  32. package/dist/src/sessions/discovery.js +51 -0
  33. package/dist/src/sessions/fixtures.js +12 -0
  34. package/dist/src/sessions/importer.js +315 -0
  35. package/dist/src/sessions/index.js +25 -0
  36. package/dist/src/sessions/knowledge-shard.js +61 -0
  37. package/dist/src/sessions/optional-backends.js +238 -0
  38. package/dist/src/sessions/policy.js +192 -0
  39. package/dist/src/sessions/ports.js +2 -0
  40. package/dist/src/sessions/promotion.js +367 -0
  41. package/dist/src/sessions/readers.js +176 -0
  42. package/dist/src/sessions/repository.js +1551 -0
  43. package/dist/src/skills/adapters/agent-skills.js +59 -0
  44. package/dist/src/skills/adapters/local.js +19 -1
  45. package/dist/src/skills/agent-skills.js +249 -0
  46. package/dist/src/skills/cli.js +463 -7
  47. package/dist/src/skills/deployer.js +554 -0
  48. package/dist/src/skills/doctor.js +105 -0
  49. package/dist/src/skills/exporter.js +382 -0
  50. package/dist/src/skills/importer.js +921 -0
  51. package/dist/src/skills/registry.js +19 -0
  52. package/dist/src/skills/validator.js +323 -0
  53. package/package.json +2 -2
@@ -0,0 +1,554 @@
1
+ /**
2
+ * Managed Agent Skills provider deployment.
3
+ *
4
+ * Imported source trees remain immutable. Provider surfaces receive an atomic,
5
+ * strict Agent Skills projection plus ownership metadata outside SKILL.md.
6
+ *
7
+ * @implements #1879
8
+ */
9
+ import fs from 'node:fs';
10
+ import os from 'node:os';
11
+ import path from 'node:path';
12
+ import { randomUUID } from 'node:crypto';
13
+ import { stringify } from 'yaml';
14
+ import { getProviderDefinition, normalizeProviderDefinitionId, } from '../providers/provider-definitions.js';
15
+ import { AGENT_SKILLS_SIDECAR_SCHEMA, AIWG_SKILL_CONTROL_FIELDS, createAgentSkillSidecar, projectStrictAgentSkill, } from './agent-skills.js';
16
+ import { getImportedAgentSkill } from './importer.js';
17
+ import { validateAgentSkillContent } from './validator.js';
18
+ export const AGENT_SKILL_MANAGED_MARKER = '.aiwg-managed';
19
+ export const AGENT_SKILL_DEPLOYMENT_SIDECAR = '.aiwg-agent-skill.json';
20
+ const MARKER_CONTENT = 'aiwg-agent-skill-v1\n';
21
+ const STANDARD_FIELDS = [
22
+ 'name',
23
+ 'description',
24
+ 'license',
25
+ 'compatibility',
26
+ 'metadata',
27
+ 'allowed-tools',
28
+ ];
29
+ const AIWG_FIELDS = new Set(AIWG_SKILL_CONTROL_FIELDS);
30
+ export class AgentSkillDeploymentError extends Error {
31
+ code;
32
+ constructor(code, message) {
33
+ super(message);
34
+ this.code = code;
35
+ this.name = 'AgentSkillDeploymentError';
36
+ }
37
+ }
38
+ function fail(code, message) {
39
+ throw new AgentSkillDeploymentError(code, message);
40
+ }
41
+ function assertPortableName(name) {
42
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name) || name.length > 64) {
43
+ fail('AS_DEPLOY_NAME', 'Agent Skill name must be 1-64 lowercase ASCII letters, numbers, or single hyphens');
44
+ }
45
+ }
46
+ function resolvedHome(options) {
47
+ return path.resolve(options.homeDir ?? os.homedir());
48
+ }
49
+ function resolvePolicy(target, options) {
50
+ const provider = normalizeProviderDefinitionId(target);
51
+ if (!provider) {
52
+ fail('AS_DEPLOY_PROVIDER', `unknown Agent Skills target "${target}"`);
53
+ }
54
+ const definition = getProviderDefinition(provider);
55
+ if (!definition) {
56
+ fail('AS_DEPLOY_PROVIDER', `provider definition is unavailable for "${provider}"`);
57
+ }
58
+ const namespace = definition.skillNamespace;
59
+ const reasons = [];
60
+ const warnings = [];
61
+ let root = namespace.pathType === 'home-dir'
62
+ ? path.join(resolvedHome(options), namespace.skillsBaseDir)
63
+ : path.join(path.resolve(options.projectDir), namespace.skillsBaseDir);
64
+ let status = 'native';
65
+ let supported = true;
66
+ let appendToDescription;
67
+ let maxDescriptionLength;
68
+ switch (provider) {
69
+ case 'codex':
70
+ root = path.join(path.resolve(options.projectDir), '.agents', 'skills');
71
+ status = 'projected';
72
+ maxDescriptionLength = namespace.maxDescriptionLength;
73
+ reasons.push('uses the project .agents/skills compatibility surface without the legacy 100/500 truncation transform');
74
+ break;
75
+ case 'factory':
76
+ status = 'projected';
77
+ appendToDescription = namespace.appendToDescription;
78
+ reasons.push('applies the Factory description guidance before strict validation');
79
+ break;
80
+ case 'hermes':
81
+ reasons.push('uses the user-global ~/.hermes/skills bundle surface with strict managed ownership markers');
82
+ break;
83
+ case 'openhuman':
84
+ status = 'projected';
85
+ reasons.push('uses the verified global one-level ~/.openhuman/skills layout');
86
+ break;
87
+ case 'windsurf':
88
+ status = 'projected';
89
+ reasons.push('uses one skill bundle directly below the one-level .windsurf/skills surface');
90
+ break;
91
+ default:
92
+ reasons.push('provider exposes a native recursive Agent Skills bundle surface');
93
+ }
94
+ return {
95
+ provider,
96
+ root,
97
+ status,
98
+ supported,
99
+ reasons,
100
+ warnings,
101
+ appendToDescription,
102
+ maxDescriptionLength,
103
+ };
104
+ }
105
+ function assertSafeSourceEntry(sourceRoot, absolutePath, relativePath) {
106
+ const stat = fs.lstatSync(absolutePath);
107
+ if (stat.isSymbolicLink()) {
108
+ fail('AS_DEPLOY_SOURCE_SYMLINK', `managed source contains a symlink: ${relativePath}`);
109
+ }
110
+ if (!stat.isDirectory() && !stat.isFile()) {
111
+ fail('AS_DEPLOY_SOURCE_SPECIAL_FILE', `managed source contains a non-regular entry: ${relativePath}`);
112
+ }
113
+ const real = fs.realpathSync.native(absolutePath);
114
+ const relativeReal = path.relative(fs.realpathSync.native(sourceRoot), real);
115
+ if (relativeReal.startsWith('..') || path.isAbsolute(relativeReal)) {
116
+ fail('AS_DEPLOY_SOURCE_ESCAPE', `managed source entry escapes its root: ${relativePath}`);
117
+ }
118
+ return stat;
119
+ }
120
+ function collectSourceEntries(sourceRoot) {
121
+ const entries = [];
122
+ const walk = (current, prefix) => {
123
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })
124
+ .sort((left, right) => left.name.localeCompare(right.name))) {
125
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
126
+ if (relativePath === AGENT_SKILL_MANAGED_MARKER
127
+ || relativePath === AGENT_SKILL_DEPLOYMENT_SIDECAR) {
128
+ fail('AS_DEPLOY_RESERVED_PATH', `managed source uses reserved deployment path "${relativePath}"`);
129
+ }
130
+ const absolutePath = path.join(current, entry.name);
131
+ const stat = assertSafeSourceEntry(sourceRoot, absolutePath, relativePath);
132
+ if (stat.isDirectory()) {
133
+ entries.push({ kind: 'directory', relativePath });
134
+ walk(absolutePath, relativePath);
135
+ }
136
+ else {
137
+ entries.push({
138
+ kind: 'file',
139
+ relativePath,
140
+ bytes: fs.readFileSync(absolutePath),
141
+ });
142
+ }
143
+ }
144
+ };
145
+ walk(sourceRoot, '');
146
+ return entries;
147
+ }
148
+ function projectionDocument(record) {
149
+ const sourceEntries = collectSourceEntries(record.managedLocation);
150
+ const skillFile = sourceEntries.find((entry) => (entry.kind === 'file' && entry.relativePath === 'SKILL.md'));
151
+ if (!skillFile) {
152
+ fail('AS_DEPLOY_SKILL_MISSING', 'managed source does not contain SKILL.md');
153
+ }
154
+ const content = new TextDecoder('utf-8', { fatal: true }).decode(skillFile.bytes);
155
+ const validation = validateAgentSkillContent(content, {
156
+ profile: record.validationProfile,
157
+ file: path.join(record.managedLocation, 'SKILL.md'),
158
+ directoryName: record.name,
159
+ skillRoot: record.managedLocation,
160
+ checkResources: true,
161
+ });
162
+ if (!validation.valid || !validation.frontmatter) {
163
+ fail('AS_DEPLOY_SOURCE_INVALID', `managed source for "${record.name}" no longer passes ${record.validationProfile} validation`);
164
+ }
165
+ const standard = {};
166
+ for (const key of STANDARD_FIELDS) {
167
+ const value = validation.frontmatter[key];
168
+ if (value !== undefined) {
169
+ Object.assign(standard, { [key]: structuredClone(value) });
170
+ }
171
+ }
172
+ const aiwg = {};
173
+ for (const [key, value] of Object.entries(validation.frontmatter)) {
174
+ if (AIWG_FIELDS.has(key)) {
175
+ aiwg[key] = structuredClone(value);
176
+ }
177
+ }
178
+ return {
179
+ document: {
180
+ standard: standard,
181
+ body: validation.body,
182
+ resources: [],
183
+ aiwg,
184
+ unknownFields: [],
185
+ },
186
+ sourceEntries,
187
+ };
188
+ }
189
+ function strictSkillBytes(record, policy, document) {
190
+ const metadata = projectStrictAgentSkill(document);
191
+ if (policy.appendToDescription) {
192
+ const suffix = policy.appendToDescription;
193
+ if (!metadata.description.endsWith(suffix)) {
194
+ metadata.description = `${metadata.description.trimEnd()} ${suffix}`;
195
+ }
196
+ }
197
+ if (policy.maxDescriptionLength !== undefined
198
+ && metadata.description.length > policy.maxDescriptionLength) {
199
+ policy.status = 'degraded';
200
+ policy.supported = false;
201
+ policy.reasons.push(`description length ${metadata.description.length} exceeds the provider limit ${policy.maxDescriptionLength}; no truncation was applied`);
202
+ return undefined;
203
+ }
204
+ const frontmatter = stringify(metadata, {
205
+ lineWidth: 0,
206
+ sortMapEntries: false,
207
+ }).trimEnd();
208
+ const content = `---\n${frontmatter}\n---\n${document.body}`;
209
+ const validation = validateAgentSkillContent(content, {
210
+ profile: 'strict',
211
+ file: 'SKILL.md',
212
+ directoryName: record.name,
213
+ });
214
+ if (!validation.valid) {
215
+ policy.status = 'degraded';
216
+ policy.supported = false;
217
+ policy.reasons.push(`provider projection failed strict validation: ${validation.diagnostics
218
+ .filter((item) => item.severity === 'error')
219
+ .map((item) => item.code)
220
+ .join(', ')}`);
221
+ return undefined;
222
+ }
223
+ return Buffer.from(content, 'utf8');
224
+ }
225
+ function sidecarBytes(record, policy, document) {
226
+ const portableSidecar = createAgentSkillSidecar(document, {
227
+ sourceKind: record.source.kind,
228
+ locator: record.source.locator,
229
+ ...(record.source.kind === 'git'
230
+ ? {
231
+ requestedRevision: record.source.requestedRevision,
232
+ resolvedRevision: record.source.resolvedRevision,
233
+ }
234
+ : {}),
235
+ sourceDigest: record.digest,
236
+ importedAt: record.importedAt,
237
+ aiwgVersion: record.aiwgVersion,
238
+ }, record.validationProfile, record.trust);
239
+ return Buffer.from(`${JSON.stringify({
240
+ schemaVersion: 1,
241
+ kind: 'aiwg-managed-agent-skill-projection',
242
+ name: record.name,
243
+ provider: policy.provider,
244
+ projectionStatus: policy.status,
245
+ sourceDigest: record.digest,
246
+ reasons: policy.reasons,
247
+ warnings: policy.warnings,
248
+ portable: portableSidecar,
249
+ }, null, 2)}\n`, 'utf8');
250
+ }
251
+ function readDeploymentSidecar(targetPath, expectedName, expectedProvider) {
252
+ try {
253
+ const sidecarPath = path.join(targetPath, AGENT_SKILL_DEPLOYMENT_SIDECAR);
254
+ const stat = fs.lstatSync(sidecarPath);
255
+ if (!stat.isFile() || stat.isSymbolicLink())
256
+ return undefined;
257
+ const value = JSON.parse(fs.readFileSync(sidecarPath, 'utf8'));
258
+ if (value.schemaVersion !== 1
259
+ || value.kind !== 'aiwg-managed-agent-skill-projection'
260
+ || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value.name)
261
+ || !normalizeProviderDefinitionId(value.provider)
262
+ || !/^[0-9a-f]{64}$/.test(value.sourceDigest)
263
+ || !['native', 'projected', 'degraded', 'unsupported'].includes(value.projectionStatus)
264
+ || !Array.isArray(value.reasons)
265
+ || !value.reasons.every((item) => typeof item === 'string')
266
+ || !Array.isArray(value.warnings)
267
+ || !value.warnings.every((item) => typeof item === 'string')
268
+ || value.portable?.$schema !== AGENT_SKILLS_SIDECAR_SCHEMA
269
+ || value.portable.schemaVersion !== 1
270
+ || typeof value.portable.aiwg !== 'object'
271
+ || value.portable.aiwg === null
272
+ || Array.isArray(value.portable.aiwg)
273
+ || value.portable.provenance?.sourceDigest !== value.sourceDigest
274
+ || !['strict', 'compatible', 'discovery'].includes(value.portable.validationProfile)
275
+ || !['untrusted', 'trusted', 'revoked'].includes(value.portable.trust?.state)
276
+ || !['inactive', 'active', 'blocked'].includes(value.portable.trust?.activation)
277
+ || (expectedName !== undefined && value.name !== expectedName)
278
+ || (expectedProvider !== undefined && value.provider !== expectedProvider)
279
+ || path.basename(targetPath) !== value.name) {
280
+ return undefined;
281
+ }
282
+ return value;
283
+ }
284
+ catch {
285
+ return undefined;
286
+ }
287
+ }
288
+ function buildProjectionPlan(record, options) {
289
+ const policy = resolvePolicy(options.target, options);
290
+ const targetPath = path.join(policy.root, record.name);
291
+ if (!policy.supported) {
292
+ return {
293
+ policy,
294
+ targetPath,
295
+ sourceDigest: record.digest,
296
+ desiredEntries: [],
297
+ };
298
+ }
299
+ const { document, sourceEntries } = projectionDocument(record);
300
+ const skillBytes = strictSkillBytes(record, policy, document);
301
+ if (!skillBytes) {
302
+ return {
303
+ policy,
304
+ targetPath,
305
+ sourceDigest: record.digest,
306
+ desiredEntries: [],
307
+ };
308
+ }
309
+ const desiredEntries = sourceEntries
310
+ .filter((entry) => entry.relativePath !== 'SKILL.md')
311
+ .map((entry) => (entry.kind === 'directory'
312
+ ? { ...entry }
313
+ : { ...entry, bytes: Buffer.from(entry.bytes) }));
314
+ desiredEntries.push({ kind: 'file', relativePath: 'SKILL.md', bytes: skillBytes }, {
315
+ kind: 'file',
316
+ relativePath: AGENT_SKILL_MANAGED_MARKER,
317
+ bytes: Buffer.from(MARKER_CONTENT, 'utf8'),
318
+ }, {
319
+ kind: 'file',
320
+ relativePath: AGENT_SKILL_DEPLOYMENT_SIDECAR,
321
+ bytes: sidecarBytes(record, policy, document),
322
+ });
323
+ desiredEntries.sort((left, right) => (left.relativePath.localeCompare(right.relativePath)
324
+ || left.kind.localeCompare(right.kind)));
325
+ return {
326
+ policy,
327
+ targetPath,
328
+ sourceDigest: record.digest,
329
+ desiredEntries,
330
+ };
331
+ }
332
+ function isManagedTarget(targetPath, expectedName, expectedProvider) {
333
+ if (!fs.existsSync(targetPath))
334
+ return false;
335
+ try {
336
+ const targetStat = fs.lstatSync(targetPath);
337
+ const marker = path.join(targetPath, AGENT_SKILL_MANAGED_MARKER);
338
+ return (targetStat.isDirectory()
339
+ && !targetStat.isSymbolicLink()
340
+ && fs.lstatSync(marker).isFile()
341
+ && !fs.lstatSync(marker).isSymbolicLink()
342
+ && fs.readFileSync(marker, 'utf8') === MARKER_CONTENT
343
+ && readDeploymentSidecar(targetPath, expectedName, expectedProvider) !== undefined);
344
+ }
345
+ catch {
346
+ return false;
347
+ }
348
+ }
349
+ function targetMatches(targetPath, desired, name, provider) {
350
+ if (!isManagedTarget(targetPath, name, provider))
351
+ return false;
352
+ const actual = new Map();
353
+ const walk = (current, prefix) => {
354
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })
355
+ .sort((left, right) => left.name.localeCompare(right.name))) {
356
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
357
+ const absolutePath = path.join(current, entry.name);
358
+ const stat = fs.lstatSync(absolutePath);
359
+ if (stat.isSymbolicLink())
360
+ return false;
361
+ if (stat.isDirectory()) {
362
+ actual.set(relativePath, 'directory');
363
+ if (!walk(absolutePath, relativePath))
364
+ return false;
365
+ }
366
+ else if (stat.isFile()) {
367
+ actual.set(relativePath, fs.readFileSync(absolutePath));
368
+ }
369
+ else {
370
+ return false;
371
+ }
372
+ }
373
+ return true;
374
+ };
375
+ if (!walk(targetPath, ''))
376
+ return false;
377
+ if (actual.size !== desired.length)
378
+ return false;
379
+ return desired.every((entry) => {
380
+ const value = actual.get(entry.relativePath);
381
+ return entry.kind === 'directory'
382
+ ? value === 'directory'
383
+ : Buffer.isBuffer(value) && value.equals(entry.bytes);
384
+ });
385
+ }
386
+ function writeDesiredTree(root, desired) {
387
+ fs.mkdirSync(root, { recursive: false, mode: 0o700 });
388
+ for (const entry of desired.filter((item) => item.kind === 'directory').sort((left, right) => (left.relativePath.split('/').length - right.relativePath.split('/').length
389
+ || left.relativePath.localeCompare(right.relativePath)))) {
390
+ fs.mkdirSync(path.join(root, ...entry.relativePath.split('/')), {
391
+ recursive: true,
392
+ mode: 0o700,
393
+ });
394
+ }
395
+ for (const entry of desired.filter((item) => item.kind === 'file').sort((left, right) => left.relativePath.localeCompare(right.relativePath))) {
396
+ const target = path.join(root, ...entry.relativePath.split('/'));
397
+ fs.mkdirSync(path.dirname(target), { recursive: true, mode: 0o700 });
398
+ fs.writeFileSync(target, entry.bytes, { flag: 'wx', mode: 0o600 });
399
+ }
400
+ }
401
+ function promoteAtomically(name, plan) {
402
+ const parent = path.dirname(plan.targetPath);
403
+ fs.mkdirSync(parent, { recursive: true, mode: 0o700 });
404
+ const suffix = randomUUID();
405
+ const staging = path.join(parent, `.${path.basename(plan.targetPath)}.staging-${suffix}`);
406
+ const backup = path.join(parent, `.${path.basename(plan.targetPath)}.backup-${suffix}`);
407
+ const existed = fs.existsSync(plan.targetPath);
408
+ try {
409
+ writeDesiredTree(staging, plan.desiredEntries);
410
+ if (existed) {
411
+ if (!isManagedTarget(plan.targetPath, name, plan.policy.provider)) {
412
+ fail('AS_DEPLOY_COLLISION_RACE', 'deployment target ownership changed before atomic promotion');
413
+ }
414
+ fs.renameSync(plan.targetPath, backup);
415
+ }
416
+ try {
417
+ fs.renameSync(staging, plan.targetPath);
418
+ }
419
+ catch (error) {
420
+ if (fs.existsSync(plan.targetPath)) {
421
+ fs.rmSync(plan.targetPath, { recursive: true, force: true });
422
+ }
423
+ if (fs.existsSync(backup))
424
+ fs.renameSync(backup, plan.targetPath);
425
+ throw error;
426
+ }
427
+ if (fs.existsSync(backup)) {
428
+ try {
429
+ fs.rmSync(backup, { recursive: true, force: true });
430
+ }
431
+ catch (error) {
432
+ fs.rmSync(plan.targetPath, { recursive: true, force: true });
433
+ fs.renameSync(backup, plan.targetPath);
434
+ throw error;
435
+ }
436
+ }
437
+ return existed ? 'updated' : 'deployed';
438
+ }
439
+ finally {
440
+ if (fs.existsSync(staging))
441
+ fs.rmSync(staging, { recursive: true, force: true });
442
+ if (fs.existsSync(backup) && !fs.existsSync(plan.targetPath)) {
443
+ fs.renameSync(backup, plan.targetPath);
444
+ }
445
+ }
446
+ }
447
+ function result(operation, outcome, dryRun, name, plan) {
448
+ return {
449
+ schemaVersion: 1,
450
+ operation,
451
+ outcome,
452
+ dryRun,
453
+ name,
454
+ provider: plan.policy.provider,
455
+ projectionStatus: plan.policy.status,
456
+ path: plan.targetPath,
457
+ reasons: [...plan.policy.reasons],
458
+ warnings: [...plan.policy.warnings],
459
+ sourceDigest: plan.sourceDigest,
460
+ };
461
+ }
462
+ function requireDeployableImport(name, projectDir) {
463
+ const record = getImportedAgentSkill(projectDir, name);
464
+ if (!record) {
465
+ fail('AS_DEPLOY_IMPORT_MISSING', `managed Agent Skill "${name}" is not imported`);
466
+ }
467
+ if (record.trust.state !== 'trusted'
468
+ || record.trust.activation !== 'active') {
469
+ fail('AS_DEPLOY_IMPORT_INACTIVE', `managed Agent Skill "${name}" is ${record.trust.state}/${record.trust.activation}`);
470
+ }
471
+ if (record.diagnostics.some((item) => item.code === 'AS_IMPORT_MANAGED_DRIFT')) {
472
+ fail('AS_DEPLOY_IMPORT_DRIFT', `managed Agent Skill "${name}" no longer matches digest ${record.digest}`);
473
+ }
474
+ return record;
475
+ }
476
+ export function deployImportedAgentSkill(name, options) {
477
+ assertPortableName(name);
478
+ const projectDir = path.resolve(options.projectDir);
479
+ const record = requireDeployableImport(name, projectDir);
480
+ const plan = buildProjectionPlan(record, { ...options, projectDir });
481
+ const dryRun = options.dryRun ?? false;
482
+ if (!plan.policy.supported) {
483
+ return result('deploy', 'blocked', dryRun, name, plan);
484
+ }
485
+ if (fs.existsSync(plan.targetPath)
486
+ && !isManagedTarget(plan.targetPath, name, plan.policy.provider)) {
487
+ plan.policy.status = 'degraded';
488
+ plan.policy.reasons.push('target collision is not owned by AIWG');
489
+ plan.policy.warnings.push('the existing user-owned target was not modified');
490
+ return result('deploy', 'blocked', dryRun, name, plan);
491
+ }
492
+ if (targetMatches(plan.targetPath, plan.desiredEntries, name, plan.policy.provider)) {
493
+ return result('deploy', 'unchanged', dryRun, name, plan);
494
+ }
495
+ if (dryRun)
496
+ return result('deploy', 'planned', true, name, plan);
497
+ return result('deploy', promoteAtomically(name, plan), false, name, plan);
498
+ }
499
+ export function uninstallImportedAgentSkill(name, options) {
500
+ assertPortableName(name);
501
+ const projectDir = path.resolve(options.projectDir);
502
+ const record = getImportedAgentSkill(projectDir, name);
503
+ const policy = resolvePolicy(options.target, { ...options, projectDir });
504
+ const plan = {
505
+ policy,
506
+ targetPath: path.join(policy.root, name),
507
+ sourceDigest: record?.digest ?? '',
508
+ desiredEntries: [],
509
+ };
510
+ const deployedSidecar = readDeploymentSidecar(plan.targetPath, name, policy.provider);
511
+ if (deployedSidecar) {
512
+ plan.sourceDigest = deployedSidecar.sourceDigest;
513
+ }
514
+ const dryRun = options.dryRun ?? false;
515
+ if (!fs.existsSync(plan.targetPath)) {
516
+ return result('uninstall', 'absent', dryRun, name, plan);
517
+ }
518
+ if (!isManagedTarget(plan.targetPath, name, plan.policy.provider)) {
519
+ plan.policy.status = 'degraded';
520
+ plan.policy.reasons.push('target collision is not owned by AIWG');
521
+ plan.policy.warnings.push('the existing user-owned target was not removed');
522
+ return result('uninstall', 'blocked', dryRun, name, plan);
523
+ }
524
+ if (dryRun)
525
+ return result('uninstall', 'planned', true, name, plan);
526
+ fs.rmSync(plan.targetPath, { recursive: true, force: true });
527
+ return result('uninstall', 'removed', false, name, plan);
528
+ }
529
+ export function inspectImportedAgentSkillProjection(name, options) {
530
+ assertPortableName(name);
531
+ const projectDir = path.resolve(options.projectDir);
532
+ const record = getImportedAgentSkill(projectDir, name);
533
+ if (!record) {
534
+ fail('AS_DEPLOY_IMPORT_MISSING', `managed Agent Skill "${name}" is not imported`);
535
+ }
536
+ const plan = buildProjectionPlan(record, { ...options, projectDir, dryRun: true });
537
+ const exists = fs.existsSync(plan.targetPath);
538
+ const managed = exists && isManagedTarget(plan.targetPath, name, plan.policy.provider);
539
+ return {
540
+ provider: plan.policy.provider,
541
+ projectionStatus: plan.policy.status,
542
+ path: plan.targetPath,
543
+ sourceDigest: plan.sourceDigest,
544
+ supported: plan.policy.supported,
545
+ exists,
546
+ managed,
547
+ matches: (plan.policy.supported
548
+ && managed
549
+ && targetMatches(plan.targetPath, plan.desiredEntries, name, plan.policy.provider)),
550
+ reasons: [...plan.policy.reasons],
551
+ warnings: [...plan.policy.warnings],
552
+ };
553
+ }
554
+ //# sourceMappingURL=deployer.js.map
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Agent Skills health diagnostics for managed imports and active projections.
3
+ *
4
+ * @implements #1878
5
+ */
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import { listProviderDefinitions } from '../providers/provider-definitions.js';
9
+ import { AGENT_SKILLS_BASELINE, } from './agent-skills.js';
10
+ import { inspectImportedAgentSkillProjection } from './deployer.js';
11
+ import { listImportedAgentSkills } from './importer.js';
12
+ import { validateAgentSkillFile } from './validator.js';
13
+ function diagnostic(code, severity, file, yamlPath, message, remediation) {
14
+ return {
15
+ code,
16
+ severity,
17
+ file,
18
+ yamlPath,
19
+ message,
20
+ upstreamBaseline: AGENT_SKILLS_BASELINE.revision,
21
+ remediation,
22
+ };
23
+ }
24
+ export function buildAgentSkillsDoctorSection(projectDir, options = {}) {
25
+ const diagnostics = [];
26
+ let imported;
27
+ try {
28
+ imported = listImportedAgentSkills(projectDir);
29
+ }
30
+ catch (error) {
31
+ diagnostics.push(diagnostic('AS_DOCTOR_IMPORT_STORE', 'error', path.join(projectDir, '.aiwg', 'skills', 'imported'), '$', `managed Agent Skills store is invalid: ${error instanceof Error ? error.message : String(error)}`, 'Inspect or restore the managed import manifest, then re-import the source.'));
32
+ imported = [];
33
+ }
34
+ for (const skill of imported) {
35
+ diagnostics.push(...skill.diagnostics);
36
+ const sourceFile = path.join(skill.managedLocation, 'SKILL.md');
37
+ if (!fs.existsSync(sourceFile)) {
38
+ diagnostics.push(diagnostic('AS_DOCTOR_SOURCE_MISSING', 'error', sourceFile, '$', `managed source for "${skill.name}" is missing SKILL.md`, 'Restore the managed import with an explicit forced import.'));
39
+ continue;
40
+ }
41
+ diagnostics.push(...validateAgentSkillFile(sourceFile, {
42
+ profile: skill.validationProfile,
43
+ directoryName: skill.name,
44
+ }).diagnostics);
45
+ if (skill.trust.activation !== 'active')
46
+ continue;
47
+ for (const provider of listProviderDefinitions()
48
+ .sort((left, right) => left.id.localeCompare(right.id))) {
49
+ let inspection;
50
+ try {
51
+ inspection = inspectImportedAgentSkillProjection(skill.name, {
52
+ projectDir,
53
+ homeDir: options.homeDir,
54
+ target: provider.id,
55
+ dryRun: true,
56
+ });
57
+ }
58
+ catch (error) {
59
+ diagnostics.push(diagnostic('AS_DOCTOR_PROJECTION_PLAN', 'error', sourceFile, '$', `provider "${provider.id}" projection for "${skill.name}" cannot be planned: ${error instanceof Error ? error.message : String(error)}`, `Repair the managed import, then redeploy "${skill.name}" to ${provider.id}.`));
60
+ continue;
61
+ }
62
+ if (!inspection.supported) {
63
+ diagnostics.push(diagnostic('AS_DOCTOR_PROVIDER_UNSUPPORTED', 'warning', inspection.path, '$', `provider "${provider.id}" cannot project active imported skill "${skill.name}": ${inspection.reasons.join('; ')}`, `Use a supported provider surface or retain "${skill.name}" in the managed import store.`));
64
+ }
65
+ else if (!inspection.exists) {
66
+ diagnostics.push(diagnostic('AS_DOCTOR_PROVIDER_DEGRADED', 'warning', inspection.path, '$', `active imported skill "${skill.name}" is not projected to provider "${provider.id}"`, `Deploy or repair the ${provider.id} Agent Skills projection.`));
67
+ }
68
+ else if (!inspection.managed || !inspection.matches) {
69
+ diagnostics.push(diagnostic('AS_DOCTOR_DEPLOYED_DRIFT', 'error', inspection.path, '$', `provider "${provider.id}" projection for "${skill.name}" differs from its expected strict managed projection`, `Redeploy "${skill.name}" to ${provider.id} from the managed import.`));
70
+ }
71
+ }
72
+ }
73
+ const unique = [...new Map(diagnostics.map((item) => [
74
+ [
75
+ item.file,
76
+ item.code,
77
+ item.yamlPath,
78
+ item.message,
79
+ ].join('\0'),
80
+ item,
81
+ ])).values()].sort((left, right) => (left.file.localeCompare(right.file)
82
+ || left.code.localeCompare(right.code)
83
+ || left.yamlPath.localeCompare(right.yamlPath)
84
+ || left.message.localeCompare(right.message)));
85
+ const lines = ['\n── Agent Skills conformance ──'];
86
+ if (imported.length === 0 && unique.length === 0) {
87
+ lines.push(' ✓ no managed Agent Skills imports');
88
+ }
89
+ else if (unique.length === 0) {
90
+ lines.push(` ✓ ${imported.length} managed import(s) conform and have no detected drift`);
91
+ }
92
+ else {
93
+ for (const item of unique) {
94
+ const mark = item.severity === 'error' ? '✗' : '⚠';
95
+ lines.push(` ${mark} ${item.code} ${item.file}: ${item.message}`);
96
+ lines.push(` Fix: ${item.remediation}`);
97
+ }
98
+ }
99
+ return {
100
+ diagnostics: unique,
101
+ output: lines.join('\n'),
102
+ hasFailures: unique.some((item) => item.severity === 'error'),
103
+ };
104
+ }
105
+ //# sourceMappingURL=doctor.js.map