@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.
- package/CHANGELOG.md +15 -0
- package/README.md +20 -10
- package/bin/skillmux.js +89 -0
- package/docs/README.md +4 -4
- package/docs/cli.md +15 -5
- package/docs/concepts.md +4 -3
- package/docs/configuration.md +22 -5
- package/docs/deployment.md +3 -3
- package/docs/getting-started.md +32 -7
- package/docs/releasing.md +17 -2
- package/docs/sdd/cli-remote-target-parity/schema-bucket-b.yaml +297 -0
- package/docs/sdd/cli-remote-target-parity/spec-bucket-b.md +95 -0
- package/docs/sdd/cli-remote-target-parity/spec.md +111 -0
- package/docs/sdd/cli-remote-target-parity/think.md +183 -0
- package/docs/sdd/cli-surface-consistency/think.md +149 -0
- package/docs/sdd/config-authority-rerank-resilience/plan.md +338 -0
- package/docs/sdd/config-authority-rerank-resilience/review.md +124 -0
- package/docs/sdd/config-authority-rerank-resilience/spec.md +78 -0
- package/docs/sdd/feat-packaging-deployment/review.md +95 -0
- package/docs/sdd/fetch-outcome-flywheel/review.md +103 -0
- package/docs/sdd/fetch-outcome-flywheel/schema.json +347 -0
- package/docs/sdd/fetch-outcome-flywheel/spec.md +185 -0
- package/docs/sdd/npm-native-binaries/review.md +103 -0
- package/docs/sdd/npm-native-binaries/spec.md +148 -0
- package/docs/sdd/ranked-shortlist-2/pr3-calibration-removal.md +56 -0
- package/docs/sdd/refactor-builtin-target-paths/spec.md +41 -0
- package/docs/sdd/runtime-resource-hardening/review.md +86 -0
- package/docs/sdd/runtime-resource-hardening/spec.md +43 -0
- package/docs/sdd/security-hardening-phase/think.md +129 -0
- package/docs/sdd/security-observability/review.md +96 -0
- package/docs/sdd/security-observability/spec.md +55 -0
- package/docs/sdd/skill-provenance-update/review.md +152 -0
- package/docs/sdd/skill-provenance-update/schema.json +193 -0
- package/docs/sdd/skill-provenance-update/spec.md +134 -0
- package/docs/sdd/supply-chain-hardening/review.md +50 -0
- package/docs/sdd/supply-chain-hardening/spec.md +42 -0
- package/docs/sdd/target-marker-rehome/spec.md +33 -0
- package/docs/skill-management.md +1 -1
- package/docs/troubleshooting.md +18 -2
- package/package.json +15 -8
- package/src/adapters.ts +0 -438
- package/src/audit.ts +0 -21
- package/src/cli.ts +0 -928
- package/src/clients.ts +0 -368
- package/src/commands/audit.ts +0 -85
- package/src/commands/config.ts +0 -224
- package/src/commands/context.ts +0 -104
- package/src/commands/core.ts +0 -56
- package/src/commands/doctor.ts +0 -97
- package/src/commands/eval.ts +0 -88
- package/src/commands/init.ts +0 -672
- package/src/commands/install.ts +0 -155
- package/src/commands/local-vault.ts +0 -60
- package/src/commands/models.ts +0 -10
- package/src/commands/outdated.ts +0 -115
- package/src/commands/project.ts +0 -580
- package/src/commands/report.ts +0 -66
- package/src/commands/scan.ts +0 -71
- package/src/commands/shared.ts +0 -38
- package/src/commands/skill.ts +0 -33
- package/src/commands/sync.ts +0 -233
- package/src/commands/target.ts +0 -225
- package/src/commands/update.ts +0 -277
- package/src/completions.ts +0 -196
- package/src/concurrency-limiter.ts +0 -61
- package/src/config-service.ts +0 -398
- package/src/config-watcher.ts +0 -253
- package/src/config.ts +0 -583
- package/src/context.ts +0 -188
- package/src/db-audit.ts +0 -286
- package/src/db-index.ts +0 -238
- package/src/db.ts +0 -3
- package/src/deployment.ts +0 -39
- package/src/doctor.ts +0 -205
- package/src/eval.ts +0 -302
- package/src/global-flags.ts +0 -46
- package/src/init-agents.ts +0 -329
- package/src/init-instructions.ts +0 -192
- package/src/init.ts +0 -389
- package/src/install.ts +0 -232
- package/src/lifecycle.ts +0 -51
- package/src/logger.ts +0 -26
- package/src/manifest.ts +0 -352
- package/src/mcp-registration.ts +0 -89
- package/src/metrics.ts +0 -121
- package/src/models.ts +0 -20
- package/src/output.ts +0 -214
- package/src/project-setup.ts +0 -36
- package/src/prompts.ts +0 -124
- package/src/provenance.ts +0 -99
- package/src/rate-limiter.ts +0 -137
- package/src/readiness.ts +0 -30
- package/src/redact.ts +0 -52
- package/src/router-core.ts +0 -666
- package/src/rrf.ts +0 -31
- package/src/scan.ts +0 -300
- package/src/server.ts +0 -930
- package/src/setup.ts +0 -145
- package/src/snapshot.ts +0 -135
- package/src/stats.ts +0 -340
- package/src/sync.ts +0 -482
- package/src/toml-writer.ts +0 -51
- package/src/types.ts +0 -212
- package/src/vault.ts +0 -207
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://klh.app/schemas/skillmux/skill-provenance-update.json",
|
|
4
|
+
"title": "Skillmux — skill provenance + update contract",
|
|
5
|
+
"description": "Contract delta for docs/sdd/skill-provenance-update/spec.md. Each $def is either a NEW type or a full replacement for a def of the same name in docs/schema.json. At implementation time these merge into docs/schema.json $defs; nothing here is a second source of truth. Merge targets are noted per def in $comment.",
|
|
6
|
+
"$defs": {
|
|
7
|
+
"GitCommitSha": {
|
|
8
|
+
"$comment": "NEW. AC1, AC3, AC5, AC9. The resolved commit SHA at HEAD of a shallow clone (git rev-parse) or of git ls-remote output. Git's default object format, SHA-1: 40 lowercase hex chars.",
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "Git commit SHA (SHA-1, 40 lowercase hex chars) identifying a resolved repository state.",
|
|
11
|
+
"pattern": "^[0-9a-f]{40}$"
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
"SkillContentHash": {
|
|
15
|
+
"$comment": "NEW. AC1, AC7, AC9. Distinct def from router-core.json#/$defs/Sha256Hex: that hashes exact SKILL.md bytes only, this hashes the whole installed skill directory (SKILL.md + supporting files) per src/provenance.ts hashSkillContent(dir), for drift detection.",
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "Lowercase hex SHA-256 over the installed skill's full content (SKILL.md plus every file returned by listSupportingFiles), used to detect local drift and remote content changes.",
|
|
18
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
"RelativeSkillPath": {
|
|
22
|
+
"$comment": "NEW. AC1, AC6. Subpath within the source repo that resolveSkillDir used to locate the skill, e.g. 'skills/my-skill'. Absent when the skill lived at the repo root.",
|
|
23
|
+
"type": "string",
|
|
24
|
+
"minLength": 1,
|
|
25
|
+
"maxLength": 512,
|
|
26
|
+
"pattern": "^(?!/)(?!.*\\.\\.).+$"
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
"SkillOrigin": {
|
|
30
|
+
"$comment": "NEW. AC1, AC2. On-disk shape of the .skillmux-origin sidecar, written by install.ts/runInstall (fresh install and --force reinstall) and rewritten by update.ts on successful update. Mirrors the .skillmux marker's schema_version convention in sync.ts but is a distinct file and type, never confused with SkillmuxMarker (AC2).",
|
|
31
|
+
"type": "object",
|
|
32
|
+
"properties": {
|
|
33
|
+
"schema_version": { "const": 1 },
|
|
34
|
+
"source_url": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"minLength": 1,
|
|
37
|
+
"description": "Git URL the skill was fetched from (resolveRepoSource output), e.g. https://github.com/owner/repo.git."
|
|
38
|
+
},
|
|
39
|
+
"skill_path": { "$ref": "#/$defs/RelativeSkillPath" },
|
|
40
|
+
"commit": { "$ref": "#/$defs/GitCommitSha" },
|
|
41
|
+
"installed_at": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"format": "date-time",
|
|
44
|
+
"description": "AC1: ISO-8601 timestamp of the install or update that produced this sidecar."
|
|
45
|
+
},
|
|
46
|
+
"content_hash": { "$ref": "#/$defs/SkillContentHash" }
|
|
47
|
+
},
|
|
48
|
+
"required": ["schema_version", "source_url", "commit", "installed_at", "content_hash"],
|
|
49
|
+
"additionalProperties": false
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
"ScanFinding": {
|
|
53
|
+
"$comment": "NEW to docs/schema.json. Transcribes ValidationResult.findings at src/install.ts:52-78 (RuleMatch at src/scan.ts:7-11, extended with skill_id and file). Reused by update.ts's skipped_scan_fail results (AC8).",
|
|
54
|
+
"type": "object",
|
|
55
|
+
"properties": {
|
|
56
|
+
"skill_id": { "$ref": "https://klh.app/schemas/skillmux/router-core.json#/$defs/SkillId" },
|
|
57
|
+
"file": { "type": "string", "minLength": 1, "description": "File the finding was reported against, relative to the skill directory (e.g. 'SKILL.md')." },
|
|
58
|
+
"rule_id": { "type": "string", "minLength": 1 },
|
|
59
|
+
"severity": { "enum": ["low", "medium", "high"] },
|
|
60
|
+
"message": { "type": "string", "minLength": 1 },
|
|
61
|
+
"line": { "type": "integer", "minimum": 1 }
|
|
62
|
+
},
|
|
63
|
+
"required": ["skill_id", "file", "rule_id", "severity", "message"],
|
|
64
|
+
"additionalProperties": false
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
"OutdatedCheckStatus": {
|
|
68
|
+
"$comment": "NEW. AC3, AC4.",
|
|
69
|
+
"type": "string",
|
|
70
|
+
"enum": ["up_to_date", "outdated", "check_failed"]
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
"OutdatedCheckResult": {
|
|
74
|
+
"$comment": "NEW. AC3, AC4. One row of `skillmux outdated` output, for every vault skill carrying a .skillmux-origin sidecar. Skills with no sidecar never produce a row (AC3).",
|
|
75
|
+
"type": "object",
|
|
76
|
+
"properties": {
|
|
77
|
+
"skill_id": { "$ref": "https://klh.app/schemas/skillmux/router-core.json#/$defs/SkillId" },
|
|
78
|
+
"source_url": { "type": "string", "minLength": 1 },
|
|
79
|
+
"recorded_commit": { "$ref": "#/$defs/GitCommitSha" },
|
|
80
|
+
"remote_commit": {
|
|
81
|
+
"oneOf": [{ "$ref": "#/$defs/GitCommitSha" }, { "type": "null" }],
|
|
82
|
+
"description": "Null only when status is check_failed."
|
|
83
|
+
},
|
|
84
|
+
"status": { "$ref": "#/$defs/OutdatedCheckStatus" },
|
|
85
|
+
"reason": {
|
|
86
|
+
"oneOf": [{ "type": "string", "minLength": 1 }, { "type": "null" }],
|
|
87
|
+
"description": "AC4: human-readable failure reason (unreachable repo, deleted branch/ref). Null unless status is check_failed."
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"required": ["skill_id", "source_url", "recorded_commit", "remote_commit", "status", "reason"],
|
|
91
|
+
"additionalProperties": false,
|
|
92
|
+
"allOf": [
|
|
93
|
+
{
|
|
94
|
+
"$comment": "AC4 invariant: remote_commit is present iff the check did not fail; reason is present iff it did.",
|
|
95
|
+
"if": { "properties": { "status": { "const": "check_failed" } }, "required": ["status"] },
|
|
96
|
+
"then": {
|
|
97
|
+
"properties": {
|
|
98
|
+
"remote_commit": { "type": "null" },
|
|
99
|
+
"reason": { "type": "string", "minLength": 1 }
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"else": {
|
|
103
|
+
"properties": {
|
|
104
|
+
"remote_commit": { "$ref": "#/$defs/GitCommitSha" },
|
|
105
|
+
"reason": { "type": "null" }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
]
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
"OutdatedOutput": {
|
|
113
|
+
"$comment": "NEW. AC3, AC4. JSON output of `skillmux outdated --json`. checks_failed drives the process exit code alongside any CLI-level error.",
|
|
114
|
+
"type": "object",
|
|
115
|
+
"description": "JSON output of `skillmux outdated --json`.",
|
|
116
|
+
"properties": {
|
|
117
|
+
"skills": {
|
|
118
|
+
"type": "array",
|
|
119
|
+
"items": { "$ref": "#/$defs/OutdatedCheckResult" }
|
|
120
|
+
},
|
|
121
|
+
"checks_failed": {
|
|
122
|
+
"type": "integer",
|
|
123
|
+
"minimum": 0,
|
|
124
|
+
"description": "AC4: count of skills.status === 'check_failed'."
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
"required": ["skills", "checks_failed"],
|
|
128
|
+
"additionalProperties": false
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
"UpdateSkillStatus": {
|
|
132
|
+
"$comment": "NEW. AC7, AC8, AC9, AC10, AC11. would_update/would_skip_* appear only under --dry-run; updated/skipped_* appear only on a real run. up_to_date appears in both, for an explicitly named skill-id whose remote HEAD already matches its recorded commit.",
|
|
133
|
+
"type": "string",
|
|
134
|
+
"enum": [
|
|
135
|
+
"updated",
|
|
136
|
+
"would_update",
|
|
137
|
+
"up_to_date",
|
|
138
|
+
"skipped_drift",
|
|
139
|
+
"would_skip_drift",
|
|
140
|
+
"skipped_scan_failed",
|
|
141
|
+
"would_skip_scan_failed"
|
|
142
|
+
]
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
"UpdateSkillResult": {
|
|
146
|
+
"$comment": "NEW. AC6, AC7, AC8, AC9, AC10, AC11. One row of `skillmux update` output, per candidate skill (the named skill-id, or every outdated sidecar-carrying skill in batch mode).",
|
|
147
|
+
"type": "object",
|
|
148
|
+
"properties": {
|
|
149
|
+
"skill_id": { "$ref": "https://klh.app/schemas/skillmux/router-core.json#/$defs/SkillId" },
|
|
150
|
+
"old_commit": { "$ref": "#/$defs/GitCommitSha" },
|
|
151
|
+
"new_commit": { "$ref": "#/$defs/GitCommitSha" },
|
|
152
|
+
"content_changed": {
|
|
153
|
+
"type": "boolean",
|
|
154
|
+
"description": "AC9: whether the newly fetched skill's content_hash differs from the currently installed one, independent of whether old_commit === new_commit."
|
|
155
|
+
},
|
|
156
|
+
"status": { "$ref": "#/$defs/UpdateSkillStatus" },
|
|
157
|
+
"findings": {
|
|
158
|
+
"type": "array",
|
|
159
|
+
"items": { "$ref": "#/$defs/ScanFinding" },
|
|
160
|
+
"description": "AC8: present (possibly empty) only when status is skipped_scan_failed or would_skip_scan_failed."
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
"required": ["skill_id", "old_commit", "new_commit", "content_changed", "status"],
|
|
164
|
+
"additionalProperties": false,
|
|
165
|
+
"allOf": [
|
|
166
|
+
{
|
|
167
|
+
"$comment": "findings is only meaningful, and only required, on a scan-gate outcome (AC8).",
|
|
168
|
+
"if": {
|
|
169
|
+
"properties": { "status": { "enum": ["skipped_scan_failed", "would_skip_scan_failed"] } },
|
|
170
|
+
"required": ["status"]
|
|
171
|
+
},
|
|
172
|
+
"then": { "required": ["findings"] },
|
|
173
|
+
"else": { "properties": { "findings": false } }
|
|
174
|
+
}
|
|
175
|
+
]
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
"UpdateOutput": {
|
|
179
|
+
"$comment": "NEW. AC9, AC10, AC11. JSON output of `skillmux update [skill-id] --json`, for both dry-run and real invocations, and for both single-skill and batch runs. An empty skills array is the AC11 no-op case ('nothing matched') and is a success, not an error.",
|
|
180
|
+
"type": "object",
|
|
181
|
+
"description": "JSON output of `skillmux update`.",
|
|
182
|
+
"properties": {
|
|
183
|
+
"dry_run": { "type": "boolean" },
|
|
184
|
+
"skills": {
|
|
185
|
+
"type": "array",
|
|
186
|
+
"items": { "$ref": "#/$defs/UpdateSkillResult" }
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
"required": ["dry_run", "skills"],
|
|
190
|
+
"additionalProperties": false
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Skill provenance + update
|
|
2
|
+
|
|
3
|
+
<!-- plane_project: SMX -->
|
|
4
|
+
<!-- plane_module: skill-provenance-update -->
|
|
5
|
+
|
|
6
|
+
## Goal
|
|
7
|
+
|
|
8
|
+
Give skills installed via `skillmux install <repo>` a recorded origin, and let
|
|
9
|
+
users check whether that origin has moved on and pull the update — closing
|
|
10
|
+
the gap where an installed skill's provenance is forgotten the moment
|
|
11
|
+
`install` finishes.
|
|
12
|
+
|
|
13
|
+
## Acceptance Criteria
|
|
14
|
+
|
|
15
|
+
1. `skillmux install <repo>[/path]` writes a provenance sidecar inside the
|
|
16
|
+
installed skill's directory: schema version, source repo URL, subpath (if
|
|
17
|
+
any), the resolved commit SHA from the temp clone, install timestamp
|
|
18
|
+
(ISO-8601), and a content hash of the installed skill (SKILL.md +
|
|
19
|
+
supporting files) for drift detection. `--force` reinstall overwrites the
|
|
20
|
+
sidecar the same way a fresh install does.
|
|
21
|
+
2. The sidecar uses its own filename (`.skillmux-origin`), distinct from the
|
|
22
|
+
`.skillmux` sync marker — it is never mistaken for a `SkillmuxMarker`, is
|
|
23
|
+
excluded from `scan.ts` content scanning and from `listSupportingFiles`,
|
|
24
|
+
and is skipped by `sync.ts`'s managed-entries bookkeeping.
|
|
25
|
+
3. `skillmux outdated [--json]` lists every installed skill carrying a
|
|
26
|
+
provenance sidecar, checks the source repo's current default-branch HEAD
|
|
27
|
+
via `git ls-remote` (no full clone), and reports each as up-to-date or
|
|
28
|
+
outdated by comparing recorded vs. remote commit SHA. Skills with no
|
|
29
|
+
sidecar are omitted entirely — not listed, not flagged as errors.
|
|
30
|
+
4. A per-skill remote check failure (unreachable repo, deleted branch/ref)
|
|
31
|
+
does not abort the whole command: `outdated` reports that skill's check as
|
|
32
|
+
failed with a reason, keeps checking the rest, and reflects any failures in
|
|
33
|
+
the exit code and JSON output (`checks_failed` count) while still
|
|
34
|
+
returning results for skills that succeeded.
|
|
35
|
+
5. `skillmux update [skill-id] [--yes] [--dry-run] [--force] [--fail-on
|
|
36
|
+
low|medium|high] [--json]`:
|
|
37
|
+
- With a `skill-id`, updates that one skill.
|
|
38
|
+
- Without one, updates every skill carrying a provenance sidecar whose
|
|
39
|
+
remote HEAD differs from its recorded commit (i.e., every skill
|
|
40
|
+
`outdated` would report as outdated).
|
|
41
|
+
6. For each candidate skill, `update` re-fetches from the sidecar's recorded
|
|
42
|
+
repo URL + subpath into a temp clone, exactly as `install` does
|
|
43
|
+
(`resolveRepoSource` inputs are already known from the sidecar, so no repo
|
|
44
|
+
argument is needed).
|
|
45
|
+
7. Before touching any skill's files, `update` hashes that skill's current
|
|
46
|
+
on-disk content and compares it to the hash recorded at last
|
|
47
|
+
install/update. A mismatch (local drift — the user hand-edited the
|
|
48
|
+
installed copy) causes that skill to be refused and reported as skipped,
|
|
49
|
+
unless `--force` is also passed.
|
|
50
|
+
8. `update` runs `validateSkillCandidate` (the same scan `install` runs)
|
|
51
|
+
against each newly fetched skill and applies `--fail-on` gating per skill,
|
|
52
|
+
before any write for that skill. A skill that fails the scan gate is
|
|
53
|
+
skipped and reported, and does not block other skills in a batch.
|
|
54
|
+
9. `update --dry-run` reports, per candidate skill, the old SHA, the new SHA,
|
|
55
|
+
and whether content actually differs — without writing anything. This
|
|
56
|
+
works for both single-skill and batch invocations.
|
|
57
|
+
10. A real (non-dry-run) update requires `--yes` in non-interactive/`--json`
|
|
58
|
+
mode (`confirmIfNeeded`, matching `audit prune`), or an interactive prompt
|
|
59
|
+
otherwise. The prompt/confirmation covers the full resolved set of skills
|
|
60
|
+
that will actually be written (after drift and scan filtering) shown up
|
|
61
|
+
front, matching `init`'s "show every write before confirmation" pattern.
|
|
62
|
+
On confirmation, each skill's directory content is overwritten and its
|
|
63
|
+
sidecar rewritten with the new commit SHA, new content hash, and new
|
|
64
|
+
timestamp.
|
|
65
|
+
11. Running `update <skill-id>` against a skill with no provenance sidecar
|
|
66
|
+
fails clearly (e.g. "no origin recorded — was this skill installed via
|
|
67
|
+
`skillmux install`?"). Running bare `update` with no matching skills
|
|
68
|
+
(nothing outdated, or nothing with provenance) succeeds as a no-op and
|
|
69
|
+
says so.
|
|
70
|
+
12. Neither `outdated` nor `update` touches `skillmux.toml`-pinned
|
|
71
|
+
core/project skills or native sync targets (the `.skillmux` /
|
|
72
|
+
`SkillmuxMarker` machinery in `sync.ts`) — scope is limited to
|
|
73
|
+
vault-local, install-from-git skill directories.
|
|
74
|
+
13. `docs/cli.md` documents `outdated` and `update`; `docs/schema.json`
|
|
75
|
+
documents the `.skillmux-origin` sidecar shape, following how the
|
|
76
|
+
`.skillmux` marker is already documented there.
|
|
77
|
+
|
|
78
|
+
## Scope
|
|
79
|
+
|
|
80
|
+
- **`src/provenance.ts`** (new): `.skillmux-origin` filename constant, a
|
|
81
|
+
`SkillOrigin` interface (`schema_version`, `source_url`, `skill_path?`,
|
|
82
|
+
`commit`, `installed_at`, `content_hash`), read/write/validate helpers
|
|
83
|
+
mirroring `sync.ts`'s marker pattern, and `hashSkillContent(dir)` reusing
|
|
84
|
+
`listSupportingFiles`.
|
|
85
|
+
- **`src/install.ts`**: add `remoteHeadCommit(url, ref?)` via `git
|
|
86
|
+
ls-remote`; capture the resolved commit SHA from the existing temp clone in
|
|
87
|
+
the `cloneToTemp`/`resolveSkillDir` flow.
|
|
88
|
+
- **`src/cli.ts` `runInstall`**: write the provenance sidecar right after
|
|
89
|
+
`installIntoVault` succeeds (both fresh install and `--force` reinstall).
|
|
90
|
+
- **`src/commands/outdated.ts`** (new): scans the vault for skills with
|
|
91
|
+
sidecars, checks each against its remote, following `commands/audit.ts`'s
|
|
92
|
+
shape (arg parsing loop, `emitSuccess`, `--json`).
|
|
93
|
+
- **`src/commands/update.ts`** (new): `runUpdate`, reusing
|
|
94
|
+
`resolveRepoSource`/`cloneToTemp`/`resolveSkillDir`/`validateSkillCandidate`/`installIntoVault`/`confirmIfNeeded`/`emitSuccess`,
|
|
95
|
+
handling both single-skill and batch resolution.
|
|
96
|
+
- Wire `outdated` and `update` into `src/cli.ts`'s command dispatcher and
|
|
97
|
+
usage/help strings.
|
|
98
|
+
- `docs/cli.md` and `docs/schema.json` updates.
|
|
99
|
+
- Tests under `tests/`: provenance read/write/hash round-trip, `outdated`
|
|
100
|
+
remote-check logic (mockable git call) including partial-failure handling,
|
|
101
|
+
`update` drift/force/dry-run/fail-on/yes-gating for both single and batch
|
|
102
|
+
paths.
|
|
103
|
+
|
|
104
|
+
## Out of Scope
|
|
105
|
+
|
|
106
|
+
- Skill versioning/semver, changelogs, or any lifecycle concept beyond a git
|
|
107
|
+
commit SHA.
|
|
108
|
+
- Dependency graphs between skills.
|
|
109
|
+
- Any registry, marketplace, or skill-discovery UI/index.
|
|
110
|
+
- Automatic or scheduled updates — `update` is always a manually invoked,
|
|
111
|
+
consent-gated command; no cron, no update-on-server-start.
|
|
112
|
+
- Deprecation warnings authored by skill maintainers, or any metadata
|
|
113
|
+
contract for upstream skills to signal their own lifecycle state.
|
|
114
|
+
- `skillmux.toml`-pinned core/project skills and the `sync.ts`
|
|
115
|
+
target/local_vault marker machinery.
|
|
116
|
+
- Line-level diffing of SKILL.md content — `update --dry-run` reports
|
|
117
|
+
SHA/hash-level change status, not a textual diff.
|
|
118
|
+
- Resolving non-default branches/tags for update — `update` re-resolves the
|
|
119
|
+
repo's default branch HEAD the same way `install` does today; pinning a
|
|
120
|
+
skill to a specific ref is future work.
|
|
121
|
+
|
|
122
|
+
## Tasks
|
|
123
|
+
|
|
124
|
+
1. Provenance module: sidecar type, read/write/validate, `hashSkillContent`,
|
|
125
|
+
`remoteHeadCommit`.
|
|
126
|
+
2. Wire sidecar write into `install`/`--force` reinstall path.
|
|
127
|
+
3. Exempt `.skillmux-origin` from `scan.ts` and `listSupportingFiles`.
|
|
128
|
+
4. `skillmux outdated`: resolve sidecars, batch remote checks, partial-failure
|
|
129
|
+
handling, `--json`.
|
|
130
|
+
5. `skillmux update`: single + batch resolution, drift check, scan gate,
|
|
131
|
+
dry-run reporting, consent gate, sidecar rewrite on success.
|
|
132
|
+
6. CLI wiring (dispatcher, usage strings, help text).
|
|
133
|
+
7. Docs pass: `docs/cli.md`, `docs/schema.json`.
|
|
134
|
+
8. Tests for all of the above.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Review: Supply-Chain Hardening
|
|
2
|
+
|
|
3
|
+
**Method:** goose's `sdd-review` fan-out hit its 24h token rate limit (not transient — `token limit exceeded (279006/250000, resets every 24h)`), so this review ran manually per the skill's fallback path: five focus passes over `git diff main...HEAD` (327 lines, 9 files), plus the real test suite.
|
|
4
|
+
|
|
5
|
+
**Diff:** `feat/supply-chain-hardening` vs `main`, 4 commits (efd5e76, d35033b, b64981c, 2ec41b2).
|
|
6
|
+
|
|
7
|
+
## Compliance
|
|
8
|
+
|
|
9
|
+
| Criterion | Status | Notes |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| AC1: `allowed_hosts` unset leaves install/update unrestricted | ✅ Done | `tests/install.test.ts` + two CLI-level tests |
|
|
12
|
+
| AC2: install rejects disallowed host before fetch | ✅ Done | `tests/cli.test.ts` "AC2" |
|
|
13
|
+
| AC3: install succeeds for allowed host | ✅ Done | Pure-function test proves the check passes; CLI-level test proves it isn't blocked and proceeds to the actual fetch attempt (a real successful clone isn't testable in this sandbox — no outbound network) |
|
|
14
|
+
| AC4: update applies same check against recorded origin | ✅ Done | `tests/cli.test.ts` "AC4" |
|
|
15
|
+
| AC5: host matching is exact-hostname, case-insensitive | ✅ Done | `tests/install.test.ts` "AC5" |
|
|
16
|
+
|
|
17
|
+
5/5 met.
|
|
18
|
+
|
|
19
|
+
## Security
|
|
20
|
+
|
|
21
|
+
- Reviewed for common egress-check bypass classes: subdomain-suffix (`github.com.evil.com`), userinfo tricks (`GITHUB.COM@evil.com`), scp-regex misfire against a real `https://user@host:port/...` URL. None found — exact-match comparison plus WHATWG `URL.hostname` parsing (which correctly separates userinfo from host) close these off. `assertHostAllowed` runs after upstream validation (`isGitUrl`/`readSkillOrigin`'s `validateOrigin`), so the malformed-URL `new URL()`-throws case can't be reached with attacker-controlled input.
|
|
22
|
+
- **Finding (ASK, medium confidence):** `skillmux update` (batch mode, no explicit skill-id) and `skillmux outdated` both call `checkOutdated` → `remoteHeadCommit` (`git ls-remote`) against every installed skill's recorded origin host, unconditionally — before `buildPlan`'s `assertHostAllowed` ever runs. So a disallowed host still receives one real network probe (`ls-remote`, not a content fetch) in batch mode, even though the actual content re-fetch (`cloneToTemp`) is correctly blocked. This matches the spec as written — `spec.md`'s Scope names only `resolveRepoSource`/`cloneToTemp` enforcement, and "before re-fetching" was read as "before the content fetch," not "before any network call." Whether to also gate `remoteHeadCommit` is a scope call, not a bug in what was built. Left as-is per spec; flagging for a decision now rather than silently shipping a narrower guarantee than "egress allowlist" might imply. `skillmux install` has no equivalent gap — its only network call is the fetch itself, already gated.
|
|
23
|
+
|
|
24
|
+
## Correctness
|
|
25
|
+
|
|
26
|
+
No issues found. `extractHost`'s scp-like regex only matches genuine `user@host:path` shapes (verified it doesn't misfire on `https://user@host:port/path`). Reusing the single early `loadConfig()` call in `runInstall` for `vaultPath` later is safe — nothing between the two original call sites depended on a fresh reload.
|
|
27
|
+
|
|
28
|
+
**Nitpick:** `extractHost` doesn't special-case IPv6 literal hosts (`https://[::1]/x.git`). Not a security issue (worst case is a slightly odd host string in the allowlist comparison), and out of scope for this slice's "no glob support" minimal design. Not worth fixing now.
|
|
29
|
+
|
|
30
|
+
## Performance
|
|
31
|
+
|
|
32
|
+
No concerns. `assertHostAllowed` is an O(allowed_hosts.length) array scan over a config-sized list, called once per install/update candidate.
|
|
33
|
+
|
|
34
|
+
## Tests (static + runtime)
|
|
35
|
+
|
|
36
|
+
Coverage is solid: pure-function tests for `assertHostAllowed` (unset, blocked, allowed, case-insensitive, scp-like, file:// exemption) plus CLI-level integration tests proving the wiring into both `install` and `update`.
|
|
37
|
+
|
|
38
|
+
**Nitpick:** no test exercises the *batch* `skillmux update` path (no explicit skill-id) reaching `assertHostAllowed` — only the explicit single-skill-id path is covered. Since `buildPlan` is the same function for both, divergence risk is low; not blocking.
|
|
39
|
+
|
|
40
|
+
**Test execution:** `bun test` — 774 pass, 0 fail, 1981 assertions.
|
|
41
|
+
|
|
42
|
+
**Build check:** SKIPPED — no build_command supplied.
|
|
43
|
+
|
|
44
|
+
## Needs Decision
|
|
45
|
+
|
|
46
|
+
1. ~~Should `checkOutdated`/`remoteHeadCommit`... also be gated?~~ **Resolved:** closed. `checkOutdated` now calls `assertHostAllowed` before `remoteHeadCommit`, threaded through both `runOutdated` and batch `runUpdate` (`resolveCandidateOrigins`). A disallowed host now reports `check_failed` with the allowlist error as `reason` instead of attempting `git ls-remote`. Covered by a new `outdated`-command-level test; batch `update`'s own path isn't independently tested since `checkOutdated` already filters `check_failed` out of its output before batch `update` ever sees it — a test at that layer can't distinguish "blocked by the allowlist" from "the host was simply unreachable," so it would pass without the fix (confirmed, then removed as a TDD false-positive). Coverage rests on the shared `checkOutdated` function's test plus the mechanical, low-risk parameter thread-through.
|
|
47
|
+
|
|
48
|
+
## Security escalation
|
|
49
|
+
|
|
50
|
+
Not recommended as a separate `/security-review` pass — the diff is a narrow, additive host-allowlist check with no auth/session/secrets/deserialization/process-execution surface, and the fan-out's security pass (run manually above) already covered the relevant bypass classes for this shape of change.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<!-- source: docs/sdd/security-hardening-phase/think.md -->
|
|
2
|
+
<!-- plane_project: SMX -->
|
|
3
|
+
<!-- plane_module: supply-chain-hardening -->
|
|
4
|
+
|
|
5
|
+
# Spec: Supply-Chain Hardening
|
|
6
|
+
|
|
7
|
+
Slice 1 of 3 from the security-hardening-phase (see `docs/sdd/security-hardening-phase/think.md`).
|
|
8
|
+
|
|
9
|
+
**Correction (discovered during `/sdd-tdd`, before any test was written):** the original AC5-AC8 below (hash-pinned scan verdicts / quarantine) were dropped after finding they're already fully shipped and tested — `src/provenance.ts` (`hashSkillContent`, `content_hash` on the `.skillmux-origin` sidecar) plus `src/commands/update.ts`'s `buildPlan` (local-drift check before fetch, unconditional scan on every fetched update before write). See `tests/cli.test.ts`'s `skillmux update CLI` suite. `think.md`'s landscape table marked this item "Not started" — that was wrong; it shipped as part of the earlier skill-provenance-update feature. Remaining scope is egress only.
|
|
10
|
+
|
|
11
|
+
## Goal
|
|
12
|
+
|
|
13
|
+
Enforce an install/update-time egress allowlist so `skillmux install`/`update` only fetch skill content from git hosts the operator has explicitly trusted.
|
|
14
|
+
|
|
15
|
+
## Acceptance Criteria
|
|
16
|
+
|
|
17
|
+
1. With `[egress] allowed_hosts` unset (default), `skillmux install`/`update` behavior is unchanged — no host is blocked.
|
|
18
|
+
2. With `allowed_hosts` set, `install <source>` from a host not in the list is rejected before any network fetch, with an error naming the disallowed host.
|
|
19
|
+
3. With `allowed_hosts` set, `install <source>` from a listed host succeeds unchanged.
|
|
20
|
+
4. `skillmux update` applies the same host check against the skill's recorded origin before re-fetching.
|
|
21
|
+
5. Host matching is exact-hostname, case-insensitive — no globbing in this slice.
|
|
22
|
+
|
|
23
|
+
## Scope
|
|
24
|
+
|
|
25
|
+
- `[egress] allowed_hosts: string[]` config (new `egress` section, unset = unrestricted).
|
|
26
|
+
- Enforcement in `resolveRepoSource` (`src/install.ts`, shared by install and update call sites).
|
|
27
|
+
- **Extended post-review:** also enforced in `checkOutdated`'s `git ls-remote` probe (`src/commands/outdated.ts`), used by both `skillmux outdated` and batch `skillmux update`. `/sdd-review` found the original scope left this network call unguarded even though the actual content fetch was correctly blocked — closed per user decision.
|
|
28
|
+
|
|
29
|
+
## Out of Scope
|
|
30
|
+
|
|
31
|
+
- Remote-inference egress (embedding/reranker calls) — that's `runtime-resource-hardening` (slice 2).
|
|
32
|
+
- Hash-pinned scan verdicts / quarantine — already shipped, see Correction above.
|
|
33
|
+
- Any staging/approval workflow, or new CLI subcommands/flags beyond the existing install/update/`--yes` flow.
|
|
34
|
+
- Signer verification, SBOM/provenance, security posture visibility command, privacy reporting/purge — deferred phase-wide per `think.md`.
|
|
35
|
+
- Glob/wildcard host patterns in `allowed_hosts` — exact-match list only for this slice.
|
|
36
|
+
|
|
37
|
+
## Tasks
|
|
38
|
+
|
|
39
|
+
1. Config schema: add `[egress] allowed_hosts` to `config.ts`.
|
|
40
|
+
2. Enforcement: host-check in `resolveRepoSource`, wired into `install.ts` and `update.ts` call sites.
|
|
41
|
+
3. Docs: document the `[egress]` config key. (Landed in `docs/skill-management.md` and `docs/configuration.md` — that's where install/update and the `[server]`-style config reference actually live, not `cli.md`/`deployment.md` as originally guessed.)
|
|
42
|
+
4. Tests: config validation, allow/block enforcement, backward compatibility (unset = unrestricted).
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
## Goal
|
|
2
|
+
|
|
3
|
+
Safely rehome every managed directory for one active target after its vault
|
|
4
|
+
checkout moves, without changing skill links or unrelated targets.
|
|
5
|
+
|
|
6
|
+
## Acceptance Criteria
|
|
7
|
+
|
|
8
|
+
- [ ] `skillmux target rehome <name> --yes` updates the matching target
|
|
9
|
+
markers' `vault_path` to the configured vault path.
|
|
10
|
+
- [ ] The command updates both the global target directory and every existing
|
|
11
|
+
project pin directory materialized for that target on the current host.
|
|
12
|
+
- [ ] The command rejects a marker whose `role`, `target`, or managed symlink
|
|
13
|
+
destinations do not prove it belongs to the configured vault, and leaves all
|
|
14
|
+
markers unchanged on failure.
|
|
15
|
+
- [ ] `--dry-run` reports the marker paths that would change and writes
|
|
16
|
+
nothing.
|
|
17
|
+
- [ ] Other targets and their markers are unchanged.
|
|
18
|
+
|
|
19
|
+
## Scope
|
|
20
|
+
|
|
21
|
+
Add a `target rehome` CLI subcommand, its guarded marker migration logic,
|
|
22
|
+
tests, CLI help, and user documentation.
|
|
23
|
+
|
|
24
|
+
## Out of Scope
|
|
25
|
+
|
|
26
|
+
Changing `skillmux.toml`, recreating target directories, adding/removing skill
|
|
27
|
+
links, migrating full-vault surfaces, or modifying a non-matching target.
|
|
28
|
+
|
|
29
|
+
## Tasks
|
|
30
|
+
|
|
31
|
+
1. Add a failing CLI test for global and project target marker migration.
|
|
32
|
+
2. Implement preflight validation and atomic marker rewrites.
|
|
33
|
+
3. Add dry-run, rejection, and preservation coverage plus documentation.
|
package/docs/skill-management.md
CHANGED
|
@@ -171,7 +171,7 @@ skillmux core unpin csv-formatter --yes
|
|
|
171
171
|
|
|
172
172
|
One command can change several skill IDs. Skillmux validates the complete
|
|
173
173
|
change before writing, so a conflict prevents the whole operation. Core stays
|
|
174
|
-
capped at 25 skills.
|
|
174
|
+
capped at 25 skills by default, which `[core].limit` overrides.
|
|
175
175
|
|
|
176
176
|
Run `skillmux sync` after a direct pin or unpin command.
|
|
177
177
|
|
package/docs/troubleshooting.md
CHANGED
|
@@ -29,7 +29,7 @@ downloads, contexts, evaluation, project/target/local-vault
|
|
|
29
29
|
management, or `config init`/`config set`, install and use the host CLI:
|
|
30
30
|
|
|
31
31
|
```sh
|
|
32
|
-
|
|
32
|
+
npm install -g @klhapp/skillmux
|
|
33
33
|
skillmux <the-command Docker rejected>
|
|
34
34
|
```
|
|
35
35
|
|
|
@@ -50,10 +50,25 @@ uname -m
|
|
|
50
50
|
sha256sum skillmux-linux-amd64
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
Download the file again with the [standalone installation procedure](getting-started.md#
|
|
53
|
+
Download the file again with the [standalone installation procedure](getting-started.md#standalone-executable).
|
|
54
54
|
For GitHub build-provenance verification instead of the published SHA-256
|
|
55
55
|
digest, use the [GitHub CLI attestation procedure](getting-started.md#install-with-github-cli-attestation).
|
|
56
56
|
|
|
57
|
+
### `no native executable for <platform>-<arch>`
|
|
58
|
+
|
|
59
|
+
The launcher could not find the platform package that carries the executable.
|
|
60
|
+
This usually means the install skipped optional dependencies, which is what
|
|
61
|
+
`npm install --no-optional` and `npm ci --omit=optional` do. Reinstall without
|
|
62
|
+
that flag:
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
npm install -g @klhapp/skillmux
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
If the platform genuinely has no published build, install the
|
|
69
|
+
[standalone executable](getting-started.md#standalone-executable) for it, or
|
|
70
|
+
point `SKILLMUX_BINARY` at an executable you already have.
|
|
71
|
+
|
|
57
72
|
### `skillmux: command not found` after installation
|
|
58
73
|
|
|
59
74
|
The standalone installer defaults to `~/.local/bin`. Add it to the shell's
|
|
@@ -167,6 +182,7 @@ step depends on the installation:
|
|
|
167
182
|
| Installation | Expected action |
|
|
168
183
|
| --- | --- |
|
|
169
184
|
| Skillmux CLI with local inference | Download the local model and rebuild the index |
|
|
185
|
+
| Skillmux CLI on Intel macOS | Expected: no ONNX Runtime exists for that platform. Configure remote embeddings |
|
|
170
186
|
| Skillmux server (full image) | Inspect `doctor`, `/health/ready`, or `skill_router_deployment_info` for `image_variant=full`; do not infer it from the tag |
|
|
171
187
|
| Skillmux server (slim image) | Configure remote embeddings or keep lexical fallback |
|
|
172
188
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@klhapp/skillmux",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "Skill management and retrieval for AI agents: sync native skills across clients and route the long tail over MCP",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
},
|
|
16
16
|
"homepage": "https://github.com/klhq/skillmux#readme",
|
|
17
17
|
"bin": {
|
|
18
|
-
"skillmux": "./
|
|
18
|
+
"skillmux": "./bin/skillmux.js"
|
|
19
19
|
},
|
|
20
20
|
"publishConfig": {
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
|
-
"
|
|
24
|
+
"bin",
|
|
25
25
|
"docs",
|
|
26
26
|
"README.md",
|
|
27
27
|
"LICENSE",
|
|
@@ -36,18 +36,25 @@
|
|
|
36
36
|
"eval": "bun run src/cli.ts eval",
|
|
37
37
|
"doctor": "bun run src/cli.ts doctor",
|
|
38
38
|
"models:download": "bun run src/cli.ts models download",
|
|
39
|
-
"build": "bun
|
|
39
|
+
"build": "bun run scripts/build-binaries.ts host",
|
|
40
|
+
"build:binaries": "bun run scripts/build-binaries.ts",
|
|
41
|
+
"build:npm-packages": "bun run scripts/package-npm-binaries.ts"
|
|
40
42
|
},
|
|
41
43
|
"devDependencies": {
|
|
42
|
-
"bun-types": "^1.3.14"
|
|
43
|
-
},
|
|
44
|
-
"dependencies": {
|
|
45
44
|
"@huggingface/transformers": "^4.2.0",
|
|
46
45
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
46
|
+
"bun-types": "^1.3.14",
|
|
47
47
|
"zod": "^4.4.3"
|
|
48
48
|
},
|
|
49
49
|
"trustedDependencies": [
|
|
50
50
|
"onnxruntime-node",
|
|
51
51
|
"protobufjs"
|
|
52
|
-
]
|
|
52
|
+
],
|
|
53
|
+
"optionalDependencies": {
|
|
54
|
+
"@klhapp/skillmux-darwin-arm64": "1.13.0",
|
|
55
|
+
"@klhapp/skillmux-darwin-x64": "1.13.0",
|
|
56
|
+
"@klhapp/skillmux-linux-arm64": "1.13.0",
|
|
57
|
+
"@klhapp/skillmux-linux-x64": "1.13.0",
|
|
58
|
+
"@klhapp/skillmux-win32-x64": "1.13.0"
|
|
59
|
+
}
|
|
53
60
|
}
|