@skill-map/spec 0.6.1 → 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,229 @@
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
+
3
227
  ## 0.6.1
4
228
 
5
229
  ### Patch 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,7 +12,7 @@ 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`. |
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
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`. |
@@ -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/index.json CHANGED
@@ -190,20 +190,20 @@
190
190
  }
191
191
  ]
192
192
  },
193
- "specPackageVersion": "0.6.1",
193
+ "specPackageVersion": "1.0.0",
194
194
  "integrity": {
195
195
  "algorithm": "sha256",
196
196
  "files": {
197
- "CHANGELOG.md": "73e7db22a362dfe6b1d7aa8f456d57d2106936b831c7de6fc9b44c9f7f9642a2",
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": "ef98b87b70c46d7deb9853a8015c3e366a296088a70e13e4ffe223d91b9b4622",
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",
@@ -216,20 +216,20 @@
216
216
  "conformance/fixtures/rename-high-after/skills/bar.md": "16f7678829c7702f8ebaeef920a891756da198466a1884badd8d8b4a7d1bab6a",
217
217
  "conformance/fixtures/rename-high-before/skills/foo.md": "16f7678829c7702f8ebaeef920a891756da198466a1884badd8d8b4a7d1bab6a",
218
218
  "db-schema.md": "002224f629403a247c0243d4b242c1e35e28bd93073ea53137ec1d30084d9bd7",
219
- "interfaces/security-scanner.md": "81dc3dc2c439a75f4603b6d52e714f44ac564032c8aa424385ebbf4502adae3e",
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": "a37acdd6198e38dfc429161d92988170ddac91c6e98969e0aaaa8d717f5b9ba3",
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.1",
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).",
@@ -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
  },