@skill-map/spec 0.6.1 → 0.7.1
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 +313 -0
- package/architecture.md +56 -6
- package/cli-contract.md +7 -3
- package/conformance/coverage.md +4 -3
- package/index.json +14 -13
- package/interfaces/security-scanner.md +1 -1
- package/package.json +2 -1
- package/plugin-author-guide.md +335 -0
- package/schemas/extensions/action.schema.json +8 -10
- package/schemas/extensions/adapter.schema.json +1 -1
- package/schemas/extensions/audit.schema.json +3 -3
- package/schemas/extensions/detector.schema.json +7 -1
- package/schemas/extensions/renderer.schema.json +1 -1
- package/schemas/extensions/rule.schema.json +7 -1
- package/schemas/project-config.schema.json +12 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,318 @@
|
|
|
1
1
|
# Spec changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 0463a0f: Step 9.4 — plugin author guide + reference plugin + diagnostics polish.
|
|
8
|
+
**Step 9 fully closed** with this changeset.
|
|
9
|
+
|
|
10
|
+
### Spec — plugin author guide (additive prose)
|
|
11
|
+
|
|
12
|
+
New document at `spec/plugin-author-guide.md` covering:
|
|
13
|
+
|
|
14
|
+
- Discovery roots (`<project>/.skill-map/plugins/`,
|
|
15
|
+
`~/.skill-map/plugins/`, `--plugin-dir <path>`).
|
|
16
|
+
- Manifest fields with the normative schema reference.
|
|
17
|
+
- `specCompat` strategy — narrow ranges pre-`v1.0.0`, `^1.0.0`
|
|
18
|
+
recommendation post-`v1.0.0`.
|
|
19
|
+
- The six extension kinds with one minimal worked example each
|
|
20
|
+
(detector, rule, renderer in full; adapter / audit / action flagged
|
|
21
|
+
for later expansion alongside Step 10).
|
|
22
|
+
- Storage choice (KV vs Dedicated) cross-linking `plugin-kv-api.md`
|
|
23
|
+
and the Step 9.2 triple-protection rule.
|
|
24
|
+
- Execution modes (deterministic / probabilistic) cross-linking
|
|
25
|
+
`architecture.md`.
|
|
26
|
+
- Testkit usage with `runDetectorOnFixture`, `runRuleOnGraph`,
|
|
27
|
+
`runRendererOnGraph`, `makeFakeRunner`.
|
|
28
|
+
- The five plugin statuses (`loaded` / `disabled` / `incompatible-spec`
|
|
29
|
+
/ `invalid-manifest` / `load-error`) and how to read them.
|
|
30
|
+
- Stability section (document is stable; widening additions are minor
|
|
31
|
+
bumps; breaking edits are major).
|
|
32
|
+
|
|
33
|
+
`spec/package.json#files` updated to ship the new doc; `spec/index.json`
|
|
34
|
+
regenerated (57 → 58 hashed files). `coverage.md` unchanged because the
|
|
35
|
+
guide is prose, not a schema.
|
|
36
|
+
|
|
37
|
+
### Reference plugin — `examples/hello-world/`
|
|
38
|
+
|
|
39
|
+
Smallest viable plugin in the principal repo (Arquitecto's pick: in
|
|
40
|
+
the main repo, not separate). One detector (`hello-world-greet`)
|
|
41
|
+
emitting `references` links per `@greet:<name>` token in node bodies.
|
|
42
|
+
Includes:
|
|
43
|
+
|
|
44
|
+
- `plugin.json` declaring one extension and pinning `specCompat: ^1.0.0`.
|
|
45
|
+
- `extensions/greet-detector.mjs` — runtime instance with both
|
|
46
|
+
manifest fields and the `detect` method.
|
|
47
|
+
- `README.md` — what it does, file layout, three-step "try it
|
|
48
|
+
locally" recipe, what's intentionally missing (storage,
|
|
49
|
+
multi-extension, probabilistic mode), pointers for production-grade
|
|
50
|
+
patterns.
|
|
51
|
+
- `test/greet-detector.test.mjs` — four-assertion test using
|
|
52
|
+
`@skill-map/testkit`, runnable via `node --test` with no build step.
|
|
53
|
+
|
|
54
|
+
Verified end-to-end: the example plugin loads cleanly under
|
|
55
|
+
`sm plugins list`, scans contribute its links to the persisted graph,
|
|
56
|
+
and the testkit-based test passes. The example is **not** registered
|
|
57
|
+
as a workspace — it's intentionally standalone so users can copy it.
|
|
58
|
+
|
|
59
|
+
### CLI — diagnostics polish on `PluginLoader.reason`
|
|
60
|
+
|
|
61
|
+
Each failure-mode reason string now carries an actionable hint:
|
|
62
|
+
|
|
63
|
+
- `invalid-manifest` (JSON parse): names the manifest path, suggests
|
|
64
|
+
validating the JSON.
|
|
65
|
+
- `invalid-manifest` (AJV): names the manifest path AND points at
|
|
66
|
+
`spec/schemas/plugins-registry.schema.json#/$defs/PluginManifest`.
|
|
67
|
+
- `invalid-manifest` (specCompat not a valid range): suggests a range
|
|
68
|
+
shape (`"^1.0.0"`).
|
|
69
|
+
- `incompatible-spec`: suggests two remediations (update the plugin's
|
|
70
|
+
`specCompat`, or pin sm to a compatible spec version).
|
|
71
|
+
- `load-error` (extension file not found): includes the absolute
|
|
72
|
+
resolved path, pointer to `plugin.json#/extensions`.
|
|
73
|
+
- `load-error` (default export missing kind): lists the valid kinds.
|
|
74
|
+
- `load-error` (unknown kind): lists the valid kinds.
|
|
75
|
+
- `load-error` (extension manifest schema fails): names the
|
|
76
|
+
per-kind schema (`spec/schemas/extensions/<kind>.schema.json`).
|
|
77
|
+
|
|
78
|
+
6 new tests under `test/plugin-loader.test.ts` (`Step 9.4 diagnostics
|
|
79
|
+
polish` describe block) assert each hint shape is present without
|
|
80
|
+
pinning the full text. Test count 437 → **443 cli + 30 testkit = 473**.
|
|
81
|
+
|
|
82
|
+
### Step 9 closed
|
|
83
|
+
|
|
84
|
+
The four sub-steps — 9.1 (plugin runtime wiring), 9.2 (plugin
|
|
85
|
+
migrations + triple protection), 9.3 (`@skill-map/testkit` workspace),
|
|
86
|
+
9.4 (author guide + reference plugin + diagnostics polish) — together
|
|
87
|
+
turn `skill-map` plugins from "discovered but inert" into a
|
|
88
|
+
first-class authoring surface with documentation, tests, and a
|
|
89
|
+
working reference. Next step: **Step 10 — job subsystem + first
|
|
90
|
+
probabilistic extension** (wave 2 begins).
|
|
91
|
+
|
|
92
|
+
## 0.7.0
|
|
93
|
+
|
|
94
|
+
### Minor Changes
|
|
95
|
+
|
|
96
|
+
- d730094: Spec — Execution modes (deterministic / probabilistic) lifted to a first-class architectural property
|
|
97
|
+
|
|
98
|
+
Frames a meta-property of skill-map that was previously implicit and scattered:
|
|
99
|
+
**every analytical extension is one of two modes** — `deterministic` (pure code,
|
|
100
|
+
runs in scan-time pipelines) or `probabilistic` (invokes an LLM through
|
|
101
|
+
`RunnerPort`, runs only as queued jobs). The dual-mode capability now spans four
|
|
102
|
+
of the six extension kinds; Adapter and Renderer remain locked to deterministic
|
|
103
|
+
because they sit at the system boundaries (filesystem and graph-to-string) where
|
|
104
|
+
non-determinism would break boot reproducibility and snapshot diffing.
|
|
105
|
+
|
|
106
|
+
**Spec changes:**
|
|
107
|
+
|
|
108
|
+
- `architecture.md` — new top-level section **§Execution modes** before
|
|
109
|
+
§Extension kinds. Defines the two modes, the per-kind capability matrix
|
|
110
|
+
(Detector / Rule / Action dual-mode by manifest declaration; Audit dual-mode
|
|
111
|
+
with mode **derived** from `composes[]`; Adapter / Renderer deterministic-only),
|
|
112
|
+
the runtime separation (`deterministic` runs in `sm scan` / `sm check`;
|
|
113
|
+
`probabilistic` runs only via `sm job submit <kind>:<id>`), and the
|
|
114
|
+
`RunnerPort` injection contract for probabilistic extensions.
|
|
115
|
+
- `architecture.md` §Extension kinds — table updated: each row clarifies the
|
|
116
|
+
mode posture (Adapter / Renderer marked deterministic-only; Detector / Rule /
|
|
117
|
+
Action marked dual-mode; Audit marked derived-mode).
|
|
118
|
+
- `architecture.md` §Stability — new clause: execution modes and the per-kind
|
|
119
|
+
capability matrix are stable as of v1.0.0; adding a third mode, changing
|
|
120
|
+
which kinds are dual-mode, or changing the audit's derivation rule is a major
|
|
121
|
+
bump.
|
|
122
|
+
|
|
123
|
+
**Schema changes:**
|
|
124
|
+
|
|
125
|
+
- `schemas/extensions/detector.schema.json`:
|
|
126
|
+
- New optional `mode` field (`deterministic` | `probabilistic`, default
|
|
127
|
+
`deterministic`). Omitting is equivalent to deterministic — keeps existing
|
|
128
|
+
detectors valid without an update.
|
|
129
|
+
- Description updated to spell out the dual-mode contract.
|
|
130
|
+
- `schemas/extensions/rule.schema.json`:
|
|
131
|
+
- Same shape: new optional `mode` field with default `deterministic`.
|
|
132
|
+
- Description rewritten — the previous "Rules MUST be deterministic" claim
|
|
133
|
+
moved into the deterministic-mode contract; probabilistic rules are now
|
|
134
|
+
explicitly allowed and run only as queued jobs.
|
|
135
|
+
- `schemas/extensions/action.schema.json`:
|
|
136
|
+
- **Breaking** — `mode` enum renamed: `local` → `deterministic`,
|
|
137
|
+
`invocation-template` → `probabilistic`. Pre-1.0; no consumers depend on
|
|
138
|
+
the old values (no third-party action plugins shipped). Description, the
|
|
139
|
+
two `if/then` branches, and the `expectedDurationSeconds` /
|
|
140
|
+
`promptTemplateRef` field descriptions updated accordingly.
|
|
141
|
+
- **Bug fix** — the schema previously declared `allOf` twice at the root
|
|
142
|
+
(lines 6–8 and 71–80); the second silently overrode the first, dropping
|
|
143
|
+
`$ref: base.schema.json`. Both blocks are now merged into a single `allOf`
|
|
144
|
+
so the action schema actually composes the base shape.
|
|
145
|
+
- `schemas/extensions/audit.schema.json`:
|
|
146
|
+
- Description rewritten — the "deterministic workflow" claim is replaced by
|
|
147
|
+
the **derived-mode** rule: the audit's effective mode is computed from
|
|
148
|
+
`composes[]` at load time. If every composed primitive is deterministic,
|
|
149
|
+
the audit is deterministic; if any is probabilistic, the audit is
|
|
150
|
+
probabilistic and dispatches as a job. Declaring `mode` directly is a
|
|
151
|
+
load-time error.
|
|
152
|
+
- `composes[]` description updated to mention that each primitive's mode
|
|
153
|
+
participates in derivation; dangling references stay a load-time error.
|
|
154
|
+
- `reportSchemaRef` description updated: probabilistic audits MUST extend
|
|
155
|
+
`report-base.schema.json` (carries `safety` / `confidence`); deterministic
|
|
156
|
+
audits MAY extend it but are not required to.
|
|
157
|
+
- `schemas/extensions/adapter.schema.json`:
|
|
158
|
+
- Description updated to state explicitly that adapters are deterministic-only
|
|
159
|
+
and that `mode` MUST NOT appear. Recommendation for users who want
|
|
160
|
+
LLM-assisted classification: write a probabilistic Detector that emits
|
|
161
|
+
classification hints as `Link[]`.
|
|
162
|
+
- `schemas/extensions/renderer.schema.json`:
|
|
163
|
+
- Description updated to state that renderers are deterministic-only and
|
|
164
|
+
that `mode` MUST NOT appear. Probabilistic narrators of the graph belong
|
|
165
|
+
in jobs and emit Findings, not in renderer manifests.
|
|
166
|
+
|
|
167
|
+
**Why major (despite pre-1.0 minor norm):**
|
|
168
|
+
|
|
169
|
+
Renaming the `Action.mode` enum (`local` → `deterministic`,
|
|
170
|
+
`invocation-template` → `probabilistic`) is breaking by definition. No
|
|
171
|
+
third-party Actions exist yet, but the rename touches the canonical surface and
|
|
172
|
+
deserves the bump. New optional fields on Detector / Rule and the new derived-
|
|
173
|
+
mode contract on Audit are additive and would have been minor on their own.
|
|
174
|
+
|
|
175
|
+
**Implementation work intentionally NOT included here:**
|
|
176
|
+
|
|
177
|
+
- `src/extensions/built-ins.ts` and the per-extension TS files keep working
|
|
178
|
+
unchanged because the new `mode` is optional with `deterministic` default.
|
|
179
|
+
Explicitly threading `mode: 'deterministic'` through every built-in is a
|
|
180
|
+
follow-up.
|
|
181
|
+
- `RunnerPort` injection through `ctx.runner` for probabilistic extensions is
|
|
182
|
+
spec'd here; the actual context plumbing lands with the first probabilistic
|
|
183
|
+
extension (Step 10 — first summarizer). `MockRunner` continues to satisfy
|
|
184
|
+
tests until then.
|
|
185
|
+
- Conformance case `extension-mode-derivation` (audit composes mixed
|
|
186
|
+
primitives → derives `probabilistic`) is mentioned in `architecture.md` and
|
|
187
|
+
pending under `spec/conformance/coverage.md` for the next release.
|
|
188
|
+
- ROADMAP.md rephrase of Steps 10–11 (from "summarizers" to "wave 2:
|
|
189
|
+
probabilistic extensions") and a positioning section in `README.md` follow
|
|
190
|
+
in separate commits to keep this changeset spec-only.
|
|
191
|
+
|
|
192
|
+
### Minor Changes
|
|
193
|
+
|
|
194
|
+
- a73f3f4: Step 7.1 — File watcher (`sm watch` / `sm scan --watch`)
|
|
195
|
+
|
|
196
|
+
Long-running watcher that subscribes to the scan roots, debounces
|
|
197
|
+
filesystem events, and triggers an incremental scan per batch. Reuses
|
|
198
|
+
the existing `runScanWithRenames` pipeline, the `IIgnoreFilter` chain
|
|
199
|
+
(`.skill-mapignore` + `config.ignore` + bundled defaults), and the
|
|
200
|
+
`scan.*` non-job events from `job-events.md` — one ScanResult per
|
|
201
|
+
batch, emitted as ndjson under `--json`.
|
|
202
|
+
|
|
203
|
+
**Spec changes (minor)**:
|
|
204
|
+
|
|
205
|
+
- `spec/schemas/project-config.schema.json` — new `scan.watch` object
|
|
206
|
+
with a single key `debounceMs` (integer ≥ 0, default 300). Groups
|
|
207
|
+
bursts of filesystem events (editor saves, branch switches, npm
|
|
208
|
+
installs) into a single scan pass. Set to 0 to disable debouncing.
|
|
209
|
+
- `spec/cli-contract.md` §Scan — documents `sm watch [roots...]` as
|
|
210
|
+
the primary verb and `sm scan --watch` as the alias. Watcher
|
|
211
|
+
respects the same ignore chain as one-shot scans, emits one
|
|
212
|
+
ScanResult per batch (ndjson under `--json`), closes cleanly on
|
|
213
|
+
`SIGINT` / `SIGTERM`, exits 0 on clean shutdown. Exit-code rule
|
|
214
|
+
carved out for the watcher: per-batch error issues do not flip the
|
|
215
|
+
exit code (the loop keeps running); operational errors still exit 2.
|
|
216
|
+
|
|
217
|
+
No new events. No new ports. The watcher is implementation-defined
|
|
218
|
+
inside the kernel package; a future `WatchPort` can be added when /
|
|
219
|
+
if a non-Node implementation needs to swap the chokidar wrapper.
|
|
220
|
+
|
|
221
|
+
**Runtime changes (minor — new verb + new config key)**:
|
|
222
|
+
|
|
223
|
+
- `chokidar@5.0.0` pinned in `src/package.json` (single new runtime
|
|
224
|
+
dependency, MIT). Chokidar v5 requires Node ≥ 20.19; the project
|
|
225
|
+
already pins `engines.node: ">=24.0"` so this is a no-op for
|
|
226
|
+
consumers. Brings in `readdirp@5` as a transitive.
|
|
227
|
+
- `src/kernel/scan/watcher.ts` — `IFsWatcher` interface + concrete
|
|
228
|
+
`ChokidarWatcher` wrapping `chokidar.watch()` with the existing
|
|
229
|
+
`IIgnoreFilter` plumbed through, debouncer, batch coalescing,
|
|
230
|
+
and explicit `stop()` for clean teardown.
|
|
231
|
+
- `src/cli/commands/watch.ts` — new `WatchCommand`. `sm scan
|
|
232
|
+
--watch` delegates to the same code path so the two surfaces are
|
|
233
|
+
byte-aligned (no parallel implementations).
|
|
234
|
+
- `src/config/defaults.json` — new `scan.watch.debounceMs: 300`
|
|
235
|
+
default.
|
|
236
|
+
|
|
237
|
+
**Why minor (not patch)**: new public verb (`sm watch`), new public
|
|
238
|
+
config key (`scan.watch.debounceMs`), and a new flag on an existing
|
|
239
|
+
verb (`sm scan --watch`). All three are surface additions, not bug
|
|
240
|
+
fixes — minor under both the spec and the runtime semver policies.
|
|
241
|
+
No breaking changes; existing `sm scan` without `--watch` is
|
|
242
|
+
byte-identical to before.
|
|
243
|
+
|
|
244
|
+
**Roadmap**: Step 7 — Robustness, sub-step 7.1 (chokidar watcher).
|
|
245
|
+
Trigger normalization is implicit-already-landed (cabled into every
|
|
246
|
+
detector at Steps 3–4 with full unit tests in
|
|
247
|
+
`src/kernel/trigger-normalize.test.ts`); we do not write a sub-step
|
|
248
|
+
for it. Next sub-steps: 7.2 detector conflict resolution, 7.3 `sm
|
|
249
|
+
job prune` + retention enforcement.
|
|
250
|
+
|
|
251
|
+
### Patch Changes
|
|
252
|
+
|
|
253
|
+
- a73f3f4: Step 7.2 — Detector conflict resolution
|
|
254
|
+
|
|
255
|
+
Two pieces:
|
|
256
|
+
|
|
257
|
+
1. **New built-in rule `link-conflict`** (`src/extensions/rules/link-conflict/`).
|
|
258
|
+
Surfaces detector disagreement. Groups links by `(source, target)` and
|
|
259
|
+
emits one `warn` Issue per pair where the set of distinct `kind` values
|
|
260
|
+
has size ≥ 2. Agreement (single kind across multiple detectors) is
|
|
261
|
+
silent — by design, to avoid massive noise on real graphs.
|
|
262
|
+
Issue payload (`data`) carries `{ source, target, variants }` where
|
|
263
|
+
each `variant` is `{ kind, sources: detectorId[], confidence }`. Variant
|
|
264
|
+
sources are deduped + sorted; confidence is the highest across rows
|
|
265
|
+
of the same kind (`high` > `medium` > `low`).
|
|
266
|
+
|
|
267
|
+
This is the kernel piece of Decision #90 read-time "consumers that
|
|
268
|
+
need uniqueness aggregate at read time" — the rule is one such
|
|
269
|
+
consumer, on the alarming side. Storage stays untouched (one row
|
|
270
|
+
per detector, no merge, no dedup). Severity is `warn`, not `error`:
|
|
271
|
+
the rule cannot pick which kind is correct, so per `cli-contract.md`
|
|
272
|
+
§Exit codes the verb stays exit 0.
|
|
273
|
+
|
|
274
|
+
2. **`sm show` pretty link aggregation** (`src/cli/commands/show.ts`).
|
|
275
|
+
The human renderer now groups `linksOut` / `linksIn` by `(endpoint,
|
|
276
|
+
kind, normalizedTrigger)` and prints one row per group with the
|
|
277
|
+
union of detector ids in a `sources:` field. The section header
|
|
278
|
+
reports both the raw row count and the unique-after-grouping count
|
|
279
|
+
(`Links out (12, 9 unique)`). When N > 1 detector emits the same
|
|
280
|
+
logical link, the row also gets a `(×N)` suffix.
|
|
281
|
+
|
|
282
|
+
`--json` output is byte-identical to before — raw rows, no merge.
|
|
283
|
+
Storage is byte-identical to before. The grouping is purely a
|
|
284
|
+
read-time presentation choice for human eyes.
|
|
285
|
+
|
|
286
|
+
**Spec changes (patch)**:
|
|
287
|
+
|
|
288
|
+
- `spec/cli-contract.md` §Browse — `sm show` row clarifies that pretty
|
|
289
|
+
output groups identical-shape links and that `--json` emits raw rows.
|
|
290
|
+
Patch (not minor) because the JSON contract is unchanged; the human
|
|
291
|
+
output format is non-normative anyway.
|
|
292
|
+
|
|
293
|
+
**Runtime changes (minor — new rule + new presentation)**:
|
|
294
|
+
|
|
295
|
+
- New rule `link-conflict` registered in `src/extensions/built-ins.ts`.
|
|
296
|
+
- `sm show` pretty output groups links + reports unique counts.
|
|
297
|
+
|
|
298
|
+
**UI inspector aggregation deferred to Step 13**: the current Flavor A
|
|
299
|
+
inspector renders the `Relations` card from `node.frontmatter.metadata.{
|
|
300
|
+
related, requires, supersedes, provides, conflictsWith}` directly — it
|
|
301
|
+
does NOT consume `linksOut` / `linksIn` rows from `scan_links`. There
|
|
302
|
+
is no link table to aggregate today. When Step 13's Flavor B lands (Hono
|
|
303
|
+
BFF + WS + full link panel from scan), the aggregation logic from
|
|
304
|
+
`src/cli/commands/show.ts` will need to be ported.
|
|
305
|
+
|
|
306
|
+
**Roadmap**: Step 7 — Robustness, sub-step 7.2 (detector conflict
|
|
307
|
+
resolution). Closes one of the three remaining frentes; 7.3 (`sm job
|
|
308
|
+
prune` + retention) still pending. Decision #90 unchanged: storage
|
|
309
|
+
keeps raw per-detector rows. The `related` vs LLM-amplification
|
|
310
|
+
discussion is documented in `.tmp/skill-map-related-test/` (status
|
|
311
|
+
quo retained — fields stay opt-in under `metadata.*`; revisit if
|
|
312
|
+
real-world amplification appears).
|
|
313
|
+
|
|
314
|
+
**Tests**: 327 → 335 (+8 new for the rule, no regressions).
|
|
315
|
+
|
|
3
316
|
## 0.6.1
|
|
4
317
|
|
|
5
318
|
### 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.
|
|
128
|
-
| **Audit** |
|
|
129
|
-
| **Renderer** | Serializes the graph. | Graph + optional filter. | String (ASCII / Mermaid / DOT / JSON / user-defined). |
|
|
172
|
+
| **Adapter** | Recognizes a platform. Decides which files are nodes and what kind they are. Declares per-kind `defaultRefreshAction` (an action id that drives the probabilistic-refresh surface). Deterministic-only. | Filesystem walk results, candidate path. | `{ kind, adapter } \| null`. |
|
|
173
|
+
| **Detector** | Extracts signals from a node body. Dual-mode: `deterministic` runs in scan, `probabilistic` runs in jobs. | Parsed node (frontmatter + body). | `Link[]`. |
|
|
174
|
+
| **Rule** | Evaluates the graph. Dual-mode: `deterministic` runs in `sm check`, `probabilistic` runs in jobs. | Full graph (nodes + links). | `Issue[]`. |
|
|
175
|
+
| **Action** | Operates on one or more nodes. Dual-mode: `deterministic` (in-process code) or `probabilistic` (rendered prompt the runner executes). | Node(s), optional args. | Deterministic: report JSON. Probabilistic: rendered prompt that a runner executes. |
|
|
176
|
+
| **Audit** | Workflow that composes rules and actions. Effective mode is derived from `composes[]` — deterministic if all composed primitives are deterministic, probabilistic otherwise. Produces a structured report. | Graph + optional scope filter. | Audit report (hardcoded shape, kind-specific). |
|
|
177
|
+
| **Renderer** | Serializes the graph. Deterministic-only. | Graph + optional filter. | String (ASCII / Mermaid / DOT / JSON / user-defined). |
|
|
130
178
|
|
|
131
179
|
### Adapter · `defaultRefreshAction`
|
|
132
180
|
|
|
@@ -267,6 +315,8 @@ The **port list** is stable as of spec v1.0.0. Adding a sixth port is a major bu
|
|
|
267
315
|
|
|
268
316
|
The **extension kind list** (6 kinds) is stable as of spec v1.0.0. Adding a seventh kind is a major bump.
|
|
269
317
|
|
|
318
|
+
The **execution modes** (`deterministic` / `probabilistic`) and the per-kind mode capability matrix above are stable as of spec v1.0.0. Adding a third mode, changing which kinds are dual-mode, or changing the audit's mode-derivation rule is a major bump. Renaming or repurposing the mode enum values is a major bump.
|
|
319
|
+
|
|
270
320
|
The **dependency rules** above are stable as of spec v1.0.0. Relaxing any is a major bump; tightening (forbidding an allowed import) is a minor bump.
|
|
271
321
|
|
|
272
322
|
The **Detector · trigger normalization** pipeline (six steps, in order) is stable from the next spec release. Adding a new step at the end is a minor bump; reordering, removing, or changing any existing step (including the character classes in step 4) is a major bump. Implementations that produce different `normalizedTrigger` output for equivalent input are non-conforming.
|
package/cli-contract.md
CHANGED
|
@@ -166,11 +166,15 @@ Keys are dot-paths (`jobs.minimumTtlSeconds`, `scan.tokenize`). Unknown keys →
|
|
|
166
166
|
| `sm scan` | Full scan. Truncates `scan_*` and repopulates. |
|
|
167
167
|
| `sm scan -n <node.path>` | Partial scan: one node. |
|
|
168
168
|
| `sm scan --changed` | Incremental: only files changed since last scan (mtime heuristic). |
|
|
169
|
+
| `sm scan --watch` | Long-running: watch the roots and trigger an incremental scan after each debounced batch of filesystem events. Alias of `sm watch`. |
|
|
169
170
|
| `sm scan --compare-with <path>` | Delta report: compare current state with a saved scan dump. Does not modify the DB. |
|
|
171
|
+
| `sm watch [roots...]` | Long-running watcher. Same semantics as `sm scan --watch`, exposed as a top-level verb because the watcher is a loop, not a one-shot scan. |
|
|
170
172
|
|
|
171
|
-
`--json` output conforms to `schemas/scan-result.schema.json`.
|
|
173
|
+
`--json` output conforms to `schemas/scan-result.schema.json`. `sm watch` (and `sm scan --watch`) emit one ScanResult per batch — under `--json` this is an `ndjson` stream of ScanResult documents.
|
|
172
174
|
|
|
173
|
-
|
|
175
|
+
The watcher subscribes to the same roots that `sm scan` walks and respects `.skill-mapignore` plus `config.ignore` exactly as the one-shot scan does. Filesystem events are grouped using `scan.watch.debounceMs` (default 300ms) before the watcher re-runs the incremental scan and persists. `SIGINT` / `SIGTERM` close the watcher cleanly. Exit code on clean shutdown is 0.
|
|
176
|
+
|
|
177
|
+
Exit: 0 on clean (or clean watcher shutdown), 1 if error-severity issues exist (one-shot scan only — the watcher does not flip exit code based on per-batch issues), 2 on operational error.
|
|
174
178
|
|
|
175
179
|
---
|
|
176
180
|
|
|
@@ -179,7 +183,7 @@ Exit: 0 on clean, 1 if error-severity issues exist, 2 on operational error.
|
|
|
179
183
|
| Command | Purpose |
|
|
180
184
|
|---|---|
|
|
181
185
|
| `sm list [--kind <k>] [--issue] [--sort-by ...] [--limit N]` | Tabular listing. `--json` emits an array conforming to `node.schema.json`. |
|
|
182
|
-
| `sm show <node.path>` | Node detail: weight (bytes/tokens triple-split), frontmatter, links in/out, issues, findings, summary. `--json` emits a detail object. |
|
|
186
|
+
| `sm show <node.path>` | Node detail: weight (bytes/tokens triple-split), frontmatter, links in/out, issues, findings, summary. `--json` emits a detail object with the raw link rows. Pretty output groups identical-shape links (same endpoint, kind, normalized trigger) onto one line and lists the union of detector ids in a `sources:` field; the section header reports both the raw row count and the unique-after-grouping count, e.g. `Links out (12, 9 unique)`. Storage keeps one row per detector (`scan_links` is unchanged) — the grouping is purely a read-time presentation choice. |
|
|
183
187
|
| `sm check` | Print all current issues. Equivalent to `sm scan --json \| jq '.issues'` but faster (reads from DB). |
|
|
184
188
|
| `sm findings [--kind ...] [--since ...] [--threshold <n>]` | Probabilistic findings (injection, stale summaries, low confidence). `--json` emits an array of finding objects. |
|
|
185
189
|
| `sm graph [--format ascii\|mermaid\|dot]` | Render the full graph via the named renderer. |
|
package/conformance/coverage.md
CHANGED
|
@@ -12,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 `
|
|
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 `
|
|
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.
|
|
193
|
+
"specPackageVersion": "0.7.1",
|
|
194
194
|
"integrity": {
|
|
195
195
|
"algorithm": "sha256",
|
|
196
196
|
"files": {
|
|
197
|
-
"CHANGELOG.md": "
|
|
197
|
+
"CHANGELOG.md": "11a026e881126ac96703de9e3e4e3ddd9ebf7b776ba4d2197ed8c68dce5e6d98",
|
|
198
198
|
"README.md": "8bd57e02d9a9d3f0a4efd18c0f0bd1f4bbe13eb206add0317659e48eab435e7e",
|
|
199
|
-
"architecture.md": "
|
|
200
|
-
"cli-contract.md": "
|
|
199
|
+
"architecture.md": "0ebaacef9e57206bc0dde27ff44a02e0a7def9ae9ceba2f27053b31ff708708b",
|
|
200
|
+
"cli-contract.md": "12ca455496d48a61fc83888808433acf1470f09c261cf1161375b01f0f3f85c4",
|
|
201
201
|
"conformance/README.md": "79c5e63f18a368951dc9f3e31e9bf9574de3f8b97150b2d75365d4febd8eb6dc",
|
|
202
202
|
"conformance/cases/basic-scan.json": "24623da0cad8c8c54b3ff9b09820ea1276fe8b8f0fc680bf6e8abeb4edb8e424",
|
|
203
203
|
"conformance/cases/kernel-empty-boot.json": "175524674b14d993d29f10080d7697074b3a2eee25b359ff903344d73c6acc98",
|
|
204
204
|
"conformance/cases/orphan-detection.json": "7fea6e866d775d09cadb70ccd764f6c8317ca61316c6d187a97cb2466db4e19e",
|
|
205
205
|
"conformance/cases/rename-high.json": "f23513893e25fc4259db06a497906137de981da775d8ab2ef262554d54af5f27",
|
|
206
|
-
"conformance/coverage.md": "
|
|
206
|
+
"conformance/coverage.md": "a9580457cd868638676a450ace478438f832d057ab9c3ad64c088366afc07b7a",
|
|
207
207
|
"conformance/fixtures/minimal-claude/agents/reviewer.md": "d0dd681ba63838301e480116aa09825329f01832b0116de5c5476fdd8a5dcf54",
|
|
208
208
|
"conformance/fixtures/minimal-claude/commands/status.md": "3f36e053fd1c059ffd902f84a55be8a458c26072f97cb37dd7e97314ae2a9bf5",
|
|
209
209
|
"conformance/fixtures/minimal-claude/hooks/pre-commit.md": "ec9cec8ac4ce34d40ec055ffd90e8f06ea3e5764d6ec3ee84e0d97de71b930c7",
|
|
@@ -216,20 +216,21 @@
|
|
|
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": "
|
|
219
|
+
"interfaces/security-scanner.md": "e46d33d6e39b15672c8f7350f1cbd4755534510fe57c679c2b1d0be57577d818",
|
|
220
220
|
"job-events.md": "08796b7fbeb55e5b03cf3bc394224e70a23438a4d15a46ad1d70121c2c68b967",
|
|
221
221
|
"job-lifecycle.md": "1fe88b1a2ed204e41bb41ac172fbb3e912dccd0dd8a1f8ea8e21a681b336d6ee",
|
|
222
|
+
"plugin-author-guide.md": "d8ddba9d47eed4ff973862cb3af5e22b693bb5bede3275df8817bbcebcd7689c",
|
|
222
223
|
"plugin-kv-api.md": "04b2178f46fb88adeae9240df9c9e1761b660396072001dac32cd402e11a2d7d",
|
|
223
224
|
"prompt-preamble.md": "23a8eff0477fbbc46192a27781bc781bda4202bb9c669b7a7a002b0d668146b0",
|
|
224
225
|
"schemas/conformance-case.schema.json": "d69c501bbca079da0ca87685eb4cbdbc2e405334469fc937929ca9134e01a2b3",
|
|
225
226
|
"schemas/execution-record.schema.json": "ec0f3acf1d0ce099c059d73eb434936bfd1bcf12023693bd572efb2a7352faa6",
|
|
226
|
-
"schemas/extensions/action.schema.json": "
|
|
227
|
-
"schemas/extensions/adapter.schema.json": "
|
|
228
|
-
"schemas/extensions/audit.schema.json": "
|
|
227
|
+
"schemas/extensions/action.schema.json": "63736f3efe33e35abcaa12de6d746c405e9bf0927b999bc0d49de3ba948d5831",
|
|
228
|
+
"schemas/extensions/adapter.schema.json": "819a696d4379262b8b1df96a16bc56bc46df60339ddddf4a9d92752dd008d682",
|
|
229
|
+
"schemas/extensions/audit.schema.json": "58b1895fd447cee7d5ed9e8c9139ecd6b0fe11d439903c30ec82f34ece14b24b",
|
|
229
230
|
"schemas/extensions/base.schema.json": "c832a8c9976a7ddc70b8f9226a54de14aa3e85d71bc77ed7a8671a77d599c0e4",
|
|
230
|
-
"schemas/extensions/detector.schema.json": "
|
|
231
|
-
"schemas/extensions/renderer.schema.json": "
|
|
232
|
-
"schemas/extensions/rule.schema.json": "
|
|
231
|
+
"schemas/extensions/detector.schema.json": "a693c17b7e75bcf37eb87f84eea30e89d7aae179b5b89ef5a1cff330c333c029",
|
|
232
|
+
"schemas/extensions/renderer.schema.json": "187e3498d0f3bddb49b9793bca9601fe461ff8d23625069e4c5c8ba18acbb81a",
|
|
233
|
+
"schemas/extensions/rule.schema.json": "75e5adababcf1f0c5c6aaf8009795d49e7a7e196cee13a58940a076429d0be5e",
|
|
233
234
|
"schemas/frontmatter/agent.schema.json": "0e63d7692efb29facccc69472fff48a25f44934618346bfc09738864c6917787",
|
|
234
235
|
"schemas/frontmatter/base.schema.json": "e68fbb85d3e873c4897af776eaf873860bd6e86b5abc1799e801d35c4f7937cf",
|
|
235
236
|
"schemas/frontmatter/command.schema.json": "7b8463ce9c83edd2e3073dd4cd1bbeec4b42e53b03b48bc9a59e540136c2de89",
|
|
@@ -242,7 +243,7 @@
|
|
|
242
243
|
"schemas/link.schema.json": "3e92f5c9def61a857a2c7b22846d82b988157de083463615144ddc92403a489e",
|
|
243
244
|
"schemas/node.schema.json": "14f345fac450f5728c895d1b878e0015eabb9d72ba9da4a8d2236c82933d3fcf",
|
|
244
245
|
"schemas/plugins-registry.schema.json": "92b2052bd06e366709dd6e1449d99408999e33707c4007afc7662980e73c3ef1",
|
|
245
|
-
"schemas/project-config.schema.json": "
|
|
246
|
+
"schemas/project-config.schema.json": "74f8f2ba2c4897ee47a5cc08e27ec3898dc0a938fe7e3823f33f6c5005724d1f",
|
|
246
247
|
"schemas/report-base.schema.json": "a1021e9a59b4df9f99cd92454d797e88469766e7d49f52d231c4645ffdfdad8f",
|
|
247
248
|
"schemas/scan-result.schema.json": "5efe9b1954c5e729c4b55dbc4dd51263d97967d16c0b3cea398877ace74d37b7",
|
|
248
249
|
"schemas/summaries/agent.schema.json": "3d22558eeb170e00c4fc32018a810d27333cc632c9e528ff386100cfdfded087",
|
|
@@ -49,7 +49,7 @@ The Action receives a standard invocation: a single node, or (via `--all`) a set
|
|
|
49
49
|
|
|
50
50
|
i.e. applies to every node. A scanner MAY narrow to specific kinds if the vendor's check only applies to, for example, shell-hook content.
|
|
51
51
|
|
|
52
|
-
Scanners are **
|
|
52
|
+
Scanners are **deterministic-mode** Actions by default: no LLM involvement. The Action runs its own logic (HTTP request to a vendor API, local regex scan, dependency check) and writes a report. Scanners MAY also be `probabilistic` Actions if the scanner relies on model analysis — the same report shape applies.
|
|
53
53
|
|
|
54
54
|
---
|
|
55
55
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skill-map/spec",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "JSON Schemas, prose contracts, and conformance suite for the skill-map specification.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"prompt-preamble.md",
|
|
39
39
|
"db-schema.md",
|
|
40
40
|
"plugin-kv-api.md",
|
|
41
|
+
"plugin-author-guide.md",
|
|
41
42
|
"interfaces/",
|
|
42
43
|
"schemas/",
|
|
43
44
|
"conformance/",
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# Plugin author guide
|
|
2
|
+
|
|
3
|
+
How to ship a third-party `skill-map` plugin: directory layout, manifest fields, the six extension kinds, storage choice, version compatibility, dual-mode posture, and how to test the result with `@skill-map/testkit`.
|
|
4
|
+
|
|
5
|
+
This guide is **descriptive prose**, not the normative contract. The normative pieces live in the schemas and the architecture document — every claim here is cross-linked to its source. When the two disagree, [`architecture.md`](./architecture.md) wins.
|
|
6
|
+
|
|
7
|
+
> **Status.** Ships with spec v1.0.0. The author surface is intended to stay stable through the v1.x line; widening (new extension kind, new storage mode) is a minor bump per [`versioning.md`](./versioning.md).
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
my-plugin/
|
|
15
|
+
├── plugin.json ← manifest (required)
|
|
16
|
+
└── extensions/
|
|
17
|
+
└── detector.mjs ← one file per declared extension
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```jsonc
|
|
21
|
+
// my-plugin/plugin.json
|
|
22
|
+
{
|
|
23
|
+
"id": "my-plugin",
|
|
24
|
+
"version": "1.0.0",
|
|
25
|
+
"specCompat": "^1.0.0",
|
|
26
|
+
"extensions": ["./extensions/detector.mjs"]
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```javascript
|
|
31
|
+
// my-plugin/extensions/detector.mjs
|
|
32
|
+
export default {
|
|
33
|
+
id: 'my-detector',
|
|
34
|
+
kind: 'detector',
|
|
35
|
+
version: '1.0.0',
|
|
36
|
+
emitsLinkKinds: ['references'],
|
|
37
|
+
defaultConfidence: 'high',
|
|
38
|
+
scope: 'body',
|
|
39
|
+
detect(ctx) {
|
|
40
|
+
// ctx.node, ctx.body, ctx.frontmatter — return Link[]
|
|
41
|
+
return [];
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Drop the directory under one of the discovery roots and `sm plugins list` will pick it up.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Discovery
|
|
51
|
+
|
|
52
|
+
The kernel scans two roots, in this order:
|
|
53
|
+
|
|
54
|
+
1. `<project>/.skill-map/plugins/` — committed-with-the-repo plugins.
|
|
55
|
+
2. `~/.skill-map/plugins/` — user-level plugins available across every project.
|
|
56
|
+
|
|
57
|
+
A plugin is any direct child directory containing a `plugin.json`. Nested directories are not searched recursively. Pass `--plugin-dir <path>` to override both roots (mostly for testing).
|
|
58
|
+
|
|
59
|
+
After every change to the `plugins/` folder, run `sm plugins list` to see the load status of each. The five statuses are documented under [Diagnostics](#diagnostics) below.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Manifest
|
|
64
|
+
|
|
65
|
+
Required fields (see [`schemas/plugins-registry.schema.json#/$defs/PluginManifest`](./schemas/plugins-registry.schema.json) for the normative shape):
|
|
66
|
+
|
|
67
|
+
| Field | Type | Notes |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| `id` | kebab-case string | Globally unique. Pattern: `^[a-z][a-z0-9]*(-[a-z0-9]+)*$`. |
|
|
70
|
+
| `version` | semver | Plugin version, independent of `specCompat`. |
|
|
71
|
+
| `specCompat` | semver range | Spec versions this plugin is compatible with. Checked via `semver.satisfies(specVersion, this)` at load time. |
|
|
72
|
+
| `extensions` | string[] | Relative paths to extension files. Each file's default export is the extension's runtime instance. `minItems: 1`. |
|
|
73
|
+
|
|
74
|
+
Optional fields:
|
|
75
|
+
|
|
76
|
+
| Field | Type | Notes |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| `description` | string | One-line summary shown in `sm plugins list`. |
|
|
79
|
+
| `storage` | object | `{ "mode": "kv" }` or `{ "mode": "dedicated", "tables": [...], "migrations": [...] }`. Absent means the plugin does not persist state. |
|
|
80
|
+
| `author` | string | Free-form. |
|
|
81
|
+
| `license` | string | SPDX identifier. |
|
|
82
|
+
| `homepage` | string | URL. |
|
|
83
|
+
| `repository` | string | URL. |
|
|
84
|
+
|
|
85
|
+
### `specCompat` strategy
|
|
86
|
+
|
|
87
|
+
Pre-`v1.0.0` of the spec, narrow ranges are the defensive default — minor bumps **MAY** carry breaking changes per [`versioning.md`](./versioning.md). A plugin that spans minor boundaries can load successfully and crash at first use against a changed schema.
|
|
88
|
+
|
|
89
|
+
After the spec hits v1.0.0, the recommended ranges are:
|
|
90
|
+
|
|
91
|
+
- `"^1.0.0"` — most plugins. Loads against any v1.x.
|
|
92
|
+
- `">=1.0.0 <2.0.0"` — equivalent, more explicit.
|
|
93
|
+
- A pre-release pin (`"^1.0.0-beta.5"`) — only when you depend on a feature added between minors.
|
|
94
|
+
|
|
95
|
+
Authors who explicitly review each minor's changelog **MAY** widen across the next major (`"^1.0.0 || ^2.0.0"`) at their own risk.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## The six extension kinds
|
|
100
|
+
|
|
101
|
+
The kernel knows six categories. Four are dual-mode (deterministic or probabilistic per [`architecture.md` §Execution modes](./architecture.md)); two are deterministic-only because they sit at the system boundaries.
|
|
102
|
+
|
|
103
|
+
| Kind | Method | Receives | Returns | Mode |
|
|
104
|
+
|---|---|---|---|---|
|
|
105
|
+
| `adapter` | `walk(roots, opts)` | filesystem roots | `IRawNode[]` | deterministic only |
|
|
106
|
+
| `detector` | `detect(ctx)` | one node + body + frontmatter | `Link[]` | dual-mode |
|
|
107
|
+
| `rule` | `evaluate(ctx)` | full graph | `Issue[]` | dual-mode |
|
|
108
|
+
| `action` | `run(ctx)` | one or more nodes | execution record | dual-mode |
|
|
109
|
+
| `audit` | `audit(ctx)` | full graph | `TAuditReport` | derived (from `composes[]`) |
|
|
110
|
+
| `renderer` | `render(ctx)` | full graph | `string` | deterministic only |
|
|
111
|
+
|
|
112
|
+
The runtime instance you `export default` from an extension file MUST include both the manifest fields (id, kind, version, plus kind-specific metadata) AND the runtime method. The kernel strips function-typed properties before AJV-validating the manifest shape, so `detect` / `evaluate` / etc. live alongside metadata without confusing the schema.
|
|
113
|
+
|
|
114
|
+
### Detectors
|
|
115
|
+
|
|
116
|
+
Pure single-node analysis. **Never** read another node, the graph, or the database — cross-node reasoning is for rules. Spec at [`schemas/extensions/detector.schema.json`](./schemas/extensions/detector.schema.json).
|
|
117
|
+
|
|
118
|
+
> **Pick a syntax that doesn't collide with built-ins.** The built-in `at-directive` detector fires on any `@token`; the built-in `slash` detector fires on any `/token`. A new detector that also matches one of those prefixes will likely fire on the same input, and if the two emit different `target` shapes the kernel raises a `trigger-collision` error. The example below uses a wikilink-style `[[ref:<name>]]` pattern to side-step this; reserve `@` and `/` for the built-ins.
|
|
119
|
+
|
|
120
|
+
```javascript
|
|
121
|
+
import { normalizeTrigger } from '@skill-map/cli';
|
|
122
|
+
|
|
123
|
+
export default {
|
|
124
|
+
id: 'ref-detector',
|
|
125
|
+
kind: 'detector',
|
|
126
|
+
version: '1.0.0',
|
|
127
|
+
description: 'Detects [[ref:<name>]] tokens in the body.',
|
|
128
|
+
stability: 'experimental',
|
|
129
|
+
emitsLinkKinds: ['references'],
|
|
130
|
+
defaultConfidence: 'medium',
|
|
131
|
+
scope: 'body',
|
|
132
|
+
detect(ctx) {
|
|
133
|
+
const matches = [...ctx.body.matchAll(/\[\[ref:([a-z0-9-]+)\]\]/gi)];
|
|
134
|
+
return matches.map((m) => ({
|
|
135
|
+
source: ctx.node.path,
|
|
136
|
+
target: m[1],
|
|
137
|
+
kind: 'references',
|
|
138
|
+
confidence: 'medium',
|
|
139
|
+
sources: ['ref-detector'],
|
|
140
|
+
trigger: { originalTrigger: m[0], normalizedTrigger: m[0].toLowerCase() },
|
|
141
|
+
}));
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Rules
|
|
147
|
+
|
|
148
|
+
Cross-node reasoning over the merged graph. Run after every adapter and detector has completed. Spec at [`schemas/extensions/rule.schema.json`](./schemas/extensions/rule.schema.json).
|
|
149
|
+
|
|
150
|
+
```javascript
|
|
151
|
+
export default {
|
|
152
|
+
id: 'orphan-skill',
|
|
153
|
+
kind: 'rule',
|
|
154
|
+
version: '1.0.0',
|
|
155
|
+
description: 'Flags skill nodes with zero inbound links.',
|
|
156
|
+
evaluate(ctx) {
|
|
157
|
+
const inboundCount = new Map();
|
|
158
|
+
for (const link of ctx.links) {
|
|
159
|
+
inboundCount.set(link.target, (inboundCount.get(link.target) ?? 0) + 1);
|
|
160
|
+
}
|
|
161
|
+
return ctx.nodes
|
|
162
|
+
.filter((n) => n.kind === 'skill' && (inboundCount.get(n.path) ?? 0) === 0)
|
|
163
|
+
.map((n) => ({
|
|
164
|
+
ruleId: 'orphan-skill',
|
|
165
|
+
severity: 'info',
|
|
166
|
+
message: `Skill ${n.path} has no inbound references.`,
|
|
167
|
+
nodeIds: [n.path],
|
|
168
|
+
}));
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Renderers
|
|
174
|
+
|
|
175
|
+
Graph-to-string serializers. Invoked by `sm graph --format <name>`. Output **MUST** be byte-deterministic for the same input graph (the snapshot-test suite relies on this). Spec at [`schemas/extensions/renderer.schema.json`](./schemas/extensions/renderer.schema.json).
|
|
176
|
+
|
|
177
|
+
```javascript
|
|
178
|
+
export default {
|
|
179
|
+
id: 'csv-renderer',
|
|
180
|
+
kind: 'renderer',
|
|
181
|
+
version: '1.0.0',
|
|
182
|
+
format: 'csv',
|
|
183
|
+
contentType: 'text/csv',
|
|
184
|
+
render(ctx) {
|
|
185
|
+
const rows = ['source,target,kind,confidence'];
|
|
186
|
+
for (const link of ctx.links) {
|
|
187
|
+
rows.push([link.source, link.target, link.kind, link.confidence].join(','));
|
|
188
|
+
}
|
|
189
|
+
return rows.join('\n');
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Adapters / Audits / Actions
|
|
195
|
+
|
|
196
|
+
These ship later in the v1.x line as bundled built-ins; the spec already pins their manifest shapes. Until the testkit grows full helpers for them (planned alongside Step 10), authors are encouraged to test them with a live kernel via `sm scan` against a fixture directory rather than in unit tests.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Storage
|
|
201
|
+
|
|
202
|
+
A plugin that needs to persist state declares `storage` in its manifest. Two modes; each is documented in full at [`plugin-kv-api.md`](./plugin-kv-api.md).
|
|
203
|
+
|
|
204
|
+
### Mode A — KV
|
|
205
|
+
|
|
206
|
+
```jsonc
|
|
207
|
+
{ "storage": { "mode": "kv" } }
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Backed by the kernel-owned `state_plugin_kvs` table. The plugin gets `ctx.store` with `get` / `set` / `list` / `delete`. No migrations to write, ready immediately.
|
|
211
|
+
|
|
212
|
+
Pick KV when your state is a small map (less than ~1 MB total, simple key lookup or prefix list). 90 % of plugins fit.
|
|
213
|
+
|
|
214
|
+
### Mode B — Dedicated
|
|
215
|
+
|
|
216
|
+
```jsonc
|
|
217
|
+
{
|
|
218
|
+
"storage": {
|
|
219
|
+
"mode": "dedicated",
|
|
220
|
+
"tables": ["plugin_my_plugin_items", "plugin_my_plugin_history"],
|
|
221
|
+
"migrations": ["./migrations/001_init.sql"]
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
The plugin owns SQL tables prefixed `plugin_<normalizedId>_*`. Migrations live under `<plugin-dir>/migrations/NNN_<name>.sql` and apply through `sm db migrate` (mixed with kernel migrations, after them).
|
|
227
|
+
|
|
228
|
+
Pick Dedicated when you need indexes, joins, or relational shape.
|
|
229
|
+
|
|
230
|
+
#### Triple protection
|
|
231
|
+
|
|
232
|
+
Every DDL or DML object a plugin migration creates / alters / drops MUST live in the `plugin_<normalizedId>_*` namespace. The kernel enforces this in three places:
|
|
233
|
+
|
|
234
|
+
1. **Discovery (Layer 1)**: every pending migration file is parsed and validated before any of them run. A bad file aborts the whole batch with no DB writes.
|
|
235
|
+
2. **Apply (Layer 2)**: the same validator re-runs immediately before `db.exec(sql)`, defending against TOCTOU edits between discovery and apply.
|
|
236
|
+
3. **Catalog assertion (Layer 3)**: `sqlite_master` is swept after each plugin's batch commits; any new object outside the prefix is reported as an intrusion (exit 2).
|
|
237
|
+
|
|
238
|
+
Forbidden in plugin migrations: `BEGIN` / `COMMIT` / `ROLLBACK` / `SAVEPOINT` / `PRAGMA` / `ATTACH` / `DETACH` / `VACUUM` / `REINDEX` / `ANALYZE`. The runner wraps each migration in its own transaction. Schema qualifiers other than `main.` are also rejected.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Execution modes
|
|
243
|
+
|
|
244
|
+
Detector / Rule / Action declare `mode` in the manifest with default `deterministic`. Audit forbids `mode` — the kernel derives it from `composes[]` at load time. Adapter / Renderer must NOT declare `mode`.
|
|
245
|
+
|
|
246
|
+
```jsonc
|
|
247
|
+
// deterministic detector — default, runs in sm scan
|
|
248
|
+
{ "kind": "detector", "id": "my-detector", "mode": "deterministic", ... }
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
```jsonc
|
|
252
|
+
// probabilistic action — runs only as a queued job, dispatched via `sm job submit action:my-action`
|
|
253
|
+
{ "kind": "action", "id": "my-action", "mode": "probabilistic", ... }
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
A `probabilistic` extension receives `ctx.runner` (a `RunnerPort`) and dispatches its work to the configured LLM runner (CLI, Skill Agent, or in-process per [`architecture.md`](./architecture.md)). It MUST NOT register scan-time hooks; the kernel rejects probabilistic extensions that do.
|
|
257
|
+
|
|
258
|
+
The full per-kind capability matrix lives in [`architecture.md` §Execution modes](./architecture.md).
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Testing with `@skill-map/testkit`
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
npm install --save-dev @skill-map/testkit
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
The testkit ships builders, per-kind context factories, in-memory KV / runner fakes, and high-level `runDetectorOnFixture` / `runRuleOnGraph` / `runRendererOnGraph` helpers. Most plugin tests reduce to one line per assertion.
|
|
269
|
+
|
|
270
|
+
```javascript
|
|
271
|
+
import { test } from 'node:test';
|
|
272
|
+
import { strictEqual } from 'node:assert';
|
|
273
|
+
import { runDetectorOnFixture, node } from '@skill-map/testkit';
|
|
274
|
+
|
|
275
|
+
import detector from '../extensions/detector.mjs';
|
|
276
|
+
|
|
277
|
+
test('emits one reference per [[ref:<name>]] token', async () => {
|
|
278
|
+
const links = await runDetectorOnFixture(detector, {
|
|
279
|
+
body: 'Talk to [[ref:architect]] or [[ref:sre]].',
|
|
280
|
+
context: { node: node({ path: 'a.md' }) },
|
|
281
|
+
});
|
|
282
|
+
strictEqual(links.length, 2);
|
|
283
|
+
strictEqual(links[0].target, 'architect');
|
|
284
|
+
});
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
For rule tests, `runRuleOnGraph(rule, { context: { nodes, links } })` returns the issue array. For renderer tests, `runRendererOnGraph(renderer, { context: { nodes, links, issues } })` returns the rendered string.
|
|
288
|
+
|
|
289
|
+
For probabilistic extensions, `makeFakeRunner()` queues canned responses and records every call:
|
|
290
|
+
|
|
291
|
+
```javascript
|
|
292
|
+
import { makeFakeRunner } from '@skill-map/testkit';
|
|
293
|
+
|
|
294
|
+
const runner = makeFakeRunner();
|
|
295
|
+
runner.queue({ text: '5 nodes summarized' });
|
|
296
|
+
const result = await myAction.run({ runner, ... });
|
|
297
|
+
strictEqual(runner.history[0].action, 'skill-summarizer');
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Full surface in `@skill-map/testkit/index.ts`.
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Diagnostics
|
|
305
|
+
|
|
306
|
+
`sm plugins list` shows every discovered plugin with one of five statuses. When a plugin doesn't behave the way you expect, this is the first thing to check.
|
|
307
|
+
|
|
308
|
+
| Status | Meaning | Common cause |
|
|
309
|
+
|---|---|---|
|
|
310
|
+
| `loaded` | manifest valid, specCompat satisfied, every extension imported and validated. | — |
|
|
311
|
+
| `disabled` | user toggled it off via `sm plugins disable` or `settings.json#/plugins/<id>/enabled`. Manifest parsed; extensions not imported. | Intentional. |
|
|
312
|
+
| `incompatible-spec` | manifest parsed but `semver.satisfies` failed against the installed spec. | Plugin built against an older / newer spec. |
|
|
313
|
+
| `invalid-manifest` | `plugin.json` missing, unparseable, or AJV-fails. | Typo, missing required field, wrong shape. |
|
|
314
|
+
| `load-error` | manifest passed but an extension module failed to import or its default export failed schema validation. | Missing `kind` field, wrong `kind` for the file, runtime import error. |
|
|
315
|
+
|
|
316
|
+
`sm plugins doctor` runs the full load pass and exits 1 if any plugin is in a non-`loaded` / non-`disabled` state. Wire it into CI to catch breakage early.
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## See also
|
|
321
|
+
|
|
322
|
+
- [`architecture.md`](./architecture.md) — extension contract, ports, execution modes.
|
|
323
|
+
- [`plugin-kv-api.md`](./plugin-kv-api.md) — Storage Mode A normative API.
|
|
324
|
+
- [`db-schema.md`](./db-schema.md) — table catalog and migration rules (Mode B).
|
|
325
|
+
- [`schemas/plugins-registry.schema.json`](./schemas/plugins-registry.schema.json) — normative manifest shape.
|
|
326
|
+
- [`schemas/extensions/*.schema.json`](./schemas/extensions) — per-kind manifest schemas.
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## Stability
|
|
331
|
+
|
|
332
|
+
- Document status: **stable** as of spec v1.0.0. Future minor revisions add new sections (e.g. richer testkit coverage when actions / audits gain helpers); breaking edits to the documented surface require a major bump per [`versioning.md`](./versioning.md).
|
|
333
|
+
- The five plugin statuses (`loaded` / `disabled` / `incompatible-spec` / `invalid-manifest` / `load-error`) are stable; adding a sixth status is a minor bump.
|
|
334
|
+
- The recommended `specCompat` strategy is descriptive prose; revising the recommendation does not require a spec bump as long as the schema stays unchanged.
|
|
335
|
+
- The example code blocks track the public TypeScript surface of `@skill-map/cli`; bumping their imports follows the cli's own semver.
|
|
@@ -2,10 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://skill-map.dev/spec/v0/extensions/action.schema.json",
|
|
4
4
|
"title": "ExtensionAction",
|
|
5
|
-
"description": "Manifest shape for an `Action` extension. An action operates on one or more nodes in one of two modes: `
|
|
6
|
-
"allOf": [
|
|
7
|
-
{ "$ref": "base.schema.json" }
|
|
8
|
-
],
|
|
5
|
+
"description": "Manifest shape for an `Action` extension. An action operates on one or more nodes in one of two modes: `deterministic` (code runs in-process, returns a report JSON directly) or `probabilistic` (kernel renders a prompt, a runner executes it against an LLM, the callback closes the job). The `mode` discriminator drives which additional fields are required. See `architecture.md` §Execution modes for the cross-extension contract.",
|
|
9
6
|
"type": "object",
|
|
10
7
|
"required": ["id", "kind", "version", "mode", "reportSchemaRef"],
|
|
11
8
|
"unevaluatedProperties": false,
|
|
@@ -13,8 +10,8 @@
|
|
|
13
10
|
"kind": { "const": "action" },
|
|
14
11
|
"mode": {
|
|
15
12
|
"type": "string",
|
|
16
|
-
"enum": ["
|
|
17
|
-
"description": "`
|
|
13
|
+
"enum": ["deterministic", "probabilistic"],
|
|
14
|
+
"description": "`deterministic`: the plugin's code computes the report synchronously, no job file, no runner. `probabilistic`: the kernel renders a prompt + preamble into a job file; a runner executes it via `RunnerPort`; `sm record` closes the job."
|
|
18
15
|
},
|
|
19
16
|
"reportSchemaRef": {
|
|
20
17
|
"type": "string",
|
|
@@ -23,11 +20,11 @@
|
|
|
23
20
|
"expectedDurationSeconds": {
|
|
24
21
|
"type": "integer",
|
|
25
22
|
"minimum": 1,
|
|
26
|
-
"description": "Best-effort estimate of wall-clock duration. Drives TTL (`ttl = max(expectedDurationSeconds × graceMultiplier, minimumTtlSeconds)`). Required for `
|
|
23
|
+
"description": "Best-effort estimate of wall-clock duration. Drives TTL (`ttl = max(expectedDurationSeconds × graceMultiplier, minimumTtlSeconds)`). Required for `probabilistic`; advisory for `deterministic`."
|
|
27
24
|
},
|
|
28
25
|
"promptTemplateRef": {
|
|
29
26
|
"type": "string",
|
|
30
|
-
"description": "Path (relative to the extension file) to the prompt template the kernel renders at `sm job submit`. REQUIRED when `mode:
|
|
27
|
+
"description": "Path (relative to the extension file) to the prompt template the kernel renders at `sm job submit`. REQUIRED when `mode: probabilistic`; FORBIDDEN when `mode: deterministic`. The template MUST NOT interpolate user text outside `<user-content>` blocks (see `prompt-preamble.md`)."
|
|
31
28
|
},
|
|
32
29
|
"precondition": {
|
|
33
30
|
"type": "object",
|
|
@@ -69,12 +66,13 @@
|
|
|
69
66
|
}
|
|
70
67
|
},
|
|
71
68
|
"allOf": [
|
|
69
|
+
{ "$ref": "base.schema.json" },
|
|
72
70
|
{
|
|
73
|
-
"if": { "properties": { "mode": { "const": "
|
|
71
|
+
"if": { "properties": { "mode": { "const": "probabilistic" } } },
|
|
74
72
|
"then": { "required": ["promptTemplateRef", "expectedDurationSeconds"] }
|
|
75
73
|
},
|
|
76
74
|
{
|
|
77
|
-
"if": { "properties": { "mode": { "const": "
|
|
75
|
+
"if": { "properties": { "mode": { "const": "deterministic" } } },
|
|
78
76
|
"then": { "not": { "required": ["promptTemplateRef"] } }
|
|
79
77
|
}
|
|
80
78
|
]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://skill-map.dev/spec/v0/extensions/adapter.schema.json",
|
|
4
4
|
"title": "ExtensionAdapter",
|
|
5
|
-
"description": "Manifest shape for an `Adapter` extension. An adapter recognizes a platform (Claude Code, Codex, Gemini, Obsidian vault, generic MD) and classifies each candidate file into a node `kind`. Exactly zero or one adapter MUST match any given file; multiple matches → the kernel emits an issue `adapter-ambiguous` and the file is left unclassified. Stability: stable as of spec v1.0.0 except where noted.",
|
|
5
|
+
"description": "Manifest shape for an `Adapter` extension. An adapter recognizes a platform (Claude Code, Codex, Gemini, Obsidian vault, generic MD) and classifies each candidate file into a node `kind`. Exactly zero or one adapter MUST match any given file; multiple matches → the kernel emits an issue `adapter-ambiguous` and the file is left unclassified. Adapters are deterministic-only — they sit at the filesystem boundary and run during boot; probabilistic classification would make boot slow, costly, and non-reproducible. The `mode` field MUST NOT appear in adapter manifests. If you need LLM-assisted classification, write a probabilistic Detector that emits classification hints as `Link[]`. Stability: stable as of spec v1.0.0 except where noted.",
|
|
6
6
|
"allOf": [
|
|
7
7
|
{ "$ref": "base.schema.json" }
|
|
8
8
|
],
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://skill-map.dev/spec/v0/extensions/audit.schema.json",
|
|
4
4
|
"title": "ExtensionAudit",
|
|
5
|
-
"description": "Manifest shape for an `Audit` extension. An audit is a hardcoded
|
|
5
|
+
"description": "Manifest shape for an `Audit` extension. An audit is a hardcoded workflow that composes rules and actions into a single report. The audit's execution mode is NOT declared in the manifest — it is **derived** from the modes of the primitives it composes: if every composed primitive is `deterministic`, the audit's effective mode is `deterministic` and runs synchronously inside `sm audit <id>`; if any composed primitive is `probabilistic`, the audit's effective mode is `probabilistic` and dispatches as a queued job (`sm job submit audit:<id>`). Declaring `mode` in the manifest is a load-time error. See `architecture.md` §Execution modes for the full derivation contract.",
|
|
6
6
|
"allOf": [
|
|
7
7
|
{ "$ref": "base.schema.json" }
|
|
8
8
|
],
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"kind": { "const": "audit" },
|
|
14
14
|
"composes": {
|
|
15
15
|
"type": "array",
|
|
16
|
-
"description": "Ordered list of rule
|
|
16
|
+
"description": "Ordered list of rule and action references the audit executes in sequence. The kernel resolves each reference in the registry at load time; a dangling reference (id not found, kind mismatch, or primitive disabled) disables the audit with status `invalid-manifest`. Each composed primitive's `mode` participates in the audit's mode derivation.",
|
|
17
17
|
"minItems": 1,
|
|
18
18
|
"items": {
|
|
19
19
|
"type": "object",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"reportSchemaRef": {
|
|
34
34
|
"type": "string",
|
|
35
|
-
"description": "Reference to the JSON Schema of the audit's report shape.
|
|
35
|
+
"description": "Reference to the JSON Schema of the audit's report shape. Probabilistic audits MUST extend `report-base.schema.json` (carries `safety` / `confidence` per the report-base contract). Deterministic audits MAY extend it but are not required to."
|
|
36
36
|
},
|
|
37
37
|
"exitCodeMap": {
|
|
38
38
|
"type": "object",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://skill-map.dev/spec/v0/extensions/detector.schema.json",
|
|
4
4
|
"title": "ExtensionDetector",
|
|
5
|
-
"description": "Manifest shape for a `Detector` extension. A detector consumes a parsed node (frontmatter + body) and emits `Link[]` pointing to other nodes or to external URLs (the latter only if it is the designated URL-counter detector). Detectors run in isolation: they MUST NOT read other nodes, the graph, or the DB. Cross-node reasoning lives in Rules.",
|
|
5
|
+
"description": "Manifest shape for a `Detector` extension. A detector consumes a parsed node (frontmatter + body) and emits `Link[]` pointing to other nodes or to external URLs (the latter only if it is the designated URL-counter detector). Detectors run in isolation: they MUST NOT read other nodes, the graph, or the DB. Cross-node reasoning lives in Rules. Detectors are dual-mode: `deterministic` detectors run synchronously inside `sm scan`; `probabilistic` detectors invoke an LLM through the kernel's `RunnerPort` and execute only as queued jobs (never during scan). See `architecture.md` §Execution modes for the full contract.",
|
|
6
6
|
"allOf": [
|
|
7
7
|
{ "$ref": "base.schema.json" }
|
|
8
8
|
],
|
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
"unevaluatedProperties": false,
|
|
12
12
|
"properties": {
|
|
13
13
|
"kind": { "const": "detector" },
|
|
14
|
+
"mode": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"enum": ["deterministic", "probabilistic"],
|
|
17
|
+
"default": "deterministic",
|
|
18
|
+
"description": "`deterministic` (default): pure code, runs synchronously during `sm scan`. Same input → same output, every run. `probabilistic`: invokes an LLM via `ctx.runner` and runs only as a queued job (`sm job submit detector:<id>`); never participates in `sm scan`. The kernel rejects probabilistic detectors that try to register scan-time hooks at load time. Omitting the field is equivalent to declaring `deterministic`."
|
|
19
|
+
},
|
|
14
20
|
"emitsLinkKinds": {
|
|
15
21
|
"type": "array",
|
|
16
22
|
"description": "Subset of `Link.kind` values this detector is allowed to emit. Emitting an unlisted kind at runtime → kernel rejects the link and logs `detector-kind-violation`.",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://skill-map.dev/spec/v0/extensions/renderer.schema.json",
|
|
4
4
|
"title": "ExtensionRenderer",
|
|
5
|
-
"description": "Manifest shape for a `Renderer` extension. A renderer serializes the graph (or a filtered subgraph) into a string in a declared format. Renderers are invoked by `sm graph --format <format>` and `sm export`.
|
|
5
|
+
"description": "Manifest shape for a `Renderer` extension. A renderer serializes the graph (or a filtered subgraph) into a string in a declared format. Renderers are invoked by `sm graph --format <format>` and `sm export`. Renderers are deterministic-only — they sit at the graph-to-string boundary and their output MUST be byte-deterministic for the same input graph (the snapshot-test suite relies on this). The `mode` field MUST NOT appear in renderer manifests. Probabilistic narrators of the graph are a valid product but they live in jobs and emit Findings, not in renderers.",
|
|
6
6
|
"allOf": [
|
|
7
7
|
{ "$ref": "base.schema.json" }
|
|
8
8
|
],
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://skill-map.dev/spec/v0/extensions/rule.schema.json",
|
|
4
4
|
"title": "ExtensionRule",
|
|
5
|
-
"description": "Manifest shape for a `Rule` extension. A rule consumes the full graph (nodes + links) after all detectors have run and emits `Issue[]`. Rules MUST be
|
|
5
|
+
"description": "Manifest shape for a `Rule` extension. A rule consumes the full graph (nodes + links) after all detectors have run and emits `Issue[]`. Rules are dual-mode: `deterministic` rules MUST be byte-for-byte reproducible (same graph in → same issues out; time, random, and network are forbidden) and run synchronously inside `sm check` / `sm scan`. `probabilistic` rules invoke an LLM through the kernel's `RunnerPort` and execute only as queued jobs (`sm job submit rule:<id>`); their output MAY vary across runs and they NEVER participate in `sm scan`. See `architecture.md` §Execution modes for the full contract.",
|
|
6
6
|
"allOf": [
|
|
7
7
|
{ "$ref": "base.schema.json" }
|
|
8
8
|
],
|
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
"unevaluatedProperties": false,
|
|
12
12
|
"properties": {
|
|
13
13
|
"kind": { "const": "rule" },
|
|
14
|
+
"mode": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"enum": ["deterministic", "probabilistic"],
|
|
17
|
+
"default": "deterministic",
|
|
18
|
+
"description": "`deterministic` (default): pure code, byte-for-byte reproducible, runs during `sm check` and `sm scan`. `probabilistic`: invokes an LLM via `ctx.runner` and runs only as a queued job; never participates in scan-time pipelines. The kernel rejects probabilistic rules that try to register scan-time hooks at load time. Omitting the field is equivalent to declaring `deterministic`."
|
|
19
|
+
},
|
|
14
20
|
"emitsRuleIds": {
|
|
15
21
|
"type": "array",
|
|
16
22
|
"description": "List of `rule_id` values this rule may emit on issues. Typically a singleton (`trigger-collision` → emits `trigger-collision`). A rule emitting a `rule_id` not in this list → kernel logs `rule-id-violation` but keeps the issue (forward compatibility).",
|
|
@@ -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
|
},
|