@hasna/skills 0.10.38 → 0.10.41

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
@@ -2418,3 +2418,29 @@ Migration compares the original witness before projecting the new contract,
2418
2418
  refuses native discovery or unknown control changes, and changes policy only.
2419
2419
  After explicit migration, ordinary supported local MCP updates require no
2420
2420
  coordinated policy write.
2421
+
2422
+ ### Reproduce a reviewed runtime dependency closure
2423
+
2424
+ For an exact-version copyfile update, supply an owner-private absolute npm
2425
+ lockfile path and its reviewed SHA-256 together:
2426
+
2427
+ ```sh
2428
+ skills self-update --version <version> --reviewed-lock /absolute/package-lock.json \
2429
+ --reviewed-lock-sha256 <sha256> --min-release-age 7 --json
2430
+ ```
2431
+
2432
+ Use the same lock for a separately reviewed bootstrap. The lock must describe
2433
+ the verified package archive as `file:./verified.tgz`, use lockfile format 3,
2434
+ and bind its version, integrity and dependency declarations. Registry packages
2435
+ must have exact versions and SHA-512 integrity at the configured npm registry.
2436
+ The updater verifies each frozen version against current registry metadata
2437
+ and the requested age policy, runs `npm ci` without re-resolving ranges, then
2438
+ checks lock byte continuity and the actual required dependency closure with
2439
+ `npm sbom --sbom-format=cyclonedx --package-lock-only=false`. This forces
2440
+ installed-manifest inspection; ordinary `npm ls` can trust a hidden lockfile.
2441
+ Existing publisher exemptions remain available through the repeatable
2442
+ `--min-release-age-exclude` option. The receipt records the reviewed lock hash.
2443
+
2444
+ A reviewed lock does not authorize native skill loading, profile changes,
2445
+ credential provisioning or session reconciliation. Without this optional input,
2446
+ the existing update flow continues to resolve its dependency lock normally.
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.10.38",
36871
+ version: "0.10.41",
36872
36872
  description: "Skills library for AI coding agents",
36873
36873
  type: "module",
36874
36874
  bin: {
@@ -110034,8 +110034,139 @@ var init_remote_account2 = __esm(() => {
110034
110034
  init_remote_client();
110035
110035
  });
110036
110036
 
110037
+ // src/cli/commands/reviewed-runtime-lock.ts
110038
+ import { createHash as createHash30 } from "crypto";
110039
+ function row(value) {
110040
+ if (!value || typeof value !== "object" || Array.isArray(value))
110041
+ throw new Error("REVIEWED_LOCK_SHAPE_INVALID");
110042
+ return value;
110043
+ }
110044
+ function same4(a, b) {
110045
+ const entries = (v) => Object.entries(row(v ?? {})).sort(([a2], [b2]) => a2.localeCompare(b2));
110046
+ return JSON.stringify(entries(a)) === JSON.stringify(entries(b));
110047
+ }
110048
+ function packageName(path) {
110049
+ const parts = path.split("/node_modules/");
110050
+ if (!parts[0].startsWith("node_modules/"))
110051
+ throw new Error("REVIEWED_LOCK_LOCATION_INVALID");
110052
+ parts[0] = parts[0].slice("node_modules/".length);
110053
+ if (parts.some((name) => !NAME.test(name) || name.split("/").some((part) => part === "." || part === "..")))
110054
+ throw new Error("REVIEWED_LOCK_LOCATION_INVALID");
110055
+ return parts.at(-1);
110056
+ }
110057
+ function excluded(name, patterns) {
110058
+ return patterns.some((pattern) => new RegExp(`^${pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".")}$`).test(name));
110059
+ }
110060
+ async function metadata(name, context2) {
110061
+ const url2 = new URL(encodeURIComponent(name), `${context2.registryOrigin}/`);
110062
+ const response = await (context2.fetcher ?? fetch)(url2, { redirect: "error", signal: AbortSignal.timeout(30000) });
110063
+ if (!response.ok || !response.body)
110064
+ throw new Error("REVIEWED_LOCK_METADATA_UNAVAILABLE");
110065
+ const reader = response.body.getReader();
110066
+ const chunks = [];
110067
+ let size2 = 0;
110068
+ try {
110069
+ for (;; ) {
110070
+ const { done, value } = await reader.read();
110071
+ if (done)
110072
+ break;
110073
+ size2 += value.byteLength;
110074
+ if (size2 > MAX_METADATA_BYTES) {
110075
+ await reader.cancel();
110076
+ throw new Error("REVIEWED_LOCK_METADATA_TOO_LARGE");
110077
+ }
110078
+ chunks.push(value);
110079
+ }
110080
+ } finally {
110081
+ reader.releaseLock();
110082
+ }
110083
+ try {
110084
+ return row(JSON.parse(Buffer.concat(chunks).toString("utf8")));
110085
+ } catch {
110086
+ throw new Error("REVIEWED_LOCK_METADATA_INVALID");
110087
+ }
110088
+ }
110089
+ async function validateReviewedRuntimeLock(bytes, sha2562, context2) {
110090
+ if (!/^[a-f0-9]{64}$/.test(sha2562))
110091
+ throw new Error("REVIEWED_LOCK_SHA256_INVALID");
110092
+ if (bytes.byteLength > MAX_LOCK_BYTES || createHash30("sha256").update(bytes).digest("hex") !== sha2562)
110093
+ throw new Error("REVIEWED_LOCK_HASH_MISMATCH");
110094
+ if (!Number.isSafeInteger(context2.minReleaseAge) || context2.minReleaseAge < 1)
110095
+ throw new Error("REVIEWED_LOCK_RELEASE_AGE_REQUIRED");
110096
+ let lock;
110097
+ try {
110098
+ lock = row(JSON.parse(Buffer.from(bytes).toString("utf8")));
110099
+ } catch {
110100
+ throw new Error("REVIEWED_LOCK_SHAPE_INVALID");
110101
+ }
110102
+ if (lock.lockfileVersion !== 3 || lock.requires !== true)
110103
+ throw new Error("REVIEWED_LOCK_FORMAT_UNSUPPORTED");
110104
+ const packages = row(lock.packages);
110105
+ if (Object.keys(packages).length > 5000)
110106
+ throw new Error("REVIEWED_LOCK_POPULATION_TOO_LARGE");
110107
+ const root2 = row(packages[""]);
110108
+ if (!same4(root2.dependencies, { [PACKAGE2]: "file:./verified.tgz" }) || Object.keys(row(root2.devDependencies ?? {})).length || Object.keys(row(root2.optionalDependencies ?? {})).length)
110109
+ throw new Error("REVIEWED_LOCK_ROOT_MISMATCH");
110110
+ const target = row(packages[`node_modules/${PACKAGE2}`]);
110111
+ if (target.version !== context2.version || target.resolved !== "file:verified.tgz" || target.integrity !== context2.archiveIntegrity || target.name !== undefined && target.name !== PACKAGE2)
110112
+ throw new Error("REVIEWED_LOCK_ARCHIVE_MISMATCH");
110113
+ for (const field2 of ["dependencies", "optionalDependencies", "peerDependencies"]) {
110114
+ if (!same4(target[field2], context2.packageManifest[field2]))
110115
+ throw new Error("REVIEWED_LOCK_MANIFEST_MISMATCH");
110116
+ }
110117
+ const registry2 = new URL(context2.registryOrigin);
110118
+ const identities = new Map;
110119
+ for (const [location, value] of Object.entries(packages)) {
110120
+ if (!location)
110121
+ continue;
110122
+ const name = packageName(location), entry = row(value);
110123
+ if (entry.link || entry.dev || entry.name !== undefined && entry.name !== name)
110124
+ throw new Error("REVIEWED_LOCK_LOCATION_INVALID");
110125
+ if (location === `node_modules/${PACKAGE2}`)
110126
+ continue;
110127
+ if (typeof entry.version !== "string" || !VERSION.test(entry.version) || typeof entry.resolved !== "string" || typeof entry.integrity !== "string" || !INTEGRITY.test(entry.integrity))
110128
+ throw new Error("REVIEWED_LOCK_PACKAGE_INVALID");
110129
+ let url2;
110130
+ try {
110131
+ url2 = new URL(entry.resolved);
110132
+ } catch {
110133
+ throw new Error("REVIEWED_LOCK_REGISTRY_INVALID");
110134
+ }
110135
+ if (url2.origin !== registry2.origin || url2.username || url2.password || url2.search || url2.hash || !url2.pathname.endsWith(".tgz"))
110136
+ throw new Error("REVIEWED_LOCK_REGISTRY_INVALID");
110137
+ const entries = identities.get(name) ?? [];
110138
+ entries.push({ name, version: entry.version, resolved: entry.resolved, integrity: entry.integrity });
110139
+ identities.set(name, entries);
110140
+ }
110141
+ const names = [...identities.keys()];
110142
+ const now3 = context2.now ?? Date.now();
110143
+ for (let offset = 0;offset < names.length; offset += 6) {
110144
+ await Promise.all(names.slice(offset, offset + 6).map(async (name) => {
110145
+ const data = await metadata(name, context2), versions2 = row(data.versions), times2 = row(data.time);
110146
+ if (data.name !== name)
110147
+ throw new Error("REVIEWED_LOCK_METADATA_IDENTITY_MISMATCH");
110148
+ for (const entry of identities.get(name)) {
110149
+ const version2 = row(versions2[entry.version]), dist = row(version2.dist);
110150
+ if (version2.name !== name || version2.version !== entry.version || dist.integrity !== entry.integrity || dist.tarball !== entry.resolved)
110151
+ throw new Error("REVIEWED_LOCK_METADATA_IDENTITY_MISMATCH");
110152
+ const published = typeof times2[entry.version] === "string" ? Date.parse(times2[entry.version]) : NaN;
110153
+ if (!Number.isFinite(published) || published > now3 || !excluded(name, context2.minReleaseAgeExclude) && now3 - published < context2.minReleaseAge * 86400000)
110154
+ throw new Error("REVIEWED_LOCK_RELEASE_AGE_REFUSED");
110155
+ }
110156
+ }));
110157
+ }
110158
+ }
110159
+ var PACKAGE2 = "@hasna/skills", NAME, VERSION, INTEGRITY, MAX_LOCK_BYTES, MAX_METADATA_BYTES;
110160
+ var init_reviewed_runtime_lock = __esm(() => {
110161
+ NAME = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/;
110162
+ VERSION = /^\d+\.\d+\.\d+(?:-[a-zA-Z0-9.-]+)?(?:\+[a-zA-Z0-9.-]+)?$/;
110163
+ INTEGRITY = /^sha512-[A-Za-z0-9+/]{86}==$/;
110164
+ MAX_LOCK_BYTES = 8 * 1024 * 1024;
110165
+ MAX_METADATA_BYTES = 32 * 1024 * 1024;
110166
+ });
110167
+
110037
110168
  // src/cli/commands/runtime-copyfile.ts
110038
- import { createHash as createHash30, randomUUID as randomUUID9 } from "crypto";
110169
+ import { createHash as createHash31, randomUUID as randomUUID9 } from "crypto";
110039
110170
  import { Readable } from "stream";
110040
110171
  import { createGunzip as createGunzip2 } from "zlib";
110041
110172
  import {
@@ -110059,7 +110190,7 @@ function object9(value, code2) {
110059
110190
  return value;
110060
110191
  }
110061
110192
  function hash5(bytes) {
110062
- return createHash30("sha256").update(bytes).digest("hex");
110193
+ return createHash31("sha256").update(bytes).digest("hex");
110063
110194
  }
110064
110195
  function isWithin(parent, child) {
110065
110196
  const rel = relative10(parent, child);
@@ -110210,7 +110341,7 @@ function verifyIntegrity(bytes, integrity) {
110210
110341
  const match = /^sha512-([A-Za-z0-9+/]+={0,2})$/.exec(integrity);
110211
110342
  if (!match)
110212
110343
  throw new Error("REGISTRY_INTEGRITY_UNSUPPORTED");
110213
- const actual = createHash30("sha512").update(bytes).digest("base64");
110344
+ const actual = createHash31("sha512").update(bytes).digest("base64");
110214
110345
  if (actual !== match[1])
110215
110346
  throw new Error("TARBALL_INTEGRITY_MISMATCH");
110216
110347
  }
@@ -110293,6 +110424,8 @@ async function preflightTarball(bytes, limits = {}) {
110293
110424
  const pathParts2 = rawPath.split("/");
110294
110425
  if (rawPath !== "package" && !rawPath.startsWith("package/") || rawPath.startsWith("/") || rawPath.includes("\\") || pathParts2.some((part) => !part || part === "." || part === ".."))
110295
110426
  throw new Error("TARBALL_PATH_INVALID");
110427
+ if (pathParts2[1] === "node_modules")
110428
+ throw new Error("TARBALL_BUNDLED_DEPENDENCIES_UNSUPPORTED");
110296
110429
  if (paths.has(rawPath))
110297
110430
  throw new Error("TARBALL_DUPLICATE_PATH");
110298
110431
  paths.add(rawPath);
@@ -110320,10 +110453,10 @@ async function downloadPackage(version2, stagingRoot, registryOrigin, fetcher) {
110320
110453
  const metadataResponse = await fetcher(metadataUrl, { redirect: "error", signal: timeout });
110321
110454
  if (!metadataResponse.ok || new URL(metadataResponse.url || metadataUrl).origin !== new URL(registryOrigin).origin)
110322
110455
  throw new Error("REGISTRY_METADATA_FETCH_FAILED");
110323
- const metadata = object9(JSON.parse(new TextDecoder().decode(await readBodyCapped(metadataResponse, 5 * 1024 * 1024, "REGISTRY_METADATA_SIZE_INVALID"))), "REGISTRY_METADATA_INVALID");
110324
- if (metadata.name !== PACKAGE_NAME || metadata.version !== version2 || metadata._id !== `${PACKAGE_NAME}@${version2}`)
110456
+ const metadata2 = object9(JSON.parse(new TextDecoder().decode(await readBodyCapped(metadataResponse, 5 * 1024 * 1024, "REGISTRY_METADATA_SIZE_INVALID"))), "REGISTRY_METADATA_INVALID");
110457
+ if (metadata2.name !== PACKAGE_NAME || metadata2.version !== version2 || metadata2._id !== `${PACKAGE_NAME}@${version2}`)
110325
110458
  throw new Error("REGISTRY_PACKAGE_IDENTITY_MISMATCH");
110326
- const dist = object9(metadata.dist, "REGISTRY_DIST_INVALID");
110459
+ const dist = object9(metadata2.dist, "REGISTRY_DIST_INVALID");
110327
110460
  const integrity = typeof dist.integrity === "string" ? dist.integrity : "";
110328
110461
  const tarballUrl = expectedTarballUrl(dist.tarball, registryOrigin, version2);
110329
110462
  const response = await fetcher(tarballUrl, { redirect: "error", signal: AbortSignal.timeout(30000) });
@@ -110394,7 +110527,7 @@ async function readBodyCapped(response, limit, errorCode) {
110394
110527
  }
110395
110528
  return bytes;
110396
110529
  }
110397
- function readTree(root2, includeModes) {
110530
+ function readTree(root2, includeModes, excludeNpmDependencies = false) {
110398
110531
  const base3 = realpathSync16(root2);
110399
110532
  const entries = [];
110400
110533
  const walk2 = (dir, prefix = "") => {
@@ -110402,6 +110535,11 @@ function readTree(root2, includeModes) {
110402
110535
  const path = join58(dir, item);
110403
110536
  const rel = prefix ? `${prefix}/${item}` : item;
110404
110537
  const stat3 = lstatSync30(path);
110538
+ if (excludeNpmDependencies && !prefix && item === "node_modules") {
110539
+ if (!stat3.isDirectory() || stat3.isSymbolicLink())
110540
+ throw new Error("INSTALLED_DEPENDENCY_DIRECTORY_UNSAFE");
110541
+ continue;
110542
+ }
110405
110543
  if (stat3.isSymbolicLink()) {
110406
110544
  const link2 = readlinkSync5(path);
110407
110545
  const resolved = resolve25(dirname31(path), link2);
@@ -110610,8 +110748,10 @@ ${policy.minReleaseAgeExclude.map((pattern) => `min-release-age-exclude[]=${patt
110610
110748
  return npmVersion;
110611
110749
  }
110612
110750
  function comparePackageTrees(expectedRoot, installedRoot) {
110751
+ if (entryExists(join58(expectedRoot, "node_modules")))
110752
+ throw new Error("TARBALL_BUNDLED_DEPENDENCIES_UNSUPPORTED");
110613
110753
  const expected = readTree(expectedRoot, false);
110614
- const installed = readTree(installedRoot, false);
110754
+ const installed = readTree(installedRoot, false, true);
110615
110755
  if (JSON.stringify(expected.entries) !== JSON.stringify(installed.entries))
110616
110756
  throw new Error("INSTALLED_PACKAGE_BYTES_MISMATCH");
110617
110757
  return expected.digest;
@@ -110987,6 +111127,24 @@ async function updateCopyfileRuntime(version2, options = {}) {
110987
111127
  if (!STABLE_SEMVER_PATTERN.test(version2))
110988
111128
  throw new Error("EXACT_STABLE_VERSION_REQUIRED");
110989
111129
  const policy = releaseAgePolicy(options);
111130
+ let reviewedLockBytes;
111131
+ if (options.reviewedLock !== undefined || options.reviewedLockSha256 !== undefined) {
111132
+ if (!options.reviewedLock || !options.reviewedLockSha256)
111133
+ throw new Error("REVIEWED_LOCK_INPUT_PAIR_REQUIRED");
111134
+ if (!policy)
111135
+ throw new Error("REVIEWED_LOCK_RELEASE_AGE_REQUIRED");
111136
+ if (!isAbsolute25(options.reviewedLock))
111137
+ throw new Error("REVIEWED_LOCK_ABSOLUTE_PATH_REQUIRED");
111138
+ assertNoSymlinkAncestors(dirname31(options.reviewedLock));
111139
+ const stat3 = lstatSync30(options.reviewedLock);
111140
+ if (!stat3.isFile() || stat3.isSymbolicLink() || stat3.nlink !== 1 || stat3.uid !== process.getuid?.() || stat3.mode & 63 || stat3.size > 8 * 1024 * 1024)
111141
+ throw new Error("REVIEWED_LOCK_FILE_UNSAFE");
111142
+ reviewedLockBytes = readFileSync38(options.reviewedLock);
111143
+ if (!/^[a-f0-9]{64}$/.test(options.reviewedLockSha256))
111144
+ throw new Error("REVIEWED_LOCK_SHA256_INVALID");
111145
+ if (hash5(reviewedLockBytes) !== options.reviewedLockSha256)
111146
+ throw new Error("REVIEWED_LOCK_HASH_MISMATCH");
111147
+ }
110990
111148
  const home = resolve25(options.homeDir ?? process.env.HOME ?? "");
110991
111149
  if (!home || !existsSync34(home))
110992
111150
  throw new Error("HOME_NOT_FOUND");
@@ -111023,12 +111181,37 @@ async function updateCopyfileRuntime(version2, options = {}) {
111023
111181
  const stagePackage = join58(stagePath, "install");
111024
111182
  mkdirSync24(stagePackage, { mode: 448 });
111025
111183
  copyFileSync(artifact.tarballPath, join58(stagePackage, "verified.tgz"));
111026
- writeFileSync23(join58(stagePackage, "package.json"), JSON.stringify({ private: true, dependencies: { [PACKAGE_NAME]: "file:./verified.tgz" } }, null, 2), { mode: 384, flag: "wx" });
111027
- const npmVersion = await runNpm(["install", "--package-lock-only", "--prefix", stagePackage], stagePackage, home, stagePath, policy);
111184
+ writeFileSync23(join58(stagePackage, "package.json"), JSON.stringify({ name: "skills-runtime-install", version: "0.0.0", private: true, dependencies: { [PACKAGE_NAME]: "file:./verified.tgz" } }, null, 2), { mode: 384, flag: "wx" });
111185
+ let npmVersion;
111186
+ if (reviewedLockBytes) {
111187
+ await validateReviewedRuntimeLock(reviewedLockBytes, options.reviewedLockSha256, {
111188
+ version: version2,
111189
+ archiveIntegrity: artifact.integrity,
111190
+ packageManifest: object9(JSON.parse(readFileSync38(join58(stagePath, "verified-archive", "package", "package.json"), "utf8")), "PACKAGE_MANIFEST_INVALID"),
111191
+ registryOrigin: REGISTRY_ORIGIN,
111192
+ ...policy,
111193
+ fetcher
111194
+ });
111195
+ writeFileSync23(join58(stagePackage, "package-lock.json"), reviewedLockBytes, { mode: 384, flag: "wx" });
111196
+ if (!readFileSync38(join58(stagePackage, "package-lock.json")).equals(reviewedLockBytes))
111197
+ throw new Error("REVIEWED_LOCK_COPY_MISMATCH");
111198
+ } else {
111199
+ npmVersion = await runNpm(["install", "--package-lock-only", "--prefix", stagePackage], stagePackage, home, stagePath, policy);
111200
+ }
111028
111201
  const ciNpmVersion = await runNpm(["ci", "--prefix", stagePackage], stagePackage, home, stagePath, policy);
111029
- if (npmVersion !== ciNpmVersion)
111202
+ if (!reviewedLockBytes && npmVersion !== ciNpmVersion)
111030
111203
  throw new Error("NPM_VERSION_DRIFT_DURING_UPDATE");
111204
+ npmVersion = ciNpmVersion;
111031
111205
  const lockBytes = readFileSync38(join58(stagePackage, "package-lock.json"));
111206
+ if (reviewedLockBytes) {
111207
+ if (!lockBytes.equals(reviewedLockBytes))
111208
+ throw new Error("REVIEWED_LOCK_DRIFT_DURING_INSTALL");
111209
+ const treeNpmVersion = await runNpm(["sbom", "--sbom-format=cyclonedx", "--package-lock-only=false", "--omit=dev", "--prefix", stagePackage], stagePackage, home, stagePath, policy);
111210
+ if (treeNpmVersion !== npmVersion)
111211
+ throw new Error("NPM_VERSION_DRIFT_DURING_UPDATE");
111212
+ if (!readFileSync38(join58(stagePackage, "package-lock.json")).equals(reviewedLockBytes))
111213
+ throw new Error("REVIEWED_LOCK_DRIFT_DURING_INSTALL");
111214
+ }
111032
111215
  const installedPackage = join58(stagePackage, "node_modules", "@hasna", "skills");
111033
111216
  const installed = readPackage(join58(installedPackage, "package.json"));
111034
111217
  if (installed.version !== version2 || JSON.stringify(Object.entries(installed.bins).sort()) !== JSON.stringify(Object.entries(layout.bin).sort())) {
@@ -111069,6 +111252,7 @@ async function updateCopyfileRuntime(version2, options = {}) {
111069
111252
  configs: configPreimages,
111070
111253
  preimageSha256,
111071
111254
  ...policy ? { dependencyPolicy: { ...policy, npmVersion } } : {},
111255
+ ...reviewedLockBytes ? { reviewedLockSha256: options.reviewedLockSha256 } : {},
111072
111256
  launchers,
111073
111257
  switchedLaunchers: [],
111074
111258
  rollbackCompletedLaunchers: []
@@ -111107,7 +111291,7 @@ async function updateCopyfileRuntime(version2, options = {}) {
111107
111291
  }
111108
111292
  receipt.state = "switched";
111109
111293
  persistReceipt(join58(finalPath, "rollout-receipt.json"), receipt);
111110
- return { updated: true, version: version2, currentVersion: layout.currentVersion, receiptId: id2, runtimeRoot: finalPath, tarballSha256: artifact.sha256, tarballIntegrity: artifact.integrity, runtimeTreeSha256: stagedTree.digest, launcherCount: launchers.length, configCount: configPreimages.length, ...policy ? { dependencyPolicy: { ...policy, npmVersion } } : {} };
111294
+ return { updated: true, version: version2, currentVersion: layout.currentVersion, receiptId: id2, runtimeRoot: finalPath, tarballSha256: artifact.sha256, tarballIntegrity: artifact.integrity, runtimeTreeSha256: stagedTree.digest, launcherCount: launchers.length, configCount: configPreimages.length, ...policy ? { dependencyPolicy: { ...policy, npmVersion } } : {}, ...reviewedLockBytes ? { reviewedLockSha256: options.reviewedLockSha256 } : {} };
111111
111295
  } catch (error61) {
111112
111296
  if (switched.length > 0 || moved) {
111113
111297
  let rollbackFailed = false;
@@ -111244,6 +111428,7 @@ function rollbackCopyfileRuntime(receiptId, options = {}) {
111244
111428
  var PACKAGE_NAME = "@hasna/skills", REGISTRY_ORIGIN = "https://registry.npmjs.org", STABLE_SEMVER_PATTERN, MAX_TARBALL_BYTES, MAX_TARBALL_EXPANDED_BYTES, MAX_TARBALL_ENTRIES = 20000, CONFIG_PREIMAGE_PATHS;
111245
111429
  var init_runtime_copyfile = __esm(() => {
111246
111430
  init_skill_contract();
111431
+ init_reviewed_runtime_lock();
111247
111432
  STABLE_SEMVER_PATTERN = /^\d+\.\d+\.\d+$/;
111248
111433
  MAX_TARBALL_BYTES = 100 * 1024 * 1024;
111249
111434
  MAX_TARBALL_EXPANDED_BYTES = 256 * 1024 * 1024;
@@ -111260,7 +111445,7 @@ var exports_runtime = {};
111260
111445
  __export(exports_runtime, {
111261
111446
  registerRuntime: () => registerRuntime
111262
111447
  });
111263
- import { createHash as createHash31, randomUUID as randomUUID10 } from "crypto";
111448
+ import { createHash as createHash32, randomUUID as randomUUID10 } from "crypto";
111264
111449
  import { lstatSync as lstatSync31, mkdirSync as mkdirSync25, readFileSync as readFileSync39, realpathSync as realpathSync17, writeFileSync as writeFileSync24 } from "fs";
111265
111450
  import { basename as basename16, isAbsolute as isAbsolute26, join as join59 } from "path";
111266
111451
  import { createInterface } from "readline";
@@ -111301,7 +111486,7 @@ function registerRuntime(parent) {
111301
111486
  if (!expected)
111302
111487
  throw new Error("Cloud artifact not found");
111303
111488
  const bytes = await client2.download(id2, name);
111304
- if (bytes.length !== expected.byteSize || createHash31("sha256").update(bytes).digest("hex") !== expected.sha256)
111489
+ if (bytes.length !== expected.byteSize || createHash32("sha256").update(bytes).digest("hex") !== expected.sha256)
111305
111490
  throw new Error("Cloud artifact integrity verification failed");
111306
111491
  writeFileSync24(options.output, bytes, { flag: "wx", mode: 384 });
111307
111492
  console.log(JSON.stringify({ path: options.output, sha256: expected.sha256, byteSize: bytes.length }));
@@ -111331,7 +111516,12 @@ function registerRuntime(parent) {
111331
111516
  });
111332
111517
  const setup = parent.command("setup").description("Point this CLI at a Skills API server, or register agent integrations").option("--api-url <url>", "Skills API origin to send remote work to").option("--global", "Accepted for compatibility; the API origin is always per-user", false).option("--json", "Output setup result as JSON", false).action(async (options) => handleSetup(options));
111333
111518
  setup.command("agents").option("--json", "Output registration result as JSON", false).description("Register the Skills MCP server with all supported agents").action(async (options) => handleMcp({ register: "all", json: options.json }));
111334
- parent.command("self-update").description("Update @hasna/skills to the latest version").option("--json", "Output result as JSON", false).option("--version <version>", "Install this exact @hasna/skills version as a staged copyfile runtime").option("--min-release-age <days>", "Require a positive integer dependency release age in days for an exact-version update").option("--min-release-age-exclude <pattern>", "Exclude matching package identities from the release-age policy (repeatable)", (pattern, prior) => [...prior, pattern], []).option("--rollback <receipt-id>", "Restore launcher links from an exact copyfile rollout receipt").option("--adopt-aliases", "Switch verified legacy @hasna/skills aliases to the active copyfile runtime").option("--rollback-aliases <receipt-id>", "Restore aliases from an exact adoption receipt").action(async (options) => {
111519
+ parent.command("self-update").description("Update @hasna/skills to the latest version").option("--json", "Output result as JSON", false).option("--version <version>", "Install this exact @hasna/skills version as a staged copyfile runtime").option("--min-release-age <days>", "Require a positive integer dependency release age in days for an exact-version update").option("--min-release-age-exclude <pattern>", "Exclude matching package identities from the release-age policy (repeatable)", (pattern, prior) => [...prior, pattern], []).option("--reviewed-lock <path>", "Install from this private absolute package-lock.json path without resolving dependency ranges").option("--reviewed-lock-sha256 <sha256>", "Exact reviewed SHA-256 of the supplied package-lock.json").option("--rollback <receipt-id>", "Restore launcher links from an exact copyfile rollout receipt").option("--adopt-aliases", "Switch verified legacy @hasna/skills aliases to the active copyfile runtime").option("--rollback-aliases <receipt-id>", "Restore aliases from an exact adoption receipt").action(async (options) => {
111520
+ if ((options.reviewedLock !== undefined || options.reviewedLockSha256 !== undefined) && (!options.version || options.rollback || options.adoptAliases || options.rollbackAliases)) {
111521
+ console.error(JSON.stringify({ error: "REVIEWED_LOCK_REQUIRES_EXACT_VERSION" }));
111522
+ process.exitCode = 1;
111523
+ return;
111524
+ }
111335
111525
  if ((options.minReleaseAge !== undefined || options.minReleaseAgeExclude.length > 0) && (!options.version || options.rollback || options.adoptAliases || options.rollbackAliases)) {
111336
111526
  console.error(JSON.stringify({ error: "RELEASE_AGE_POLICY_REQUIRES_EXACT_VERSION" }));
111337
111527
  process.exitCode = 1;
@@ -111373,7 +111563,7 @@ function registerRuntime(parent) {
111373
111563
  if (options.version) {
111374
111564
  try {
111375
111565
  const minReleaseAge = options.minReleaseAge === undefined ? undefined : /^\d+$/.test(options.minReleaseAge) ? Number(options.minReleaseAge) : NaN;
111376
- const result2 = await updateCopyfileRuntime(options.version, { minReleaseAge, minReleaseAgeExclude: options.minReleaseAgeExclude });
111566
+ const result2 = await updateCopyfileRuntime(options.version, { minReleaseAge, minReleaseAgeExclude: options.minReleaseAgeExclude, reviewedLock: options.reviewedLock, reviewedLockSha256: options.reviewedLockSha256 });
111377
111567
  if (options.json)
111378
111568
  console.log(JSON.stringify(result2));
111379
111569
  else
@@ -112950,7 +113140,7 @@ var init_portable_snapshot_filter = __esm(() => {
112950
113140
  });
112951
113141
 
112952
113142
  // src/lib/station-snapshot.ts
112953
- import { createHash as createHash32 } from "crypto";
113143
+ import { createHash as createHash33 } from "crypto";
112954
113144
  import {
112955
113145
  copyFileSync as copyFileSync2,
112956
113146
  mkdirSync as mkdirSync26,
@@ -112965,7 +113155,7 @@ function validateStationId(stationId) {
112965
113155
  }
112966
113156
  }
112967
113157
  function sha256File(filePath) {
112968
- return createHash32("sha256").update(readFileSync40(filePath)).digest("hex");
113158
+ return createHash33("sha256").update(readFileSync40(filePath)).digest("hex");
112969
113159
  }
112970
113160
  function scanHome(definition, homesRoot) {
112971
113161
  const homePath = homePathFor(definition, homesRoot);
@@ -113655,7 +113845,7 @@ var init_codex_hook_policy_recovery = __esm(() => {
113655
113845
 
113656
113846
  // src/lib/agent-codex-trust.ts
113657
113847
  import { lstatSync as lstatSync33, mkdirSync as mkdirSync27, readdirSync as readdirSync23, readFileSync as readFileSync41, realpathSync as realpathSync19, statSync as statSync22 } from "fs";
113658
- import { createHash as createHash33, randomUUID as randomUUID11 } from "crypto";
113848
+ import { createHash as createHash34, randomUUID as randomUUID11 } from "crypto";
113659
113849
  import { execFileSync as execFileSync2 } from "child_process";
113660
113850
  import { dirname as dirname35, isAbsolute as isAbsolute29, join as join63, resolve as resolve27 } from "path";
113661
113851
  import { homedir as homedir12 } from "os";
@@ -113665,11 +113855,11 @@ function verifyReleaseArtifact(proof, skillsCli) {
113665
113855
  const tarPath = realpathSync19(proof.packageTarPath);
113666
113856
  need5(statSync22(tarPath).isFile(), "RECONCILE_RELEASE_PROVENANCE_PATH");
113667
113857
  const tarBytes = readFileSync41(tarPath);
113668
- need5(tarBytes.byteLength === proof.packageTarBytes && createHash33("sha256").update(tarBytes).digest("hex") === proof.packageTarSha256, "RECONCILE_RELEASE_ARTIFACT_CHANGED");
113858
+ need5(tarBytes.byteLength === proof.packageTarBytes && createHash34("sha256").update(tarBytes).digest("hex") === proof.packageTarSha256, "RECONCILE_RELEASE_ARTIFACT_CHANGED");
113669
113859
  const packageRoot = dirname35(dirname35(realpathSync19(skillsCli.receipt.path)));
113670
113860
  const manifest = readFileSync41(join63(packageRoot, "package.json"));
113671
- need5(createHash33("sha256").update(manifest).digest("hex") === skillsCli.receipt.manifestSha256 && skillsCli.receipt.manifestSha256 === proof.manifestSha256, "RECONCILE_RELEASE_MANIFEST_CHANGED");
113672
- need5(createHash33("sha256").update(readFileSync41(skillsCli.receipt.path)).digest("hex") === proof.executableSha256 && skillsCli.receipt.sha256 === proof.executableSha256, "RECONCILE_RELEASE_EXECUTABLE_CHANGED");
113861
+ need5(createHash34("sha256").update(manifest).digest("hex") === skillsCli.receipt.manifestSha256 && skillsCli.receipt.manifestSha256 === proof.manifestSha256, "RECONCILE_RELEASE_MANIFEST_CHANGED");
113862
+ need5(createHash34("sha256").update(readFileSync41(skillsCli.receipt.path)).digest("hex") === proof.executableSha256 && skillsCli.receipt.sha256 === proof.executableSha256, "RECONCILE_RELEASE_EXECUTABLE_CHANGED");
113673
113863
  const members2 = [["package/package.json", manifest], ["package/bin/index.js", readFileSync41(skillsCli.receipt.path)]];
113674
113864
  for (const [member, expected] of members2) {
113675
113865
  let actual;
@@ -113987,7 +114177,7 @@ async function enrollCodexNativeHooksUnlocked(options, connect) {
113987
114177
  }
113988
114178
  }
113989
114179
  var json2 = (value) => JSON.stringify(value, null, 2) + `
113990
- `, structure3 = (text4) => JSON.parse(JSON.stringify(Bun.TOML.parse(text4))), eventNames, makePlanDigest = (value) => createHash33("sha256").update(json2(value)).digest("hex"), safeNativeHook = (hook) => ({ key: hook.key, eventName: hook.eventName, handlerType: hook.handlerType, command: hook.command, matcher: hook.matcher, timeoutSec: hook.timeoutSec, async: hook.async, statusMessage: hook.statusMessage, additionalContextLimit: hook.additionalContextLimit, sourcePath: hook.sourcePath, source: hook.source, pluginId: hook.pluginId, isManaged: hook.isManaged, currentHash: hook.currentHash, enabled: hook.enabled, trustStatus: hook.trustStatus }), withoutCodexCommand = (value) => {
114180
+ `, structure3 = (text4) => JSON.parse(JSON.stringify(Bun.TOML.parse(text4))), eventNames, makePlanDigest = (value) => createHash34("sha256").update(json2(value)).digest("hex"), safeNativeHook = (hook) => ({ key: hook.key, eventName: hook.eventName, handlerType: hook.handlerType, command: hook.command, matcher: hook.matcher, timeoutSec: hook.timeoutSec, async: hook.async, statusMessage: hook.statusMessage, additionalContextLimit: hook.additionalContextLimit, sourcePath: hook.sourcePath, source: hook.source, pluginId: hook.pluginId, isManaged: hook.isManaged, currentHash: hook.currentHash, enabled: hook.enabled, trustStatus: hook.trustStatus }), withoutCodexCommand = (value) => {
113991
114181
  const copy = structuredClone(value);
113992
114182
  if (copy?.bridge?.commands && Object.hasOwn(copy.bridge.commands, "codex"))
113993
114183
  copy.bridge.commands.codex = "<reviewed-codex-binding>";
@@ -114129,7 +114319,7 @@ __export(exports_agent_integration, {
114129
114319
  registerAgentIntegration: () => registerAgentIntegration
114130
114320
  });
114131
114321
  import { existsSync as existsSync36, lstatSync as lstatSync34, readFileSync as readFileSync42, readSync as readSync17, writeFileSync as writeFileSync26 } from "fs";
114132
- import { createHash as createHash34 } from "crypto";
114322
+ import { createHash as createHash35 } from "crypto";
114133
114323
  import { homedir as homedir13 } from "os";
114134
114324
  import { isAbsolute as isAbsolute30 } from "path";
114135
114325
  function pinnedHookProfile(input3, configuredProfile) {
@@ -114254,7 +114444,7 @@ function registerAgentIntegration(parent) {
114254
114444
  skillCount: catalog.skills.length,
114255
114445
  output: options.output,
114256
114446
  bytes: Buffer.byteLength(bytes),
114257
- sha256: createHash34("sha256").update(bytes).digest("hex")
114447
+ sha256: createHash35("sha256").update(bytes).digest("hex")
114258
114448
  };
114259
114449
  await writeCliOutput(options.json ? JSON.stringify(receipt) : `Captured ${receipt.skillCount} native Codex skill(s) in ${receipt.output}.`);
114260
114450
  } catch (error61) {
@@ -114770,7 +114960,7 @@ var init_grants = __esm(() => {
114770
114960
  });
114771
114961
 
114772
114962
  // src/lib/station-hydrate.ts
114773
- import { createHash as createHash35 } from "crypto";
114963
+ import { createHash as createHash36 } from "crypto";
114774
114964
  import {
114775
114965
  copyFileSync as copyFileSync3,
114776
114966
  mkdirSync as mkdirSync28,
@@ -114973,7 +115163,7 @@ function skillSha256(skill) {
114973
115163
  return sha256File(skill.files[0].winner.fullPath);
114974
115164
  }
114975
115165
  const joined = skill.files.map((file2) => sha256File(file2.winner.fullPath));
114976
- return createHash35("sha256").update(joined.sort().join(`
115166
+ return createHash36("sha256").update(joined.sort().join(`
114977
115167
  `)).digest("hex");
114978
115168
  }
114979
115169
  function writeStationHydration(options) {
@@ -115130,7 +115320,7 @@ var init_hydrate = __esm(() => {
115130
115320
  });
115131
115321
 
115132
115322
  // src/lib/prepare-skill.ts
115133
- import { createHash as createHash36, randomUUID as randomUUID12 } from "crypto";
115323
+ import { createHash as createHash37, randomUUID as randomUUID12 } from "crypto";
115134
115324
  import { chmodSync as chmodSync5, existsSync as existsSync37, lstatSync as lstatSync35, mkdirSync as mkdirSync29, mkdtempSync as mkdtempSync6, readFileSync as readFileSync44, readdirSync as readdirSync25, renameSync as renameSync14, rmSync as rmSync9, writeFileSync as writeFileSync30 } from "fs";
115135
115325
  import { tmpdir as tmpdir2 } from "os";
115136
115326
  import { dirname as dirname37, isAbsolute as isAbsolute31, join as join65, relative as relative12, resolve as resolve29, sep as sep15 } from "path";
@@ -115329,7 +115519,7 @@ function snapshot2(root2) {
115329
115519
  if (rootStat.isSymbolicLink() || !rootStat.isDirectory())
115330
115520
  throw new Error("Skill root must be a real directory, not a symlink.");
115331
115521
  const files = [], directories = [];
115332
- const hash6 = createHash36("sha256").update(`${rootStat.dev}:${rootStat.ino}\x00`);
115522
+ const hash6 = createHash37("sha256").update(`${rootStat.dev}:${rootStat.ino}\x00`);
115333
115523
  let count = 0, bytes = 0;
115334
115524
  const walk2 = (relative13) => {
115335
115525
  for (const name of readdirSync25(join65(root2, relative13)).sort()) {
@@ -115760,7 +115950,7 @@ function createRegistrySyncArtifact(options = {}) {
115760
115950
  const includeDocs = options.includeDocs ?? true;
115761
115951
  const includeRequirements = options.includeRequirements ?? true;
115762
115952
  const includeValidation = options.includeValidation ?? true;
115763
- const packageName = options.packageName ?? "@hasna/skills";
115953
+ const packageName2 = options.packageName ?? "@hasna/skills";
115764
115954
  const packageVersion = options.packageVersion ?? package_default.version;
115765
115955
  const sourceRepository = options.sourceRepository ?? "hasna/skills";
115766
115956
  const registry2 = [...loadRegistryProfile(profile)].sort((a, b) => a.name.localeCompare(b.name));
@@ -115778,7 +115968,7 @@ function createRegistrySyncArtifact(options = {}) {
115778
115968
  tags: [...skill.tags].sort(),
115779
115969
  dependencies: [...skill.dependencies ?? []].sort(),
115780
115970
  source: {
115781
- packageName,
115971
+ packageName: packageName2,
115782
115972
  packageVersion,
115783
115973
  repository: sourceRepository,
115784
115974
  directory: directory2
@@ -115796,7 +115986,7 @@ function createRegistrySyncArtifact(options = {}) {
115796
115986
  return {
115797
115987
  schemaVersion: 1,
115798
115988
  source: {
115799
- packageName,
115989
+ packageName: packageName2,
115800
115990
  packageVersion,
115801
115991
  repository: sourceRepository,
115802
115992
  profile
@@ -115872,10 +116062,10 @@ async function exportSkillVersionForReview(spec, output2, client2) {
115872
116062
  const target = assertPrivateDestination(output2);
115873
116063
  if (existsSync38(target))
115874
116064
  throw new Error("Review output already exists; choose a new filename.");
115875
- const metadata = await client2.getSkillVersion(slug2, version2);
115876
- if (!metadata)
116065
+ const metadata2 = await client2.getSkillVersion(slug2, version2);
116066
+ if (!metadata2)
115877
116067
  throw new Error(`Published skill version ${slug2}@${version2} was not found.`);
115878
- if (!/^[a-f0-9]{64}$/i.test(metadata.bundleSha256) || !Number.isSafeInteger(metadata.bundleByteSize) || metadata.bundleByteSize < 1 || metadata.bundleByteSize > MAX_REVIEW_BUNDLE_BYTES) {
116068
+ if (!/^[a-f0-9]{64}$/i.test(metadata2.bundleSha256) || !Number.isSafeInteger(metadata2.bundleByteSize) || metadata2.bundleByteSize < 1 || metadata2.bundleByteSize > MAX_REVIEW_BUNDLE_BYTES) {
115879
116069
  throw new Error("Published bundle metadata is invalid or exceeds the review size limit.");
115880
116070
  }
115881
116071
  const response = await client2.getBundle(slug2, version2);
@@ -115887,12 +116077,12 @@ async function exportSkillVersionForReview(spec, output2, client2) {
115887
116077
  }
115888
116078
  const headerSha = response.headers.get("X-Skill-Bundle-Sha256");
115889
116079
  const headerVersion = response.headers.get("X-Skill-Version");
115890
- if (!headerSha || headerSha.toLowerCase() !== metadata.bundleSha256.toLowerCase() || headerVersion !== version2) {
116080
+ if (!headerSha || headerSha.toLowerCase() !== metadata2.bundleSha256.toLowerCase() || headerVersion !== version2) {
115891
116081
  response.body?.cancel().catch(() => {});
115892
116082
  throw new Error("Published bundle headers do not match the exact version metadata.");
115893
116083
  }
115894
- const bytes = await readBoundedResponse(response, Math.min(metadata.bundleByteSize, MAX_REVIEW_BUNDLE_BYTES));
115895
- if (bytes.byteLength !== metadata.bundleByteSize || sha256Hex(bytes) !== metadata.bundleSha256.toLowerCase()) {
116084
+ const bytes = await readBoundedResponse(response, Math.min(metadata2.bundleByteSize, MAX_REVIEW_BUNDLE_BYTES));
116085
+ if (bytes.byteLength !== metadata2.bundleByteSize || sha256Hex(bytes) !== metadata2.bundleSha256.toLowerCase()) {
115896
116086
  throw new Error("Published bundle bytes do not match the exact version metadata.");
115897
116087
  }
115898
116088
  const fd = openSync25(target, constants23.O_WRONLY | constants23.O_CREAT | constants23.O_EXCL | constants23.O_NOFOLLOW, 384);
@@ -115906,7 +116096,7 @@ async function exportSkillVersionForReview(spec, output2, client2) {
115906
116096
  } finally {
115907
116097
  closeSync25(fd);
115908
116098
  }
115909
- return { slug: slug2, version: version2, bundleSha256: metadata.bundleSha256.toLowerCase(), bundleByteSize: bytes.byteLength, output: target };
116099
+ return { slug: slug2, version: version2, bundleSha256: metadata2.bundleSha256.toLowerCase(), bundleByteSize: bytes.byteLength, output: target };
115910
116100
  }
115911
116101
  var MAX_REVIEW_BUNDLE_BYTES, SLUG2, NATIVE_CONTAINERS;
115912
116102
  var init_review_export = __esm(() => {
@@ -118090,10 +118280,10 @@ async function reconcileRegistry(options = {}) {
118090
118280
  throw new ReconcileRegistryError(`Registry listing failed: expected an array of skills, got ${shape}.`, ["Check HASNA_SKILLS_API_URL and the resolved credential; a failed listing must not be read as an empty registry."]);
118091
118281
  }
118092
118282
  const remotes = new Map;
118093
- for (const row of remoteRowsPayload) {
118094
- if (!row || typeof row !== "object")
118283
+ for (const row2 of remoteRowsPayload) {
118284
+ if (!row2 || typeof row2 !== "object")
118095
118285
  continue;
118096
- const skill = remoteRowToSkill(row);
118286
+ const skill = remoteRowToSkill(row2);
118097
118287
  if (skill)
118098
118288
  remotes.set(skill.slug, skill);
118099
118289
  }
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.10.38",
20022
+ version: "0.10.41",
20023
20023
  description: "Skills library for AI coding agents",
20024
20024
  type: "module",
20025
20025
  bin: {
package/bin/migrate.js CHANGED
@@ -7,7 +7,7 @@ import { join as join5 } from "path";
7
7
  // package.json
8
8
  var package_default = {
9
9
  name: "@hasna/skills",
10
- version: "0.10.38",
10
+ version: "0.10.41",
11
11
  description: "Skills library for AI coding agents",
12
12
  type: "module",
13
13
  bin: {
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.10.38",
24125
+ version: "0.10.41",
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.10.38",
24128
+ version: "0.10.41",
24129
24129
  description: "Skills library for AI coding agents",
24130
24130
  type: "module",
24131
24131
  bin: {
@@ -0,0 +1,17 @@
1
+ type Row = Record<string, unknown>;
2
+ export interface ReviewedRuntimeLockContext {
3
+ version: string;
4
+ archiveIntegrity: string;
5
+ packageManifest: Row;
6
+ registryOrigin: string;
7
+ minReleaseAge: number;
8
+ minReleaseAgeExclude: string[];
9
+ fetcher?: typeof fetch;
10
+ now?: number;
11
+ }
12
+ /** Validate the immutable install input without resolving any version ranges.
13
+ * npm ci subsequently verifies archive bytes and package/root range agreement;
14
+ * npm sbom forceActual validates the installed required closure, including optional peers.
15
+ */
16
+ export declare function validateReviewedRuntimeLock(bytes: Uint8Array, sha256: string, context: ReviewedRuntimeLockContext): Promise<void>;
17
+ export {};
@@ -28,6 +28,8 @@ export declare function updateCopyfileRuntime(version: string, options?: {
28
28
  fetcher?: typeof fetch;
29
29
  minReleaseAge?: number;
30
30
  minReleaseAgeExclude?: string[];
31
+ reviewedLock?: string;
32
+ reviewedLockSha256?: string;
31
33
  onLauncherSwitched?: (path: string) => void;
32
34
  }): Promise<JsonObject>;
33
35
  export declare function rollbackCopyfileRuntime(receiptId: string, options?: {
package/dist/index.js CHANGED
@@ -11040,7 +11040,7 @@ function replaceSkillSession(expected, replacement, plan, options = {}, beforeCo
11040
11040
  // package.json
11041
11041
  var package_default = {
11042
11042
  name: "@hasna/skills",
11043
- version: "0.10.38",
11043
+ version: "0.10.41",
11044
11044
  description: "Skills library for AI coding agents",
11045
11045
  type: "module",
11046
11046
  bin: {
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.10.38",
32376
+ version: "0.10.41",
32377
32377
  description: "Skills library for AI coding agents",
32378
32378
  type: "module",
32379
32379
  bin: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/skills",
3
- "version": "0.10.38",
3
+ "version": "0.10.41",
4
4
  "description": "Skills library for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {