@aiwg/cli 2026.10.10 → 2026.10.12
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/agentic/code/providers/model-capabilities.v1.json +78 -14
- package/agentic/code/providers/model-catalog.v1.json +62 -12
- package/dist/src/artifacts/binding-target-identity.js +51 -0
- package/dist/src/artifacts/checksum-manifest.js +12 -0
- package/dist/src/artifacts/index-builder.js +11 -13
- package/dist/src/artifacts/query-engine.js +5 -11
- package/dist/src/cli/handlers/doctor-help.js +32 -0
- package/dist/src/cli/handlers/gates.js +19 -18
- package/dist/src/cli/handlers/models-help.js +7 -1
- package/dist/src/cli/handlers/models.js +3 -3
- package/dist/src/cli/handlers/script-runner.js +8 -5
- package/dist/src/cli/handlers/sessions-intake.js +104 -0
- package/dist/src/cli/handlers/sessions-tail.js +172 -0
- package/dist/src/cli/handlers/sessions.js +287 -31
- package/dist/src/cli/handlers/skill-lint.js +10 -3
- package/dist/src/cli/handlers/steward-format.js +100 -0
- package/dist/src/cli/handlers/steward-models.js +167 -0
- package/dist/src/cli/handlers/steward.js +14 -248
- package/dist/src/cli/handlers/use-bundle-counts.js +61 -0
- package/dist/src/cli/handlers/use.js +18 -28
- package/dist/src/cli/handlers/utilities.js +1 -24
- package/dist/src/config/aiwg-config.js +7 -0
- package/dist/src/config/authorization-cli.js +89 -0
- package/dist/src/config/cli-help.js +1 -0
- package/dist/src/config/cli.js +10 -1
- package/dist/src/config/worker-context.js +337 -0
- package/dist/src/decision/context-pruning.js +3 -2
- package/dist/src/decision/ensemble/contract.js +15 -4
- package/dist/src/decision/ensemble-study/artifacts.js +5 -0
- package/dist/src/decision/ensemble-study/score.js +4 -3
- package/dist/src/decision/feature-export.js +3 -9
- package/dist/src/decision/heldout/collector.js +2 -2
- package/dist/src/decision/issue-triage/runtime.js +3 -9
- package/dist/src/decision/qualification/gates.js +2 -1
- package/dist/src/decision/qualification/release.js +2 -3
- package/dist/src/decision/routing/report.js +2 -0
- package/dist/src/decision/sdlc-screening.js +3 -5
- package/dist/src/decision/sensitivity/comparative.js +5 -3
- package/dist/src/gates/driver.js +6 -77
- package/dist/src/gates/evaluate.js +4 -11
- package/dist/src/gates/floors.js +4 -4
- package/dist/src/gates/index.js +3 -3
- package/dist/src/gates/lattice.js +26 -0
- package/dist/src/gates/registry.js +2 -1
- package/dist/src/gates/report.js +19 -7
- package/dist/src/issues/audit-cli.js +120 -0
- package/dist/src/issues/audit-contract.js +64 -0
- package/dist/src/issues/audit-local.js +82 -0
- package/dist/src/issues/audit.js +220 -0
- package/dist/src/lint/rule-activation.js +15 -1
- package/dist/src/mcp/tools/worker-invoke.mjs +108 -10
- package/dist/src/mission-protocol/canonical-codecs.js +270 -0
- package/dist/src/mission-protocol/codecs.js +22 -550
- package/dist/src/mission-protocol/health-trace-codecs.js +281 -0
- package/dist/src/mission-protocol/health-trace-types.js +43 -0
- package/dist/src/mission-protocol/types.js +2 -42
- package/dist/src/mission-protocol/worker-contract-json.js +10 -0
- package/dist/src/mission-protocol/worker-review-gate.js +100 -0
- package/dist/src/mission-protocol/worker-review-types.js +2 -0
- package/dist/src/models/catalog-policy.js +280 -0
- package/dist/src/models/index.js +1 -1
- package/dist/src/models/model-capabilities.v1.json +78 -14
- package/dist/src/models/model-catalog.v1.json +62 -12
- package/dist/src/models/policy-resources.js +28 -0
- package/dist/src/models/provider-policy.js +10 -539
- package/dist/src/models/resolver.js +26 -8
- package/dist/src/models/router.js +15 -2
- package/dist/src/models/tiers.js +13 -0
- package/dist/src/models/types.js +0 -6
- package/dist/src/models/worker-ceilings.js +59 -0
- package/dist/src/models/worker-invocation-policy.js +243 -0
- package/dist/src/models/worker-plan.js +16 -20
- package/dist/src/models/worker-review-lineage.js +55 -0
- package/dist/src/models/worker-selection-policy.js +1 -1
- package/dist/src/models/wrapper-route.js +6 -3
- package/dist/src/policy/authorization.js +30 -1
- package/dist/src/policy/hitl-config.js +63 -0
- package/dist/src/policy/hitl-receipt.js +57 -0
- package/dist/src/policy/hitl-types.js +4 -0
- package/dist/src/policy/hitl-validation.js +39 -0
- package/dist/src/policy/hitl.js +117 -0
- package/dist/src/process/aiwg-probe-command.d.mts +15 -0
- package/dist/src/process/aiwg-probe-command.mjs +37 -0
- package/dist/src/rules/activation.d.mts +1 -1
- package/dist/src/rules/activation.mjs +0 -2
- package/dist/src/rules/gate-coverage.js +51 -0
- package/dist/src/rules/gate-references.js +16 -0
- package/dist/src/rules/rule-frontmatter.schema.json +7 -1
- package/dist/src/serve/worker-command-request.js +21 -4
- package/dist/src/serve/worker-invocation-preparation.js +3 -1
- package/dist/src/serve/worker-invocation.js +21 -10
- package/dist/src/sessions/adapters/deepseek-harness.js +1 -0
- package/dist/src/sessions/adapters/grok-build-native.js +269 -0
- package/dist/src/sessions/adapters/grok-build.js +57 -15
- package/dist/src/sessions/adapters/grokbot.js +7 -0
- package/dist/src/sessions/adapters/hermes-state-db.js +252 -0
- package/dist/src/sessions/adapters/hermes.js +42 -3
- package/dist/src/sessions/adapters/warp.js +1 -1
- package/dist/src/sessions/batch-import.js +44 -6
- package/dist/src/sessions/contracts.js +20 -1
- package/dist/src/sessions/coverage-preflight.js +7 -1
- package/dist/src/sessions/discovery-capability.js +38 -0
- package/dist/src/sessions/fortemi-export-mapping.js +33 -3
- package/dist/src/sessions/host-catalog.js +157 -0
- package/dist/src/sessions/image-blobs.js +141 -0
- package/dist/src/sessions/importer.js +9 -232
- package/dist/src/sessions/index.js +6 -0
- package/dist/src/sessions/intake-dir.js +71 -0
- package/dist/src/sessions/normalization.js +251 -0
- package/dist/src/sessions/refresh.js +25 -2
- package/dist/src/sessions/repository.js +164 -22
- package/dist/src/sessions/tail.js +91 -0
- package/dist/src/sessions/workspace-discovery.js +88 -9
- package/dist/src/skills/agent-skill-sidecar.js +28 -0
- package/dist/src/skills/agent-skills.js +4 -23
- package/dist/src/skills/claude-surface-projection.js +25 -0
- package/dist/src/skills/deployer.js +8 -288
- package/dist/src/skills/deployment-policy.js +114 -0
- package/dist/src/skills/deployment-projection.js +152 -0
- package/dist/src/skills/deployment-types.js +20 -0
- package/dist/src/skills/exporter.js +8 -31
- package/dist/src/skills/provider-extensions.js +41 -0
- package/dist/src/skills/run.js +6 -0
- package/dist/src/skills/types.js +0 -8
- package/dist/src/skills/validator-resources.js +68 -0
- package/dist/src/skills/validator.js +6 -55
- package/dist/src/storage/cli-migrate-options.js +125 -0
- package/dist/src/storage/cli-migrate.js +111 -0
- package/dist/src/storage/cli.js +4 -279
- package/dist/src/storage/migration-adapter.js +79 -0
- package/dist/src/storage/migration-copy.js +108 -0
- package/dist/src/storage/migration-protocol.js +14 -197
- package/dist/src/storage/migration-records.js +186 -0
- package/dist/src/storage/migration-types.js +12 -0
- package/dist/src/tracker/forge-http.js +2 -1
- package/dist/src/tracker/issue-pages.js +110 -0
- package/package.json +7 -1
- package/schemas/mission-v1.schema.json +335 -0
- package/schemas/models/model-catalog.v1.schema.json +19 -1
- package/schemas/models/model-config-compatibility.v1.schema.json +3 -2
- package/schemas/models/model-policy.v1.schema.json +1 -1
- package/tools/agents/providers/provider-paths-staging.mjs +35 -0
- package/tools/agents/providers/rule-binding-targets.mjs +146 -0
- package/tools/agents/providers/rule-delivery.mjs +7 -126
- package/tools/ralph-external/lib/muse-adapter.mjs +10 -0
- package/tools/ralph-external/lib/worker-context.mjs +237 -0
- package/tools/ralph-external/lib/worker-invocation-ancestry.mjs +132 -0
- package/tools/ralph-external/lib/worker-invocation-processes.mjs +123 -0
- package/tools/ralph-external/lib/worker-isolation-status.mjs +48 -0
- package/tools/ralph-external/lib/worker-isolation.mjs +17 -29
- package/tools/ralph-external/lib/worker-launch-cli.mjs +3 -1
- package/tools/ralph-external/lib/worker-overflow.mjs +48 -13
- package/tools/ralph-external/lib/worker-retry-evidence.mjs +103 -0
- package/tools/ralph-external/lib/worker-termination.mjs +82 -0
- package/tools/ralph-external/session-launcher.mjs +51 -96
- package/vscode-extension/schemas/aiwg.config.v1.json +177 -0
|
@@ -13,18 +13,10 @@ import fs from 'node:fs';
|
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
15
15
|
import { stringify } from 'yaml';
|
|
16
|
-
import { AGENT_SKILLS_BASELINE,
|
|
16
|
+
import { AGENT_SKILLS_BASELINE, projectStrictAgentSkill, } from './agent-skills.js';
|
|
17
17
|
import { validateAgentSkillContent } from './validator.js';
|
|
18
|
+
import { agentSkillDocument } from './provider-extensions.js';
|
|
18
19
|
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
20
|
export class AgentSkillExportError extends Error {
|
|
29
21
|
code;
|
|
30
22
|
constructor(code, message) {
|
|
@@ -160,28 +152,11 @@ function parseDocument(sourceRoot, entries) {
|
|
|
160
152
|
.map((item) => item.code)
|
|
161
153
|
.join(', ')}`);
|
|
162
154
|
}
|
|
163
|
-
const
|
|
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
|
-
}
|
|
155
|
+
const document = agentSkillDocument(validation.frontmatter, validation.body);
|
|
175
156
|
return {
|
|
176
|
-
document
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
resources: [],
|
|
180
|
-
aiwg,
|
|
181
|
-
unknownFields: [],
|
|
182
|
-
},
|
|
183
|
-
description: validation.frontmatter.description,
|
|
184
|
-
omittedAiwgFields: Object.keys(aiwg).sort(),
|
|
157
|
+
document,
|
|
158
|
+
description: document.standard.description,
|
|
159
|
+
omittedAiwgFields: Object.keys(document.aiwg).sort(),
|
|
185
160
|
};
|
|
186
161
|
}
|
|
187
162
|
function strictSkillEntry(sourceRoot, document) {
|
|
@@ -332,6 +307,7 @@ export function exportAgentSkillDirectory(sourceDir, options) {
|
|
|
332
307
|
sourceDigest,
|
|
333
308
|
exportDigest: '',
|
|
334
309
|
omittedAiwgFields,
|
|
310
|
+
...(document.providers ? { providers: document.providers } : {}),
|
|
335
311
|
fileCount: 0,
|
|
336
312
|
totalBytes: 0,
|
|
337
313
|
exportedAt,
|
|
@@ -360,6 +336,7 @@ export function exportAgentSkillDirectory(sourceDir, options) {
|
|
|
360
336
|
sourceDigest,
|
|
361
337
|
exportDigest,
|
|
362
338
|
omittedAiwgFields,
|
|
339
|
+
...(document.providers ? { providers: document.providers } : {}),
|
|
363
340
|
fileCount,
|
|
364
341
|
totalBytes,
|
|
365
342
|
exportedAt,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Classify Claude-native fields separately from portable Agent Skills metadata.
|
|
2
|
+
import { AIWG_SKILL_CONTROL_FIELDS, STANDARD_SKILL_FIELDS, } from './agent-skills.js';
|
|
3
|
+
export const CLAUDE_SKILL_FIELDS = [
|
|
4
|
+
'disable-model-invocation', 'user-invocable', 'disallowed-tools', 'context',
|
|
5
|
+
'agent', 'background', 'model', 'effort', 'paths', 'shell', 'argument-hint',
|
|
6
|
+
'arguments',
|
|
7
|
+
];
|
|
8
|
+
export function agentSkillDocument(frontmatter, body) {
|
|
9
|
+
const standard = Object.fromEntries(STANDARD_SKILL_FIELDS
|
|
10
|
+
.filter((key) => Object.hasOwn(frontmatter, key))
|
|
11
|
+
.map((key) => [key, structuredClone(frontmatter[key])]));
|
|
12
|
+
const aiwg = Object.fromEntries(AIWG_SKILL_CONTROL_FIELDS
|
|
13
|
+
.filter((key) => Object.hasOwn(frontmatter, key))
|
|
14
|
+
.map((key) => [key, structuredClone(frontmatter[key])]));
|
|
15
|
+
const claude = Object.fromEntries(CLAUDE_SKILL_FIELDS
|
|
16
|
+
.filter((key) => Object.hasOwn(frontmatter, key))
|
|
17
|
+
.map((key) => [key, structuredClone(frontmatter[key])]));
|
|
18
|
+
const known = new Set([
|
|
19
|
+
...STANDARD_SKILL_FIELDS, ...AIWG_SKILL_CONTROL_FIELDS, ...CLAUDE_SKILL_FIELDS,
|
|
20
|
+
]);
|
|
21
|
+
return {
|
|
22
|
+
standard: standard,
|
|
23
|
+
aiwg,
|
|
24
|
+
...(Object.keys(claude).length ? { providers: { claude } } : {}),
|
|
25
|
+
body,
|
|
26
|
+
resources: [],
|
|
27
|
+
unknownFields: Object.entries(frontmatter)
|
|
28
|
+
.filter(([key]) => !known.has(key))
|
|
29
|
+
.map(([key, value]) => ({ key, value: structuredClone(value) })),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** Older sidecars retained context/effort under aiwg; preserve that spelling. */
|
|
33
|
+
export function claudeSkillExtensions(document) {
|
|
34
|
+
return {
|
|
35
|
+
...Object.fromEntries(CLAUDE_SKILL_FIELDS
|
|
36
|
+
.filter((key) => Object.hasOwn(document.aiwg, key))
|
|
37
|
+
.map((key) => [key, structuredClone(document.aiwg[key])])),
|
|
38
|
+
...structuredClone(document.providers?.claude ?? {}),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=provider-extensions.js.map
|
package/dist/src/skills/run.js
CHANGED
|
@@ -159,6 +159,12 @@ export async function runSkill(opts) {
|
|
|
159
159
|
...(aiwgRoot ? { AIWG_ROOT: aiwgRoot } : {}),
|
|
160
160
|
...opts.env,
|
|
161
161
|
};
|
|
162
|
+
// Native source owner shares the same indexed script declaration and project CWD.
|
|
163
|
+
if (entry.path.replaceAll('\\', '/').endsWith('agentic/code/frameworks/sdlc-complete/skills/issue-audit/SKILL.md')
|
|
164
|
+
&& entry.script.entrypoint === 'scripts/audit.mjs') {
|
|
165
|
+
const audit = await import('../issues/audit-cli.js');
|
|
166
|
+
return audit.main(opts.args, runCwd, { env });
|
|
167
|
+
}
|
|
162
168
|
const fullArgs = [...invocation.prefixArgs, entrypointPath, ...opts.args];
|
|
163
169
|
return new Promise((resolve) => {
|
|
164
170
|
const child = spawn(invocation.command, fullArgs, {
|
package/dist/src/skills/types.js
CHANGED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Diagnose bundled skill resource references without changing parser policy.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { AGENT_SKILLS_BASELINE } from './agent-skills.js';
|
|
5
|
+
const MARKDOWN_LINK = /!?\[[^\]]*]\(\s*(?:<([^>]+)>|([^\s)]+))(?:\s+["'][^)]*["'])?\s*\)/g;
|
|
6
|
+
function diagnostic(code, severity, file, yamlPath, message, remediation) {
|
|
7
|
+
return {
|
|
8
|
+
code,
|
|
9
|
+
severity,
|
|
10
|
+
file,
|
|
11
|
+
yamlPath,
|
|
12
|
+
message,
|
|
13
|
+
upstreamBaseline: AGENT_SKILLS_BASELINE.revision,
|
|
14
|
+
remediation,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function resourceDiagnostics(content, file, skillRoot, checkResources) {
|
|
18
|
+
const diagnostics = [];
|
|
19
|
+
const references = new Set();
|
|
20
|
+
for (const match of content.matchAll(MARKDOWN_LINK)) {
|
|
21
|
+
const raw = (match[1] ?? match[2] ?? '').trim();
|
|
22
|
+
if (raw.length === 0
|
|
23
|
+
|| raw.startsWith('#')
|
|
24
|
+
|| /^(?:https?|mailto|data):/i.test(raw)) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const withoutFragment = raw.split('#', 1)[0] ?? '';
|
|
28
|
+
const decoded = (() => {
|
|
29
|
+
try {
|
|
30
|
+
return decodeURIComponent(withoutFragment);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return withoutFragment;
|
|
34
|
+
}
|
|
35
|
+
})();
|
|
36
|
+
references.add(decoded);
|
|
37
|
+
}
|
|
38
|
+
for (const reference of [...references].sort()) {
|
|
39
|
+
const normalized = reference.replaceAll('\\', '/');
|
|
40
|
+
if (path.isAbsolute(reference)
|
|
41
|
+
|| normalized === '..'
|
|
42
|
+
|| normalized.startsWith('../')
|
|
43
|
+
|| normalized.includes('/../')) {
|
|
44
|
+
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.'));
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const segments = normalized.split('/').filter(Boolean);
|
|
48
|
+
if (segments.length > 2) {
|
|
49
|
+
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.'));
|
|
50
|
+
}
|
|
51
|
+
if (!checkResources || !skillRoot)
|
|
52
|
+
continue;
|
|
53
|
+
const resolved = path.resolve(skillRoot, reference);
|
|
54
|
+
const relative = path.relative(skillRoot, resolved);
|
|
55
|
+
if (relative.startsWith('..')
|
|
56
|
+
|| path.isAbsolute(relative)
|
|
57
|
+
|| !fs.existsSync(resolved)) {
|
|
58
|
+
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.'));
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const stat = fs.lstatSync(resolved);
|
|
62
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
63
|
+
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.'));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return diagnostics;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=validator-resources.js.map
|
|
@@ -10,11 +10,13 @@
|
|
|
10
10
|
import fs from 'node:fs';
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
import { parseDocument } from 'yaml';
|
|
13
|
+
import { resourceDiagnostics } from './validator-resources.js';
|
|
14
|
+
import { CLAUDE_SKILL_FIELDS } from './provider-extensions.js';
|
|
13
15
|
import { AGENT_SKILLS_BASELINE, AGENT_SKILL_VALIDATION_PROFILES, AIWG_SKILL_CONTROL_FIELDS, STANDARD_SKILL_FIELDS, validateCompatibleAgentSkillMetadata, } from './agent-skills.js';
|
|
14
16
|
const FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
|
|
15
|
-
const MARKDOWN_LINK = /!?\[[^\]]*]\(\s*(?:<([^>]+)>|([^\s)]+))(?:\s+["'][^)]*["'])?\s*\)/g;
|
|
16
17
|
const STANDARD_FIELDS = new Set(STANDARD_SKILL_FIELDS);
|
|
17
18
|
const AIWG_FIELDS = new Set(AIWG_SKILL_CONTROL_FIELDS);
|
|
19
|
+
const CLAUDE_FIELDS = new Set(CLAUDE_SKILL_FIELDS);
|
|
18
20
|
function diagnostic(code, severity, file, yamlPath, message, remediation) {
|
|
19
21
|
return {
|
|
20
22
|
code,
|
|
@@ -49,64 +51,13 @@ function normalizeMetadataDiagnostics(diagnostics, frontmatter, profile, file) {
|
|
|
49
51
|
});
|
|
50
52
|
if (!policy.recognizedAiwgFields) {
|
|
51
53
|
for (const key of Object.keys(frontmatter).sort()) {
|
|
52
|
-
if (!AIWG_FIELDS.has(key))
|
|
54
|
+
if (!AIWG_FIELDS.has(key) && !CLAUDE_FIELDS.has(key))
|
|
53
55
|
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.'));
|
|
56
|
+
normalized.push(diagnostic('AS_FIELD_EXTENSION', 'error', file, `$.${key}`, `Provider or AIWG extension field "${key}" is not allowed by the strict profile`, 'Remove the extension field or validate with the compatible profile.'));
|
|
55
57
|
}
|
|
56
58
|
}
|
|
57
59
|
return normalized;
|
|
58
60
|
}
|
|
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
61
|
function resultState(diagnostics, profile) {
|
|
111
62
|
const errors = diagnostics.filter((item) => item.severity === 'error');
|
|
112
63
|
if (errors.length > 0) {
|
|
@@ -213,7 +164,7 @@ export function validateAgentSkillContent(content, options = {}) {
|
|
|
213
164
|
// Defensive policy check: a future field added to one allow-list must not
|
|
214
165
|
// silently escape classification in this parser.
|
|
215
166
|
for (const key of Object.keys(frontmatter).sort()) {
|
|
216
|
-
if (STANDARD_FIELDS.has(key) || AIWG_FIELDS.has(key))
|
|
167
|
+
if (STANDARD_FIELDS.has(key) || AIWG_FIELDS.has(key) || CLAUDE_FIELDS.has(key))
|
|
217
168
|
continue;
|
|
218
169
|
if (!diagnostics.some((item) => item.yamlPath === `$.${key}`)) {
|
|
219
170
|
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.'));
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// Parses migration flags and resolves existing storage adapters.
|
|
2
|
+
import { join, resolve as resolvePath } from 'node:path';
|
|
3
|
+
import { BACKEND_TYPES, SUBSYSTEM_KEYS, FilesystemAdapter, ObsidianAdapter, LogseqAdapter, FortemiAdapter, } from './index.js';
|
|
4
|
+
export function parseMigrateArgs(args) {
|
|
5
|
+
let subsystem;
|
|
6
|
+
let from;
|
|
7
|
+
let to;
|
|
8
|
+
let fromFolder;
|
|
9
|
+
let toFolder;
|
|
10
|
+
let dryRun = false;
|
|
11
|
+
let textOnly = false;
|
|
12
|
+
for (let i = 0; i < args.length; i++) {
|
|
13
|
+
const a = args[i];
|
|
14
|
+
if (a === '--from')
|
|
15
|
+
from = args[++i];
|
|
16
|
+
else if (a === '--to')
|
|
17
|
+
to = args[++i];
|
|
18
|
+
else if (a === '--from-folder')
|
|
19
|
+
fromFolder = args[++i];
|
|
20
|
+
else if (a === '--to-folder')
|
|
21
|
+
toFolder = args[++i];
|
|
22
|
+
else if (a === '--dry-run')
|
|
23
|
+
dryRun = true;
|
|
24
|
+
else if (a === '--text-only')
|
|
25
|
+
textOnly = true;
|
|
26
|
+
else if (!a.startsWith('--') && !subsystem)
|
|
27
|
+
subsystem = a;
|
|
28
|
+
else
|
|
29
|
+
throw new Error(`Unknown migrate flag: ${a}`);
|
|
30
|
+
}
|
|
31
|
+
if (!subsystem || !SUBSYSTEM_KEYS.includes(subsystem)) {
|
|
32
|
+
throw new Error(`Usage: aiwg storage migrate <subsystem> --from <type>:<location> --to <type>:<location> [--dry-run]\n Valid subsystems: ${SUBSYSTEM_KEYS.join(', ')}`);
|
|
33
|
+
}
|
|
34
|
+
if (!from)
|
|
35
|
+
throw new Error('storage migrate: --from <type>:<location> is required');
|
|
36
|
+
if (!to)
|
|
37
|
+
throw new Error('storage migrate: --to <type>:<location> is required');
|
|
38
|
+
return {
|
|
39
|
+
subsystem: subsystem,
|
|
40
|
+
from: { ...parseSpec(from), ...(fromFolder ? { folder: fromFolder } : {}) },
|
|
41
|
+
to: { ...parseSpec(to), ...(toFolder ? { folder: toFolder } : {}) },
|
|
42
|
+
dryRun,
|
|
43
|
+
textOnly,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export function parseSpec(raw) {
|
|
47
|
+
const idx = raw.indexOf(':');
|
|
48
|
+
if (idx === -1) {
|
|
49
|
+
throw new Error(`Invalid backend spec "${raw}" — expected <type>:<location>`);
|
|
50
|
+
}
|
|
51
|
+
const type = raw.slice(0, idx);
|
|
52
|
+
const location = raw.slice(idx + 1);
|
|
53
|
+
if (!BACKEND_TYPES.includes(type)) {
|
|
54
|
+
throw new Error(`Unknown backend type "${type}" in spec "${raw}". Valid: ${BACKEND_TYPES.join(', ')}`);
|
|
55
|
+
}
|
|
56
|
+
if (location.length === 0) {
|
|
57
|
+
throw new Error(`Empty location in backend spec "${raw}"`);
|
|
58
|
+
}
|
|
59
|
+
return { type: type, location };
|
|
60
|
+
}
|
|
61
|
+
export function buildAdapter(subsystem, spec, projectRoot) {
|
|
62
|
+
switch (spec.type) {
|
|
63
|
+
case 'fs': {
|
|
64
|
+
const root = expandFsLocation(spec.location, projectRoot);
|
|
65
|
+
return new FilesystemAdapter(root);
|
|
66
|
+
}
|
|
67
|
+
case 'obsidian':
|
|
68
|
+
return new ObsidianAdapter({
|
|
69
|
+
type: 'obsidian',
|
|
70
|
+
vault: expandFsLocation(spec.location, projectRoot),
|
|
71
|
+
...(spec.folder ? { folder: spec.folder } : {}),
|
|
72
|
+
useCli: false,
|
|
73
|
+
});
|
|
74
|
+
case 'logseq':
|
|
75
|
+
return new LogseqAdapter({
|
|
76
|
+
type: 'logseq',
|
|
77
|
+
graph: expandFsLocation(spec.location, projectRoot),
|
|
78
|
+
useApi: false,
|
|
79
|
+
});
|
|
80
|
+
case 'fortemi':
|
|
81
|
+
return new FortemiAdapter({
|
|
82
|
+
subsystem,
|
|
83
|
+
config: { type: 'fortemi', mcpServer: spec.location },
|
|
84
|
+
});
|
|
85
|
+
case 'notion':
|
|
86
|
+
case 'anythingllm':
|
|
87
|
+
case 's3':
|
|
88
|
+
case 'webdav':
|
|
89
|
+
throw new Error(`storage migrate: backend "${spec.type}" not yet implemented. ` +
|
|
90
|
+
`See issues #959, #960, #962, #963 for tracking.`);
|
|
91
|
+
default: {
|
|
92
|
+
const _exhaustive = spec.type;
|
|
93
|
+
void _exhaustive;
|
|
94
|
+
throw new Error(`Unhandled backend type ${spec.type}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export function expandFsLocation(loc, projectRoot) {
|
|
99
|
+
if (loc.startsWith('~/')) {
|
|
100
|
+
const { homedir } = require('os');
|
|
101
|
+
return join(homedir(), loc.slice(2));
|
|
102
|
+
}
|
|
103
|
+
if (loc === '~') {
|
|
104
|
+
const { homedir } = require('os');
|
|
105
|
+
return homedir();
|
|
106
|
+
}
|
|
107
|
+
if (loc.startsWith('/'))
|
|
108
|
+
return loc;
|
|
109
|
+
return resolvePath(projectRoot, loc);
|
|
110
|
+
}
|
|
111
|
+
export function locationsEqual(a, b, projectRoot) {
|
|
112
|
+
if (a.type !== b.type)
|
|
113
|
+
return false;
|
|
114
|
+
if (a.type === 'fortemi')
|
|
115
|
+
return a.location === b.location;
|
|
116
|
+
// For file-shaped backends, compare resolved fs paths
|
|
117
|
+
return expandFsLocation(a.location, projectRoot) === expandFsLocation(b.location, projectRoot);
|
|
118
|
+
}
|
|
119
|
+
export function describeSpec(spec) {
|
|
120
|
+
return spec.folder ? `${spec.type}:${spec.location} (folder=${spec.folder})` : `${spec.type}:${spec.location}`;
|
|
121
|
+
}
|
|
122
|
+
export function specSlug(spec) {
|
|
123
|
+
return `${spec.type}-${spec.location.replace(/[^a-zA-Z0-9]/g, '_').slice(0, 32)}`;
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=cli-migrate-options.js.map
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// Runs storage migrate through the protocol copy coordinator and preserves CLI output.
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { appendFile, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { dirname } from 'node:path';
|
|
5
|
+
import { projectAiwgPath } from '../config/project-artifacts.js';
|
|
6
|
+
import { STORAGE_MIGRATION_PROTOCOL, StorageMigrationCoordinator } from './migration-protocol.js';
|
|
7
|
+
import { isTextCorpusEntry, migrationEndpoint } from './migration-adapter.js';
|
|
8
|
+
import { sha256, stableStringify } from './migration-records.js';
|
|
9
|
+
import { buildAdapter, describeSpec, expandFsLocation, locationsEqual, parseMigrateArgs, specSlug, } from './cli-migrate-options.js';
|
|
10
|
+
export async function handleMigrate(projectRoot, args) {
|
|
11
|
+
const opts = parseMigrateArgs(args);
|
|
12
|
+
if (locationsEqual(opts.from, opts.to, projectRoot)) {
|
|
13
|
+
throw new Error(`Refusing to migrate: source and destination resolve to the same location.\n ${describeSpec(opts.from)}\n ${describeSpec(opts.to)}`);
|
|
14
|
+
}
|
|
15
|
+
const source = buildAdapter(opts.subsystem, opts.from, projectRoot);
|
|
16
|
+
const destination = buildAdapter(opts.subsystem, opts.to, projectRoot);
|
|
17
|
+
const identity = (spec) => ({
|
|
18
|
+
backend: spec.type, instance: stableStringify({
|
|
19
|
+
location: spec.type === 'fortemi' ? spec.location : expandFsLocation(spec.location, projectRoot),
|
|
20
|
+
folder: spec.folder ?? '',
|
|
21
|
+
}), tenant: projectRoot, subsystem: opts.subsystem, schemaVersion: '1',
|
|
22
|
+
});
|
|
23
|
+
const sourceEndpoint = migrationEndpoint(source, identity(opts.from));
|
|
24
|
+
const destinationEndpoint = migrationEndpoint(destination, identity(opts.to));
|
|
25
|
+
const migrationLogPath = projectAiwgPath(projectRoot, '.storage-cache', 'migrations', `${opts.subsystem}-${specSlug(opts.from)}-to-${specSlug(opts.to)}.jsonl`);
|
|
26
|
+
// Bind checkpoint filenames to full, resolved identities, folders and selection policy.
|
|
27
|
+
const binding = sha256(stableStringify([sourceEndpoint.identity, destinationEndpoint.identity, opts.textOnly]));
|
|
28
|
+
const checkpointPath = `${migrationLogPath}.${binding}.checkpoint.json`;
|
|
29
|
+
const receiptPath = `${checkpointPath}.receipt.json`;
|
|
30
|
+
try {
|
|
31
|
+
if (source.init)
|
|
32
|
+
await source.init();
|
|
33
|
+
if (!opts.dryRun && destination.init)
|
|
34
|
+
await destination.init();
|
|
35
|
+
console.log(`storage migrate (${opts.dryRun ? 'DRY RUN' : 'live'})`);
|
|
36
|
+
console.log(` subsystem: ${opts.subsystem}`);
|
|
37
|
+
console.log(` from: ${describeSpec(opts.from)}`);
|
|
38
|
+
console.log(` to: ${describeSpec(opts.to)}`);
|
|
39
|
+
console.log('');
|
|
40
|
+
const checkpoint = await loadCheckpoint(checkpointPath);
|
|
41
|
+
const result = await StorageMigrationCoordinator.copy({
|
|
42
|
+
source: sourceEndpoint, destination: destinationEndpoint, checkpoint, dryRun: opts.dryRun,
|
|
43
|
+
exclude: path => opts.textOnly && !isTextCorpusEntry(path) ? 'non-text attachment skipped' : undefined,
|
|
44
|
+
async save(value) {
|
|
45
|
+
await atomicJson(checkpointPath, value);
|
|
46
|
+
await atomicJson(receiptPath, { protocol: value.protocol, migrationId: value.migrationId, ...value.receipt });
|
|
47
|
+
},
|
|
48
|
+
async report(action, item) {
|
|
49
|
+
if (action === 'errored')
|
|
50
|
+
console.log(` ✗ ${item.path} (${item.reason})`);
|
|
51
|
+
else if (action === 'skipped') {
|
|
52
|
+
const marker = item.reason === 'already migrated, skipped' ? '✓' : '·';
|
|
53
|
+
console.log(` ${marker} ${item.path} (${item.reason})`);
|
|
54
|
+
}
|
|
55
|
+
else if (opts.dryRun) {
|
|
56
|
+
console.log(` → ${item.path} (would ${action === 'deleted' ? 'delete' : 'copy'})`);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
console.log(` ✓ ${item.path}${action === 'copied' ? '' : ` (${action}: ${item.reason})`}`);
|
|
60
|
+
await mkdir(dirname(migrationLogPath), { recursive: true });
|
|
61
|
+
await appendFile(migrationLogPath, JSON.stringify({
|
|
62
|
+
path: item.path, ts: new Date().toISOString(), action, reason: item.reason, protocol: STORAGE_MIGRATION_PROTOCOL,
|
|
63
|
+
}) + '\n', 'utf-8');
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
const receipt = result.receipt;
|
|
68
|
+
const unsupported = receipt.skipped.filter(item => item.reason === 'non-text attachment skipped').length;
|
|
69
|
+
const copied = receipt.copied.length + receipt.recopied.length;
|
|
70
|
+
const skipped = receipt.skipped.length - unsupported;
|
|
71
|
+
if (!result.plan.length)
|
|
72
|
+
console.log('No entries to migrate.');
|
|
73
|
+
console.log('');
|
|
74
|
+
console.log(`Summary: copied=${copied} skipped=${skipped} unsupported=${unsupported} ` +
|
|
75
|
+
`errored=${receipt.errored.length} total=${result.plan.length} ` +
|
|
76
|
+
`recopied=${receipt.recopied.length} deleted=${receipt.deleted.length}`);
|
|
77
|
+
if (!opts.dryRun) {
|
|
78
|
+
console.log(`Migration log: ${migrationLogPath}`);
|
|
79
|
+
console.log(`Migration checkpoint: ${checkpointPath}`);
|
|
80
|
+
console.log(`Migration receipt: ${receiptPath}`);
|
|
81
|
+
}
|
|
82
|
+
if (receipt.errored.length) {
|
|
83
|
+
throw new Error(`${receipt.errored.length} entr${receipt.errored.length === 1 ? 'y' : 'ies'} failed to migrate.`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
await Promise.allSettled([source.close?.(), destination.close?.()]);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function loadCheckpoint(path) {
|
|
91
|
+
try {
|
|
92
|
+
return JSON.parse(await readFile(path, 'utf-8'));
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (error.code === 'ENOENT')
|
|
96
|
+
return undefined;
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function atomicJson(path, value) {
|
|
101
|
+
await mkdir(dirname(path), { recursive: true });
|
|
102
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
103
|
+
try {
|
|
104
|
+
await writeFile(temporary, JSON.stringify(value, null, 2) + '\n', 'utf-8');
|
|
105
|
+
await rename(temporary, path);
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
await rm(temporary, { force: true });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=cli-migrate.js.map
|