@hasna/skills 0.9.1 → 0.9.3

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 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` and `--include-vendor` retirement options |
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.1",
36871
+ version: "0.9.3",
36872
36872
  description: "Skills library for AI coding agents",
36873
36873
  type: "module",
36874
36874
  bin: {
@@ -36877,6 +36877,7 @@ var init_package = __esm(() => {
36877
36877
  "skills-serve": "bin/server.js",
36878
36878
  "skills-server": "bin/server.js",
36879
36879
  "skills-worker": "bin/worker.js",
36880
+ "skills-maintenance": "bin/maintenance.js",
36880
36881
  "skills-migrate": "bin/migrate.js"
36881
36882
  },
36882
36883
  exports: {
@@ -36917,7 +36918,7 @@ var init_package = __esm(() => {
36917
36918
  types: "./dist/index.d.ts",
36918
36919
  scripts: {
36919
36920
  clean: "rm -rf bin/ dist/",
36920
- build: "bun run clean && bun build ./src/cli/index.tsx --outdir ./bin --target bun && bun build ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build ./src/server/index.ts --outfile ./bin/server.js --target bun && bun build ./src/server/worker.ts --outfile ./bin/worker.js --target bun && bun build ./src/server/migrate.ts --outfile ./bin/migrate.js --target bun && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
36921
+ build: "bun run clean && bun build ./src/cli/index.tsx --outdir ./bin --target bun && bun build ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build ./src/server/index.ts --outfile ./bin/server.js --target bun && bun build ./src/server/worker.ts --outfile ./bin/worker.js --target bun && bun build ./src/server/migrate.ts --outfile ./bin/migrate.js --target bun && bun build ./src/server/maintenance.ts --outfile ./bin/maintenance.js --target bun && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
36921
36922
  "build:js": "rm -rf dist && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
36922
36923
  test: "bun test --timeout 30000",
36923
36924
  dev: "bun run ./src/cli/index.tsx",
@@ -55358,6 +55359,82 @@ function treeHash(root2) {
55358
55359
  visit2(root2);
55359
55360
  return hash2.digest("hex");
55360
55361
  }
55362
+ function parseNativeMigrationTargetManifest(input) {
55363
+ const digest2 = sha(input);
55364
+ let value;
55365
+ try {
55366
+ value = JSON.parse(input.toString());
55367
+ } catch {
55368
+ throw new Error("Invalid native migration target manifest JSON");
55369
+ }
55370
+ if (!value || typeof value !== "object" || Array.isArray(value))
55371
+ throw new Error("Invalid native migration target manifest");
55372
+ const record2 = value;
55373
+ if (record2.schema !== "hasna.skills-native-migration-targets.v1" || Object.keys(record2).some((key) => !["schema", "targets"].includes(key)))
55374
+ throw new Error("Unsupported native migration target manifest schema");
55375
+ if (!Array.isArray(record2.targets) || record2.targets.length === 0 || record2.targets.length > 1000)
55376
+ throw new Error("Native migration target manifest must contain 1 to 1000 targets");
55377
+ const targets = [];
55378
+ const identities = new Set;
55379
+ for (const target of record2.targets) {
55380
+ if (!target || typeof target !== "object" || Array.isArray(target))
55381
+ throw new Error("Invalid native migration target");
55382
+ const item = target;
55383
+ 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")
55384
+ throw new Error("Invalid native migration target fields");
55385
+ if (!INTEGRATION_AGENTS.includes(item.agent))
55386
+ throw new Error(`Unsupported native migration target agent: ${item.agent}`);
55387
+ const projectRoot = resolve16(item.projectRoot);
55388
+ if (!isAbsolute15(item.projectRoot) || projectRoot !== item.projectRoot || !existsSync15(projectRoot) || !lstatSync18(projectRoot).isDirectory())
55389
+ throw new Error(`Native migration target project root must be an existing absolute directory: ${item.projectRoot}`);
55390
+ assertSafePath(projectRoot);
55391
+ if (isAbsolute15(item.path) || item.path.includes("\\") || item.path.length === 0 || item.path.includes("\x00"))
55392
+ throw new Error(`Native migration target path must be normalized and project-relative: ${item.path}`);
55393
+ const resolved = resolve16(projectRoot, item.path), normalized = relative5(projectRoot, resolved);
55394
+ if (!normalized || normalized === ".." || normalized.startsWith(`..${sep6}`) || isAbsolute15(normalized) || normalized !== item.path)
55395
+ throw new Error(`Native migration target path escapes or is not normalized: ${item.path}`);
55396
+ if (!/^[0-9a-f]{64}$/.test(item.treeSha256))
55397
+ throw new Error(`Invalid native migration tree digest for ${item.path}`);
55398
+ const identity4 = `${item.agent}\x00${projectRoot}\x00${normalized}`;
55399
+ if (identities.has(identity4))
55400
+ throw new Error(`Duplicate native migration target: ${item.path}`);
55401
+ identities.add(identity4);
55402
+ targets.push({ agent: item.agent, projectRoot, path: normalized, treeSha256: item.treeSha256 });
55403
+ }
55404
+ return { schema: "hasna.skills-native-migration-targets.v1", targets, digest: digest2 };
55405
+ }
55406
+ function readNativeMigrationTargetManifest(path) {
55407
+ return parseNativeMigrationTargetManifest(readNativeBytes(path, 4 * 1024 * 1024));
55408
+ }
55409
+ function selectNativeMigrationTargets(inventory, manifest) {
55410
+ if (manifest.schema !== "hasna.skills-native-migration-targets.v1" || !/^[0-9a-f]{64}$/.test(manifest.digest) || !Array.isArray(manifest.targets) || manifest.targets.length === 0)
55411
+ throw new Error("Invalid native migration target manifest");
55412
+ const identities = new Set;
55413
+ const selected = [];
55414
+ for (const target of manifest.targets) {
55415
+ const projectRoot = resolve16(target.projectRoot), normalized = relative5(projectRoot, resolve16(projectRoot, target.path));
55416
+ if (!isAbsolute15(target.projectRoot) || projectRoot !== target.projectRoot || !existsSync15(projectRoot) || !lstatSync18(projectRoot).isDirectory())
55417
+ throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
55418
+ assertSafePath(projectRoot);
55419
+ 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))
55420
+ throw new Error(`Invalid native migration target: ${target.path}`);
55421
+ const identity4 = `${target.agent}\x00${projectRoot}\x00${normalized}`;
55422
+ if (identities.has(identity4))
55423
+ throw new Error(`Duplicate native migration target: ${target.path}`);
55424
+ identities.add(identity4);
55425
+ const expected = resolve16(target.projectRoot, target.path);
55426
+ const matches2 = inventory.filter((entry2) => entry2.agent === target.agent && resolve16(entry2.path) === expected);
55427
+ if (matches2.length !== 1)
55428
+ throw new Error(`Native migration target was not found exactly once: ${target.agent} ${target.projectRoot}/${target.path}`);
55429
+ const entry = matches2[0];
55430
+ if (entry.bridge || entry.system || entry.vendor)
55431
+ throw new Error(`Native migration target is protected or vendor-owned: ${entry.path}`);
55432
+ if (entry.hash !== target.treeSha256 || treeHash(entry.path) !== target.treeSha256)
55433
+ throw new Error(`Native migration target changed after review: ${entry.path}`);
55434
+ selected.push(entry);
55435
+ }
55436
+ return selected;
55437
+ }
55361
55438
  function projectAncestorDirectories(projects) {
55362
55439
  const directories = new Set;
55363
55440
  for (const project of projects) {
@@ -55375,10 +55452,12 @@ function projectAncestorDirectories(projects) {
55375
55452
  }
55376
55453
  function inventoryNativeSkills(home = homedir4(), options = {}) {
55377
55454
  const aliases = rootAliases(home, options.allowRootAliases);
55378
- const roots = ROOTS.map(([agent, path]) => [agent, canonicalAgentPath(join29(home, path), aliases)]);
55455
+ const selectedAgents = options.agents ? new Set(options.agents) : undefined;
55456
+ const rootDefinitions = selectedAgents ? ROOTS.filter(([agent, path]) => selectedAgents.has(agent) || selectedAgents.has("hermes") && agent === "codex" && path === ".agents/skills") : ROOTS;
55457
+ const roots = rootDefinitions.map(([agent, path]) => [agent, canonicalAgentPath(join29(home, path), aliases)]);
55379
55458
  const bridgePaths = Object.values(AGENT_ADAPTERS).map((adapter) => canonicalAgentPath(join29(home, adapter.root, CLI_BRIDGE_NAME), aliases));
55380
55459
  for (const project of projectAncestorDirectories([...options.projectDirs ?? [], ...options.projectDir ? [options.projectDir] : []])) {
55381
- for (const [agent, path] of ROOTS)
55460
+ for (const [agent, path] of rootDefinitions)
55382
55461
  roots.push([agent, canonicalAgentPath(join29(project, path), aliases)]);
55383
55462
  }
55384
55463
  const entries = [], seen = new Set;
@@ -55634,7 +55713,7 @@ function planAgentIntegration(options) {
55634
55713
  return [agent, { command, profileId: profileId2 }];
55635
55714
  }));
55636
55715
  const discoveries = [...new Set(options.agents)].map((agent) => resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, canonical: (path) => canonicalAgentPath(path, aliases) }));
55637
- const nativeSkills = inventoryNativeSkills(home, { includeVendor: true, guardHermes: options.agents.includes("hermes"), projectDir: options.projectDir, agentRoots: discoveries.flatMap((binding) => binding.roots.map((path) => ({ agent: binding.agent, path }))), allowRootAliases: options.allowRootAliases });
55716
+ const nativeSkills = inventoryNativeSkills(home, { includeVendor: true, guardHermes: options.agents.includes("hermes"), agents: options.agents, projectDir: options.projectDir, agentRoots: discoveries.flatMap((binding) => binding.roots.map((path) => ({ agent: binding.agent, path }))), allowRootAliases: options.allowRootAliases });
55638
55717
  const changes = [];
55639
55718
  for (const agent of [...new Set(options.agents)]) {
55640
55719
  if (!INTEGRATION_AGENTS.includes(agent))
@@ -55846,12 +55925,13 @@ function archiveNativeSkills(inventory, options) {
55846
55925
  if (!expected || treeHash(entry.path) !== entry.hash || !isOwnedCliBridge(entry.path, [expected]))
55847
55926
  throw new Error(`Skills bridge changed after planning: ${entry.path}`);
55848
55927
  }
55849
- const selected = inventory.filter((entry) => !entry.bridge && !entry.system && (entry.vendor ? options.includeVendor : entry.managed || options.includeUnmanaged));
55928
+ const selected = options.targetManifest ? selectNativeMigrationTargets(inventory, options.targetManifest) : inventory.filter((entry) => !entry.bridge && !entry.system && (entry.vendor ? options.includeVendor : entry.managed || options.includeUnmanaged));
55850
55929
  for (const entry of selected)
55851
55930
  if (treeHash(entry.path) !== entry.hash)
55852
55931
  throw new Error(`Native skill changed after planning: ${entry.path}`);
55932
+ const targetManifest = options.targetManifest ? { schema: options.targetManifest.schema, digest: options.targetManifest.digest, targetCount: options.targetManifest.targets.length } : undefined;
55853
55933
  if (!selected.length)
55854
- return { entries: [], ...aliases.length ? { rootAliases: aliases } : {} };
55934
+ return { entries: [], ...targetManifest ? { targetManifest } : {}, ...aliases.length ? { rootAliases: aliases } : {} };
55855
55935
  const operationId = randomUUID6(), archiveRoot = join29(options.dataDir ?? getDataDir(), "migration", operationId, "native"), receiptPath = join29(archiveRoot, "receipt.json");
55856
55936
  const entries = selected.map((entry, index) => ({
55857
55937
  source: entry.vendor ? join29(entry.path, "SKILL.md") : entry.path,
@@ -55860,7 +55940,7 @@ function archiveNativeSkills(inventory, options) {
55860
55940
  ...entry.vendor ? { discoveryOnly: true } : {},
55861
55941
  status: "planned"
55862
55942
  }));
55863
- const journal = { version: 2, operationId, status: "planned", entries, ...aliases.length ? { rootAliases: aliases } : {} };
55943
+ const journal = { version: 2, operationId, status: "planned", entries, ...targetManifest ? { targetManifest } : {}, ...aliases.length ? { rootAliases: aliases } : {} };
55864
55944
  const moved = [];
55865
55945
  const verifyArchive = (entry) => {
55866
55946
  assertSafePath(entry.archive);
@@ -55954,7 +56034,7 @@ function archiveNativeSkills(inventory, options) {
55954
56034
  } catch {}
55955
56035
  throw new Error(`Native archive failed; inspect the recovery journal at ${receiptPath} before retrying.`, { cause: error });
55956
56036
  }
55957
- return { entries: entries.map(({ status, conflict, ...entry }) => entry), receiptPath, ...aliases.length ? { rootAliases: aliases } : {} };
56037
+ return { entries: entries.map(({ status, conflict, ...entry }) => entry), receiptPath, ...targetManifest ? { targetManifest } : {}, ...aliases.length ? { rootAliases: aliases } : {} };
55958
56038
  }
55959
56039
  function assertManagedAgentBridge(agent, options = {}) {
55960
56040
  const home = resolve16(options.home ?? homedir4()), dataDir3 = options.dataDir ?? getDataDirReadOnly();
@@ -56033,7 +56113,7 @@ function assertManagedAgentBridge(agent, options = {}) {
56033
56113
  } catch (error) {
56034
56114
  throw new Error(`NATIVE_SKILL_DRIFT: ${error.message}`);
56035
56115
  }
56036
- const inventory = inventoryNativeSkills(home, { includeVendor: true, guardHermes: agent === "hermes", projectDirs: [...roots], agentRoots: discovery.roots.map((path) => ({ agent, path })), allowRootAliases: aliases.length > 0 });
56116
+ const inventory = inventoryNativeSkills(home, { includeVendor: true, guardHermes: agent === "hermes", agents: [agent], projectDirs: [...roots], agentRoots: discovery.roots.map((path) => ({ agent, path })), allowRootAliases: aliases.length > 0 });
56037
56117
  const unexpected = inventory.filter((entry) => visible(entry) && !entry.bridge && !disabledBuiltin(entry));
56038
56118
  if (unexpected.length) {
56039
56119
  const paths = unexpected.slice(0, 3).map((entry) => {
@@ -107944,7 +108024,7 @@ async function connectCodexHookRpc(options) {
107944
108024
  }
107945
108025
  var SUPPORTED_CODEX_HOOK_VERSIONS;
107946
108026
  var init_codex_hook_rpc = __esm(() => {
107947
- SUPPORTED_CODEX_HOOK_VERSIONS = ["codex-cli 0.153.0", "codex-cli 0.154.0", "codex-cli 0.155.0"];
108027
+ SUPPORTED_CODEX_HOOK_VERSIONS = ["codex-cli 0.153.0", "codex-cli 0.154.0", "codex-cli 0.155.0", "codex-cli 0.155.1"];
107948
108028
  });
107949
108029
 
107950
108030
  // src/lib/codex-hook-trust-layout.ts
@@ -108541,11 +108621,16 @@ function registerAgentIntegration(parent) {
108541
108621
  }
108542
108622
  });
108543
108623
  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) => {
108624
+ 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
108625
  try {
108546
108626
  const discoveryInputs = options.discoveryInputs ? JSON.parse(readFileSync37(options.discoveryInputs, "utf8")) : undefined;
108627
+ if (options.targetManifest && (options.includeUnmanaged || options.includeVendor))
108628
+ throw new Error("--target-manifest cannot be combined with broad native migration selectors");
108629
+ const targetManifest = options.targetManifest ? readNativeMigrationTargetManifest(options.targetManifest) : undefined;
108547
108630
  const inventory = inventoryNativeSkills(undefined, { projectDirs: [process.cwd(), ...options.project ? [options.project] : []], includeVendor: options.includeVendor, configured: options.includeVendor, discoveryInputs, allowRootAliases: options.allowRootAliases });
108548
- const result2 = options.apply ? archiveNativeSkills(inventory, { includeUnmanaged: options.includeUnmanaged, includeVendor: options.includeVendor, allowRootAliases: options.allowRootAliases }) : { entries: [] };
108631
+ if (targetManifest)
108632
+ selectNativeMigrationTargets(inventory, targetManifest);
108633
+ 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
108634
  if (options.json)
108550
108635
  await writeCliOutput(JSON.stringify({ applied: options.apply, inventory, ...result2 }));
108551
108636
  else