@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
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @implements #539
|
|
8
8
|
*/
|
|
9
|
+
import { AgentSkillsAdapter } from './adapters/agent-skills.js';
|
|
9
10
|
import { LocalAdapter } from './adapters/local.js';
|
|
10
11
|
import { ClawHubAdapter } from './adapters/clawhub.js';
|
|
11
12
|
import { OpenClawAdapter } from './adapters/openclaw.js';
|
|
@@ -14,9 +15,27 @@ import { OpenClawAdapter } from './adapters/openclaw.js';
|
|
|
14
15
|
*/
|
|
15
16
|
const ALL_ADAPTERS = [
|
|
16
17
|
new LocalAdapter(),
|
|
18
|
+
new AgentSkillsAdapter(),
|
|
17
19
|
new ClawHubAdapter(),
|
|
18
20
|
new OpenClawAdapter(),
|
|
19
21
|
];
|
|
22
|
+
/**
|
|
23
|
+
* Import a standard Agent Skills directory or pinned Git source.
|
|
24
|
+
*/
|
|
25
|
+
export async function importSkillSource(source, options, providerId = 'agentskills') {
|
|
26
|
+
const adapter = getAdapter(providerId);
|
|
27
|
+
if (!adapter) {
|
|
28
|
+
throw new Error(`Unknown registry: ${providerId}`);
|
|
29
|
+
}
|
|
30
|
+
if (!adapter.importSource) {
|
|
31
|
+
throw new Error(`Registry '${providerId}' does not support source import`);
|
|
32
|
+
}
|
|
33
|
+
const available = await adapter.isAvailable();
|
|
34
|
+
if (!available) {
|
|
35
|
+
throw new Error(`Registry '${providerId}' is not available`);
|
|
36
|
+
}
|
|
37
|
+
return adapter.importSource(source, options);
|
|
38
|
+
}
|
|
20
39
|
/**
|
|
21
40
|
* Get adapter by ID
|
|
22
41
|
*/
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Agent Skills parser and conformance validator.
|
|
3
|
+
*
|
|
4
|
+
* This module is the only place that parses SKILL.md for Agent Skills
|
|
5
|
+
* conformance. Consumers may add their own quality or deployment policy, but
|
|
6
|
+
* must retain these diagnostics unchanged.
|
|
7
|
+
*
|
|
8
|
+
* @implements #1878
|
|
9
|
+
*/
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { parseDocument } from 'yaml';
|
|
13
|
+
import { AGENT_SKILLS_BASELINE, AGENT_SKILL_VALIDATION_PROFILES, AIWG_SKILL_CONTROL_FIELDS, STANDARD_SKILL_FIELDS, validateCompatibleAgentSkillMetadata, } from './agent-skills.js';
|
|
14
|
+
const FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
|
|
15
|
+
const MARKDOWN_LINK = /!?\[[^\]]*]\(\s*(?:<([^>]+)>|([^\s)]+))(?:\s+["'][^)]*["'])?\s*\)/g;
|
|
16
|
+
const STANDARD_FIELDS = new Set(STANDARD_SKILL_FIELDS);
|
|
17
|
+
const AIWG_FIELDS = new Set(AIWG_SKILL_CONTROL_FIELDS);
|
|
18
|
+
function diagnostic(code, severity, file, yamlPath, message, remediation) {
|
|
19
|
+
return {
|
|
20
|
+
code,
|
|
21
|
+
severity,
|
|
22
|
+
file,
|
|
23
|
+
yamlPath,
|
|
24
|
+
message,
|
|
25
|
+
upstreamBaseline: AGENT_SKILLS_BASELINE.revision,
|
|
26
|
+
remediation,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function sortDiagnostics(diagnostics) {
|
|
30
|
+
return diagnostics.sort((left, right) => (left.file.localeCompare(right.file)
|
|
31
|
+
|| left.code.localeCompare(right.code)
|
|
32
|
+
|| left.yamlPath.localeCompare(right.yamlPath)
|
|
33
|
+
|| left.message.localeCompare(right.message)));
|
|
34
|
+
}
|
|
35
|
+
function normalizeMetadataDiagnostics(diagnostics, frontmatter, profile, file) {
|
|
36
|
+
const policy = AGENT_SKILL_VALIDATION_PROFILES[profile];
|
|
37
|
+
const normalized = diagnostics.flatMap((item) => {
|
|
38
|
+
if (item.code === 'AS_FIELD_UNKNOWN') {
|
|
39
|
+
return [{
|
|
40
|
+
...item,
|
|
41
|
+
severity: policy.unknownField,
|
|
42
|
+
}];
|
|
43
|
+
}
|
|
44
|
+
if (profile === 'discovery'
|
|
45
|
+
&& (item.code === 'AS_NAME_FORMAT' || item.code === 'AS_NAME_DIRECTORY')) {
|
|
46
|
+
return [{ ...item, severity: policy.cosmeticNameDefect }];
|
|
47
|
+
}
|
|
48
|
+
return [item];
|
|
49
|
+
});
|
|
50
|
+
if (!policy.recognizedAiwgFields) {
|
|
51
|
+
for (const key of Object.keys(frontmatter).sort()) {
|
|
52
|
+
if (!AIWG_FIELDS.has(key))
|
|
53
|
+
continue;
|
|
54
|
+
normalized.push(diagnostic('AS_FIELD_EXTENSION', 'error', file, `$.${key}`, `AIWG extension field "${key}" is not allowed by the strict profile`, 'Remove the extension field or validate with the compatible profile.'));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return normalized;
|
|
58
|
+
}
|
|
59
|
+
function resourceDiagnostics(content, file, skillRoot, checkResources) {
|
|
60
|
+
const diagnostics = [];
|
|
61
|
+
const references = new Set();
|
|
62
|
+
for (const match of content.matchAll(MARKDOWN_LINK)) {
|
|
63
|
+
const raw = (match[1] ?? match[2] ?? '').trim();
|
|
64
|
+
if (raw.length === 0
|
|
65
|
+
|| raw.startsWith('#')
|
|
66
|
+
|| /^(?:https?|mailto|data):/i.test(raw)) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const withoutFragment = raw.split('#', 1)[0] ?? '';
|
|
70
|
+
const decoded = (() => {
|
|
71
|
+
try {
|
|
72
|
+
return decodeURIComponent(withoutFragment);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return withoutFragment;
|
|
76
|
+
}
|
|
77
|
+
})();
|
|
78
|
+
references.add(decoded);
|
|
79
|
+
}
|
|
80
|
+
for (const reference of [...references].sort()) {
|
|
81
|
+
const normalized = reference.replaceAll('\\', '/');
|
|
82
|
+
if (path.isAbsolute(reference)
|
|
83
|
+
|| normalized === '..'
|
|
84
|
+
|| normalized.startsWith('../')
|
|
85
|
+
|| normalized.includes('/../')) {
|
|
86
|
+
diagnostics.push(diagnostic('AS_RESOURCE_PATH', 'warning', file, '$.body', `resource reference "${reference}" is not an in-skill relative path`, 'Reference a file relative to SKILL.md without parent traversal.'));
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const segments = normalized.split('/').filter(Boolean);
|
|
90
|
+
if (segments.length > 2) {
|
|
91
|
+
diagnostics.push(diagnostic('AS_ADVISORY_RESOURCE_DEPTH', 'warning', file, '$.body', `resource reference "${reference}" is deeper than one resource-directory level`, 'Prefer resources such as references/topic.md, scripts/run.sh, or assets/example.json.'));
|
|
92
|
+
}
|
|
93
|
+
if (!checkResources || !skillRoot)
|
|
94
|
+
continue;
|
|
95
|
+
const resolved = path.resolve(skillRoot, reference);
|
|
96
|
+
const relative = path.relative(skillRoot, resolved);
|
|
97
|
+
if (relative.startsWith('..')
|
|
98
|
+
|| path.isAbsolute(relative)
|
|
99
|
+
|| !fs.existsSync(resolved)) {
|
|
100
|
+
diagnostics.push(diagnostic('AS_RESOURCE_MISSING', 'warning', file, '$.body', `referenced resource "${reference}" does not exist`, 'Add the referenced file or correct the relative resource path.'));
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const stat = fs.lstatSync(resolved);
|
|
104
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
105
|
+
diagnostics.push(diagnostic('AS_RESOURCE_TYPE', 'warning', file, '$.body', `referenced resource "${reference}" is not a regular file`, 'Replace the reference target with an in-tree regular file.'));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return diagnostics;
|
|
109
|
+
}
|
|
110
|
+
function resultState(diagnostics, profile) {
|
|
111
|
+
const errors = diagnostics.filter((item) => item.severity === 'error');
|
|
112
|
+
if (errors.length > 0) {
|
|
113
|
+
if (profile === 'discovery'
|
|
114
|
+
&& errors.some((item) => (item.code === 'AS_DESCRIPTION_REQUIRED'
|
|
115
|
+
|| item.code === 'AS_FRONTMATTER_REQUIRED'
|
|
116
|
+
|| item.code === 'AS_YAML_PARSE'
|
|
117
|
+
|| item.code === 'AS_YAML_TYPE'))) {
|
|
118
|
+
return 'skipped';
|
|
119
|
+
}
|
|
120
|
+
return 'invalid';
|
|
121
|
+
}
|
|
122
|
+
return diagnostics.some((item) => item.severity === 'warning')
|
|
123
|
+
? 'warning'
|
|
124
|
+
: 'valid';
|
|
125
|
+
}
|
|
126
|
+
export function validateAgentSkillContent(content, options = {}) {
|
|
127
|
+
const profile = options.profile ?? 'compatible';
|
|
128
|
+
const file = options.file ?? 'SKILL.md';
|
|
129
|
+
const directoryName = options.directoryName
|
|
130
|
+
?? (options.skillRoot ? path.basename(options.skillRoot) : undefined)
|
|
131
|
+
?? (path.dirname(file) !== '.' ? path.basename(path.dirname(file)) : undefined);
|
|
132
|
+
const lines = content.split(/\r?\n/).length;
|
|
133
|
+
const metrics = {
|
|
134
|
+
lines,
|
|
135
|
+
// The upstream guidance is advisory. A deterministic UTF-16/4 estimate
|
|
136
|
+
// avoids a runtime tokenizer dependency while keeping CI snapshots stable.
|
|
137
|
+
estimatedTokens: Math.ceil(content.length / 4),
|
|
138
|
+
};
|
|
139
|
+
const diagnostics = [];
|
|
140
|
+
const match = FRONTMATTER.exec(content);
|
|
141
|
+
if (!match) {
|
|
142
|
+
diagnostics.push(diagnostic('AS_FRONTMATTER_REQUIRED', 'error', file, '$', 'SKILL.md must begin with YAML frontmatter', 'Add a leading YAML mapping delimited by `---` lines.'));
|
|
143
|
+
const state = resultState(diagnostics, profile);
|
|
144
|
+
return {
|
|
145
|
+
schemaVersion: 1,
|
|
146
|
+
profile,
|
|
147
|
+
file,
|
|
148
|
+
state,
|
|
149
|
+
valid: false,
|
|
150
|
+
discoverable: false,
|
|
151
|
+
body: content,
|
|
152
|
+
diagnostics,
|
|
153
|
+
metrics,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const document = parseDocument(match[1] ?? '', {
|
|
157
|
+
prettyErrors: false,
|
|
158
|
+
strict: true,
|
|
159
|
+
uniqueKeys: true,
|
|
160
|
+
});
|
|
161
|
+
if (document.errors.length > 0) {
|
|
162
|
+
for (const error of document.errors) {
|
|
163
|
+
diagnostics.push(diagnostic('AS_YAML_PARSE', 'error', file, '$', `SKILL.md frontmatter is invalid YAML: ${error.message}`, 'Correct the YAML syntax before validation or discovery.'));
|
|
164
|
+
}
|
|
165
|
+
const state = resultState(diagnostics, profile);
|
|
166
|
+
return {
|
|
167
|
+
schemaVersion: 1,
|
|
168
|
+
profile,
|
|
169
|
+
file,
|
|
170
|
+
state,
|
|
171
|
+
valid: false,
|
|
172
|
+
discoverable: false,
|
|
173
|
+
body: content.slice(match[0].length),
|
|
174
|
+
diagnostics: sortDiagnostics(diagnostics),
|
|
175
|
+
metrics,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
const parsed = document.toJS({ maxAliasCount: 100 });
|
|
179
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
180
|
+
diagnostics.push(diagnostic('AS_YAML_TYPE', 'error', file, '$', 'SKILL.md frontmatter must be a YAML mapping', 'Use top-level key/value fields.'));
|
|
181
|
+
const state = resultState(diagnostics, profile);
|
|
182
|
+
return {
|
|
183
|
+
schemaVersion: 1,
|
|
184
|
+
profile,
|
|
185
|
+
file,
|
|
186
|
+
state,
|
|
187
|
+
valid: false,
|
|
188
|
+
discoverable: false,
|
|
189
|
+
body: content.slice(match[0].length),
|
|
190
|
+
diagnostics,
|
|
191
|
+
metrics,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
const frontmatter = parsed;
|
|
195
|
+
diagnostics.push(...normalizeMetadataDiagnostics(validateCompatibleAgentSkillMetadata(frontmatter, directoryName
|
|
196
|
+
?? (typeof frontmatter['name'] === 'string' ? frontmatter['name'] : ''), file, lines), frontmatter, profile, file));
|
|
197
|
+
if (Object.prototype.hasOwnProperty.call(frontmatter, 'allowed-tools')) {
|
|
198
|
+
const value = frontmatter['allowed-tools'];
|
|
199
|
+
if (typeof value === 'string'
|
|
200
|
+
&& (value.trim() !== value || value.length === 0 || /\s{2,}|\t|\r|\n/.test(value))) {
|
|
201
|
+
diagnostics.push(diagnostic('AS_ALLOWED_TOOLS_FORMAT', 'error', file, '$.allowed-tools', 'allowed-tools must contain non-empty tool identifiers separated by single spaces', 'Normalize allowed-tools to a single space-delimited string.'));
|
|
202
|
+
}
|
|
203
|
+
diagnostics.push(diagnostic('AS_ALLOWED_TOOLS_EXPERIMENTAL', 'warning', file, '$.allowed-tools', 'allowed-tools is experimental in the pinned Agent Skills baseline', 'Treat allowed-tools as advisory unless the target provider documents enforcement.'));
|
|
204
|
+
}
|
|
205
|
+
const body = content.slice(match[0].length);
|
|
206
|
+
if (body.trim().length === 0) {
|
|
207
|
+
diagnostics.push(diagnostic('AS_BODY_REQUIRED', 'error', file, '$.body', 'SKILL.md must contain Markdown instructions after frontmatter', 'Add the skill instructions below the closing frontmatter delimiter.'));
|
|
208
|
+
}
|
|
209
|
+
if (metrics.estimatedTokens > 5_000) {
|
|
210
|
+
diagnostics.push(diagnostic('AS_ADVISORY_TOKENS', 'warning', file, '$', `SKILL.md is approximately ${metrics.estimatedTokens} tokens; the recommendation is at most 5,000`, 'Move detailed material to referenced resources.'));
|
|
211
|
+
}
|
|
212
|
+
diagnostics.push(...resourceDiagnostics(body, file, options.skillRoot, options.checkResources ?? Boolean(options.skillRoot)));
|
|
213
|
+
// Defensive policy check: a future field added to one allow-list must not
|
|
214
|
+
// silently escape classification in this parser.
|
|
215
|
+
for (const key of Object.keys(frontmatter).sort()) {
|
|
216
|
+
if (STANDARD_FIELDS.has(key) || AIWG_FIELDS.has(key))
|
|
217
|
+
continue;
|
|
218
|
+
if (!diagnostics.some((item) => item.yamlPath === `$.${key}`)) {
|
|
219
|
+
diagnostics.push(diagnostic('AS_FIELD_UNKNOWN', AGENT_SKILL_VALIDATION_PROFILES[profile].unknownField, file, `$.${key}`, `unrecognized top-level field "${key}"`, 'Remove the field or map it explicitly before granting it policy meaning.'));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
sortDiagnostics(diagnostics);
|
|
223
|
+
const state = resultState(diagnostics, profile);
|
|
224
|
+
return {
|
|
225
|
+
schemaVersion: 1,
|
|
226
|
+
profile,
|
|
227
|
+
file,
|
|
228
|
+
state,
|
|
229
|
+
valid: state === 'valid' || state === 'warning',
|
|
230
|
+
discoverable: state !== 'skipped',
|
|
231
|
+
frontmatter,
|
|
232
|
+
body,
|
|
233
|
+
diagnostics,
|
|
234
|
+
metrics,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
export function validateAgentSkillFile(filePath, options = {}) {
|
|
238
|
+
const resolved = path.resolve(filePath);
|
|
239
|
+
const stat = fs.lstatSync(resolved);
|
|
240
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
241
|
+
const content = '';
|
|
242
|
+
const result = validateAgentSkillContent(content, {
|
|
243
|
+
...options,
|
|
244
|
+
file: resolved,
|
|
245
|
+
skillRoot: path.dirname(resolved),
|
|
246
|
+
});
|
|
247
|
+
result.diagnostics = [diagnostic('AS_SKILL_FILE_TYPE', 'error', resolved, '$', 'SKILL.md must be a regular file, not a symbolic link or special file', 'Replace SKILL.md with an in-tree regular file.')];
|
|
248
|
+
result.state = options.profile === 'discovery' ? 'skipped' : 'invalid';
|
|
249
|
+
result.valid = false;
|
|
250
|
+
result.discoverable = false;
|
|
251
|
+
return result;
|
|
252
|
+
}
|
|
253
|
+
const result = validateAgentSkillContent(fs.readFileSync(resolved, 'utf8'), {
|
|
254
|
+
...options,
|
|
255
|
+
file: resolved,
|
|
256
|
+
directoryName: options.directoryName ?? path.basename(path.dirname(resolved)),
|
|
257
|
+
skillRoot: path.dirname(resolved),
|
|
258
|
+
checkResources: true,
|
|
259
|
+
});
|
|
260
|
+
if (path.basename(resolved) !== 'SKILL.md') {
|
|
261
|
+
result.diagnostics.push(diagnostic('AS_SKILL_FILENAME', 'error', resolved, '$', 'the skill entrypoint must be named SKILL.md', 'Rename the entrypoint to SKILL.md.'));
|
|
262
|
+
sortDiagnostics(result.diagnostics);
|
|
263
|
+
result.state = resultState(result.diagnostics, result.profile);
|
|
264
|
+
result.valid = false;
|
|
265
|
+
}
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
function collectSkillFiles(targetPath, recursive, files) {
|
|
269
|
+
const resolved = path.resolve(targetPath);
|
|
270
|
+
if (!fs.existsSync(resolved))
|
|
271
|
+
return;
|
|
272
|
+
const stat = fs.lstatSync(resolved);
|
|
273
|
+
if (stat.isSymbolicLink())
|
|
274
|
+
return;
|
|
275
|
+
if (stat.isFile()) {
|
|
276
|
+
if (path.basename(resolved) === 'SKILL.md')
|
|
277
|
+
files.add(resolved);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (!stat.isDirectory())
|
|
281
|
+
return;
|
|
282
|
+
const direct = path.join(resolved, 'SKILL.md');
|
|
283
|
+
if (fs.existsSync(direct) && !fs.lstatSync(direct).isSymbolicLink()) {
|
|
284
|
+
files.add(direct);
|
|
285
|
+
}
|
|
286
|
+
if (!recursive)
|
|
287
|
+
return;
|
|
288
|
+
for (const entry of fs.readdirSync(resolved, { withFileTypes: true })
|
|
289
|
+
.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
290
|
+
if (!entry.isDirectory()
|
|
291
|
+
|| entry.isSymbolicLink()
|
|
292
|
+
|| entry.name.startsWith('.')
|
|
293
|
+
|| entry.name === 'node_modules') {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
collectSkillFiles(path.join(resolved, entry.name), true, files);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
export function scanAgentSkillPaths(targetPaths, options = {}) {
|
|
300
|
+
const profile = options.profile ?? 'compatible';
|
|
301
|
+
const files = new Set();
|
|
302
|
+
for (const target of typeof targetPaths === 'string' ? [targetPaths] : targetPaths) {
|
|
303
|
+
collectSkillFiles(target, options.recursive ?? true, files);
|
|
304
|
+
}
|
|
305
|
+
const results = [...files]
|
|
306
|
+
.sort((left, right) => left.localeCompare(right))
|
|
307
|
+
.map((file) => validateAgentSkillFile(file, { profile }));
|
|
308
|
+
return {
|
|
309
|
+
schemaVersion: 1,
|
|
310
|
+
profile,
|
|
311
|
+
files: results,
|
|
312
|
+
summary: {
|
|
313
|
+
scanned: results.length,
|
|
314
|
+
valid: results.filter((result) => result.state === 'valid').length,
|
|
315
|
+
warnings: results.filter((result) => result.state === 'warning').length,
|
|
316
|
+
invalid: results.filter((result) => result.state === 'invalid').length,
|
|
317
|
+
skipped: results.filter((result) => result.state === 'skipped').length,
|
|
318
|
+
errors: results.reduce((count, result) => count
|
|
319
|
+
+ result.diagnostics.filter((item) => item.severity === 'error').length, 0),
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
//# sourceMappingURL=validator.js.map
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { promises as fs } from 'node:fs';
|
|
18
18
|
import * as path from 'node:path';
|
|
19
|
+
import { projectAiwgPath } from '../../config/project-artifacts.js';
|
|
19
20
|
import { buildParallelismSection, replaceOrAppendParallelismBlock } from './parallelism-section.js';
|
|
20
21
|
import { buildContextFinalizationBlock, replaceOrAppendFinalizationBlock } from './finalization.js';
|
|
21
22
|
import { buildExternalLinksSection, replaceOrAppendExternalLinksBlock, } from './external-links-section.js';
|
|
@@ -78,13 +79,16 @@ export async function buildAiwgMdContent(projectPath, stagedClaudeMdContent) {
|
|
|
78
79
|
return replaceOrAppendExternalLinksBlock(withFinalization, externalLinksSection);
|
|
79
80
|
}
|
|
80
81
|
// Fallback stub.
|
|
82
|
+
const normalizedPath = projectAiwgPath(projectPath, 'AIWG.md');
|
|
83
|
+
const normalizedRelative = path.relative(projectPath, normalizedPath).replace(/\\/g, '/');
|
|
84
|
+
const normalizedLink = normalizedRelative.startsWith('.') ? normalizedRelative : `./${normalizedRelative}`;
|
|
81
85
|
const stub = [
|
|
82
86
|
'# AIWG.md',
|
|
83
87
|
AIWG_SIGNATURE_COMMENT,
|
|
84
88
|
'<!-- CLAUDE.md companion for non-Claude providers. -->',
|
|
85
89
|
'',
|
|
86
90
|
'CLAUDE.md was not found at project root. AIWG.md normally mirrors that content.',
|
|
87
|
-
|
|
91
|
+
`See [${normalizedRelative}](${normalizedLink}) for the project framework context.`,
|
|
88
92
|
'',
|
|
89
93
|
].join('\n');
|
|
90
94
|
const withParallelism = replaceOrAppendParallelismBlock(stub, parallelismSection);
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import * as fs from 'fs/promises';
|
|
23
23
|
import * as path from 'path';
|
|
24
|
-
import { buildProviderBootstrapBlock } from './workspace-context.js';
|
|
24
|
+
import { buildProviderBootstrapBlock, PROVIDER_BOOTSTRAP_START, PROVIDER_BOOTSTRAP_END, } from './workspace-context.js';
|
|
25
25
|
export const CLAUDE_HOOK_START = '<!-- AIWG:claude-md-hook:start -->';
|
|
26
26
|
export const CLAUDE_HOOK_END = '<!-- AIWG:claude-md-hook:end -->';
|
|
27
27
|
/**
|
|
@@ -76,6 +76,26 @@ export async function ensureClaudeMdHook(projectPath, opts = {}) {
|
|
|
76
76
|
const endIdx = existing.indexOf(CLAUDE_HOOK_END);
|
|
77
77
|
// Case 2: marker block does not exist — append the block to end of file.
|
|
78
78
|
if (startIdx === -1 && endIdx === -1) {
|
|
79
|
+
const bootstrapStarts = existing.split(PROVIDER_BOOTSTRAP_START).length - 1;
|
|
80
|
+
const bootstrapEnds = existing.split(PROVIDER_BOOTSTRAP_END).length - 1;
|
|
81
|
+
// Current and legacy AIWG initialization may have emitted the provider
|
|
82
|
+
// bootstrap directly. Migrate that exact managed block in place instead
|
|
83
|
+
// of appending a hook containing a second copy (#1867).
|
|
84
|
+
if (bootstrapStarts === 1 && bootstrapEnds === 1) {
|
|
85
|
+
const bootstrapStartIdx = existing.indexOf(PROVIDER_BOOTSTRAP_START);
|
|
86
|
+
const bootstrapEndIdx = existing.indexOf(PROVIDER_BOOTSTRAP_END, bootstrapStartIdx)
|
|
87
|
+
+ PROVIDER_BOOTSTRAP_END.length;
|
|
88
|
+
const updated = existing.substring(0, bootstrapStartIdx)
|
|
89
|
+
+ block
|
|
90
|
+
+ existing.substring(bootstrapEndIdx);
|
|
91
|
+
await fs.writeFile(claudeMdPath, updated, 'utf8');
|
|
92
|
+
result.action = 'updated';
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
if (bootstrapStarts !== bootstrapEnds || bootstrapStarts > 1) {
|
|
96
|
+
result.warnings.push('CLAUDE.md has malformed or duplicate AIWG provider-bootstrap markers; refusing to append a second managed bootstrap.');
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
79
99
|
// Ensure the file ends with a single newline before appending.
|
|
80
100
|
const trimmed = existing.replace(/\n+$/, '\n');
|
|
81
101
|
const updated = `${trimmed}\n${block}\n`;
|
|
@@ -62,6 +62,7 @@ export async function buildContextFinalizationBlock(projectPath) {
|
|
|
62
62
|
const installedNames = installed.map(([name]) => name);
|
|
63
63
|
const providerDeployments = new Set();
|
|
64
64
|
const normalizedAiwgMdPath = displayProjectPath(projectPath, projectAiwgPath(projectPath, 'AIWG.md'));
|
|
65
|
+
const normalizedAiwgMdLabel = `\`${normalizedAiwgMdPath}\``;
|
|
65
66
|
for (const [, entry] of installed) {
|
|
66
67
|
for (const provider of Object.keys(entry.deployedTo ?? {})) {
|
|
67
68
|
providerDeployments.add(provider);
|
|
@@ -94,9 +95,9 @@ export async function buildContextFinalizationBlock(projectPath) {
|
|
|
94
95
|
'',
|
|
95
96
|
'### Source Model',
|
|
96
97
|
'',
|
|
97
|
-
|
|
98
|
+
`- ${normalizedAiwgMdLabel} is the normalized project-local context entry point.`,
|
|
98
99
|
'- Root `AIWG.md` is the generated cross-provider companion loaded through `AGENTS.md` and provider twins.',
|
|
99
|
-
|
|
100
|
+
`- \`AGENTS.md\`, \`WARP.md\`, \`.hermes.md\`, and \`.github/copilot-instructions.md\` are provider-facing bridges, not replacements for ${normalizedAiwgMdLabel}.`,
|
|
100
101
|
FINALIZATION_END,
|
|
101
102
|
'',
|
|
102
103
|
];
|
|
@@ -113,6 +114,7 @@ export function replaceOrAppendFinalizationBlock(content, block) {
|
|
|
113
114
|
export async function buildNormalizedAiwgMd(projectPath, existing = '') {
|
|
114
115
|
const block = await buildContextFinalizationBlock(projectPath);
|
|
115
116
|
const externalLinksSection = await buildExternalLinksSection(projectPath);
|
|
117
|
+
const normalizedAiwgMdPath = displayProjectPath(projectPath, projectAiwgPath(projectPath, 'AIWG.md'));
|
|
116
118
|
const base = existing.trim().length > 0
|
|
117
119
|
? existing
|
|
118
120
|
: [
|
|
@@ -120,7 +122,7 @@ export async function buildNormalizedAiwgMd(projectPath, existing = '') {
|
|
|
120
122
|
AIWG_SIGNATURE_COMMENT,
|
|
121
123
|
'<!-- Normalized project-local AIWG context. Operator notes may live outside AIWG-managed blocks. -->',
|
|
122
124
|
'',
|
|
123
|
-
|
|
125
|
+
`This file is the stable \`${normalizedAiwgMdPath}\` entry point for AIWG skills, rules, and generated provider context.`,
|
|
124
126
|
'',
|
|
125
127
|
].join('\n');
|
|
126
128
|
const signed = base.includes(AIWG_SIGNATURE_COMMENT)
|
|
@@ -21,6 +21,7 @@ import { shouldEmitAgentsMd, shouldEmitAiwgMd, shouldEmitClaudeMdHook } from './
|
|
|
21
21
|
import { ensureClaudeMdHook } from './claude-hook.js';
|
|
22
22
|
import { ensureManagedHook } from './managed-hook.js';
|
|
23
23
|
import { buildProviderBootstrapBlock, ensureWorkspaceContext, registerProviderContext, } from './workspace-context.js';
|
|
24
|
+
import { projectAiwgPath } from '../../config/project-artifacts.js';
|
|
24
25
|
const SECTION_TITLES = {
|
|
25
26
|
agents: 'Agents',
|
|
26
27
|
rules: 'Rules',
|
|
@@ -136,7 +137,9 @@ export async function buildAgentsMd(opts) {
|
|
|
136
137
|
parts.push('');
|
|
137
138
|
parts.push('See [AIWG.md](./AIWG.md) for the full AIWG framework context');
|
|
138
139
|
parts.push('(active frameworks, addons, agents, behaviors, rules).');
|
|
139
|
-
|
|
140
|
+
const configRelative = path.relative(opts.projectPath, projectAiwgPath(opts.projectPath, 'aiwg.config')).replace(/\\/g, '/');
|
|
141
|
+
const configLink = configRelative.startsWith('.') ? configRelative : `./${configRelative}`;
|
|
142
|
+
parts.push(`Tracker and delivery source of truth: [${configRelative}](${configLink}).`);
|
|
140
143
|
parts.push('');
|
|
141
144
|
parts.push('Deployed artifacts live under your provider\'s native directory');
|
|
142
145
|
parts.push('(for example `.codex/agents/`, `.warp/agents/`, `.github/agents/`).');
|
|
@@ -16,6 +16,27 @@
|
|
|
16
16
|
import { readAiwgConfig, resolveParallelism, PROVIDER_PARALLELISM_DEFAULTS } from '../../config/aiwg-config.js';
|
|
17
17
|
export const PARALLELISM_BLOCK_START = '<!-- AIWG-PARALLELISM-CAP:START -->';
|
|
18
18
|
export const PARALLELISM_BLOCK_END = '<!-- AIWG-PARALLELISM-CAP:END -->';
|
|
19
|
+
function delegationSupport(provider) {
|
|
20
|
+
switch (provider) {
|
|
21
|
+
case 'claude':
|
|
22
|
+
case 'codex':
|
|
23
|
+
case 'copilot':
|
|
24
|
+
case 'cursor':
|
|
25
|
+
case 'factory':
|
|
26
|
+
case 'opencode':
|
|
27
|
+
case 'openclaw':
|
|
28
|
+
return `**Provider behavior (${provider})**: native custom subagents can select the deployed model-worker wrapper. Verify the resolved model when provider or account policy may substitute it.`;
|
|
29
|
+
case 'openhuman':
|
|
30
|
+
return '**Provider behavior (openhuman)**: wrapper roles compile to OpenHuman agent definitions; exact pins validate while semantic hints may be routed dynamically.';
|
|
31
|
+
case 'warp':
|
|
32
|
+
case 'hermes':
|
|
33
|
+
return `**Provider behavior (${provider})**: delegation is available, but model selection is global/run-scoped. Use the wrapper rubric for task selection while reporting that heterogeneous per-worker pinning is not enforced.`;
|
|
34
|
+
case 'windsurf':
|
|
35
|
+
return '**Provider behavior (windsurf)**: portable subagent model selection is unsupported. Apply the decomposition rubric in the primary agent and do not claim wrapper pinning or parallel delegation.';
|
|
36
|
+
default:
|
|
37
|
+
return '**Provider behavior (unknown)**: delegation and per-worker model selection are unverified. Apply the rubric only where the active provider exposes those capabilities, and state any fallback explicitly.';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
19
40
|
/**
|
|
20
41
|
* Build the parallelism-cap markdown section for injection into context files.
|
|
21
42
|
* Returns `''` when injection should be skipped:
|
|
@@ -61,7 +82,19 @@ export async function buildParallelismSection(projectPath) {
|
|
|
61
82
|
lines.push(`*Rationale*: ${resolved.rationale}`);
|
|
62
83
|
}
|
|
63
84
|
lines.push('');
|
|
64
|
-
lines.push('
|
|
85
|
+
lines.push('### Model-selected delegation rubric');
|
|
86
|
+
lines.push('');
|
|
87
|
+
lines.push('For each non-trivial task, assess whether it contains independent, bounded subtasks that can run concurrently. When delegation is supported, prefer the deployed model-pinned wrappers by task characteristics and consequence:');
|
|
88
|
+
lines.push('');
|
|
89
|
+
lines.push('- `aiwg-model-efficiency-worker`: discovery, inventory, focused edits, and other bounded low-cost work.');
|
|
90
|
+
lines.push('- `aiwg-model-coding-worker`: implementation, tests, debugging, and routine technical delivery.');
|
|
91
|
+
lines.push('- `aiwg-model-reasoning-worker`: architecture, synthesis, difficult analysis, and high-consequence review.');
|
|
92
|
+
lines.push('');
|
|
93
|
+
lines.push('Do not delegate trivial work, tightly coupled changes, serial dependencies, or tasks likely to collide in shared state; also keep work local when coordination costs exceed the expected benefit. Parallelize only independent work, and take the MIN of provider limits, `max_parallel_subagents`, `AIWG_CONTEXT_WINDOW` budget, framework-specific caps (including the RLM 7-agent hard cap for RLM dispatches), and natural task decomposition. Bump the project cap via `aiwg config set --project parallelism.max_parallel_subagents N`.');
|
|
94
|
+
lines.push('');
|
|
95
|
+
lines.push('The primary agent retains orchestration, final integration, conflict resolution, validation, and user-facing accountability.');
|
|
96
|
+
lines.push('');
|
|
97
|
+
lines.push(delegationSupport(primary));
|
|
65
98
|
lines.push('');
|
|
66
99
|
lines.push(PARALLELISM_BLOCK_END);
|
|
67
100
|
lines.push('');
|
|
@@ -384,7 +384,15 @@ export async function ensureWorkspaceContext(projectPath, options = {}) {
|
|
|
384
384
|
}
|
|
385
385
|
const block = buildWorkspaceManagedBlock(projectPath, options.providerFiles);
|
|
386
386
|
if (existing.includes(WORKSPACE_MANAGED_START) || existing.includes(WORKSPACE_MANAGED_END)) {
|
|
387
|
-
|
|
387
|
+
let updated = replaceBlock(existing, WORKSPACE_MANAGED_START, WORKSPACE_MANAGED_END, block);
|
|
388
|
+
// A managed project-extraction block explicitly points operators back to
|
|
389
|
+
// README/package metadata as its source. Refresh that block during normal
|
|
390
|
+
// regeneration so the documented edit-source-then-regenerate contract is
|
|
391
|
+
// true (#1866). Workspaces without an extraction remain opt-in.
|
|
392
|
+
if (updated.includes(PROJECT_EXTRACTION_START) || updated.includes(PROJECT_EXTRACTION_END)) {
|
|
393
|
+
const extracted = await extractExistingProjectContext(projectPath);
|
|
394
|
+
updated = replaceBlock(updated, PROJECT_EXTRACTION_START, PROJECT_EXTRACTION_END, extracted.content);
|
|
395
|
+
}
|
|
388
396
|
if (updated === existing)
|
|
389
397
|
return { path: workspacePath, action: 'unchanged', warnings };
|
|
390
398
|
await atomicWrite(workspacePath, updated);
|
|
@@ -512,6 +520,7 @@ function sensitiveEvidence(content) {
|
|
|
512
520
|
async function nestedInstructionFiles(projectPath) {
|
|
513
521
|
const found = [];
|
|
514
522
|
const ignored = new Set(['.git', 'node_modules', 'dist', 'build', 'coverage', '.aiwg']);
|
|
523
|
+
const artifactRoot = path.resolve(resolveProjectAiwgDir(projectPath));
|
|
515
524
|
async function walk(directory) {
|
|
516
525
|
let entries;
|
|
517
526
|
try {
|
|
@@ -522,8 +531,11 @@ async function nestedInstructionFiles(projectPath) {
|
|
|
522
531
|
}
|
|
523
532
|
for (const entry of entries) {
|
|
524
533
|
if (entry.isDirectory()) {
|
|
525
|
-
|
|
526
|
-
|
|
534
|
+
const child = path.join(directory, entry.name);
|
|
535
|
+
if (!ignored.has(entry.name)
|
|
536
|
+
&& !entry.name.startsWith('.context-migration-')
|
|
537
|
+
&& path.resolve(child) !== artifactRoot)
|
|
538
|
+
await walk(child);
|
|
527
539
|
}
|
|
528
540
|
else if (['AGENTS.md', 'CLAUDE.md', 'WARP.md'].includes(entry.name)) {
|
|
529
541
|
const relative = path.relative(projectPath, path.join(directory, entry.name)).replace(/\\/g, '/');
|
|
@@ -12,6 +12,7 @@ import { analyzeCLI } from './analyzers/cli-analyzer.js';
|
|
|
12
12
|
import { generateServer, updateRegistry } from './generator.js';
|
|
13
13
|
import path from 'path';
|
|
14
14
|
import { fileURLToPath } from 'url';
|
|
15
|
+
import { projectAiwgPath } from '../../config/project-artifacts.js';
|
|
15
16
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
16
17
|
async function main() {
|
|
17
18
|
console.log('MCPsmith Example: Generating Git MCP Server\n');
|
|
@@ -28,7 +29,8 @@ async function main() {
|
|
|
28
29
|
console.log();
|
|
29
30
|
// Step 2: Generate the MCP server
|
|
30
31
|
console.log('Step 2: Generating MCP server...');
|
|
31
|
-
const
|
|
32
|
+
const projectRoot = path.resolve(__dirname, '..', '..', '..');
|
|
33
|
+
const outputDir = projectAiwgPath(projectRoot, 'smiths', 'mcpsmith', 'servers');
|
|
32
34
|
const generatedServer = await generateServer({
|
|
33
35
|
serverId: 'git',
|
|
34
36
|
serverName: 'Git CLI MCP Server',
|
|
@@ -10,6 +10,7 @@ import fs from 'fs/promises';
|
|
|
10
10
|
import path from 'path';
|
|
11
11
|
import { fileURLToPath } from 'url';
|
|
12
12
|
import crypto from 'crypto';
|
|
13
|
+
import { projectAiwgPath } from '../../config/project-artifacts.js';
|
|
13
14
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
14
15
|
/**
|
|
15
16
|
* Generate MCP server from analyzer result
|
|
@@ -174,7 +175,8 @@ function createManifest(serverId, serverName, analyzerResult, config) {
|
|
|
174
175
|
*/
|
|
175
176
|
async function generateServerCode(serverId, serverName, analyzerResult) {
|
|
176
177
|
// Load template
|
|
177
|
-
const
|
|
178
|
+
const projectRoot = path.resolve(path.dirname(__dirname), '..', '..');
|
|
179
|
+
const templatePath = projectAiwgPath(projectRoot, 'smiths', 'mcpsmith', 'templates', 'server-template.mjs');
|
|
178
180
|
let template = await fs.readFile(templatePath, 'utf-8');
|
|
179
181
|
// Replace template variables
|
|
180
182
|
const now = new Date().toISOString();
|
|
@@ -13,6 +13,7 @@ import { existsSync, constants } from 'fs';
|
|
|
13
13
|
import { resolve, join, dirname } from 'path';
|
|
14
14
|
import { platform, arch, homedir, tmpdir, cpus, totalmem, freemem } from 'os';
|
|
15
15
|
import { fileURLToPath } from 'url';
|
|
16
|
+
import { projectAiwgPath } from '../../config/project-artifacts-runtime.mjs';
|
|
16
17
|
|
|
17
18
|
const __filename = fileURLToPath(import.meta.url);
|
|
18
19
|
const __dirname = dirname(__filename);
|
|
@@ -22,7 +23,7 @@ const __dirname = dirname(__filename);
|
|
|
22
23
|
*/
|
|
23
24
|
export class RuntimeDiscovery {
|
|
24
25
|
constructor(basePath = null) {
|
|
25
|
-
this.basePath = basePath ||
|
|
26
|
+
this.basePath = basePath || projectAiwgPath(process.cwd(), 'smiths', 'toolsmith');
|
|
26
27
|
this.knownTools = this.#loadKnownTools();
|
|
27
28
|
}
|
|
28
29
|
|
package/dist/src/storage/cli.js
CHANGED
|
@@ -18,6 +18,7 @@ import { existsSync } from 'fs';
|
|
|
18
18
|
import { mkdir, readFile, writeFile, appendFile } from 'fs/promises';
|
|
19
19
|
import { dirname, join, resolve as resolvePath } from 'path';
|
|
20
20
|
import { BACKEND_TYPES, FilesystemAdapter, ObsidianAdapter, LogseqAdapter, FortemiAdapter, SUBSYSTEM_KEYS, getLoadedConfig, initStorage, resolveStorage, resolveSubsystemRoot, storageConfigPath, } from './index.js';
|
|
21
|
+
import { projectAiwgPath, resolveProjectAiwgDir } from '../config/project-artifacts.js';
|
|
21
22
|
export async function main(args) {
|
|
22
23
|
const subcommand = args[0];
|
|
23
24
|
const subArgs = args.slice(1);
|
|
@@ -47,7 +48,7 @@ async function handleShow(projectRoot) {
|
|
|
47
48
|
const config = await getLoadedConfig(projectRoot);
|
|
48
49
|
const cfgPath = storageConfigPath(projectRoot);
|
|
49
50
|
if (!config) {
|
|
50
|
-
console.log(`No storage.config — every subsystem uses the default fs backend under
|
|
51
|
+
console.log(`No storage.config — every subsystem uses the default fs backend under ${resolveProjectAiwgDir(projectRoot)}.\n`);
|
|
51
52
|
console.log(`Expected location (when configured): ${cfgPath}\n`);
|
|
52
53
|
}
|
|
53
54
|
else {
|
|
@@ -178,7 +179,7 @@ async function handleMigrate(projectRoot, args) {
|
|
|
178
179
|
return;
|
|
179
180
|
}
|
|
180
181
|
// Resume support: read the per-migration log; skip already-done paths
|
|
181
|
-
const migrationLogPath =
|
|
182
|
+
const migrationLogPath = projectAiwgPath(projectRoot, '.storage-cache', 'migrations', `${opts.subsystem}-${specSlug(opts.from)}-to-${specSlug(opts.to)}.jsonl`);
|
|
182
183
|
const completed = await readCompletedSet(migrationLogPath);
|
|
183
184
|
let copied = 0;
|
|
184
185
|
let skipped = 0;
|