@skill-map/spec 0.6.0 → 1.0.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 +343 -0
- package/architecture.md +56 -6
- package/cli-contract.md +7 -3
- package/conformance/coverage.md +5 -4
- package/db-schema.md +9 -1
- package/index.json +14 -14
- package/interfaces/security-scanner.md +1 -1
- package/package.json +1 -1
- package/schemas/extensions/action.schema.json +8 -10
- package/schemas/extensions/adapter.schema.json +1 -1
- package/schemas/extensions/audit.schema.json +3 -3
- package/schemas/extensions/detector.schema.json +7 -1
- package/schemas/extensions/renderer.schema.json +1 -1
- package/schemas/extensions/rule.schema.json +7 -1
- package/schemas/project-config.schema.json +13 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,348 @@
|
|
|
1
1
|
# Spec changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- d730094: Spec — Execution modes (deterministic / probabilistic) lifted to a first-class architectural property
|
|
8
|
+
|
|
9
|
+
Frames a meta-property of skill-map that was previously implicit and scattered:
|
|
10
|
+
**every analytical extension is one of two modes** — `deterministic` (pure code,
|
|
11
|
+
runs in scan-time pipelines) or `probabilistic` (invokes an LLM through
|
|
12
|
+
`RunnerPort`, runs only as queued jobs). The dual-mode capability now spans four
|
|
13
|
+
of the six extension kinds; Adapter and Renderer remain locked to deterministic
|
|
14
|
+
because they sit at the system boundaries (filesystem and graph-to-string) where
|
|
15
|
+
non-determinism would break boot reproducibility and snapshot diffing.
|
|
16
|
+
|
|
17
|
+
**Spec changes:**
|
|
18
|
+
|
|
19
|
+
- `architecture.md` — new top-level section **§Execution modes** before
|
|
20
|
+
§Extension kinds. Defines the two modes, the per-kind capability matrix
|
|
21
|
+
(Detector / Rule / Action dual-mode by manifest declaration; Audit dual-mode
|
|
22
|
+
with mode **derived** from `composes[]`; Adapter / Renderer deterministic-only),
|
|
23
|
+
the runtime separation (`deterministic` runs in `sm scan` / `sm check`;
|
|
24
|
+
`probabilistic` runs only via `sm job submit <kind>:<id>`), and the
|
|
25
|
+
`RunnerPort` injection contract for probabilistic extensions.
|
|
26
|
+
- `architecture.md` §Extension kinds — table updated: each row clarifies the
|
|
27
|
+
mode posture (Adapter / Renderer marked deterministic-only; Detector / Rule /
|
|
28
|
+
Action marked dual-mode; Audit marked derived-mode).
|
|
29
|
+
- `architecture.md` §Stability — new clause: execution modes and the per-kind
|
|
30
|
+
capability matrix are stable as of v1.0.0; adding a third mode, changing
|
|
31
|
+
which kinds are dual-mode, or changing the audit's derivation rule is a major
|
|
32
|
+
bump.
|
|
33
|
+
|
|
34
|
+
**Schema changes:**
|
|
35
|
+
|
|
36
|
+
- `schemas/extensions/detector.schema.json`:
|
|
37
|
+
- New optional `mode` field (`deterministic` | `probabilistic`, default
|
|
38
|
+
`deterministic`). Omitting is equivalent to deterministic — keeps existing
|
|
39
|
+
detectors valid without an update.
|
|
40
|
+
- Description updated to spell out the dual-mode contract.
|
|
41
|
+
- `schemas/extensions/rule.schema.json`:
|
|
42
|
+
- Same shape: new optional `mode` field with default `deterministic`.
|
|
43
|
+
- Description rewritten — the previous "Rules MUST be deterministic" claim
|
|
44
|
+
moved into the deterministic-mode contract; probabilistic rules are now
|
|
45
|
+
explicitly allowed and run only as queued jobs.
|
|
46
|
+
- `schemas/extensions/action.schema.json`:
|
|
47
|
+
- **Breaking** — `mode` enum renamed: `local` → `deterministic`,
|
|
48
|
+
`invocation-template` → `probabilistic`. Pre-1.0; no consumers depend on
|
|
49
|
+
the old values (no third-party action plugins shipped). Description, the
|
|
50
|
+
two `if/then` branches, and the `expectedDurationSeconds` /
|
|
51
|
+
`promptTemplateRef` field descriptions updated accordingly.
|
|
52
|
+
- **Bug fix** — the schema previously declared `allOf` twice at the root
|
|
53
|
+
(lines 6–8 and 71–80); the second silently overrode the first, dropping
|
|
54
|
+
`$ref: base.schema.json`. Both blocks are now merged into a single `allOf`
|
|
55
|
+
so the action schema actually composes the base shape.
|
|
56
|
+
- `schemas/extensions/audit.schema.json`:
|
|
57
|
+
- Description rewritten — the "deterministic workflow" claim is replaced by
|
|
58
|
+
the **derived-mode** rule: the audit's effective mode is computed from
|
|
59
|
+
`composes[]` at load time. If every composed primitive is deterministic,
|
|
60
|
+
the audit is deterministic; if any is probabilistic, the audit is
|
|
61
|
+
probabilistic and dispatches as a job. Declaring `mode` directly is a
|
|
62
|
+
load-time error.
|
|
63
|
+
- `composes[]` description updated to mention that each primitive's mode
|
|
64
|
+
participates in derivation; dangling references stay a load-time error.
|
|
65
|
+
- `reportSchemaRef` description updated: probabilistic audits MUST extend
|
|
66
|
+
`report-base.schema.json` (carries `safety` / `confidence`); deterministic
|
|
67
|
+
audits MAY extend it but are not required to.
|
|
68
|
+
- `schemas/extensions/adapter.schema.json`:
|
|
69
|
+
- Description updated to state explicitly that adapters are deterministic-only
|
|
70
|
+
and that `mode` MUST NOT appear. Recommendation for users who want
|
|
71
|
+
LLM-assisted classification: write a probabilistic Detector that emits
|
|
72
|
+
classification hints as `Link[]`.
|
|
73
|
+
- `schemas/extensions/renderer.schema.json`:
|
|
74
|
+
- Description updated to state that renderers are deterministic-only and
|
|
75
|
+
that `mode` MUST NOT appear. Probabilistic narrators of the graph belong
|
|
76
|
+
in jobs and emit Findings, not in renderer manifests.
|
|
77
|
+
|
|
78
|
+
**Why major (despite pre-1.0 minor norm):**
|
|
79
|
+
|
|
80
|
+
Renaming the `Action.mode` enum (`local` → `deterministic`,
|
|
81
|
+
`invocation-template` → `probabilistic`) is breaking by definition. No
|
|
82
|
+
third-party Actions exist yet, but the rename touches the canonical surface and
|
|
83
|
+
deserves the bump. New optional fields on Detector / Rule and the new derived-
|
|
84
|
+
mode contract on Audit are additive and would have been minor on their own.
|
|
85
|
+
|
|
86
|
+
**Implementation work intentionally NOT included here:**
|
|
87
|
+
|
|
88
|
+
- `src/extensions/built-ins.ts` and the per-extension TS files keep working
|
|
89
|
+
unchanged because the new `mode` is optional with `deterministic` default.
|
|
90
|
+
Explicitly threading `mode: 'deterministic'` through every built-in is a
|
|
91
|
+
follow-up.
|
|
92
|
+
- `RunnerPort` injection through `ctx.runner` for probabilistic extensions is
|
|
93
|
+
spec'd here; the actual context plumbing lands with the first probabilistic
|
|
94
|
+
extension (Step 10 — first summarizer). `MockRunner` continues to satisfy
|
|
95
|
+
tests until then.
|
|
96
|
+
- Conformance case `extension-mode-derivation` (audit composes mixed
|
|
97
|
+
primitives → derives `probabilistic`) is mentioned in `architecture.md` and
|
|
98
|
+
pending under `spec/conformance/coverage.md` for the next release.
|
|
99
|
+
- ROADMAP.md rephrase of Steps 10–11 (from "summarizers" to "wave 2:
|
|
100
|
+
probabilistic extensions") and a positioning section in `README.md` follow
|
|
101
|
+
in separate commits to keep this changeset spec-only.
|
|
102
|
+
|
|
103
|
+
### Minor Changes
|
|
104
|
+
|
|
105
|
+
- a73f3f4: Step 7.1 — File watcher (`sm watch` / `sm scan --watch`)
|
|
106
|
+
|
|
107
|
+
Long-running watcher that subscribes to the scan roots, debounces
|
|
108
|
+
filesystem events, and triggers an incremental scan per batch. Reuses
|
|
109
|
+
the existing `runScanWithRenames` pipeline, the `IIgnoreFilter` chain
|
|
110
|
+
(`.skill-mapignore` + `config.ignore` + bundled defaults), and the
|
|
111
|
+
`scan.*` non-job events from `job-events.md` — one ScanResult per
|
|
112
|
+
batch, emitted as ndjson under `--json`.
|
|
113
|
+
|
|
114
|
+
**Spec changes (minor)**:
|
|
115
|
+
|
|
116
|
+
- `spec/schemas/project-config.schema.json` — new `scan.watch` object
|
|
117
|
+
with a single key `debounceMs` (integer ≥ 0, default 300). Groups
|
|
118
|
+
bursts of filesystem events (editor saves, branch switches, npm
|
|
119
|
+
installs) into a single scan pass. Set to 0 to disable debouncing.
|
|
120
|
+
- `spec/cli-contract.md` §Scan — documents `sm watch [roots...]` as
|
|
121
|
+
the primary verb and `sm scan --watch` as the alias. Watcher
|
|
122
|
+
respects the same ignore chain as one-shot scans, emits one
|
|
123
|
+
ScanResult per batch (ndjson under `--json`), closes cleanly on
|
|
124
|
+
`SIGINT` / `SIGTERM`, exits 0 on clean shutdown. Exit-code rule
|
|
125
|
+
carved out for the watcher: per-batch error issues do not flip the
|
|
126
|
+
exit code (the loop keeps running); operational errors still exit 2.
|
|
127
|
+
|
|
128
|
+
No new events. No new ports. The watcher is implementation-defined
|
|
129
|
+
inside the kernel package; a future `WatchPort` can be added when /
|
|
130
|
+
if a non-Node implementation needs to swap the chokidar wrapper.
|
|
131
|
+
|
|
132
|
+
**Runtime changes (minor — new verb + new config key)**:
|
|
133
|
+
|
|
134
|
+
- `chokidar@5.0.0` pinned in `src/package.json` (single new runtime
|
|
135
|
+
dependency, MIT). Chokidar v5 requires Node ≥ 20.19; the project
|
|
136
|
+
already pins `engines.node: ">=24.0"` so this is a no-op for
|
|
137
|
+
consumers. Brings in `readdirp@5` as a transitive.
|
|
138
|
+
- `src/kernel/scan/watcher.ts` — `IFsWatcher` interface + concrete
|
|
139
|
+
`ChokidarWatcher` wrapping `chokidar.watch()` with the existing
|
|
140
|
+
`IIgnoreFilter` plumbed through, debouncer, batch coalescing,
|
|
141
|
+
and explicit `stop()` for clean teardown.
|
|
142
|
+
- `src/cli/commands/watch.ts` — new `WatchCommand`. `sm scan
|
|
143
|
+
--watch` delegates to the same code path so the two surfaces are
|
|
144
|
+
byte-aligned (no parallel implementations).
|
|
145
|
+
- `src/config/defaults.json` — new `scan.watch.debounceMs: 300`
|
|
146
|
+
default.
|
|
147
|
+
|
|
148
|
+
**Why minor (not patch)**: new public verb (`sm watch`), new public
|
|
149
|
+
config key (`scan.watch.debounceMs`), and a new flag on an existing
|
|
150
|
+
verb (`sm scan --watch`). All three are surface additions, not bug
|
|
151
|
+
fixes — minor under both the spec and the runtime semver policies.
|
|
152
|
+
No breaking changes; existing `sm scan` without `--watch` is
|
|
153
|
+
byte-identical to before.
|
|
154
|
+
|
|
155
|
+
**Roadmap**: Step 7 — Robustness, sub-step 7.1 (chokidar watcher).
|
|
156
|
+
Trigger normalization is implicit-already-landed (cabled into every
|
|
157
|
+
detector at Steps 3–4 with full unit tests in
|
|
158
|
+
`src/kernel/trigger-normalize.test.ts`); we do not write a sub-step
|
|
159
|
+
for it. Next sub-steps: 7.2 detector conflict resolution, 7.3 `sm
|
|
160
|
+
job prune` + retention enforcement.
|
|
161
|
+
|
|
162
|
+
### Patch Changes
|
|
163
|
+
|
|
164
|
+
- a73f3f4: Step 7.2 — Detector conflict resolution
|
|
165
|
+
|
|
166
|
+
Two pieces:
|
|
167
|
+
|
|
168
|
+
1. **New built-in rule `link-conflict`** (`src/extensions/rules/link-conflict/`).
|
|
169
|
+
Surfaces detector disagreement. Groups links by `(source, target)` and
|
|
170
|
+
emits one `warn` Issue per pair where the set of distinct `kind` values
|
|
171
|
+
has size ≥ 2. Agreement (single kind across multiple detectors) is
|
|
172
|
+
silent — by design, to avoid massive noise on real graphs.
|
|
173
|
+
Issue payload (`data`) carries `{ source, target, variants }` where
|
|
174
|
+
each `variant` is `{ kind, sources: detectorId[], confidence }`. Variant
|
|
175
|
+
sources are deduped + sorted; confidence is the highest across rows
|
|
176
|
+
of the same kind (`high` > `medium` > `low`).
|
|
177
|
+
|
|
178
|
+
This is the kernel piece of Decision #90 read-time "consumers that
|
|
179
|
+
need uniqueness aggregate at read time" — the rule is one such
|
|
180
|
+
consumer, on the alarming side. Storage stays untouched (one row
|
|
181
|
+
per detector, no merge, no dedup). Severity is `warn`, not `error`:
|
|
182
|
+
the rule cannot pick which kind is correct, so per `cli-contract.md`
|
|
183
|
+
§Exit codes the verb stays exit 0.
|
|
184
|
+
|
|
185
|
+
2. **`sm show` pretty link aggregation** (`src/cli/commands/show.ts`).
|
|
186
|
+
The human renderer now groups `linksOut` / `linksIn` by `(endpoint,
|
|
187
|
+
kind, normalizedTrigger)` and prints one row per group with the
|
|
188
|
+
union of detector ids in a `sources:` field. The section header
|
|
189
|
+
reports both the raw row count and the unique-after-grouping count
|
|
190
|
+
(`Links out (12, 9 unique)`). When N > 1 detector emits the same
|
|
191
|
+
logical link, the row also gets a `(×N)` suffix.
|
|
192
|
+
|
|
193
|
+
`--json` output is byte-identical to before — raw rows, no merge.
|
|
194
|
+
Storage is byte-identical to before. The grouping is purely a
|
|
195
|
+
read-time presentation choice for human eyes.
|
|
196
|
+
|
|
197
|
+
**Spec changes (patch)**:
|
|
198
|
+
|
|
199
|
+
- `spec/cli-contract.md` §Browse — `sm show` row clarifies that pretty
|
|
200
|
+
output groups identical-shape links and that `--json` emits raw rows.
|
|
201
|
+
Patch (not minor) because the JSON contract is unchanged; the human
|
|
202
|
+
output format is non-normative anyway.
|
|
203
|
+
|
|
204
|
+
**Runtime changes (minor — new rule + new presentation)**:
|
|
205
|
+
|
|
206
|
+
- New rule `link-conflict` registered in `src/extensions/built-ins.ts`.
|
|
207
|
+
- `sm show` pretty output groups links + reports unique counts.
|
|
208
|
+
|
|
209
|
+
**UI inspector aggregation deferred to Step 13**: the current Flavor A
|
|
210
|
+
inspector renders the `Relations` card from `node.frontmatter.metadata.{
|
|
211
|
+
related, requires, supersedes, provides, conflictsWith}` directly — it
|
|
212
|
+
does NOT consume `linksOut` / `linksIn` rows from `scan_links`. There
|
|
213
|
+
is no link table to aggregate today. When Step 13's Flavor B lands (Hono
|
|
214
|
+
BFF + WS + full link panel from scan), the aggregation logic from
|
|
215
|
+
`src/cli/commands/show.ts` will need to be ported.
|
|
216
|
+
|
|
217
|
+
**Roadmap**: Step 7 — Robustness, sub-step 7.2 (detector conflict
|
|
218
|
+
resolution). Closes one of the three remaining frentes; 7.3 (`sm job
|
|
219
|
+
prune` + retention) still pending. Decision #90 unchanged: storage
|
|
220
|
+
keeps raw per-detector rows. The `related` vs LLM-amplification
|
|
221
|
+
discussion is documented in `.tmp/skill-map-related-test/` (status
|
|
222
|
+
quo retained — fields stay opt-in under `metadata.*`; revisit if
|
|
223
|
+
real-world amplification appears).
|
|
224
|
+
|
|
225
|
+
**Tests**: 327 → 335 (+8 new for the rule, no regressions).
|
|
226
|
+
|
|
227
|
+
## 0.6.1
|
|
228
|
+
|
|
229
|
+
### Patch Changes
|
|
230
|
+
|
|
231
|
+
- f41dbad: Step 6.1 — Spec migration: rename the canonical config file from
|
|
232
|
+
`.skill-map.json` (single project-root file) to `.skill-map/settings.json`
|
|
233
|
+
inside the `.skill-map/` scope folder, with a sibling `.skill-map/settings.local.json`
|
|
234
|
+
partner for machine-specific overrides. Aligns the spec with the layered
|
|
235
|
+
config hierarchy described in the roadmap (library defaults → user → user-local
|
|
236
|
+
→ project → project-local → env / flags).
|
|
237
|
+
|
|
238
|
+
**Spec change (breaking, minor under pre-1.0 versioning policy)**:
|
|
239
|
+
|
|
240
|
+
- `spec/schemas/project-config.schema.json` description updated to point at
|
|
241
|
+
`.skill-map/settings.json` and explicitly mention the `.local.json` partner
|
|
242
|
+
and the layered-merge contract. The schema _shape_ (keys, types, validation
|
|
243
|
+
rules) is unchanged — only the on-disk filename moves. Consumers that read
|
|
244
|
+
values without caring about the source path are unaffected; consumers that
|
|
245
|
+
hard-code the filename must update.
|
|
246
|
+
- `spec/db-schema.md` §Scopes: `history.share: true` reference updated to
|
|
247
|
+
`.skill-map/settings.json`.
|
|
248
|
+
- `spec/conformance/coverage.md` row #6 description updated to reference the
|
|
249
|
+
new path and the optional `settings.local.json` overlay.
|
|
250
|
+
|
|
251
|
+
**Why minor (not major) at pre-1.0**: per `spec/versioning.md` §Pre-1.0,
|
|
252
|
+
breaking changes ARE allowed in minor bumps while the spec is `0.y.z`. The
|
|
253
|
+
shape of the data is unchanged; only the file name on disk moves.
|
|
254
|
+
|
|
255
|
+
**No backward-compat shim**: there is no real implementation of the loader
|
|
256
|
+
yet (lands in 6.2), so no live consumer reads `.skill-map.json` today. The
|
|
257
|
+
only known prior reference is the demo `mock-collection/.claude/commands/init*.md`
|
|
258
|
+
fixture, which is updated together with `sm init` in 6.5.
|
|
259
|
+
|
|
260
|
+
**Runtime change**: none in 6.1 — pure spec edit. The matching loader,
|
|
261
|
+
`sm init`, and `sm config` verbs land in subsequent sub-steps.
|
|
262
|
+
|
|
263
|
+
**Roadmap update**: `ROADMAP.md` §Configuration "Spec migration" call-out
|
|
264
|
+
flipped from "pending" to "landed Step 6.1, 2026-04-27".
|
|
265
|
+
|
|
266
|
+
Test count: unchanged (213 → 213 — spec-only edit).
|
|
267
|
+
|
|
268
|
+
- 8a4667f: Step 6.6 — `sm plugins enable / disable` + the `config_plugins`
|
|
269
|
+
override layer they read from. The two stub verbs become real, and
|
|
270
|
+
the `PluginLoader` finally honours user intent: a disabled plugin
|
|
271
|
+
surfaces in `sm plugins list` with status `disabled`, but its
|
|
272
|
+
extensions are NOT imported and the kernel will not run them.
|
|
273
|
+
|
|
274
|
+
**Decision (recorded in spec)**: enable/disable resolution favours the
|
|
275
|
+
DB row over `settings.json` over the installed default. The DB
|
|
276
|
+
override is local-machine; `settings.json` is the team-shared baseline.
|
|
277
|
+
A developer can locally disable a misbehaving plugin without
|
|
278
|
+
committing the toggle to the team's config; conversely, a baseline
|
|
279
|
+
that explicitly enables a plugin is overridable per-machine. The rule
|
|
280
|
+
is documented in `spec/db-schema.md` §`config_plugins`.
|
|
281
|
+
|
|
282
|
+
**Spec change (additive, patch)**:
|
|
283
|
+
|
|
284
|
+
- `spec/db-schema.md` — appended an "Effective enable/disable
|
|
285
|
+
resolution" subsection under `config_plugins` documenting the
|
|
286
|
+
three-layer precedence (DB > `settings.json` > installed default).
|
|
287
|
+
No schema changes; the `config_plugins` table itself was already
|
|
288
|
+
defined in the initial migration.
|
|
289
|
+
|
|
290
|
+
**Runtime change**:
|
|
291
|
+
|
|
292
|
+
- `src/kernel/types/plugin.ts` — `TPluginLoadStatus` gains a `disabled`
|
|
293
|
+
variant. JSDoc explains all five states.
|
|
294
|
+
- `src/kernel/adapters/sqlite/plugins.ts` — new file. Storage helpers
|
|
295
|
+
over the `config_plugins` table: `setPluginEnabled` (upsert),
|
|
296
|
+
`getPluginEnabled` (single read), `loadPluginOverrideMap` (bulk
|
|
297
|
+
read for one round-trip per process), `deletePluginOverride`
|
|
298
|
+
(idempotent drop, used by future `sm config reset plugins.<id>`).
|
|
299
|
+
- `src/kernel/config/plugin-resolver.ts` — new file.
|
|
300
|
+
`resolvePluginEnabled` implements the precedence above;
|
|
301
|
+
`makeEnabledResolver` curries the layered config and DB map into
|
|
302
|
+
the `(id) => boolean` shape `IPluginLoaderOptions.resolveEnabled`
|
|
303
|
+
expects.
|
|
304
|
+
- `src/kernel/adapters/plugin-loader.ts` — new optional
|
|
305
|
+
`resolveEnabled` callback in `IPluginLoaderOptions`. When supplied,
|
|
306
|
+
the loader checks AFTER manifest + specCompat validation and
|
|
307
|
+
short-circuits with `status: 'disabled'` (manifest preserved,
|
|
308
|
+
extensions array omitted, reason `"disabled by config_plugins or
|
|
309
|
+
settings.json"`). Omitting the callback keeps the legacy "always
|
|
310
|
+
load" behaviour for tests / kernel-empty-boot.
|
|
311
|
+
- `src/cli/commands/plugins.ts` — wires the loader to the resolver:
|
|
312
|
+
every read (`list / show / doctor`) loads `config_plugins` once and
|
|
313
|
+
feeds the resolver. Two new commands `PluginsEnableCommand` and
|
|
314
|
+
`PluginsDisableCommand` write to the DB. `--all` toggles every
|
|
315
|
+
discovered plugin; `<id>` and `--all` are mutually exclusive.
|
|
316
|
+
`sm plugins doctor` now treats `disabled` as intentional (does not
|
|
317
|
+
contribute to the issue list, does not flip exit code).
|
|
318
|
+
- `src/cli/commands/plugins.ts` — adds `off` to the status icon legend
|
|
319
|
+
in human output (`off mock-a@0.1.0 · disabled by config_plugins or
|
|
320
|
+
settings.json`).
|
|
321
|
+
- `src/cli/commands/stubs.ts` — `PluginsEnableCommand` and
|
|
322
|
+
`PluginsDisableCommand` removed; replaced-at-step comment kept.
|
|
323
|
+
- `context/cli-reference.md` — regenerated; the two new verbs appear
|
|
324
|
+
with their flag tables.
|
|
325
|
+
|
|
326
|
+
**Tests**:
|
|
327
|
+
|
|
328
|
+
- `src/test/plugin-overrides.test.ts` — 8 unit tests covering storage
|
|
329
|
+
round-trip (upsert + read), `loadPluginOverrideMap` bulk read,
|
|
330
|
+
`deletePluginOverride` idempotency, resolver precedence (default ⇒
|
|
331
|
+
true, `settings.json` overrides default, DB overrides
|
|
332
|
+
`settings.json`), `makeEnabledResolver` currying, and PluginLoader
|
|
333
|
+
surfacing `disabled` status with manifest preserved + no extensions
|
|
334
|
+
- omitting the resolver still loads.
|
|
335
|
+
- `src/test/plugins-cli.test.ts` — 9 end-to-end tests via the binary:
|
|
336
|
+
`disable <id>` writes a DB row + `sm plugins list` reflects `off`,
|
|
337
|
+
`enable <id>` flips back, `--all` covers every discovered plugin,
|
|
338
|
+
unknown id → exit 5, no-arg → exit 2, both `<id>` and `--all` →
|
|
339
|
+
exit 2, `settings.json` baseline overridden by DB `enable`,
|
|
340
|
+
`settings.json` baseline applies when DB has no row, and
|
|
341
|
+
`sm plugins doctor` exits 0 when the only non-loaded plugin is
|
|
342
|
+
intentionally disabled.
|
|
343
|
+
|
|
344
|
+
Test count: 273 → 291 (+18).
|
|
345
|
+
|
|
3
346
|
## 0.6.0
|
|
4
347
|
|
|
5
348
|
### Minor Changes
|
package/architecture.md
CHANGED
|
@@ -115,18 +115,66 @@ No extension is privileged. The Claude adapter ships bundled with the reference
|
|
|
115
115
|
|
|
116
116
|
---
|
|
117
117
|
|
|
118
|
+
## Execution modes
|
|
119
|
+
|
|
120
|
+
Every analytical extension in skill-map is one of two **modes**:
|
|
121
|
+
|
|
122
|
+
- **`deterministic`** — pure code. Same input → same output, every run.
|
|
123
|
+
- **`probabilistic`** — calls an LLM through the kernel's `RunnerPort`. Output may vary across runs; cost and latency are non-trivial.
|
|
124
|
+
|
|
125
|
+
Mode is a property of the extension as a whole, not of an individual call. **An extension is one mode or the other; it cannot switch at runtime.** If a plugin author needs both flavors of the same idea (regex-based AND LLM-based "find suspicious imports"), they ship two extensions with distinct ids.
|
|
126
|
+
|
|
127
|
+
### Which kinds support which modes
|
|
128
|
+
|
|
129
|
+
| Kind | Modes | How mode is set |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
| **Detector** | deterministic / probabilistic | declared in manifest (`mode` field, optional; defaults to `deterministic`) |
|
|
132
|
+
| **Rule** | deterministic / probabilistic | declared in manifest (`mode` field, optional; defaults to `deterministic`) |
|
|
133
|
+
| **Action** | deterministic / probabilistic | declared in manifest (`mode` field, **required** — no default) |
|
|
134
|
+
| **Audit** | deterministic / probabilistic | derived from `composes[]` (see below) |
|
|
135
|
+
| **Adapter** | deterministic-only | implicit; `mode` field MUST NOT appear |
|
|
136
|
+
| **Renderer** | deterministic-only | implicit; `mode` field MUST NOT appear |
|
|
137
|
+
|
|
138
|
+
Adapter and Renderer are locked to deterministic because they sit at the **boundaries** of the system. An adapter resolves `path → kind` during boot; probabilistic classification would make the boot phase slow, costly, and non-reproducible. A renderer must produce diffable output (`sm scan` snapshots round-trip in CI). Probabilistic narrators of the graph are a valid product but they live in jobs and emit Findings, not in renderers.
|
|
139
|
+
|
|
140
|
+
### Audit · derived mode
|
|
141
|
+
|
|
142
|
+
An audit is a **composer**: it declares which primitives it runs and the kernel handles dispatch. The audit manifest does NOT carry a `mode` field. Instead it declares `composes[]` — the rule and action references the audit executes in sequence. At load time the kernel resolves each entry and computes the audit's **effective mode**:
|
|
143
|
+
|
|
144
|
+
- If every composed primitive is `deterministic` → the audit's effective mode is `deterministic`. Runs synchronously inside `sm audit <id>`.
|
|
145
|
+
- If any composed primitive is `probabilistic` → the audit's effective mode is `probabilistic`. Dispatches as a job via `sm job submit audit:<id>`.
|
|
146
|
+
|
|
147
|
+
A dangling reference in `composes[]` (the id doesn't resolve, the kind is wrong, or the primitive is disabled) is a **load-time error**. The audit is rejected with status `invalid-manifest`, not silently skipped. This matches the rule already in place for `defaultRefreshAction`. Declaring `mode` directly on an audit manifest is also a load-time error.
|
|
148
|
+
|
|
149
|
+
The effective mode is exposed to the UI and to `sm audit show <id>` so consumers can preview cost before invoking.
|
|
150
|
+
|
|
151
|
+
### When each mode runs
|
|
152
|
+
|
|
153
|
+
- **Deterministic extensions** run synchronously inside the standard kernel pipelines (`sm scan`, `sm check`, `sm list`). Fast, free, reproducible. CI-safe.
|
|
154
|
+
- **Probabilistic extensions** never run during `sm scan`. They are dispatched as **jobs** via `sm job submit <kind>:<id>`. Jobs are async, queued, persisted under `state_jobs`, and resume on next boot. The same scan snapshot can be re-analyzed by probabilistic extensions on demand without re-walking the filesystem.
|
|
155
|
+
|
|
156
|
+
This separation is normative: a probabilistic extension cannot register a hook that fires from `sm scan`. The kernel rejects it at load time.
|
|
157
|
+
|
|
158
|
+
### How probabilistic extensions invoke the LLM
|
|
159
|
+
|
|
160
|
+
The kernel exposes the LLM through the `RunnerPort` (see §Ports above). Reference impl: `ClaudeCliRunner`. Tests: `MockRunner`. Other adapters (OpenAI, local Ollama, etc.) implement the same port without spec changes.
|
|
161
|
+
|
|
162
|
+
A probabilistic extension receives the runner in its invocation context alongside `ctx.store`. The extension never imports a specific LLM SDK — the runner contract is what the spec normalizes; wire format and model selection are adapter concerns.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
118
166
|
## Extension kinds
|
|
119
167
|
|
|
120
168
|
Six kinds, all first-class, all loaded through the same registry. Each kind has a JSON Schema describing its manifest shape under [`schemas/extensions/`](./schemas/extensions/). Implementations MUST validate every extension manifest against the schema for its declared kind at load time; validation failure → the extension is skipped with status `invalid-manifest`.
|
|
121
169
|
|
|
122
170
|
| Kind | Role | Input | Output |
|
|
123
171
|
|---|---|---|---|
|
|
124
|
-
| **Adapter** | Recognizes a platform. Decides which files are nodes and what kind they are. Declares per-kind `defaultRefreshAction` (an action id that drives the probabilistic-refresh surface). | Filesystem walk results, candidate path. | `{ kind, adapter } \| null`. |
|
|
125
|
-
| **Detector** | Extracts signals from a node body. | Parsed node (frontmatter + body). | `Link[]`. |
|
|
126
|
-
| **Rule** | Evaluates the graph. | Full graph (nodes + links). | `Issue[]`. |
|
|
127
|
-
| **Action** | Operates on one or more nodes.
|
|
128
|
-
| **Audit** |
|
|
129
|
-
| **Renderer** | Serializes the graph. | Graph + optional filter. | String (ASCII / Mermaid / DOT / JSON / user-defined). |
|
|
172
|
+
| **Adapter** | Recognizes a platform. Decides which files are nodes and what kind they are. Declares per-kind `defaultRefreshAction` (an action id that drives the probabilistic-refresh surface). Deterministic-only. | Filesystem walk results, candidate path. | `{ kind, adapter } \| null`. |
|
|
173
|
+
| **Detector** | Extracts signals from a node body. Dual-mode: `deterministic` runs in scan, `probabilistic` runs in jobs. | Parsed node (frontmatter + body). | `Link[]`. |
|
|
174
|
+
| **Rule** | Evaluates the graph. Dual-mode: `deterministic` runs in `sm check`, `probabilistic` runs in jobs. | Full graph (nodes + links). | `Issue[]`. |
|
|
175
|
+
| **Action** | Operates on one or more nodes. Dual-mode: `deterministic` (in-process code) or `probabilistic` (rendered prompt the runner executes). | Node(s), optional args. | Deterministic: report JSON. Probabilistic: rendered prompt that a runner executes. |
|
|
176
|
+
| **Audit** | Workflow that composes rules and actions. Effective mode is derived from `composes[]` — deterministic if all composed primitives are deterministic, probabilistic otherwise. Produces a structured report. | Graph + optional scope filter. | Audit report (hardcoded shape, kind-specific). |
|
|
177
|
+
| **Renderer** | Serializes the graph. Deterministic-only. | Graph + optional filter. | String (ASCII / Mermaid / DOT / JSON / user-defined). |
|
|
130
178
|
|
|
131
179
|
### Adapter · `defaultRefreshAction`
|
|
132
180
|
|
|
@@ -267,6 +315,8 @@ The **port list** is stable as of spec v1.0.0. Adding a sixth port is a major bu
|
|
|
267
315
|
|
|
268
316
|
The **extension kind list** (6 kinds) is stable as of spec v1.0.0. Adding a seventh kind is a major bump.
|
|
269
317
|
|
|
318
|
+
The **execution modes** (`deterministic` / `probabilistic`) and the per-kind mode capability matrix above are stable as of spec v1.0.0. Adding a third mode, changing which kinds are dual-mode, or changing the audit's mode-derivation rule is a major bump. Renaming or repurposing the mode enum values is a major bump.
|
|
319
|
+
|
|
270
320
|
The **dependency rules** above are stable as of spec v1.0.0. Relaxing any is a major bump; tightening (forbidding an allowed import) is a minor bump.
|
|
271
321
|
|
|
272
322
|
The **Detector · trigger normalization** pipeline (six steps, in order) is stable from the next spec release. Adding a new step at the end is a minor bump; reordering, removing, or changing any existing step (including the character classes in step 4) is a major bump. Implementations that produce different `normalizedTrigger` output for equivalent input are non-conforming.
|
package/cli-contract.md
CHANGED
|
@@ -166,11 +166,15 @@ Keys are dot-paths (`jobs.minimumTtlSeconds`, `scan.tokenize`). Unknown keys →
|
|
|
166
166
|
| `sm scan` | Full scan. Truncates `scan_*` and repopulates. |
|
|
167
167
|
| `sm scan -n <node.path>` | Partial scan: one node. |
|
|
168
168
|
| `sm scan --changed` | Incremental: only files changed since last scan (mtime heuristic). |
|
|
169
|
+
| `sm scan --watch` | Long-running: watch the roots and trigger an incremental scan after each debounced batch of filesystem events. Alias of `sm watch`. |
|
|
169
170
|
| `sm scan --compare-with <path>` | Delta report: compare current state with a saved scan dump. Does not modify the DB. |
|
|
171
|
+
| `sm watch [roots...]` | Long-running watcher. Same semantics as `sm scan --watch`, exposed as a top-level verb because the watcher is a loop, not a one-shot scan. |
|
|
170
172
|
|
|
171
|
-
`--json` output conforms to `schemas/scan-result.schema.json`.
|
|
173
|
+
`--json` output conforms to `schemas/scan-result.schema.json`. `sm watch` (and `sm scan --watch`) emit one ScanResult per batch — under `--json` this is an `ndjson` stream of ScanResult documents.
|
|
172
174
|
|
|
173
|
-
|
|
175
|
+
The watcher subscribes to the same roots that `sm scan` walks and respects `.skill-mapignore` plus `config.ignore` exactly as the one-shot scan does. Filesystem events are grouped using `scan.watch.debounceMs` (default 300ms) before the watcher re-runs the incremental scan and persists. `SIGINT` / `SIGTERM` close the watcher cleanly. Exit code on clean shutdown is 0.
|
|
176
|
+
|
|
177
|
+
Exit: 0 on clean (or clean watcher shutdown), 1 if error-severity issues exist (one-shot scan only — the watcher does not flip exit code based on per-batch issues), 2 on operational error.
|
|
174
178
|
|
|
175
179
|
---
|
|
176
180
|
|
|
@@ -179,7 +183,7 @@ Exit: 0 on clean, 1 if error-severity issues exist, 2 on operational error.
|
|
|
179
183
|
| Command | Purpose |
|
|
180
184
|
|---|---|
|
|
181
185
|
| `sm list [--kind <k>] [--issue] [--sort-by ...] [--limit N]` | Tabular listing. `--json` emits an array conforming to `node.schema.json`. |
|
|
182
|
-
| `sm show <node.path>` | Node detail: weight (bytes/tokens triple-split), frontmatter, links in/out, issues, findings, summary. `--json` emits a detail object. |
|
|
186
|
+
| `sm show <node.path>` | Node detail: weight (bytes/tokens triple-split), frontmatter, links in/out, issues, findings, summary. `--json` emits a detail object with the raw link rows. Pretty output groups identical-shape links (same endpoint, kind, normalized trigger) onto one line and lists the union of detector ids in a `sources:` field; the section header reports both the raw row count and the unique-after-grouping count, e.g. `Links out (12, 9 unique)`. Storage keeps one row per detector (`scan_links` is unchanged) — the grouping is purely a read-time presentation choice. |
|
|
183
187
|
| `sm check` | Print all current issues. Equivalent to `sm scan --json \| jq '.issues'` but faster (reads from DB). |
|
|
184
188
|
| `sm findings [--kind ...] [--since ...] [--threshold <n>]` | Probabilistic findings (injection, stale summaries, low confidence). `--json` emits an array of finding objects. |
|
|
185
189
|
| `sm graph [--format ascii\|mermaid\|dot]` | Render the full graph via the named renderer. |
|
package/conformance/coverage.md
CHANGED
|
@@ -12,8 +12,8 @@ This file is hand-maintained. A CI check before spec release compares the schema
|
|
|
12
12
|
| 2 | `link.schema.json` | — | 🔴 missing | Needs fixture with at least one `invokes` + `references` + `mentions` link, both `high`/`medium`/`low` confidence. |
|
|
13
13
|
| 3 | `issue.schema.json` | — | 🔴 missing | Needs fixture triggering `trigger-collision` + `broken-ref` + `superseded`. |
|
|
14
14
|
| 4 | `scan-result.schema.json` | `basic-scan`, `kernel-empty-boot` | 🟢 covered | Zero-filled (empty-boot) + populated (minimal-claude) both asserted. |
|
|
15
|
-
| 5 | `execution-record.schema.json` | — | 🔴 missing | Blocked by Step 5 (history). Needs a case that runs a `
|
|
16
|
-
| 6 | `project-config.schema.json` | — | 🔴 missing | Case: init a scope, write a partial `.skill-map.json
|
|
15
|
+
| 5 | `execution-record.schema.json` | — | 🔴 missing | Blocked by Step 5 (history). Needs a case that runs a `deterministic` action and inspects `state_executions` via `sm history --json`. |
|
|
16
|
+
| 6 | `project-config.schema.json` | — | 🔴 missing | Case: init a scope, write a partial `.skill-map/settings.json` (optionally with a `.skill-map/settings.local.json` overlay), assert effective config after the layered merge. |
|
|
17
17
|
| 7 | `plugins-registry.schema.json` | — | 🔴 missing | Two sub-cases required: (a) `PluginManifest` validation via `sm plugins show --json`; (b) aggregate `PluginsRegistry` via `sm plugins list --json`. |
|
|
18
18
|
| 8 | `job.schema.json` | — | 🔴 missing | Blocked by Step 10 (job system). Needs a case that submits a local action (no LLM), inspects `sm job show --json`. |
|
|
19
19
|
| 9 | `report-base.schema.json` | — | 🔴 missing | Indirect coverage once any summarizer case lands. Direct contract case: validate a handcrafted minimal report ({confidence, safety}) against the base schema. |
|
|
@@ -33,8 +33,8 @@ This file is hand-maintained. A CI check before spec release compares the schema
|
|
|
33
33
|
| 23 | `extensions/adapter.schema.json` | — | 🔴 missing | Case: the `claude` adapter manifest validates; a crafted invalid manifest (missing `defaultRefreshAction`) fails with `invalid-manifest`. |
|
|
34
34
|
| 24 | `extensions/detector.schema.json` | — | 🔴 missing | Case: `frontmatter` + `slash` + `at-directive` detector manifests validate; a detector emitting a disallowed `emitsLinkKinds` value fails. |
|
|
35
35
|
| 25 | `extensions/rule.schema.json` | — | 🔴 missing | Case: `trigger-collision`, `broken-ref`, `superseded` manifests validate. |
|
|
36
|
-
| 26 | `extensions/action.schema.json` | — | 🔴 missing | Case: a `
|
|
37
|
-
| 27 | `extensions/audit.schema.json` | — | 🔴 missing | Case: `validate-all` audit manifest validates; an audit referencing a non-existent rule id in `composes` fails at load with `invalid-manifest
|
|
36
|
+
| 26 | `extensions/action.schema.json` | — | 🔴 missing | Case: a `deterministic` action manifest validates; a `probabilistic` action WITHOUT `promptTemplateRef` fails. |
|
|
37
|
+
| 27 | `extensions/audit.schema.json` | — | 🔴 missing | Case: `validate-all` audit manifest validates; an audit referencing a non-existent rule id in `composes` fails at load with `invalid-manifest`; an audit declaring `mode` directly fails at load. |
|
|
38
38
|
| 28 | `extensions/renderer.schema.json` | — | 🔴 missing | Case: `ascii` renderer manifest validates. |
|
|
39
39
|
| 29 | `history-stats.schema.json` | — | 🔴 missing | Blocked by Step 5 (history). Case: seed `state_executions` with a deterministic fixture, run `sm history stats --json --since <T0> --until <T1> --period month --top 5`, assert the document validates and that `totals.executionsCount == sum(perAction.executionsCount)` and `errorRates.global == totals.failedCount / totals.executionsCount`. Percentiles (`p95`/`p99`) intentionally omitted in v1 — add later as a minor bump without breaking consumers. |
|
|
40
40
|
|
|
@@ -48,6 +48,7 @@ These have their own conformance cases even though they are not JSON Schemas.
|
|
|
48
48
|
|---|---|---|---|---|
|
|
49
49
|
| A | Preamble verbatim text | `preamble-bitwise-match` | 🟠 deferred | Deferred to Step 10 (needs `sm job preview` to render a job file). Fixture: `fixtures/preamble-v1.txt` (already present, byte-identical to `prompt-preamble.md` source). |
|
|
50
50
|
| B | Kernel empty-boot invariant | `kernel-empty-boot` | 🟢 covered | All extensions disabled → empty ScanResult. |
|
|
51
|
+
| C | Audit mode derivation | `extension-mode-derivation` | 🟠 deferred | Deferred to Step 10 (audit's effective mode is derived from `composes[]` at load time; full validation requires the job subsystem to verify dispatch routing). Sub-cases: (1) audit composing only deterministic primitives → effective mode `deterministic`, runs synchronously inside `sm audit <id>`; (2) audit composing at least one probabilistic primitive → effective mode `probabilistic`, dispatches as a job; (3) audit declaring `mode` directly in the manifest → load-time error `invalid-manifest`; (4) audit composing a dangling reference → load-time error `invalid-manifest`. See `architecture.md` §Execution modes. |
|
|
51
52
|
| C | Atomic-claim race safety | — | 🔴 missing | Blocked by Step 10. Two concurrent `sm job claim` invocations against a single queued row — exactly one MUST succeed. |
|
|
52
53
|
| D | Duplicate detection | — | 🔴 missing | Blocked by Step 10. Two `sm job submit` with same `(action, version, node, contentHash)` — second exits 3. |
|
|
53
54
|
| E | `--force` bypass | — | 🔴 missing | Blocked by Step 10. |
|
package/db-schema.md
CHANGED
|
@@ -19,7 +19,7 @@ Two scopes. Each has its own database file and its own migration ledger.
|
|
|
19
19
|
| `project` (default) | `./.skill-map/skill-map.db` | The current repository. |
|
|
20
20
|
| `global` (`-g`) | `~/.skill-map/skill-map.db` | User-level skill directories (e.g. `~/.claude/`). |
|
|
21
21
|
|
|
22
|
-
The project DB is gitignored by default. Teams MAY opt in to sharing it by setting `history.share: true` in `.skill-map.json` — the file is then committed and the execution log becomes a team artifact. Both zones use the same schema.
|
|
22
|
+
The project DB is gitignored by default. Teams MAY opt in to sharing it by setting `history.share: true` in `.skill-map/settings.json` — the file is then committed and the execution log becomes a team artifact. Both zones use the same schema.
|
|
23
23
|
|
|
24
24
|
The `--db <path>` CLI flag overrides location for both scopes as an escape hatch.
|
|
25
25
|
|
|
@@ -272,6 +272,14 @@ Persists user-toggled enable/disable overrides. Discovery is still filesystem-ba
|
|
|
272
272
|
| `config_json` | TEXT | NULL |
|
|
273
273
|
| `updated_at` | INTEGER | NOT NULL |
|
|
274
274
|
|
|
275
|
+
**Effective enable/disable resolution.** A plugin is enabled iff the highest-precedence layer that mentions it says so. Order from highest to lowest:
|
|
276
|
+
|
|
277
|
+
1. `config_plugins.enabled` for the row whose `plugin_id` matches — written by `sm plugins enable/disable`. Local-machine user override; never committed (the DB is gitignored unless `history.share: true`).
|
|
278
|
+
2. `.skill-map/settings.json#/plugins/<id>/enabled` — committed team-shared baseline.
|
|
279
|
+
3. Installed default — every discovered plugin is enabled until told otherwise.
|
|
280
|
+
|
|
281
|
+
The DB intentionally takes precedence over `settings.json` so a developer can locally disable a misbehaving plugin without committing the toggle to the team's config. Conversely, a team baseline that explicitly enables a plugin is overridable per-machine — no agreement is required to experiment.
|
|
282
|
+
|
|
275
283
|
### `config_preferences`
|
|
276
284
|
|
|
277
285
|
General-purpose key-value for user preferences (`sm config set`).
|
package/index.json
CHANGED
|
@@ -190,20 +190,20 @@
|
|
|
190
190
|
}
|
|
191
191
|
]
|
|
192
192
|
},
|
|
193
|
-
"specPackageVersion": "0.
|
|
193
|
+
"specPackageVersion": "1.0.0",
|
|
194
194
|
"integrity": {
|
|
195
195
|
"algorithm": "sha256",
|
|
196
196
|
"files": {
|
|
197
|
-
"CHANGELOG.md": "
|
|
197
|
+
"CHANGELOG.md": "a66be9c8e92c583f37a1625c0de24354e4bace97c0db466500273876d19100dc",
|
|
198
198
|
"README.md": "8bd57e02d9a9d3f0a4efd18c0f0bd1f4bbe13eb206add0317659e48eab435e7e",
|
|
199
|
-
"architecture.md": "
|
|
200
|
-
"cli-contract.md": "
|
|
199
|
+
"architecture.md": "0ebaacef9e57206bc0dde27ff44a02e0a7def9ae9ceba2f27053b31ff708708b",
|
|
200
|
+
"cli-contract.md": "12ca455496d48a61fc83888808433acf1470f09c261cf1161375b01f0f3f85c4",
|
|
201
201
|
"conformance/README.md": "79c5e63f18a368951dc9f3e31e9bf9574de3f8b97150b2d75365d4febd8eb6dc",
|
|
202
202
|
"conformance/cases/basic-scan.json": "24623da0cad8c8c54b3ff9b09820ea1276fe8b8f0fc680bf6e8abeb4edb8e424",
|
|
203
203
|
"conformance/cases/kernel-empty-boot.json": "175524674b14d993d29f10080d7697074b3a2eee25b359ff903344d73c6acc98",
|
|
204
204
|
"conformance/cases/orphan-detection.json": "7fea6e866d775d09cadb70ccd764f6c8317ca61316c6d187a97cb2466db4e19e",
|
|
205
205
|
"conformance/cases/rename-high.json": "f23513893e25fc4259db06a497906137de981da775d8ab2ef262554d54af5f27",
|
|
206
|
-
"conformance/coverage.md": "
|
|
206
|
+
"conformance/coverage.md": "a9580457cd868638676a450ace478438f832d057ab9c3ad64c088366afc07b7a",
|
|
207
207
|
"conformance/fixtures/minimal-claude/agents/reviewer.md": "d0dd681ba63838301e480116aa09825329f01832b0116de5c5476fdd8a5dcf54",
|
|
208
208
|
"conformance/fixtures/minimal-claude/commands/status.md": "3f36e053fd1c059ffd902f84a55be8a458c26072f97cb37dd7e97314ae2a9bf5",
|
|
209
209
|
"conformance/fixtures/minimal-claude/hooks/pre-commit.md": "ec9cec8ac4ce34d40ec055ffd90e8f06ea3e5764d6ec3ee84e0d97de71b930c7",
|
|
@@ -215,21 +215,21 @@
|
|
|
215
215
|
"conformance/fixtures/preamble-v1.txt": "1e0aeef224b64477bdc13a949c3ad402e68249caf499ecdba1302371677c068b",
|
|
216
216
|
"conformance/fixtures/rename-high-after/skills/bar.md": "16f7678829c7702f8ebaeef920a891756da198466a1884badd8d8b4a7d1bab6a",
|
|
217
217
|
"conformance/fixtures/rename-high-before/skills/foo.md": "16f7678829c7702f8ebaeef920a891756da198466a1884badd8d8b4a7d1bab6a",
|
|
218
|
-
"db-schema.md": "
|
|
219
|
-
"interfaces/security-scanner.md": "
|
|
218
|
+
"db-schema.md": "002224f629403a247c0243d4b242c1e35e28bd93073ea53137ec1d30084d9bd7",
|
|
219
|
+
"interfaces/security-scanner.md": "e46d33d6e39b15672c8f7350f1cbd4755534510fe57c679c2b1d0be57577d818",
|
|
220
220
|
"job-events.md": "08796b7fbeb55e5b03cf3bc394224e70a23438a4d15a46ad1d70121c2c68b967",
|
|
221
221
|
"job-lifecycle.md": "1fe88b1a2ed204e41bb41ac172fbb3e912dccd0dd8a1f8ea8e21a681b336d6ee",
|
|
222
222
|
"plugin-kv-api.md": "04b2178f46fb88adeae9240df9c9e1761b660396072001dac32cd402e11a2d7d",
|
|
223
223
|
"prompt-preamble.md": "23a8eff0477fbbc46192a27781bc781bda4202bb9c669b7a7a002b0d668146b0",
|
|
224
224
|
"schemas/conformance-case.schema.json": "d69c501bbca079da0ca87685eb4cbdbc2e405334469fc937929ca9134e01a2b3",
|
|
225
225
|
"schemas/execution-record.schema.json": "ec0f3acf1d0ce099c059d73eb434936bfd1bcf12023693bd572efb2a7352faa6",
|
|
226
|
-
"schemas/extensions/action.schema.json": "
|
|
227
|
-
"schemas/extensions/adapter.schema.json": "
|
|
228
|
-
"schemas/extensions/audit.schema.json": "
|
|
226
|
+
"schemas/extensions/action.schema.json": "63736f3efe33e35abcaa12de6d746c405e9bf0927b999bc0d49de3ba948d5831",
|
|
227
|
+
"schemas/extensions/adapter.schema.json": "819a696d4379262b8b1df96a16bc56bc46df60339ddddf4a9d92752dd008d682",
|
|
228
|
+
"schemas/extensions/audit.schema.json": "58b1895fd447cee7d5ed9e8c9139ecd6b0fe11d439903c30ec82f34ece14b24b",
|
|
229
229
|
"schemas/extensions/base.schema.json": "c832a8c9976a7ddc70b8f9226a54de14aa3e85d71bc77ed7a8671a77d599c0e4",
|
|
230
|
-
"schemas/extensions/detector.schema.json": "
|
|
231
|
-
"schemas/extensions/renderer.schema.json": "
|
|
232
|
-
"schemas/extensions/rule.schema.json": "
|
|
230
|
+
"schemas/extensions/detector.schema.json": "a693c17b7e75bcf37eb87f84eea30e89d7aae179b5b89ef5a1cff330c333c029",
|
|
231
|
+
"schemas/extensions/renderer.schema.json": "187e3498d0f3bddb49b9793bca9601fe461ff8d23625069e4c5c8ba18acbb81a",
|
|
232
|
+
"schemas/extensions/rule.schema.json": "75e5adababcf1f0c5c6aaf8009795d49e7a7e196cee13a58940a076429d0be5e",
|
|
233
233
|
"schemas/frontmatter/agent.schema.json": "0e63d7692efb29facccc69472fff48a25f44934618346bfc09738864c6917787",
|
|
234
234
|
"schemas/frontmatter/base.schema.json": "e68fbb85d3e873c4897af776eaf873860bd6e86b5abc1799e801d35c4f7937cf",
|
|
235
235
|
"schemas/frontmatter/command.schema.json": "7b8463ce9c83edd2e3073dd4cd1bbeec4b42e53b03b48bc9a59e540136c2de89",
|
|
@@ -242,7 +242,7 @@
|
|
|
242
242
|
"schemas/link.schema.json": "3e92f5c9def61a857a2c7b22846d82b988157de083463615144ddc92403a489e",
|
|
243
243
|
"schemas/node.schema.json": "14f345fac450f5728c895d1b878e0015eabb9d72ba9da4a8d2236c82933d3fcf",
|
|
244
244
|
"schemas/plugins-registry.schema.json": "92b2052bd06e366709dd6e1449d99408999e33707c4007afc7662980e73c3ef1",
|
|
245
|
-
"schemas/project-config.schema.json": "
|
|
245
|
+
"schemas/project-config.schema.json": "74f8f2ba2c4897ee47a5cc08e27ec3898dc0a938fe7e3823f33f6c5005724d1f",
|
|
246
246
|
"schemas/report-base.schema.json": "a1021e9a59b4df9f99cd92454d797e88469766e7d49f52d231c4645ffdfdad8f",
|
|
247
247
|
"schemas/scan-result.schema.json": "5efe9b1954c5e729c4b55dbc4dd51263d97967d16c0b3cea398877ace74d37b7",
|
|
248
248
|
"schemas/summaries/agent.schema.json": "3d22558eeb170e00c4fc32018a810d27333cc632c9e528ff386100cfdfded087",
|
|
@@ -49,7 +49,7 @@ The Action receives a standard invocation: a single node, or (via `--all`) a set
|
|
|
49
49
|
|
|
50
50
|
i.e. applies to every node. A scanner MAY narrow to specific kinds if the vendor's check only applies to, for example, shell-hook content.
|
|
51
51
|
|
|
52
|
-
Scanners are **
|
|
52
|
+
Scanners are **deterministic-mode** Actions by default: no LLM involvement. The Action runs its own logic (HTTP request to a vendor API, local regex scan, dependency check) and writes a report. Scanners MAY also be `probabilistic` Actions if the scanner relies on model analysis — the same report shape applies.
|
|
53
53
|
|
|
54
54
|
---
|
|
55
55
|
|
package/package.json
CHANGED
|
@@ -2,10 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://skill-map.dev/spec/v0/extensions/action.schema.json",
|
|
4
4
|
"title": "ExtensionAction",
|
|
5
|
-
"description": "Manifest shape for an `Action` extension. An action operates on one or more nodes in one of two modes: `
|
|
6
|
-
"allOf": [
|
|
7
|
-
{ "$ref": "base.schema.json" }
|
|
8
|
-
],
|
|
5
|
+
"description": "Manifest shape for an `Action` extension. An action operates on one or more nodes in one of two modes: `deterministic` (code runs in-process, returns a report JSON directly) or `probabilistic` (kernel renders a prompt, a runner executes it against an LLM, the callback closes the job). The `mode` discriminator drives which additional fields are required. See `architecture.md` §Execution modes for the cross-extension contract.",
|
|
9
6
|
"type": "object",
|
|
10
7
|
"required": ["id", "kind", "version", "mode", "reportSchemaRef"],
|
|
11
8
|
"unevaluatedProperties": false,
|
|
@@ -13,8 +10,8 @@
|
|
|
13
10
|
"kind": { "const": "action" },
|
|
14
11
|
"mode": {
|
|
15
12
|
"type": "string",
|
|
16
|
-
"enum": ["
|
|
17
|
-
"description": "`
|
|
13
|
+
"enum": ["deterministic", "probabilistic"],
|
|
14
|
+
"description": "`deterministic`: the plugin's code computes the report synchronously, no job file, no runner. `probabilistic`: the kernel renders a prompt + preamble into a job file; a runner executes it via `RunnerPort`; `sm record` closes the job."
|
|
18
15
|
},
|
|
19
16
|
"reportSchemaRef": {
|
|
20
17
|
"type": "string",
|
|
@@ -23,11 +20,11 @@
|
|
|
23
20
|
"expectedDurationSeconds": {
|
|
24
21
|
"type": "integer",
|
|
25
22
|
"minimum": 1,
|
|
26
|
-
"description": "Best-effort estimate of wall-clock duration. Drives TTL (`ttl = max(expectedDurationSeconds × graceMultiplier, minimumTtlSeconds)`). Required for `
|
|
23
|
+
"description": "Best-effort estimate of wall-clock duration. Drives TTL (`ttl = max(expectedDurationSeconds × graceMultiplier, minimumTtlSeconds)`). Required for `probabilistic`; advisory for `deterministic`."
|
|
27
24
|
},
|
|
28
25
|
"promptTemplateRef": {
|
|
29
26
|
"type": "string",
|
|
30
|
-
"description": "Path (relative to the extension file) to the prompt template the kernel renders at `sm job submit`. REQUIRED when `mode:
|
|
27
|
+
"description": "Path (relative to the extension file) to the prompt template the kernel renders at `sm job submit`. REQUIRED when `mode: probabilistic`; FORBIDDEN when `mode: deterministic`. The template MUST NOT interpolate user text outside `<user-content>` blocks (see `prompt-preamble.md`)."
|
|
31
28
|
},
|
|
32
29
|
"precondition": {
|
|
33
30
|
"type": "object",
|
|
@@ -69,12 +66,13 @@
|
|
|
69
66
|
}
|
|
70
67
|
},
|
|
71
68
|
"allOf": [
|
|
69
|
+
{ "$ref": "base.schema.json" },
|
|
72
70
|
{
|
|
73
|
-
"if": { "properties": { "mode": { "const": "
|
|
71
|
+
"if": { "properties": { "mode": { "const": "probabilistic" } } },
|
|
74
72
|
"then": { "required": ["promptTemplateRef", "expectedDurationSeconds"] }
|
|
75
73
|
},
|
|
76
74
|
{
|
|
77
|
-
"if": { "properties": { "mode": { "const": "
|
|
75
|
+
"if": { "properties": { "mode": { "const": "deterministic" } } },
|
|
78
76
|
"then": { "not": { "required": ["promptTemplateRef"] } }
|
|
79
77
|
}
|
|
80
78
|
]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://skill-map.dev/spec/v0/extensions/adapter.schema.json",
|
|
4
4
|
"title": "ExtensionAdapter",
|
|
5
|
-
"description": "Manifest shape for an `Adapter` extension. An adapter recognizes a platform (Claude Code, Codex, Gemini, Obsidian vault, generic MD) and classifies each candidate file into a node `kind`. Exactly zero or one adapter MUST match any given file; multiple matches → the kernel emits an issue `adapter-ambiguous` and the file is left unclassified. Stability: stable as of spec v1.0.0 except where noted.",
|
|
5
|
+
"description": "Manifest shape for an `Adapter` extension. An adapter recognizes a platform (Claude Code, Codex, Gemini, Obsidian vault, generic MD) and classifies each candidate file into a node `kind`. Exactly zero or one adapter MUST match any given file; multiple matches → the kernel emits an issue `adapter-ambiguous` and the file is left unclassified. Adapters are deterministic-only — they sit at the filesystem boundary and run during boot; probabilistic classification would make boot slow, costly, and non-reproducible. The `mode` field MUST NOT appear in adapter manifests. If you need LLM-assisted classification, write a probabilistic Detector that emits classification hints as `Link[]`. Stability: stable as of spec v1.0.0 except where noted.",
|
|
6
6
|
"allOf": [
|
|
7
7
|
{ "$ref": "base.schema.json" }
|
|
8
8
|
],
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://skill-map.dev/spec/v0/extensions/audit.schema.json",
|
|
4
4
|
"title": "ExtensionAudit",
|
|
5
|
-
"description": "Manifest shape for an `Audit` extension. An audit is a hardcoded
|
|
5
|
+
"description": "Manifest shape for an `Audit` extension. An audit is a hardcoded workflow that composes rules and actions into a single report. The audit's execution mode is NOT declared in the manifest — it is **derived** from the modes of the primitives it composes: if every composed primitive is `deterministic`, the audit's effective mode is `deterministic` and runs synchronously inside `sm audit <id>`; if any composed primitive is `probabilistic`, the audit's effective mode is `probabilistic` and dispatches as a queued job (`sm job submit audit:<id>`). Declaring `mode` in the manifest is a load-time error. See `architecture.md` §Execution modes for the full derivation contract.",
|
|
6
6
|
"allOf": [
|
|
7
7
|
{ "$ref": "base.schema.json" }
|
|
8
8
|
],
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"kind": { "const": "audit" },
|
|
14
14
|
"composes": {
|
|
15
15
|
"type": "array",
|
|
16
|
-
"description": "Ordered list of rule
|
|
16
|
+
"description": "Ordered list of rule and action references the audit executes in sequence. The kernel resolves each reference in the registry at load time; a dangling reference (id not found, kind mismatch, or primitive disabled) disables the audit with status `invalid-manifest`. Each composed primitive's `mode` participates in the audit's mode derivation.",
|
|
17
17
|
"minItems": 1,
|
|
18
18
|
"items": {
|
|
19
19
|
"type": "object",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"reportSchemaRef": {
|
|
34
34
|
"type": "string",
|
|
35
|
-
"description": "Reference to the JSON Schema of the audit's report shape.
|
|
35
|
+
"description": "Reference to the JSON Schema of the audit's report shape. Probabilistic audits MUST extend `report-base.schema.json` (carries `safety` / `confidence` per the report-base contract). Deterministic audits MAY extend it but are not required to."
|
|
36
36
|
},
|
|
37
37
|
"exitCodeMap": {
|
|
38
38
|
"type": "object",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://skill-map.dev/spec/v0/extensions/detector.schema.json",
|
|
4
4
|
"title": "ExtensionDetector",
|
|
5
|
-
"description": "Manifest shape for a `Detector` extension. A detector consumes a parsed node (frontmatter + body) and emits `Link[]` pointing to other nodes or to external URLs (the latter only if it is the designated URL-counter detector). Detectors run in isolation: they MUST NOT read other nodes, the graph, or the DB. Cross-node reasoning lives in Rules.",
|
|
5
|
+
"description": "Manifest shape for a `Detector` extension. A detector consumes a parsed node (frontmatter + body) and emits `Link[]` pointing to other nodes or to external URLs (the latter only if it is the designated URL-counter detector). Detectors run in isolation: they MUST NOT read other nodes, the graph, or the DB. Cross-node reasoning lives in Rules. Detectors are dual-mode: `deterministic` detectors run synchronously inside `sm scan`; `probabilistic` detectors invoke an LLM through the kernel's `RunnerPort` and execute only as queued jobs (never during scan). See `architecture.md` §Execution modes for the full contract.",
|
|
6
6
|
"allOf": [
|
|
7
7
|
{ "$ref": "base.schema.json" }
|
|
8
8
|
],
|
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
"unevaluatedProperties": false,
|
|
12
12
|
"properties": {
|
|
13
13
|
"kind": { "const": "detector" },
|
|
14
|
+
"mode": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"enum": ["deterministic", "probabilistic"],
|
|
17
|
+
"default": "deterministic",
|
|
18
|
+
"description": "`deterministic` (default): pure code, runs synchronously during `sm scan`. Same input → same output, every run. `probabilistic`: invokes an LLM via `ctx.runner` and runs only as a queued job (`sm job submit detector:<id>`); never participates in `sm scan`. The kernel rejects probabilistic detectors that try to register scan-time hooks at load time. Omitting the field is equivalent to declaring `deterministic`."
|
|
19
|
+
},
|
|
14
20
|
"emitsLinkKinds": {
|
|
15
21
|
"type": "array",
|
|
16
22
|
"description": "Subset of `Link.kind` values this detector is allowed to emit. Emitting an unlisted kind at runtime → kernel rejects the link and logs `detector-kind-violation`.",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://skill-map.dev/spec/v0/extensions/renderer.schema.json",
|
|
4
4
|
"title": "ExtensionRenderer",
|
|
5
|
-
"description": "Manifest shape for a `Renderer` extension. A renderer serializes the graph (or a filtered subgraph) into a string in a declared format. Renderers are invoked by `sm graph --format <format>` and `sm export`.
|
|
5
|
+
"description": "Manifest shape for a `Renderer` extension. A renderer serializes the graph (or a filtered subgraph) into a string in a declared format. Renderers are invoked by `sm graph --format <format>` and `sm export`. Renderers are deterministic-only — they sit at the graph-to-string boundary and their output MUST be byte-deterministic for the same input graph (the snapshot-test suite relies on this). The `mode` field MUST NOT appear in renderer manifests. Probabilistic narrators of the graph are a valid product but they live in jobs and emit Findings, not in renderers.",
|
|
6
6
|
"allOf": [
|
|
7
7
|
{ "$ref": "base.schema.json" }
|
|
8
8
|
],
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://skill-map.dev/spec/v0/extensions/rule.schema.json",
|
|
4
4
|
"title": "ExtensionRule",
|
|
5
|
-
"description": "Manifest shape for a `Rule` extension. A rule consumes the full graph (nodes + links) after all detectors have run and emits `Issue[]`. Rules MUST be
|
|
5
|
+
"description": "Manifest shape for a `Rule` extension. A rule consumes the full graph (nodes + links) after all detectors have run and emits `Issue[]`. Rules are dual-mode: `deterministic` rules MUST be byte-for-byte reproducible (same graph in → same issues out; time, random, and network are forbidden) and run synchronously inside `sm check` / `sm scan`. `probabilistic` rules invoke an LLM through the kernel's `RunnerPort` and execute only as queued jobs (`sm job submit rule:<id>`); their output MAY vary across runs and they NEVER participate in `sm scan`. See `architecture.md` §Execution modes for the full contract.",
|
|
6
6
|
"allOf": [
|
|
7
7
|
{ "$ref": "base.schema.json" }
|
|
8
8
|
],
|
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
"unevaluatedProperties": false,
|
|
12
12
|
"properties": {
|
|
13
13
|
"kind": { "const": "rule" },
|
|
14
|
+
"mode": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"enum": ["deterministic", "probabilistic"],
|
|
17
|
+
"default": "deterministic",
|
|
18
|
+
"description": "`deterministic` (default): pure code, byte-for-byte reproducible, runs during `sm check` and `sm scan`. `probabilistic`: invokes an LLM via `ctx.runner` and runs only as a queued job; never participates in scan-time pipelines. The kernel rejects probabilistic rules that try to register scan-time hooks at load time. Omitting the field is equivalent to declaring `deterministic`."
|
|
19
|
+
},
|
|
14
20
|
"emitsRuleIds": {
|
|
15
21
|
"type": "array",
|
|
16
22
|
"description": "List of `rule_id` values this rule may emit on issues. Typically a singleton (`trigger-collision` → emits `trigger-collision`). A rule emitting a `rule_id` not in this list → kernel logs `rule-id-violation` but keeps the issue (forward compatibility).",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://skill-map.dev/spec/v0/project-config.schema.json",
|
|
4
4
|
"title": "ProjectConfig",
|
|
5
|
-
"description": "Shape of `.skill-map.json`
|
|
5
|
+
"description": "Shape of `.skill-map/settings.json` (and its `.skill-map/settings.local.json` partner) inside a scope. Loaded by the layered config hierarchy (library defaults → user → user-local → project → project-local → env/flags) and deep-merged per key. All fields optional; defaults apply when absent. camelCase keys throughout — consistent with the rest of the spec.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": false,
|
|
8
8
|
"properties": {
|
|
@@ -45,6 +45,18 @@
|
|
|
45
45
|
"type": "integer",
|
|
46
46
|
"minimum": 1,
|
|
47
47
|
"description": "Files larger than this are skipped with an `info`-level log entry. Default 1048576 (1 MiB). Protects against scanning accidental binary drops or generated artefacts."
|
|
48
|
+
},
|
|
49
|
+
"watch": {
|
|
50
|
+
"type": "object",
|
|
51
|
+
"additionalProperties": false,
|
|
52
|
+
"description": "File-watcher knobs for `sm watch` and `sm scan --watch`. The watcher subscribes to the same roots `sm scan` walks, applies the `.skill-mapignore` filter, and triggers an incremental scan after each batch.",
|
|
53
|
+
"properties": {
|
|
54
|
+
"debounceMs": {
|
|
55
|
+
"type": "integer",
|
|
56
|
+
"minimum": 0,
|
|
57
|
+
"description": "Milliseconds to wait after the last filesystem event before triggering an incremental scan. Groups bursts (editor saves, branch switches, package installs) into a single scan pass. Default 300. Set to 0 to disable debouncing — every filesystem event triggers a scan immediately."
|
|
58
|
+
}
|
|
59
|
+
}
|
|
48
60
|
}
|
|
49
61
|
}
|
|
50
62
|
},
|