@hasna/skills 0.10.56 → 0.10.57

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/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.56",
20022
+ version: "0.10.57",
20023
20023
  description: "Skills library for AI coding agents",
20024
20024
  type: "module",
20025
20025
  bin: {
package/bin/migrate.js CHANGED
@@ -25,7 +25,7 @@ import { join as join5 } from "path";
25
25
  // package.json
26
26
  var package_default = {
27
27
  name: "@hasna/skills",
28
- version: "0.10.56",
28
+ version: "0.10.57",
29
29
  description: "Skills library for AI coding agents",
30
30
  type: "module",
31
31
  bin: {
package/bin/server.js CHANGED
@@ -24140,7 +24140,7 @@ restoreLaunchCwd();
24140
24140
  // package.json
24141
24141
  var package_default = {
24142
24142
  name: "@hasna/skills",
24143
- version: "0.10.56",
24143
+ version: "0.10.57",
24144
24144
  description: "Skills library for AI coding agents",
24145
24145
  type: "module",
24146
24146
  bin: {
package/bin/worker.js CHANGED
@@ -24143,7 +24143,7 @@ import { randomUUID as randomUUID6 } from "crypto";
24143
24143
  // package.json
24144
24144
  var package_default = {
24145
24145
  name: "@hasna/skills",
24146
- version: "0.10.56",
24146
+ version: "0.10.57",
24147
24147
  description: "Skills library for AI coding agents",
24148
24148
  type: "module",
24149
24149
  bin: {
package/dist/index.d.ts CHANGED
@@ -65,6 +65,8 @@ export type { ClaudeManagedHookProjection, ClaudeManagedHookProjectionOptions }
65
65
  export { captureDiscoveryDirectories, captureDiscoveryByteSources, captureDiscoveryPathSources, DISCOVERY_ROOT_UNRESOLVED, isDiscoveryRootUnresolved, type DiscoveryDirectory, type DiscoveryExecutable, type DiscoverySource } from "./lib/agent-discovery.js";
66
66
  export { captureClaudeMarketplaceRegistry, captureClaudeMarketplaceRegistryV2 } from "./lib/claude-marketplace-registry.js";
67
67
  export { captureClaudeMarketplaceEntry, hashClaudeMarketplaceEntry, CLAUDE_MARKETPLACE_ENTRY_HASH_MODE, type ClaudeMarketplaceEntrySelector, type ClaudeMarketplaceEntryWitness } from "./lib/claude-marketplace-entry-witness.js";
68
+ export { captureClaudeProspectiveCandidateClosure } from "./lib/claude-prospective-review.js";
69
+ export { hashClaudePluginManifest } from "./lib/claude-plugin-manifest-witness.js";
68
70
  export { captureClaudeSettings, captureClaudeSettingsV2, captureClaudeSettingsV3, captureClaudeSettingsV4, upgradeClaudeSettingsWitness, upgradeClaudeSettingsWitnessV4 } from "./lib/claude-settings-witness.js";
69
71
  export { buildPluginProjection, pluginTreeDigest, type PluginProjectionManifest, type PluginPayloadMapping, type PluginFileWitness } from "./lib/plugin-projection.js";
70
72
  export { planPluginAdmission, admitPlugin, resolveAdmittedPlugin, pluginResolverCommand, type PluginAdmissionTarget, type PluginAdmissionBinding, type PluginAdmissionPrincipal, type PluginAdmissionPlan, type PluginAdmissionIdentity, type PluginBundleIdentity, type PluginRoutingIdentity, type PluginAdmissionReceipt } from "./lib/plugin-admission.js";
package/dist/index.js CHANGED
@@ -11833,7 +11833,7 @@ function replaceSkillSession(expected, replacement, plan, options = {}, beforeCo
11833
11833
  // package.json
11834
11834
  var package_default = {
11835
11835
  name: "@hasna/skills",
11836
- version: "0.10.56",
11836
+ version: "0.10.57",
11837
11837
  description: "Skills library for AI coding agents",
11838
11838
  type: "module",
11839
11839
  bin: {
@@ -48343,6 +48343,89 @@ async function reconcileSkillSession(input2, options = {}) {
48343
48343
  const result = replaceSkillSession(skillSessionSnapshotBinding(snapshot2), replacement, { ...plan, planDigest }, options, () => assertPlanWindow(issuedAt, expiresAt, (options.now ?? Date.now)()));
48344
48344
  return { applied: true, plan, planDigest, ...result };
48345
48345
  }
48346
+ // src/lib/claude-prospective-review.ts
48347
+ import { createHash as createHash35 } from "crypto";
48348
+ import { lstatSync as lstatSync28, readFileSync as readFileSync30, readdirSync as readdirSync17, realpathSync as realpathSync15 } from "fs";
48349
+ import { basename as basename14, dirname as dirname30, isAbsolute as isAbsolute26, join as join44, relative as relative12, resolve as resolve27, sep as sep15 } from "path";
48350
+ var MAX_CANDIDATE_FILES = 16384;
48351
+ var MAX_CANDIDATE_BYTES = 256 * 1024 * 1024;
48352
+ var sha6 = (data) => createHash35("sha256").update(data).digest("hex");
48353
+ function refuse4(reason2) {
48354
+ throw new Error(`CLAUDE_CANDIDATE_REVIEW_REFUSED: ${reason2}`);
48355
+ }
48356
+ var GENERATED_MARKETPLACE_TIME = Object.freeze({ $generated: "claude-utc-timestamp", field: "known-marketplaces.lastUpdated" });
48357
+ var GENERATED_FRESH_PLUGIN_INSTALLED_AT = Object.freeze({ $generated: "claude-utc-timestamp", field: "installed_plugins.fresh-row.installedAt" });
48358
+ var GENERATED_PLUGIN_UPDATE_TIME = Object.freeze({ $generated: "claude-utc-timestamp", field: "installed_plugins.selected-row.lastUpdated" });
48359
+ function abs(path, name) {
48360
+ if (typeof path !== "string" || !path || path.length > 4096 || /[\x00-\x1f\x7f]/.test(path) || !isAbsolute26(path) || resolve27(path) !== path)
48361
+ refuse4(`${name} must be a normalized absolute path`);
48362
+ }
48363
+ function safeFile(path, name) {
48364
+ abs(path, name);
48365
+ for (let at = dirname30(path);; at = dirname30(at)) {
48366
+ const stat3 = lstatSync28(at, { throwIfNoEntry: false });
48367
+ if (!stat3?.isDirectory() || stat3.isSymbolicLink())
48368
+ refuse4(`${name} traverses a symlink or non-directory`);
48369
+ if (at === dirname30(at))
48370
+ break;
48371
+ }
48372
+ const stat2 = lstatSync28(path, { throwIfNoEntry: false });
48373
+ if (!stat2?.isFile() || stat2.isSymbolicLink() || stat2.size > MAX_CANDIDATE_BYTES)
48374
+ refuse4(`${name} is not a bounded regular file`);
48375
+ return readFileSync30(path);
48376
+ }
48377
+ function canonicalDir(path, name) {
48378
+ abs(path, name);
48379
+ if (realpathSync15(path) !== path || !lstatSync28(path).isDirectory())
48380
+ refuse4(`${name} is not a canonical directory`);
48381
+ for (let at = path;; at = dirname30(at)) {
48382
+ const stat2 = lstatSync28(at);
48383
+ if (!stat2.isDirectory() || stat2.isSymbolicLink())
48384
+ refuse4(`${name} traverses a symlink`);
48385
+ if (at === dirname30(at))
48386
+ break;
48387
+ }
48388
+ }
48389
+ function captureClaudeProspectiveCandidateClosure(root2) {
48390
+ canonicalDir(root2, "candidate root");
48391
+ const entries = [];
48392
+ let bytes = 0;
48393
+ const walk2 = (directory2) => {
48394
+ const beforeDirectory = lstatSync28(directory2);
48395
+ const names = readdirSync17(directory2).sort();
48396
+ if (new Set(names).size !== names.length)
48397
+ refuse4("candidate contains ambiguous directory entries");
48398
+ for (const name of names) {
48399
+ if (!name || name === "." || name === ".." || name.includes("/") || name.includes("\\") || /[\x00-\x1f\x7f]/.test(name))
48400
+ refuse4("candidate contains an unsupported path member");
48401
+ const path = join44(directory2, name), rel = relative12(root2, path).split(sep15).join("/");
48402
+ if (entries.length >= MAX_CANDIDATE_FILES)
48403
+ refuse4("candidate closure exceeds its member limit");
48404
+ const stat2 = lstatSync28(path);
48405
+ if (stat2.isSymbolicLink())
48406
+ refuse4("candidate closure contains a symlink");
48407
+ if (stat2.isDirectory()) {
48408
+ entries.push([rel, "directory", null]);
48409
+ walk2(path);
48410
+ } else if (stat2.isFile()) {
48411
+ bytes += stat2.size;
48412
+ if (bytes > MAX_CANDIDATE_BYTES)
48413
+ refuse4("candidate closure exceeds its byte limit");
48414
+ const content = safeFile(path, "candidate member"), after = lstatSync28(path);
48415
+ if (!after.isFile() || after.dev !== stat2.dev || after.ino !== stat2.ino || after.size !== stat2.size || after.mtimeMs !== stat2.mtimeMs || after.ctimeMs !== stat2.ctimeMs)
48416
+ refuse4("candidate member changed while being read");
48417
+ entries.push([rel, "file", sha6(content)]);
48418
+ } else
48419
+ refuse4("candidate closure contains a special file");
48420
+ }
48421
+ const afterDirectory = lstatSync28(directory2);
48422
+ if (!afterDirectory.isDirectory() || afterDirectory.dev !== beforeDirectory.dev || afterDirectory.ino !== beforeDirectory.ino || afterDirectory.mtimeMs !== beforeDirectory.mtimeMs || afterDirectory.ctimeMs !== beforeDirectory.ctimeMs)
48423
+ refuse4("candidate directory changed while being read");
48424
+ };
48425
+ walk2(root2);
48426
+ entries.sort((a, b) => a[0].localeCompare(b[0]));
48427
+ return sha6(JSON.stringify(entries));
48428
+ }
48346
48429
  export {
48347
48430
  writeStationSnapshot,
48348
48431
  writeStationHydration,
@@ -48483,6 +48566,7 @@ export {
48483
48566
  inspectPrivatePublication,
48484
48567
  importSkillsLocalSnapshot,
48485
48568
  homePathFor,
48569
+ hashClaudePluginManifest,
48486
48570
  hashClaudeMarketplaceEntry,
48487
48571
  getToolPrimitive,
48488
48572
  getStorageStatus,
@@ -48566,6 +48650,7 @@ export {
48566
48650
  captureClaudeSettingsV3,
48567
48651
  captureClaudeSettingsV2,
48568
48652
  captureClaudeSettings,
48653
+ captureClaudeProspectiveCandidateClosure,
48569
48654
  captureClaudeMarketplaceRegistryV2,
48570
48655
  captureClaudeMarketplaceRegistry,
48571
48656
  captureClaudeMarketplaceEntry,
@@ -99,6 +99,8 @@ export interface AgentIntegrationPlan {
99
99
  }
100
100
  /** Normalize only the verified OS prefix, never hide user-controlled links. */
101
101
  export declare function canonicalSystemPath(path: string): string;
102
+ export declare function canonicalAgentPath(path: string, aliases: AgentRootAlias[]): string;
103
+ export declare function recheckRootAliases(aliases: AgentRootAlias[]): void;
102
104
  /** Parse the reviewed exact-target binding used by native migration. Paths are
103
105
  * deliberately project-relative so a manifest cannot silently select another
104
106
  * checkout when a project root changes. */
@@ -0,0 +1,140 @@
1
+ import { type ReviewedDiscoveryInputs } from "./agent-discovery.js";
2
+ export declare const CLAUDE_PROSPECTIVE_REVIEW_SCHEMA: "skills.claude-plugin-prospective-review/v1";
3
+ export interface ClaudeProspectiveReviewRequest {
4
+ schema: typeof CLAUDE_PROSPECTIVE_REVIEW_SCHEMA;
5
+ scope: {
6
+ kind: "user" | "project";
7
+ projectPath: string;
8
+ };
9
+ native: {
10
+ home: string;
11
+ executable: {
12
+ path: string;
13
+ target: string;
14
+ version: string;
15
+ sha256: string;
16
+ };
17
+ configRoot: string;
18
+ pluginRoot: string;
19
+ };
20
+ skills: {
21
+ dataDir: string;
22
+ policySha256: string;
23
+ discoverySha256: string;
24
+ discoveryInputs: ReviewedDiscoveryInputs;
25
+ };
26
+ candidate: {
27
+ marketplace: string;
28
+ root: string;
29
+ catalogPath: string;
30
+ catalogSha256: string;
31
+ closureSha256: string;
32
+ plugins: Array<{
33
+ plugin: string;
34
+ manifestPath: string;
35
+ manifestSha256: string;
36
+ optionsPatch: Record<string, unknown>;
37
+ }>;
38
+ releaseManifest?: {
39
+ path: string;
40
+ sha256: string;
41
+ };
42
+ functionHooksEnv?: "1";
43
+ };
44
+ preimages: {
45
+ settings: {
46
+ path: string;
47
+ sha256: string | null;
48
+ };
49
+ userSettings: {
50
+ path: string;
51
+ sha256: string | null;
52
+ };
53
+ projectSettings: {
54
+ path: string;
55
+ sha256: string | null;
56
+ };
57
+ projectSettingsLocal: {
58
+ path: string;
59
+ sha256: string | null;
60
+ };
61
+ marketplaces: {
62
+ path: string;
63
+ sha256: string | null;
64
+ };
65
+ installedPlugins: {
66
+ path: string;
67
+ sha256: string | null;
68
+ };
69
+ };
70
+ /** Complete semantic documents expected after native adoption. The one
71
+ * timestamps generated by Claude for the owned marketplace and registration
72
+ * rows use exact typed field markers documented below; every other field is
73
+ * concrete. */
74
+ delta: {
75
+ settingsAfter: Record<string, unknown>;
76
+ knownMarketplacesAfter: Record<string, unknown>;
77
+ installedPluginsAfter: Record<string, unknown>;
78
+ };
79
+ review: {
80
+ pluginHooks: "reviewed-no-skill-injection";
81
+ };
82
+ }
83
+ export interface ClaudeProspectiveReviewReceipt {
84
+ schema: typeof CLAUDE_PROSPECTIVE_REVIEW_SCHEMA;
85
+ accepted: true;
86
+ applied: false;
87
+ scope: ClaudeProspectiveReviewRequest["scope"];
88
+ pluginIds: string[];
89
+ native: {
90
+ executablePath: string;
91
+ executableTarget: string;
92
+ executableVersion: string;
93
+ executableSha256: string;
94
+ configRoot: string;
95
+ pluginRoot: string;
96
+ };
97
+ candidate: {
98
+ marketplace: string;
99
+ plugins: Array<{
100
+ plugin: string;
101
+ pluginId: string;
102
+ manifestSha256: string;
103
+ optionsPatchSha256: string;
104
+ }>;
105
+ closureSha256: string;
106
+ catalogSha256: string;
107
+ releaseManifest?: {
108
+ path: string;
109
+ sha256: string;
110
+ };
111
+ };
112
+ targetSettingsPath: string;
113
+ skillsDiscoverySha256: string;
114
+ requestFileSha256: string;
115
+ semanticDocumentsSha256: string;
116
+ operationSha256: string;
117
+ expectedPostStateSha256: string;
118
+ preimages: Array<{
119
+ name: string;
120
+ path: string;
121
+ sha256: string | null;
122
+ }>;
123
+ ownedDelta: {
124
+ settingsEnabledPlugins: string[];
125
+ selectedPluginOptions: string[];
126
+ functionHooksEnv: string | null;
127
+ knownMarketplace: string;
128
+ installedPlugins: string[];
129
+ unrelatedGraph: "preserved-by-complete-semantic-documents";
130
+ };
131
+ identityPrecondition: "Harnesses must resolve the installed Claude home/config/plugin roots and executable path/version/hash plus the installed Skills data directory through their package-owned consumers, then compare every value before trusting this receipt; this validator checks supplied paths and current bytes only and does not establish actual consumer identity";
132
+ reviewPrecondition: "The caller's no-skill-injection assessment is bound to the raw request hash; structural checks are not semantic hook review, and Harnesses must retain its existing review record and verify the exact candidate binding";
133
+ limitation: "validation-only; native installer atomicity and crash recovery are not established";
134
+ }
135
+ /** Fingerprint every file byte and directory name under the frozen candidate.
136
+ * Symlinks and special files refuse; the digest binds the complete closure. */
137
+ export declare function captureClaudeProspectiveCandidateClosure(root: string): string;
138
+ /** Validate a frozen candidate review against the current filesystem. This is
139
+ * intentionally a planner only: its receipt is not native-install authority. */
140
+ export declare function reviewClaudeProspectiveCandidate(request: ClaudeProspectiveReviewRequest, requestFileSha256: string): ClaudeProspectiveReviewReceipt;
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.56",
32376
+ version: "0.10.57",
32377
32377
  description: "Skills library for AI coding agents",
32378
32378
  type: "module",
32379
32379
  bin: {
@@ -173,6 +173,65 @@ new loaders still require their dedicated review. API failure refuses an update;
173
173
  stale projection as synchronized. Claude may retain its previous installation
174
174
  after a refused update, whose existing local drift checks continue to apply.
175
175
 
176
+ ## Prospective Claude candidate review
177
+
178
+ Before adding or changing a marketplace, an installer may call
179
+ `skills hook review-claude-candidate --request <absolute-json-path> --json`.
180
+ The `skills.claude-plugin-prospective-review/v1` request binds one frozen
181
+ marketplace/catalog closure and a non-empty `plugins` array. Each selected
182
+ plugin supplies its exact `plugin`, `manifestPath`, `manifestSha256` and bounded
183
+ JSON `optionsPatch`; an optional `releaseManifest` carries the separate
184
+ release.json path and raw-byte SHA-256. The request also binds current Skills
185
+ and Claude preimages, explicit user/project scope, caller assessment and one
186
+ complete expected semantic document set for the whole selected plugin batch.
187
+ The validator checks every selected manifest and source against the same
188
+ closure before returning one receipt. It derives the owned changes and refuses
189
+ any unrelated graph change. Harnesses validates each options patch against its
190
+ release manifest before invoking Claude; Skills does not copy its private
191
+ option allowlists or infer that caller-supplied options are release-approved.
192
+ The supported catalog shape uses relative selected-plugin sources inside that
193
+ closure. Nontrivial `metadata.pluginRoot` values and native catalog or selected
194
+ plugin dependency declarations refuse because this validator does not resolve
195
+ or review unselected external plugins.
196
+ The request may declare `functionHooksEnv: "1"`; without that declaration the
197
+ environment document is preserved unchanged. The validator shallow-merges
198
+ each declared options patch into the selected plugin's existing `options`;
199
+ the patch must explicitly bind `enabled: true`, and the validator enforces that
200
+ value after merging. This preserves all other selected options and unrelated
201
+ plugin configuration. It refuses a selected `enabledPlugins` array shape that
202
+ this schema does not model. Existing target registration rows keep their array
203
+ positions, new rows append, and unrelated rows remain byte-semantically bound.
204
+ Its receipt binds the raw request bytes and the complete projected documents;
205
+ semantic digests use UTF-8 JSON with object keys recursively sorted in
206
+ ECMAScript UTF-16 order, array order retained and JavaScript JSON primitive
207
+ serialization.
208
+
209
+ Native-generated fields use typed markers only in the exact selected rows:
210
+ `{"$generated":"claude-utc-timestamp","field":"<path>"}` requires a valid generated
211
+ UTC timestamp at that field. For a native update that may preserve or refresh
212
+ `lastUpdated`, `{"$allowed":[<exact prior timestamp>,<generated marker>]}` permits
213
+ only those two values. Existing `installedAt` remains exact; a fresh install's
214
+ `installedAt` is generated. No other row fields accept markers.
215
+
216
+ This is a read-only validator. It does not establish that caller-supplied
217
+ `native.home`, config/plugin roots, executable identity or Skills `dataDir` are
218
+ the installed consumers. Harnesses must resolve them through the installed
219
+ package-owned consumers and compare them before trusting the receipt. It must
220
+ also retain its existing review record and bind it to the exact candidate:
221
+ the caller's no-skill-injection assessment is not a semantic hook review, and
222
+ structural refusal checks do not prove hook behavior. Recheck all preimages and
223
+ candidate closure immediately before the first native mutation. The validator
224
+ does not claim native install atomicity or crash recovery.
225
+
226
+ The optional release manifest witness binds the complete supplied release.json
227
+ file bytes, path and SHA-256 in the request/receipt. Harnesses remains
228
+ responsible for verifying that its release options correspond to every
229
+ selected `optionsPatch`; the public Skills validator deliberately does not
230
+ interpret private release schemas.
231
+ Installed consumers can use the public `captureClaudeProspectiveCandidateClosure`
232
+ and `hashClaudePluginManifest` exports to prepare the exact digests without
233
+ copying the package's hashing algorithms.
234
+
176
235
  ## Reviewed plugin-hook attestation scope
177
236
 
178
237
  A reviewed discovery input carries `pluginHooks: "reviewed-no-skill-injection"`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/skills",
3
- "version": "0.10.56",
3
+ "version": "0.10.57",
4
4
  "description": "Skills library for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {