@hasna/skills 0.10.71 → 0.10.73

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/dist/storage.js CHANGED
@@ -53,13 +53,13 @@ import {
53
53
  mkdirSync as mkdirSync3,
54
54
  readFileSync as readFileSync3,
55
55
  readdirSync,
56
- statSync,
56
+ statSync as statSync2,
57
57
  writeFileSync as writeFileSync3
58
58
  } from "fs";
59
59
  import { dirname as dirname2, join as join4, normalize, relative, sep } from "path";
60
60
 
61
61
  // src/lib/config.ts
62
- import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync } from "fs";
62
+ import { closeSync, constants, existsSync as existsSync2, fstatSync, lstatSync, openSync, readFileSync, statSync, writeFileSync, mkdirSync } from "fs";
63
63
  import { join as join2, dirname } from "path";
64
64
 
65
65
  // src/lib/retired-settings.ts
@@ -195,7 +195,8 @@ function skillsDataRootForHome(home) {
195
195
  var ENUM_KEYS = {
196
196
  defaultAgent: ["claude", "codex", "gemini", "pi", "opencode", "all"],
197
197
  defaultScope: ["global", "project"],
198
- format: ["compact", "json", "csv"]
198
+ format: ["compact", "json", "csv"],
199
+ sessionPinMigration: ["auto", "off"]
199
200
  };
200
201
  var STRING_KEYS = ["extensionsDir"];
201
202
  function validKeys() {
@@ -266,6 +267,9 @@ function saveConfig(key, value, scope = "project") {
266
267
  if (!validKeys().includes(key)) {
267
268
  throw new Error(`Unknown config key: ${key}. Valid keys: ${validKeys().join(", ")}`);
268
269
  }
270
+ if (key === "sessionPinMigration" && scope !== "global") {
271
+ throw new Error(`${key} is read only from the global configuration. Use: skills config set ${key} <auto|off> --global`);
272
+ }
269
273
  const normalized = normalizeConfigValue(key, value);
270
274
  if (normalized === undefined) {
271
275
  const allowed = allowedValues(key);
@@ -959,7 +963,7 @@ function walkFiles(dir) {
959
963
  const files = [];
960
964
  for (const entry of readdirSync(dir)) {
961
965
  const full = join4(dir, entry);
962
- const stats = statSync(full);
966
+ const stats = statSync2(full);
963
967
  if (stats.isDirectory())
964
968
  files.push(...walkFiles(full));
965
969
  else
@@ -195,7 +195,7 @@ plugin dependency declarations refuse because this validator does not resolve
195
195
  or review unselected external plugins.
196
196
  The request may declare `functionHooksEnv: "1"`; without that declaration the
197
197
  environment document is preserved unchanged. For a measured Claude 2.1.276,
198
- 2.1.278, 2.1.282, 2.1.287, 2.1.293, 2.1.296 or 2.1.299 user-scoped install,
198
+ 2.1.278, 2.1.282, 2.1.287, 2.1.293, 2.1.296, 2.1.299 or 2.1.300 user-scoped install,
199
199
  `candidate.settingsMarketplaceSource: "directory"`
200
200
  explicitly declares the native settings registration. Skills derives
201
201
  `extraKnownMarketplaces[candidate.marketplace].source` from the exact frozen
@@ -249,6 +249,49 @@ structural refusal checks do not prove hook behavior. Recheck all preimages and
249
249
  candidate closure immediately before the first native mutation. The validator
250
250
  does not claim native install atomicity or crash recovery.
251
251
 
252
+ The reviewed discovery inputs must bind every current Claude settings and
253
+ registration source: the user settings, both project settings files,
254
+ `known_marketplaces.json` and `installed_plugins.json`. A project
255
+ `.claude/settings.json` or `.claude/settings.local.json` that the reviewed
256
+ sources omit is exempt only when no other expected source (the target settings
257
+ or the user settings) shares its path, and it is either:
258
+
259
+ - absent, with a null preimage; or
260
+ - present, and all of the following hold:
261
+ - its current bytes match its preimage SHA-256; a mismatch refuses with
262
+ `<name> preimage changed`;
263
+ - those exact bytes are strict UTF-8 without a byte-order mark and parse as
264
+ one JSON object within the native JSON witness limits (no comments,
265
+ trailing commas, trailing content or duplicate keys at any depth);
266
+ - none of `enabledPlugins`, `extraKnownMarketplaces`,
267
+ `additionalMarketplaces` or `skillOverrides` is present at any value,
268
+ including `null`, `{}` and `[]`;
269
+ - every top-level key is on the exemption allowlist with its type:
270
+ `permissions` (an object), `outputStyle`, `model`, `theme` and
271
+ `effortLevel` (strings), and `verbose` and `spinnerTipsEnabled` (booleans);
272
+ - `permissions` holds only `allow`, `deny` and `ask` (arrays of strings) and
273
+ `defaultMode` (a string).
274
+
275
+ Anything else keeps the file required, including `env` (Claude reads
276
+ `CLAUDE_CODE_PLUGIN_DIRS` from it), `hooks`, `permissions.additionalDirectories`
277
+ (Claude loads skills, commands and agents from those directories), `__proto__`
278
+ and unknown keys. The allowlist names settings reviewed as not configuring
279
+ plugins, marketplaces, skills, hooks, extra directories or the environment. It
280
+ does not prove that Claude cannot discover anything from the file; a setting
281
+ joins it only after that review.
282
+
283
+ This covers the permissions-only `~/.claude/settings.local.json` that Claude
284
+ writes when it saves a permission rule during a user-scope install, whose
285
+ project path is the Claude home. A present unreviewed file that fails the parse,
286
+ discovery-key or allowlist checks refuses and names the reason. Each exempt file
287
+ is re-checked against its exact preimage (still absent, or the same bytes) after
288
+ discovery and again before the result, so a change, creation or removal in
289
+ between refuses. The live hooks' project check still runs on top; it refuses
290
+ any of the four discovery keys (`additionalMarketplaces` included, because
291
+ Claude 2.1.300 rewrites it into `extraKnownMarketplaces` on every settings read)
292
+ and its other project skill and hook settings. The user settings and both
293
+ registries are never exempt.
294
+
252
295
  The optional release manifest witness binds the complete supplied release.json
253
296
  file bytes, path and SHA-256 in the request/receipt. Harnesses remains
254
297
  responsible for verifying that its release options correspond to every
@@ -270,8 +313,12 @@ skills hook capture-claude-installer --request /absolute/request.json --json
270
313
  The UTF-8 request is a bounded JSON object with schema
271
314
  `skills.claude-installer-capture/v1`, the exact configured `commandPath`, one
272
315
  fixed `operation` (`settings`, `marketplace-registry`, `managed-plugin-registry`,
273
- `path-bytes`, `discovery-directories`, `plugin-manifest`, or `candidate`), and
274
- only that operation's documented inputs. The result uses
316
+ `path-bytes`, `discovery-directories`, `plugin-manifest`, `candidate`, or
317
+ `discovery-binding`), and only that operation's documented inputs. The
318
+ `discovery-binding` operation verifies the stored Claude discovery binding as a
319
+ whole, including witnesses retained under an absent retired plugin-cache
320
+ version; its request and result contract is in the README section
321
+ "Claude installer witness CLI". The result uses
275
322
  `skills.claude-installer-capture-result/v1` and contains a Skills producer
276
323
  receipt plus the selected existing witness result. Candidate manifests include
277
324
  their plugin name, canonical path, `hashMode: "claude-plugin-manifest-v1"`, and
@@ -330,9 +377,23 @@ What the software currently enforces is narrower:
330
377
  managed row's `installPath` to be `cache/<marketplace>/<plugin>/<version>`,
331
378
  checks every file in every retained cache version against the admission
332
379
  receipts without the review listing them, and binds unmanaged rows exactly.
380
+ - Claude rewrites the 13-digit millisecond timestamp in
381
+ `.claude/plugins/cache/<marketplace>/<plugin>/<version>/.orphaned_at` when it
382
+ starts. Capturing that exact path with `captureDiscoveryByteSources` or
383
+ `captureDiscoveryPathSources` (including the installer `path-bytes`
384
+ operation) produces a `claude-plugin-orphaned-at-v1` witness, and a fresh
385
+ explicit review converts a plain byte or `path-bytes` witness of it to that
386
+ form. The witness binds the marker's presence or absence, not its bytes or
387
+ inode. Every capture and check still requires a regular, non-symlink file
388
+ owned by the current user, at most 32 bytes, matching `^[1-9][0-9]{12}$`.
389
+ The name stays in `directories` membership, so other added or removed names
390
+ are still detected. A marker at any other depth, any other name, and any
391
+ Skills-owned producer tree keep their exact byte witnesses. A stored review
392
+ that binds the marker's bytes keeps that meaning and needs one fresh review.
333
393
  - An entirely absent retired Claude version root exempts only its plain byte
334
394
  sources (`hashMode` omitted or `"bytes"`, with no `format`, `fields` or
335
- `managedPlugins`). Such a source with a non-null `sha256` is also the only
395
+ `managedPlugins`) and a present `claude-plugin-orphaned-at-v1` marker. Such a
396
+ source with a non-null `sha256` is also the only
336
397
  thing that identifies the root, and the root qualifies only while it stays
337
398
  entirely absent and the reviewed settings and registry select a different,
338
399
  verified version. Every other source under that root is re-hashed as usual,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/skills",
3
- "version": "0.10.71",
3
+ "version": "0.10.73",
4
4
  "description": "Skills library for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {