@hasna/skills 0.10.73 → 0.10.75
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 +77 -0
- package/bin/index.js +684 -142
- package/bin/mcp.js +215 -104
- package/bin/migrate.js +1 -1
- package/bin/server.js +1 -1
- package/bin/worker.js +1 -1
- package/dist/cli/commands/provenance.d.ts +18 -0
- package/dist/cli/stray-arguments.d.ts +29 -0
- package/dist/index.js +206 -111
- package/dist/lib/portable-skills-files.d.ts +10 -0
- package/dist/lib/search.d.ts +18 -0
- package/dist/lib/selected-run.d.ts +45 -0
- package/dist/lib/selection-provenance.d.ts +108 -0
- package/dist/runtime-prerequisites.js +1 -1
- package/dist/sdk/index.js +172 -33
- package/docs/plugin-admission.md +13 -9
- package/docs/skill-standard.md +18 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8566,8 +8566,8 @@ function ensureInstructionSkillFiles(skillPath, manifest) {
|
|
|
8566
8566
|
displayName: manifest.displayName ?? displayName(manifest.name),
|
|
8567
8567
|
category: manifest.category ?? "Development Tools",
|
|
8568
8568
|
tags: manifest.tags?.length ? manifest.tags : ["custom", manifest.name],
|
|
8569
|
-
inputs: [],
|
|
8570
|
-
commands: []
|
|
8569
|
+
inputs: Array.isArray(manifest.inputs) ? manifest.inputs : [],
|
|
8570
|
+
commands: Array.isArray(manifest.commands) ? manifest.commands : []
|
|
8571
8571
|
};
|
|
8572
8572
|
if (!existsSync6(join6(skillPath, "SKILL.md"))) {
|
|
8573
8573
|
writeFileSync2(join6(skillPath, "SKILL.md"), renderSkillMd(next));
|
|
@@ -15144,7 +15144,7 @@ import { basename as basename13, dirname as dirname24, isAbsolute as isAbsolute2
|
|
|
15144
15144
|
import { homedir as homedir5 } from "os";
|
|
15145
15145
|
import { isAbsolute as isAbsolute25, join as join36, resolve as resolve24 } from "path";
|
|
15146
15146
|
// package.json
|
|
15147
|
-
var version = "0.10.
|
|
15147
|
+
var version = "0.10.75";
|
|
15148
15148
|
var package_default = {
|
|
15149
15149
|
name: "@hasna/skills",
|
|
15150
15150
|
version,
|
|
@@ -20212,11 +20212,19 @@ function planAgentIntegration(options) {
|
|
|
20212
20212
|
const executable2 = discoveryExecutable(agent) ?? (prior ? recordedDiscoveryExecutable(agent, prior) : null);
|
|
20213
20213
|
executables.set(agent, executable2);
|
|
20214
20214
|
const retainedReview = options.discoveryInputs === undefined && policy.bridge?.discovery?.[agent]?.method === "reviewed" ? { ...policy.bridge.discovery[agent], ...agent === "codex" ? { codexDisabledPluginSkills: policy.bridge.codexPluginSkills ?? [] } : {} } : undefined;
|
|
20215
|
-
|
|
20216
|
-
|
|
20217
|
-
|
|
20215
|
+
try {
|
|
20216
|
+
if (retainedReview) {
|
|
20217
|
+
assertRetainedReviewState(agent, { home, dataDir: dataDir2, projectDir: options.projectDir ?? home });
|
|
20218
|
+
retainedAgents.push(agent);
|
|
20219
|
+
}
|
|
20220
|
+
return resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, retainedReview, canonical: (path) => canonicalAgentPath(path, aliases), executable: executable2 });
|
|
20221
|
+
} catch (error) {
|
|
20222
|
+
if (isDiscoveryRootUnresolved(error) || error instanceof SumiPathResolverError || error instanceof NativeSkillCopiesDriftError)
|
|
20223
|
+
throw error;
|
|
20224
|
+
if (!retainedReview)
|
|
20225
|
+
throw error;
|
|
20226
|
+
throw new Error(`NATIVE_SKILL_DRIFT: ${error.message.replace(/^NATIVE_SKILL_DRIFT: /, "")}. This agent reuses a saved discovery review and that review no longer verifies, so a fresh explicit review is required: skills hook install --agent ${agent} --discovery-inputs <review> --apply.`);
|
|
20218
20227
|
}
|
|
20219
|
-
return resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, retainedReview, canonical: (path) => canonicalAgentPath(path, aliases), executable: executable2 });
|
|
20220
20228
|
});
|
|
20221
20229
|
const codexConfigBefore = options.agents.includes("codex") ? readOptional(canonicalAgentPath(join31(home, ".codex", "config.toml"), aliases)) : null;
|
|
20222
20230
|
const codexConfig = Bun.TOML.parse(codexConfigBefore ?? "");
|
|
@@ -20970,7 +20978,7 @@ function applyAgentIntegrationUnlocked(plan, options = {}, assertCurrent) {
|
|
|
20970
20978
|
assertCurrent();
|
|
20971
20979
|
recheckRootAliases(aliases);
|
|
20972
20980
|
for (const agent of plan.retainedReviewChecks?.agents ?? [])
|
|
20973
|
-
|
|
20981
|
+
assertRetainedReviewState(agent, { ...plan.retainedReviewChecks, dataDir: plan.dataDir });
|
|
20974
20982
|
if (plan.reviewArtifactMigration) {
|
|
20975
20983
|
const verified = planReviewedArtifactMigration(plan.reviewArtifactMigration);
|
|
20976
20984
|
if (JSON.stringify(verified.changes) !== JSON.stringify(plan.changes) || JSON.stringify(verified.discoveryAfter) !== JSON.stringify(plan.discoveryAfter))
|
|
@@ -21013,6 +21021,8 @@ function applyAgentIntegrationUnlocked(plan, options = {}, assertCurrent) {
|
|
|
21013
21021
|
if (!plan.changes.length) {
|
|
21014
21022
|
assertCurrent();
|
|
21015
21023
|
recheckRootAliases(aliases);
|
|
21024
|
+
for (const agent of plan.retainedReviewChecks?.agents ?? [])
|
|
21025
|
+
assertManagedAgentBridge(agent, { ...plan.retainedReviewChecks, dataDir: plan.dataDir });
|
|
21016
21026
|
return { changed: [], backups, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
21017
21027
|
}
|
|
21018
21028
|
assertSafePath(backupRoot);
|
|
@@ -21062,6 +21072,8 @@ function applyAgentIntegrationUnlocked(plan, options = {}, assertCurrent) {
|
|
|
21062
21072
|
}
|
|
21063
21073
|
for (const agent of plan.managedAgentChecks?.agents ?? [])
|
|
21064
21074
|
assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.projectDir ?? plan.managedAgentChecks.home });
|
|
21075
|
+
for (const agent of plan.retainedReviewChecks?.agents ?? [])
|
|
21076
|
+
assertManagedAgentBridge(agent, { ...plan.retainedReviewChecks, dataDir: plan.dataDir });
|
|
21065
21077
|
atomicWrite2(join31(backupRoot, "receipt.json"), JSON.stringify({ version: 1, changes: written.map((change) => ({ path: change.path, beforeHash: change.before === null ? null : sha4(change.before), afterHash: sha4(change.after) })), backups, ...plan.cacheWitnessUpgrade ? { cacheWitnessUpgrade: plan.cacheWitnessUpgrade } : {}, ...plan.retainedReviewArtifacts?.length ? { retainedReviewArtifacts: plan.retainedReviewArtifacts.map((item) => ({ path: item.path, ...item.artifact })) } : {}, ...aliases.length ? { rootAliases: aliases } : {} }) + `
|
|
21066
21078
|
`);
|
|
21067
21079
|
} catch (error) {
|
|
@@ -21393,7 +21405,7 @@ enabled = false
|
|
|
21393
21405
|
function assertManagedAgentBridge(agent, options = {}) {
|
|
21394
21406
|
return assertManagedAgentBridgeWithDiscovery(agent, options);
|
|
21395
21407
|
}
|
|
21396
|
-
function
|
|
21408
|
+
function assertManagedBridgeCustody(agent, options = {}, provenDiscovery) {
|
|
21397
21409
|
const home = resolve23(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
21398
21410
|
assertSafePath(join31(dataDir2, "agent-policy.json"));
|
|
21399
21411
|
const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
|
|
@@ -21410,7 +21422,6 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
21410
21422
|
if (!isOwnedCliBridge(expected, [expected]))
|
|
21411
21423
|
throw new Error("NATIVE_SKILL_DRIFT: the native Skills bridge is missing or modified; repair it before continuing");
|
|
21412
21424
|
const roots = projectAncestorDirectories([options.projectDir ?? process.cwd(), ...options.projectDirs ?? [], ...agent === "hermes" && process.env.TERMINAL_CWD ? [process.cwd()] : []]);
|
|
21413
|
-
const visible = (entry) => entry.agent === agent || ["codex", "gemini", "opencode", "hermes", "sumi"].includes(agent) && entry.path.includes(`${sep14}.agents${sep14}skills${sep14}`) || ["opencode", "sumi"].includes(agent) && entry.agent === "claude";
|
|
21414
21425
|
const discovery = provenDiscovery ?? binding.discovery?.[agent];
|
|
21415
21426
|
assertProjectDiscovery(agent, [...roots], home, (path) => canonicalAgentPath(path, aliases), discovery);
|
|
21416
21427
|
for (const source of discovery?.sources ?? [])
|
|
@@ -21425,11 +21436,30 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
21425
21436
|
throw new Error("NATIVE_SKILL_DRIFT: the native hook command/profile binding is missing");
|
|
21426
21437
|
if (options.profileId !== undefined && options.profileId !== profile)
|
|
21427
21438
|
throw new Error("NATIVE_SKILL_DRIFT: the hook selection profile differs from its managed binding; restart the native client after reviewing skills hook install");
|
|
21439
|
+
return { home, dataDir: dataDir2, binding, aliases, expected, roots, discovery, configPath, config, command, profile };
|
|
21440
|
+
}
|
|
21441
|
+
function assertUnrepairableBridgeProtection(custody, agent) {
|
|
21442
|
+
if (agent !== "hermes")
|
|
21443
|
+
return;
|
|
21444
|
+
const { home, dataDir: dataDir2, binding, config, command, profile } = custody;
|
|
21445
|
+
const supervisor = binding.supervisors?.hermes;
|
|
21446
|
+
if (supervisor?.path !== join31(dataDir2, "agent-hooks", "hermes.js") || supervisor?.sha256 !== sha4(renderHermesSupervisor(command, profile)))
|
|
21447
|
+
throw new Error("NATIVE_SKILL_DRIFT: Hermes supervisor binding changed; run skills hook install");
|
|
21448
|
+
assertHermesProtection(home, config, command, profile, supervisor);
|
|
21449
|
+
}
|
|
21450
|
+
function assertRetainedReviewState(agent, options) {
|
|
21451
|
+
const custody = assertManagedBridgeCustody(agent, options);
|
|
21452
|
+
assertUnrepairableBridgeProtection(custody, agent);
|
|
21453
|
+
assertManagedNativeDiscovery(custody, agent, options);
|
|
21454
|
+
}
|
|
21455
|
+
|
|
21456
|
+
class NativeSkillCopiesDriftError extends Error {
|
|
21457
|
+
}
|
|
21458
|
+
function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscovery) {
|
|
21459
|
+
const custody = assertManagedBridgeCustody(agent, options, provenDiscovery);
|
|
21460
|
+
const { home, aliases, expected, discovery, config, command, profile } = custody;
|
|
21428
21461
|
if (agent === "hermes") {
|
|
21429
|
-
|
|
21430
|
-
if (supervisor?.path !== join31(dataDir2, "agent-hooks", "hermes.js") || supervisor?.sha256 !== sha4(renderHermesSupervisor(command, profile)))
|
|
21431
|
-
throw new Error("NATIVE_SKILL_DRIFT: Hermes supervisor binding changed; run skills hook install");
|
|
21432
|
-
assertHermesProtection(home, config, command, profile, supervisor);
|
|
21462
|
+
assertUnrepairableBridgeProtection(custody, agent);
|
|
21433
21463
|
} else if (agent === "sumi") {
|
|
21434
21464
|
if (JSON.stringify(config.permissions?.filter((rule) => rule.action === "skill")) !== JSON.stringify(SUMI_SKILL_PERMISSIONS) || !isManagedSumiPlugin(readOptional(join31(sumiConfigDirectory(home, process.env, options.projectDir ?? process.cwd()), "plugins", "skills-cli.js")), command, profile))
|
|
21435
21465
|
throw new Error("NATIVE_SKILL_DRIFT: Sumi bridge protection changed; run skills hook install");
|
|
@@ -21451,6 +21481,21 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
21451
21481
|
if (agent === "gemini" && (config.hooksConfig?.enabled === false || config.skills?.enabled !== true || !["antigravity-support", "skill-creator"].every((name) => config.skills?.disabled?.includes(name)) || config.skills?.disabled?.includes(CLI_BRIDGE_NAME)))
|
|
21452
21482
|
throw new Error("NATIVE_SKILL_DRIFT: Gemini bridge or bundled-skill protection changed; run skills hook install");
|
|
21453
21483
|
}
|
|
21484
|
+
if (agent === "codex") {
|
|
21485
|
+
const codexConfig = Bun.TOML.parse(readOptional(canonicalAgentPath(join31(home, ".codex", "config.toml"), aliases)) ?? "");
|
|
21486
|
+
if (codexConfig.skills?.bundled?.enabled !== false)
|
|
21487
|
+
throw new Error("NATIVE_SKILL_DRIFT: Codex bundled skill reseeding is not disabled (skills.bundled.enabled); run skills hook install");
|
|
21488
|
+
const setting = (path) => (codexConfig.skills?.config ?? []).filter((entry) => typeof entry.path === "string" && [path, join31(path, "SKILL.md")].includes(canonicalAgentPath(entry.path, aliases)));
|
|
21489
|
+
if ([...setting(expected), ...(codexConfig.skills?.config ?? []).filter((entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME)].some((entry) => entry.enabled === false))
|
|
21490
|
+
throw new Error("NATIVE_SKILL_DRIFT: the Codex CLI bridge is disabled; run skills hook install");
|
|
21491
|
+
}
|
|
21492
|
+
if (agent === "gemini" && discovery && !discovery.builtinNames?.every((name) => config.skills.disabled.includes(name)))
|
|
21493
|
+
throw new Error("NATIVE_SKILL_DRIFT: an installed Gemini builtin is not disabled");
|
|
21494
|
+
return assertManagedNativeDiscovery(custody, agent, options);
|
|
21495
|
+
}
|
|
21496
|
+
function assertManagedNativeDiscovery(custody, agent, options) {
|
|
21497
|
+
const { home, dataDir: dataDir2, binding, aliases, expected, roots, discovery } = custody;
|
|
21498
|
+
const visible = (entry) => entry.agent === agent || ["codex", "gemini", "opencode", "hermes", "sumi"].includes(agent) && entry.path.includes(`${sep14}.agents${sep14}skills${sep14}`) || ["opencode", "sumi"].includes(agent) && entry.agent === "claude";
|
|
21454
21499
|
const codexPath = canonicalAgentPath(join31(home, ".codex", "config.toml"), aliases);
|
|
21455
21500
|
const vendorCache = canonicalAgentPath(join31(home, ".codex", "plugins", "cache"), aliases);
|
|
21456
21501
|
const witnessTolerance = agent === "codex" && options.codexPromptHook === true ? {
|
|
@@ -21461,11 +21506,6 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
21461
21506
|
if (options.codexDiscoveryRecovery && (agent !== "codex" || options.codexDiscoveryRecovery.configPath !== codexPath))
|
|
21462
21507
|
throw new Error("NATIVE_SKILL_DRIFT: Codex trust recovery names a different configuration root");
|
|
21463
21508
|
const codexConfig = agent === "codex" ? Bun.TOML.parse(readOptional(codexPath) ?? "") : {};
|
|
21464
|
-
if (agent === "codex" && codexConfig.skills?.bundled?.enabled !== false)
|
|
21465
|
-
throw new Error("NATIVE_SKILL_DRIFT: Codex bundled skill reseeding is not disabled (skills.bundled.enabled); run skills hook install");
|
|
21466
|
-
const setting = (path) => (codexConfig.skills?.config ?? []).filter((entry) => typeof entry.path === "string" && [path, join31(path, "SKILL.md")].includes(canonicalAgentPath(entry.path, aliases)));
|
|
21467
|
-
if (agent === "codex" && [...setting(expected), ...(codexConfig.skills?.config ?? []).filter((entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME)].some((entry) => entry.enabled === false))
|
|
21468
|
-
throw new Error("NATIVE_SKILL_DRIFT: the Codex CLI bridge is disabled; run skills hook install");
|
|
21469
21509
|
if (agent === "codex" && !reviewedCodexPluginCapabilitiesUnchanged(canonicalAgentPath(join31(home, ".codex/plugins/cache"), aliases), binding.codexPluginSkills ?? [], (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)), codexConfig.skills?.config ?? []))
|
|
21470
21510
|
throw new Error("NATIVE_SKILL_DRIFT: reviewed native plugin capability controls changed; run skills hook install with a fresh discovery review");
|
|
21471
21511
|
const disabledPlugin = (entry) => {
|
|
@@ -21502,8 +21542,6 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
21502
21542
|
};
|
|
21503
21543
|
if (!discovery || discovery.agent !== agent)
|
|
21504
21544
|
throw new Error("NATIVE_SKILL_DRIFT: native discovery coverage is missing; run skills hook install");
|
|
21505
|
-
if (agent === "gemini" && !discovery.builtinNames?.every((name) => config.skills.disabled.includes(name)))
|
|
21506
|
-
throw new Error("NATIVE_SKILL_DRIFT: an installed Gemini builtin is not disabled");
|
|
21507
21545
|
try {
|
|
21508
21546
|
verifyAgentDiscovery(agent === "codex" ? { ...discovery, codexDisabledPluginSkills: binding.codexPluginSkills ?? [] } : discovery, options.codexDiscoveryRecovery, aliases, witnessTolerance);
|
|
21509
21547
|
if (agent === "sumi" && discovery.method === "reviewed")
|
|
@@ -21558,7 +21596,7 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
21558
21596
|
return false;
|
|
21559
21597
|
}
|
|
21560
21598
|
}, read: (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)) });
|
|
21561
|
-
throw new
|
|
21599
|
+
throw new NativeSkillCopiesDriftError(`NATIVE_SKILL_DRIFT: ${blocking.length} unexpected native skill copies were found: ${nativeSkillPathList(blocking.map((entry) => entry.path))}. ${remediation}${adapterRefusal ? ` Native policy adapter refused: ${adapterRefusal}` : ""}`);
|
|
21562
21600
|
}
|
|
21563
21601
|
recheckRootAliases(aliases);
|
|
21564
21602
|
const discoveryChanges = (witnessTolerance?.changes ?? []).filter((change, index, all) => all.findIndex((other) => other.kind === change.kind && other.path === change.path) === index);
|
|
@@ -22689,6 +22727,115 @@ function readIfExists(path) {
|
|
|
22689
22727
|
return null;
|
|
22690
22728
|
}
|
|
22691
22729
|
|
|
22730
|
+
// src/lib/search.ts
|
|
22731
|
+
function editDistance(a, b) {
|
|
22732
|
+
if (a === b)
|
|
22733
|
+
return 0;
|
|
22734
|
+
if (a.length === 0)
|
|
22735
|
+
return b.length;
|
|
22736
|
+
if (b.length === 0)
|
|
22737
|
+
return a.length;
|
|
22738
|
+
const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
22739
|
+
const curr = new Array(b.length + 1);
|
|
22740
|
+
for (let i = 1;i <= a.length; i++) {
|
|
22741
|
+
curr[0] = i;
|
|
22742
|
+
for (let j = 1;j <= b.length; j++) {
|
|
22743
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
22744
|
+
curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
22745
|
+
}
|
|
22746
|
+
prev.splice(0, prev.length, ...curr);
|
|
22747
|
+
}
|
|
22748
|
+
return prev[b.length];
|
|
22749
|
+
}
|
|
22750
|
+
function fuzzyMatchScore(word, target) {
|
|
22751
|
+
if (target.includes(word))
|
|
22752
|
+
return 1;
|
|
22753
|
+
const tokens = target.split(/[\s\-_]+/).filter(Boolean);
|
|
22754
|
+
for (const token of tokens) {
|
|
22755
|
+
if (token.startsWith(word))
|
|
22756
|
+
return 0.6;
|
|
22757
|
+
}
|
|
22758
|
+
if (word.length >= 3) {
|
|
22759
|
+
const maxDist = word.length <= 3 ? 1 : 2;
|
|
22760
|
+
for (const token of tokens) {
|
|
22761
|
+
if (Math.abs(token.length - word.length) <= maxDist) {
|
|
22762
|
+
const dist = editDistance(word, token);
|
|
22763
|
+
if (dist <= maxDist)
|
|
22764
|
+
return 0.4;
|
|
22765
|
+
}
|
|
22766
|
+
}
|
|
22767
|
+
}
|
|
22768
|
+
return 0;
|
|
22769
|
+
}
|
|
22770
|
+
function searchSkills(query, registry = loadRegistry()) {
|
|
22771
|
+
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
22772
|
+
if (words.length === 0)
|
|
22773
|
+
return [];
|
|
22774
|
+
const scored = [];
|
|
22775
|
+
for (const skill of registry) {
|
|
22776
|
+
const nameLower = skill.name.toLowerCase();
|
|
22777
|
+
const displayNameLower = skill.displayName.toLowerCase();
|
|
22778
|
+
const descriptionLower = skill.description.toLowerCase();
|
|
22779
|
+
const tagsLower = skill.tags.map((t2) => t2.toLowerCase());
|
|
22780
|
+
const tagsCombined = tagsLower.join(" ");
|
|
22781
|
+
let score = 0;
|
|
22782
|
+
let allWordsMatch = true;
|
|
22783
|
+
for (const word of words) {
|
|
22784
|
+
let wordScore = 0;
|
|
22785
|
+
const nameMatch = fuzzyMatchScore(word, nameLower);
|
|
22786
|
+
if (nameMatch > 0)
|
|
22787
|
+
wordScore += 10 * nameMatch;
|
|
22788
|
+
const displayMatch = fuzzyMatchScore(word, displayNameLower);
|
|
22789
|
+
if (displayMatch > 0)
|
|
22790
|
+
wordScore += 7 * displayMatch;
|
|
22791
|
+
const tagMatch = Math.max(...tagsLower.map((t2) => fuzzyMatchScore(word, t2)), fuzzyMatchScore(word, tagsCombined));
|
|
22792
|
+
if (tagMatch > 0)
|
|
22793
|
+
wordScore += 5 * tagMatch;
|
|
22794
|
+
const descMatch = fuzzyMatchScore(word, descriptionLower);
|
|
22795
|
+
if (descMatch > 0)
|
|
22796
|
+
wordScore += 2 * descMatch;
|
|
22797
|
+
if (wordScore === 0) {
|
|
22798
|
+
allWordsMatch = false;
|
|
22799
|
+
break;
|
|
22800
|
+
}
|
|
22801
|
+
score += wordScore;
|
|
22802
|
+
}
|
|
22803
|
+
if (allWordsMatch && score > 0) {
|
|
22804
|
+
scored.push({ skill, score });
|
|
22805
|
+
}
|
|
22806
|
+
}
|
|
22807
|
+
scored.sort((a, b) => b.score - a.score);
|
|
22808
|
+
return scored.map((s) => s.skill);
|
|
22809
|
+
}
|
|
22810
|
+
function findCloseSkillNames(query, names, maxResults = 3) {
|
|
22811
|
+
if (maxResults <= 0)
|
|
22812
|
+
return [];
|
|
22813
|
+
const q = query.trim().toLowerCase();
|
|
22814
|
+
if (q.length === 0)
|
|
22815
|
+
return [];
|
|
22816
|
+
const tokens = q.split("-").filter(Boolean);
|
|
22817
|
+
const ranked = [];
|
|
22818
|
+
if (q.length >= 3) {
|
|
22819
|
+
for (const name of names) {
|
|
22820
|
+
const candidate = name.toLowerCase();
|
|
22821
|
+
const rank = candidate === q ? 0 : candidate.endsWith(`-${q}`) || candidate.startsWith(`${q}-`) ? 1 : tokens.length > 1 && tokens.every((token) => candidate.split("-").includes(token)) ? 2 : undefined;
|
|
22822
|
+
if (rank !== undefined)
|
|
22823
|
+
ranked.push([name, rank]);
|
|
22824
|
+
}
|
|
22825
|
+
}
|
|
22826
|
+
ranked.sort((a, b) => a[1] - b[1] || a[0].length - b[0].length || a[0].localeCompare(b[0]));
|
|
22827
|
+
const found = ranked.slice(0, maxResults).map(([name]) => name);
|
|
22828
|
+
if (found.length === maxResults)
|
|
22829
|
+
return found;
|
|
22830
|
+
const taken = new Set(found);
|
|
22831
|
+
const threshold = Math.max(3, Math.floor(q.length / 2));
|
|
22832
|
+
const byDistance = names.filter((name) => !taken.has(name)).map((name) => ({ name, distance: editDistance(q, name.toLowerCase()) })).filter((entry) => entry.distance <= threshold).sort((a, b) => a.distance - b.distance || a.name.localeCompare(b.name));
|
|
22833
|
+
return [...found, ...byDistance.slice(0, maxResults - found.length).map((entry) => entry.name)];
|
|
22834
|
+
}
|
|
22835
|
+
function findSimilarSkills(query, maxResults = 3, registry = loadRegistry()) {
|
|
22836
|
+
return findCloseSkillNames(query, registry.map((skill) => skill.name), maxResults);
|
|
22837
|
+
}
|
|
22838
|
+
|
|
22692
22839
|
// src/lib/selected-document.ts
|
|
22693
22840
|
var DOCUMENT_PATHS = ["SKILL.md", "README.md", "CLAUDE.md"];
|
|
22694
22841
|
function readSelectedDocument(entries, file) {
|
|
@@ -22805,11 +22952,31 @@ function exactProfileSelection(spec, profile) {
|
|
|
22805
22952
|
const at = spec.lastIndexOf("@");
|
|
22806
22953
|
const slug = at > 0 ? spec.slice(0, at) : spec;
|
|
22807
22954
|
const version2 = at > 0 ? spec.slice(at + 1) : undefined;
|
|
22808
|
-
const
|
|
22809
|
-
if (
|
|
22810
|
-
throw new SkillSelectionError("SKILL_NOT_SELECTED",
|
|
22955
|
+
const named = profile.selections.filter((entry) => entry.slug === slug || entry.aliases?.includes(slug));
|
|
22956
|
+
if (version2 === "")
|
|
22957
|
+
throw new SkillSelectionError("SKILL_NOT_SELECTED", emptyVersionRefusal(spec));
|
|
22958
|
+
if (named.length === 0)
|
|
22959
|
+
throw new SkillSelectionError("SKILL_NOT_SELECTED", unknownNameRefusal(slug, profile));
|
|
22960
|
+
const selection = version2 === undefined ? named[0] : named.find((entry) => entry.version === version2);
|
|
22961
|
+
if (!selection)
|
|
22962
|
+
throw new SkillSelectionError("SKILL_NOT_SELECTED", wrongVersionRefusal(slug, version2, named));
|
|
22811
22963
|
return selection;
|
|
22812
22964
|
}
|
|
22965
|
+
var ACCEPTED_FORM = "Accepted form: '<slug>' or '<slug>@<version>'.";
|
|
22966
|
+
function emptyVersionRefusal(spec) {
|
|
22967
|
+
return `The requested name '${spec}' has an empty version. ${ACCEPTED_FORM}`;
|
|
22968
|
+
}
|
|
22969
|
+
function wrongVersionRefusal(slug, version2, named) {
|
|
22970
|
+
const versions = [...new Set(named.map((entry) => entry.version))].sort();
|
|
22971
|
+
const canonical9 = named[0].slug;
|
|
22972
|
+
return `Skill '${slug}' is selected at version '${versions.join("', '")}', but '${version2}' was requested. ` + `Accepted form: '${canonical9}@${versions[0]}'.`;
|
|
22973
|
+
}
|
|
22974
|
+
function unknownNameRefusal(slug, profile) {
|
|
22975
|
+
const names = profile.selections.flatMap((entry) => [entry.slug, ...entry.aliases ?? []]);
|
|
22976
|
+
const close = findCloseSkillNames(slug, names);
|
|
22977
|
+
const hint = close.length > 0 ? ` Did you mean: ${close.join(", ")}?` : "";
|
|
22978
|
+
return `Skill '${slug}' is not selected by this Skills profile or project lock.${hint} ` + `Read the accepted names with 'skills search <term>' or 'skills list --all --json'; accept one with 'skills pin <name>'. ${ACCEPTED_FORM}`;
|
|
22979
|
+
}
|
|
22813
22980
|
|
|
22814
22981
|
// src/lib/selected-manifest.ts
|
|
22815
22982
|
function jsonEntry(entries, path) {
|
|
@@ -24133,6 +24300,12 @@ function verifyAgentDiscovery(binding, codexRecovery, reviewedRootAliases, witne
|
|
|
24133
24300
|
}
|
|
24134
24301
|
var CLAUDE_CONTROL_WITNESS_MODES = new Set(["claude-settings-v1", "claude-settings-v2", "claude-settings-v3", "claude-settings-v4", "claude-marketplace-registry", "claude-marketplace-registry-v2", "claude-marketplace-entry-v1", "claude-plugin-registry"]);
|
|
24135
24302
|
var CLAUDE_CONTROL_FILES = new Set(["settings.json", "settings.local.json", "installed_plugins.json", "known_marketplaces.json"]);
|
|
24303
|
+
function claudeControlSource(source) {
|
|
24304
|
+
return CLAUDE_CONTROL_WITNESS_MODES.has(source.hashMode ?? "") || CLAUDE_CONTROL_FILES.has(basename15(source.path)) && !source.path.includes(`${sep15}.claude${sep15}plugins${sep15}cache${sep15}`);
|
|
24305
|
+
}
|
|
24306
|
+
function retainedAbsentClaudeRoot(absentRoots, source) {
|
|
24307
|
+
return claudeControlSource(source) ? undefined : retainedAbsentRoot(absentRoots, source);
|
|
24308
|
+
}
|
|
24136
24309
|
function codexInstallationRoots(binding) {
|
|
24137
24310
|
const proof = binding.codexInstallationInputs;
|
|
24138
24311
|
if (proof === undefined)
|
|
@@ -24542,7 +24715,10 @@ function resolveAgentDiscovery(options) {
|
|
|
24542
24715
|
if (!review.sources.some((source) => source.path === canonical9(configPath)))
|
|
24543
24716
|
throw new Error("Discovery review must include the agent configuration source");
|
|
24544
24717
|
const reviewedSources = supersedesSettingsProjection(agent, review.sources, canonical9(configPath)) ? sources.filter((source) => !isSupersededSettingsProjection(agent, canonical9(configPath), source)) : sources;
|
|
24718
|
+
const absentRoots = absentRetiredClaudeRoots(supplied);
|
|
24545
24719
|
const reviewedSourcesWithManifestProjection = review.sources.map((source) => {
|
|
24720
|
+
if (retainedAbsentClaudeRoot(absentRoots, source) !== undefined)
|
|
24721
|
+
return source;
|
|
24546
24722
|
if (agent === "claude" && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && source.reviewArtifact === undefined && [undefined, "bytes", "path-bytes"].includes(source.hashMode) && isClaudePluginCacheOrphanMarker(source.path)) {
|
|
24547
24723
|
return { path: source.path, hashMode: CLAUDE_ORPHAN_MARKER_MODE, sha256: hashClaudePluginCacheOrphanMarker(source.path) };
|
|
24548
24724
|
}
|
|
@@ -24575,6 +24751,11 @@ function resolveAgentDiscovery(options) {
|
|
|
24575
24751
|
});
|
|
24576
24752
|
if (agent === "claude")
|
|
24577
24753
|
assertReviewedClaudeCacheClosures(home, config, review, canonical9);
|
|
24754
|
+
for (const root2 of absentRoots) {
|
|
24755
|
+
safe6(root2);
|
|
24756
|
+
if (lstatSync32(root2, { throwIfNoEntry: false }))
|
|
24757
|
+
throw new Error("Retired native materialization changed during discovery review");
|
|
24758
|
+
}
|
|
24578
24759
|
const combined = [...reviewedSources, ...reviewedSourcesWithManifestProjection];
|
|
24579
24760
|
const unique = combined.filter((source, index) => combined.findIndex((candidate2) => isDeepStrictEqual9(candidate2, source)) === index);
|
|
24580
24761
|
return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: unique, ...agent === "gemini" ? { builtinNames } : {} };
|
|
@@ -25302,92 +25483,6 @@ var CATEGORIES = [
|
|
|
25302
25483
|
"Event Management"
|
|
25303
25484
|
];
|
|
25304
25485
|
var BASIC_SKILL_NAMES = [];
|
|
25305
|
-
|
|
25306
|
-
// src/lib/search.ts
|
|
25307
|
-
function editDistance(a, b) {
|
|
25308
|
-
if (a === b)
|
|
25309
|
-
return 0;
|
|
25310
|
-
if (a.length === 0)
|
|
25311
|
-
return b.length;
|
|
25312
|
-
if (b.length === 0)
|
|
25313
|
-
return a.length;
|
|
25314
|
-
const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
25315
|
-
const curr = new Array(b.length + 1);
|
|
25316
|
-
for (let i = 1;i <= a.length; i++) {
|
|
25317
|
-
curr[0] = i;
|
|
25318
|
-
for (let j = 1;j <= b.length; j++) {
|
|
25319
|
-
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
25320
|
-
curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
25321
|
-
}
|
|
25322
|
-
prev.splice(0, prev.length, ...curr);
|
|
25323
|
-
}
|
|
25324
|
-
return prev[b.length];
|
|
25325
|
-
}
|
|
25326
|
-
function fuzzyMatchScore(word, target) {
|
|
25327
|
-
if (target.includes(word))
|
|
25328
|
-
return 1;
|
|
25329
|
-
const tokens = target.split(/[\s\-_]+/).filter(Boolean);
|
|
25330
|
-
for (const token of tokens) {
|
|
25331
|
-
if (token.startsWith(word))
|
|
25332
|
-
return 0.6;
|
|
25333
|
-
}
|
|
25334
|
-
if (word.length >= 3) {
|
|
25335
|
-
const maxDist = word.length <= 3 ? 1 : 2;
|
|
25336
|
-
for (const token of tokens) {
|
|
25337
|
-
if (Math.abs(token.length - word.length) <= maxDist) {
|
|
25338
|
-
const dist = editDistance(word, token);
|
|
25339
|
-
if (dist <= maxDist)
|
|
25340
|
-
return 0.4;
|
|
25341
|
-
}
|
|
25342
|
-
}
|
|
25343
|
-
}
|
|
25344
|
-
return 0;
|
|
25345
|
-
}
|
|
25346
|
-
function searchSkills(query, registry = loadRegistry()) {
|
|
25347
|
-
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
25348
|
-
if (words.length === 0)
|
|
25349
|
-
return [];
|
|
25350
|
-
const scored = [];
|
|
25351
|
-
for (const skill of registry) {
|
|
25352
|
-
const nameLower = skill.name.toLowerCase();
|
|
25353
|
-
const displayNameLower = skill.displayName.toLowerCase();
|
|
25354
|
-
const descriptionLower = skill.description.toLowerCase();
|
|
25355
|
-
const tagsLower = skill.tags.map((t2) => t2.toLowerCase());
|
|
25356
|
-
const tagsCombined = tagsLower.join(" ");
|
|
25357
|
-
let score = 0;
|
|
25358
|
-
let allWordsMatch = true;
|
|
25359
|
-
for (const word of words) {
|
|
25360
|
-
let wordScore = 0;
|
|
25361
|
-
const nameMatch = fuzzyMatchScore(word, nameLower);
|
|
25362
|
-
if (nameMatch > 0)
|
|
25363
|
-
wordScore += 10 * nameMatch;
|
|
25364
|
-
const displayMatch = fuzzyMatchScore(word, displayNameLower);
|
|
25365
|
-
if (displayMatch > 0)
|
|
25366
|
-
wordScore += 7 * displayMatch;
|
|
25367
|
-
const tagMatch = Math.max(...tagsLower.map((t2) => fuzzyMatchScore(word, t2)), fuzzyMatchScore(word, tagsCombined));
|
|
25368
|
-
if (tagMatch > 0)
|
|
25369
|
-
wordScore += 5 * tagMatch;
|
|
25370
|
-
const descMatch = fuzzyMatchScore(word, descriptionLower);
|
|
25371
|
-
if (descMatch > 0)
|
|
25372
|
-
wordScore += 2 * descMatch;
|
|
25373
|
-
if (wordScore === 0) {
|
|
25374
|
-
allWordsMatch = false;
|
|
25375
|
-
break;
|
|
25376
|
-
}
|
|
25377
|
-
score += wordScore;
|
|
25378
|
-
}
|
|
25379
|
-
if (allWordsMatch && score > 0) {
|
|
25380
|
-
scored.push({ skill, score });
|
|
25381
|
-
}
|
|
25382
|
-
}
|
|
25383
|
-
scored.sort((a, b) => b.score - a.score);
|
|
25384
|
-
return scored.map((s) => s.skill);
|
|
25385
|
-
}
|
|
25386
|
-
function findSimilarSkills(query, maxResults = 3, registry = loadRegistry()) {
|
|
25387
|
-
const q = query.toLowerCase();
|
|
25388
|
-
const scored = registry.map((s) => ({ name: s.name, dist: editDistance(q, s.name.toLowerCase()) })).filter((s) => s.dist <= Math.max(3, Math.floor(q.length / 2))).sort((a, b) => a.dist - b.dist);
|
|
25389
|
-
return scored.slice(0, maxResults).map((s) => s.name);
|
|
25390
|
-
}
|
|
25391
25486
|
// src/lib/registry.ts
|
|
25392
25487
|
function isBasicSkillName(name) {
|
|
25393
25488
|
return BASIC_SKILL_NAMES.includes(name);
|
|
@@ -52,6 +52,16 @@ export declare function ensurePortableSkillFiles(skillPath: string, manifest: Po
|
|
|
52
52
|
* locally, so `port` must never fabricate executable stubs (package.json, bin,
|
|
53
53
|
* src/index.ts, tsconfig.json, AGENTS.md). Apart from aligning declared names
|
|
54
54
|
* after an import rename, it preserves the copied prose and metadata.
|
|
55
|
+
*
|
|
56
|
+
* "Metadata" includes what the candidate declared in skill.json. An instruction
|
|
57
|
+
* skill may legitimately bundle a helper command (and inputs for it) — the
|
|
58
|
+
* validator permits a package.json bin on an instruction skill for exactly that
|
|
59
|
+
* reason — so declared `commands` and `inputs` are carried through verbatim and
|
|
60
|
+
* only an absent declaration becomes an explicit empty array. Forcing them to []
|
|
61
|
+
* here silently erased the declaration on disk: writeSkillJsonWithHash() merges
|
|
62
|
+
* the rebuilt manifest over the copied candidate skill.json, so the candidate's
|
|
63
|
+
* own array was destroyed and the manifest was left self-inconsistent
|
|
64
|
+
* (runtime.entrypoint still naming an entry no command declared).
|
|
55
65
|
*/
|
|
56
66
|
export declare function ensureInstructionSkillFiles(skillPath: string, manifest: PortableSkillManifest): PortableSkillManifest;
|
|
57
67
|
export declare function copySkillDirectory(source: string, destination: string): void;
|
package/dist/lib/search.d.ts
CHANGED
|
@@ -11,6 +11,24 @@ export declare function editDistance(a: string, b: string): number;
|
|
|
11
11
|
* Search skills by name, description, and tags using fuzzy matching.
|
|
12
12
|
*/
|
|
13
13
|
export declare function searchSkills(query: string, registry?: SkillMeta[]): SkillMeta[];
|
|
14
|
+
/**
|
|
15
|
+
* Bounded close canonical names for a refused name, strongest evidence first:
|
|
16
|
+
*
|
|
17
|
+
* 0. the exact name;
|
|
18
|
+
* 1. a hyphen-delimited containment — the candidate owns the query as a whole
|
|
19
|
+
* leading or trailing segment (`fleet-refresh` → `instructions-fleet-refresh`);
|
|
20
|
+
* 2. a multi-word token subset of the candidate;
|
|
21
|
+
* 3. the edit-distance rule that was the only signal before.
|
|
22
|
+
*
|
|
23
|
+
* Containment is what edit distance cannot see, and it is the case that cost
|
|
24
|
+
* real time: the canonical name that owns a shorter name as its trailing
|
|
25
|
+
* segment is 13 edits from the short name, so a caller naming the procedure by
|
|
26
|
+
* its last words was told the procedure did not exist (Agents Audit A27). The
|
|
27
|
+
* distance pass still runs and fills any slots containment leaves, so a
|
|
28
|
+
* distance-only miss — including every query under three characters — keeps the
|
|
29
|
+
* answer it has always had.
|
|
30
|
+
*/
|
|
31
|
+
export declare function findCloseSkillNames(query: string, names: readonly string[], maxResults?: number): string[];
|
|
14
32
|
/**
|
|
15
33
|
* Find skills with names similar to the given query (for "did you mean?" suggestions).
|
|
16
34
|
*/
|
|
@@ -13,6 +13,50 @@ export interface ResolvedSelectedRun {
|
|
|
13
13
|
cacheDir: string;
|
|
14
14
|
}
|
|
15
15
|
export declare function resolveSelectedRun(spec: string, profileId: string, options?: SelectionResolverOptions): Promise<ResolvedSelectedRun>;
|
|
16
|
+
/**
|
|
17
|
+
* The operation an instruction skill declares, if it declares one.
|
|
18
|
+
*
|
|
19
|
+
* An instruction skill is prose-first: agents follow SKILL.md, and `skills run`
|
|
20
|
+
* refuses it. Its manifest may still declare the one operation it ships — the
|
|
21
|
+
* first `commands` entry, naming the helper — together with an explicit runtime
|
|
22
|
+
* contract in `runtime` (`runtime` plus its `version`/`timeout` policy). Such a
|
|
23
|
+
* skill is runnable through that declared operation, and only through it: the
|
|
24
|
+
* executed file is exactly the entry the selected command declares, it must be a
|
|
25
|
+
* regular file in the selected bundle, and a `runtime.entrypoint` that disagrees
|
|
26
|
+
* with it is an inconsistent declaration that refuses (see
|
|
27
|
+
* {@link instructionEntrypointMismatch}) rather than quietly running the other
|
|
28
|
+
* file. An instruction skill that declares no operation — no command, a
|
|
29
|
+
* non-string or empty entry, or no runtime contract — keeps the prose-only
|
|
30
|
+
* refusal (`skills load`, never `skills run`), so an instruction bundle is never
|
|
31
|
+
* mistaken for a runnable stub.
|
|
32
|
+
*/
|
|
33
|
+
export declare function declaredOperation(manifest: Record<string, any>): {
|
|
34
|
+
entry: string;
|
|
35
|
+
} | null;
|
|
36
|
+
/**
|
|
37
|
+
* The entry the instruction skill's declared command names, when it names one.
|
|
38
|
+
* The first `commands` entry is the operation: nothing else in the manifest
|
|
39
|
+
* selects what an instruction skill runs.
|
|
40
|
+
*/
|
|
41
|
+
export declare function instructionCommandEntry(manifest: Record<string, any>): string | null;
|
|
42
|
+
/**
|
|
43
|
+
* A declared `runtime.entrypoint` that disagrees with the declared command entry,
|
|
44
|
+
* for an instruction skill. The command entry is the authority, so a disagreement
|
|
45
|
+
* is an inconsistent declaration: it refuses, and the disagreeing file never runs.
|
|
46
|
+
*/
|
|
47
|
+
export declare function instructionEntrypointMismatch(manifest: Record<string, any>): string | null;
|
|
48
|
+
/**
|
|
49
|
+
* Why an instruction skill cannot run locally, if it cannot. `isBundledFile` says
|
|
50
|
+
* whether a path is a regular file in the selected bundle, so a declared command
|
|
51
|
+
* entry that is not there is refused by name instead of being substituted. The
|
|
52
|
+
* CLI, the MCP tool and the local executor all ask this one function, so the
|
|
53
|
+
* reason and the code cannot drift between them. A runnable instruction skill
|
|
54
|
+
* (declared command, agreeing runtime, entry present in the bundle) returns null.
|
|
55
|
+
*/
|
|
56
|
+
export declare function instructionRunRefusal(manifest: Record<string, any>, isBundledFile: (path: string) => boolean): {
|
|
57
|
+
code: string;
|
|
58
|
+
message: string;
|
|
59
|
+
} | null;
|
|
16
60
|
export interface SelectedLocalRunOptions {
|
|
17
61
|
args?: string[];
|
|
18
62
|
input?: unknown;
|
|
@@ -54,6 +98,7 @@ export declare function executeSelectedLocal(selected: ResolvedSelectedRun, opti
|
|
|
54
98
|
selection: ResolvedSkillSelection;
|
|
55
99
|
target: "local";
|
|
56
100
|
runtime: any;
|
|
101
|
+
entrypoint: string;
|
|
57
102
|
inputDigest: string;
|
|
58
103
|
runDirectory: string;
|
|
59
104
|
}>;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { type SelectionCacheOptions } from "./selection-cache.js";
|
|
2
|
+
/** Bounded so one receipt can never carry an unbounded drift list. */
|
|
3
|
+
export declare const PROVENANCE_DRIFT_LIMIT = 200;
|
|
4
|
+
export type SelectionProvenanceStatus = "current" | "behind" | "pinned" | "unverified";
|
|
5
|
+
export type SelectionProvenanceSource = "local-receipt" | "authority-receipt" | "session-receipt";
|
|
6
|
+
export type SelectionProvenanceReference = "authority" | "synced-snapshot" | "unavailable";
|
|
7
|
+
/**
|
|
8
|
+
* The fixed diagnostic vocabulary. A reader's exception text is never carried,
|
|
9
|
+
* only one of these codes, so a foreign body or a credential inside an error
|
|
10
|
+
* string cannot reach a receipt that may be shared.
|
|
11
|
+
*/
|
|
12
|
+
export type SelectionProvenanceReason = "CURRENT_SCHEMA_MATCH" | "REVISION_BEHIND" | "PINNED_BY_DESIGN" | "IDENTITY_MISMATCH" | "INCOMPLETE_RECEIPT" | "DIFFERENT_PROFILE" | "NO_LOCAL_RECEIPT" | "NO_SESSION_NAMED" | "NO_SESSION_RECEIPT" | "NO_STATION_RECEIPT" | "READ_REFUSED" | "REFERENCE_FROM_AUTHORITY" | "REFERENCE_NOT_AUTHORITY" | "REFERENCE_UNAVAILABLE" | "NO_STATION_REPORTED";
|
|
13
|
+
export interface SelectionProvenanceVersion {
|
|
14
|
+
slug: string;
|
|
15
|
+
version: string;
|
|
16
|
+
bundleDigest?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface SelectionProvenanceConsumer {
|
|
19
|
+
kind: "station" | "session";
|
|
20
|
+
source: SelectionProvenanceSource;
|
|
21
|
+
/** The local host for a local receipt, the requested station only for the authority receipt. */
|
|
22
|
+
id: string;
|
|
23
|
+
/** Whether `id` names the host running this command or a station named on the command line. */
|
|
24
|
+
idKind: "local-host" | "requested-station";
|
|
25
|
+
profileId?: string;
|
|
26
|
+
profileRevision?: string;
|
|
27
|
+
/** Compared, never assumed: a matching revision under another identity is not current. */
|
|
28
|
+
authority?: string;
|
|
29
|
+
workspaceId?: string;
|
|
30
|
+
observedAt?: string;
|
|
31
|
+
selectionCount?: number;
|
|
32
|
+
/** Only for a session: how many of its selections it really loaded. */
|
|
33
|
+
loadedCount?: number;
|
|
34
|
+
/** Only for a session: the exact versions it really loaded. */
|
|
35
|
+
consumed?: SelectionProvenanceVersion[];
|
|
36
|
+
status: SelectionProvenanceStatus;
|
|
37
|
+
reasonCode: SelectionProvenanceReason;
|
|
38
|
+
reason: string;
|
|
39
|
+
}
|
|
40
|
+
export interface SelectionProvenanceDrift {
|
|
41
|
+
source: SelectionProvenanceSource;
|
|
42
|
+
id: string;
|
|
43
|
+
slug: string;
|
|
44
|
+
selectedVersion: string;
|
|
45
|
+
observedVersion: string;
|
|
46
|
+
}
|
|
47
|
+
export interface SelectionProvenanceReceipt {
|
|
48
|
+
schemaVersion: 1;
|
|
49
|
+
readOnly: true;
|
|
50
|
+
selectionProfile: string;
|
|
51
|
+
selected: {
|
|
52
|
+
profileId: string;
|
|
53
|
+
profileRevision?: string;
|
|
54
|
+
selectionCount?: number;
|
|
55
|
+
authority?: string;
|
|
56
|
+
workspaceId?: string;
|
|
57
|
+
source: SelectionProvenanceReference;
|
|
58
|
+
observedAt?: string;
|
|
59
|
+
reasonCode: SelectionProvenanceReason;
|
|
60
|
+
reason: string;
|
|
61
|
+
};
|
|
62
|
+
consumers: SelectionProvenanceConsumer[];
|
|
63
|
+
drift: SelectionProvenanceDrift[];
|
|
64
|
+
driftTruncated: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* What this receipt deliberately does not attest. Version and digest evidence
|
|
67
|
+
* for published payloads, native discovery, native locks and live consumer
|
|
68
|
+
* adoption are outside this command and stay unverified here rather than being
|
|
69
|
+
* implied by a clean-looking result.
|
|
70
|
+
*/
|
|
71
|
+
notAttested: string[];
|
|
72
|
+
}
|
|
73
|
+
export interface SelectionProvenanceInput {
|
|
74
|
+
profileId: string;
|
|
75
|
+
/** The station whose authority-held sync receipt is read. */
|
|
76
|
+
stationId: string;
|
|
77
|
+
/** The host this command runs on. Never conflated with `stationId`. */
|
|
78
|
+
localStationId: string;
|
|
79
|
+
sessionId?: string;
|
|
80
|
+
/** Read the reference from the locally synced snapshot; never ask the authority. */
|
|
81
|
+
cached?: boolean;
|
|
82
|
+
}
|
|
83
|
+
/** A station receipt reduced to what this receipt compares. */
|
|
84
|
+
export interface ProvenanceStationObservation {
|
|
85
|
+
profileId: string;
|
|
86
|
+
profileRevision: string;
|
|
87
|
+
appliedAt: string;
|
|
88
|
+
workspaceId?: string;
|
|
89
|
+
selections: readonly {
|
|
90
|
+
slug: string;
|
|
91
|
+
version: string;
|
|
92
|
+
}[];
|
|
93
|
+
}
|
|
94
|
+
export interface SelectionProvenanceOptions extends SelectionCacheOptions {
|
|
95
|
+
/** Injected by the SDK and tests; defaults to the authenticated reads. */
|
|
96
|
+
readSelected?: (profileId: string) => Promise<{
|
|
97
|
+
profileId: string;
|
|
98
|
+
profileRevision: string;
|
|
99
|
+
authority?: string;
|
|
100
|
+
workspaceId?: string;
|
|
101
|
+
selections: readonly {
|
|
102
|
+
slug: string;
|
|
103
|
+
version: string;
|
|
104
|
+
}[];
|
|
105
|
+
}>;
|
|
106
|
+
readStation?: (stationId: string) => Promise<ProvenanceStationObservation>;
|
|
107
|
+
}
|
|
108
|
+
export declare function readSelectionProvenance(input: SelectionProvenanceInput, options?: SelectionProvenanceOptions): Promise<SelectionProvenanceReceipt>;
|