@hasna/skills 0.10.2 → 0.10.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 +14 -9
- package/bin/index.js +119 -42
- package/bin/mcp.js +1 -1
- package/bin/migrate.js +1 -1
- package/bin/server.js +1 -1
- package/bin/worker.js +1 -1
- package/dist/cli/commands/context.d.ts +1 -0
- package/dist/index.js +18 -6
- package/dist/lib/agent-integration.d.ts +2 -0
- package/dist/lib/session-reconciliation.d.ts +7 -1
- package/dist/sdk/index.js +18 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -453,17 +453,22 @@ during installation. A successful update does not reload an existing session.
|
|
|
453
453
|
At session start, the hook authenticates and refreshes the profile. Prompt hooks
|
|
454
454
|
select complete skill instructions from that verified cache using explicit
|
|
455
455
|
`$skill` references, profile keywords, paths and always-required selections.
|
|
456
|
-
A session retains its
|
|
456
|
+
A session retains its loaded selections; compaction restores loaded instructions,
|
|
457
457
|
and subagents inherit the parent's selection. Instructions that exceed the
|
|
458
458
|
context budget produce an explicit `skills load` command. A hook never executes
|
|
459
|
-
a skill. Cached use is explicit and expires after 24 hours. When
|
|
460
|
-
expires
|
|
461
|
-
|
|
462
|
-
receipt
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
459
|
+
a skill. Cached use is explicit and expires after 24 hours. When a session
|
|
460
|
+
receipt expires, the hook authenticates the current profile. If every loaded
|
|
461
|
+
selection still has identical metadata and bundle digest, it archives the old
|
|
462
|
+
receipt and advances that session to the current profile under an exact receipt
|
|
463
|
+
precondition. Unloaded selections may be added or removed. If a loaded selection
|
|
464
|
+
changed or disappeared, the hook blocks and requires the explicit reconciliation
|
|
465
|
+
below. A missing cache may be recovered through authenticated context.
|
|
466
|
+
Authentication, integrity and native discovery failures still block; they do
|
|
467
|
+
not silently switch to a local catalog.
|
|
468
|
+
|
|
469
|
+
An intentional change to a loaded selection or profile identity for an already
|
|
470
|
+
running session uses a separate reviewed operation. `sync` and hook
|
|
471
|
+
reinstallation do not migrate session pins:
|
|
467
472
|
|
|
468
473
|
```bash
|
|
469
474
|
skills sessions show <session-id> --json
|
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.
|
|
36871
|
+
version: "0.10.3",
|
|
36872
36872
|
description: "Skills library for AI coding agents",
|
|
36873
36873
|
type: "module",
|
|
36874
36874
|
bin: {
|
|
@@ -51164,7 +51164,7 @@ function parseNativeMigrationTargetManifest(input) {
|
|
|
51164
51164
|
if (!target || typeof target !== "object" || Array.isArray(target))
|
|
51165
51165
|
throw new Error("Invalid native migration target");
|
|
51166
51166
|
const item = target;
|
|
51167
|
-
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")
|
|
51167
|
+
if (Object.keys(item).some((key) => !["agent", "projectRoot", "path", "treeSha256", "vendor"].includes(key)) || typeof item.agent !== "string" || typeof item.projectRoot !== "string" || typeof item.path !== "string" || typeof item.treeSha256 !== "string" || item.vendor !== undefined && item.vendor !== true)
|
|
51168
51168
|
throw new Error("Invalid native migration target fields");
|
|
51169
51169
|
if (!ROOTS.some(([agent]) => agent === item.agent))
|
|
51170
51170
|
throw new Error(`Unsupported native migration target agent: ${item.agent}`);
|
|
@@ -51183,7 +51183,7 @@ function parseNativeMigrationTargetManifest(input) {
|
|
|
51183
51183
|
if (identities.has(identity4))
|
|
51184
51184
|
throw new Error(`Duplicate native migration target: ${item.path}`);
|
|
51185
51185
|
identities.add(identity4);
|
|
51186
|
-
targets.push({ agent: item.agent, projectRoot, path: normalized, treeSha256: item.treeSha256 });
|
|
51186
|
+
targets.push({ agent: item.agent, projectRoot, path: normalized, treeSha256: item.treeSha256, ...item.vendor === true ? { vendor: true } : {} });
|
|
51187
51187
|
}
|
|
51188
51188
|
return { schema: "hasna.skills-native-migration-targets.v1", targets, digest };
|
|
51189
51189
|
}
|
|
@@ -51211,8 +51211,8 @@ function selectNativeMigrationTargets(inventory, manifest) {
|
|
|
51211
51211
|
if (matches2.length !== 1)
|
|
51212
51212
|
throw new Error(`Native migration target was not found exactly once: ${target.agent} ${target.projectRoot}/${target.path}`);
|
|
51213
51213
|
const entry = matches2[0];
|
|
51214
|
-
if (entry.bridge || entry.system || entry.vendor)
|
|
51215
|
-
throw new Error(`Native migration target is protected or vendor
|
|
51214
|
+
if (entry.bridge || entry.system || entry.vendor !== (target.vendor === true))
|
|
51215
|
+
throw new Error(`Native migration target is protected or vendor classification changed: ${entry.path}`);
|
|
51216
51216
|
if (entry.hash !== target.treeSha256 || treeHash(entry.path) !== target.treeSha256)
|
|
51217
51217
|
throw new Error(`Native migration target changed after review: ${entry.path}`);
|
|
51218
51218
|
selected.push(entry);
|
|
@@ -51235,6 +51235,8 @@ function projectAncestorDirectories(projects) {
|
|
|
51235
51235
|
return [...directories];
|
|
51236
51236
|
}
|
|
51237
51237
|
function inventoryNativeSkills(home = homedir3(), options = {}) {
|
|
51238
|
+
if (options.reviewedCacheAlias !== undefined && (!options.includeVendor || !isAbsolute9(options.reviewedCacheAlias) || resolve10(options.reviewedCacheAlias) !== options.reviewedCacheAlias))
|
|
51239
|
+
throw new Error("A reviewed cache alias requires vendor inventory and an exact absolute path");
|
|
51238
51240
|
const aliases = rootAliases(home, options.allowRootAliases);
|
|
51239
51241
|
const selectedAgents = options.agents ? new Set(options.agents) : undefined;
|
|
51240
51242
|
const includesAgent = (agent) => !selectedAgents || selectedAgents.has(agent);
|
|
@@ -51255,14 +51257,22 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
|
|
|
51255
51257
|
if (discoveryPathBytes > 4 * 1024 * 1024)
|
|
51256
51258
|
throw new Error("Native skill discovery metadata limit exceeded");
|
|
51257
51259
|
}
|
|
51258
|
-
|
|
51260
|
+
let reviewedCacheAliasFound = false;
|
|
51261
|
+
function verifiedSiblingCacheAlias(path, parent) {
|
|
51259
51262
|
const link2 = readlinkSync3(path), target = resolve10(parent, link2);
|
|
51260
51263
|
if (dirname10(target) !== parent || (isAbsolute9(link2) ? link2 !== target : dirname10(link2) !== "."))
|
|
51261
51264
|
return false;
|
|
51262
51265
|
if (!lstatSync14(target, { throwIfNoEntry: false })?.isDirectory())
|
|
51263
51266
|
return false;
|
|
51264
51267
|
const scan = cacheScans.get(target);
|
|
51265
|
-
|
|
51268
|
+
if (!scan?.complete || scan.entries > maxAliasProofEntries)
|
|
51269
|
+
return false;
|
|
51270
|
+
if (scan.hasSkills) {
|
|
51271
|
+
if (path !== options.reviewedCacheAlias)
|
|
51272
|
+
return false;
|
|
51273
|
+
reviewedCacheAliasFound = true;
|
|
51274
|
+
}
|
|
51275
|
+
return true;
|
|
51266
51276
|
}
|
|
51267
51277
|
function visit2(agent, path, vendor = false, depth = 0, pluginCache = false, admitted = false) {
|
|
51268
51278
|
if (!admitted)
|
|
@@ -51319,7 +51329,7 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
|
|
|
51319
51329
|
const child = join20(path, name);
|
|
51320
51330
|
if (isVendor && lstatSync14(child, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
|
51321
51331
|
const target = statSync7(child, { throwIfNoEntry: false });
|
|
51322
|
-
if (target?.isFile() || pluginCache && target?.isDirectory() &&
|
|
51332
|
+
if (target?.isFile() || pluginCache && target?.isDirectory() && verifiedSiblingCacheAlias(child, path)) {
|
|
51323
51333
|
scan.entries++;
|
|
51324
51334
|
continue;
|
|
51325
51335
|
}
|
|
@@ -51353,6 +51363,8 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
|
|
|
51353
51363
|
for (const root of [...options.agentRoots ?? [], ...configured])
|
|
51354
51364
|
if (includesAgent(root.agent))
|
|
51355
51365
|
scanRoot(root.agent, canonicalAgentPath(root.path, aliases), true);
|
|
51366
|
+
if (options.reviewedCacheAlias !== undefined && !reviewedCacheAliasFound)
|
|
51367
|
+
throw new Error("Reviewed cache alias was not an inventoried skill-containing sibling alias");
|
|
51356
51368
|
recheckRootAliases(aliases);
|
|
51357
51369
|
return entries;
|
|
51358
51370
|
}
|
|
@@ -81557,6 +81569,7 @@ var init_skill_context = __esm(() => {
|
|
|
81557
81569
|
|
|
81558
81570
|
// src/lib/session-reconciliation.ts
|
|
81559
81571
|
import { createHash as createHash22 } from "crypto";
|
|
81572
|
+
import { isDeepStrictEqual as isDeepStrictEqual8 } from "util";
|
|
81560
81573
|
function exactTimestamp(value) {
|
|
81561
81574
|
const parsed = value === undefined ? Number.NaN : Date.parse(value);
|
|
81562
81575
|
if (!Number.isFinite(parsed) || new Date(parsed).toISOString() !== value) {
|
|
@@ -81589,6 +81602,52 @@ function inspectSkillSession(sessionId, options = {}) {
|
|
|
81589
81602
|
loadedCount: receipt.loaded.length
|
|
81590
81603
|
};
|
|
81591
81604
|
}
|
|
81605
|
+
async function reconcileSkillSessionIfSafe(sessionId, profileId, options = {}) {
|
|
81606
|
+
const snapshot2 = readSkillSessionSnapshotIfExists(sessionId, options);
|
|
81607
|
+
if (!snapshot2)
|
|
81608
|
+
return false;
|
|
81609
|
+
const old = snapshot2.receipt;
|
|
81610
|
+
if (old.profile.profileId !== profileId)
|
|
81611
|
+
throw new SkillSelectionError("PROFILE_LOCK_MISMATCH", "The session is pinned to a different Skills profile.");
|
|
81612
|
+
const now3 = (options.now ?? Date.now)();
|
|
81613
|
+
const age = now3 - Date.parse(old.verifiedAt);
|
|
81614
|
+
if (age >= 0 && age <= MAX_CACHED_PROFILE_AGE_MS)
|
|
81615
|
+
return false;
|
|
81616
|
+
if (age < 0)
|
|
81617
|
+
throw new SkillSelectionError("SESSION_RECONCILIATION_REQUIRED", "The session receipt has a future timestamp; inspect it before changing its pin.");
|
|
81618
|
+
const client2 = options.client ?? await createProfileClient();
|
|
81619
|
+
const target = structuredClone(await client2.resolveProfile(profileId));
|
|
81620
|
+
validateResolvedProfile(target, client2.authority);
|
|
81621
|
+
if (target.profileId !== profileId || target.authority !== old.profile.authority || target.workspaceId !== old.profile.workspaceId) {
|
|
81622
|
+
throw new SkillSelectionError("PROFILE_IDENTITY_MISMATCH", "The authenticated Skills profile does not match this session's profile, authority and workspace.");
|
|
81623
|
+
}
|
|
81624
|
+
const oldByKey = new Map(old.profile.selections.map((selection) => [selectionKey(selection), selection]));
|
|
81625
|
+
const targetBySlug = new Map(target.selections.map((selection) => [selection.slug, selection]));
|
|
81626
|
+
for (const key of old.loaded) {
|
|
81627
|
+
const previous = oldByKey.get(key);
|
|
81628
|
+
const current = targetBySlug.get(previous.slug);
|
|
81629
|
+
if (!current || !isDeepStrictEqual8({ ...previous, profileRevision: target.profileRevision }, current)) {
|
|
81630
|
+
throw new SkillSelectionError("SESSION_RECONCILIATION_REQUIRED", "A loaded Skills selection was removed or changed; review skills sessions reconcile before changing this session's pin.");
|
|
81631
|
+
}
|
|
81632
|
+
}
|
|
81633
|
+
const replacement = {
|
|
81634
|
+
...old,
|
|
81635
|
+
generation: nextSkillSessionGeneration(snapshot2.generation),
|
|
81636
|
+
verifiedAt: new Date((options.now ?? Date.now)()).toISOString(),
|
|
81637
|
+
profile: target,
|
|
81638
|
+
loaded: [...old.loaded]
|
|
81639
|
+
};
|
|
81640
|
+
replaceSkillSession(skillSessionSnapshotBinding(snapshot2), replacement, {
|
|
81641
|
+
schemaVersion: 1,
|
|
81642
|
+
kind: "managed-hook-safe-reconciliation",
|
|
81643
|
+
sessionId,
|
|
81644
|
+
fromProfileRevision: old.profile.profileRevision,
|
|
81645
|
+
toProfileRevision: target.profileRevision,
|
|
81646
|
+
retainedLoadedCount: old.loaded.length,
|
|
81647
|
+
retiredLoadedCount: 0
|
|
81648
|
+
}, options);
|
|
81649
|
+
return true;
|
|
81650
|
+
}
|
|
81592
81651
|
async function reconcileSkillSession(input2, options = {}) {
|
|
81593
81652
|
if (![input2.fromProfile, input2.fromRevision, input2.selectionProfile, input2.profileRevision].every((value) => typeof value === "string" && value.trim()) || !/^[a-f0-9]{64}$/.test(input2.receiptSha256)) {
|
|
81594
81653
|
throw new SkillSelectionError("INVALID_SESSION_RECONCILIATION", "Name the exact old receipt SHA256, old profile/revision and intended target profile/revision.");
|
|
@@ -81707,7 +81766,7 @@ Review digest: ${result2.reviewDigest}`);
|
|
|
81707
81766
|
reportContextError(error61, options.json);
|
|
81708
81767
|
}
|
|
81709
81768
|
});
|
|
81710
|
-
sessions.command("reconcile <id>").requiredOption("--from-profile <id>", "Expected current session profile").requiredOption("--from-revision <revision>", "Expected current session profile revision").requiredOption("--receipt-sha256 <sha256>", "Expected exact current receipt bytes").requiredOption("--selection-profile <id>", "Intended target selection profile").requiredOption("--profile-revision <revision>", "Expected current API target revision").option("--apply", "Archive the old receipt and atomically apply the reviewed plan", false).option("--plan-digest <sha256>", "Exact reviewed plan digest; required with --apply").option("--plan-issued-at <timestamp>", "Exact reviewed plan issuedAt; required with --apply").option("--plan-expires-at <timestamp>", "Exact reviewed plan expiresAt; required with --apply").option("--json", "Return the plan or application receipt as JSON", false).description("Plan an intentional migration of one session pin
|
|
81769
|
+
sessions.command("reconcile <id>").requiredOption("--from-profile <id>", "Expected current session profile").requiredOption("--from-revision <revision>", "Expected current session profile revision").requiredOption("--receipt-sha256 <sha256>", "Expected exact current receipt bytes").requiredOption("--selection-profile <id>", "Intended target selection profile").requiredOption("--profile-revision <revision>", "Expected current API target revision").option("--apply", "Archive the old receipt and atomically apply the reviewed plan", false).option("--plan-digest <sha256>", "Exact reviewed plan digest; required with --apply").option("--plan-issued-at <timestamp>", "Exact reviewed plan issuedAt; required with --apply").option("--plan-expires-at <timestamp>", "Exact reviewed plan expiresAt; required with --apply").option("--json", "Return the plan or application receipt as JSON", false).description("Plan an intentional migration of one session pin when loaded selections changed").action(async (id, options) => {
|
|
81711
81770
|
try {
|
|
81712
81771
|
const result2 = await reconcileSkillSession({ ...options, sessionId: id });
|
|
81713
81772
|
await writeCliOutput(options.json ? JSON.stringify(result2) : result2.applied ? `Reconciled ${JSON.stringify(id)} to ${result2.plan.target.profileId} at ${result2.plan.target.profileRevision}.
|
|
@@ -81729,18 +81788,27 @@ Preserved original receipt: ${result2.archivePath}` : `Planned one session recon
|
|
|
81729
81788
|
reportContextError(error61, options.json);
|
|
81730
81789
|
}
|
|
81731
81790
|
});
|
|
81732
|
-
parent.command("context [prompt]").description("Select bounded, versioned skill context for this prompt without running skills").option("--stdin", "Read native hook JSON or a prompt from stdin", false).option("--selection-profile <id>", "Selection profile (separate from the credential --profile)").option("--cached", "Use only an explicitly selected verified cache, at most 24 hours old", false).option("--session <id>", "Keep the same skill versions across this session").option("--restore", "Re-emit loaded session context after compaction or resume", false).option("--max-chars <count>", "Maximum injected characters", "8000").option("--max-skills <count>", "Maximum loaded skills", "3").option("--json", "Return context and selection receipts as JSON", false).action(async (prompt, options) => {
|
|
81791
|
+
parent.command("context [prompt]").description("Select bounded, versioned skill context for this prompt without running skills").option("--stdin", "Read native hook JSON or a prompt from stdin", false).option("--selection-profile <id>", "Selection profile (separate from the credential --profile)").option("--cached", "Use only an explicitly selected verified cache, at most 24 hours old", false).option("--auto-reconcile-safe", "For managed hooks, archive and renew an expired session only when all loaded selections are unchanged", false).option("--session <id>", "Keep the same skill versions across this session").option("--restore", "Re-emit loaded session context after compaction or resume", false).option("--max-chars <count>", "Maximum injected characters", "8000").option("--max-skills <count>", "Maximum loaded skills", "3").option("--json", "Return context and selection receipts as JSON", false).action(async (prompt, options) => {
|
|
81733
81792
|
try {
|
|
81793
|
+
if (options.autoReconcileSafe && !options.cached)
|
|
81794
|
+
throw new SkillSelectionError("INVALID_CONTEXT_INPUT", "--auto-reconcile-safe requires --cached.");
|
|
81734
81795
|
const input2 = options.stdin ? parseSkillContextInput(await readContextStdin()) : { prompt: prompt ?? "" };
|
|
81735
81796
|
input2.profileId = selectedProfileId(options.selectionProfile ?? input2.profileId);
|
|
81736
81797
|
input2.cwd ??= process.cwd();
|
|
81737
81798
|
input2.sessionId = options.session ?? input2.sessionId;
|
|
81738
81799
|
input2.restore ||= options.restore;
|
|
81739
|
-
const
|
|
81740
|
-
|
|
81741
|
-
|
|
81742
|
-
|
|
81743
|
-
})
|
|
81800
|
+
const resolverOptions = { ...contextResolverOptions(options), maxChars: Number(options.maxChars), maxSkills: Number(options.maxSkills) };
|
|
81801
|
+
let result2;
|
|
81802
|
+
try {
|
|
81803
|
+
result2 = await buildSkillContext(input2, resolverOptions);
|
|
81804
|
+
} catch (error61) {
|
|
81805
|
+
if (!options.cached || !options.autoReconcileSafe || !(error61 instanceof SkillSelectionError) || error61.code !== "CACHED_PROFILE_EXPIRED" || !input2.sessionId)
|
|
81806
|
+
throw error61;
|
|
81807
|
+
const sessionId = input2.agentId ? `${input2.sessionId}:${input2.agentId}` : input2.sessionId;
|
|
81808
|
+
if (!await reconcileSkillSessionIfSafe(sessionId, input2.profileId, resolverOptions))
|
|
81809
|
+
throw error61;
|
|
81810
|
+
result2 = await buildSkillContext(input2, resolverOptions);
|
|
81811
|
+
}
|
|
81744
81812
|
await writeCliOutput(options.json ? JSON.stringify(result2) : result2.context);
|
|
81745
81813
|
} catch (error61) {
|
|
81746
81814
|
reportContextError(error61, options.json);
|
|
@@ -86158,7 +86226,7 @@ var init_execution_grants = __esm(() => {
|
|
|
86158
86226
|
});
|
|
86159
86227
|
|
|
86160
86228
|
// src/lib/execution-grant-client.ts
|
|
86161
|
-
import { isDeepStrictEqual as
|
|
86229
|
+
import { isDeepStrictEqual as isDeepStrictEqual9 } from "util";
|
|
86162
86230
|
function id(value) {
|
|
86163
86231
|
if (!grantIdentifier(value))
|
|
86164
86232
|
throw new Error("Invalid execution grant profile or revision identifier.");
|
|
@@ -86210,7 +86278,7 @@ async function saveExecutionGrantPolicy(profileId, grants, revision) {
|
|
|
86210
86278
|
headers: revision === undefined ? { "If-None-Match": "*" } : { "If-Match": `"${revision}"` },
|
|
86211
86279
|
body: JSON.stringify({ grants: snapshot2 })
|
|
86212
86280
|
}));
|
|
86213
|
-
if (policy.profileId !== profileId || policy.previousRevision !== (revision ?? null) || !
|
|
86281
|
+
if (policy.profileId !== profileId || policy.previousRevision !== (revision ?? null) || !isDeepStrictEqual9(policy.grants, snapshot2)) {
|
|
86214
86282
|
throw new Error("The API did not preserve the reviewed execution grant policy; read it before retrying.");
|
|
86215
86283
|
}
|
|
86216
86284
|
return policy;
|
|
@@ -86221,7 +86289,7 @@ async function resolveExecutionGrant(input2) {
|
|
|
86221
86289
|
if (!grantRecord(value) || Object.keys(value).length !== 3 || !grantIdentifier(value.policyRevision) || !grantIdentifier(value.grantId) || !grantRecord(value.bindings)) {
|
|
86222
86290
|
throw new SkillSelectionError("INVALID_EXECUTION_GRANT", "The Skills API returned an invalid execution grant.");
|
|
86223
86291
|
}
|
|
86224
|
-
if (!
|
|
86292
|
+
if (!isDeepStrictEqual9(value.bindings.selection, snapshot2.selection) || !isDeepStrictEqual9(value.bindings.consumer, snapshot2.consumer)) {
|
|
86225
86293
|
throw new SkillSelectionError("EXECUTION_GRANT_SCOPE_MISMATCH", "The Skills API returned a grant for a different selection or consumer.");
|
|
86226
86294
|
}
|
|
86227
86295
|
return value;
|
|
@@ -109440,7 +109508,7 @@ var init_create_sync_config = __esm(() => {
|
|
|
109440
109508
|
});
|
|
109441
109509
|
|
|
109442
109510
|
// src/lib/codex-native-config.ts
|
|
109443
|
-
import { isDeepStrictEqual as
|
|
109511
|
+
import { isDeepStrictEqual as isDeepStrictEqual10 } from "util";
|
|
109444
109512
|
function isRecord7(value) {
|
|
109445
109513
|
if (!value || typeof value !== "object")
|
|
109446
109514
|
return false;
|
|
@@ -109462,7 +109530,7 @@ function decimalIdentity(text4) {
|
|
|
109462
109530
|
return `${match[1]}${coefficient}e${exponent}`;
|
|
109463
109531
|
}
|
|
109464
109532
|
function codexNativeConfigEqual(native, expected) {
|
|
109465
|
-
if (
|
|
109533
|
+
if (isDeepStrictEqual10(native, expected))
|
|
109466
109534
|
return true;
|
|
109467
109535
|
if (typeof expected === "number") {
|
|
109468
109536
|
if (!Number.isFinite(expected) || !isRecord7(native))
|
|
@@ -109628,7 +109696,7 @@ var init_codex_hook_rpc = __esm(() => {
|
|
|
109628
109696
|
// src/lib/codex-hook-trust-identity.ts
|
|
109629
109697
|
import { realpathSync as realpathSync13 } from "fs";
|
|
109630
109698
|
import { dirname as dirname31, isAbsolute as isAbsolute25, join as join57 } from "path";
|
|
109631
|
-
import { isDeepStrictEqual as
|
|
109699
|
+
import { isDeepStrictEqual as isDeepStrictEqual11 } from "util";
|
|
109632
109700
|
function inspectRecordedSkillsCli(command, expected) {
|
|
109633
109701
|
need4(isAbsolute25(command) && !command.includes("\x00"), "RECONCILE_RECORDED_COMMAND_REQUIRED");
|
|
109634
109702
|
const resolved = realpathSync13(command);
|
|
@@ -109638,7 +109706,7 @@ function inspectRecordedSkillsCli(command, expected) {
|
|
|
109638
109706
|
need4(pkg.name === "@hasna/skills" && /^\d+\.\d+\.\d+$/.test(pkg.version) && pkg.bin?.skills === "bin/index.js" && resolved === join57(dirname31(manifest.file), "bin/index.js"), "SKILLS_PACKAGE_MISMATCH");
|
|
109639
109707
|
need4((cli.stat.mode & 0o100n) !== 0n, "SKILLS_COMMAND_NOT_EXECUTABLE");
|
|
109640
109708
|
const receipt = { path: resolved, version: pkg.version, sha256: cli.sha256, manifestSha256: manifest.sha256 };
|
|
109641
|
-
need4(
|
|
109709
|
+
need4(isDeepStrictEqual11(receipt, expected), "RECONCILE_SKILLS_BINDING_CHANGED");
|
|
109642
109710
|
return { receipt, recheck() {
|
|
109643
109711
|
need4(realpathSync13(command) === resolved, "SKILLS_COMMAND_CHANGED");
|
|
109644
109712
|
unchanged2(cli);
|
|
@@ -109674,19 +109742,19 @@ var init_codex_hook_trust_identity = __esm(() => {
|
|
|
109674
109742
|
});
|
|
109675
109743
|
|
|
109676
109744
|
// src/lib/codex-hook-policy-recovery.ts
|
|
109677
|
-
import { isDeepStrictEqual as
|
|
109745
|
+
import { isDeepStrictEqual as isDeepStrictEqual12 } from "util";
|
|
109678
109746
|
function reviewedClaudeDiscoveryChange(beforeText, currentText, options) {
|
|
109679
109747
|
need4(typeof options.reason === "string" && options.reason.trim().length > 0 && options.reason.length <= 512 && !/[\x00-\x1f\x7f]/.test(options.reason), "RECONCILE_RECOVERY_REASON_REQUIRED");
|
|
109680
109748
|
need4(options.reviewedPlanDigest === undefined || /^[a-f0-9]{64}$/.test(options.reviewedPlanDigest), "RECONCILE_RECOVERY_PLAN_CHANGED");
|
|
109681
109749
|
const before2 = parseManagedSkillPolicy(beforeText), current = parseManagedSkillPolicy(currentText);
|
|
109682
109750
|
const oldDiscovery = before2.bridge?.discovery?.claude, newDiscovery = current.bridge?.discovery?.claude;
|
|
109683
109751
|
need4(oldDiscovery?.agent === "claude" && newDiscovery?.agent === "claude" && oldDiscovery.method === "reviewed" && newDiscovery.method === "reviewed", "RECONCILE_POLICY_CHANGED");
|
|
109684
|
-
const changes = ["roots", "sources"].filter((key) => !
|
|
109752
|
+
const changes = ["roots", "sources"].filter((key) => !isDeepStrictEqual12(oldDiscovery[key], newDiscovery[key])).map((key) => ({ path: `bridge.discovery.claude.${key}`, before: oldDiscovery[key], after: newDiscovery[key] }));
|
|
109685
109753
|
need4(changes.length > 0, "RECONCILE_POLICY_CHANGED");
|
|
109686
109754
|
const compared = structuredClone(current);
|
|
109687
109755
|
compared.bridge.discovery.claude.roots = oldDiscovery.roots;
|
|
109688
109756
|
compared.bridge.discovery.claude.sources = oldDiscovery.sources;
|
|
109689
|
-
need4(
|
|
109757
|
+
need4(isDeepStrictEqual12(before2, compared), "RECONCILE_POLICY_CHANGED");
|
|
109690
109758
|
verifyAgentDiscovery(newDiscovery);
|
|
109691
109759
|
return { reason: options.reason, changes };
|
|
109692
109760
|
}
|
|
@@ -109702,7 +109770,7 @@ import { createHash as createHash28, randomUUID as randomUUID10 } from "crypto";
|
|
|
109702
109770
|
import { execFileSync } from "child_process";
|
|
109703
109771
|
import { dirname as dirname32, isAbsolute as isAbsolute26, join as join58, resolve as resolve23 } from "path";
|
|
109704
109772
|
import { homedir as homedir12 } from "os";
|
|
109705
|
-
import { isDeepStrictEqual as
|
|
109773
|
+
import { isDeepStrictEqual as isDeepStrictEqual13 } from "util";
|
|
109706
109774
|
function verifyReleaseArtifact(proof, skillsCli) {
|
|
109707
109775
|
need4(isAbsolute26(proof.packageTarPath) && !proof.packageTarPath.includes("\x00"), "RECONCILE_RELEASE_PROVENANCE_PATH");
|
|
109708
109776
|
const tarPath = realpathSync14(proof.packageTarPath);
|
|
@@ -109744,7 +109812,7 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
109744
109812
|
const configSha256 = intent.configSha256 ?? configBefore.sha256, configVersion = intent.configVersion ?? intent.expectedVersion;
|
|
109745
109813
|
const digestInput = { version: 1, home: intent.home ?? home, configPath: intent.configPath, ...intent.nativeConfigPath !== undefined ? { nativeConfigPath: intent.nativeConfigPath } : {}, configSha256, configVersion, policySha256: intent.policySha256, hooksSha256: intent.hooksSha256, skillsCli: intent.skillsCli, nativeVersion: intent.nativeVersion, declarations, admitted };
|
|
109746
109814
|
need4(makePlanDigest(digestInput) === intent.planDigest, "RECONCILE_PLAN_CHANGED");
|
|
109747
|
-
need4(
|
|
109815
|
+
need4(isDeepStrictEqual13(intent.hooks, admitted.filter((hook) => !hook.enabled || hook.trustStatus !== "trusted")), "RECONCILE_INTENT_HOOKS_CHANGED");
|
|
109748
109816
|
need4(configBefore.sha256 === (intent.beforeSha256 ?? intent.configSha256) && hooksBefore.sha256 === intent.hooksSha256 && policyBefore.sha256 === intent.policySha256, "RECONCILE_JOURNAL_CHANGED");
|
|
109749
109817
|
const policyCurrent = snapshot(join58(dataDir3, "agent-policy.json"), true);
|
|
109750
109818
|
const { claudeDiscoveryRecovery: claudeRecovery, nativeHookAdditionsRecovery: additionsRecovery } = options;
|
|
@@ -109757,7 +109825,7 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
109757
109825
|
if (policyCurrent.sha256 !== policyBefore.sha256 && !claudeRecovery) {
|
|
109758
109826
|
need4(options.supersedeBinding === true, "RECONCILE_POLICY_CHANGED");
|
|
109759
109827
|
const oldPolicy = JSON.parse(policyBefore.text), currentPolicy = JSON.parse(policyCurrent.text);
|
|
109760
|
-
need4(
|
|
109828
|
+
need4(isDeepStrictEqual13(withoutCodexCommand(oldPolicy), withoutCodexCommand(currentPolicy)), "RECONCILE_POLICY_CHANGED");
|
|
109761
109829
|
supersededBinding = true;
|
|
109762
109830
|
}
|
|
109763
109831
|
if (supersededBinding || recovery) {
|
|
@@ -109793,7 +109861,7 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
109793
109861
|
need4(state?.enabled === true && typeof state.trusted_hash === "string" && /^sha256:[a-f0-9]{64}$/.test(state.trusted_hash), "RECONCILE_CONFIG_DRIFT");
|
|
109794
109862
|
}
|
|
109795
109863
|
} else
|
|
109796
|
-
need4(
|
|
109864
|
+
need4(isDeepStrictEqual13(expectedConfig, current), "RECONCILE_CONFIG_DRIFT");
|
|
109797
109865
|
const configDiscoveryRecovery = recovery && binding.discovery?.codex ? createCodexHookDiscoveryRecovery(binding.discovery.codex, configBefore, currentConfig, intent.hooks, additions?.trustProof) : undefined;
|
|
109798
109866
|
assertManagedAgentBridge("codex", { home, dataDir: dataDir3, projectDir: home, codexDiscoveryRecovery: configDiscoveryRecovery });
|
|
109799
109867
|
const recoveryCli = recovery ? bindSkillsCli("skills", options.reviewedSkillsCli) : undefined;
|
|
@@ -109802,14 +109870,14 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
109802
109870
|
const skillsCli = recovery ? inspectRecordedSkillsCli(binding.commands.codex, intent.skillsCli) : bindSkillsCli(binding.commands.codex, options.reviewedSkillsCli);
|
|
109803
109871
|
if (supersededBinding)
|
|
109804
109872
|
verifyReleaseArtifact(options.releaseProof, skillsCli);
|
|
109805
|
-
const skillsBindingChanged = !
|
|
109873
|
+
const skillsBindingChanged = !isDeepStrictEqual13(skillsCli.receipt, intent.skillsCli);
|
|
109806
109874
|
need4(!skillsBindingChanged || supersededBinding, "RECONCILE_SKILLS_BINDING_CHANGED");
|
|
109807
109875
|
if (!additions && hooksCurrent.sha256 !== intent.hooksSha256 && hooksCurrent.sha256 !== hooksBefore.sha256) {
|
|
109808
109876
|
need4(supersededBinding, "RECONCILE_HOOKS_CHANGED");
|
|
109809
109877
|
const currentDeclarations = JSON.parse(hooksCurrent.text).hooks;
|
|
109810
109878
|
for (const [event, [nativeEvent]] of Object.entries(eventNames)) {
|
|
109811
109879
|
const command = renderAgentHookCommand(binding.commands.codex, "codex", binding.profiles.codex, event);
|
|
109812
|
-
need4(
|
|
109880
|
+
need4(isDeepStrictEqual13(currentDeclarations?.[event], [{ hooks: [{ type: "command", command, timeout: 15 }] }]), "RECONCILE_BINDING_HOOK_CHANGED");
|
|
109813
109881
|
}
|
|
109814
109882
|
}
|
|
109815
109883
|
rpc = await connect({ command: options.codexCommand ?? "codex", home, codexHome });
|
|
@@ -109832,9 +109900,9 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
109832
109900
|
delete identity4.command;
|
|
109833
109901
|
return identity4;
|
|
109834
109902
|
};
|
|
109835
|
-
need4(
|
|
109903
|
+
need4(isDeepStrictEqual13(stable(actual), stable(expected)), "RECONCILE_NATIVE_IDENTITY_CHANGED");
|
|
109836
109904
|
if (!plannedKeys.has(expected.key))
|
|
109837
|
-
need4(
|
|
109905
|
+
need4(isDeepStrictEqual13(actual, expected), "RECONCILE_UNRELATED_HOOK_CHANGED");
|
|
109838
109906
|
}
|
|
109839
109907
|
for (const wanted of intent.hooks) {
|
|
109840
109908
|
const matches2 = nativeHooks.filter((h2) => h2.key === wanted.key || h2.key === wanted.key.replace(join58(home, ".codex/hooks.json"), hooksPath));
|
|
@@ -109853,7 +109921,7 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
109853
109921
|
const layers = config2?.layers?.filter((layer) => layer.name?.type === "user" && layer.name.file === nativeConfigPath && !layer.name.profile);
|
|
109854
109922
|
need4(layers?.length === 1 && !layers[0].disabledReason && codexNativeConfigEqual(layers[0].config, current), "RECONCILE_NATIVE_CONFIG_MISMATCH");
|
|
109855
109923
|
const finalDiscovery = await rpc.request("hooks/list", { cwds: [home] });
|
|
109856
|
-
need4(Array.isArray(finalDiscovery?.data) && finalDiscovery.data.length === 1 && finalDiscovery.data[0].cwd === home && !finalDiscovery.data[0].errors?.length && !finalDiscovery.data[0].warnings?.length && (!recordedNativeHooks ||
|
|
109924
|
+
need4(Array.isArray(finalDiscovery?.data) && finalDiscovery.data.length === 1 && finalDiscovery.data[0].cwd === home && !finalDiscovery.data[0].errors?.length && !finalDiscovery.data[0].warnings?.length && (!recordedNativeHooks || isDeepStrictEqual13(finalDiscovery.data[0].hooks.map(safeNativeHook), nativeHooks.map(safeNativeHook))), "RECONCILE_NATIVE_STATE_CHANGED");
|
|
109857
109925
|
const finalConfig = await rpc.request("config/read", { includeLayers: true, cwd: home });
|
|
109858
109926
|
const finalLayers = finalConfig?.layers?.filter((layer) => layer.name?.type === "user" && layer.name.file === nativeConfigPath && !layer.name.profile);
|
|
109859
109927
|
need4(finalLayers?.length === 1 && !finalLayers[0].disabledReason && codexNativeConfigEqual(finalLayers[0].config, current), "RECONCILE_NATIVE_CONFIG_CHANGED");
|
|
@@ -109914,7 +109982,7 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
109914
109982
|
const groups = declarations[event];
|
|
109915
109983
|
need4(Array.isArray(groups), "DECLARATION_CHANGED");
|
|
109916
109984
|
const matches2 = groups.flatMap((group, gi) => (group.hooks ?? []).flatMap((entry, hi) => entry.command === command ? [{ group, entry, gi, hi }] : []));
|
|
109917
|
-
need4(matches2.length === 1 &&
|
|
109985
|
+
need4(matches2.length === 1 && isDeepStrictEqual13(matches2[0].group, { hooks: [{ type: "command", command, timeout: 15 }] }), "DECLARATION_CHANGED");
|
|
109918
109986
|
expected.push({ event: nativeEvent, key: `${join58(home, ".codex/hooks.json")}:${label}:${matches2[0].gi}:${matches2[0].hi}`, command });
|
|
109919
109987
|
}
|
|
109920
109988
|
codexTrustTextWitness(configFile.text, expected.map((h) => h.key));
|
|
@@ -109980,7 +110048,7 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
109980
110048
|
save(join58(journal, "hooks.before.json"), hooksFile.bytes);
|
|
109981
110049
|
save(join58(journal, "policy.before.json"), policyFile.bytes);
|
|
109982
110050
|
save(join58(journal, "intent.json"), json2({ ...digestInput, planDigest, hooks: planned, nativeHooks: discovered.map(safeNativeHook) }));
|
|
109983
|
-
need4(
|
|
110051
|
+
need4(isDeepStrictEqual13(admit(await list2()), admitted), "NATIVE_DISCOVERY_CHANGED");
|
|
109984
110052
|
checkInputs();
|
|
109985
110053
|
let result2;
|
|
109986
110054
|
try {
|
|
@@ -109994,7 +110062,7 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
109994
110062
|
expectedConfig.hooks.state ??= {};
|
|
109995
110063
|
for (const h of planned)
|
|
109996
110064
|
expectedConfig.hooks.state[h.key] = { ...expectedConfig.hooks.state[h.key], enabled: true, trusted_hash: h.currentHash };
|
|
109997
|
-
need4(
|
|
110065
|
+
need4(isDeepStrictEqual13(structure3(after2.text), expectedConfig) && codexTrustTextWitness(after2.text, keys3) === preservationText, "PRESERVATION_FAILED");
|
|
109998
110066
|
const savedConfig = snapshot(join58(journal, "config.before.toml"), true);
|
|
109999
110067
|
need4(savedConfig.sha256 === configFile.sha256, "JOURNAL_CHANGED");
|
|
110000
110068
|
const discoveryTransition = binding.discovery?.codex ? createCodexHookDiscoveryRecovery(binding.discovery.codex, savedConfig, after2, planned) : undefined;
|
|
@@ -110002,8 +110070,8 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
110002
110070
|
unchanged2(hooksFile);
|
|
110003
110071
|
assertManagedAgentBridge("codex", { home, dataDir: dataDir3, projectDir: home, codexDiscoveryRecovery: discoveryTransition });
|
|
110004
110072
|
const current = await list2(), confirmed = admit(current);
|
|
110005
|
-
need4(confirmed.every((h) => h.enabled && h.trustStatus === "trusted") &&
|
|
110006
|
-
need4(
|
|
110073
|
+
need4(confirmed.every((h) => h.enabled && h.trustStatus === "trusted") && isDeepStrictEqual13(confirmed.map((h) => [h.key, h.currentHash]), admitted.map((h) => [h.key, h.currentHash])), "NATIVE_NOT_ELIGIBLE");
|
|
110074
|
+
need4(isDeepStrictEqual13(current.filter((h) => !keys3.includes(h.key)), discovered.filter((h) => !keys3.includes(h.key))), "UNRELATED_HOOK_CHANGED");
|
|
110007
110075
|
unchanged2(after2);
|
|
110008
110076
|
unchanged2(savedConfig);
|
|
110009
110077
|
unchanged2(hooksFile);
|
|
@@ -110070,6 +110138,9 @@ function hookFailureReason(error61, profileId) {
|
|
|
110070
110138
|
if (code2 === "PROFILE_LOCK_MISMATCH") {
|
|
110071
110139
|
return `${prefix} This session or project is pinned to a different profile. Inspect the session with skills sessions show <session-id> --json; review skills sessions reconcile for an intentional session change, or explicit project sync for a project change. Sync alone does not change that pin.`;
|
|
110072
110140
|
}
|
|
110141
|
+
if (code2 === "SESSION_RECONCILIATION_REQUIRED") {
|
|
110142
|
+
return `${prefix} A loaded skill changed or was removed. Inspect this session with skills sessions show <session-id> --json, then review skills sessions reconcile for an intentional change. Sync alone does not change this pin.`;
|
|
110143
|
+
}
|
|
110073
110144
|
if (code2.startsWith("SESSION_")) {
|
|
110074
110145
|
return `${prefix} Inspect the session with skills sessions show <session-id> --json and review its parent or concurrent writer before retrying. Do not remove or rewrite its receipt to bypass this refusal.`;
|
|
110075
110146
|
}
|
|
@@ -110112,6 +110183,7 @@ var init_hook_diagnostics = __esm(() => {
|
|
|
110112
110183
|
"SESSION_GENERATION_EXHAUSTED",
|
|
110113
110184
|
"SESSION_PARENT_CHANGED",
|
|
110114
110185
|
"SESSION_RECEIPT_CHANGED",
|
|
110186
|
+
"SESSION_RECONCILIATION_REQUIRED",
|
|
110115
110187
|
"SESSION_RECONCILIATION_INCOMPLETE",
|
|
110116
110188
|
"SESSION_WRITE_LOCKED",
|
|
110117
110189
|
"SESSION_WRITE_LOCK_CHANGED",
|
|
@@ -110156,7 +110228,7 @@ async function contextForHook(input3, profileId, cached2, deadline) {
|
|
|
110156
110228
|
throw new HookDiagnosticError("SKILLS_HOOK_TIMEOUT", "context");
|
|
110157
110229
|
const args2 = [process.execPath, process.argv[1], "context", "--stdin", "--json", "--selection-profile", profileId];
|
|
110158
110230
|
if (cached2)
|
|
110159
|
-
args2.push("--cached");
|
|
110231
|
+
args2.push("--cached", "--auto-reconcile-safe");
|
|
110160
110232
|
const child = Bun.spawn(args2, { stdin: new Blob([JSON.stringify(input3)]), stdout: "pipe", stderr: "pipe", env: { ...process.env, NO_COLOR: "1" } });
|
|
110161
110233
|
let timedOut = false;
|
|
110162
110234
|
const timer = setTimeout(() => {
|
|
@@ -110363,13 +110435,18 @@ function registerAgentIntegration(parent) {
|
|
|
110363
110435
|
}
|
|
110364
110436
|
});
|
|
110365
110437
|
const migrate = parent.command("migrate").description("Preserve and retire native agent skill copies");
|
|
110366
|
-
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) => {
|
|
110438
|
+
migrate.command("native").option("--agent <agent>", "Limit inventory and migration to one maintained agent").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("--reviewed-cache-alias <path>", "Exact skill-containing vendor cache alias reviewed for migration; runtime hooks still refuse it").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) => {
|
|
110367
110439
|
try {
|
|
110440
|
+
if (options.agent && !INTEGRATION_AGENTS.includes(options.agent))
|
|
110441
|
+
throw new Error(`Unsupported migration agent: ${options.agent}`);
|
|
110368
110442
|
const discoveryInputs = options.discoveryInputs ? JSON.parse(readFileSync40(options.discoveryInputs, "utf8")) : undefined;
|
|
110369
110443
|
if (options.targetManifest && (options.includeUnmanaged || options.includeVendor))
|
|
110370
110444
|
throw new Error("--target-manifest cannot be combined with broad native migration selectors");
|
|
110371
110445
|
const targetManifest = options.targetManifest ? readNativeMigrationTargetManifest(options.targetManifest) : undefined;
|
|
110372
|
-
|
|
110446
|
+
if (options.apply && options.reviewedCacheAlias && !targetManifest)
|
|
110447
|
+
throw new Error("Applying a reviewed cache alias requires an exact --target-manifest");
|
|
110448
|
+
const includeVendor = options.includeVendor || Boolean(targetManifest?.targets.some((target) => target.vendor));
|
|
110449
|
+
const inventory = inventoryNativeSkills(undefined, { projectDirs: [process.cwd(), ...options.project ? [options.project] : []], agents: options.agent ? [options.agent] : undefined, includeVendor, configured: includeVendor, discoveryInputs, allowRootAliases: options.allowRootAliases, reviewedCacheAlias: options.reviewedCacheAlias });
|
|
110373
110450
|
if (targetManifest)
|
|
110374
110451
|
selectNativeMigrationTargets(inventory, targetManifest);
|
|
110375
110452
|
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 } } : {} };
|
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.
|
|
20022
|
+
version: "0.10.3",
|
|
20023
20023
|
description: "Skills library for AI coding agents",
|
|
20024
20024
|
type: "module",
|
|
20025
20025
|
bin: {
|
package/bin/migrate.js
CHANGED
package/bin/server.js
CHANGED
|
@@ -24122,7 +24122,7 @@ var init_dist_es9 = __esm(() => {
|
|
|
24122
24122
|
// package.json
|
|
24123
24123
|
var package_default = {
|
|
24124
24124
|
name: "@hasna/skills",
|
|
24125
|
-
version: "0.10.
|
|
24125
|
+
version: "0.10.3",
|
|
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.
|
|
24128
|
+
version: "0.10.3",
|
|
24129
24129
|
description: "Skills library for AI coding agents",
|
|
24130
24130
|
type: "module",
|
|
24131
24131
|
bin: {
|
package/dist/index.js
CHANGED
|
@@ -9611,7 +9611,7 @@ import { isAbsolute as isAbsolute11, join as join23, resolve as resolve11 } from
|
|
|
9611
9611
|
// package.json
|
|
9612
9612
|
var package_default = {
|
|
9613
9613
|
name: "@hasna/skills",
|
|
9614
|
-
version: "0.10.
|
|
9614
|
+
version: "0.10.3",
|
|
9615
9615
|
description: "Skills library for AI coding agents",
|
|
9616
9616
|
type: "module",
|
|
9617
9617
|
bin: {
|
|
@@ -13309,8 +13309,8 @@ function selectNativeMigrationTargets(inventory, manifest) {
|
|
|
13309
13309
|
if (matches.length !== 1)
|
|
13310
13310
|
throw new Error(`Native migration target was not found exactly once: ${target.agent} ${target.projectRoot}/${target.path}`);
|
|
13311
13311
|
const entry = matches[0];
|
|
13312
|
-
if (entry.bridge || entry.system || entry.vendor)
|
|
13313
|
-
throw new Error(`Native migration target is protected or vendor
|
|
13312
|
+
if (entry.bridge || entry.system || entry.vendor !== (target.vendor === true))
|
|
13313
|
+
throw new Error(`Native migration target is protected or vendor classification changed: ${entry.path}`);
|
|
13314
13314
|
if (entry.hash !== target.treeSha256 || treeHash(entry.path) !== target.treeSha256)
|
|
13315
13315
|
throw new Error(`Native migration target changed after review: ${entry.path}`);
|
|
13316
13316
|
selected.push(entry);
|
|
@@ -13333,6 +13333,8 @@ function projectAncestorDirectories(projects) {
|
|
|
13333
13333
|
return [...directories];
|
|
13334
13334
|
}
|
|
13335
13335
|
function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
13336
|
+
if (options.reviewedCacheAlias !== undefined && (!options.includeVendor || !isAbsolute9(options.reviewedCacheAlias) || resolve9(options.reviewedCacheAlias) !== options.reviewedCacheAlias))
|
|
13337
|
+
throw new Error("A reviewed cache alias requires vendor inventory and an exact absolute path");
|
|
13336
13338
|
const aliases = rootAliases(home, options.allowRootAliases);
|
|
13337
13339
|
const selectedAgents = options.agents ? new Set(options.agents) : undefined;
|
|
13338
13340
|
const includesAgent = (agent) => !selectedAgents || selectedAgents.has(agent);
|
|
@@ -13353,14 +13355,22 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
13353
13355
|
if (discoveryPathBytes > 4 * 1024 * 1024)
|
|
13354
13356
|
throw new Error("Native skill discovery metadata limit exceeded");
|
|
13355
13357
|
}
|
|
13356
|
-
|
|
13358
|
+
let reviewedCacheAliasFound = false;
|
|
13359
|
+
function verifiedSiblingCacheAlias(path, parent) {
|
|
13357
13360
|
const link = readlinkSync3(path), target = resolve9(parent, link);
|
|
13358
13361
|
if (dirname10(target) !== parent || (isAbsolute9(link) ? link !== target : dirname10(link) !== "."))
|
|
13359
13362
|
return false;
|
|
13360
13363
|
if (!lstatSync14(target, { throwIfNoEntry: false })?.isDirectory())
|
|
13361
13364
|
return false;
|
|
13362
13365
|
const scan = cacheScans.get(target);
|
|
13363
|
-
|
|
13366
|
+
if (!scan?.complete || scan.entries > maxAliasProofEntries)
|
|
13367
|
+
return false;
|
|
13368
|
+
if (scan.hasSkills) {
|
|
13369
|
+
if (path !== options.reviewedCacheAlias)
|
|
13370
|
+
return false;
|
|
13371
|
+
reviewedCacheAliasFound = true;
|
|
13372
|
+
}
|
|
13373
|
+
return true;
|
|
13364
13374
|
}
|
|
13365
13375
|
function visit2(agent, path, vendor = false, depth = 0, pluginCache = false, admitted = false) {
|
|
13366
13376
|
if (!admitted)
|
|
@@ -13417,7 +13427,7 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
13417
13427
|
const child = join16(path, name);
|
|
13418
13428
|
if (isVendor && lstatSync14(child, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
|
13419
13429
|
const target = statSync7(child, { throwIfNoEntry: false });
|
|
13420
|
-
if (target?.isFile() || pluginCache && target?.isDirectory() &&
|
|
13430
|
+
if (target?.isFile() || pluginCache && target?.isDirectory() && verifiedSiblingCacheAlias(child, path)) {
|
|
13421
13431
|
scan.entries++;
|
|
13422
13432
|
continue;
|
|
13423
13433
|
}
|
|
@@ -13451,6 +13461,8 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
|
|
|
13451
13461
|
for (const root of [...options.agentRoots ?? [], ...configured])
|
|
13452
13462
|
if (includesAgent(root.agent))
|
|
13453
13463
|
scanRoot(root.agent, canonicalAgentPath(root.path, aliases), true);
|
|
13464
|
+
if (options.reviewedCacheAlias !== undefined && !reviewedCacheAliasFound)
|
|
13465
|
+
throw new Error("Reviewed cache alias was not an inventoried skill-containing sibling alias");
|
|
13454
13466
|
recheckRootAliases(aliases);
|
|
13455
13467
|
return entries;
|
|
13456
13468
|
}
|
|
@@ -29,6 +29,7 @@ export interface NativeMigrationTarget {
|
|
|
29
29
|
projectRoot: string;
|
|
30
30
|
path: string;
|
|
31
31
|
treeSha256: string;
|
|
32
|
+
vendor?: true;
|
|
32
33
|
}
|
|
33
34
|
export interface NativeMigrationTargetManifest {
|
|
34
35
|
schema: "hasna.skills-native-migration-targets.v1";
|
|
@@ -84,6 +85,7 @@ export declare function inventoryNativeSkills(home?: string, options?: {
|
|
|
84
85
|
configured?: boolean;
|
|
85
86
|
discoveryInputs?: ReviewedDiscoveryInputs;
|
|
86
87
|
allowRootAliases?: boolean;
|
|
88
|
+
reviewedCacheAlias?: string;
|
|
87
89
|
}): NativeSkillEntry[];
|
|
88
90
|
/** Planning is read-only; credentials and unrelated settings never appear in CLI output. */
|
|
89
91
|
export declare function planAgentIntegration(options: {
|
|
@@ -30,7 +30,13 @@ export declare function inspectSkillSession(sessionId: string, options?: Selecti
|
|
|
30
30
|
selectionCount: number;
|
|
31
31
|
loadedCount: number;
|
|
32
32
|
};
|
|
33
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Managed hooks may move an expired pin only when every skill already loaded
|
|
35
|
+
* by this session is still selected with exactly the same behavior and bytes.
|
|
36
|
+
* The old receipt is archived and replaced under its exact snapshot lock.
|
|
37
|
+
*/
|
|
38
|
+
export declare function reconcileSkillSessionIfSafe(sessionId: string, profileId: string, options?: SessionReconciliationOptions): Promise<boolean>;
|
|
39
|
+
/** Explicit review for changed loaded selections or intentional profile migration. */
|
|
34
40
|
export declare function reconcileSkillSession(input: SessionReconciliationInput, options?: SessionReconciliationOptions): Promise<{
|
|
35
41
|
applied: false;
|
|
36
42
|
plan: {
|
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.
|
|
32376
|
+
version: "0.10.3",
|
|
32377
32377
|
description: "Skills library for AI coding agents",
|
|
32378
32378
|
type: "module",
|
|
32379
32379
|
bin: {
|
|
@@ -41180,8 +41180,8 @@ function selectNativeMigrationTargets(inventory, manifest) {
|
|
|
41180
41180
|
if (matches.length !== 1)
|
|
41181
41181
|
throw new Error(`Native migration target was not found exactly once: ${target.agent} ${target.projectRoot}/${target.path}`);
|
|
41182
41182
|
const entry = matches[0];
|
|
41183
|
-
if (entry.bridge || entry.system || entry.vendor)
|
|
41184
|
-
throw new Error(`Native migration target is protected or vendor
|
|
41183
|
+
if (entry.bridge || entry.system || entry.vendor !== (target.vendor === true))
|
|
41184
|
+
throw new Error(`Native migration target is protected or vendor classification changed: ${entry.path}`);
|
|
41185
41185
|
if (entry.hash !== target.treeSha256 || treeHash(entry.path) !== target.treeSha256)
|
|
41186
41186
|
throw new Error(`Native migration target changed after review: ${entry.path}`);
|
|
41187
41187
|
selected.push(entry);
|
|
@@ -41204,6 +41204,8 @@ function projectAncestorDirectories(projects) {
|
|
|
41204
41204
|
return [...directories];
|
|
41205
41205
|
}
|
|
41206
41206
|
function inventoryNativeSkills(home = homedir3(), options = {}) {
|
|
41207
|
+
if (options.reviewedCacheAlias !== undefined && (!options.includeVendor || !isAbsolute15(options.reviewedCacheAlias) || resolve14(options.reviewedCacheAlias) !== options.reviewedCacheAlias))
|
|
41208
|
+
throw new Error("A reviewed cache alias requires vendor inventory and an exact absolute path");
|
|
41207
41209
|
const aliases = rootAliases(home, options.allowRootAliases);
|
|
41208
41210
|
const selectedAgents = options.agents ? new Set(options.agents) : undefined;
|
|
41209
41211
|
const includesAgent = (agent) => !selectedAgents || selectedAgents.has(agent);
|
|
@@ -41224,14 +41226,22 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
|
|
|
41224
41226
|
if (discoveryPathBytes > 4 * 1024 * 1024)
|
|
41225
41227
|
throw new Error("Native skill discovery metadata limit exceeded");
|
|
41226
41228
|
}
|
|
41227
|
-
|
|
41229
|
+
let reviewedCacheAliasFound = false;
|
|
41230
|
+
function verifiedSiblingCacheAlias(path, parent) {
|
|
41228
41231
|
const link = readlinkSync3(path), target = resolve14(parent, link);
|
|
41229
41232
|
if (dirname14(target) !== parent || (isAbsolute15(link) ? link !== target : dirname14(link) !== "."))
|
|
41230
41233
|
return false;
|
|
41231
41234
|
if (!lstatSync18(target, { throwIfNoEntry: false })?.isDirectory())
|
|
41232
41235
|
return false;
|
|
41233
41236
|
const scan = cacheScans.get(target);
|
|
41234
|
-
|
|
41237
|
+
if (!scan?.complete || scan.entries > maxAliasProofEntries)
|
|
41238
|
+
return false;
|
|
41239
|
+
if (scan.hasSkills) {
|
|
41240
|
+
if (path !== options.reviewedCacheAlias)
|
|
41241
|
+
return false;
|
|
41242
|
+
reviewedCacheAliasFound = true;
|
|
41243
|
+
}
|
|
41244
|
+
return true;
|
|
41235
41245
|
}
|
|
41236
41246
|
function visit2(agent, path, vendor = false, depth = 0, pluginCache = false, admitted = false) {
|
|
41237
41247
|
if (!admitted)
|
|
@@ -41288,7 +41298,7 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
|
|
|
41288
41298
|
const child = join21(path, name);
|
|
41289
41299
|
if (isVendor && lstatSync18(child, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
|
41290
41300
|
const target = statSync5(child, { throwIfNoEntry: false });
|
|
41291
|
-
if (target?.isFile() || pluginCache && target?.isDirectory() &&
|
|
41301
|
+
if (target?.isFile() || pluginCache && target?.isDirectory() && verifiedSiblingCacheAlias(child, path)) {
|
|
41292
41302
|
scan.entries++;
|
|
41293
41303
|
continue;
|
|
41294
41304
|
}
|
|
@@ -41322,6 +41332,8 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
|
|
|
41322
41332
|
for (const root2 of [...options.agentRoots ?? [], ...configured])
|
|
41323
41333
|
if (includesAgent(root2.agent))
|
|
41324
41334
|
scanRoot(root2.agent, canonicalAgentPath(root2.path, aliases), true);
|
|
41335
|
+
if (options.reviewedCacheAlias !== undefined && !reviewedCacheAliasFound)
|
|
41336
|
+
throw new Error("Reviewed cache alias was not an inventoried skill-containing sibling alias");
|
|
41325
41337
|
recheckRootAliases(aliases);
|
|
41326
41338
|
return entries;
|
|
41327
41339
|
}
|