@aiwg/cli 2026.7.19 → 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.
- package/README.md +397 -385
- package/dist/src/api/index.d.ts +1 -0
- package/dist/src/api/index.js +1 -0
- package/dist/src/artifacts/browser-export.js +7 -0
- package/dist/src/artifacts/citation-parser.js +96 -35
- package/dist/src/artifacts/cli.js +59 -5
- package/dist/src/artifacts/discover-facets.js +15 -0
- package/dist/src/artifacts/discovery-eval.js +290 -0
- package/dist/src/artifacts/fortemi-core-query-adapter.js +1 -1
- package/dist/src/artifacts/fortemi-shard-export.js +1 -1
- package/dist/src/artifacts/index-builder.js +54 -17
- package/dist/src/artifacts/query-engine.js +10 -6
- package/dist/src/artifacts/state-transfer.js +27 -0
- package/dist/src/artifacts/stats.js +8 -0
- package/dist/src/cli/cli-extension-loader.js +73 -0
- package/dist/src/cli/handlers/help.js +2 -1
- package/dist/src/cli/handlers/index.js +6 -2
- package/dist/src/cli/handlers/resource-versions.js +247 -0
- package/dist/src/cli/handlers/sessions.js +966 -0
- package/dist/src/cli/handlers/skill-lint.js +49 -45
- package/dist/src/cli/handlers/subcommands.js +55 -3
- package/dist/src/cli/handlers/use.js +154 -59
- package/dist/src/cli/handlers/utilities.js +49 -34
- package/dist/src/cli/skill-usage.js +146 -24
- package/dist/src/config/cli.js +13 -9
- package/dist/src/config/project-artifacts-runtime.mjs +68 -0
- package/dist/src/config/project-artifacts.js +1 -68
- package/dist/src/extensions/commands/definitions.js +65 -2
- package/dist/src/extensions/manifest.js +29 -0
- package/dist/src/extensions/project-local-discovery.js +86 -2
- package/dist/src/extensions/project-local-remove.js +52 -56
- package/dist/src/extensions/shadow-resolver.js +3 -1
- package/dist/src/plugins/standalone-packager.js +143 -0
- package/dist/src/resources/cache-cleanup.js +67 -0
- package/dist/src/resources/doctor.js +107 -0
- package/dist/src/resources/lockfile.js +125 -0
- package/dist/src/resources/resolver.js +133 -0
- package/dist/src/resources/web-release.d.ts +8 -0
- package/dist/src/resources/web-release.js +159 -1
- package/dist/src/sessions/adapters/claude.js +357 -0
- package/dist/src/sessions/adapters/codex.js +521 -0
- package/dist/src/sessions/adapters/copilot.js +226 -0
- package/dist/src/sessions/adapters/cursor.js +372 -0
- package/dist/src/sessions/adapters/factory.js +345 -0
- package/dist/src/sessions/adapters/generic.js +225 -0
- package/dist/src/sessions/adapters/hermes.js +341 -0
- package/dist/src/sessions/adapters/openclaw.js +381 -0
- package/dist/src/sessions/adapters/opencode.js +454 -0
- package/dist/src/sessions/adapters/openhuman.js +315 -0
- package/dist/src/sessions/adapters/warp.js +160 -0
- package/dist/src/sessions/adapters/windsurf.js +212 -0
- package/dist/src/sessions/candidates.js +210 -0
- package/dist/src/sessions/contracts.js +310 -0
- package/dist/src/sessions/discovery.js +51 -0
- package/dist/src/sessions/fixtures.js +12 -0
- package/dist/src/sessions/importer.js +315 -0
- package/dist/src/sessions/index.js +25 -0
- package/dist/src/sessions/knowledge-shard.js +61 -0
- package/dist/src/sessions/optional-backends.js +238 -0
- package/dist/src/sessions/policy.js +192 -0
- package/dist/src/sessions/ports.js +2 -0
- package/dist/src/sessions/promotion.js +367 -0
- package/dist/src/sessions/readers.js +176 -0
- package/dist/src/sessions/repository.js +1551 -0
- package/dist/src/skills/adapters/agent-skills.js +59 -0
- package/dist/src/skills/adapters/local.js +19 -1
- package/dist/src/skills/agent-skills.js +249 -0
- package/dist/src/skills/cli.js +463 -7
- package/dist/src/skills/deployer.js +554 -0
- package/dist/src/skills/doctor.js +105 -0
- package/dist/src/skills/exporter.js +382 -0
- package/dist/src/skills/importer.js +921 -0
- package/dist/src/skills/registry.js +19 -0
- package/dist/src/skills/validator.js +323 -0
- package/dist/src/smiths/context-pipeline/aiwg-md.js +5 -1
- package/dist/src/smiths/context-pipeline/claude-hook.js +21 -1
- package/dist/src/smiths/context-pipeline/finalization.js +5 -3
- package/dist/src/smiths/context-pipeline/generator.js +4 -1
- package/dist/src/smiths/context-pipeline/parallelism-section.js +34 -1
- package/dist/src/smiths/context-pipeline/workspace-context.js +15 -3
- package/dist/src/smiths/mcpsmith/example.js +3 -1
- package/dist/src/smiths/mcpsmith/generator.js +3 -1
- package/dist/src/smiths/toolsmith/runtime-discovery.mjs +2 -1
- package/dist/src/storage/cli.js +3 -2
- package/dist/src/storage/subsystem-cli.js +7 -2
- package/dist/src/update/notifier.mjs +1 -1
- package/dist/src/update/service.mjs +123 -0
- package/package.json +3 -2
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strict Agent Skills export boundary.
|
|
3
|
+
*
|
|
4
|
+
* Canonical AIWG skills can carry provider and orchestration metadata. Export
|
|
5
|
+
* writes a portable SKILL.md projection plus a non-portable provenance sidecar
|
|
6
|
+
* so the omitted AIWG fields are auditable without entering standard
|
|
7
|
+
* frontmatter.
|
|
8
|
+
*
|
|
9
|
+
* @implements #1896
|
|
10
|
+
*/
|
|
11
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { stringify } from 'yaml';
|
|
16
|
+
import { AGENT_SKILLS_BASELINE, AIWG_SKILL_CONTROL_FIELDS, projectStrictAgentSkill, } from './agent-skills.js';
|
|
17
|
+
import { validateAgentSkillContent } from './validator.js';
|
|
18
|
+
export const AGENT_SKILL_EXPORT_SIDECAR = '.aiwg-agent-skill-export.json';
|
|
19
|
+
const STANDARD_FIELDS = [
|
|
20
|
+
'name',
|
|
21
|
+
'description',
|
|
22
|
+
'license',
|
|
23
|
+
'compatibility',
|
|
24
|
+
'metadata',
|
|
25
|
+
'allowed-tools',
|
|
26
|
+
];
|
|
27
|
+
const AIWG_FIELDS = new Set(AIWG_SKILL_CONTROL_FIELDS);
|
|
28
|
+
export class AgentSkillExportError extends Error {
|
|
29
|
+
code;
|
|
30
|
+
constructor(code, message) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.code = code;
|
|
33
|
+
this.name = 'AgentSkillExportError';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function fail(code, message) {
|
|
37
|
+
throw new AgentSkillExportError(code, message);
|
|
38
|
+
}
|
|
39
|
+
function pathContains(parent, child) {
|
|
40
|
+
const relative = path.relative(parent, child);
|
|
41
|
+
return relative === ''
|
|
42
|
+
|| (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
43
|
+
}
|
|
44
|
+
function detectAiwgVersion() {
|
|
45
|
+
let current = path.dirname(fileURLToPath(import.meta.url));
|
|
46
|
+
for (let depth = 0; depth < 6; depth += 1) {
|
|
47
|
+
const packagePath = path.join(current, 'package.json');
|
|
48
|
+
if (fs.existsSync(packagePath)) {
|
|
49
|
+
try {
|
|
50
|
+
const parsed = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
51
|
+
if (parsed.name === 'aiwg'
|
|
52
|
+
&& typeof parsed.version === 'string'
|
|
53
|
+
&& parsed.version.length > 0) {
|
|
54
|
+
return parsed.version;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Continue toward the package root.
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
current = path.dirname(current);
|
|
62
|
+
}
|
|
63
|
+
return 'unknown';
|
|
64
|
+
}
|
|
65
|
+
function resolveExportedAt(value) {
|
|
66
|
+
if (value === undefined)
|
|
67
|
+
return new Date().toISOString();
|
|
68
|
+
const parsed = new Date(value);
|
|
69
|
+
if (!Number.isFinite(parsed.valueOf()) || parsed.toISOString() !== value) {
|
|
70
|
+
fail('AS_EXPORT_TIME_INVALID', 'exportedAt must be an exact ISO-8601 timestamp');
|
|
71
|
+
}
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
function assertSafeSourceEntry(sourceRoot, absolutePath, relativePath) {
|
|
75
|
+
if (relativePath.length === 0
|
|
76
|
+
|| relativePath.includes('\\')
|
|
77
|
+
|| path.posix.isAbsolute(relativePath)
|
|
78
|
+
|| relativePath.split('/').some((segment) => segment === '.' || segment === '..')
|
|
79
|
+
|| /[\u0000-\u001f\u007f]/.test(relativePath)) {
|
|
80
|
+
fail('AS_EXPORT_PATH', `unsafe export source path "${relativePath}"`);
|
|
81
|
+
}
|
|
82
|
+
const stat = fs.lstatSync(absolutePath);
|
|
83
|
+
if (stat.isSymbolicLink()) {
|
|
84
|
+
fail('AS_EXPORT_SYMLINK', `source contains a symlink: ${relativePath}`);
|
|
85
|
+
}
|
|
86
|
+
if (!stat.isDirectory() && !stat.isFile()) {
|
|
87
|
+
fail('AS_EXPORT_SPECIAL_FILE', `source contains a non-regular entry: ${relativePath}`);
|
|
88
|
+
}
|
|
89
|
+
const realRoot = fs.realpathSync.native(sourceRoot);
|
|
90
|
+
const realEntry = fs.realpathSync.native(absolutePath);
|
|
91
|
+
if (!pathContains(realRoot, realEntry)) {
|
|
92
|
+
fail('AS_EXPORT_ESCAPE', `source entry escapes its root: ${relativePath}`);
|
|
93
|
+
}
|
|
94
|
+
return stat;
|
|
95
|
+
}
|
|
96
|
+
function collectSourceEntries(sourceRoot) {
|
|
97
|
+
const resolved = path.resolve(sourceRoot);
|
|
98
|
+
const rootStat = fs.lstatSync(resolved);
|
|
99
|
+
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
|
|
100
|
+
fail('AS_EXPORT_SOURCE_TYPE', 'export source must be a real skill directory');
|
|
101
|
+
}
|
|
102
|
+
const entries = [];
|
|
103
|
+
const walk = (current, prefix) => {
|
|
104
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })
|
|
105
|
+
.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
106
|
+
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
107
|
+
const absolutePath = path.join(current, entry.name);
|
|
108
|
+
const stat = assertSafeSourceEntry(resolved, absolutePath, relativePath);
|
|
109
|
+
if (stat.isDirectory()) {
|
|
110
|
+
entries.push({ kind: 'directory', relativePath });
|
|
111
|
+
walk(absolutePath, relativePath);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
entries.push({
|
|
115
|
+
kind: 'file',
|
|
116
|
+
relativePath,
|
|
117
|
+
bytes: fs.readFileSync(absolutePath),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
walk(resolved, '');
|
|
123
|
+
return entries;
|
|
124
|
+
}
|
|
125
|
+
function treeDigest(entries, prefix) {
|
|
126
|
+
const hash = createHash('sha256');
|
|
127
|
+
hash.update(prefix);
|
|
128
|
+
hash.update('\0');
|
|
129
|
+
for (const entry of [...entries].sort((left, right) => (left.relativePath.localeCompare(right.relativePath)
|
|
130
|
+
|| left.kind.localeCompare(right.kind)))) {
|
|
131
|
+
hash.update(entry.kind);
|
|
132
|
+
hash.update('\0');
|
|
133
|
+
hash.update(entry.relativePath);
|
|
134
|
+
hash.update('\0');
|
|
135
|
+
if (entry.kind === 'file') {
|
|
136
|
+
hash.update(String(entry.bytes.length));
|
|
137
|
+
hash.update('\0');
|
|
138
|
+
hash.update(entry.bytes);
|
|
139
|
+
}
|
|
140
|
+
hash.update('\0');
|
|
141
|
+
}
|
|
142
|
+
return hash.digest('hex');
|
|
143
|
+
}
|
|
144
|
+
function parseDocument(sourceRoot, entries) {
|
|
145
|
+
const skillFile = entries.find((entry) => (entry.kind === 'file' && entry.relativePath === 'SKILL.md'));
|
|
146
|
+
if (!skillFile) {
|
|
147
|
+
fail('AS_EXPORT_SKILL_MISSING', 'export source must contain SKILL.md');
|
|
148
|
+
}
|
|
149
|
+
const content = new TextDecoder('utf-8', { fatal: true }).decode(skillFile.bytes);
|
|
150
|
+
const validation = validateAgentSkillContent(content, {
|
|
151
|
+
profile: 'compatible',
|
|
152
|
+
file: path.join(sourceRoot, 'SKILL.md'),
|
|
153
|
+
directoryName: path.basename(sourceRoot),
|
|
154
|
+
skillRoot: sourceRoot,
|
|
155
|
+
checkResources: true,
|
|
156
|
+
});
|
|
157
|
+
const errors = validation.diagnostics.filter((item) => item.severity === 'error');
|
|
158
|
+
if (!validation.frontmatter || errors.length > 0) {
|
|
159
|
+
fail('AS_EXPORT_VALIDATION', `source skill is not Agent Skills compatible: ${errors
|
|
160
|
+
.map((item) => item.code)
|
|
161
|
+
.join(', ')}`);
|
|
162
|
+
}
|
|
163
|
+
const standard = {};
|
|
164
|
+
for (const key of STANDARD_FIELDS) {
|
|
165
|
+
const value = validation.frontmatter[key];
|
|
166
|
+
if (value !== undefined)
|
|
167
|
+
Object.assign(standard, { [key]: structuredClone(value) });
|
|
168
|
+
}
|
|
169
|
+
const aiwg = {};
|
|
170
|
+
for (const [key, value] of Object.entries(validation.frontmatter)) {
|
|
171
|
+
if (AIWG_FIELDS.has(key)) {
|
|
172
|
+
aiwg[key] = structuredClone(value);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
document: {
|
|
177
|
+
standard: standard,
|
|
178
|
+
body: validation.body,
|
|
179
|
+
resources: [],
|
|
180
|
+
aiwg,
|
|
181
|
+
unknownFields: [],
|
|
182
|
+
},
|
|
183
|
+
description: validation.frontmatter.description,
|
|
184
|
+
omittedAiwgFields: Object.keys(aiwg).sort(),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function strictSkillEntry(sourceRoot, document) {
|
|
188
|
+
const frontmatter = stringify(projectStrictAgentSkill(document), {
|
|
189
|
+
lineWidth: 0,
|
|
190
|
+
sortMapEntries: false,
|
|
191
|
+
}).trimEnd();
|
|
192
|
+
const content = `---\n${frontmatter}\n---\n${document.body}`;
|
|
193
|
+
const validation = validateAgentSkillContent(content, {
|
|
194
|
+
profile: 'strict',
|
|
195
|
+
file: 'SKILL.md',
|
|
196
|
+
directoryName: document.standard.name,
|
|
197
|
+
skillRoot: sourceRoot,
|
|
198
|
+
checkResources: true,
|
|
199
|
+
});
|
|
200
|
+
const errors = validation.diagnostics.filter((item) => item.severity === 'error');
|
|
201
|
+
if (errors.length > 0) {
|
|
202
|
+
fail('AS_EXPORT_STRICT_VALIDATION', `strict export failed validation: ${errors.map((item) => item.code).join(', ')}`);
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
kind: 'file',
|
|
206
|
+
relativePath: 'SKILL.md',
|
|
207
|
+
bytes: Buffer.from(content, 'utf8'),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function sidecarEntry(result) {
|
|
211
|
+
return {
|
|
212
|
+
kind: 'file',
|
|
213
|
+
relativePath: AGENT_SKILL_EXPORT_SIDECAR,
|
|
214
|
+
bytes: Buffer.from(`${JSON.stringify({
|
|
215
|
+
schemaVersion: 1,
|
|
216
|
+
kind: 'aiwg-agent-skill-export',
|
|
217
|
+
upstreamBaseline: AGENT_SKILLS_BASELINE,
|
|
218
|
+
...result,
|
|
219
|
+
}, null, 2)}\n`, 'utf8'),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function outputMatches(outputPath, desired) {
|
|
223
|
+
if (!fs.existsSync(outputPath) || !fs.lstatSync(outputPath).isDirectory()) {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
const actual = new Map();
|
|
227
|
+
const walk = (current, prefix) => {
|
|
228
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })
|
|
229
|
+
.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
230
|
+
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
231
|
+
const absolutePath = path.join(current, entry.name);
|
|
232
|
+
const stat = fs.lstatSync(absolutePath);
|
|
233
|
+
if (stat.isSymbolicLink())
|
|
234
|
+
return false;
|
|
235
|
+
if (stat.isDirectory()) {
|
|
236
|
+
actual.set(relativePath, 'directory');
|
|
237
|
+
if (!walk(absolutePath, relativePath))
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
else if (stat.isFile()) {
|
|
241
|
+
actual.set(relativePath, fs.readFileSync(absolutePath));
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return true;
|
|
248
|
+
};
|
|
249
|
+
if (!walk(outputPath, ''))
|
|
250
|
+
return false;
|
|
251
|
+
if (actual.size !== desired.length)
|
|
252
|
+
return false;
|
|
253
|
+
return desired.every((entry) => {
|
|
254
|
+
const value = actual.get(entry.relativePath);
|
|
255
|
+
return entry.kind === 'directory'
|
|
256
|
+
? value === 'directory'
|
|
257
|
+
: Buffer.isBuffer(value) && value.equals(entry.bytes);
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
function writeDesiredTree(root, desired) {
|
|
261
|
+
fs.mkdirSync(root, { recursive: false, mode: 0o700 });
|
|
262
|
+
for (const entry of desired
|
|
263
|
+
.filter((item) => item.kind === 'directory')
|
|
264
|
+
.sort((left, right) => (left.relativePath.split('/').length - right.relativePath.split('/').length
|
|
265
|
+
|| left.relativePath.localeCompare(right.relativePath)))) {
|
|
266
|
+
fs.mkdirSync(path.join(root, ...entry.relativePath.split('/')), {
|
|
267
|
+
recursive: true,
|
|
268
|
+
mode: 0o700,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
for (const entry of desired
|
|
272
|
+
.filter((item) => item.kind === 'file')
|
|
273
|
+
.sort((left, right) => left.relativePath.localeCompare(right.relativePath))) {
|
|
274
|
+
const target = path.join(root, ...entry.relativePath.split('/'));
|
|
275
|
+
fs.mkdirSync(path.dirname(target), { recursive: true, mode: 0o700 });
|
|
276
|
+
fs.writeFileSync(target, entry.bytes, { flag: 'wx', mode: 0o600 });
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function promoteAtomically(outputPath, desired) {
|
|
280
|
+
const parent = path.dirname(outputPath);
|
|
281
|
+
fs.mkdirSync(parent, { recursive: true, mode: 0o700 });
|
|
282
|
+
const suffix = randomUUID();
|
|
283
|
+
const staging = path.join(parent, `.${path.basename(outputPath)}.export-staging-${suffix}`);
|
|
284
|
+
const backup = path.join(parent, `.${path.basename(outputPath)}.export-backup-${suffix}`);
|
|
285
|
+
const existed = fs.existsSync(outputPath);
|
|
286
|
+
try {
|
|
287
|
+
writeDesiredTree(staging, desired);
|
|
288
|
+
if (existed)
|
|
289
|
+
fs.renameSync(outputPath, backup);
|
|
290
|
+
try {
|
|
291
|
+
fs.renameSync(staging, outputPath);
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
if (fs.existsSync(outputPath))
|
|
295
|
+
fs.rmSync(outputPath, { recursive: true, force: true });
|
|
296
|
+
if (fs.existsSync(backup))
|
|
297
|
+
fs.renameSync(backup, outputPath);
|
|
298
|
+
throw error;
|
|
299
|
+
}
|
|
300
|
+
if (fs.existsSync(backup))
|
|
301
|
+
fs.rmSync(backup, { recursive: true, force: true });
|
|
302
|
+
return existed ? 'updated' : 'exported';
|
|
303
|
+
}
|
|
304
|
+
finally {
|
|
305
|
+
if (fs.existsSync(staging))
|
|
306
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
307
|
+
if (fs.existsSync(backup) && !fs.existsSync(outputPath)) {
|
|
308
|
+
fs.renameSync(backup, outputPath);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
export function exportAgentSkillDirectory(sourceDir, options) {
|
|
313
|
+
const resolvedSource = path.resolve(sourceDir);
|
|
314
|
+
const exportedAt = resolveExportedAt(options.exportedAt);
|
|
315
|
+
const aiwgVersion = options.aiwgVersion ?? detectAiwgVersion();
|
|
316
|
+
const sourceEntries = collectSourceEntries(resolvedSource);
|
|
317
|
+
const { document, description, omittedAiwgFields } = parseDocument(resolvedSource, sourceEntries);
|
|
318
|
+
const outputPath = path.resolve(options.outDir, document.standard.name);
|
|
319
|
+
const sourceDigest = treeDigest(sourceEntries, 'aiwg-agent-skill-export-source-v1');
|
|
320
|
+
const portableEntries = sourceEntries
|
|
321
|
+
.filter((entry) => (entry.relativePath !== 'SKILL.md'
|
|
322
|
+
&& entry.relativePath !== AGENT_SKILL_EXPORT_SIDECAR))
|
|
323
|
+
.map((entry) => (entry.kind === 'directory'
|
|
324
|
+
? { ...entry }
|
|
325
|
+
: { ...entry, bytes: Buffer.from(entry.bytes) }));
|
|
326
|
+
portableEntries.push(strictSkillEntry(resolvedSource, document));
|
|
327
|
+
const preliminary = {
|
|
328
|
+
name: document.standard.name,
|
|
329
|
+
description,
|
|
330
|
+
sourcePath: resolvedSource,
|
|
331
|
+
outputPath,
|
|
332
|
+
sourceDigest,
|
|
333
|
+
exportDigest: '',
|
|
334
|
+
omittedAiwgFields,
|
|
335
|
+
fileCount: 0,
|
|
336
|
+
totalBytes: 0,
|
|
337
|
+
exportedAt,
|
|
338
|
+
aiwgVersion,
|
|
339
|
+
};
|
|
340
|
+
const exportDigest = treeDigest(portableEntries, 'aiwg-agent-skill-export-v1');
|
|
341
|
+
const desiredEntries = [
|
|
342
|
+
...portableEntries,
|
|
343
|
+
sidecarEntry({
|
|
344
|
+
...preliminary,
|
|
345
|
+
exportDigest,
|
|
346
|
+
fileCount: portableEntries.filter((entry) => entry.kind === 'file').length,
|
|
347
|
+
totalBytes: portableEntries.reduce((total, entry) => total + (entry.kind === 'file' ? entry.bytes.length : 0), 0),
|
|
348
|
+
}),
|
|
349
|
+
].sort((left, right) => (left.relativePath.localeCompare(right.relativePath)
|
|
350
|
+
|| left.kind.localeCompare(right.kind)));
|
|
351
|
+
const fileCount = desiredEntries.filter((entry) => entry.kind === 'file').length;
|
|
352
|
+
const totalBytes = desiredEntries.reduce((total, entry) => total + (entry.kind === 'file' ? entry.bytes.length : 0), 0);
|
|
353
|
+
const resultBase = {
|
|
354
|
+
schemaVersion: 1,
|
|
355
|
+
dryRun: Boolean(options.dryRun),
|
|
356
|
+
name: document.standard.name,
|
|
357
|
+
description,
|
|
358
|
+
sourcePath: resolvedSource,
|
|
359
|
+
outputPath,
|
|
360
|
+
sourceDigest,
|
|
361
|
+
exportDigest,
|
|
362
|
+
omittedAiwgFields,
|
|
363
|
+
fileCount,
|
|
364
|
+
totalBytes,
|
|
365
|
+
exportedAt,
|
|
366
|
+
aiwgVersion,
|
|
367
|
+
};
|
|
368
|
+
if (outputMatches(outputPath, desiredEntries)) {
|
|
369
|
+
return { ...resultBase, status: 'unchanged' };
|
|
370
|
+
}
|
|
371
|
+
if (fs.existsSync(outputPath) && !options.force) {
|
|
372
|
+
fail('AS_EXPORT_COLLISION', `export target already exists at ${outputPath}; use --force after review`);
|
|
373
|
+
}
|
|
374
|
+
if (options.dryRun) {
|
|
375
|
+
return { ...resultBase, status: 'planned' };
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
...resultBase,
|
|
379
|
+
status: promoteAtomically(outputPath, desiredEntries),
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
//# sourceMappingURL=exporter.js.map
|