@hasna/skills 0.9.1 → 0.9.2
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 +10 -1
- package/bin/index.js +89 -7
- package/bin/mcp.js +1 -1
- package/bin/migrate.js +1 -1
- package/bin/server.js +1 -1
- package/bin/worker.js +1 -1
- package/dist/index.js +35 -5
- package/dist/lib/agent-integration.d.ts +26 -0
- package/dist/sdk/index.js +35 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -271,6 +271,15 @@ Vendor file restoration uses an exclusive hard link to preserve concurrent files
|
|
|
271
271
|
Do not retry an interrupted operation until its journal and both paths have been
|
|
272
272
|
reconciled; a failed final journal write may leave the earlier durable intent.
|
|
273
273
|
|
|
274
|
+
For a reviewed migration that must retire one exact native copy, pass
|
|
275
|
+
`--target-manifest <file>`. The JSON manifest uses schema
|
|
276
|
+
`hasna.skills-native-migration-targets.v1` and binds each target to an agent,
|
|
277
|
+
an existing absolute `projectRoot`, a normalized project-relative `path`, and
|
|
278
|
+
the complete directory `treeSha256`. Exact-target mode refuses broad selector
|
|
279
|
+
flags, protected or vendor entries, missing or ambiguous paths, symlinks,
|
|
280
|
+
digest drift, and writes a manifest digest into the recovery journal. Every
|
|
281
|
+
inventory entry outside the manifest remains untouched.
|
|
282
|
+
|
|
274
283
|
`skills hook agents --json` reports the supported adapters and coverage limits.
|
|
275
284
|
Claude and Codex have lifecycle context hooks; Gemini uses `BeforeAgent`, and
|
|
276
285
|
OpenCode uses its awaited message plugin. Cursor receives selected context at
|
|
@@ -758,7 +767,7 @@ of app folders, and `XDG_CONFIG_HOME` is not consulted at all.
|
|
|
758
767
|
| `skills sessions show <id> --json` | | Inspect one session's exact profile revision and receipt hash without loading payloads |
|
|
759
768
|
| `skills sessions reconcile <id> --from-profile <id> --from-revision <rev> --receipt-sha256 <sha> --selection-profile <id> --profile-revision <rev>` | | Plan an explicit migration of one live session; `--apply --plan-digest <digest> --plan-issued-at <time> --plan-expires-at <time>` (within five minutes) preserves its old receipt and applies the reviewed replacement |
|
|
760
769
|
| `skills hook agents --json` | | Report maintained adapters and explicit coverage limits |
|
|
761
|
-
| `skills migrate native` | | Inventory native copies; `--apply` archives managed copies, with explicit `--include-unmanaged
|
|
770
|
+
| `skills migrate native` | | Inventory native copies; `--apply` archives managed copies, with explicit `--include-unmanaged`, `--include-vendor`, or reviewed `--target-manifest` selection |
|
|
762
771
|
| `skills pull --all --selection-profile <id>` | | With CLI loading active, refresh the selected profile into the verified cache |
|
|
763
772
|
| `skills sync --selection-profile <id> [--check] [--station <id>]` | | Sync or check the selected profile/cache; optionally record station state |
|
|
764
773
|
| `skills station-state <id>` | | Read a station's sync receipt in the authenticated workspace |
|
package/bin/index.js
CHANGED
|
@@ -36868,7 +36868,7 @@ var package_default;
|
|
|
36868
36868
|
var init_package = __esm(() => {
|
|
36869
36869
|
package_default = {
|
|
36870
36870
|
name: "@hasna/skills",
|
|
36871
|
-
version: "0.9.
|
|
36871
|
+
version: "0.9.2",
|
|
36872
36872
|
description: "Skills library for AI coding agents",
|
|
36873
36873
|
type: "module",
|
|
36874
36874
|
bin: {
|
|
@@ -55358,6 +55358,82 @@ function treeHash(root2) {
|
|
|
55358
55358
|
visit2(root2);
|
|
55359
55359
|
return hash2.digest("hex");
|
|
55360
55360
|
}
|
|
55361
|
+
function parseNativeMigrationTargetManifest(input) {
|
|
55362
|
+
const digest2 = sha(input);
|
|
55363
|
+
let value;
|
|
55364
|
+
try {
|
|
55365
|
+
value = JSON.parse(input.toString());
|
|
55366
|
+
} catch {
|
|
55367
|
+
throw new Error("Invalid native migration target manifest JSON");
|
|
55368
|
+
}
|
|
55369
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
55370
|
+
throw new Error("Invalid native migration target manifest");
|
|
55371
|
+
const record2 = value;
|
|
55372
|
+
if (record2.schema !== "hasna.skills-native-migration-targets.v1" || Object.keys(record2).some((key) => !["schema", "targets"].includes(key)))
|
|
55373
|
+
throw new Error("Unsupported native migration target manifest schema");
|
|
55374
|
+
if (!Array.isArray(record2.targets) || record2.targets.length === 0 || record2.targets.length > 1000)
|
|
55375
|
+
throw new Error("Native migration target manifest must contain 1 to 1000 targets");
|
|
55376
|
+
const targets = [];
|
|
55377
|
+
const identities = new Set;
|
|
55378
|
+
for (const target of record2.targets) {
|
|
55379
|
+
if (!target || typeof target !== "object" || Array.isArray(target))
|
|
55380
|
+
throw new Error("Invalid native migration target");
|
|
55381
|
+
const item = target;
|
|
55382
|
+
if (Object.keys(item).some((key) => !["agent", "projectRoot", "path", "treeSha256"].includes(key)) || typeof item.agent !== "string" || typeof item.projectRoot !== "string" || typeof item.path !== "string" || typeof item.treeSha256 !== "string")
|
|
55383
|
+
throw new Error("Invalid native migration target fields");
|
|
55384
|
+
if (!INTEGRATION_AGENTS.includes(item.agent))
|
|
55385
|
+
throw new Error(`Unsupported native migration target agent: ${item.agent}`);
|
|
55386
|
+
const projectRoot = resolve16(item.projectRoot);
|
|
55387
|
+
if (!isAbsolute15(item.projectRoot) || projectRoot !== item.projectRoot || !existsSync15(projectRoot) || !lstatSync18(projectRoot).isDirectory())
|
|
55388
|
+
throw new Error(`Native migration target project root must be an existing absolute directory: ${item.projectRoot}`);
|
|
55389
|
+
assertSafePath(projectRoot);
|
|
55390
|
+
if (isAbsolute15(item.path) || item.path.includes("\\") || item.path.length === 0 || item.path.includes("\x00"))
|
|
55391
|
+
throw new Error(`Native migration target path must be normalized and project-relative: ${item.path}`);
|
|
55392
|
+
const resolved = resolve16(projectRoot, item.path), normalized = relative5(projectRoot, resolved);
|
|
55393
|
+
if (!normalized || normalized === ".." || normalized.startsWith(`..${sep6}`) || isAbsolute15(normalized) || normalized !== item.path)
|
|
55394
|
+
throw new Error(`Native migration target path escapes or is not normalized: ${item.path}`);
|
|
55395
|
+
if (!/^[0-9a-f]{64}$/.test(item.treeSha256))
|
|
55396
|
+
throw new Error(`Invalid native migration tree digest for ${item.path}`);
|
|
55397
|
+
const identity4 = `${item.agent}\x00${projectRoot}\x00${normalized}`;
|
|
55398
|
+
if (identities.has(identity4))
|
|
55399
|
+
throw new Error(`Duplicate native migration target: ${item.path}`);
|
|
55400
|
+
identities.add(identity4);
|
|
55401
|
+
targets.push({ agent: item.agent, projectRoot, path: normalized, treeSha256: item.treeSha256 });
|
|
55402
|
+
}
|
|
55403
|
+
return { schema: "hasna.skills-native-migration-targets.v1", targets, digest: digest2 };
|
|
55404
|
+
}
|
|
55405
|
+
function readNativeMigrationTargetManifest(path) {
|
|
55406
|
+
return parseNativeMigrationTargetManifest(readNativeBytes(path, 4 * 1024 * 1024));
|
|
55407
|
+
}
|
|
55408
|
+
function selectNativeMigrationTargets(inventory, manifest) {
|
|
55409
|
+
if (manifest.schema !== "hasna.skills-native-migration-targets.v1" || !/^[0-9a-f]{64}$/.test(manifest.digest) || !Array.isArray(manifest.targets) || manifest.targets.length === 0)
|
|
55410
|
+
throw new Error("Invalid native migration target manifest");
|
|
55411
|
+
const identities = new Set;
|
|
55412
|
+
const selected = [];
|
|
55413
|
+
for (const target of manifest.targets) {
|
|
55414
|
+
const projectRoot = resolve16(target.projectRoot), normalized = relative5(projectRoot, resolve16(projectRoot, target.path));
|
|
55415
|
+
if (!isAbsolute15(target.projectRoot) || projectRoot !== target.projectRoot || !existsSync15(projectRoot) || !lstatSync18(projectRoot).isDirectory())
|
|
55416
|
+
throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
|
|
55417
|
+
assertSafePath(projectRoot);
|
|
55418
|
+
if (isAbsolute15(target.path) || target.path.includes("\\") || target.path.includes("\x00") || !normalized || normalized === ".." || normalized.startsWith(`..${sep6}`) || normalized !== target.path || !/^[0-9a-f]{64}$/.test(target.treeSha256))
|
|
55419
|
+
throw new Error(`Invalid native migration target: ${target.path}`);
|
|
55420
|
+
const identity4 = `${target.agent}\x00${projectRoot}\x00${normalized}`;
|
|
55421
|
+
if (identities.has(identity4))
|
|
55422
|
+
throw new Error(`Duplicate native migration target: ${target.path}`);
|
|
55423
|
+
identities.add(identity4);
|
|
55424
|
+
const expected = resolve16(target.projectRoot, target.path);
|
|
55425
|
+
const matches2 = inventory.filter((entry2) => entry2.agent === target.agent && resolve16(entry2.path) === expected);
|
|
55426
|
+
if (matches2.length !== 1)
|
|
55427
|
+
throw new Error(`Native migration target was not found exactly once: ${target.agent} ${target.projectRoot}/${target.path}`);
|
|
55428
|
+
const entry = matches2[0];
|
|
55429
|
+
if (entry.bridge || entry.system || entry.vendor)
|
|
55430
|
+
throw new Error(`Native migration target is protected or vendor-owned: ${entry.path}`);
|
|
55431
|
+
if (entry.hash !== target.treeSha256 || treeHash(entry.path) !== target.treeSha256)
|
|
55432
|
+
throw new Error(`Native migration target changed after review: ${entry.path}`);
|
|
55433
|
+
selected.push(entry);
|
|
55434
|
+
}
|
|
55435
|
+
return selected;
|
|
55436
|
+
}
|
|
55361
55437
|
function projectAncestorDirectories(projects) {
|
|
55362
55438
|
const directories = new Set;
|
|
55363
55439
|
for (const project of projects) {
|
|
@@ -55846,12 +55922,13 @@ function archiveNativeSkills(inventory, options) {
|
|
|
55846
55922
|
if (!expected || treeHash(entry.path) !== entry.hash || !isOwnedCliBridge(entry.path, [expected]))
|
|
55847
55923
|
throw new Error(`Skills bridge changed after planning: ${entry.path}`);
|
|
55848
55924
|
}
|
|
55849
|
-
const selected = inventory.filter((entry) => !entry.bridge && !entry.system && (entry.vendor ? options.includeVendor : entry.managed || options.includeUnmanaged));
|
|
55925
|
+
const selected = options.targetManifest ? selectNativeMigrationTargets(inventory, options.targetManifest) : inventory.filter((entry) => !entry.bridge && !entry.system && (entry.vendor ? options.includeVendor : entry.managed || options.includeUnmanaged));
|
|
55850
55926
|
for (const entry of selected)
|
|
55851
55927
|
if (treeHash(entry.path) !== entry.hash)
|
|
55852
55928
|
throw new Error(`Native skill changed after planning: ${entry.path}`);
|
|
55929
|
+
const targetManifest = options.targetManifest ? { schema: options.targetManifest.schema, digest: options.targetManifest.digest, targetCount: options.targetManifest.targets.length } : undefined;
|
|
55853
55930
|
if (!selected.length)
|
|
55854
|
-
return { entries: [], ...aliases.length ? { rootAliases: aliases } : {} };
|
|
55931
|
+
return { entries: [], ...targetManifest ? { targetManifest } : {}, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
55855
55932
|
const operationId = randomUUID6(), archiveRoot = join29(options.dataDir ?? getDataDir(), "migration", operationId, "native"), receiptPath = join29(archiveRoot, "receipt.json");
|
|
55856
55933
|
const entries = selected.map((entry, index) => ({
|
|
55857
55934
|
source: entry.vendor ? join29(entry.path, "SKILL.md") : entry.path,
|
|
@@ -55860,7 +55937,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
55860
55937
|
...entry.vendor ? { discoveryOnly: true } : {},
|
|
55861
55938
|
status: "planned"
|
|
55862
55939
|
}));
|
|
55863
|
-
const journal = { version: 2, operationId, status: "planned", entries, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
55940
|
+
const journal = { version: 2, operationId, status: "planned", entries, ...targetManifest ? { targetManifest } : {}, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
55864
55941
|
const moved = [];
|
|
55865
55942
|
const verifyArchive = (entry) => {
|
|
55866
55943
|
assertSafePath(entry.archive);
|
|
@@ -55954,7 +56031,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
55954
56031
|
} catch {}
|
|
55955
56032
|
throw new Error(`Native archive failed; inspect the recovery journal at ${receiptPath} before retrying.`, { cause: error });
|
|
55956
56033
|
}
|
|
55957
|
-
return { entries: entries.map(({ status, conflict, ...entry }) => entry), receiptPath, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
56034
|
+
return { entries: entries.map(({ status, conflict, ...entry }) => entry), receiptPath, ...targetManifest ? { targetManifest } : {}, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
55958
56035
|
}
|
|
55959
56036
|
function assertManagedAgentBridge(agent, options = {}) {
|
|
55960
56037
|
const home = resolve16(options.home ?? homedir4()), dataDir3 = options.dataDir ?? getDataDirReadOnly();
|
|
@@ -108541,11 +108618,16 @@ function registerAgentIntegration(parent) {
|
|
|
108541
108618
|
}
|
|
108542
108619
|
});
|
|
108543
108620
|
const migrate = parent.command("migrate").description("Preserve and retire native agent skill copies");
|
|
108544
|
-
migrate.command("native").option("--project <directory>", "Also inventory a project and its ancestors (the current directory and its ancestors are always included)").option("--include-unmanaged", "Archive user-authored skills as well as Skills-managed copies", false).option("--include-vendor", "Retire vendor SKILL.md discovery files while preserving plugin scripts and assets", false).option("--discovery-inputs <file>", "Advanced reviewed active plugin roots and source hashes").option("--allow-root-aliases", "Allow home .claude/.codex aliases to existing directories within this home", false).option("--apply", "Move selected skills to private archives outside agent discovery roots", false).option("--json", "Output inventory and archive receipt as JSON", false).description("Inventory native skill copies; preserve complete directories before retiring them").action(async (options) => {
|
|
108621
|
+
migrate.command("native").option("--project <directory>", "Also inventory a project and its ancestors (the current directory and its ancestors are always included)").option("--include-unmanaged", "Archive user-authored skills as well as Skills-managed copies", false).option("--include-vendor", "Retire vendor SKILL.md discovery files while preserving plugin scripts and assets", false).option("--target-manifest <file>", "Reviewed exact native migration target manifest").option("--discovery-inputs <file>", "Advanced reviewed active plugin roots and source hashes").option("--allow-root-aliases", "Allow home .claude/.codex aliases to existing directories within this home", false).option("--apply", "Move selected skills to private archives outside agent discovery roots", false).option("--json", "Output inventory and archive receipt as JSON", false).description("Inventory native skill copies; preserve complete directories before retiring them").action(async (options) => {
|
|
108545
108622
|
try {
|
|
108546
108623
|
const discoveryInputs = options.discoveryInputs ? JSON.parse(readFileSync37(options.discoveryInputs, "utf8")) : undefined;
|
|
108624
|
+
if (options.targetManifest && (options.includeUnmanaged || options.includeVendor))
|
|
108625
|
+
throw new Error("--target-manifest cannot be combined with broad native migration selectors");
|
|
108626
|
+
const targetManifest = options.targetManifest ? readNativeMigrationTargetManifest(options.targetManifest) : undefined;
|
|
108547
108627
|
const inventory = inventoryNativeSkills(undefined, { projectDirs: [process.cwd(), ...options.project ? [options.project] : []], includeVendor: options.includeVendor, configured: options.includeVendor, discoveryInputs, allowRootAliases: options.allowRootAliases });
|
|
108548
|
-
|
|
108628
|
+
if (targetManifest)
|
|
108629
|
+
selectNativeMigrationTargets(inventory, targetManifest);
|
|
108630
|
+
const result2 = options.apply ? archiveNativeSkills(inventory, { includeUnmanaged: options.includeUnmanaged, includeVendor: options.includeVendor, allowRootAliases: options.allowRootAliases, targetManifest }) : { entries: [], ...targetManifest ? { targetManifest: { schema: targetManifest.schema, digest: targetManifest.digest, targetCount: targetManifest.targets.length } } : {} };
|
|
108549
108631
|
if (options.json)
|
|
108550
108632
|
await writeCliOutput(JSON.stringify({ applied: options.apply, inventory, ...result2 }));
|
|
108551
108633
|
else
|
package/bin/mcp.js
CHANGED
|
@@ -20019,7 +20019,7 @@ var package_default;
|
|
|
20019
20019
|
var init_package = __esm(() => {
|
|
20020
20020
|
package_default = {
|
|
20021
20021
|
name: "@hasna/skills",
|
|
20022
|
-
version: "0.9.
|
|
20022
|
+
version: "0.9.2",
|
|
20023
20023
|
description: "Skills library for AI coding agents",
|
|
20024
20024
|
type: "module",
|
|
20025
20025
|
bin: {
|
package/bin/migrate.js
CHANGED
package/bin/server.js
CHANGED
|
@@ -24122,7 +24122,7 @@ var init_dist_es9 = __esm(() => {
|
|
|
24122
24122
|
// package.json
|
|
24123
24123
|
var package_default = {
|
|
24124
24124
|
name: "@hasna/skills",
|
|
24125
|
-
version: "0.9.
|
|
24125
|
+
version: "0.9.2",
|
|
24126
24126
|
description: "Skills library for AI coding agents",
|
|
24127
24127
|
type: "module",
|
|
24128
24128
|
bin: {
|
package/bin/worker.js
CHANGED
|
@@ -24125,7 +24125,7 @@ import { randomUUID as randomUUID6 } from "crypto";
|
|
|
24125
24125
|
// package.json
|
|
24126
24126
|
var package_default = {
|
|
24127
24127
|
name: "@hasna/skills",
|
|
24128
|
-
version: "0.9.
|
|
24128
|
+
version: "0.9.2",
|
|
24129
24129
|
description: "Skills library for AI coding agents",
|
|
24130
24130
|
type: "module",
|
|
24131
24131
|
bin: {
|
package/dist/index.js
CHANGED
|
@@ -11448,7 +11448,7 @@ import { isAbsolute as isAbsolute10, join as join20, resolve as resolve10 } from
|
|
|
11448
11448
|
// package.json
|
|
11449
11449
|
var package_default = {
|
|
11450
11450
|
name: "@hasna/skills",
|
|
11451
|
-
version: "0.9.
|
|
11451
|
+
version: "0.9.2",
|
|
11452
11452
|
description: "Skills library for AI coding agents",
|
|
11453
11453
|
type: "module",
|
|
11454
11454
|
bin: {
|
|
@@ -15738,6 +15738,35 @@ function treeHash(root2) {
|
|
|
15738
15738
|
visit2(root2);
|
|
15739
15739
|
return hash2.digest("hex");
|
|
15740
15740
|
}
|
|
15741
|
+
function selectNativeMigrationTargets(inventory, manifest) {
|
|
15742
|
+
if (manifest.schema !== "hasna.skills-native-migration-targets.v1" || !/^[0-9a-f]{64}$/.test(manifest.digest) || !Array.isArray(manifest.targets) || manifest.targets.length === 0)
|
|
15743
|
+
throw new Error("Invalid native migration target manifest");
|
|
15744
|
+
const identities = new Set;
|
|
15745
|
+
const selected = [];
|
|
15746
|
+
for (const target of manifest.targets) {
|
|
15747
|
+
const projectRoot = resolve15(target.projectRoot), normalized = relative5(projectRoot, resolve15(projectRoot, target.path));
|
|
15748
|
+
if (!isAbsolute15(target.projectRoot) || projectRoot !== target.projectRoot || !existsSync11(projectRoot) || !lstatSync18(projectRoot).isDirectory())
|
|
15749
|
+
throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
|
|
15750
|
+
assertSafePath(projectRoot);
|
|
15751
|
+
if (isAbsolute15(target.path) || target.path.includes("\\") || target.path.includes("\x00") || !normalized || normalized === ".." || normalized.startsWith(`..${sep6}`) || normalized !== target.path || !/^[0-9a-f]{64}$/.test(target.treeSha256))
|
|
15752
|
+
throw new Error(`Invalid native migration target: ${target.path}`);
|
|
15753
|
+
const identity3 = `${target.agent}\x00${projectRoot}\x00${normalized}`;
|
|
15754
|
+
if (identities.has(identity3))
|
|
15755
|
+
throw new Error(`Duplicate native migration target: ${target.path}`);
|
|
15756
|
+
identities.add(identity3);
|
|
15757
|
+
const expected = resolve15(target.projectRoot, target.path);
|
|
15758
|
+
const matches = inventory.filter((entry2) => entry2.agent === target.agent && resolve15(entry2.path) === expected);
|
|
15759
|
+
if (matches.length !== 1)
|
|
15760
|
+
throw new Error(`Native migration target was not found exactly once: ${target.agent} ${target.projectRoot}/${target.path}`);
|
|
15761
|
+
const entry = matches[0];
|
|
15762
|
+
if (entry.bridge || entry.system || entry.vendor)
|
|
15763
|
+
throw new Error(`Native migration target is protected or vendor-owned: ${entry.path}`);
|
|
15764
|
+
if (entry.hash !== target.treeSha256 || treeHash(entry.path) !== target.treeSha256)
|
|
15765
|
+
throw new Error(`Native migration target changed after review: ${entry.path}`);
|
|
15766
|
+
selected.push(entry);
|
|
15767
|
+
}
|
|
15768
|
+
return selected;
|
|
15769
|
+
}
|
|
15741
15770
|
function projectAncestorDirectories(projects) {
|
|
15742
15771
|
const directories = new Set;
|
|
15743
15772
|
for (const project of projects) {
|
|
@@ -16226,12 +16255,13 @@ function archiveNativeSkills(inventory, options) {
|
|
|
16226
16255
|
if (!expected || treeHash(entry.path) !== entry.hash || !isOwnedCliBridge(entry.path, [expected]))
|
|
16227
16256
|
throw new Error(`Skills bridge changed after planning: ${entry.path}`);
|
|
16228
16257
|
}
|
|
16229
|
-
const selected = inventory.filter((entry) => !entry.bridge && !entry.system && (entry.vendor ? options.includeVendor : entry.managed || options.includeUnmanaged));
|
|
16258
|
+
const selected = options.targetManifest ? selectNativeMigrationTargets(inventory, options.targetManifest) : inventory.filter((entry) => !entry.bridge && !entry.system && (entry.vendor ? options.includeVendor : entry.managed || options.includeUnmanaged));
|
|
16230
16259
|
for (const entry of selected)
|
|
16231
16260
|
if (treeHash(entry.path) !== entry.hash)
|
|
16232
16261
|
throw new Error(`Native skill changed after planning: ${entry.path}`);
|
|
16262
|
+
const targetManifest = options.targetManifest ? { schema: options.targetManifest.schema, digest: options.targetManifest.digest, targetCount: options.targetManifest.targets.length } : undefined;
|
|
16233
16263
|
if (!selected.length)
|
|
16234
|
-
return { entries: [], ...aliases.length ? { rootAliases: aliases } : {} };
|
|
16264
|
+
return { entries: [], ...targetManifest ? { targetManifest } : {}, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
16235
16265
|
const operationId = randomUUID3(), archiveRoot = join25(options.dataDir ?? getDataDir(), "migration", operationId, "native"), receiptPath = join25(archiveRoot, "receipt.json");
|
|
16236
16266
|
const entries = selected.map((entry, index) => ({
|
|
16237
16267
|
source: entry.vendor ? join25(entry.path, "SKILL.md") : entry.path,
|
|
@@ -16240,7 +16270,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
16240
16270
|
...entry.vendor ? { discoveryOnly: true } : {},
|
|
16241
16271
|
status: "planned"
|
|
16242
16272
|
}));
|
|
16243
|
-
const journal = { version: 2, operationId, status: "planned", entries, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
16273
|
+
const journal = { version: 2, operationId, status: "planned", entries, ...targetManifest ? { targetManifest } : {}, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
16244
16274
|
const moved = [];
|
|
16245
16275
|
const verifyArchive = (entry) => {
|
|
16246
16276
|
assertSafePath(entry.archive);
|
|
@@ -16334,7 +16364,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
16334
16364
|
} catch {}
|
|
16335
16365
|
throw new Error(`Native archive failed; inspect the recovery journal at ${receiptPath} before retrying.`, { cause: error });
|
|
16336
16366
|
}
|
|
16337
|
-
return { entries: entries.map(({ status, conflict, ...entry }) => entry), receiptPath, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
16367
|
+
return { entries: entries.map(({ status, conflict, ...entry }) => entry), receiptPath, ...targetManifest ? { targetManifest } : {}, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
16338
16368
|
}
|
|
16339
16369
|
function assertNativeExportAllowed(dataDir2 = getDataDirReadOnly()) {
|
|
16340
16370
|
if (requiresCliSkillLoading(dataDir2))
|
|
@@ -22,6 +22,17 @@ export interface NativeSkillEntry {
|
|
|
22
22
|
bridgeHome?: string;
|
|
23
23
|
rootAlias?: AgentRootAlias;
|
|
24
24
|
}
|
|
25
|
+
export interface NativeMigrationTarget {
|
|
26
|
+
agent: string;
|
|
27
|
+
projectRoot: string;
|
|
28
|
+
path: string;
|
|
29
|
+
treeSha256: string;
|
|
30
|
+
}
|
|
31
|
+
export interface NativeMigrationTargetManifest {
|
|
32
|
+
schema: "hasna.skills-native-migration-targets.v1";
|
|
33
|
+
targets: NativeMigrationTarget[];
|
|
34
|
+
digest: string;
|
|
35
|
+
}
|
|
25
36
|
export interface AgentConfigChange {
|
|
26
37
|
path: string;
|
|
27
38
|
before: string | null;
|
|
@@ -40,6 +51,15 @@ export interface AgentIntegrationPlan {
|
|
|
40
51
|
discoveryAfter?: AgentDiscoveryBinding[];
|
|
41
52
|
rootAliases?: AgentRootAlias[];
|
|
42
53
|
}
|
|
54
|
+
/** Parse the reviewed exact-target binding used by native migration. Paths are
|
|
55
|
+
* deliberately project-relative so a manifest cannot silently select another
|
|
56
|
+
* checkout when a project root changes. */
|
|
57
|
+
export declare function parseNativeMigrationTargetManifest(input: string | Buffer): NativeMigrationTargetManifest;
|
|
58
|
+
/** Read a manifest without following links or opening special files, with a
|
|
59
|
+
* finite bound so a CLI cannot block on an untrusted path. */
|
|
60
|
+
export declare function readNativeMigrationTargetManifest(path: string): NativeMigrationTargetManifest;
|
|
61
|
+
/** Validate every reviewed target before any archive directory or journal is created. */
|
|
62
|
+
export declare function selectNativeMigrationTargets(inventory: NativeSkillEntry[], manifest: NativeMigrationTargetManifest): NativeSkillEntry[];
|
|
43
63
|
export declare function inventoryNativeSkills(home?: string, options?: {
|
|
44
64
|
includeVendor?: boolean;
|
|
45
65
|
guardHermes?: boolean;
|
|
@@ -75,6 +95,7 @@ export declare function archiveNativeSkills(inventory: NativeSkillEntry[], optio
|
|
|
75
95
|
includeUnmanaged?: boolean;
|
|
76
96
|
includeVendor?: boolean;
|
|
77
97
|
allowRootAliases?: boolean;
|
|
98
|
+
targetManifest?: NativeMigrationTargetManifest;
|
|
78
99
|
}): {
|
|
79
100
|
entries: Array<{
|
|
80
101
|
source: string;
|
|
@@ -84,6 +105,11 @@ export declare function archiveNativeSkills(inventory: NativeSkillEntry[], optio
|
|
|
84
105
|
}>;
|
|
85
106
|
receiptPath?: string;
|
|
86
107
|
rootAliases?: AgentRootAlias[];
|
|
108
|
+
targetManifest?: {
|
|
109
|
+
schema: string;
|
|
110
|
+
digest: string;
|
|
111
|
+
targetCount: number;
|
|
112
|
+
};
|
|
87
113
|
};
|
|
88
114
|
/** Run before any prompt context load. Missing ownership or reappearing native
|
|
89
115
|
* discovery files require repair; verified cache availability is not an override. */
|
package/dist/sdk/index.js
CHANGED
|
@@ -32373,7 +32373,7 @@ var init_event_sink = __esm(() => {
|
|
|
32373
32373
|
// package.json
|
|
32374
32374
|
var package_default = {
|
|
32375
32375
|
name: "@hasna/skills",
|
|
32376
|
-
version: "0.9.
|
|
32376
|
+
version: "0.9.2",
|
|
32377
32377
|
description: "Skills library for AI coding agents",
|
|
32378
32378
|
type: "module",
|
|
32379
32379
|
bin: {
|
|
@@ -40661,6 +40661,35 @@ function treeHash(root2) {
|
|
|
40661
40661
|
visit2(root2);
|
|
40662
40662
|
return hash2.digest("hex");
|
|
40663
40663
|
}
|
|
40664
|
+
function selectNativeMigrationTargets(inventory, manifest) {
|
|
40665
|
+
if (manifest.schema !== "hasna.skills-native-migration-targets.v1" || !/^[0-9a-f]{64}$/.test(manifest.digest) || !Array.isArray(manifest.targets) || manifest.targets.length === 0)
|
|
40666
|
+
throw new Error("Invalid native migration target manifest");
|
|
40667
|
+
const identities = new Set;
|
|
40668
|
+
const selected = [];
|
|
40669
|
+
for (const target of manifest.targets) {
|
|
40670
|
+
const projectRoot = resolve14(target.projectRoot), normalized = relative3(projectRoot, resolve14(projectRoot, target.path));
|
|
40671
|
+
if (!isAbsolute15(target.projectRoot) || projectRoot !== target.projectRoot || !existsSync6(projectRoot) || !lstatSync17(projectRoot).isDirectory())
|
|
40672
|
+
throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
|
|
40673
|
+
assertSafePath(projectRoot);
|
|
40674
|
+
if (isAbsolute15(target.path) || target.path.includes("\\") || target.path.includes("\x00") || !normalized || normalized === ".." || normalized.startsWith(`..${sep4}`) || normalized !== target.path || !/^[0-9a-f]{64}$/.test(target.treeSha256))
|
|
40675
|
+
throw new Error(`Invalid native migration target: ${target.path}`);
|
|
40676
|
+
const identity3 = `${target.agent}\x00${projectRoot}\x00${normalized}`;
|
|
40677
|
+
if (identities.has(identity3))
|
|
40678
|
+
throw new Error(`Duplicate native migration target: ${target.path}`);
|
|
40679
|
+
identities.add(identity3);
|
|
40680
|
+
const expected = resolve14(target.projectRoot, target.path);
|
|
40681
|
+
const matches = inventory.filter((entry2) => entry2.agent === target.agent && resolve14(entry2.path) === expected);
|
|
40682
|
+
if (matches.length !== 1)
|
|
40683
|
+
throw new Error(`Native migration target was not found exactly once: ${target.agent} ${target.projectRoot}/${target.path}`);
|
|
40684
|
+
const entry = matches[0];
|
|
40685
|
+
if (entry.bridge || entry.system || entry.vendor)
|
|
40686
|
+
throw new Error(`Native migration target is protected or vendor-owned: ${entry.path}`);
|
|
40687
|
+
if (entry.hash !== target.treeSha256 || treeHash(entry.path) !== target.treeSha256)
|
|
40688
|
+
throw new Error(`Native migration target changed after review: ${entry.path}`);
|
|
40689
|
+
selected.push(entry);
|
|
40690
|
+
}
|
|
40691
|
+
return selected;
|
|
40692
|
+
}
|
|
40664
40693
|
function projectAncestorDirectories(projects) {
|
|
40665
40694
|
const directories = new Set;
|
|
40666
40695
|
for (const project of projects) {
|
|
@@ -41149,12 +41178,13 @@ function archiveNativeSkills(inventory, options) {
|
|
|
41149
41178
|
if (!expected || treeHash(entry.path) !== entry.hash || !isOwnedCliBridge(entry.path, [expected]))
|
|
41150
41179
|
throw new Error(`Skills bridge changed after planning: ${entry.path}`);
|
|
41151
41180
|
}
|
|
41152
|
-
const selected = inventory.filter((entry) => !entry.bridge && !entry.system && (entry.vendor ? options.includeVendor : entry.managed || options.includeUnmanaged));
|
|
41181
|
+
const selected = options.targetManifest ? selectNativeMigrationTargets(inventory, options.targetManifest) : inventory.filter((entry) => !entry.bridge && !entry.system && (entry.vendor ? options.includeVendor : entry.managed || options.includeUnmanaged));
|
|
41153
41182
|
for (const entry of selected)
|
|
41154
41183
|
if (treeHash(entry.path) !== entry.hash)
|
|
41155
41184
|
throw new Error(`Native skill changed after planning: ${entry.path}`);
|
|
41185
|
+
const targetManifest = options.targetManifest ? { schema: options.targetManifest.schema, digest: options.targetManifest.digest, targetCount: options.targetManifest.targets.length } : undefined;
|
|
41156
41186
|
if (!selected.length)
|
|
41157
|
-
return { entries: [], ...aliases.length ? { rootAliases: aliases } : {} };
|
|
41187
|
+
return { entries: [], ...targetManifest ? { targetManifest } : {}, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
41158
41188
|
const operationId = randomUUID3(), archiveRoot = join21(options.dataDir ?? getDataDir(), "migration", operationId, "native"), receiptPath = join21(archiveRoot, "receipt.json");
|
|
41159
41189
|
const entries = selected.map((entry, index) => ({
|
|
41160
41190
|
source: entry.vendor ? join21(entry.path, "SKILL.md") : entry.path,
|
|
@@ -41163,7 +41193,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
41163
41193
|
...entry.vendor ? { discoveryOnly: true } : {},
|
|
41164
41194
|
status: "planned"
|
|
41165
41195
|
}));
|
|
41166
|
-
const journal = { version: 2, operationId, status: "planned", entries, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
41196
|
+
const journal = { version: 2, operationId, status: "planned", entries, ...targetManifest ? { targetManifest } : {}, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
41167
41197
|
const moved = [];
|
|
41168
41198
|
const verifyArchive = (entry) => {
|
|
41169
41199
|
assertSafePath(entry.archive);
|
|
@@ -41257,7 +41287,7 @@ function archiveNativeSkills(inventory, options) {
|
|
|
41257
41287
|
} catch {}
|
|
41258
41288
|
throw new Error(`Native archive failed; inspect the recovery journal at ${receiptPath} before retrying.`, { cause: error });
|
|
41259
41289
|
}
|
|
41260
|
-
return { entries: entries.map(({ status, conflict, ...entry }) => entry), receiptPath, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
41290
|
+
return { entries: entries.map(({ status, conflict, ...entry }) => entry), receiptPath, ...targetManifest ? { targetManifest } : {}, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
41261
41291
|
}
|
|
41262
41292
|
function assertNativeExportAllowed(dataDir2 = getDataDirReadOnly()) {
|
|
41263
41293
|
if (requiresCliSkillLoading(dataDir2))
|