@klhapp/skillmux 1.11.2 → 1.13.0

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.
Files changed (104) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +20 -10
  3. package/bin/skillmux.js +89 -0
  4. package/docs/README.md +4 -4
  5. package/docs/cli.md +15 -5
  6. package/docs/concepts.md +4 -3
  7. package/docs/configuration.md +22 -5
  8. package/docs/deployment.md +3 -3
  9. package/docs/getting-started.md +32 -7
  10. package/docs/releasing.md +17 -2
  11. package/docs/sdd/cli-remote-target-parity/schema-bucket-b.yaml +297 -0
  12. package/docs/sdd/cli-remote-target-parity/spec-bucket-b.md +95 -0
  13. package/docs/sdd/cli-remote-target-parity/spec.md +111 -0
  14. package/docs/sdd/cli-remote-target-parity/think.md +183 -0
  15. package/docs/sdd/cli-surface-consistency/think.md +149 -0
  16. package/docs/sdd/config-authority-rerank-resilience/plan.md +338 -0
  17. package/docs/sdd/config-authority-rerank-resilience/review.md +124 -0
  18. package/docs/sdd/config-authority-rerank-resilience/spec.md +78 -0
  19. package/docs/sdd/feat-packaging-deployment/review.md +95 -0
  20. package/docs/sdd/fetch-outcome-flywheel/review.md +103 -0
  21. package/docs/sdd/fetch-outcome-flywheel/schema.json +347 -0
  22. package/docs/sdd/fetch-outcome-flywheel/spec.md +185 -0
  23. package/docs/sdd/npm-native-binaries/review.md +103 -0
  24. package/docs/sdd/npm-native-binaries/spec.md +148 -0
  25. package/docs/sdd/ranked-shortlist-2/pr3-calibration-removal.md +56 -0
  26. package/docs/sdd/refactor-builtin-target-paths/spec.md +41 -0
  27. package/docs/sdd/runtime-resource-hardening/review.md +86 -0
  28. package/docs/sdd/runtime-resource-hardening/spec.md +43 -0
  29. package/docs/sdd/security-hardening-phase/think.md +129 -0
  30. package/docs/sdd/security-observability/review.md +96 -0
  31. package/docs/sdd/security-observability/spec.md +55 -0
  32. package/docs/sdd/skill-provenance-update/review.md +152 -0
  33. package/docs/sdd/skill-provenance-update/schema.json +193 -0
  34. package/docs/sdd/skill-provenance-update/spec.md +134 -0
  35. package/docs/sdd/supply-chain-hardening/review.md +50 -0
  36. package/docs/sdd/supply-chain-hardening/spec.md +42 -0
  37. package/docs/sdd/target-marker-rehome/spec.md +33 -0
  38. package/docs/skill-management.md +1 -1
  39. package/docs/troubleshooting.md +18 -2
  40. package/package.json +15 -8
  41. package/src/adapters.ts +0 -438
  42. package/src/audit.ts +0 -21
  43. package/src/cli.ts +0 -928
  44. package/src/clients.ts +0 -368
  45. package/src/commands/audit.ts +0 -85
  46. package/src/commands/config.ts +0 -224
  47. package/src/commands/context.ts +0 -104
  48. package/src/commands/core.ts +0 -56
  49. package/src/commands/doctor.ts +0 -97
  50. package/src/commands/eval.ts +0 -88
  51. package/src/commands/init.ts +0 -672
  52. package/src/commands/install.ts +0 -155
  53. package/src/commands/local-vault.ts +0 -60
  54. package/src/commands/models.ts +0 -10
  55. package/src/commands/outdated.ts +0 -115
  56. package/src/commands/project.ts +0 -580
  57. package/src/commands/report.ts +0 -66
  58. package/src/commands/scan.ts +0 -71
  59. package/src/commands/shared.ts +0 -38
  60. package/src/commands/skill.ts +0 -33
  61. package/src/commands/sync.ts +0 -233
  62. package/src/commands/target.ts +0 -225
  63. package/src/commands/update.ts +0 -277
  64. package/src/completions.ts +0 -196
  65. package/src/concurrency-limiter.ts +0 -61
  66. package/src/config-service.ts +0 -398
  67. package/src/config-watcher.ts +0 -253
  68. package/src/config.ts +0 -583
  69. package/src/context.ts +0 -188
  70. package/src/db-audit.ts +0 -286
  71. package/src/db-index.ts +0 -238
  72. package/src/db.ts +0 -3
  73. package/src/deployment.ts +0 -39
  74. package/src/doctor.ts +0 -205
  75. package/src/eval.ts +0 -302
  76. package/src/global-flags.ts +0 -46
  77. package/src/init-agents.ts +0 -329
  78. package/src/init-instructions.ts +0 -192
  79. package/src/init.ts +0 -389
  80. package/src/install.ts +0 -232
  81. package/src/lifecycle.ts +0 -51
  82. package/src/logger.ts +0 -26
  83. package/src/manifest.ts +0 -352
  84. package/src/mcp-registration.ts +0 -89
  85. package/src/metrics.ts +0 -121
  86. package/src/models.ts +0 -20
  87. package/src/output.ts +0 -214
  88. package/src/project-setup.ts +0 -36
  89. package/src/prompts.ts +0 -124
  90. package/src/provenance.ts +0 -99
  91. package/src/rate-limiter.ts +0 -137
  92. package/src/readiness.ts +0 -30
  93. package/src/redact.ts +0 -52
  94. package/src/router-core.ts +0 -666
  95. package/src/rrf.ts +0 -31
  96. package/src/scan.ts +0 -300
  97. package/src/server.ts +0 -930
  98. package/src/setup.ts +0 -145
  99. package/src/snapshot.ts +0 -135
  100. package/src/stats.ts +0 -340
  101. package/src/sync.ts +0 -482
  102. package/src/toml-writer.ts +0 -51
  103. package/src/types.ts +0 -212
  104. package/src/vault.ts +0 -207
package/src/types.ts DELETED
@@ -1,212 +0,0 @@
1
- export interface RecallConfig {
2
- k_lexical: number;
3
- k_vector: number;
4
- k_rerank?: number;
5
- }
6
-
7
- export interface OutputConfig {
8
- top_k: number;
9
- max_top_k: number;
10
- }
11
-
12
- export interface Thresholds {
13
- /** @deprecated Use output.top_k instead */
14
- candidate_limit?: number;
15
- match_score?: number;
16
- match_margin?: number;
17
- candidate_floor?: number;
18
- }
19
-
20
- export type ONNXDevice =
21
- | "cpu"
22
- | "auto"
23
- | "gpu"
24
- | "wasm"
25
- | "webgpu"
26
- | "cuda"
27
- | "dml"
28
- | "coreml"
29
- | "webnn"
30
- | "webnn-npu"
31
- | "webnn-gpu"
32
- | "webnn-cpu";
33
-
34
- export type ONNXDtype =
35
- | "q8"
36
- | "auto"
37
- | "fp32"
38
- | "fp16"
39
- | "int8"
40
- | "uint8"
41
- | "q4"
42
- | "bnb4"
43
- | "q4f16"
44
- | "q2"
45
- | "q2f16"
46
- | "q1"
47
- | "q1f16";
48
-
49
- export interface ModelConfig {
50
- model: string;
51
- device?: ONNXDevice;
52
- dtype?: ONNXDtype;
53
- }
54
-
55
- export interface LocalInferenceConfig {
56
- mode: "local";
57
- bundle: string;
58
- models_dir: string;
59
- embedding: ModelConfig & { dimension: number };
60
- }
61
-
62
- export interface RemoteEmbeddingConfig {
63
- provider: "openai";
64
- endpoint: string;
65
- model: string;
66
- dimension: number;
67
- api_key_env?: string;
68
- }
69
-
70
- export interface RemoteRerankerConfig {
71
- adapter: "jina-v1" | "bifrost-v1";
72
- endpoint: string;
73
- model: string;
74
- api_key_env?: string;
75
- }
76
-
77
- export interface RemoteInferenceConfig {
78
- mode: "remote";
79
- timeout_ms: number;
80
- embedding: RemoteEmbeddingConfig;
81
- reranker?: RemoteRerankerConfig;
82
- }
83
-
84
- export type InferenceConfig = LocalInferenceConfig | RemoteInferenceConfig;
85
-
86
- export interface RateLimitConfig {
87
- enabled: boolean;
88
- requests_per_minute: number;
89
- trust_proxy?: boolean;
90
- }
91
-
92
- export interface AdminConfig {
93
- enabled: boolean;
94
- token_env: string;
95
- }
96
-
97
- export interface ServerConfig {
98
- auth_enabled: boolean;
99
- auth_token_env: string;
100
- allowed_origins: string[];
101
- hostname?: string;
102
- rate_limit?: RateLimitConfig;
103
- admin?: AdminConfig;
104
- max_body_bytes?: number;
105
- max_concurrent_requests?: number;
106
- }
107
-
108
- export interface ConfigPolicy {
109
- environment_overrides?: boolean;
110
- }
111
-
112
- export interface EgressConfig {
113
- allowed_hosts?: string[];
114
- }
115
-
116
- export interface AuditConfig {
117
- /** Age in days beyond which audit rows are pruned. 0 disables pruning. */
118
- retention_days: number;
119
- }
120
-
121
- export interface Config {
122
- config?: ConfigPolicy;
123
- vault_path: string;
124
- local_vault_paths: string[];
125
- state_dir: string;
126
- recall: RecallConfig;
127
- output: OutputConfig;
128
- inference: InferenceConfig;
129
- server?: ServerConfig;
130
- audit?: AuditConfig;
131
- egress?: EgressConfig;
132
- }
133
-
134
- export interface RankedCandidate {
135
- rank: number;
136
- skill_id: string;
137
- description: string;
138
- score: number | null;
139
- }
140
-
141
- export type RetrievalCapability = "exact" | "reranked" | "hybrid" | "lexical";
142
-
143
- export type DegradationReason =
144
- | "embedding_timeout"
145
- | "embedding_unavailable"
146
- | "embedding_protocol_error"
147
- | "reranker_timeout"
148
- | "reranker_unavailable"
149
- | "reranker_protocol_error";
150
-
151
- export interface ResolveResult {
152
- request_id: string;
153
- retrieval: RetrievalCapability;
154
- degraded_from?: "reranked" | "hybrid";
155
- degradation_reason?: DegradationReason;
156
- candidates: RankedCandidate[];
157
- }
158
-
159
- export interface ResolveSkillInput {
160
- query: string;
161
- top_k?: number;
162
- /** Test/ops escape hatch: use lexical retrieval only. Not exposed on the MCP wire. */
163
- forceLexical?: boolean;
164
- }
165
-
166
- export interface FetchSkillInput {
167
- skill_id: string;
168
- request_id?: string;
169
- }
170
-
171
- export interface FetchSkillResult {
172
- skill_id: string;
173
- title: string;
174
- content_sha256: string;
175
- body: string;
176
- files: string[];
177
- }
178
-
179
- export interface AuditCandidate {
180
- skill_id: string;
181
- score: number | null;
182
- }
183
-
184
- export interface AuditRow {
185
- id: number;
186
- ts: string;
187
- /** Null for rows written before request_id existed (AC4). */
188
- request_id: string | null;
189
- query: string;
190
- retrieval: RetrievalCapability;
191
- degraded_from?: "reranked" | "hybrid" | null;
192
- degradation_reason?: DegradationReason | null;
193
- candidates: AuditCandidate[];
194
- latency_ms: number;
195
- }
196
-
197
- export interface FetchAuditRow {
198
- id: number;
199
- ts: string;
200
- skill_id: string;
201
- /** Exactly as supplied by the caller, including an unknown value. Null when the caller sent none. */
202
- request_id: string | null;
203
- /** Null when the fetch is uncorrelated: no request_id, an unknown/malformed one, or a pruned resolve row. */
204
- resolve_audit_id: number | null;
205
- /** Rank of skill_id in the correlated resolve's shortlist. Null when uncorrelated or absent from the shortlist. */
206
- rank_at_resolve: number | null;
207
- }
208
-
209
- export interface Clients {
210
- embed(texts: string[]): Promise<Float32Array[]>;
211
- rerank?: (query: string, docs: { skill_id: string; text: string }[]) => Promise<number[]>;
212
- }
package/src/vault.ts DELETED
@@ -1,207 +0,0 @@
1
- import { existsSync, lstatSync, readdirSync, statSync } from "node:fs";
2
- import { join, relative } from "node:path";
3
-
4
- export const SKILL_ID_PATTERN = /^[a-z0-9][a-z0-9-]{1,127}$/;
5
-
6
- /** Provenance sidecar filename (see provenance.ts). Kept here, not re-imported from
7
- * provenance.ts, so listSupportingFiles can exclude it without a circular import. */
8
- export const SKILLMUX_ORIGIN_FILENAME = ".skillmux-origin";
9
-
10
- export interface VaultSkill {
11
- skill_id: string;
12
- title: string;
13
- description: string;
14
- aliases: string[];
15
- body: string;
16
- content_sha256: string;
17
- }
18
-
19
- export function sha256Hex(data: string | Uint8Array): string {
20
- return new Bun.CryptoHasher("sha256").update(data).digest("hex");
21
- }
22
-
23
- interface Frontmatter {
24
- name?: unknown;
25
- description?: unknown;
26
- aliases?: unknown;
27
- }
28
-
29
- export function parseSkillMd(skillId: string, raw: string): VaultSkill {
30
- let fm: Frontmatter = {};
31
- if (raw.startsWith("---\n")) {
32
- const end = raw.indexOf("\n---", 4);
33
- if (end === -1) throw new Error(`unterminated frontmatter in ${skillId}/SKILL.md`);
34
- fm = (Bun.YAML.parse(raw.slice(4, end)) ?? {}) as Frontmatter;
35
- }
36
- const aliases = Array.isArray(fm.aliases) ? fm.aliases.map(String) : [];
37
- return {
38
- skill_id: skillId,
39
- title: typeof fm.name === "string" && fm.name.length > 0 ? fm.name : skillId,
40
- description: typeof fm.description === "string" ? fm.description : "",
41
- aliases,
42
- body: raw,
43
- content_sha256: sha256Hex(raw),
44
- };
45
- }
46
-
47
- /** Strict decode: invalid UTF-8 throws instead of silently mangling content. */
48
- export function decodeUtf8Strict(bytes: Uint8Array): string {
49
- return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
50
- }
51
-
52
- /** SKILL.md itself is read directly (not via listSupportingFiles, which excludes
53
- * it by name) — so its own symlink-ness must be checked here. Without this, a
54
- * symlinked SKILL.md reachable via a shared git-backed vault or a hand-edit would
55
- * have its target's content indexed and delivered to agents as the skill body.
56
- *
57
- * The skill directory itself must be checked too, separately from SKILL.md's own
58
- * leaf check: `lstat` only refuses to follow the *final* path component, so a
59
- * symlinked skill directory containing a real (non-symlink) SKILL.md file at its
60
- * target silently passes the leaf check while still resolving straight through
61
- * to arbitrary host content. */
62
- export async function readSkill(vaultPath: string, skillId: string): Promise<VaultSkill> {
63
- const skillDir = join(vaultPath, skillId);
64
- if (lstatSync(skillDir).isSymbolicLink()) {
65
- throw new Error(`refusing to read ${skillId}: the skill directory is a symlink`);
66
- }
67
- const path = join(skillDir, "SKILL.md");
68
- if (lstatSync(path).isSymbolicLink()) {
69
- throw new Error(`refusing to read ${skillId}/SKILL.md: it is a symlink`);
70
- }
71
- const bytes = await Bun.file(path).bytes();
72
- return parseSkillMd(skillId, decodeUtf8Strict(bytes));
73
- }
74
-
75
- export async function scanVault(
76
- vaultPath: string,
77
- onInvalid?: (skillId: string, error: unknown) => void,
78
- ): Promise<VaultSkill[]> {
79
- const skills: VaultSkill[] = [];
80
- for (const entry of readdirSync(vaultPath, { withFileTypes: true })) {
81
- if (!entry.isDirectory() || !SKILL_ID_PATTERN.test(entry.name)) continue;
82
- try {
83
- skills.push(await readSkill(vaultPath, entry.name));
84
- } catch (error) {
85
- onInvalid?.(entry.name, error);
86
- }
87
- }
88
- return skills;
89
- }
90
-
91
- /** Local overlays checked first (in order), canonical vault_path is the fallback. */
92
- export function vaultResolutionOrder(vaultPath: string, localVaultPaths: string[]): string[] {
93
- return [...localVaultPaths, vaultPath];
94
- }
95
-
96
- /** Which configured root actually backs skillId, per local-overrides-first precedence. */
97
- export function resolveSkillRoot(skillId: string, vaultPath: string, localVaultPaths: string[]): string | null {
98
- for (const root of vaultResolutionOrder(vaultPath, localVaultPaths)) {
99
- if (existsSync(join(root, skillId, "SKILL.md"))) return root;
100
- }
101
- return null;
102
- }
103
-
104
- /** Scans every configured root and merges by skill_id — first-seen (per resolution order) wins. */
105
- export async function scanVaults(
106
- vaultPath: string,
107
- localVaultPaths: string[],
108
- onInvalid?: (skillId: string, error: unknown) => void,
109
- ): Promise<VaultSkill[]> {
110
- const seen = new Set<string>();
111
- const merged: VaultSkill[] = [];
112
- for (const root of vaultResolutionOrder(vaultPath, localVaultPaths)) {
113
- if (!existsSync(root)) continue;
114
- for (const skill of await scanVault(root, onInvalid)) {
115
- if (seen.has(skill.skill_id)) continue;
116
- seen.add(skill.skill_id);
117
- merged.push(skill);
118
- }
119
- }
120
- return merged;
121
- }
122
-
123
- export interface ShadowedSkill {
124
- skill_id: string;
125
- winner: string;
126
- shadowed: string[];
127
- }
128
-
129
- /** skill_ids present in more than one configured root — every root after the winner is silently shadowed. */
130
- export function findShadowedSkills(vaultPath: string, localVaultPaths: string[]): ShadowedSkill[] {
131
- const rootsBySkillId = new Map<string, string[]>();
132
- for (const root of vaultResolutionOrder(vaultPath, localVaultPaths)) {
133
- if (!existsSync(root)) continue;
134
- for (const entry of readdirSync(root, { withFileTypes: true })) {
135
- if (!entry.isDirectory() || !SKILL_ID_PATTERN.test(entry.name)) continue;
136
- if (!existsSync(join(root, entry.name, "SKILL.md"))) continue;
137
- const roots = rootsBySkillId.get(entry.name) ?? [];
138
- roots.push(root);
139
- rootsBySkillId.set(entry.name, roots);
140
- }
141
- }
142
- const shadowed: ShadowedSkill[] = [];
143
- for (const [skillId, roots] of rootsBySkillId) {
144
- if (roots.length > 1) shadowed.push({ skill_id: skillId, winner: roots[0]!, shadowed: roots.slice(1) });
145
- }
146
- return shadowed.sort((a, b) => a.skill_id.localeCompare(b.skill_id));
147
- }
148
-
149
- /** Relative paths of everything under the skill dir except SKILL.md and the
150
- * provenance sidecar, sorted. Symlinks are excluded rather than followed —
151
- * install/sync already refuse a skill containing one, but a symlink can still
152
- * reach the vault directly (a shared git-backed vault pulled in, or a hand-edit),
153
- * and this function otherwise feeds skill content straight into content
154
- * scanning (scan.ts) and drift hashing (provenance.ts). `skillId` must be a
155
- * single path segment — not the stricter SKILL_ID_PATTERN, since scanPath's
156
- * ad-hoc single-directory mode legitimately passes an arbitrary, not-yet-
157
- * normalized directory name — so `..`/`.`/a path separator is rejected to
158
- * keep the walk inside `vaultPath` without breaking that mode. */
159
- export function listSupportingFiles(vaultPath: string, skillId: string): string[] {
160
- if (skillId === "" || skillId === "." || skillId === ".." || skillId.includes("/") || skillId.includes("\\")) {
161
- return [];
162
- }
163
- const root = join(vaultPath, skillId);
164
- // A symlinked skill directory must not be walked into: readdirSync follows a
165
- // symlinked path argument (unlike the entry.isSymbolicLink() check below, which
166
- // only applies to entries *found by* the walk), so without this a symlinked
167
- // `root` would return the target directory's file listing straight through.
168
- if (!existsSync(root) || lstatSync(root).isSymbolicLink()) return [];
169
- const files: string[] = [];
170
- const walk = (dir: string) => {
171
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
172
- if (entry.isSymbolicLink()) continue;
173
- const abs = join(dir, entry.name);
174
- if (entry.isDirectory()) walk(abs);
175
- else if (statSync(abs).isFile()) {
176
- const rel = relative(root, abs);
177
- if (rel !== "SKILL.md" && rel !== SKILLMUX_ORIGIN_FILENAME) files.push(rel);
178
- }
179
- }
180
- };
181
- walk(root);
182
- return files.sort();
183
- }
184
-
185
- export function getVaultMaxMtime(vaultPath: string): number {
186
- try {
187
- let maxMtime = statSync(vaultPath).mtimeMs;
188
- const entries = readdirSync(vaultPath, { withFileTypes: true });
189
- for (const entry of entries) {
190
- if (entry.isDirectory() && SKILL_ID_PATTERN.test(entry.name)) {
191
- try {
192
- const folderPath = join(vaultPath, entry.name);
193
- const folderMtime = statSync(folderPath).mtimeMs;
194
- maxMtime = Math.max(maxMtime, folderMtime);
195
-
196
- const fileMtime = statSync(join(folderPath, "SKILL.md")).mtimeMs;
197
- maxMtime = Math.max(maxMtime, fileMtime);
198
- } catch {
199
- // Ignore deleted files
200
- }
201
- }
202
- }
203
- return maxMtime;
204
- } catch {
205
- return 0;
206
- }
207
- }