@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 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. Two modes: `local` (code) or `invocation-template` (LLM prompt). | Node(s), optional args. | Local: report JSON. Template: rendered prompt that a runner executes. |
128
- | **Audit** | Deterministic workflow that composes rules and actions. Produces a structured report. | Graph + optional scope filter. | Audit report (hardcoded shape, kind-specific). |
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
- Exit: 0 on clean, 1 if error-severity issues exist, 2 on operational error.
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. |
@@ -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 `local` 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.json`, assert effective config after merge. |
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 `local` action manifest validates; an `invocation-template` 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`. |
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.6.0",
193
+ "specPackageVersion": "1.0.0",
194
194
  "integrity": {
195
195
  "algorithm": "sha256",
196
196
  "files": {
197
- "CHANGELOG.md": "e74921b61ca835c4ebc8f2a4814d2c9513a59ce47c8c719b4d17865b39d93cba",
197
+ "CHANGELOG.md": "a66be9c8e92c583f37a1625c0de24354e4bace97c0db466500273876d19100dc",
198
198
  "README.md": "8bd57e02d9a9d3f0a4efd18c0f0bd1f4bbe13eb206add0317659e48eab435e7e",
199
- "architecture.md": "99f9d6a1a90e6c96d3c8a6f36c2650da4a1af0a1bc21173ea8eb2c492008539a",
200
- "cli-contract.md": "bab14bb72ddd8a57e00808f7f12741c63a33da99055b278e4407ab9b4bb7e2c1",
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": "a67fb38c69765bb3cafb78c75e211c530ab00a88d8ccf0254335d3b6d4f69b8d",
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": "e9b436b143e2e56985c69498c7d48527bde0baa441be586e2740a4051b366c2d",
219
- "interfaces/security-scanner.md": "81dc3dc2c439a75f4603b6d52e714f44ac564032c8aa424385ebbf4502adae3e",
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": "c7520d3cefecf75d27d3e04473821fd6e5dc5a7924eede147f74275ba6caccad",
227
- "schemas/extensions/adapter.schema.json": "429b865e738664bb437ac62690a2d7282ce992339fbb300417c73625f5cdb7c8",
228
- "schemas/extensions/audit.schema.json": "9ec2c68584707696423a1d617bc1e003cf8ee96a2c67b2f008f6647b2927c86c",
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": "077b9cccb0bd3d58ca53d61d59c609aa42709225d187e341412a857ab341462f",
231
- "schemas/extensions/renderer.schema.json": "2ec52545c85bb5e36d0f4f67c155b0e1656468b62a1045d2eb268255202306f0",
232
- "schemas/extensions/rule.schema.json": "dd957deaafd41699309cb073a4620e4e8e45d3ba15541adba0e693e6d85cdf76",
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": "286895996eb8cfb8054eb53a9042ea89fbaa36c1eb9e38fdcfbe08440ae692e3",
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 **local-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 `invocation-template` Actions if the scanner relies on model analysis — the same report shape applies.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skill-map/spec",
3
- "version": "0.6.0",
3
+ "version": "1.0.0",
4
4
  "description": "JSON Schemas, prose contracts, and conformance suite for the skill-map specification.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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: `local` (code runs in-process, returns a report JSON directly) or `invocation-template` (kernel renders a prompt, a runner executes it, the callback closes the job). The `mode` discriminator drives which additional fields are required.",
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": ["local", "invocation-template"],
17
- "description": "`local`: the plugin's code computes the report synchronously, no job file, no runner. `invocation-template`: the kernel renders a prompt + preamble into a job file; a runner executes it; `sm record` closes the job."
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 `invocation-template`; advisory for `local`."
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: invocation-template`; FORBIDDEN when `mode: local`. The template MUST NOT interpolate user text outside `<user-content>` blocks (see `prompt-preamble.md`)."
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": "invocation-template" } } },
71
+ "if": { "properties": { "mode": { "const": "probabilistic" } } },
74
72
  "then": { "required": ["promptTemplateRef", "expectedDurationSeconds"] }
75
73
  },
76
74
  {
77
- "if": { "properties": { "mode": { "const": "local" } } },
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, deterministic workflow that composes rules and/or local actions into a single report. Audits MUST NOT submit LLM-backed actionstheir defining property is reproducibility. An audit that needs probabilistic signal is the wrong shape; emit a `Findings` surface via LLM verbs instead.",
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 ids and/or local action ids the audit executes in sequence. The kernel resolves each id in the registry at load time; a dangling reference disables the audit with status `invalid-manifest`.",
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. Audits do NOT extend `report-base.schema.json` they are deterministic and therefore carry no `safety` / `confidence`. Their shape is kind-specific."
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`. Output MUST be byte-deterministic for the same input graph the snapshot-test suite relies on this.",
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 deterministic: same graph in → same issues out, byte-for-byte. Any source of non-determinism (time, random, network) is forbidden and is a conformance violation.",
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` at the project root. All fields optional; defaults apply when absent. camelCase keys throughout — consistent with the rest of the spec.",
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
  },