@skill-map/spec 0.13.0 → 0.14.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,2605 +1,140 @@
1
1
  # Spec changelog
2
2
 
3
- ## 0.13.0
3
+ ## 0.14.0
4
4
 
5
5
  ### Minor Changes
6
6
 
7
- - e0fb57e: Step 14.2 REST read-side endpoints + DataSource contract
7
+ - 8f2a66d: Bare `sm` defaults to `sm serve` instead of printing help
8
8
 
9
- Fills the `### Server` subsection's endpoint catalogue from the v14.1 stub
10
- (`/api/health` real, `/api/*` 404) to the eight read-side endpoints the
11
- Angular SPA at 14.3 will consume. New `spec/schemas/api/rest-envelope.schema.json`
12
- formalises the list-envelope shape. Test totals 764 832 (+68).
9
+ `sm` invoked with no arguments now starts the Web UI server when a
10
+ `.skill-map/` project exists in the current working directory
11
+ (equivalent to `sm serve`). When no project is found, it prints a
12
+ one-line hint pointing to `sm init` and `sm --help` on stderr and
13
+ exits with code `2`. `sm --help` and `sm -h` continue to print
14
+ top-level help — help is now reserved for explicit flags.
13
15
 
14
- **Files added (server)**
16
+ **Spec change** (`spec/cli-contract.md` §Binary): the prior wording —
17
+ _"`sm`, `sm --help`, `sm -h` MUST all print top-level help"_ — is
18
+ replaced by two separate clauses. Help invocation requires `--help` or
19
+ `-h`; bare invocation routes to the server with the hint-and-exit
20
+ fallback when no project exists.
15
21
 
16
- - `src/server/path-codec.ts` — `encodeNodePath` / `decodeNodePath`. Base64url (RFC 4648 §5, no padding). Mirrored at `ui/src/services/data-source/path-codec.ts` in 14.3.
17
- - `src/server/envelope.ts` list / single / value envelope builders. `REST_ENVELOPE_SCHEMA_VERSION = '1'`. Hardcoded to track `spec/schemas/api/rest-envelope.schema.json#/properties/schemaVersion/const`.
18
- - `src/server/query-adapter.ts` `urlParamsToExportQuery(params)` lifts URL search params into the kernel's `IExportQuery` via `parseExportQuery` (one grammar, two transports). `filterNodesWithoutIssues` post-filter handles `hasIssues=false` (the one filter the kernel grammar can't express).
19
- - `src/server/routes/deps.ts` shared `IRouteDeps` bag (`options`, `runtimeContext`).
20
- - `src/server/routes/health.ts` — extracted from `app.ts` for symmetry with the other routes (no behavior change).
21
- - `src/server/routes/scan.ts` — `/api/scan` + `/api/scan?fresh=1`. DB absent → returns the empty `ScanResult` shape (matches the `loadScanResult` synthetic fallback). `?fresh=1` rejects when the server was started with `--no-built-ins` or `--no-plugins`.
22
- - `src/server/routes/nodes.ts` — `/api/nodes/:pathB64` (single) registered BEFORE `/api/nodes` (list) so the param doesn't shadow the literal prefix. Pagination defaults `offset=0`, `limit=100`; max `limit=1000`.
23
- - `src/server/routes/links.ts` — `/api/links?kind=&from=&to=`.
24
- - `src/server/routes/issues.ts` — `/api/issues?severity=&ruleId=&node=`. `ruleId` filter mirrors `sm check`'s qualified-or-suffix match.
25
- - `src/server/routes/graph.ts` — `/api/graph?format=ascii|json|md`. Per-format content-type. Unknown format → `bad-query` 400.
26
- - `src/server/routes/config.ts` — `/api/config`. Wraps `loadConfig` from the kernel. Layered-loader warnings forwarded to `process.stderr`.
27
- - `src/server/routes/plugins.ts` — `/api/plugins`. Built-ins (gated by `noBuiltIns`) + drop-ins (gated by `noPlugins`). `source: 'built-in' | 'project' | 'global'` derived from the plugin's filesystem path against `defaultProjectPluginsDir`.
22
+ **CLI change** (`src/cli/entry.ts`): empty argv is intercepted before
23
+ Clipanion sees it. If `defaultProjectDbPath(cwd)` exists, the args
24
+ are rewritten to `['serve']`. Otherwise the hint is printed via the
25
+ `tx()` i18n shim and the process exits `2`. `RootHelpCommand` no
26
+ longer carries `Command.Default`; it remains the handler for `--help`
27
+ and `-h` only.
28
28
 
29
- **Files edited (server)**
29
+ **Why pre-1.0 minor instead of major**: `spec/` and `src/` are both
30
+ in `0.Y.Z`. Per `spec/versioning.md` §Pre-1.0, breaking changes ship
31
+ as minor bumps until the deliberate 1.0 stabilization. The conformance
32
+ suite required no updates (no case asserted bare-sm = help).
30
33
 
31
- - `src/server/app.ts` — `IAppDeps` gains `runtimeContext` (mandatory). Routes registered via the new `routes/*` registrars BEFORE the `/api/*` 404 catch-all. `app.onError` extended to map `ExportQueryError` → 400 `bad-query` (alongside the existing HTTPException + uncaught-Error branches).
32
- - `src/server/index.ts` — `createServer(options, extra?)` accepts an optional `extra.runtimeContext` so tests can drive against a tempdir scope; production callers (the `sm serve` verb) leave it undefined and the composition root falls back to `defaultRuntimeContext()`.
33
- - `src/server/i18n/server.texts.ts` — adds error message templates: `dbMissingHint`, `freshScanRequiresPipeline`, `graphUnknownFormat`, `paginationLimitTooLarge`, `paginationInvalidInteger`, `nodeNotFound`, `pathB64Malformed`.
34
+ ## 0.13.1
34
35
 
35
- **Tests added (68)**
36
-
37
- - `src/test/server-endpoints.test.ts` (24) — happy + error path per endpoint. Uses real `runScan` + `persistScanResult` against a `mkdtempSync` fixture (no `:memory:` per `feedback_sqlite_in_memory_workaround.md`).
38
- - `src/test/server-pagination.test.ts` (10) — default page caps at 100, `?limit=1000` accepted, `?limit=1001` rejected, offset/limit boundaries, `?offset=-1` and `?offset=foo` rejected, offset past total returns empty + preserves total.
39
- - `src/test/server-errors.test.ts` (8) — every `code` value maps to the documented HTTP status; canonical envelope shape on every error response.
40
- - `src/test/server-query-adapter.test.ts` (16) — URL-param → IExportQuery matrix; `filterNodesWithoutIssues` post-filter behaviour.
41
- - `src/test/server-path-codec.test.ts` (10) — round-trip on POSIX / unicode / spaces / very long paths; rejection of empty, non-alphabet, single-char inputs; uniqueness for distinct inputs.
42
-
43
- **Spec**
44
-
45
- - `spec/schemas/api/rest-envelope.schema.json` — new schema. `$id: https://skill-map.dev/spec/v0/api/rest-envelope.schema.json`. `oneOf` enforces that an envelope carries exactly one of `items` / `item` / `value` per kind (with sentinel kinds `health` / `scan` / `graph` reserved for routes that don't use the envelope).
46
- - `spec/cli-contract.md` `### Server` — endpoint table expanded from 4 rows (v14.1 surface) to 12 rows (v14.2 surface) with full filters / status / shape per row. Error code source enumeration added (`not-found` / `bad-query` / `internal` / reserved `db-missing`). Stability stays `experimental — locks at v0.6.0`.
47
- - `spec/CHANGELOG.md` `[Unreleased]` `### Minor` — entry for BFF endpoints + envelope schema.
48
- - `spec/conformance/coverage.md` — row 25 added for `api/rest-envelope.schema.json` (status: 🔴 missing — implementation-side coverage exists in `src/test/server-endpoints.test.ts`; a kernel-agnostic conformance case is still required before v1.0.0 ships).
49
- - `spec/index.json` — regenerated (40 → 41 files hashed).
50
-
51
- **Decisions during implementation (flag for orchestrator)**
52
-
53
- - The `db-missing` error code is kept in the documented enum but no v14.2 route currently emits it — `/api/scan` returns the empty `ScanResult` when the DB is absent, list routes return zero items, and `/api/health` already advertises `db: 'missing'`. Documented in the spec as "reserved for future endpoints (post-v0.6.0 mutations) where degradation is not safe". Removing the code would be a breaking change to the envelope contract; keeping it costs nothing.
54
- - `ExportQueryError` from `parseExportQuery` is funneled to `bad-query` 400 via a new branch in `app.onError`. The brief listed it as a route-level concern; centralising in the global handler means future routes that go through the kernel grammar (e.g. a future `/api/export?q=...`) inherit the same envelope mapping for free.
55
- - `urlParamsToExportQuery` builds a canonical raw query string and re-parses it through `parseExportQuery` instead of constructing `IExportQuery` directly. The extra parse is microseconds and guarantees the BFF and `sm export` can never drift on what counts as a valid filter token. When the grammar grows (e.g. `has=findings` post-Step 11), only `parseExportQuery` changes.
56
- - `/api/scan?fresh=1` rejection on `--no-built-ins` / `--no-plugins` matches Decision §14.1's intent: the BFF surface should not silently produce empty results that look indistinguishable from "your project has no nodes". The `bad-query` envelope tells the operator they're holding a knife by the blade.
57
- - Tests use `noPlugins: true` by default to keep them deterministic against `process.cwd()` — `loadPluginRuntime` walks the live cwd's plugins dir, which would surface ambient plugins from the test runner's host (none in CI today, but a developer running tests locally with their own plugins installed would see flake).
58
- - The route registration order in `app.ts` is documented in the file's header comment. `/api/nodes/:pathB64` MUST register before `/api/nodes` (Hono matches in declaration order; the literal prefix wins otherwise).
59
-
60
- - d5488bf: Step 14.4.a — BFF WS broadcaster + chokidar wiring + scan event emission
61
-
62
- First half of Step 14.4 lands. The BFF's `/ws` endpoint flips from
63
- "upgrade-only stub" to a real broadcaster fed by a chokidar
64
- filesystem watcher: every debounced batch runs the same
65
- `runScanWithRenames` + persistence pipeline `sm watch` uses, and the
66
- kernel's `ProgressEmitterPort` is bridged directly to the broadcaster
67
- so `scan.*` / `extractor.completed` / `rule.completed` / `extension.error`
68
- events reach every connected client verbatim — no envelope
69
- construction in the BFF for the routine cases. Tests 832 → 854 (+22).
70
-
71
- The UI-side consumer (`WsEventStreamService`) ships separately as
72
- 14.4.b.
73
-
74
- **Files added (server)**
75
-
76
- - `src/server/broadcaster.ts` — `WsBroadcaster` class. Owns the
77
- connected-clients Set, fans `JSON.stringify(envelope)` once across
78
- every open socket, evicts on backpressure (`bufferedAmount > 4 MiB`
79
- → close 1009 + unregister), drains every client with code 1001 +
80
- reason `'server shutdown'` on `shutdown()`. `IBroadcasterClient`
81
- interface is structural so unit tests inject fakes without a real
82
- `WebSocket`.
83
- - `src/server/watcher.ts` — `createWatcherService(deps)` factory.
84
- Wraps `createChokidarWatcher` with `scan.watch.debounceMs` from
85
- config (override via `--watcher-debounce-ms`), runs the kernel scan
86
- pipeline per debounced batch, persists via `withSqlite(...).scans.persist(...)`.
87
- The per-batch `ProgressEmitterPort` bridges every event the kernel
88
- orchestrator emits during the scan to `broadcaster.broadcast(envelope)`.
89
- Per-batch failures log + continue (transient FS errors must not
90
- kill the broadcaster); chokidar instance errors broadcast a
91
- `watcher.error` advisory.
92
- - `src/server/events.ts` — envelope helpers (`IWsEventEnvelope` shape,
93
- `buildWatcherStartedEvent`, `buildWatcherErrorEvent`). The
94
- `watcher.*` events are BFF-internal advisories — non-normative,
95
- prefixed with `watcher.` to flag their non-spec status. Spec-mandated
96
- shapes (`scan.*`, `extractor.completed`, `rule.completed`) are
97
- forwarded verbatim from the kernel emitter, so this file does not
98
- build them.
99
-
100
- **Files added (tests)**
101
-
102
- - `src/test/server-ws-broadcaster.test.ts` (15 tests) — broadcaster
103
- unit tests against fake `IBroadcasterClient` instances. Coverage:
104
- register/unregister/clientCount accounting, broadcast fan-out + JSON
105
- stringify, readyState filter (skip closing/closed), per-client
106
- `send()` failure isolation, backpressure eviction at the documented
107
- threshold (`WS_BACKPRESSURE_BYTES = 4 MiB`), shutdown idempotency
108
- - close-code/reason assertions, post-shutdown register immediate
109
- close, post-shutdown broadcast no-op, circular-envelope serialization
110
- failure handling.
111
- - `src/test/server-ws-integration.test.ts` (7 tests) — end-to-end
112
- against a real server. Boots `createServer({...})` with
113
- `noWatcher: false`, watches a `mkdtempSync` cwd via the
114
- `runtimeContext` override (production callers' cwd would point at the
115
- test runner's repo root). Exercises: initial-batch `scan.completed`
116
- observed by a connected client; multi-client fan-out (one batch fires
117
- to two open clients); `clientCount` decrement on disconnect;
118
- `handle.close()` shuts the watcher cleanly under 2s;
119
- `validateServerOptions` rejects `--no-built-ins + watcher on`;
120
- `--no-watcher` confirms no `scan.*` events fire.
121
-
122
- **Files edited (server)**
123
-
124
- - `src/server/ws.ts` — `noopWebSocketRoute(app)` deleted, replaced
125
- with `attachBroadcasterRoute(app, broadcaster)`. Pulls the underlying
126
- `ws` library `WebSocket` off `WSContext.raw` and registers it on
127
- `onOpen`; unregisters on `onClose` / `onError`. Server-push only —
128
- `onMessage` intentionally not registered at v14.4.a.
129
- - `src/server/index.ts` — `createServer` composition root grows the
130
- broadcaster + watcher lifecycle: instantiate `WsBroadcaster` →
131
- build app (broadcaster threaded into `IAppDeps`) → bind listener →
132
- start watcher (unless `--no-watcher`); `handle.close()` shuts in
133
- order: `watcherService.stop()` → `broadcaster.shutdown()` → http
134
- close → `wss.close()`. `ServerHandle` exposes the `broadcaster`
135
- field for tests asserting `clientCount`.
136
- - `src/server/app.ts` — `IAppDeps.attachWs: TWsRegistrar` removed;
137
- replaced with `IAppDeps.broadcaster: WsBroadcaster`. The BFF wires
138
- `attachBroadcasterRoute` directly inside `createApp` now (route
139
- registrar pattern was the v14.1 scaffolding to allow swap-in at
140
- v14.4 — that work is done, no need for the indirection).
141
- - `src/server/options.ts` — adds `noWatcher: boolean` (default `false`
142
- per Decision #121: a server with stale DB is a footgun) and
143
- `watcherDebounceMs?: number` (override the config value).
144
- Validator gains `watcher-requires-pipeline` (rejects
145
- `--no-built-ins + watcher on` — would persist empty scans on every
146
- batch) and `watcher-debounce-invalid` (non-integer / negative).
147
- - `src/server/i18n/server.texts.ts` — eight new keys for watcher /
148
- broadcaster lifecycle log lines.
149
-
150
- **Files edited (CLI)**
151
-
152
- - `src/cli/commands/serve.ts` — plumbs `--no-watcher` (documented) +
153
- hidden `--watcher-debounce-ms` flag through to `IServerOptionsInput`.
154
- - `src/cli/i18n/serve.texts.ts` — two new keys
155
- (`watcherRequiresPipeline`, `watcherDebounceInvalid`).
156
-
157
- **Files edited (tests)**
158
-
159
- - `src/test/server-boot.test.ts` — the no-broadcaster-yet
160
- close-1000-on-`onOpen` assertion is replaced with a "connection
161
- stays open + registers" assertion. Default options grow
162
- `noWatcher: true` (the watcher is exercised in the dedicated
163
- integration file).
164
- - `src/test/server-{db-missing,endpoints,errors,pagination}.test.ts`
165
- — default options grow `noWatcher: true` so chokidar doesn't
166
- subscribe to the test runner's cwd. No behavior change for these
167
- tests; they exercise the REST surface, not the watcher.
168
-
169
- **Spec**
170
-
171
- - `spec/cli-contract.md` `### Server` — new **WebSocket protocol**
172
- subsection. Documents the wire envelope (delegated to
173
- `job-events.md` §Common envelope), the v14.4.a event catalog
174
- (`scan.started` / `scan.progress` / `scan.completed` plus the
175
- side-effect events `extractor.completed` / `rule.completed` /
176
- `extension.error`, plus the BFF-internal advisories
177
- `watcher.started` / `watcher.error`), the connection lifecycle
178
- (no state push on connect; client polls `/api/scan` to seed; close
179
- codes 1000 / 1001 / 1009), the backpressure rule, and the
180
- loopback-only assumption (no per-connection auth through v0.6.0
181
- per Decision #119). The endpoint table flips `GET /ws` from
182
- `upgrade-only` to `implemented (v14.4.a)`. The `sm serve` flag
183
- table grows `--no-watcher`. The verb-catalog row for `sm serve`
184
- mirrors the new flag.
185
- - `spec/CHANGELOG.md` `[Unreleased]` `### Minor` entry.
186
- - `spec/index.json` — regenerated (41 files hashed; no schema added).
187
-
188
- **ROADMAP.md** — bumped `Last updated`, marked Step 14.4.a landed
189
- (14.4 carries an explicit (a/b) split now), 14.4.b still owes the
190
- UI-side consumer. Earlier 14.3 prose pushed to "Earlier prose".
191
-
192
- **Decisions taken inline (flag for orchestrator)**
193
-
194
- - `issue.added` / `issue.resolved` (per `spec/job-events.md` §Issue
195
- events line 446) **deferred to a follow-up**. The diff requires
196
- comparing the new `ScanResult.issues` set against the prior
197
- persisted snapshot; the watcher already loads the prior for the
198
- rename heuristic, so the data is at hand, but the diff plumbing
199
- (key derivation, set comparison, two emit calls per delta) is
200
- enough material that it deserves its own brief. The 14.4.a surface
201
- fans out exactly what the kernel emitter already produces.
202
- - `scan.failed` **deferred to a follow-up**. The shape is not yet
203
- locked in `spec/job-events.md` and would need a normative
204
- addition. For 14.4.a, per-batch failures log via the kernel logger
205
- and the watcher loop continues — same behavior as `sm watch`'s
206
- `WATCH_TEXTS.batchFailed`.
207
- - `scan.progress` **emitted, not throttled**. The kernel
208
- orchestrator emits one event per node walked; on a small workspace
209
- this is a handful of events per batch, on a large workspace it's
210
- hundreds. The brief flagged throttling as optional at 14.4.a; the
211
- bridge forwards verbatim today. The integration test observed 13
212
- `scan.progress` events for a 4-file fixture, which is fine. A
213
- throttle (250ms aggregation) is the obvious 14.6 polish if the
214
- bundle / perf pass shows the fan-out swamping the channel.
215
- - `watcher.started` / `watcher.error` BFF-internal advisories
216
- **emitted** rather than silent. They give the SPA event-log a
217
- clear "armed" signal and a surface for chokidar errors that don't
218
- fit the spec's `scan.*` shape. Prefix marks them as non-normative;
219
- consumers that follow the spec's "ignore unknown event types"
220
- rule will not break.
221
- - `IHealthResponse.watcher: 'on' | 'off'` **NOT added**. Keeping
222
- the v14.2 health response shape stable was preferable to adding
223
- one field for what tests / `--no-watcher` already cover. The
224
- broadcaster's `clientCount` is exposed on `ServerHandle.broadcaster`
225
- for test introspection without polluting the public health surface.
226
- - The validator rejects `--no-built-ins + watcher on` because the
227
- watcher would persist empty scans on every batch, silently wiping
228
- the DB. `--no-plugins + watcher on` is OK (the built-in pipeline
229
- is still complete on its own).
230
- - `attachBroadcasterRoute` does NOT register `onMessage`. v14.4.a
231
- is server-push only. A future client-initiated heartbeat / filter
232
- request lands at 14.4.b or later.
233
- - `WsBroadcaster` is a class (not a factory) per AGENTS.md
234
- §Adapter wiring rule 5: factories scope to "adapters consumed via
235
- ports", and the broadcaster is a plain BFF helper with no kernel
236
- port to satisfy. The class is grandfathered no-`I*`-prefix per
237
- §Type naming convention category 4.
238
-
239
- **Smoke (live BFF, one-shot per AGENTS.md)**
240
-
241
- The integration tests cover the live boot + WS upgrade + chokidar
242
- batch + broadcast end-to-end against a `mkdtempSync` scope. The
243
- diagnostic line `ws events received: scan.started, scan.progress
244
- × 13, extractor.completed × 4, rule.completed × 5, scan.completed`
245
- confirms the full event sequence reaches a connected client during
246
- a real scan against a 4-file fixture.
247
-
248
- - 4ff3f38: Step 14.5.d — Provider-driven kind presentation + envelope kindRegistry
249
-
250
- Pre-1.0 minor breaking per `versioning.md` § Pre-1.0.
251
-
252
- The Provider extension surface gains the required `kinds[*].ui` field
253
- so each kind a Provider declares carries the presentation metadata the
254
- UI needs to render it (label, base color, optional dark-theme color,
255
- optional emoji, optional icon). The icon is a discriminated union —
256
- `{ kind: 'pi'; id: 'pi-…' }` for PrimeIcons or `{ kind: 'svg'; path:
257
- '…' }` for raw SVG path data. The UI derives `bg` / `fg` tints from
258
- `color` per theme via a deterministic helper, so the Provider declares
259
- one base color per theme rather than four hex values.
260
-
261
- The REST envelope shape (`spec/schemas/api/rest-envelope.schema.json`)
262
- gains a new required `kindRegistry` field on every payload-bearing
263
- variant (`nodes` / `links` / `issues` / `plugins` / `node` / `config`);
264
- sentinel envelopes (`health` / `scan` / `graph`) stay exempt. The
265
- registry is keyed by kind name and carries `{ providerId, label,
266
- color, colorDark?, emoji?, icon? }` — the BFF assembles it once at
267
- boot from every enabled Provider and attaches it to every applicable
268
- response so the UI can render Provider-declared kinds (built-in and
269
- user-plugin alike) without hardcoding a closed kind enum. The change
270
- keeps `schemaVersion` at `'1'` (greenfield — no released consumers
271
- depend on the prior shape).
272
-
273
- **Files edited (spec)**
274
-
275
- - `spec/schemas/extensions/provider.schema.json` — adds `ui` to the
276
- required field set on each `kinds[*]` entry, with discriminated
277
- `oneOf` for `icon`.
278
- - `spec/schemas/api/rest-envelope.schema.json` — new `kindRegistry`
279
- definition; required on every payload-bearing variant; sentinel
280
- variants explicitly forbid the field via `not.anyOf`. Version stays
281
- at `'1'` (greenfield).
282
- - `spec/CHANGELOG.md` — `[Unreleased]` `### Minor` entry.
283
-
284
- **Files edited (kernel + built-in)**
285
-
286
- - `src/kernel/extensions/provider.ts` — adds `IProviderKindUi` and
287
- `IProviderKindIcon`; `ui` becomes required on `IProviderKind`.
288
- - `src/built-in-plugins/providers/claude/index.ts` — every kind
289
- (skill / agent / command / hook / note) declares its `ui` block
290
- reusing the colors / labels / icons previously hardcoded in
291
- `ui/src/styles.css`, `ui/src/i18n/kinds.texts.ts`, and
292
- `ui/src/app/components/kind-icon/kind-icon.html`.
293
- - `src/built-in-plugins/providers/claude/claude.test.ts` — new test
294
- asserts every kind declares a well-formed `ui` block.
295
- - `src/test/external-provider-kind.test.ts` — three mock providers
296
- updated to declare `ui` on their `cursorRule` kinds.
297
- - `src/test/plugins-cli.test.ts` — `dropMockProvider` helper template
298
- declares `ui` on the inline mock `note` kind.
299
-
300
- **Files added (conformance)**
301
-
302
- - `spec/conformance/fixtures/plugin-missing-ui/` — drop-in Provider
303
- fixture whose `kinds[*]` omits `ui` (plus a trivial `notes/example.md`
304
- for the built-in Claude scan to grab).
305
- - `spec/conformance/cases/plugin-missing-ui-rejected.json` — locks the
306
- loader contract: `sm scan --json` exits 0, stderr matches
307
- `plugin bad-provider:.*invalid.*must have required property 'ui'`,
308
- the envelope still contains the built-in Claude provider, and the
309
- one fixture node still gets scanned (one bad plugin does not take
310
- down the scan).
311
-
312
- **Decisions taken inline (flag for orchestrator)**
313
-
314
- - `ui` is required, not optional — making it optional reintroduces the
315
- pre-14.5.d trap of silently collapsing unknown kinds to `'note'`.
316
- The cost (one object per kind in the manifest) is small.
317
- - Icon is a discriminated union (`oneOf` with `kind` discriminator)
318
- rather than two optional fields. Keeps the UI dispatch exhaustive
319
- and AJV validates each variant cleanly.
320
- - `schemaVersion` stays at `'1'` despite the required-field add.
321
- Greenfield — no released consumers; a versioned migration buys
322
- nothing today. Bumps the day a third-party consumer ships against
323
- the wire.
324
- - Severity (PrimeNG `<p-tag>` `severity` enum) is NOT declared by the
325
- Provider. The UI tints kind tags with the registry's `color`
326
- directly, avoiding a Provider-side dependency on a UI-framework
327
- enum.
328
- - BFF + UI sub-steps land in follow-up commits (14.5.d.iii / .iv /
329
- .v) — the spec + kernel + built-in surface ship first so the
330
- contract is visible before consumers wire up.
331
-
332
- - de20bc2: Step 14.5 (a + b) — Inspector polish: markdown body opt-in + linked-nodes panel + dead-link verify hybrid
333
-
334
- Two sub-steps land together as a single feature unit. The Inspector
335
- view (UI workspace) gains a real markdown body card, a dedicated
336
- linked-nodes panel fed by the BFF's `/api/links` endpoint, and a
337
- hybrid dead-link checker that combines the in-memory heuristic with
338
- on-demand BFF verification. The spec + server side ships the minimal
339
- contract the new UI surface depends on: an opt-in `?include=body`
340
- parameter on `GET /api/nodes/:pathB64`, plus a corrected single-node
341
- response shape. Tests 854 → 868 (+14 server) and UI 113 → 138 (+25
342
- inspector / linked-nodes specs).
343
-
344
- **Why on-demand body reads instead of persisting bodies in the DB**:
345
- the kernel persists `body_hash` only (per `db-schema.md` §scan_nodes)
346
- — the body itself is human content, not machine state, and
347
- duplicating it in SQLite would inflate the DB without serving any
348
- read-side query the kernel cares about. Inspector cards that DO want
349
- to render the body (markdown preview at Step 14.5) opt into the
350
- filesystem re-read; the list / graph / kind-palette views never need
351
- it.
36
+ ### Patch Changes
352
37
 
353
- **Files added (server)**
38
+ - 103fc1a: Doc revision pass — greenfield framing across READMEs, spec prose, ROADMAP, AGENTS, web, and workspace landing pages.
354
39
 
355
- - `src/server/node-body.ts` on-demand body reader. Exports
356
- `readNodeBody(cwd, relPath)` (returns `string | null`; `null` on
357
- ENOENT / EACCES / EISDIR / ENOTDIR) and `stripFrontmatter(body)`
358
- (drops the leading `---\n…\n---\n` block when present, leaves
359
- fences in mid-document untouched). Path-traversal hardened: refuses
360
- absolute paths and any relative path that resolves outside `cwd`.
361
- - `src/test/server-node-body.test.ts` (11 unit cases) — covers
362
- `stripFrontmatter` edge cases (empty, no frontmatter, missing
363
- closing fence, fence in mid-document) and `readNodeBody` traversal
364
- rejection + the four `null`-returning errno branches.
40
+ Pure documentation changes; no normative schema or code changes.
365
41
 
366
- **Files edited (server)**
42
+ `@skill-map/spec`:
367
43
 
368
- - `src/server/routes/nodes.ts` — `GET /api/nodes/:pathB64` extends
369
- with `?include=body` opt-in (CSV-tolerant via the new
370
- `parseIncludes` helper, so `?include=body,future-extension` reads
371
- cleanly the day a second include lands). Same handler also FIXES a
372
- long-standing shape bug: was emitting `{ item: { node, linksOut,
373
- linksIn, issues } }` (raw `INodeBundle` pass-through), now emits
374
- the documented `{ item: Node, links: { incoming, outgoing },
375
- issues }` that the UI's `INodeDetailApi` and `StaticDataSource`
376
- already expected. No prod consumer ran against the legacy shape
377
- (the UI was internally branching on the legacy shape before the
378
- REST adapter landed at 14.3.a), so the corrected shape ships as a
379
- minor.
380
- - `src/test/server-endpoints.test.ts` — assertions corrected to the
381
- documented shape; 2 new cases for `?include=body` (returns body
382
- on present file, returns `null` when the file is missing).
44
+ - `architecture.md` — terse rewrite of §Provider · `kinds` catalog (now lists three required fields: `schema`, `defaultRefreshAction`, `ui`); new §Provider · `ui` presentation section documenting the label / color / colorDark / emoji / icon contract; §Stability section updated for the six extension kinds + Hook trigger set.
45
+ - `plugin-author-guide.md` Provider section gains the `ui` block documentation alongside `schema` and `defaultRefreshAction`; example manifest carries both icon variants (`pi` + `svg`); migration notes stripped under greenfield framing.
46
+ - `cli-contract.md` §Server documents the `kindRegistry` envelope field on every payload-bearing variant (sentinel envelopes — health/scan/graph — exempt).
47
+ - `conformance/coverage.md` row 18 (`extensions/provider.schema.json`) flipped 🔴 🟡, points at the new `plugin-missing-ui-rejected` case; new §Stability section.
48
+ - `conformance/README.md` drop "(Phase 5 / A.13 of spec 0.8.0)" historical phase markers.
49
+ - `db-schema.md`, `plugin-author-guide.md` fix `pisar` typo (Spanish leaked into English) "are simply overwritten".
50
+ - `CHANGELOG.md` — aggressive sweep: 2114 → 77 lines (96% reduction). Every release gets a 1–3 line greenfield summary. Drops the `Files touched`, `Migration for consumers`, `Out of scope`, `Why`, and per-step decision sub-sections. Drops commit-hash prefixes and `Pre-1.0 minor per versioning.md` boilerplate from every entry. The `[Unreleased]` section preserves the three in-flight Step 14 entries.
51
+ - `conformance/fixtures/plugin-missing-ui/.skill-map/plugins/bad-provider/{plugin.json,provider.js}` recovered (lost in the merge from `main` due to `.gitignore` masking gitignored-but-tracked files; `git add -f` brings them back into the index).
383
52
 
384
- **Files added (UI)**
53
+ `@skill-map/cli`:
385
54
 
386
- - `ui/src/app/components/linked-nodes-panel/{ts,html,css,spec.ts}`
387
- standalone Angular component. Inputs: `path`. Outputs:
388
- `openPath`. Internally fires `dataSource.listLinks({from})` +
389
- `listLinks({to})` in parallel; state machine
390
- `idle/loading/ready/error`. Subscribes to `events()` filtered on
391
- `scan.completed` for reactive refresh, plus a manual refresh
392
- button in the card header. Token guard handles rapid path
393
- changes. Renders rows with kind tag + clickable path +
394
- confidence chip + sources. 10 spec cases.
395
- - `ui/src/i18n/linked-nodes-panel.texts.ts` — i18n catalog.
396
- - `ui/src/app/views/inspector-view/inspector-view.spec.ts` (15
397
- cases) — first inspector-view spec. Covers empty / loading /
398
- body-card states, stale-fetch token guard, kind-card smoke,
399
- dead-link verify icon flow (heuristic-dead renders icon,
400
- click → 404 confirms, click → 200 flips to live).
55
+ - `src/README.md` — Status section greenfield (terse: pre-1.0, what's next, what's after); usage examples expanded with `sm serve` + monorepo dev scripts.
56
+ - `src/built-in-plugins/README.md` drop the contradictory "empty on purpose" framing; document the actual built-in inventory (Claude Provider + Extractors + Rules + Formatter + `validate-all`).
401
57
 
402
- **Files edited (UI)**
58
+ `@skill-map/testkit`:
403
59
 
404
- - `ui/src/models/api.ts` — `INodeApi.body?: string | null` added.
405
- - `ui/src/services/data-source/data-source.port.ts` —
406
- `IDataSourcePort.getNode(path, opts?: {includeBody?: boolean})`.
407
- - `ui/src/services/data-source/rest-data-source.ts` — propagates
408
- `includeBody` to `?include=body`.
409
- - `ui/src/services/data-source/static-data-source.ts` — ignores
410
- the flag (demo bundle ships bodies inline; see
411
- `scripts/build-demo-dataset.js` below).
412
- - `ui/src/services/collection-loader.ts` — minor signature touch
413
- for the `getNode` opts pass-through.
414
- - `ui/src/models/node.ts` — `INodeView` loses three fields:
415
- `body`, `raw`, `mockSummary`. The "Summary" mock card is
416
- retired (description already lives in `inspector__desc`).
417
- - `ui/src/app/views/inspector-view/inspector-view.ts` — body card
418
- switches from `<pre>{{ n.body }}</pre>` to a `@switch` over a
419
- `bodyState` signal (idle / loading / empty / unavailable /
420
- error / ready) with token-guarded fetch via `effect()` keyed on
421
- `path()`; markdown rendered via `MarkdownRenderer` and
422
- `[innerHTML]`. Mounts `<sm-linked-nodes-panel>` as a separate
423
- card between Relations and Body. Dead-link verify hybrid: the
424
- Relations card chips (`supersededBy` / `supersedes` / `requires`
425
- / `related`) keep the in-memory heuristic but now carry a verify
426
- icon (`pi-question-circle`) that fires `getNode(path)` against
427
- the BFF; three visual states `live` / `dead-confirmed` (404 → red
428
- dashed border + `pi-times-circle`) / `dead-heuristic` (not in
429
- scope, not yet verified). Per-node signals
430
- `verifiedAlive` / `verifiedDead` / `verifyInFlight` reset on
431
- `path()` change. Template refactor consolidates 4 inline
432
- duplicated chip blocks into a single `<ng-template #pathChip>`
433
- shared via `*ngTemplateOutlet`.
434
- - `ui/src/app/views/inspector-view/inspector-view.{html,css}` —
435
- templates + styles for the new body / verify states.
436
- - `ui/src/i18n/inspector-view.texts.ts` — drops `summary*`, adds
437
- `body.*` (loading / empty / unavailable / renderError),
438
- `relations.verifyHint`, `relations.deadConfirmed`. `body: 'Body'`
439
- (was `'Body (raw markdown)'`).
60
+ - `testkit/README.md` — rewrite end-to-end against the actual exported helper names (`runExtractorOnFixture` instead of the long-renamed `runDetectorOnFixture`); align example with the `extract(ctx) → void` Extractor shape and the `enabled` plugin status enum.
440
61
 
441
- **Files edited (build pipeline)**
62
+ Plus `ui/` README rewrite, root README + ES mirror Status / badge bumps + `sm serve` mention + Star History embed, AGENTS.md greenfield BFF section, CONTRIBUTING.md refresh, ROADMAP.md greenfield sweep (`Earlier prose` blocks stripped, decision log reframed without rename history, 14.6+ content preserved), web copy revision (How-it-works section), examples/hello-world rewritten to the Extractor model with passing tests, and the spec/index.json regeneration that goes with it.
442
63
 
443
- - `scripts/build-demo-dataset.js` new `embedBodies(scan,
444
- fixtureDir)` post-processor reads each fixture's body from disk,
445
- strips frontmatter, attaches to the demo `data.json` so the
446
- demo experience matches the live BFF (~40 KB extra for 21
447
- fixtures; bodies-on-bundle is the explicit demo-mode tradeoff).
64
+ Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
448
65
 
449
- **Spec**
66
+ ## [Unreleased]
450
67
 
451
- - `spec/cli-contract.md` `### Server` — `/api/nodes/:pathB64` row
452
- flips its shape column from the legacy bundle to the documented
453
- `{ item, links: { incoming, outgoing }, issues }` and gains the
454
- `?include=body` filter column.
455
- - `spec/CHANGELOG.md` `[Unreleased]` `### Minor` — entry covering
456
- the `?include=body` opt-in, the corrected response shape, and
457
- the path-traversal defense.
458
- - `spec/index.json` — regenerated (41 files hashed; no schema
459
- added).
68
+ ### Minor
460
69
 
461
- **ROADMAP** `Last updated` bumped, "YOU ARE HERE" updated,
462
- completeness marker now lists 14.5.a + 14.5.b as complete; "Next"
463
- points at 14.5.c.
70
+ - **Provider-driven kind presentation + `kindRegistry` envelope.** The Provider extension surface gains a required `kinds[*].ui` block (`label`, `color`, optional `colorDark`, optional `emoji`, optional discriminated icon `{ kind: 'pi', id }` or `{ kind: 'svg', path }`). Every payload-bearing REST envelope variant embeds a required `kindRegistry` field; sentinel envelopes (`health`, `scan`, `graph`) stay exempt. New conformance case `plugin-missing-ui-rejected` locks the loader's behaviour against drop-in Providers that omit the `ui` block.
464
71
 
465
- **Decisions taken inline (flag for orchestrator)**
72
+ - **`/api/nodes/:pathB64?include=body` body opt-in.** The single-node detail endpoint accepts `?include=body` to add `item.body: string | null` (read from disk on demand; `null` when the source file is missing or unreadable). Single-node response shape is `{ schemaVersion, kind: 'node', item, links: { incoming, outgoing }, issues }`. The body reader refuses absolute paths and any relative path that resolves outside the scope root.
466
73
 
467
- - The corrected single-node shape ships as a minor (additive on
468
- the contract surface) rather than a major. Rationale: no public
469
- consumer ran against the legacy shape; the UI was decoding the
470
- legacy shape internally before the REST adapter at 14.3.a
471
- introduced the documented shape; and the spec table already
472
- documented the new shape (the bug was in the implementation,
473
- not the spec). Keeping the bump minor avoids burning a major
474
- on a never-shipped wire format.
475
- - `parseIncludes` is CSV-tolerant from day one (`?include=body`
476
- and `?include=body,foo` both parse) so the second include can
477
- land without a parser refactor. Unknown include values are
478
- silently ignored — the BFF surface mirrors the spec's
479
- "ignore unknown event types" rule for forward compatibility.
480
- - Bodies are fetched per-node on inspector open, not pre-fetched
481
- in the list endpoint. Keeps the list `/api/nodes` response
482
- small (the list view never renders bodies) and matches the
483
- read-side hot path: most nodes are listed but few are inspected.
484
- - The dead-link verify is opt-in per chip click, not auto-fired
485
- on inspector open. Heuristic-dead nodes are common in scoped
486
- scans (a workspace that scans `docs/` but references `src/`);
487
- auto-firing would burn one BFF round-trip per such reference.
488
- - Per-node verification signals reset on `path()` change to avoid
489
- stale state bleeding between inspector navigations. The signals
490
- are scoped to the component instance; no global cache (the
491
- cost is one BFF call per re-verify on revisit, which the user
492
- triggers intentionally by clicking the icon).
74
+ - **`/ws` WebSocket protocol + watcher contract.** `### Server` documents the wire envelope (delegated to `job-events.md` §Common envelope), the event catalog (`scan.started` / `scan.progress` / `scan.completed` plus `extractor.completed` / `rule.completed` / `extension.error` plus the BFF-internal advisories `watcher.started` / `watcher.error`), connection lifecycle, the backpressure rule (4 MiB `bufferedAmount` → close 1009 + unregister), and the loopback-only assumption. `sm serve --no-watcher` flag added.
493
75
 
494
76
  ## 0.12.0
495
77
 
496
- ### Minor Changes
497
-
498
- - 68c5e28: Step 14.1 — `sm serve` + Hono BFF skeleton
499
-
500
- Adds `src/server/` Hono workspace with single-port wiring (`/api/health` real,
501
- `/api/*` 404 stubs, `/ws` no-op upgrade, `serveStatic` + SPA fallback). Real
502
- `ServeCommand` extracted from stub at `cli/commands/stubs.ts` to dedicated
503
- `cli/commands/serve.ts` extending `SmCommand`. Loopback-only through v0.6.0
504
- (Decision #119). Boot resilient to missing DB — `/api/health` reports
505
- `db: 'missing'`. Spec `cli-contract.md` `sm serve` row updated to full flag
506
- set; new `### Server` subsection (skeleton — endpoints fill at 14.2).
507
-
508
- **Files added (server)**
509
-
510
- - `src/server/index.ts` — `createServer(opts)` factory returning `ServerHandle` (`{ address, close }`); resolves spec version, builds the Hono app, instantiates a `WebSocketServer({ noServer: true })`, hands both to `@hono/node-server`'s `serve({ websocket: { server: wss } })`. Closing the http server tears down the WSS automatically (node-server registers the `'close'` hook internally); `close()` calls `wss.close()` defensively for forward-compatibility.
511
- - `src/server/app.ts` — Hono app construction. Routes registered in single-port order: `GET /api/health` → real, `ALL /api/*` → structured 404, `GET /ws` via the injected `attachWs` registrar, static handler + SPA fallback. Global `app.onError` formats every uncaught throw into the error envelope.
512
- - `src/server/options.ts` — `IServerOptions` + `validateServerOptions(input)`. Loopback-only check for `--dev-cors`; port range check `[0, 65535]`; scope validation.
513
- - `src/server/paths.ts` — `resolveDefaultUiDist(ctx)` walks upwards from cwd looking for `ui/dist/browser/index.html`; `resolveExplicitUiDist(ctx, raw)` honours absolute paths for `--ui-dist`.
514
- - `src/server/static.ts` — wraps `@hono/node-server`'s `serveStatic` middleware with the SPA-fallback layer (`serveStatic` does not do SPA fallback — it `next()`s on miss, which is exactly the seam we hook into). Absolute `root` paths work on POSIX in node-server@2.0.1 (verified runtime probe — implementation is `path.join(root, filename)`); the `.d.ts` "Absolute paths are not supported" string is stale (upstream issue honojs/node-server#187 still open). When the bundle is missing (`uiDist === null`), a tiny placeholder middleware serves the boot-without-bundle hint at `/`.
515
- - `src/server/ws.ts` — `noopWebSocketRoute(app)` registers `GET /ws` via the official `upgradeWebSocket` re-exported from `@hono/node-server@2.x`. The 14.1 handler closes the connection in `onOpen` with code 1000 + reason `'no broadcaster yet'`. 14.4 swaps this registrar for the chokidar-fed broadcaster — one-line change in `index.ts`, `app.ts` untouched.
516
- - `src/server/health.ts` — `buildHealth(deps)` synchronous; `resolveSpecVersion()` async, called once at boot.
517
- - `src/server/i18n/server.texts.ts` — `SERVER_TEXTS` catalog.
518
-
519
- **Files added (CLI)**
520
-
521
- - `src/cli/commands/serve.ts` — `ServeCommand extends SmCommand`. Parses flags, validates, calls `createServer`, registers SIGINT/SIGTERM handlers, awaits shutdown. `protected emitElapsed = false` (long-running daemon).
522
- - `src/cli/i18n/serve.texts.ts` — `SERVE_TEXTS` catalog.
523
-
524
- **Tests added (15)**
525
-
526
- - `src/test/server-boot.test.ts` (7) — boot/listen/health JSON, custom port, db state present/missing, structured 404, /ws upgrade closes with code 1000 + reason 'no broadcaster yet' (uses real `WebSocket` client from `ws`), shutdown < 1s + idempotent close, inline placeholder when uiDist null.
527
- - `src/test/server-flags.test.ts` (6) — host non-loopback + dev-cors rejection, port out-of-range, port non-numeric, scope invalid, ui-dist missing, ui-dist with valid bundle.
528
- - `src/test/server-db-missing.test.ts` (2) — `--db <missing>` exits 5, default boots cleanly with db:missing.
529
-
530
- **Files edited**
531
-
532
- - `src/cli/commands/stubs.ts` — `ServeCommand` removed; replaced with a comment pointer.
533
- - `src/cli/entry.ts` — registers the new `ServeCommand`.
534
- - `src/package.json` — adds `hono@4.12.16`, `@hono/node-server@2.0.1`, `ws@8.20.0` (deps); `@types/ws@8.18.1` (dev). All exact-pinned per AGENTS.md.
535
- - `spec/cli-contract.md` — `sm serve` row replaced with the full 14.1 flag set; new `#### Server` subsection (stability: experimental).
536
- - `spec/CHANGELOG.md` — `[Unreleased]` `### Minor` entry for the spec change.
537
- - `spec/index.json` — regenerated (40 files hashed; previous head was 215 lines).
538
-
539
- **Decisions during implementation (flag for orchestrator)**
78
+ ### Minor
540
79
 
541
- - WebSocket support uses `@hono/node-server@2.x`'s built-in `upgradeWebSocket` plus the canonical `ws@8.20.0` Node WebSocket library, per the official README pattern. The previously-published `@hono/node-ws` adapter was deprecated when node-server@2.0 absorbed WebSocket support natively (PR honojs/node-server#328). The 14.4 broadcaster will replace `noopWebSocketRoute` with its own one-line registrar no API churn between 14.1 and 14.4.
542
- - The `/api/*` catch-all is wired with `app.all('/api/*', ...)` BEFORE the `/ws` registrar and the static handler so neither a `serveStatic` filesystem hit nor the SPA fallback can shadow API endpoints. `/ws` is registered BEFORE the static handler so a literal `/ws` path on disk inside `uiDist` cannot accidentally shadow the upgrade route.
543
- - `serveStatic` from `@hono/node-server/serve-static` accepts absolute root paths at runtime on POSIX (its implementation is `path.join(root, filename)`); the `.d.ts` string saying otherwise is documentation drift, not a runtime contract. Verified with a runtime probe and cross-referenced against the open upstream issue (honojs/node-server#187). Documented in `src/server/static.ts` so future contributors don't re-investigate.
80
+ - **`sm serve` + Hono BFF skeleton.** New `### Server` subsection in `cli-contract.md`. Endpoints at this bump: `GET /api/health` (real), `ALL /api/*` (structured 404 stub), `GET /ws` (no-op upgrade closes with code 1000 + reason `'no broadcaster yet'`), static handler + SPA fallback. Loopback-only through v0.6.0; boot resilient to a missing DB (`/api/health` reports `db: 'missing'`). `sm serve` flag set: `--port` (default 4242), `--host` (default 127.0.0.1), `--scope`, `--db`, `--no-built-ins`, `--no-plugins`, `--open` / `--no-open`, `--dev-cors`, `--ui-dist`.
544
81
 
545
82
  ## 0.11.0
546
83
 
547
- ### Minor Changes
548
-
549
- - f8fca25: Step 10 prep — job artifacts move into the database (B2: content-addressed storage)
550
-
551
- Removes the on-disk `.skill-map/jobs/<id>.md` and `.skill-map/reports/<id>.json` artifacts from the spec. Rendered job content and report payloads now live in the kernel database; the filesystem is no longer a normative layer of the job lifecycle. Pre-1.0 minor breaking per `versioning.md` § Pre-1.0.
552
-
553
- **Why**: every other piece of operational state (`state_summaries`, `state_enrichments`, `state_plugin_kvs`, `node_enrichments`) already lives in the DB. Jobs and reports were the only outliers — and being outliers cost real complexity (orphan-file detection, partial backups, two-source-of-truth GC). With B2 (content-addressed dedup keyed on the existing `content_hash`), retries / `--force` / cross-node fan-out reuse a single content blob, so DB-only does not blow up storage on heavy users.
554
-
555
- **Schema changes**
556
-
557
- - New table `state_job_contents` (`content_hash` PK, `content` TEXT, `created_at`). Content-addressed: multiple `state_jobs` rows MAY reference the same row.
558
- - `state_jobs.file_path` removed. The rendered content is fetched via `state_job_contents.content_hash` join.
559
- - `state_executions.report_path` → `state_executions.report_json` (TEXT, parsed-JSON-on-read per the `_json` naming convention).
560
-
561
- **Schema-typed contract changes**
562
-
563
- - `Job.filePath` removed.
564
- - `ExecutionRecord.reportPath` → `ExecutionRecord.report` (object/null — the parsed JSON payload).
565
- - `Job.failureReason` and `ExecutionRecord.failureReason` enums: `job-file-missing` → `content-missing` (defensive failure-mode label for DB corruption where a job row outlives its content row; the runtime invariant should keep this state unreachable).
566
- - `history-stats.schema.json` `perFailureReason` mirrors the rename.
567
-
568
- **CLI surface changes**
569
-
570
- - `sm job preview <id>` now prints the rendered content from `state_job_contents` (no file). Same output, different source.
571
- - `sm job claim --json` is the contracted Skill-agent handover: returns `{id, nonce, content}` so the agent can call `sm record` afterwards with the nonce in hand. The plain-stdout form (id only) is preserved for legacy scripts.
572
- - `sm record --report <path-or-dash>` accepts a file path OR `-` (stdin); the kernel reads the payload and stores it inline in `report_json`. The on-disk report file becomes operationally ephemeral — implementations SHOULD remove it after the kernel acknowledges the callback (courtesy GC, not normative).
573
- - `sm job prune --orphan-files` removed. Replaced by automatic `state_job_contents` GC inside `sm job prune`: deletes terminal jobs past retention, then collects orphan content rows in the same transaction.
574
- - `sm doctor` checks change accordingly: drops the "orphan job files / orphan DB rows pointing at missing files" pair; adds two DB-internal checks (`state_jobs` rows whose `content_hash` is missing from `state_job_contents`; `state_job_contents` rows referenced by zero `state_jobs` rows).
575
-
576
- **Event stream changes**
577
-
578
- - `job.spawning.data.jobFilePath` → `job.spawning.data.contentHash` (references the content row instead of a file path).
579
- - `job.callback.received.data.reportPath` and `job.completed.data.reportPath` → `executionId` (references the `state_executions` row that holds the inline report payload). Reports are intentionally NOT inlined in events — consumers query the row when they need the body.
580
-
581
- **Architecture changes**
582
-
583
- - `RunnerPort.run(jobFilePath, options)` → `run(jobContent, options)` returning `{report, ...}` instead of `{reportPath, ...}`. Path-based reporting is no longer part of the port contract. Runners that need an actual file (the canonical case being `claude -p` reading stdin from a path) materialize a temp file inside `run()` and remove it after spawn — temp files are operational, not normative.
584
-
585
- **Atomicity edge cases consolidated**
586
-
587
- `spec/job-lifecycle.md` §Atomicity edge cases drops the four file-related rows. Two new DB-internal cases take their place: `state_jobs` row outliving its `state_job_contents` row (failure: `content-missing`); `state_job_contents` row with no live job references (GC straggler — `sm job prune` collects).
588
-
589
- **Files touched**
590
-
591
- - `spec/db-schema.md` — new `state_job_contents` section, `state_jobs.file_path` removed, `state_executions.report_path` → `report_json`, integrity section rewritten.
592
- - `spec/job-lifecycle.md` — §Submit step 8 rewritten (DB store), §Atomic claim documents `--json` shape, §Atomicity edge cases consolidated, §Record callback rewritten for `--report` path-or-stdin semantics, §Retention extended to cover `state_job_contents` GC, failure-reason rename.
593
- - `spec/cli-contract.md` — `sm job preview` / `sm job claim` / `sm job prune` rows updated, `sm job prune --orphan-files` row removed, `sm record` block rewritten with `<path-or-dash>`, `sm doctor` integrity bullets updated.
594
- - `spec/prompt-preamble.md` — §How the kernel applies step 5 rewritten (DB store, no file).
595
- - `spec/architecture.md` — §`RunnerPort` operations + reference impls updated for content-string + parsed-report shape.
596
- - `spec/job-events.md` — `job.spawning` / `job.callback.received` / `job.completed` payloads changed.
597
- - `spec/conformance/README.md` + `coverage.md` — `preamble-bitwise-match` references updated to `sm job preview` stdout.
598
- - `spec/schemas/job.schema.json` — `filePath` property removed, failure-reason enum rename.
599
- - `spec/schemas/execution-record.schema.json` — `reportPath` → `report` (object/null), failure-reason enum rename.
600
- - `spec/schemas/history-stats.schema.json` — `perFailureReason` enum rename.
601
- - `spec/index.json` regenerated (40 files hashed); `npm run spec:check` green.
602
-
603
- **Migration for consumers**
604
-
605
- - Any consumer reading `state_jobs.file_path` or `state_executions.report_path` reads from the renamed columns / DB-only paths instead.
606
- - Any tooling that watched `.skill-map/jobs/*.md` or `.skill-map/reports/*.json` needs to query the DB or call the relevant `sm` verb.
607
- - `--orphan-files` flag callers must drop the flag; `sm job prune` already does the equivalent automatically.
608
- - Skill agents drain via `sm job claim --json` (id + nonce + content together) instead of `sm job claim` + reading a file.
609
-
610
- **Out of scope**
84
+ ### Minor
611
85
 
612
- The reference impl side of this (migration that adds `state_job_contents` + drops `state_jobs.file_path`; storage-adapter helpers; runtime piping in `ClaudeCliRunner` for the temp-file dance) lands in follow-up changesets under `@skill-map/cli`. The spec change above is self-contained: shipping it alone changes nothing at runtime, but unblocks the implementation phases.
86
+ - **Job artifacts move into the database (content-addressed).** New `state_job_contents(content_hash PK, content, created_at)`; `state_jobs.file_path` removed (rendered content fetched via join). `state_executions.report_path` → `state_executions.report_json` (parsed-JSON-on-read). `Job.filePath` removed; `ExecutionRecord.reportPath` `ExecutionRecord.report` (parsed JSON / null). `RunnerPort.run(jobContent, options)` returns `{ report, ... }` — path-based reporting is no longer part of the port contract. `sm job preview` reads from the DB; `sm job claim --json` returns `{ id, nonce, content }`; `sm record --report <path-or-dash>` accepts a file path or stdin; `sm job prune --orphan-files` removed (the verb auto-collects orphan content rows). `sm doctor` integrity checks updated. Event payload renames: `job.spawning.data.jobFilePath` → `contentHash`; `job.callback.received.data.reportPath` and `job.completed.data.reportPath` → `executionId`. The `job-file-missing` failure-reason enum is preserved with shifted semantics: it now flags a missing `state_job_contents` row (DB-corruption-only state).
613
87
 
614
88
  ## 0.10.0
615
89
 
616
- ### Minor Changes
617
-
618
- - f8a7125: Open `Node.kind` to any Provider-declared string (Phase A — spec only).
619
-
620
- The kernel always documented `IProvider.kinds` as "open by design" so future Cursor / Obsidian / Roo Providers can declare their own kinds. The spec, however, had three layers underneath that closed it back to the original five-value Claude Provider catalog (`skill` / `agent` / `command` / `hook` / `note`):
621
-
622
- - `node.schema.json#/properties/kind` carried `enum: [<5 values>]` — AJV-rejected anything else.
623
- - `db-schema.md` § `scan_nodes` and § `state_summaries` mandated `CHECK in (<5 values>)` SQL constraints.
624
- - `extensions/action.schema.json#/.../filter/kind` had the same closed list for the per-action applicability filter.
625
-
626
- This phase opens the spec end:
627
-
628
- - `node.schema.json#/properties/kind` → `{ "type": "string", "minLength": 1 }` with a description naming the built-in Claude catalog so consumers know the default contract.
629
- - `db-schema.md` drops both `CHECK in (...)` constraint rows. Both columns stay `TEXT NOT NULL`.
630
- - `extensions/action.schema.json#/.../filter/kind` widens to `{ items: { "type": "string", "minLength": 1 } }`.
631
-
632
- The TS side (`Node.kind: string`, `IProvider.classify(...): { kind: string; ... }`, Kysely `TNodeKind = string`) and the SQL `002_open_node_kinds` migration that drops the live CHECK constraints land in follow-up phases under `@skill-map/cli`. Phase A is a safe checkpoint: shipping the spec change alone changes nothing at runtime (the kernel still emits closed kinds, the live DB still enforces the existing CHECK), but it unblocks the rest of the refactor and aligns the source-of-truth artifact with the design intent.
633
-
634
- Migration for consumers:
635
-
636
- - Anyone validating an exported `Node` JSON against `node.schema.json` now accepts external-Provider kinds.
637
- - Any UI / dashboard / script that hard-coded the closed enum elsewhere (filter chips, assertion sets) needs to widen to `string` and accept whatever an enabled Provider declares.
638
-
639
- Pre-1.0 minor bump per `spec/versioning.md` § Pre-1.0 (this is breaking for consumers that relied on the enum, but pre-1.0 breakings ship as minor).
640
-
641
- ## 0.9.0
642
-
643
- ### Minor Changes
644
-
645
- - 88afe24: Cleanup pass post-v0.8.0 — finishing the renames and wiring the
646
- conformance kill-switches.
647
-
648
- **Pre-1.0 minor bump** per `spec/versioning.md` § Pre-1.0. The schema
649
- field rename below is technically breaking, but ships as a minor while
650
- the spec stays `0.Y.Z`.
651
-
652
- ## Spec changes (`@skill-map/spec`)
653
-
654
- ### Breaking — `conformance-case.schema.json`
655
-
656
- - **Rename `setup.disableAllDetectors` → `setup.disableAllExtractors`.**
657
- Finishes the kind rename Detector → Extractor introduced in 0.8.0
658
- (Phase 2 of the plug-in model overhaul). The previous name was the
659
- last residue and it never reached a release where anything consumed
660
- it.
661
- - **`setup.disableAll{Providers,Extractors,Rules}` are now consumed
662
- end-to-end.** Until this release the three toggles were declared in
663
- the schema and accepted by the runner, but the runner never threaded
664
- them anywhere — the `kernel-empty-boot` case happened to pass
665
- because its fixture is empty. The runner now injects
666
- `SKILL_MAP_DISABLE_ALL_{PROVIDERS,EXTRACTORS,RULES}=1` into the
667
- child process environment when the matching toggle is `true`, and
668
- the CLI's scan composer drops every extension of the disabled kind
669
- from the in-scan pipeline regardless of granularity gates and
670
- `--no-built-ins`. Each toggle now has a docstring on the schema
671
- property pointing at the env-var convention.
672
- - `kernel-empty-boot` case updated for the rename.
673
- - `conformance/README.md` example updated.
674
-
675
- ### Non-breaking — copy fixes
676
-
677
- - Comments and docstrings across `architecture.md` and friends already
678
- refer to "Extractor" everywhere; only the schema field stayed on the
679
- old name. No prose changes in this bump.
680
-
681
- ## CLI changes (`@skill-map/cli`)
682
-
683
- ### Breaking — `IDiscoveredPlugin.status` enum
684
-
685
- - **Rename `'loaded'` → `'enabled'`.** The schema enum
686
- (`plugins-registry.schema.json`) already used `enabled` since 0.8.0;
687
- the runtime drifted to `loaded` and has now been pulled back so the
688
- runtime status matches the spec contract. `'disabled'`, the
689
- semantic pair, was already aligned. Every consumer (`sm plugins
690
- list`, `sm plugins doctor`, `sm db prune` plugin filter, runtime
691
- plugin composer) updated. No published consumers exist.
692
-
693
- ### Non-breaking — sweep cleanup
694
-
695
- - Old `Detector` / `detector` references (kind name, manifest field
696
- names, JSDoc, comments, test fixture filenames, test variable
697
- names) replaced with `Extractor` / `extractor` across the
698
- production code and test suite. Excludes historical CHANGELOG
699
- entries, explicit migration notes ("Renamed from Detector"), and
700
- test data strings whose semantics are independent of the kind
701
- name (e.g. `'@FooDetector'` in trigger normalization tests).
702
- - A residual reference to "an audit reading `ScanResult.issues`" in
703
- `validate-all`'s docstring rewritten without the removed kind name.
704
-
705
- ## Tests
706
-
707
- - `plugin-runtime-branches.test.ts` — five new unit tests covering
708
- the env-var kill-switch in `composeScanExtensions` (per kind, all
709
- three together, and stray-value resilience).
710
- - `conformance-disable-flags.test.ts` — four new e2e tests pointing
711
- the runner at a populated fixture with each toggle in turn (and a
712
- baseline) so a regression in the env-var pipeline shows up
713
- structurally rather than relying on the empty-fixture coincidence.
714
-
715
- ## [Unreleased]
716
-
717
90
  ### Minor
718
91
 
719
- - **Provider-driven kind presentation + envelope `kindRegistry`**
720
- Step 14.5.d. The Provider extension surface gains the required
721
- `kinds[*].ui` field (label, color, optional dark-theme color, optional
722
- emoji, optional icon) so each kind a Provider declares carries the
723
- presentation metadata the UI needs to render it. The icon is a
724
- discriminated union — `{ kind: 'pi'; id: 'pi-…' }` for PrimeIcons or
725
- `{ kind: 'svg'; path: '…' }` for raw SVG path data wrapped in a
726
- `viewBox="0 0 24 24"` tinted with `currentColor`. The UI derives bg /
727
- fg tints from `color` per theme via a deterministic helper, so the
728
- Provider declares one base color per theme rather than four hex
729
- values.
730
-
731
- The REST envelope shape (`schemas/api/rest-envelope.schema.json`)
732
- gains a new required `kindRegistry` field on every payload-bearing
733
- variant (`nodes` / `links` / `issues` / `plugins` lists, the `node`
734
- single, and the `config` value envelope); sentinel envelopes
735
- (`health` / `scan` / `graph`) stay exempt because they don't carry a
736
- payload at the wire level either. The registry is keyed by kind
737
- name and carries `{ providerId, label, color, colorDark?, emoji?,
738
- icon? }` — the BFF assembles it once at boot from every enabled
739
- Provider and attaches it to every applicable response so the UI can
740
- render Provider-declared kinds (built-in and user-plugin alike)
741
- without hardcoding a closed kind enum.
742
-
743
- **Why required, not optional**: making `ui` optional reintroduces the
744
- trap the UI had pre-14.5.d (silently collapsing unknown kinds to
745
- `'note'`). Forcing every Provider to declare presentation up-front
746
- means the UI never has to invent visuals; the cost is one small
747
- object per kind in the manifest.
748
-
749
- **Why discriminated icon instead of two optional fields**: the
750
- `oneOf` shape (with `kind: 'pi' | 'svg'` discriminator) keeps the UI
751
- dispatch exhaustive without string-sniffing the payload, and AJV
752
- validates each variant cleanly — a manifest cannot ship both `id` and
753
- `path` simultaneously.
754
-
755
- **Why `schemaVersion` stays at `'1'`**: the BFF is greenfield — no
756
- released consumers depend on the previous (kindRegistry-less)
757
- shape, so a versioned migration would only add ceremony. The
758
- shape change is documented under this changelog entry; the version
759
- bumps the day a third-party consumer ships against the wire.
760
-
761
- Pre-1.0 minor breaking per `versioning.md` § Pre-1.0. The built-in
762
- Claude Provider migrates in the same step (every kind declares its
763
- `ui` block reusing the visuals previously hardcoded in the UI).
764
-
765
- Conformance: new case `plugin-missing-ui-rejected` (with fixture
766
- `plugin-missing-ui/`) locks the loader's behaviour against a drop-in
767
- Provider that omits `ui` — `sm scan --json` exits 0, stderr matches
768
- the canonical `must have required property 'ui'` diagnostic, and the
769
- rest of the pipeline (built-in Claude) keeps running. Suite total:
770
- 5/5 passing across 2 scopes.
771
-
772
- - **BFF `/api/nodes/:pathB64` body opt-in** — Step 14.5.a extends the
773
- single-node detail endpoint with the optional `?include=body` query
774
- parameter. When set, the response's `item` carries `body: string |
775
- null` (the post-frontmatter file content read from disk on demand);
776
- `null` indicates the source file was missing or unreadable when the
777
- request landed. Without the flag, `item.body` stays `undefined` and
778
- the handler does not touch the filesystem. The single-node response
779
- shape is also corrected to the documented `{ schemaVersion, kind:
780
- 'node', item: Node, links: { incoming: Link[], outgoing: Link[] },
781
- issues: Issue[] }` (the `### Server` table previously declared the
782
- shape as the legacy `{ node, linksOut, linksIn, issues }` bundle —
783
- see prose for the bug-fix rationale). No prod consumer ran against
784
- the legacy shape, so the corrected shape ships as a minor.
785
-
786
- **Why on-demand instead of persisting bodies in `scan_nodes`**: the
787
- kernel persists `body_hash` only (per `db-schema.md` §scan_nodes) —
788
- the body itself is human content, not machine state, and duplicating
789
- it in SQLite would inflate the DB without serving any read-side
790
- query the kernel cares about. Inspector cards that DO want to render
791
- the body (markdown preview at Step 14.5) opt into the filesystem
792
- re-read; the list / graph / kind-palette views never need it.
793
-
794
- **Path-traversal defense**: the body reader (`src/server/node-body.ts`)
795
- refuses absolute paths and any relative path that resolves outside
796
- the scope root. A corrupted DB row or a future Provider that
797
- forgets to sanitise its node paths cannot use this endpoint to leak
798
- arbitrary files.
799
-
800
- - **BFF `/ws` protocol + watcher contract** — Step 14.4.a documents
801
- the WebSocket surface. The `### Server` subsection grows a
802
- **WebSocket protocol** block enumerating the wire envelope (delegated
803
- to `job-events.md` §Common envelope), the v14.4.a event catalog
804
- (`scan.started` / `scan.progress` / `scan.completed` plus the
805
- side-effect events `extractor.completed` / `rule.completed` /
806
- `extension.error`, plus the BFF-internal advisories
807
- `watcher.started` / `watcher.error`), the connection lifecycle (no
808
- state push on connect; client polls `/api/scan` to seed; close codes
809
- used: 1000 / 1001 / 1009), the backpressure rule (4 MiB `bufferedAmount`
810
- → close 1009 + unregister), and the loopback-only assumption (no
811
- per-connection auth through v0.6.0 per Decision #119). The endpoint
812
- table flips `GET /ws` from `upgrade-only` to `implemented (v14.4.a)`.
813
- The `sm serve` flag table grows `--no-watcher` (default off — watcher
814
- on per Decision #121); combining `--no-watcher` is documented;
815
- combining `--no-built-ins` with the watcher is rejected (would
816
- persist empty scans on every batch). The verb-catalog row for
817
- `sm serve` mirrors the new flag.
818
-
819
- `issue.added` / `issue.resolved` (the diff-based events from
820
- `job-events.md` §Issue events) and `scan.failed` are explicitly
821
- flagged as deferred — the 14.4.a surface fans out only what the
822
- kernel emitter already produces; per-batch failure events and the
823
- diff-based issue stream require additional plumbing in the BFF
824
- watcher loop and land in a follow-up.
825
-
826
- Additive minor per `versioning.md` § Pre-1.0; no breaking change to
827
- any prior `/ws` behavior (the v14.1 / v14.2 / v14.3 surfaces all had
828
- `/ws` documented as "upgrade-only — broadcaster lands at v14.4").
829
-
830
- - **BFF read-side endpoints** — Step 14.2 fills the `### Server`
831
- subsection's endpoint catalogue from the v14.1 stub
832
- (`GET /api/health` real, `ALL /api/*` 404) to the eight read-side
833
- endpoints the Web UI consumes:
834
- `GET /api/scan` (latest persisted `ScanResult`; `?fresh=1` for an
835
- in-memory scan), `GET /api/nodes` (paginated, filtered list — same
836
- query grammar as `sm export`), `GET /api/nodes/:pathB64` (single-node
837
- bundle; `pathB64` is base64url of `node.path`), `GET /api/links`,
838
- `GET /api/issues`, `GET /api/graph?format=...` (formatter rendering),
839
- `GET /api/config`, `GET /api/plugins`. New
840
- [`schemas/api/rest-envelope.schema.json`](schemas/api/rest-envelope.schema.json)
841
- formalises the list-envelope shape (`{ schemaVersion, kind, items \|
842
- item \| value, filters, counts }`). The error envelope subsection
843
- enumerates the v14.2 sources for each `code` value (`not-found` /
844
- `bad-query` / `internal` / reserved `db-missing`). Stability stays
845
- `experimental — locks at v0.6.0` (no change). Additive minor per
846
- `versioning.md` § Pre-1.0; no breaking change — the v14.1 endpoint
847
- catalogue was a stub explicitly slated to fill at v14.2.
848
-
849
- - **`sm serve` row + `### Server` subsection** in `cli-contract.md` —
850
- Step 14.1 promotes `sm serve` from an implementation-defined stub to a
851
- documented surface. The verb row at `§Verb catalog` › `### Server`
852
- expands the flag set to the full 14.1 contract: `--port` (default
853
- `4242`), `--host` (default `127.0.0.1`, loopback-only through v0.6.0),
854
- `--scope project|global`, `--db <path>`, `--no-built-ins`,
855
- `--no-plugins`, `--open` / `--no-open`, `--dev-cors`, `--ui-dist
856
- <path>` (hidden). New `#### Server` subsection documents the
857
- single-port mandate, the boot-with-missing-DB resilience contract
858
- (`/api/health` returns `db: 'missing'`), the v14.1 endpoint surface
859
- (`GET /api/health` real, `ALL /api/*` 404 stubs, `GET /ws` upgrade-only,
860
- static + SPA fallback), the structured error envelope shape, and the
861
- flag table. Marked `*(Stability: experimental — locks at v0.6.0.)*` —
862
- endpoints fill at v14.2, broadcaster at v14.4. Additive minor per
863
- `versioning.md` § Pre-1.0 (no breaking change to the existing row's
864
- semantics; the old wording was strictly less specific).
865
-
866
- ### Minor (breaking, pre-1.0)
867
-
868
- - **`Node.kind` opens to any non-empty string (was the closed enum
869
- `skill` / `agent` / `command` / `hook` / `note`).** The kernel always
870
- permitted external Providers — `IProvider.kinds` is documented as
871
- "open by design" so a future Cursor / Obsidian / Roo Provider can
872
- declare its own kinds — but the `node.schema.json` enum + the
873
- `scan_nodes.kind` SQL CHECK + the closed TS `NodeKind` union closed
874
- three layers underneath. Effects:
875
- - `node.schema.json#/properties/kind` switches from `enum: [...5
876
- values]` to `{ "type": "string", "minLength": 1 }`. The
877
- description still names the built-in Claude Provider catalog so
878
- consumers know what to expect from the default install.
879
- - `db-schema.md` drops the `CHECK in (...)` constraint on
880
- `scan_nodes.kind` and `state_summaries.kind`. Both columns stay
881
- `TEXT NOT NULL`.
882
- - `extensions/action.schema.json#/.../filter/kind` (the per-kind
883
- filter for action applicability) widens the same way: `items:
884
- { type: 'string', minLength: 1 }` instead of the closed enum.
885
- Migration: consumers who validate exported `Node` JSON against
886
- `node.schema.json` will now accept external-Provider kinds. Any
887
- consumer that hard-coded the closed enum elsewhere (UI filter chip
888
- set, scripted assertions) needs to widen to "string". The TS +
889
- SQL counterpart lands in `@skill-map/cli` (kernel TS contract +
890
- migration `002_open_node_kinds`).
891
- - **`conformance-case.schema.json` — rename `setup.disableAllDetectors`
892
- → `setup.disableAllExtractors`.** Finishes the kind rename Detector →
893
- Extractor introduced in 0.8.0 (Phase 2 of the plug-in model
894
- overhaul). The previous name was a residue from an unfinished sweep
895
- and never reached a release that consumed it.
896
- - **`setup.disableAll{Providers,Extractors,Rules}` are now wired
897
- end-to-end.** Until this release the toggles were declared in the
898
- schema but the runner threaded them nowhere; the `kernel-empty-boot`
899
- case happened to pass because its fixture is empty. The runner now
900
- injects `SKILL_MAP_DISABLE_ALL_{PROVIDERS,EXTRACTORS,RULES}=1` into
901
- the child process environment per toggle, and the CLI's scan
902
- composer drops every extension of the disabled kind from the
903
- in-scan pipeline (overriding granularity gates and `--no-built-ins`).
904
- Migration: any case JSON authored against the unwired schema needs
905
- to swap `disableAllDetectors` for `disableAllExtractors`; behaviour
906
- changes only when the toggles were already `true` (those cases will
907
- now actually disable the kind, where previously they relied on
908
- fixture content for the same outcome).
909
-
910
- ### Patch
911
-
912
- - Updated `conformance/cases/kernel-empty-boot.json` for the field
913
- rename above.
914
- - Updated `conformance/README.md` example for the field rename above.
915
- - Schema docstrings added to each `disableAll*` property documenting
916
- the env-var convention the runner uses.
917
-
918
- ## 0.8.0
919
-
920
- ### Minor Changes
921
-
922
- - 6dad772: v0.8.0 — Pre-1.0 stabilization pass.
923
-
924
- This release combines two coherent pre-1.0 cleanup pieces that
925
- both push the project closer to v1.0 stability: the cli-architect
926
- audit review pass and the plugin model overhaul.
927
-
928
- Pre-1.0 minor bumps per `versioning.md` § Pre-1.0; breaking
929
- changes allowed within minor while in `0.Y.Z`. No real downstream
930
- ecosystem exists yet, so the breaking surface costs nothing
931
- today.
932
-
933
- ## Part 1 — Pre-1.0 audit review pass
934
-
935
- Pre-1.0 review pass — `cli-architect` audit findings.
936
-
937
- Internal audit run by the `cli-architect` agent in REVIEW mode
938
- produced a Critical / High / Medium / Low / Nit catalog. This
939
- pass bundles the implementation of every actionable finding into
940
- one unit so the review can be read end-to-end. **Pre-1.0 minor
941
- bump**: a few breaking surface changes ride along (CLI sub-verb
942
- split, exit-code enum exposed, plugin loader option). No
943
- published downstream consumers exist yet.
944
-
945
- ### Spec changes (`@skill-map/spec`)
946
-
947
- - **`cli-contract.md`** — `sm scan compare-with <dump> [roots...]`
948
- is now a sub-verb instead of a `--compare-with <path>` flag on
949
- `sm scan`. Read-only delta report against a saved `ScanResult`
950
- JSON dump. Read-only — does not modify the DB. Same exit codes
951
- (`0` empty delta / `1` drift / `2` operational error). Old flag
952
- form removed.
953
- - **`cli-contract.md`** — exit-code `2` "Operational error" row
954
- clarified to mention environment / runtime mismatches (wrong
955
- Node version, missing native dependency) explicitly. The
956
- "unhandled exception" catch-all already covered the case; this
957
- just removes ambiguity for future implementers.
958
- - **`cli-contract.md`** — new normative section **§Dry-run**
959
- between §Exit codes and §Verb catalog defining the contract for
960
- any verb exposing `-n` / `--dry-run`: no observable side effects
961
- (DB / FS / config / network / spawns), no auto-provisioning of
962
- scope directories, output mirrors the live mode with explicit
963
- "would …" framing, exit codes mirror the live mode, dry-run
964
- MUST short-circuit `--yes` / `--force` confirmation prompts.
965
- Per-verb opt-in: the flag is not global, verbs that don't
966
- declare it MUST reject it as an unknown option. Verb catalog
967
- rows for `sm init`, `sm db reset` (default + `--state` +
968
- `--hard`), and `sm db restore` amended to declare and describe
969
- their `--dry-run` previews.
970
-
971
- ### CLI changes (`@skill-map/cli`)
972
-
973
- #### Critical — kernel & adapter hygiene
974
-
975
- - **C1 — `runScanInternal` decomposed.** The 290-line monolith in
976
- `kernel/orchestrator.ts` split into a thin composer + four pure
977
- functions: `validateRoots`, `indexPriorSnapshot`,
978
- `walkAndDetect`, `runRules`. Composer is now 89 lines reading
979
- top-to-bottom through the pipeline phases. Zero behavioural
980
- change.
981
- - **C2 — `withSqlite(options, fn)` helper.** Single utility at
982
- `cli/util/with-sqlite.ts` standardises the open / use / close
983
- idiom every read-side command was open-coding. Eliminates four
984
- classes of boilerplate bugs (forgotten close, `autoBackup`
985
- drift, double-close, missing `try/finally`). Migrated 20 call
986
- sites across `check`, `export`, `graph`, `history`, `init`,
987
- `jobs`, `list`, `orphans`, `plugins`, `scan`, `show`, `watch`,
988
- plus `cli/util/plugin-runtime.ts`. Companion `tryWithSqlite`
989
- short-circuits when the DB file does not exist, replacing the
990
- `if (existsSync) { withSqlite(...) }` chain. In `scan.ts` the
991
- read-prior + persist double-open consolidated into a single
992
- `withSqlite` callback that brackets read prior → run scan →
993
- guard → persist when `willPersist`. Saves one migration
994
- discovery pass + one WAL setup per normal scan (~50–100ms).
995
-
996
- #### High — UX & contract integrity
997
-
998
- - **H3 — `--dry-run` semantics unified across `init` / `db reset`
999
- / `db restore`.** The new spec §Dry-run codifies the "no
1000
- writes, reads OK" contract; three verbs that did not previously
1001
- expose a preview now do: - `sm init --dry-run` — previews the would-create lines for
1002
- `.skill-map/`, `settings.json`, `settings.local.json`,
1003
- `.skill-mapignore`, the `.gitignore` entries that would be
1004
- appended (deduped against the existing file), the DB
1005
- provisioning, and the first-scan trigger. Honours `--force`
1006
- for the would-overwrite preview. Re-init over an existing
1007
- scope without `--force` still exits 2 (same gate as live). - `sm db reset --dry-run` (default + `--state`) — opens the DB
1008
- read-only, computes the row count per `scan_*` (and `state_*`
1009
- when `--state`) table, and prints them. No `DELETE`
1010
- statements issued. Bypasses the `--state` confirmation prompt
1011
- entirely. - `sm db reset --hard --dry-run` — reports the DB file path and
1012
- size that would be unlinked; missing-file case prints a clear
1013
- no-op line instead of an error. - `sm db restore <src> --dry-run` — validates the source exists
1014
- (still exits 5 if missing), reports the source size and
1015
- whether the target would be created or overwritten, plus the
1016
- WAL / SHM sidecars that would be dropped. Bypasses the
1017
- confirmation prompt.
1018
- Implementation: new helper `previewGitignoreEntries(scopeRoot,
1019
- entries)` in `init.ts` mirrors `ensureGitignoreEntries` parsing
1020
- so the preview tracks the live outcome exactly. Texts moved
1021
- into `cli/i18n/init.texts.ts` and `cli/i18n/db.texts.ts` per
1022
- the N4 pattern. **9 new tests** under `init-cli.test.ts` (5
1023
- cases) and `db-cli.test.ts` (9 cases) cover the previews + the
1024
- spec invariants ("DB file checksum unchanged after dry-run",
1025
- "scope directory absent after dry-run", "source-not-found
1026
- still exits 5", "confirmation prompt skipped under dry-run").
1027
- - **H1 — Centralised exit codes.** New `cli/util/exit-codes.ts`
1028
- exporting `ExitCode` (`Ok` / `Issues` / `Error` / `Duplicate` /
1029
- `NonceMismatch` / `NotFound`) and the type alias `TExitCode`.
1030
- Every `Command#execute()` migrated from numeric literals (123
1031
- sites across 17 files) to the enum. Single source of truth
1032
- aligned with `spec/cli-contract.md` §Exit codes. **Bug fix
1033
- surfaced en passant:** `sm job prune` returned `2` for "DB
1034
- missing" while every other read-side verb returned `5` via
1035
- `assertDbExists`; corrected to use the shared helper and return
1036
- `NotFound`. Companion test updated to expect `5`.
1037
- - **H2 — Plugin loader timeout.** `IPluginLoaderOptions.loadTimeoutMs`
1038
- (default `5000`, exported as `DEFAULT_PLUGIN_IMPORT_TIMEOUT_MS`).
1039
- Each dynamic `import()` now races against a timer; on timeout
1040
- the plugin is reported as `load-error` with a message naming
1041
- the elapsed budget and pointing at top-level side effects as
1042
- the likely cause (network call, infinite loop, large blocking
1043
- work). Without this a plugin with a hanging top-level `await`
1044
- blocks every host CLI command indefinitely.
1045
- - **H4 — `--strict` self-validates `--json` output.** When
1046
- `sm scan --strict --json` is invoked, the produced `ScanResult`
1047
- is validated against `scan-result.schema.json` before stdout.
1048
- Catches the case where a custom detector emits a Link that
1049
- passes the shallow `validateLink` guard but fails the full
1050
- schema, which would silently land in stdout and break a
1051
- downstream `sm scan compare-with -`.
1052
- - **H5 — External-link discrimination uses URL-shape regex.**
1053
- `isExternalUrlLink` was string-matching `http://` / `https://`
1054
- only; any other URL scheme (`mailto:`, `data:`, `file:///`,
1055
- `ftp://`) was silently classified as internal and polluted the
1056
- graph as a fake internal link with `byPath` lookups that always
1057
- missed. Replaced with the RFC 3986 scheme regex
1058
- (`/^[a-z][a-z0-9+\-.]+:/i`), guarding against Windows-style
1059
- absolute paths via the ≥ 2-char scheme constraint.
1060
- - **H6 — Prior snapshot validated under `--strict`.** Both
1061
- `sm scan` and `sm watch`, when run with `--strict`, validate
1062
- the DB-resident `ScanResult` against the spec schema before
1063
- handing it to the orchestrator. A DB corrupted manually or
1064
- mid-rollback used to slip nodes with malformed `bodyHash` /
1065
- `frontmatterHash` into the rename heuristic, where the
1066
- dereference would silently produce spurious matches.
1067
-
1068
- #### Medium — surface & extensibility
1069
-
1070
- - **M1 — `sm scan compare-with` sub-verb.** New
1071
- `ScanCompareCommand` in `cli/commands/scan-compare.ts`; the
1072
- `--compare-with` flag is removed from `ScanCommand`. The
1073
- sub-verb form structurally rejects flag combos that used to
1074
- require runtime guards (`--changed`, `--no-built-ins`,
1075
- `--allow-empty`, `--watch`): Clipanion rejects them at parse
1076
- time as unknown options.
1077
- - **M2 — `kernel/index.ts` enumerated exports.** Replaced the two
1078
- `export type *` wildcards (from `./types.js` and
1079
- `./ports/index.js`) with explicit named exports. Same set of
1080
- public types — the DTS size and tests confirm parity. Going
1081
- forward, any new domain type or port change requires an
1082
- explicit edit to the barrel, preventing silent surface drift.
1083
- - **M3 — Build hack documented (workaround retained).** Tried to
1084
- replace the post-build `restoreNodeSqliteImports` pass with
1085
- `external: ['node:sqlite']` in `tsup.config.ts`. Esbuild marks
1086
- the specifier as external but still strips the `node:` prefix;
1087
- same outcome with `[/^node:/]` regex and `packages: 'external'`
1088
- (which also externalises real npm deps). Reverted to the
1089
- post-build `replaceAll` pass, with a docstring documenting
1090
- every workaround attempted so the next agent does not repeat
1091
- the spike.
1092
- - **M4 — `tryWithSqlite` helper.** See C2.
1093
- - **M5 — `CamelCasePlugin` trap documented.** Added a
1094
- trap-warning block to `SqliteStorageAdapter`'s docstring:
1095
- `sql.raw` / `sql\`...\``template literals do NOT pass through
1096
- the`CamelCasePlugin`; raw SQL fragments must use snake_case to
1097
- match the migrations.
1098
- - **M6 — Per-extension error reporting.** When the orchestrator
1099
- drops a link emitted with an undeclared kind or an issue with
1100
- an invalid severity, it now emits a `type: 'extension.error'`
1101
- `ProgressEvent` instead of silently swallowing. The CLI
1102
- subscribes via the new `createCliProgressEmitter(stderr)`
1103
- helper and renders those events as `extension.error: <message>`
1104
- on stderr. Plugin authors finally see WHY their link / issue
1105
- disappears from the result. Wired in `scan` (normal +
1106
- compare-with), `watch`, and `init`.
1107
- - **M7 — Type naming convention documented (no rename).** Top-of-
1108
- file docstring in `kernel/types.ts` and a new section in
1109
- `AGENTS.md` describe the four-bucket convention the codebase
1110
- has always implicitly followed: domain types (no prefix,
1111
- mirrors spec schemas), hexagonal ports (`Port` suffix), runtime
1112
- extension contracts (`I` prefix), internal shapes (`I`
1113
- prefix). Mass rename was rejected after a cost-benefit pass —
1114
- naming changes are cheap to write but expensive to review;
1115
- existing names are mostly coherent. The agent base
1116
- (`_plugins/minions/shared/architect.md`) gained a "Naming
1117
- conventions check" sub-section in REVIEW mode so future audits
1118
- reach the same conclusion.
1119
-
1120
- #### Low / nit — cleanup
1121
-
1122
- - **L1 — `omitModule` JSON replacer precision.** Identifies the
1123
- ESM namespace by `[Symbol.toStringTag] === 'Module'` instead of
1124
- matching every `module` key blindly. A plugin manifest that
1125
- legitimately ships an unrelated `module` field (e.g. a string
1126
- property in `metadata`) is no longer silently dropped from
1127
- `sm plugins list --json` output.
1128
- - **L2 — Stub verbs flagged in `--help`.** Every
1129
- `not-yet-implemented` verb in `cli/commands/stubs.ts` carries a
1130
- `(planned)` suffix on its `description`, surfaced in
1131
- `sm --help`. The `notImplemented` helper now writes
1132
- `<verb>: not yet implemented (planned).` on stderr instead of
1133
- promising a specific Step number — roadmap step numbers shift
1134
- mid-flight, stale promises in `--help` are worse than no
1135
- promise.
1136
- - **L3 — Dead `eslint-disable` removed** from
1137
- `cli/util/plugin-runtime.ts`.
1138
- - **N1 — `Link.source` vs `Link.sources` doc clarified.** Both
1139
- fields now carry inline doc-comments calling out the singular /
1140
- plural naming trap. Spec-frozen, but the ambiguity is the
1141
- easiest way to misread the type for new contributors.
1142
- - **N2 — `sm check` Usage examples expanded.** The `-g/--global`
1143
- and `--db <path>` flags were declared but missing from the
1144
- `Usage.examples` block — asymmetry with `sm scan` and the rest
1145
- of the read-side verbs that ship the same flags. Two examples
1146
- added: `sm check --global` and `sm check --db
1147
- /path/to/skill-map.db`.
1148
- - **N4 — Error / hint strings extracted to `*.texts.ts` modules
1149
- with `{{name}}` template interpolation.** Pre-1.0 is the
1150
- natural moment to seed the pattern before the string set grows.
1151
- The workspace `ui/` already has a sibling layout at
1152
- `ui/src/i18n/` (functions returning template literals); CLI
1153
- takes a deliberately different shape — flat string templates
1154
- with `{{name}}` placeholders, interpolated by a tiny
1155
- `tx(template, vars)` helper. Rationale: the template form is
1156
- **drop-in compatible with Transloco / Mustache / Handlebars**
1157
- (the syntax they all share) so the day this project migrates to
1158
- a real i18n library, the strings move as-is. Functions would
1159
- have to be re-shaped first.
1160
-
1161
- Helper at `kernel/util/tx.ts`. Contract:
1162
-
1163
- - Every `{{name}}` token MUST have a matching key in the vars
1164
- object — missing key throws (silent fallback hides
1165
- forgotten args in production).
1166
- - `null` / `undefined` values throw — caller coerces
1167
- upstream.
1168
- - Whitespace inside the braces tolerated (`{{ name }}`) so
1169
- long templates wrap cleanly across `+`-joined lines.
1170
- - Plural / conditional logic does NOT live in the template;
1171
- the caller picks `*_singular` vs `*_plural` keys.
1172
-
1173
- Files created:
1174
-
1175
- - `kernel/util/tx.ts` — the helper itself, with 13 tests in
1176
- `test/tx.test.ts` (single / multi token, whitespace,
1177
- missing / null / undefined keys, identifier shapes, error
1178
- truncation).
1179
- - `kernel/i18n/orchestrator.texts.ts` — frontmatter
1180
- malformed/invalid templates, `extension.error` payloads,
1181
- root validation errors.
1182
- - `kernel/i18n/plugin-loader.texts.ts` — every `load-error` /
1183
- `invalid-manifest` / `incompatible-spec` reason, plus the
1184
- import timeout message.
1185
- - `cli/i18n/scan.texts.ts` — `sm scan` flag-clash / scan
1186
- failure / guard / summary templates, plus the `sm scan
1187
-
1188
- compare-with`dump-load errors.
1189
-
1190
- -`cli/i18n/watch.texts.ts`—`sm watch`lifecycle templates. -`cli/i18n/init.texts.ts`—`sm init`templates including
1191
- the`--dry-run`previews and the singular/plural pair for
1192
- gitignore updates. -`cli/i18n/db.texts.ts`—`sm db reset`/`sm db restore` templates including their`--dry-run`previews. -`cli/i18n/cli-progress-emitter.texts.ts`— the
1193
- `extension.error: ...` stderr line.
1194
-
1195
- String content moved verbatim — every existing test that
1196
- matches on stderr / stdout content keeps passing. Trivial
1197
- single-token strings (`'No issues.\n'`) and rare per-handler
1198
- bespoke phrases stay inline; the pattern is now established
1199
- for whoever wants to migrate them in a follow-up.
1200
-
1201
- Note on `ui/` divergence: today the two workspaces use
1202
- different shapes for their text tables (functions in `ui/`,
1203
- templates in `cli/`). Aligning them is a follow-up — the day a
1204
- real i18n library lands, both converge on its native shape.
1205
- The CLI shape is closer to the eventual destination.
1206
-
1207
- - **N6 — `TIssueSeverity` aliased to `Severity`.** SQLite schema
1208
- type now reads `type TIssueSeverity = Severity` instead of
1209
- duplicating the union literal. Keeps DB and runtime in
1210
- lock-step if the union ever evolves.
1211
-
1212
- ### Migrations consolidation (kernel DB)
1213
-
1214
- - **`src/migrations/001_initial.sql` + `002_scan_meta.sql`**
1215
- consolidated into a single `001_initial.sql`. Pre-1.0 with no
1216
- released DBs to forward-migrate, the two-file split was a
1217
- historical accident from an incremental shipment. After
1218
- consolidation: same 12 tables, same constraints, same indexes;
1219
- `PRAGMA user_version` of a freshly-initialised DB is now `1`
1220
- instead of `2`. Migration runner is unchanged (it tolerates any
1221
- count of `NNN_*.sql` files).
1222
-
1223
- ### Test coverage (Part 1)
1224
-
1225
- - New tests for H2 (plugin loader timeout — 2 cases),
1226
- M6 (orchestrator `extension.error` emission — 3 cases),
1227
- CLI progress emitter wiring (4 cases). The compare-with suite
1228
- (`scan-compare.test.ts`, 9 cases) was migrated to
1229
- `ScanCompareCommand` and the three flag-clash tests dropped
1230
- (the flags are now structurally absent on the sub-verb). Test
1231
- totals: 479 (start of pass) → 488 (after H2/M6 tests) → 485
1232
- (after the three flag-clash deletions).
1233
-
1234
- ### Deferred / out of scope
1235
-
1236
- The findings below were reviewed but did not warrant code
1237
- changes; each has its own resolution noted alongside.
1238
-
1239
- - **L4 — `runScan` / `runScanWithRenames` unification.** Already
1240
- resolved by C1 (both are thin wrappers around
1241
- `runScanInternal`).
1242
- - **L5 — Node-version-guard exit code.** Reviewed against the
1243
- updated exit-code table; existing `2` is correct under
1244
- "operational error / unhandled exception". Spec table got the
1245
- environment-mismatch clarification (above).
1246
- - **L6 — `loadSchemaValidators()` cache.** Already cached at
1247
- module level since Step 5.12.
1248
- - **L7 — `pkg with { type: 'json' }` portability.** Stable in
1249
- Node ≥ 22; `engines.node": ">=24.0"` covers it. No fallback
1250
- needed.
1251
- - **N3 — `compare-with` "dump not found" exit code.** The error
1252
- paths in `ScanCompareCommand` already use the `ExitCode.Error`
1253
- enum (= 2) for dump load failures, matching the spec clause for
1254
- operational errors.
1255
- - **N5 — Exit-code list completeness.** Verified the comment in
1256
- `cli/entry.ts` against `spec/cli-contract.md` §Exit codes —
1257
- identical, no edit needed.
1258
-
1259
- ## Part 2 — Plugin model overhaul (5-phase implementation)
1260
-
1261
- ### Summary
1262
-
1263
- The plugin model received a comprehensive overhaul before
1264
- stabilizing at v1.0. Plugin kinds total after this bump: **6**
1265
- (Provider, Extractor, Rule, Action, Formatter, Hook). All
1266
- breakings are pre-1.0 minor per `versioning.md` § Pre-1.0.
1267
-
1268
- ### Phase 1 (commit 7354c26) — Foundation
1269
-
1270
- Five sub-phases, additive or pre-1.0 minor breakings:
1271
-
1272
- - **A.4** — three-tier frontmatter validation model documented in
1273
- `plugin-author-guide.md` (default permissive + `unknown-field`
1274
- rule + `scan.strict` promote-to-error). Behavior unchanged.
1275
- - **A.5** — plugin id global uniqueness: `directory ==
1276
- manifest.id` rule, new status `id-collision` (sixth),
1277
- validation in boot/scan/doctor. Cross-root collisions block
1278
- both involved plugins; user resolves by renaming.
1279
- - **A.6** — extension ids qualified `<plugin-id>/<ext-id>` in
1280
- registry. Built-ins classified into `claude/*` (4 Claude-
1281
- specific) and `core/*` (7 kernel built-ins) bundles. New
1282
- `Registry.get/find` APIs; `defaultRefreshAction` schema
1283
- requires the qualified pattern; `extension.error` events emit
1284
- qualified ids.
1285
- - **A.10** — optional `applicableKinds` filter on Detector
1286
- manifest; fail-fast skip for non-matching kinds (zero CPU/LLM
1287
- cost); doctor warning for kinds not declared by any installed
1288
- Provider. Empty array invalid; absence preserves apply-to-all
1289
- default.
1290
- - **Granularity** — Built-ins now respect `config_plugins`
1291
- enable/disable via granularity-aware filtering. New
1292
- `IBuiltInBundle` shape with `granularity: 'bundle' |
1293
- 'extension'`; `claude` ships as bundle (all-or-nothing), `core`
1294
- as extension (each toggleable). User plugins default to bundle;
1295
- opt in via `granularity` in `plugin.json`. Both plugin ids and
1296
- qualified extension ids accepted as keys in `config_plugins`
1297
- and `settings.json#/plugins` (no schema change needed).
1298
-
1299
- 550/550 tests pass (+33 vs baseline 517).
1300
-
1301
- ### Phase 2 (commit ae3eaa6) — Renames
1302
-
1303
- Four sub-phases, all breaking but allowed in minor pre-1.0:
1304
-
1305
- - **2a (Renderer → Formatter)** — Kind, types, files renamed.
1306
- Method `render(ctx)` → `format(ctx)`; manifest field `format`
1307
- → `formatId` (TS clash resolution). Same contract: graph →
1308
- string, deterministic-only.
1309
- - **2b (Adapter → Provider)** — New required field
1310
- `explorationDir` on the manifest (e.g. `~/.claude` for the
1311
- Claude Provider). DB schema migrated in-place (column
1312
- `nodes.adapter` → `nodes.provider`, etc.). The
1313
- hexagonal-architecture `RunnerPort.adapter` /
1314
- `StoragePort.adapter` is unchanged.
1315
- - **2c (Audit removed)** — Audit kind removed. The single
1316
- built-in `validate-all` migrated to a Rule (qualified id
1317
- `core/validate-all`, `evaluate(ctx) → Issue[]`). CLI verbs
1318
- `sm audit *` removed; users invoke via `sm check --rules
1319
- core/validate-all`.
1320
- - **2d (Detector → Extractor)** — Method signature changes from
1321
- `detect(ctx) → Link[]` to `extract(ctx) → void` — output flows
1322
- through three ctx callbacks: `emitLink`, `enrichNode`, `store`.
1323
- Built-ins migrated maintain functional parity using `emitLink`.
1324
- Persistence of `enrichNode` deferred to Phase 4 (A.8 stale
1325
- layer); orchestrator buffers in memory today.
1326
-
1327
- 554/554 cli + 32/32 testkit pass.
1328
-
1329
- ### Phase 3 (commit 34f993e) — Schema relocation
1330
-
1331
- **A.2** — Per-kind frontmatter schemas relocate from spec to the
1332
- Provider that declares them. Spec keeps only `frontmatter/base`
1333
- (universal).
1334
-
1335
- - 5 schemas moved (`git mv`):
1336
- `spec/schemas/frontmatter/{skill,agent,command,hook,note}.schema.json`
1337
- → built-in Claude Provider's `schemas/` directory. New `$id`:
1338
- `https://skill-map.dev/providers/claude/v1/frontmatter/<kind>`.
1339
- Cross-package `$ref` resolves via the spec base's `$id`
1340
- (`https://skill-map.dev/spec/v0/frontmatter/base.schema.json`);
1341
- AJV resolves by `$id` when both schemas register on the same
1342
- instance.
1343
- - Provider manifest gains a required `kinds` map subsuming three
1344
- former fields: `emits` (now derives from
1345
- `Object.keys(kinds)`), the flat `defaultRefreshAction` map (now
1346
- per-entry inside `kinds[<kind>].defaultRefreshAction`), and the
1347
- new `schema` (path to the per-kind schema relative to the
1348
- provider directory).
1349
- - Built-in Claude Provider migrated: 5 kind entries (skill,
1350
- agent, command, hook, note), each with `schema`, `schemaJson`
1351
- (runtime field, AJV-compiled at load), and qualified
1352
- `defaultRefreshAction` (`claude/summarize-<kind>`).
1353
- - Kernel orchestrator parse phase asks the Provider for the
1354
- schema via `IProviderFrontmatterValidator` (composed by scan
1355
- via `buildProviderFrontmatterValidator`) instead of reading
1356
- from spec/. Flow: validate base → look up provider → validate
1357
- per-kind schema from Provider.
1358
- - `schema-validators.ts` catalog loses the 5 per-kind frontmatter
1359
- entries; only `frontmatter-base` remains kernel-known.
1360
- `plugin-loader`'s `stripFunctionsAndPluginId` now also strips
1361
- `schemaJson` (runtime-only) from each `kinds` entry before
1362
- AJV-validating the manifest.
1363
- - Coverage matrix: 28 → 23 schemas (the 5 per-kind frontmatter
1364
- schemas are now Provider-owned and ship with their own
1365
- conformance suite in Phase 5 / A.13).
1366
-
1367
- 556/556 cli + 32/32 testkit pass.
1368
-
1369
- ### Phase 4 (commit e62695f) — Probabilistic infra
1370
-
1371
- Five sub-phases, all breaking but allowed in minor pre-1.0:
1372
-
1373
- - **4a (A.9)** — fine-grained Extractor cache via new
1374
- `scan_extractor_runs` table. Resolves gap where newly
1375
- registered Extractors silently skipped cached nodes; cache hit
1376
- logic now per-(node, extractor). Uninstalled Extractors cleaned
1377
- (rows + orphan links). Migration in-place.
1378
- - **4b (A.12)** — opt-in `outputSchema` for plugin custom
1379
- storage. Manifest gains `storage.schema` (Mode A) and
1380
- `storage.schemas` (Mode B) for AJV validation of
1381
- `ctx.store.write/.set` calls. Throws on shape violation;
1382
- default absent = permissive.
1383
- - **4c (A.8)** — enrichment layer + stale tracking. New
1384
- `node_enrichments` table persists per-(node, extractor)
1385
- partials separately from author's frontmatter (immutable).
1386
- Probabilistic enrichments track `body_hash_at_enrichment`; scan
1387
- flags `stale=1` on body change (NOT deleted, preserves LLM
1388
- cost). Helper `mergeNodeWithEnrichments` filters stale +
1389
- last-write-wins. New verbs `sm refresh <node>` and
1390
- `sm refresh --stale` (stubs awaiting Step 10).
1391
- - **4d (A.11)** — sixth plugin kind `hook`. Declarative
1392
- subscriber to a curated set of 8 lifecycle events (`scan.*`,
1393
- extractor/rule/action.completed,
1394
- job.spawning/completed/failed). Other events deliberately not
1395
- hookable. Manifest declares `triggers[]` (load-time validated)
1396
- and optional `filter`. Three new kernel events added to
1397
- catalog. Dual-mode (det dispatched in-process; prob deferred to
1398
- Step 10).
1399
- - **4e (A.7)** — `sm check --include-prob` opt-in flag (stub).
1400
- Default `sm check` unchanged: det only, CI-safe. With flag:
1401
- detects prob rules, emits stderr advisory; full dispatch awaits
1402
- Step 10. Combines with `--rules`, `-n`, `--no-plugins`.
1403
-
1404
- 591/591 cli + 32/32 testkit pass.
1405
-
1406
- ### Phase 5 (commit 03b5a65) — Conformance + cleanup
1407
-
1408
- **A.13** — Conformance fixture relocation:
1409
-
1410
- - 3 cases moved (`git mv`): `basic-scan`, `orphan-detection`,
1411
- `rename-high` →
1412
- `src/extensions/providers/claude/conformance/cases/`. 11
1413
- fixture files (`minimal-claude/`, `orphan-{before,after}/`,
1414
- `rename-high-{before,after}/`) moved alongside.
1415
- - New `coverage.md` per-Provider listing the 5 frontmatter
1416
- schemas (skill, agent, command, hook, note) and their cases.
1417
- - New verb `sm conformance run [--scope spec|provider:<id>|all]`.
1418
- Discovery by convention at `<plugin-dir>/conformance/`. The
1419
- existing runner gains optional `fixturesRoot` (default
1420
- `<specRoot>/conformance/fixtures` for compat); tooling using
1421
- the public API of `@skill-map/cli/conformance` keeps working.
1422
- `--json` deferred — reporter shape not yet frozen.
1423
- - Spec keeps only the kernel-agnostic case (`kernel-empty-boot`)
1424
- and the universal preamble fixture. Coverage matrix downgrades
1425
- conservatively (rows that depended on `basic-scan` are now
1426
- partial or missing, with cross-link to the Provider's matrix).
1427
-
1428
- ROADMAP cleanup:
1429
-
1430
- - The three "Status: target state for v0.8.0 — spec catch-up
1431
- pending" banners on §Plugin system / §Frontmatter standard /
1432
- §Enrichment are removed; prose shifts from future to present
1433
- ("kinds from v0.7.0 are renamed" → "were renamed in spec
1434
- 0.8.0"; Model B enrichment now describes the shipped
1435
- `node_enrichments` table with `body_hash_at_enrichment` rather
1436
- than "table or column set decided in PR").
1437
- - Decision-log entry for the working session rewritten to
1438
- reflect "shipped" rather than "pending".
1439
- - Last-updated header gains an "implementation" paragraph
1440
- listing the four prior phase commits.
1441
-
1442
- 593/593 cli + 32/32 testkit pass (+2 vs Phase 4 baseline).
1443
- spec:check green (40 files hashed — down from 53 because the
1444
- Claude-specific cases and fixtures left the spec's hash set).
1445
-
1446
- ### Breaking changes for plugin authors (Part 2)
1447
-
1448
- Manifest renames:
1449
-
1450
- - `kind: 'adapter'` → `kind: 'provider'`
1451
- - `kind: 'detector'` → `kind: 'extractor'`
1452
- - `kind: 'renderer'` → `kind: 'formatter'`
1453
- - `kind: 'audit'` removed (migrate to `kind: 'rule'`).
1454
-
1455
- Method signatures:
1456
-
1457
- - Detector `detect(ctx) → Link[]` → Extractor `extract(ctx) →
1458
- void` (output via `ctx.emitLink` / `ctx.enrichNode` /
1459
- `ctx.store`).
1460
- - Renderer `render(ctx) → string` → Formatter `format(ctx) →
1461
- string`.
1462
-
1463
- Manifest fields:
1464
-
1465
- - Provider gains required `explorationDir`.
1466
- - Provider's flat `defaultRefreshAction` map replaced by per-kind
1467
- entries inside `kinds[<kind>].defaultRefreshAction` (must
1468
- follow qualified pattern `<plugin-id>/<ext-id>`).
1469
- - Provider's `emits` derives from `Object.keys(kinds)` (the
1470
- manifest field is gone).
1471
- - Provider's per-kind schemas declared via `kinds[<kind>].schema`
1472
- (path relative to provider dir).
1473
- - Renderer's `format` field renamed to `formatId` on the
1474
- Formatter manifest (TS clash resolution).
1475
- - New plugin kind `hook` with `triggers[]` + optional `filter`.
1476
- - Optional `outputSchema` (`storage.schema` / `storage.schemas`)
1477
- for Mode A / Mode B plugin custom storage.
1478
- - Optional `applicableKinds` filter on Extractor manifest.
1479
-
1480
- Extension ids:
1481
-
1482
- - All extension ids must be qualified
1483
- `<plugin-id>/<extension-id>` (built-ins classified into
1484
- `claude/*` and `core/*`).
1485
-
1486
- DB schema:
1487
-
1488
- - Two new tables added in-place to `001_initial.sql` (pre-1.0
1489
- consolidation, no production DBs to migrate):
1490
- `scan_extractor_runs` and `node_enrichments`.
1491
- - Column rename `nodes.adapter` → `nodes.provider` (and parallel
1492
- in `result.adapters` → `result.providers`).
1493
-
1494
- ## Test stats
1495
-
1496
- 593/593 cli + 32/32 testkit pass (post-Phase 5).
1497
- Two new DB tables (`scan_extractor_runs`, `node_enrichments`)
1498
- added in-place to `001_initial.sql` (pre-1.0 consolidation, no
1499
- production DBs to migrate). The 5 per-kind frontmatter schemas
1500
- relocated from spec/ to the Claude Provider package.
1501
-
1502
- ## [Unreleased]
1503
-
1504
- ### Minor Changes
1505
-
1506
- - Conformance fixture relocation (Phase 5 / A.13). The conformance suite splits along ownership lines: spec-owned cases (kernel-agnostic, today only `kernel-empty-boot` plus the deferred `preamble-bitwise-match`) keep living under `spec/conformance/`; Provider-owned cases that exercise a Provider's own kind catalog move next to that Provider's manifest, under `<plugin-dir>/conformance/`. The reference impl's Claude Provider now hosts `basic-scan`, `rename-high`, and `orphan-detection` together with their `minimal-claude` / `orphan-{before,after}` / `rename-high-{before,after}` fixtures at `src/extensions/providers/claude/conformance/`. The split mirrors the spec 0.8.0 Phase 3 schema relocation: cases that depend on Claude-specific kinds (`skill`) belong with the Provider that declares the kind, not in the spec. New CLI verb `sm conformance run [--scope spec|provider:<id>|all]` (default `all`) drives both buckets in one invocation; `--scope spec` and `--scope provider:claude` narrow to a single suite for targeted runs and CI matrices. The reference runner gains an optional `fixturesRoot` parameter so cases can resolve their fixtures against the Provider's directory instead of the spec's. `spec/conformance/README.md` updated for the dual-ownership layout (spec-owned + Provider-owned tables, `sm conformance run` documented, runner pseudocode amended). `spec/conformance/coverage.md` retargeted: rows that used to credit `basic-scan` (now Provider-owned) downgrade to `kernel-empty-boot`-only or `🔴 missing` and point to the Provider's coverage file (`src/extensions/providers/claude/conformance/coverage.md`); the rename-heuristic non-schema row notes the Provider ownership. `spec/cli-contract.md` adds a §Conformance subsection under §Verb catalog and adds `sm conformance run` to the elapsed-time §Scope. `spec/architecture.md` opening sentence credits both buckets. Pre-1.0 minor per `versioning.md` § Pre-1.0; breaking only for tooling that hard-codes the previous case paths under `spec/conformance/cases/{basic-scan,rename-high,orphan-detection}.json` — no real ecosystem affected today (the reference impl's runner already migrates).
1507
-
1508
- - `sm check` gains `--include-prob` opt-in flag for probabilistic Rule dispatch (Phase 4 / A.7). Default unchanged: deterministic only, CI-safe — same status quo behaviour. With the flag, the verb loads the plugin runtime, finds Rules with `mode === 'probabilistic'` (filtered by `--rules` if set), and emits a stderr advisory naming the skipped rule ids. Full dispatch lands when the job subsystem ships at Step 10; until then the flag is a stub — prob rules never produce issues, never alter the exit code. New companion flag `--async` is reserved for the future encoding (returns job ids without waiting once jobs land); today it is a no-op the advisory mentions. Companion filters `-n <node.path>` and `--rules <ids>` (comma-separated qualified or short ids) added to `sm check` for granular reads — they restrict the persisted-issue list AND filter which prob rules surface in the advisory. Does NOT extend to `sm scan` or `sm list`. Documented in `cli-contract.md` §Browse and `plugin-author-guide.md` §Rules. Pre-1.0 minor per `versioning.md` § Pre-1.0; additive — no consumer breakage.
1509
-
1510
- - Sixth plugin kind `hook` added (Phase 4 / A.11). Reacts declaratively to a curated set of 8 lifecycle events — `scan.started`, `scan.completed`, `extractor.completed`, `rule.completed`, `action.completed`, `job.spawning`, `job.completed`, `job.failed`. Other events (per-node `scan.progress`, `model.delta`, `run.*`, `job.claimed`, `job.callback.received`) are deliberately NOT hookable: too verbose, internal to the runner, or covered elsewhere. Manifest declares `triggers[]` (validated against the hookable set; an unknown trigger yields `invalid-manifest` at load time with a directed reason naming the offending trigger and the full hookable list) and an optional `filter` object (top-level field equality match against the event payload; cross-field validation is best-effort in v0.x). Dual-mode: `deterministic` (default) runs `on(ctx)` in-process during the dispatch of the matching event, synchronously between emission and the next pipeline step; `probabilistic` is enqueued as a job (deferred to the job subsystem at Step 10 — probabilistic hooks load but skip dispatch with a stderr advisory until then). Hooks REACT to events; they cannot mutate the pipeline, block emission, or alter outputs. Errors are caught by the dispatcher (logged through `extension.error` with `kind: 'hook-error'`) and never block the main flow. Three new event types added to the catalog so the aggregated Extractor / Rule / Action triggers have a normative shape: `extractor.completed` (one per Extractor, after the full walk), `rule.completed` (one per Rule, after issue validation), `action.completed` (one per Action invocation, after report recording — lands alongside the job subsystem at Step 10). New schema `schemas/extensions/hook.schema.json` (`$id` `https://skill-map.dev/spec/v0/extensions/hook.schema.json`); `schemas/extensions/base.schema.json#/properties/kind/enum` extended with `hook`. Documented in `architecture.md` §Extension kinds (table extended from 5 to 6 rows), §Mode capability matrix (Hook dual-mode), §Hook · curated trigger set (new dedicated section); `plugin-author-guide.md` retitled "## The six extension kinds" with a new Hooks subsection (worked example: Slack notifier on `scan.completed`); `job-events.md` cross-links Hook from each of the 8 hookable triggers, adds the three new aggregated event entries, and updates the experimental tag scope. Coverage matrix grows from 23 to 24 rows. Pre-1.0 minor per `versioning.md` § Pre-1.0; additive — no consumer breakage. Existing extension kinds (`provider`, `extractor`, `rule`, `action`, `formatter`) are untouched.
1511
-
1512
- - Enrichment layer formalized (Phase 4 / A.8). New kernel table `node_enrichments(node_path, extractor_id, body_hash_at_enrichment, value_json, stale, enriched_at, is_probabilistic)` stores `ctx.enrichNode(partial)` outputs separately from the author's frontmatter (which remains IMMUTABLE from any Extractor — both deterministic and probabilistic). Per-Extractor attribution is preserved (one row per `(node, extractor)` pair). Probabilistic enrichments track `body_hash_at_enrichment`; when the scan loop sees a body change, those rows are flagged `stale = 1` (NOT deleted, so the LLM cost is preserved). Deterministic enrichments regenerate via the A.9 fine-grained cache and pisar via PRIMARY KEY conflict on the next re-extract — they are never stale-flagged. Read-side helper `mergeNodeWithEnrichments(node, enrichments)` produces a "merged view" by filtering stale rows, sorting by `enriched_at` ASC, and spread-merging onto the author frontmatter (last-write-wins per field). Stale visibility is opt-in (`includeStale: true`). Rules / `sm check` / `sm export` consume `node.frontmatter` directly (deterministic CI-safe baseline); enrichment consumption is opt-in by the caller. New verbs `sm refresh <node>` (granular) and `sm refresh --stale` (batch) re-run Extractors and upsert fresh enrichment rows — STUBBED until the job subsystem ships at Step 10: deterministic Extractors persist for real, probabilistic Extractors emit a stderr advisory and skip without touching their stale rows. Migration `001_initial.sql` updated in place per the pre-1.0 consolidation precedent (no released DBs to forward-migrate). Documented in `db-schema.md` §`node_enrichments`, `architecture.md` §Extractor · enrichment layer, `cli-contract.md` §Scan, and `plugin-author-guide.md` §Extractors. Pre-1.0 minor per `versioning.md` § Pre-1.0; additive — no consumer breakage.
1513
-
1514
- - Plugin manifest gains optional `storage.schemas` map (Mode B / dedicated) and `storage.schema` (Mode A / KV) for opt-in JSON Schema validation of custom storage writes. AJV-validates `ctx.store.write(table, row)` and `ctx.store.set(key, value)` before persisting; throws on shape violation. Default absent = permissive (status quo). `emitLink` and `enrichNode` keep their universal kernel validation regardless. A schema file missing on disk or failing AJV compile at load time surfaces as `load-error` with a directed reason naming the plugin, the table (Mode B), and the schema path. Pre-1.0 minor per `versioning.md` § Pre-1.0; additive — no consumer breakage. Documented in `plugin-author-guide.md` §Storage and referenced from `architecture.md` §Extractor · output callbacks.
1515
-
1516
- - New kernel table `scan_extractor_runs(node_path, extractor_id, body_hash_at_run, ran_at)` — fine-grained Extractor cache breadcrumbs (Phase 4 / A.9). Replaces the previous "trust the node-level body+frontmatter hash" model that silently bypassed any Extractor newly registered between scans. Cache decision per `(node, extractor)` pair: a new Extractor registered between scans yields a partial cache hit (only the newcomer runs over the cached node); an uninstalled Extractor's rows disappear via replace-all, and links whose sources are exclusively that Extractor disappear with them. Documented in `db-schema.md` §`scan_extractor_runs`. Migration `001_initial.sql` updated in place per the pre-1.0 consolidation precedent (no released DBs to forward-migrate). Pre-1.0 minor per `versioning.md` § Pre-1.0; additive — no consumer breakage.
1517
-
1518
- - Per-kind frontmatter schemas relocate from spec to the Provider that declares them. Spec keeps only `frontmatter/base.schema.json` (universal — fields common to every node across every Provider). The Claude Provider gains a `kinds` map declaring its catalog (`skill` / `agent` / `command` / `hook` / `note`) with per-kind `schema` + `defaultRefreshAction`. The pre-0.8 flat fields `emits: string[]` and `defaultRefreshAction: { <kind>: actionId }` collapse into the new map: `emits` is removed (derived from `Object.keys(kinds)`); each `defaultRefreshAction[<kind>]` value moves into `kinds[<kind>].defaultRefreshAction`. The kernel parse phase asks the Provider for the schema instead of reading from `spec/schemas/frontmatter/<kind>.schema.json`. Schema files moved: `spec/schemas/frontmatter/{skill,agent,command,hook,note}.schema.json` → `src/extensions/providers/claude/schemas/{skill,agent,command,hook,note}.schema.json`; their `$id` updates from `https://skill-map.dev/spec/v0/frontmatter/<kind>.schema.json` to `https://skill-map.dev/providers/claude/v1/frontmatter/<kind>.schema.json`; their `$ref: 'base.schema.json'` updates to `$ref: 'https://skill-map.dev/spec/v0/frontmatter/base.schema.json'` (absolute `$ref`-by-`$id` so AJV resolves cross-package against the spec base registered into the same instance). `spec/schemas/extensions/provider.schema.json` updated: `kinds` is required, `emits` and the old shape of `defaultRefreshAction` removed. `spec/conformance/coverage.md` matrix shrinks from 28 to 23 rows (the five per-kind frontmatter rows belong to the Provider's own conformance suite, planned in Phase 5). `spec/index.json` no longer lists the per-kind schemas. `architecture.md` §Provider section retitled `Provider · kinds catalog and explorationDir`; `plugin-author-guide.md` Provider example updated; `README.md` directory tree updated to reflect spec/frontmatter/ now holds only `base.schema.json`. Pre-1.0 minor per `versioning.md` § Pre-1.0; breaking for any plugin or test referencing `spec/schemas/frontmatter/<kind>.schema.json` paths or `$id`s, the old `provider.emits` field, or the flat `provider.defaultRefreshAction` map — no real ecosystem affected today.
1519
-
1520
- - Plugin kind `renderer` renamed to `formatter`. Method renamed `render(ctx) → format(ctx)`. Manifest field `format` (the identifier consumed by `--format`) renamed to `formatId` to avoid clashing with the new method name. Same contract otherwise: graph → string, deterministic-only. Aligns with industry tooling (ESLint formatter, Mocha reporter, Pandoc writer). `schemas/extensions/renderer.schema.json` renamed to `formatter.schema.json`; the `kind` const flips from `"renderer"` to `"formatter"`; `base.schema.json#/properties/kind/enum` updated. `architecture.md`, `cli-contract.md`, `plugin-author-guide.md`, `README.md` updated to match (Extension kinds table, Execution modes table, testkit helper names, worked CSV example). `conformance/coverage.md` row 28 retargeted at the new schema filename. Pre-1.0 minor per `versioning.md` § Pre-1.0; breaking for any plugin or test referencing `kind: "renderer"`, `IRenderer`, `r.format`, or `render(ctx)` — no real ecosystem affected today.
1521
-
1522
- - Plugin kind `'detector'` renamed to `'extractor'`. Method signature
1523
- changes from `detect(ctx) → Link[]` to `extract(ctx) → void` — output
1524
- flows through three new ctx callbacks: `emitLink(link)` (kernel `links`
1525
- table), `enrichNode(partial)` (kernel enrichment layer, persisted into
1526
- `node_enrichments` per A.8), and the existing `ctx.store` (plugin's
1527
- own table). The Extractor absorbs what would have been a separate
1528
- `Enricher` kind via `enrichNode`. Built-ins migrated:
1529
- `claude/frontmatter`, `claude/slash`, `claude/at-directive`,
1530
- `core/external-url-counter` — all use `emitLink` to maintain
1531
- functional parity with their Detector ancestors. Schema files
1532
- renamed: `schemas/extensions/detector.schema.json` →
1533
- `schemas/extensions/extractor.schema.json`. Persisted DB rows are
1534
- unaffected (link `sources` carry extractor ids verbatim — the field
1535
- was always free-form). Pre-1.0 minor per `versioning.md` § Pre-1.0;
1536
- breaking for any plugin or test referencing `'detector'` as the
1537
- kind, `IDetector`, or the old `Link[]` return signature — no real
1538
- ecosystem affected today.
1539
-
1540
- - Plugin kind `'audit'` removed. The single built-in `'validate-all'`
1541
- migrated to a Rule (qualified id `'core/validate-all'`, method
1542
- `evaluate(ctx) → Issue[]`). The kind had dual personality (composer +
1543
- standalone reporter); the standalone reporter case is naturally a Rule,
1544
- and the composer case is deferred to post-1.0 if a real use case
1545
- appears. CLI verbs `'sm audit run'` and `'sm audit show'` removed;
1546
- users invoke the rule via `sm check --rules core/validate-all`.
1547
- `state_executions.kind` enum narrowed to `['action']` (audit was the
1548
- only other value); the column is preserved as a forward-compatibility
1549
- lever. Schema files removed: `schemas/extensions/audit.schema.json`.
1550
- Coverage matrix shrinks from 29 to 28 rows. Pre-1.0 minor per
1551
- `versioning.md` § Pre-1.0; breaking for any plugin or test referencing
1552
- the audit kind, `IAudit`, `TAuditReport`, or `sm audit` verbs — no
1553
- real ecosystem affected today.
1554
-
1555
- - Plugin kind `'adapter'` renamed to `'provider'`. Manifest gains required
1556
- field `'explorationDir'` (filesystem directory where the Provider's
1557
- content lives, e.g. `'~/.claude'` for the Claude Provider). Built-in
1558
- `claudeAdapter` renamed to `claudeProvider`. The hexagonal-architecture
1559
- `'adapter'` (`RunnerPort.adapter`, `StoragePort.adapter`,
1560
- `FilesystemPort.adapter`, `PluginLoaderPort.adapter`) is unchanged —
1561
- distinct concept, distinct namespace.
1562
- Persisted schema fields renamed: `node.adapter` → `node.provider`,
1563
- `scan-result.adapters` → `scan-result.providers` (pre-1.0 minor — no
1564
- production DBs to migrate; `001_initial.sql` was edited in place per
1565
- the consolidation precedent already established for pre-1.0).
1566
- Project config field renamed: `project-config.adapters` →
1567
- `project-config.providers`. Schema files renamed:
1568
- `schemas/extensions/adapter.schema.json` →
1569
- `schemas/extensions/provider.schema.json`. Pre-1.0 minor per
1570
- `versioning.md` § Pre-1.0; breaking for any plugin or test referencing
1571
- `'adapter'` as the kind, `IAdapter`, or any persisted/config schema
1572
- field renamed above — no real ecosystem affected today.
1573
-
1574
- ## 0.7.1
1575
-
1576
- ### Patch Changes
1577
-
1578
- - 0463a0f: Step 9.4 — plugin author guide + reference plugin + diagnostics polish.
1579
- **Step 9 fully closed** with this changeset.
1580
-
1581
- ### Spec — plugin author guide (additive prose)
1582
-
1583
- New document at `spec/plugin-author-guide.md` covering:
1584
-
1585
- - Discovery roots (`<project>/.skill-map/plugins/`,
1586
- `~/.skill-map/plugins/`, `--plugin-dir <path>`).
1587
- - Manifest fields with the normative schema reference.
1588
- - `specCompat` strategy — narrow ranges pre-`v1.0.0`, `^1.0.0`
1589
- recommendation post-`v1.0.0`.
1590
- - The six extension kinds with one minimal worked example each
1591
- (detector, rule, renderer in full; adapter / audit / action flagged
1592
- for later expansion alongside Step 10).
1593
- - Storage choice (KV vs Dedicated) cross-linking `plugin-kv-api.md`
1594
- and the Step 9.2 triple-protection rule.
1595
- - Execution modes (deterministic / probabilistic) cross-linking
1596
- `architecture.md`.
1597
- - Testkit usage with `runDetectorOnFixture`, `runRuleOnGraph`,
1598
- `runRendererOnGraph`, `makeFakeRunner`.
1599
- - The five plugin statuses (`loaded` / `disabled` / `incompatible-spec`
1600
- / `invalid-manifest` / `load-error`) and how to read them.
1601
- - Stability section (document is stable; widening additions are minor
1602
- bumps; breaking edits are major).
1603
-
1604
- `spec/package.json#files` updated to ship the new doc; `spec/index.json`
1605
- regenerated (57 → 58 hashed files). `coverage.md` unchanged because the
1606
- guide is prose, not a schema.
1607
-
1608
- ### Reference plugin — `examples/hello-world/`
1609
-
1610
- Smallest viable plugin in the principal repo (Arquitecto's pick: in
1611
- the main repo, not separate). One detector (`hello-world-greet`)
1612
- emitting `references` links per `@greet:<name>` token in node bodies.
1613
- Includes:
1614
-
1615
- - `plugin.json` declaring one extension and pinning `specCompat: ^1.0.0`.
1616
- - `extensions/greet-detector.mjs` — runtime instance with both
1617
- manifest fields and the `detect` method.
1618
- - `README.md` — what it does, file layout, three-step "try it
1619
- locally" recipe, what's intentionally missing (storage,
1620
- multi-extension, probabilistic mode), pointers for production-grade
1621
- patterns.
1622
- - `test/greet-detector.test.mjs` — four-assertion test using
1623
- `@skill-map/testkit`, runnable via `node --test` with no build step.
1624
-
1625
- Verified end-to-end: the example plugin loads cleanly under
1626
- `sm plugins list`, scans contribute its links to the persisted graph,
1627
- and the testkit-based test passes. The example is **not** registered
1628
- as a workspace — it's intentionally standalone so users can copy it.
1629
-
1630
- ### CLI — diagnostics polish on `PluginLoader.reason`
1631
-
1632
- Each failure-mode reason string now carries an actionable hint:
1633
-
1634
- - `invalid-manifest` (JSON parse): names the manifest path, suggests
1635
- validating the JSON.
1636
- - `invalid-manifest` (AJV): names the manifest path AND points at
1637
- `spec/schemas/plugins-registry.schema.json#/$defs/PluginManifest`.
1638
- - `invalid-manifest` (specCompat not a valid range): suggests a range
1639
- shape (`"^1.0.0"`).
1640
- - `incompatible-spec`: suggests two remediations (update the plugin's
1641
- `specCompat`, or pin sm to a compatible spec version).
1642
- - `load-error` (extension file not found): includes the absolute
1643
- resolved path, pointer to `plugin.json#/extensions`.
1644
- - `load-error` (default export missing kind): lists the valid kinds.
1645
- - `load-error` (unknown kind): lists the valid kinds.
1646
- - `load-error` (extension manifest schema fails): names the
1647
- per-kind schema (`spec/schemas/extensions/<kind>.schema.json`).
1648
-
1649
- 6 new tests under `test/plugin-loader.test.ts` (`Step 9.4 diagnostics
1650
- polish` describe block) assert each hint shape is present without
1651
- pinning the full text. Test count 437 → **443 cli + 30 testkit = 473**.
1652
-
1653
- ### Step 9 closed
1654
-
1655
- The four sub-steps — 9.1 (plugin runtime wiring), 9.2 (plugin
1656
- migrations + triple protection), 9.3 (`@skill-map/testkit` workspace),
1657
- 9.4 (author guide + reference plugin + diagnostics polish) — together
1658
- turn `skill-map` plugins from "discovered but inert" into a
1659
- first-class authoring surface with documentation, tests, and a
1660
- working reference. Next step: **Step 10 — job subsystem + first
1661
- probabilistic extension** (wave 2 begins).
92
+ - **`Node.kind` opens to any Provider-declared string.** `node.schema.json#/properties/kind` becomes `{ type: 'string', minLength: 1 }`; the `CHECK in (...)` SQL constraints on `scan_nodes.kind` and `state_summaries.kind` drop; `extensions/action.schema.json#/.../filter/kind` widens to a string array. Providers declare their own kind catalog through the `kinds` map; the spec no longer enumerates a closed set.
1662
93
 
1663
94
  ## 0.7.0
1664
95
 
1665
- ### Minor Changes
1666
-
1667
- - d730094: Spec — Execution modes (deterministic / probabilistic) lifted to a first-class architectural property
1668
-
1669
- Frames a meta-property of skill-map that was previously implicit and scattered:
1670
- **every analytical extension is one of two modes** — `deterministic` (pure code,
1671
- runs in scan-time pipelines) or `probabilistic` (invokes an LLM through
1672
- `RunnerPort`, runs only as queued jobs). The dual-mode capability now spans four
1673
- of the six extension kinds; Adapter and Renderer remain locked to deterministic
1674
- because they sit at the system boundaries (filesystem and graph-to-string) where
1675
- non-determinism would break boot reproducibility and snapshot diffing.
1676
-
1677
- **Spec changes:**
1678
-
1679
- - `architecture.md` — new top-level section **§Execution modes** before
1680
- §Extension kinds. Defines the two modes, the per-kind capability matrix
1681
- (Detector / Rule / Action dual-mode by manifest declaration; Audit dual-mode
1682
- with mode **derived** from `composes[]`; Adapter / Renderer deterministic-only),
1683
- the runtime separation (`deterministic` runs in `sm scan` / `sm check`;
1684
- `probabilistic` runs only via `sm job submit <kind>:<id>`), and the
1685
- `RunnerPort` injection contract for probabilistic extensions.
1686
- - `architecture.md` §Extension kinds — table updated: each row clarifies the
1687
- mode posture (Adapter / Renderer marked deterministic-only; Detector / Rule /
1688
- Action marked dual-mode; Audit marked derived-mode).
1689
- - `architecture.md` §Stability — new clause: execution modes and the per-kind
1690
- capability matrix are stable as of v1.0.0; adding a third mode, changing
1691
- which kinds are dual-mode, or changing the audit's derivation rule is a major
1692
- bump.
1693
-
1694
- **Schema changes:**
1695
-
1696
- - `schemas/extensions/detector.schema.json`:
1697
- - New optional `mode` field (`deterministic` | `probabilistic`, default
1698
- `deterministic`). Omitting is equivalent to deterministic — keeps existing
1699
- detectors valid without an update.
1700
- - Description updated to spell out the dual-mode contract.
1701
- - `schemas/extensions/rule.schema.json`:
1702
- - Same shape: new optional `mode` field with default `deterministic`.
1703
- - Description rewritten — the previous "Rules MUST be deterministic" claim
1704
- moved into the deterministic-mode contract; probabilistic rules are now
1705
- explicitly allowed and run only as queued jobs.
1706
- - `schemas/extensions/action.schema.json`:
1707
- - **Breaking** — `mode` enum renamed: `local` → `deterministic`,
1708
- `invocation-template` → `probabilistic`. Pre-1.0; no consumers depend on
1709
- the old values (no third-party action plugins shipped). Description, the
1710
- two `if/then` branches, and the `expectedDurationSeconds` /
1711
- `promptTemplateRef` field descriptions updated accordingly.
1712
- - **Bug fix** — the schema previously declared `allOf` twice at the root
1713
- (lines 6–8 and 71–80); the second silently overrode the first, dropping
1714
- `$ref: base.schema.json`. Both blocks are now merged into a single `allOf`
1715
- so the action schema actually composes the base shape.
1716
- - `schemas/extensions/audit.schema.json`:
1717
- - Description rewritten — the "deterministic workflow" claim is replaced by
1718
- the **derived-mode** rule: the audit's effective mode is computed from
1719
- `composes[]` at load time. If every composed primitive is deterministic,
1720
- the audit is deterministic; if any is probabilistic, the audit is
1721
- probabilistic and dispatches as a job. Declaring `mode` directly is a
1722
- load-time error.
1723
- - `composes[]` description updated to mention that each primitive's mode
1724
- participates in derivation; dangling references stay a load-time error.
1725
- - `reportSchemaRef` description updated: probabilistic audits MUST extend
1726
- `report-base.schema.json` (carries `safety` / `confidence`); deterministic
1727
- audits MAY extend it but are not required to.
1728
- - `schemas/extensions/adapter.schema.json`:
1729
- - Description updated to state explicitly that adapters are deterministic-only
1730
- and that `mode` MUST NOT appear. Recommendation for users who want
1731
- LLM-assisted classification: write a probabilistic Detector that emits
1732
- classification hints as `Link[]`.
1733
- - `schemas/extensions/renderer.schema.json`:
1734
- - Description updated to state that renderers are deterministic-only and
1735
- that `mode` MUST NOT appear. Probabilistic narrators of the graph belong
1736
- in jobs and emit Findings, not in renderer manifests.
1737
-
1738
- **Why major (despite pre-1.0 minor norm):**
1739
-
1740
- Renaming the `Action.mode` enum (`local` → `deterministic`,
1741
- `invocation-template` → `probabilistic`) is breaking by definition. No
1742
- third-party Actions exist yet, but the rename touches the canonical surface and
1743
- deserves the bump. New optional fields on Detector / Rule and the new derived-
1744
- mode contract on Audit are additive and would have been minor on their own.
1745
-
1746
- **Implementation work intentionally NOT included here:**
1747
-
1748
- - `src/extensions/built-ins.ts` and the per-extension TS files keep working
1749
- unchanged because the new `mode` is optional with `deterministic` default.
1750
- Explicitly threading `mode: 'deterministic'` through every built-in is a
1751
- follow-up.
1752
- - `RunnerPort` injection through `ctx.runner` for probabilistic extensions is
1753
- spec'd here; the actual context plumbing lands with the first probabilistic
1754
- extension (Step 10 — first summarizer). `MockRunner` continues to satisfy
1755
- tests until then.
1756
- - Conformance case `extension-mode-derivation` (audit composes mixed
1757
- primitives → derives `probabilistic`) is mentioned in `architecture.md` and
1758
- pending under `spec/conformance/coverage.md` for the next release.
1759
- - ROADMAP.md rephrase of Steps 10–11 (from "summarizers" to "wave 2:
1760
- probabilistic extensions") and a positioning section in `README.md` follow
1761
- in separate commits to keep this changeset spec-only.
1762
-
1763
- ### Minor Changes
1764
-
1765
- - a73f3f4: Step 7.1 — File watcher (`sm watch` / `sm scan --watch`)
1766
-
1767
- Long-running watcher that subscribes to the scan roots, debounces
1768
- filesystem events, and triggers an incremental scan per batch. Reuses
1769
- the existing `runScanWithRenames` pipeline, the `IIgnoreFilter` chain
1770
- (`.skill-mapignore` + `config.ignore` + bundled defaults), and the
1771
- `scan.*` non-job events from `job-events.md` — one ScanResult per
1772
- batch, emitted as ndjson under `--json`.
1773
-
1774
- **Spec changes (minor)**:
1775
-
1776
- - `spec/schemas/project-config.schema.json` — new `scan.watch` object
1777
- with a single key `debounceMs` (integer ≥ 0, default 300). Groups
1778
- bursts of filesystem events (editor saves, branch switches, npm
1779
- installs) into a single scan pass. Set to 0 to disable debouncing.
1780
- - `spec/cli-contract.md` §Scan — documents `sm watch [roots...]` as
1781
- the primary verb and `sm scan --watch` as the alias. Watcher
1782
- respects the same ignore chain as one-shot scans, emits one
1783
- ScanResult per batch (ndjson under `--json`), closes cleanly on
1784
- `SIGINT` / `SIGTERM`, exits 0 on clean shutdown. Exit-code rule
1785
- carved out for the watcher: per-batch error issues do not flip the
1786
- exit code (the loop keeps running); operational errors still exit 2.
1787
-
1788
- No new events. No new ports. The watcher is implementation-defined
1789
- inside the kernel package; a future `WatchPort` can be added when /
1790
- if a non-Node implementation needs to swap the chokidar wrapper.
1791
-
1792
- **Runtime changes (minor — new verb + new config key)**:
1793
-
1794
- - `chokidar@5.0.0` pinned in `src/package.json` (single new runtime
1795
- dependency, MIT). Chokidar v5 requires Node ≥ 20.19; the project
1796
- already pins `engines.node: ">=24.0"` so this is a no-op for
1797
- consumers. Brings in `readdirp@5` as a transitive.
1798
- - `src/kernel/scan/watcher.ts` — `IFsWatcher` interface + concrete
1799
- `ChokidarWatcher` wrapping `chokidar.watch()` with the existing
1800
- `IIgnoreFilter` plumbed through, debouncer, batch coalescing,
1801
- and explicit `stop()` for clean teardown.
1802
- - `src/cli/commands/watch.ts` — new `WatchCommand`. `sm scan
1803
- --watch` delegates to the same code path so the two surfaces are
1804
- byte-aligned (no parallel implementations).
1805
- - `src/config/defaults.json` — new `scan.watch.debounceMs: 300`
1806
- default.
1807
-
1808
- **Why minor (not patch)**: new public verb (`sm watch`), new public
1809
- config key (`scan.watch.debounceMs`), and a new flag on an existing
1810
- verb (`sm scan --watch`). All three are surface additions, not bug
1811
- fixes — minor under both the spec and the runtime semver policies.
1812
- No breaking changes; existing `sm scan` without `--watch` is
1813
- byte-identical to before.
1814
-
1815
- **Roadmap**: Step 7 — Robustness, sub-step 7.1 (chokidar watcher).
1816
- Trigger normalization is implicit-already-landed (cabled into every
1817
- detector at Steps 3–4 with full unit tests in
1818
- `src/kernel/trigger-normalize.test.ts`); we do not write a sub-step
1819
- for it. Next sub-steps: 7.2 detector conflict resolution, 7.3 `sm
1820
- job prune` + retention enforcement.
1821
-
1822
- ### Patch Changes
1823
-
1824
- - a73f3f4: Step 7.2 — Detector conflict resolution
1825
-
1826
- Two pieces:
1827
-
1828
- 1. **New built-in rule `link-conflict`** (`src/extensions/rules/link-conflict/`).
1829
- Surfaces detector disagreement. Groups links by `(source, target)` and
1830
- emits one `warn` Issue per pair where the set of distinct `kind` values
1831
- has size ≥ 2. Agreement (single kind across multiple detectors) is
1832
- silent — by design, to avoid massive noise on real graphs.
1833
- Issue payload (`data`) carries `{ source, target, variants }` where
1834
- each `variant` is `{ kind, sources: detectorId[], confidence }`. Variant
1835
- sources are deduped + sorted; confidence is the highest across rows
1836
- of the same kind (`high` > `medium` > `low`).
1837
-
1838
- This is the kernel piece of Decision #90 read-time "consumers that
1839
- need uniqueness aggregate at read time" — the rule is one such
1840
- consumer, on the alarming side. Storage stays untouched (one row
1841
- per detector, no merge, no dedup). Severity is `warn`, not `error`:
1842
- the rule cannot pick which kind is correct, so per `cli-contract.md`
1843
- §Exit codes the verb stays exit 0.
1844
-
1845
- 2. **`sm show` pretty link aggregation** (`src/cli/commands/show.ts`).
1846
- The human renderer now groups `linksOut` / `linksIn` by `(endpoint,
1847
- kind, normalizedTrigger)` and prints one row per group with the
1848
- union of detector ids in a `sources:` field. The section header
1849
- reports both the raw row count and the unique-after-grouping count
1850
- (`Links out (12, 9 unique)`). When N > 1 detector emits the same
1851
- logical link, the row also gets a `(×N)` suffix.
1852
-
1853
- `--json` output is byte-identical to before — raw rows, no merge.
1854
- Storage is byte-identical to before. The grouping is purely a
1855
- read-time presentation choice for human eyes.
1856
-
1857
- **Spec changes (patch)**:
1858
-
1859
- - `spec/cli-contract.md` §Browse — `sm show` row clarifies that pretty
1860
- output groups identical-shape links and that `--json` emits raw rows.
1861
- Patch (not minor) because the JSON contract is unchanged; the human
1862
- output format is non-normative anyway.
1863
-
1864
- **Runtime changes (minor — new rule + new presentation)**:
1865
-
1866
- - New rule `link-conflict` registered in `src/extensions/built-ins.ts`.
1867
- - `sm show` pretty output groups links + reports unique counts.
1868
-
1869
- **UI inspector aggregation deferred to Step 13**: the current Flavor A
1870
- inspector renders the `Relations` card from `node.frontmatter.metadata.{
1871
- related, requires, supersedes, provides, conflictsWith}` directly — it
1872
- does NOT consume `linksOut` / `linksIn` rows from `scan_links`. There
1873
- is no link table to aggregate today. When Step 13's Flavor B lands (Hono
1874
- BFF + WS + full link panel from scan), the aggregation logic from
1875
- `src/cli/commands/show.ts` will need to be ported.
1876
-
1877
- **Roadmap**: Step 7 — Robustness, sub-step 7.2 (detector conflict
1878
- resolution). Closes one of the three remaining frentes; 7.3 (`sm job
1879
- prune` + retention) still pending. Decision #90 unchanged: storage
1880
- keeps raw per-detector rows. The `related` vs LLM-amplification
1881
- discussion is documented in `.tmp/skill-map-related-test/` (status
1882
- quo retained — fields stay opt-in under `metadata.*`; revisit if
1883
- real-world amplification appears).
96
+ ### Minor
1884
97
 
1885
- **Tests**: 327 335 (+8 new for the rule, no regressions).
98
+ - **Execution modes lifted to a first-class architectural property.** `architecture.md` gains §Execution modes defining the per-kind capability matrix: Extractor / Rule / Action / Hook are dual-mode (declared in manifest); Provider and Formatter are deterministic-only (boundary-positioned). Extractor / Rule schemas gain optional `mode` (default `deterministic`); Action's `mode` enum becomes `deterministic` / `probabilistic`; Provider / Formatter forbid the field.
1886
99
 
1887
100
  ## 0.6.1
1888
101
 
1889
- ### Patch Changes
1890
-
1891
- - f41dbad: Step 6.1 — Spec migration: rename the canonical config file from
1892
- `.skill-map.json` (single project-root file) to `.skill-map/settings.json`
1893
- inside the `.skill-map/` scope folder, with a sibling `.skill-map/settings.local.json`
1894
- partner for machine-specific overrides. Aligns the spec with the layered
1895
- config hierarchy described in the roadmap (library defaults → user → user-local
1896
- → project → project-local → env / flags).
1897
-
1898
- **Spec change (breaking, minor under pre-1.0 versioning policy)**:
1899
-
1900
- - `spec/schemas/project-config.schema.json` description updated to point at
1901
- `.skill-map/settings.json` and explicitly mention the `.local.json` partner
1902
- and the layered-merge contract. The schema _shape_ (keys, types, validation
1903
- rules) is unchanged — only the on-disk filename moves. Consumers that read
1904
- values without caring about the source path are unaffected; consumers that
1905
- hard-code the filename must update.
1906
- - `spec/db-schema.md` §Scopes: `history.share: true` reference updated to
1907
- `.skill-map/settings.json`.
1908
- - `spec/conformance/coverage.md` row #6 description updated to reference the
1909
- new path and the optional `settings.local.json` overlay.
1910
-
1911
- **Why minor (not major) at pre-1.0**: per `spec/versioning.md` §Pre-1.0,
1912
- breaking changes ARE allowed in minor bumps while the spec is `0.y.z`. The
1913
- shape of the data is unchanged; only the file name on disk moves.
1914
-
1915
- **No backward-compat shim**: there is no real implementation of the loader
1916
- yet (lands in 6.2), so no live consumer reads `.skill-map.json` today. The
1917
- only known prior reference is the demo `mock-collection/.claude/commands/init*.md`
1918
- fixture, which is updated together with `sm init` in 6.5.
1919
-
1920
- **Runtime change**: none in 6.1 — pure spec edit. The matching loader,
1921
- `sm init`, and `sm config` verbs land in subsequent sub-steps.
1922
-
1923
- **Roadmap update**: `ROADMAP.md` §Configuration "Spec migration" call-out
1924
- flipped from "pending" to "landed Step 6.1, 2026-04-27".
1925
-
1926
- Test count: unchanged (213 → 213 — spec-only edit).
1927
-
1928
- - 8a4667f: Step 6.6 — `sm plugins enable / disable` + the `config_plugins`
1929
- override layer they read from. The two stub verbs become real, and
1930
- the `PluginLoader` finally honours user intent: a disabled plugin
1931
- surfaces in `sm plugins list` with status `disabled`, but its
1932
- extensions are NOT imported and the kernel will not run them.
1933
-
1934
- **Decision (recorded in spec)**: enable/disable resolution favours the
1935
- DB row over `settings.json` over the installed default. The DB
1936
- override is local-machine; `settings.json` is the team-shared baseline.
1937
- A developer can locally disable a misbehaving plugin without
1938
- committing the toggle to the team's config; conversely, a baseline
1939
- that explicitly enables a plugin is overridable per-machine. The rule
1940
- is documented in `spec/db-schema.md` §`config_plugins`.
1941
-
1942
- **Spec change (additive, patch)**:
1943
-
1944
- - `spec/db-schema.md` — appended an "Effective enable/disable
1945
- resolution" subsection under `config_plugins` documenting the
1946
- three-layer precedence (DB > `settings.json` > installed default).
1947
- No schema changes; the `config_plugins` table itself was already
1948
- defined in the initial migration.
1949
-
1950
- **Runtime change**:
1951
-
1952
- - `src/kernel/types/plugin.ts` — `TPluginLoadStatus` gains a `disabled`
1953
- variant. JSDoc explains all five states.
1954
- - `src/kernel/adapters/sqlite/plugins.ts` — new file. Storage helpers
1955
- over the `config_plugins` table: `setPluginEnabled` (upsert),
1956
- `getPluginEnabled` (single read), `loadPluginOverrideMap` (bulk
1957
- read for one round-trip per process), `deletePluginOverride`
1958
- (idempotent drop, used by future `sm config reset plugins.<id>`).
1959
- - `src/kernel/config/plugin-resolver.ts` — new file.
1960
- `resolvePluginEnabled` implements the precedence above;
1961
- `makeEnabledResolver` curries the layered config and DB map into
1962
- the `(id) => boolean` shape `IPluginLoaderOptions.resolveEnabled`
1963
- expects.
1964
- - `src/kernel/adapters/plugin-loader.ts` — new optional
1965
- `resolveEnabled` callback in `IPluginLoaderOptions`. When supplied,
1966
- the loader checks AFTER manifest + specCompat validation and
1967
- short-circuits with `status: 'disabled'` (manifest preserved,
1968
- extensions array omitted, reason `"disabled by config_plugins or
1969
- settings.json"`). Omitting the callback keeps the legacy "always
1970
- load" behaviour for tests / kernel-empty-boot.
1971
- - `src/cli/commands/plugins.ts` — wires the loader to the resolver:
1972
- every read (`list / show / doctor`) loads `config_plugins` once and
1973
- feeds the resolver. Two new commands `PluginsEnableCommand` and
1974
- `PluginsDisableCommand` write to the DB. `--all` toggles every
1975
- discovered plugin; `<id>` and `--all` are mutually exclusive.
1976
- `sm plugins doctor` now treats `disabled` as intentional (does not
1977
- contribute to the issue list, does not flip exit code).
1978
- - `src/cli/commands/plugins.ts` — adds `off` to the status icon legend
1979
- in human output (`off mock-a@0.1.0 · disabled by config_plugins or
1980
- settings.json`).
1981
- - `src/cli/commands/stubs.ts` — `PluginsEnableCommand` and
1982
- `PluginsDisableCommand` removed; replaced-at-step comment kept.
1983
- - `context/cli-reference.md` — regenerated; the two new verbs appear
1984
- with their flag tables.
1985
-
1986
- **Tests**:
1987
-
1988
- - `src/test/plugin-overrides.test.ts` — 8 unit tests covering storage
1989
- round-trip (upsert + read), `loadPluginOverrideMap` bulk read,
1990
- `deletePluginOverride` idempotency, resolver precedence (default ⇒
1991
- true, `settings.json` overrides default, DB overrides
1992
- `settings.json`), `makeEnabledResolver` currying, and PluginLoader
1993
- surfacing `disabled` status with manifest preserved + no extensions
1994
- - omitting the resolver still loads.
1995
- - `src/test/plugins-cli.test.ts` — 9 end-to-end tests via the binary:
1996
- `disable <id>` writes a DB row + `sm plugins list` reflects `off`,
1997
- `enable <id>` flips back, `--all` covers every discovered plugin,
1998
- unknown id → exit 5, no-arg → exit 2, both `<id>` and `--all` →
1999
- exit 2, `settings.json` baseline overridden by DB `enable`,
2000
- `settings.json` baseline applies when DB has no row, and
2001
- `sm plugins doctor` exits 0 when the only non-loaded plugin is
2002
- intentionally disabled.
102
+ ### Patch
2003
103
 
2004
- Test count: 273 291 (+18).
104
+ - **Config folder rename** `.skill-map.json` (single project-root file) → `.skill-map/settings.json` inside the canonical `.skill-map/` scope folder, with a sibling `.skill-map/settings.local.json` for per-machine overrides.
2005
105
 
2006
106
  ## 0.6.0
2007
107
 
2008
- ### Minor Changes
2009
-
2010
- - 9a89124: Step 5.1 — Persist scan-result metadata in a new `scan_meta` table so
2011
- `loadScanResult` returns real values for `scope` / `roots` / `scannedAt` /
2012
- `scannedBy` / `adapters` / `stats.filesWalked` / `stats.filesSkipped` /
2013
- `stats.durationMs` instead of the synthetic envelope shipped at Step 4.7.
2014
-
2015
- **Spec change (additive, minor)**:
2016
-
2017
- - New `scan_meta` table in zone `scan_*`, single-row (CHECK `id = 1`).
2018
- Columns: `scope`, `roots_json`, `scanned_at`, `scanned_by_name`,
2019
- `scanned_by_version`, `scanned_by_spec_version`, `adapters_json`,
2020
- `stats_files_walked`, `stats_files_skipped`, `stats_duration_ms`.
2021
- `nodesCount` / `linksCount` / `issuesCount` are not stored — they are
2022
- derived from `COUNT(*)` of the sibling tables.
2023
- - Replaced atomically with the rest of `scan_*` on every `sm scan`.
2024
-
2025
- **Runtime change**:
2026
-
2027
- - New kernel migration `002_scan_meta.sql`.
2028
- - `IScanMetaTable` added to `src/kernel/adapters/sqlite/schema.ts` and
2029
- bound in `IDatabase`.
2030
- - `persistScanResult` writes the row (and deletes prior rows in the same
2031
- transaction).
2032
- - `loadScanResult` reads from `scan_meta` when the row exists; degrades
2033
- to the previous synthetic envelope when it does not (DB freshly
2034
- migrated, never scanned, or pre-5.1 snapshot).
2035
- - The Step 4.7 follow-up notes in `scan-load.ts` documenting the
2036
- synthetic envelope are simplified to describe both branches.
2037
-
2038
- Test count: 151 → 154 (+3 covering meta round-trip, replace-all
2039
- single-row invariant, and synthetic-fallback on empty DB).
2040
-
2041
- - 9a89124: Step 5.7 — Conformance coverage for the rename heuristic.
2042
-
2043
- **Spec change (additive, minor)**:
2044
-
2045
- - `spec/schemas/conformance-case.schema.json` gains
2046
- `setup.priorScans: Array<{ fixture, flags? }>` — an ordered list of
2047
- staging scans the runner executes BEFORE the main `invoke`. Each
2048
- step replaces every non-`.skill-map/` directory in the scope with
2049
- the named fixture and runs `sm scan` (with optional flags). The DB
2050
- persists across steps because `.skill-map/` is preserved between
2051
- swaps. After the last step, the runner copies the top-level
2052
- `fixture` and runs the case's `invoke`.
2053
-
2054
- Required to express scenarios that need a prior snapshot (rename
2055
- heuristic, future incremental cases). The schema is purely
2056
- additive — every existing case keeps passing without modification.
2057
-
2058
- - Two new conformance cases under `spec/conformance/cases/`:
2059
-
2060
- - **`rename-high`** — moving a single file with identical body
2061
- triggers a high-confidence auto-rename. Asserts:
2062
- `stats.nodesCount === 1`, `stats.issuesCount === 0`,
2063
- `nodes[0].path === skills/bar.md`. Verifies the spec invariant
2064
- that high-confidence renames emit NO issue.
2065
- - **`orphan-detection`** — deleting a file with no replacement
2066
- emits exactly one `orphan` issue (severity `info`). Asserts the
2067
- `ruleId` and `severity` directly.
2068
-
2069
- - Four new fixture directories under `spec/conformance/fixtures/`:
2070
- `rename-high-before/`, `rename-high-after/`,
2071
- `orphan-before/`, `orphan-after/`.
2072
-
2073
- - `spec/conformance/coverage.md`: row I (Rename heuristic) flips
2074
- from `🔴 missing` to `🟢 covered`. Notes the medium / ambiguous
2075
- branches stay covered by `src/test/rename-heuristic.test.ts` for
2076
- now (assertion vocabulary in the schema is not rich enough to
2077
- express "the issues array contains an item with ruleId X and
2078
- data.confidence === 'medium'" — when the conformance schema gains
2079
- array-filter assertions, those branches can land here too).
2080
-
2081
- **Runtime change**:
2082
-
2083
- - `src/conformance/index.ts` runner: implements `setup.priorScans`.
2084
- Helper `replaceFixture(scope, specRoot, fixture)` clears every
2085
- top-level entry in the scope except `.skill-map/`, then copies the
2086
- named fixture on top. Used by both staging steps and the main
2087
- `fixture` phase.
2088
- - `src/test/conformance.test.ts`: includes the two new cases in the
2089
- Step-0b subset. Total conformance cases passing in CI: 1 → 3.
2090
-
2091
- **`spec/index.json`** regenerated (50 → 57 files). `npm run spec:check`
2092
- green.
2093
-
2094
- Test count: 201 → 203 (+2 conformance cases). The Step 5 totals close
2095
- at: 151 → 203 (+52 across 7 sub-steps).
2096
-
2097
- ### Patch Changes
2098
-
2099
- - dacd4d9: Move the auto-generated CLI reference from `docs/cli-reference.md` to
2100
- `context/cli-reference.md`. Spec change is editorial: `cli-contract.md`
2101
- references the file path in three spots (`--format md` description, the
2102
- NORMATIVE introspection section, and the "Related" link list); all three
2103
- updated to the new location. No schema or behavioural change.
2104
-
2105
- Reference impl: `scripts/build-cli-reference.mjs` writes to the new path,
2106
- the `cli:reference` / `cli:check` npm scripts point there, and `sm help`
2107
- output (which embeds the path in the `--format md` flag description) is
2108
- regenerated. The `docs/` folder is gone.
2109
-
2110
- ## 0.5.1
2111
-
2112
- ### Patch Changes
108
+ ### Minor
2113
109
 
2114
- - 18d758a: Editorial pass across spec/ and src/ docs: convert relative-path text references (e.g. `plugin-kv-api.md`, `schemas/node.schema.json`) to proper markdown links, so they resolve on GitHub and in renderers. No normative or behavioural changes prose, schemas, and CLI contract are unchanged.
110
+ - **Persisted scan-result metadata.** New `scan_meta` table backs `loadScanResult` so `scope` / `roots` / `scannedAt` / `scannedBy` / `adapters` / `stats.{filesWalked,filesSkipped,durationMs}` are real values instead of synthesised on read.
2115
111
 
2116
112
  ## 0.5.0
2117
113
 
2118
- ### Minor Changes
2119
-
2120
- - 69572fd: Align `spec/index.json` with the manifest changes declared in the `0.3.0` changelog (they had been documented but never written to the file), and fix two small referential drifts surfaced in the same audit pass.
2121
-
2122
- **`spec/index.json`** — closes the gap between what `0.3.0` notes promised and what actually shipped:
2123
-
2124
- - `specVersion` top-level field renamed to `indexPayloadVersion`. The old name collided semantically with `specPackageVersion` and with every other use of `specVersion` in the spec (compat logic, `scan-result.specVersion`, `sm help --format json`). `indexPayloadVersion` describes the shape of `index.json` itself and bumps only when this manifest's structure changes — pinned at `0.0.1` today. **This is the breaking rename already announced in the `0.3.0` release notes.**
2125
- - `schemas.topLevel` gains `history-stats` (shape for `sm history stats --json`, already referenced from `cli-contract.md` §History and hashed under `integrity.files`).
2126
- - New `schemas.extensions` subsection listing the 7 kind-manifest schemas (`base`, `adapter`, `detector`, `rule`, `action`, `audit`, `renderer`) — already required by `architecture.md` §Extension kinds for load-time manifest validation and already present under `schemas/extensions/`.
2127
-
2128
- **`spec/versioning.md` §Change process step 4** — the parenthetical `(see CLAUDE.md: "Every feature: update spec/ first, then src/")` was stale. `CLAUDE.md` has been a bare `@AGENTS.md` pointer since the 18d0c20 dedup; the rule itself lives in `AGENTS.md`. Reference fixed.
2129
-
2130
- **`spec/CHANGELOG.md` 0.3.0 entry** — text-only renumber of "decision #40a" → "decision #40". The sub-letter was a leftover from an unreleased draft; the roadmap Decision log uses `40` as the canonical anchor (see companion ROADMAP edit).
2131
-
2132
- Classification: minor per §Pre-1.0 (`0.Y.Z`). The `specVersion → indexPayloadVersion` rename is breaking for any consumer that read the old field, but the old name never shipped alongside a file that spelled it `indexPayloadVersion` — the rename is being applied here for the first time, not re-applied. The `topLevel`/`extensions` additions are purely additive.
2133
-
2134
- ### Patch Changes
2135
-
2136
- - 2699276: Fix the extension-kind schemas so they actually validate against real extension manifests.
2137
-
2138
- The six kind schemas (`schemas/extensions/action.schema.json`, `adapter.schema.json`, `audit.schema.json`, `detector.schema.json`, `renderer.schema.json`, `rule.schema.json`) used `additionalProperties: false` together with `allOf: [{ $ref: "base.schema.json" }]` — a classic JSON Schema Draft 2020-12 footgun. `additionalProperties` is evaluated independently per schema in an `allOf`, so when a consumer validated `{ id, kind, version, emitsLinkKinds, defaultConfidence }` against `detector.schema.json`, detector's `additionalProperties: false` rejected `id` / `version` / `description` (defined only on `base`) and base's own `additionalProperties: false` would have rejected `emitsLinkKinds` / `defaultConfidence` — the union of both closures is empty. No real extension could ever pass validation.
2139
-
2140
- Discovered during Step 1b while wiring the AJV validators in `skill-map` (kernel plugin loader). The right fix is `unevaluatedProperties: false` — it sees through `allOf` composition and only rejects keys that no sibling schema declared.
2141
-
2142
- Changes:
2143
-
2144
- - Every kind schema: `additionalProperties: false` → `unevaluatedProperties: false` at the manifest level. Nested `additionalProperties: false` declarations inside `$defs` / `properties` were likewise replaced with `unevaluatedProperties: false` where they participate in `allOf` composition (e.g. `action.schema.json#/$defs/Parameter`, `audit.schema.json` nested items).
2145
- - `extensions/base.schema.json`: closure removed entirely. Closed-content is now enforced only on the kind schemas, which see base's properties as "evaluated" through the `allOf` — adding closure to base too would force every kind to re-list every base key to stay valid.
2146
- - `base.schema.json` description updated to spell out the new composition rule so a future reader does not accidentally re-introduce the footgun.
114
+ ### Minor
2147
115
 
2148
- Classification: patch. No normative shape changes every manifest that was _supposed_ to pass under the old schemas still passes under the new ones, and the authored intent (closed content on kind manifests, additive base fields) is preserved. Consumers that never wired strict JSON Schema validation see zero behavioural change.
116
+ - **`spec/index.json` integrity sweep.** Reconciles `index.json` with the manifest changes documented in v0.3.0 but never written to the file. No prose / schema changes.
2149
117
 
2150
118
  ## 0.4.0
2151
119
 
2152
- ### Minor Changes
2153
-
2154
- - 334c51a: Document `--all` as targeted fan-out, not a global flag, in `spec/cli-contract.md`.
2155
-
2156
- `--all` is valid only on verbs whose contract explicitly lists it:
2157
-
2158
- - `sm plugins enable <id> | --all` and `sm plugins disable <id> | --all`.
2159
- - `sm job cancel <job.id> | --all` (cancels every `queued` and `running` job).
2160
- - `sm job submit <action> --all` and `sm job run --all`.
2161
-
2162
- Unsupported `--all` usage is an operational error (exit `2`), the same as any other unknown or invalid flag.
2163
-
2164
- Classification: minor — targeted fan-out semantics are additive for the listed verbs, while avoiding a global flag contract.
2165
-
2166
- - 3e89d8f: Audit-driven alignment pass. Multiple normative additions and a casing cleanup:
2167
-
2168
- - **Extension schemas**: add `spec/schemas/extensions/{base,adapter,detector,rule,action,audit,renderer}.schema.json` (7 new files). `architecture.md` §Extension kinds now points to them and mandates manifest validation at load time. Unblocks the "contract tests for the 6 kinds" invariant.
2169
- - **Adapter `defaultRefreshAction`**: normatively required on every `Adapter` extension. Maps node `kind` → `actionId` and drives the UI's `🧠 prob` button. Previously mentioned only in ROADMAP (Decision #45); now part of the schema.
2170
- - **Triple protection for mode B**: `db-schema.md` now specifies the exact order — parse → DDL validation → prefix injection → scoped connection. Validation runs **before** the rewrite so kernel-table references are caught under their authored names.
2171
- - **Automatic rename heuristic**: new `db-schema.md` §Rename detection. On scan, `body_hash` match → high-confidence auto-rename with `state_*` FK migration; `frontmatter_hash` match → medium-confidence, same migration + `auto-rename-medium` issue; no match → orphan with issue. Replaces the prior "scan emits orphans, user runs `sm orphans reconcile` manually" flow.
2172
- - **Skill agent envelope**: `job-events.md` now mandates a synthetic `r-ext-<ts>-<hex>` run envelope (`run.started mode=external` → `job.claimed` → `job.callback.received` → `job.completed|failed` → `run.summary`) around jobs claimed by a Skill agent without entering `sm job run`. Keeps the WebSocket broadcaster contract ("every job event inside a run envelope") intact across both runner paths.
2173
- - **"Skill runner" → "Skill agent"**: `architecture.md` and `job-lifecycle.md` clarify that the Skill path is a peer driving adapter (alongside CLI and Server), NOT a `RunnerPort` implementation. Only `ClaudeCliRunner` and its test fake implement the port. Name was misleading; structure unchanged.
2174
- - **Casing**: `db-schema.md` `auto_migrate` → `autoMigrate`; `README.md` prose mention `spec-compat` → `specCompat`. Brings prose into sync with the camelCase rule already enforced by the schemas.
2175
- - **Coverage matrix**: new `spec/conformance/coverage.md` tracks each schema (and each non-schema normative artifact) against its conformance case. 28 schemas + 11 artifact invariants catalogued; 19 schemas and 10 artifacts flagged as missing, each with a step-blocker note. Release gate: v1.0.0 requires every row 🟢 or explicitly deferred.
2176
-
2177
- Classification: minor per §Pre-1.0 (`0.Y.Z`). The new required field `defaultRefreshAction` on the Adapter kind is technically breaking — no conforming Adapter ships in the reference impl yet, so the impact is zero. Post-1.0 the same change would be major.
2178
-
2179
- ### Patch Changes
2180
-
2181
- - 93ffe34: Editorial pass: remove "MVP" terminology from four prose documents.
2182
-
2183
- The project shipped two competing readings of "MVP" — sometimes "`v0.5.0`", sometimes "the whole product through `v1.0`". That drift produced contradictions in companion docs (e.g. the summarizer pattern: was `v0.8.0` or `v0.5.0` supposed to ship them?). To close the ambiguity once, `ROADMAP.md` and `AGENTS.md` standardised on explicit versioned releases and `post-v1.0` in the same audit window. This change brings the four spec prose touches that still said "MVP" into the same vocabulary.
2184
-
2185
- - **`cli-contract.md` §Jobs**: `sm job run --all` description `(MVP: sequential)` → `(sequential through v1.0; in-runner parallelism deferred)`.
2186
- - **`job-events.md` §Event catalog**: `(post-MVP)` parallel-run note → `(deferred to post-v1.0)`.
2187
- - **`job-lifecycle.md` §Concurrency**: `MVP (v0.x): one job at a time.` → `Through v1.0 (spec v0.x): one job at a time.`
2188
- - **`plugin-kv-api.md` §Backup and retention**: `sm plugins forget <id> (post-MVP)` → `sm plugins forget <id> (deferred to post-v1.0)`.
120
+ ### Minor
2189
121
 
2190
- Classification: patch. Editorial only no schema, exit code, verb signature, or MUST/SHOULD statement changes meaning. All four replacements preserve the technical content; only the label changes from project-scoped ("MVP") to version-scoped (`v1.0`), which is the convention the rest of the spec already uses. Integrity block regenerated.
122
+ - **`--all` documented as targeted fan-out** in `cli-contract.md`. Valid only on verbs whose contract explicitly lists it.
2191
123
 
2192
124
  ## 0.3.0
2193
125
 
2194
- ### Minor Changes
2195
-
2196
- - 334c51a: Promote `--all` to a normative universal flag in `spec/cli-contract.md §Global flags`.
2197
-
2198
- Any verb that accepts a target identifier (`-n <node.path>`, `<job.id>`, `<plugin.id>`) MUST accept `--all` as "apply to every eligible target matching the verb's preconditions". Mutually exclusive with a positional target or `-n <path>` on the same invocation. Verbs that inherently target everything (`sm scan` without `-n`, `sm list`, `sm check`, `sm doctor`) accept the flag as a no-op for script-composition uniformity. Verbs where fan-out is nonsensical (`sm record`, `sm init`, `sm version`, `sm help`, `sm config get/set/reset/show`, `sm db *`, `sm serve`) MUST reject `--all` with exit `2`.
2199
-
2200
- Concretely extended in this pass:
2201
-
2202
- - `sm plugins enable <id> | --all` and `sm plugins disable <id> | --all`.
2203
- - `sm job cancel <job.id> | --all` (cancels every `queued` and `running` job).
2204
-
2205
- Already normative before this change: `sm job submit <action> --all` and `sm job run --all`.
2206
-
2207
- Classification: minor — new global flag semantics, backward compatible (existing invocations without `--all` behave identically). ROADMAP Decision #60 stays as the canonical narrative; this changeset brings the spec into line with it.
2208
-
2209
- - 3e89d8f: Audit-driven alignment pass. Multiple normative additions and a casing cleanup:
2210
-
2211
- - **Extension schemas**: add `spec/schemas/extensions/{base,adapter,detector,rule,action,audit,renderer}.schema.json` (7 new files). `architecture.md` §Extension kinds now points to them and mandates manifest validation at load time. Unblocks the "contract tests for the 6 kinds" invariant.
2212
- - **Adapter `defaultRefreshAction`**: normatively required on every `Adapter` extension. Maps node `kind` → `actionId` and drives the UI's `🧠 prob` button. Previously mentioned only in ROADMAP (Decision #45); now part of the schema.
2213
- - **Triple protection for mode B**: `db-schema.md` now specifies the exact order — parse → DDL validation → prefix injection → scoped connection. Validation runs **before** the rewrite so kernel-table references are caught under their authored names.
2214
- - **Automatic rename heuristic**: new `db-schema.md` §Rename detection. On scan, `body_hash` match → high-confidence auto-rename with `state_*` FK migration; `frontmatter_hash` match → medium-confidence, same migration + `auto-rename-medium` issue; no match → orphan with issue. Replaces the prior "scan emits orphans, user runs `sm orphans reconcile` manually" flow.
2215
- - **Skill agent envelope**: `job-events.md` now mandates a synthetic `r-ext-<ts>-<hex>` run envelope (`run.started mode=external` → `job.claimed` → `job.callback.received` → `job.completed|failed` → `run.summary`) around jobs claimed by a Skill agent without entering `sm job run`. Keeps the WebSocket broadcaster contract ("every job event inside a run envelope") intact across both runner paths.
2216
- - **"Skill runner" → "Skill agent"**: `architecture.md` and `job-lifecycle.md` clarify that the Skill path is a peer driving adapter (alongside CLI and Server), NOT a `RunnerPort` implementation. Only `ClaudeCliRunner` and its test fake implement the port. Name was misleading; structure unchanged.
2217
- - **Casing**: `db-schema.md` `auto_migrate` → `autoMigrate`; `README.md` prose mention `spec-compat` → `specCompat`. Brings prose into sync with the camelCase rule already enforced by the schemas.
2218
- - **Coverage matrix**: new `spec/conformance/coverage.md` tracks each schema (and each non-schema normative artifact) against its conformance case. 28 schemas + 11 artifact invariants catalogued; 19 schemas and 10 artifacts flagged as missing, each with a step-blocker note. Release gate: v1.0.0 cut requires every row 🟢 or explicitly deferred.
2219
-
2220
- Classification: minor per §Pre-1.0 (`0.Y.Z`). The new required field `defaultRefreshAction` on the Adapter kind is technically breaking — no conforming Adapter ships in the reference impl yet, so the impact is zero. Post-1.0 the same change would be major.
2221
-
2222
- - d41b9ae: Close two gaps surfaced in the audit pass: config keys that `ROADMAP.md` promised but `project-config.schema.json` did not declare, and WebSocket event families that `ROADMAP.md §UI` mentioned ("scan updates + issue changes") but `job-events.md` did not cover.
2223
-
2224
- **`project-config.schema.json` — new optional fields, all non-breaking:**
2225
-
2226
- - `autoMigrate: boolean` (default `true`) — auto-apply pending kernel + plugin migrations at startup after auto-backup. `false` → startup fails fast if migrations are pending.
2227
- - `tokenizer: string` (default `cl100k_base`) — name of the offline tokenizer; stored alongside counts so consumers know which encoder produced them.
2228
- - `scan.maxFileSizeBytes: integer` (default `1048576`) — files larger are skipped with an `info` log.
2229
- - `jobs.ttlSeconds: integer` (default `3600`) — global fallback TTL when an action manifest omits `expectedDurationSeconds` (typically `mode: local` actions where the field is advisory).
2230
- - `jobs.perActionPriority: { <actionId>: integer }` — per-action priority overrides. Frozen on `state_jobs.priority` at submit time; overrides action manifest `defaultPriority`; overridden by CLI `--priority`. Ratifies decision #40 in the schema.
2231
- - `jobs.retention: { completed, failed }` — GC policy for `state_jobs` rows. Defaults: `completed = 2592000` (30 days), `failed = null` (never auto-prune; keep for post-mortem). `sm job prune` reads these; no implicit pruning during normal verbs.
2232
-
2233
- **`job-events.md` — new `Non-job events` section, Stability: experimental across v0.x:**
2234
-
2235
- - `scan.*`: `scan.started`, `scan.progress` (throttled ≥250 ms), `scan.completed`.
2236
- - `issue.*`: `issue.added`, `issue.resolved` — emitted after `scan.completed` when the new scan's issue set differs from the previous one. Diff key: `(ruleId, nodeIds sorted, message)`.
2237
- - Synthetic run ids follow the existing `r-<mode>-YYYYMMDD-HHMMSS-XXXX` pattern (`r-scan-...`, `r-check-...`) alongside `r-ext-...` for external Skill claims.
2238
-
2239
- These families ship at Step 13 of the reference impl alongside the WebSocket broadcaster. Marking them experimental keeps the shape mutable until real UI consumers exercise the stream; promotion to `stable` is a later minor bump.
2240
-
2241
- Classification: minor per §Pre-1.0. All additions are optional fields in a permissive config schema and new event types outside the stable job family — zero impact on existing implementations. Matching `ROADMAP.md` §Notable config keys and §Progress events updates land in the same change.
2242
-
2243
- - d41b9ae: Align the frontmatter tools story with Claude Code's own conventions (the audit pass surfaced that the spec had `tools` on agent only and no equivalent for skills, while `ROADMAP.md` decision #55 referenced a non-existent `expected-tools` field).
2244
-
2245
- **`spec/schemas/frontmatter/base.schema.json` — two new top-level optional fields:**
2246
-
2247
- - `tools: string[]` — **allowlist**. When present, the host MUST restrict the node to exactly these tools. Matches Claude Code's subagent `tools` frontmatter. Kind-specific interpretation: an `agent` uses it to lock the spawned subagent; a `skill` uses it as a declarative hint (skills typically inherit their parent's tools, but the field is carried for parity and discovery); other kinds use it as information only.
2248
- - `allowedTools: string[]` — **pre-approval**. Tools the host MAY use without per-use permission prompts while the node is active. Distinct from `tools`: every other tool remains callable, governed by the host's normal permission settings. Matches Claude Code's skill `allowed-tools` frontmatter. Accepts argument-scoped patterns where the host supports them (e.g. `Bash(git add *)`).
2249
-
2250
- **`spec/schemas/frontmatter/agent.schema.json`:** `tools` removed from the kind-specific body because it now lives on `base` and is inherited via `allOf`. The agent schema's title/description updated to reflect that only `model` remains kind-specific. Consumers reading `tools` from an agent frontmatter see no behavioural change — the field is still there, just sourced from `base`.
2251
-
2252
- `expectedTools` on `extensions/action.schema.json` is unchanged. That field is a hint from an action template to the runner (which tools the rendered prompt expects access to) — a distinct semantics from the node-level `tools` / `allowedTools` pair, and the name difference preserves the distinction.
2253
-
2254
- Classification: minor per §Pre-1.0. Additions to `base` are optional fields in a permissive schema (no break for existing frontmatter). Removing `tools` from the agent schema's own properties is compatible because `allOf: [base]` continues to supply it — any document that validated before still validates, any document that used `additionalProperties: true` is unaffected. Matching `ROADMAP.md` updates (§Frontmatter standard, decision #55) land in the same change.
2255
-
2256
- - 5935948: Add `sm history stats` schema and normative elapsed-time reporting.
2257
-
2258
- - **New schema** `spec/schemas/history-stats.schema.json`. Shape for `sm history stats --json`: `range` (configurable via `--since` / `--until`), `totals`, `tokensPerAction[]`, `executionsPerPeriod[]` (granularity via `--period day|week|month`, default `month`), `topNodes[]` (length via `--top N`, default 10), `errorRates` (global + per-action + per failure reason — all failure-reason enum values always present with `0` when unseen for predictable dashboards), and top-level `elapsedMs`. Duration stats in `tokensPerAction[]`: `durationMsMean` + `durationMsMedian` for MVP; percentiles deferred to a later minor bump.
2259
- - **cli-contract.md §Elapsed time** (new normative section). Every verb that does non-trivial work MUST report its own wall-clock:
2260
- - **Pretty (stderr)**: last line `done in <formatted>` where `<formatted>` ∈ `{ <N>ms | <N.N>s | <M>m <S>s }`. Suppressed by `--quiet`.
2261
- - **JSON stdout**: top-level `elapsedMs` when the shape is an object; schemas whose shape is an array or ndjson don't carry it (stderr is the sole carrier).
2262
- - **Exempt** verbs (sub-millisecond, informational): `sm --version`, `sm --help`, `sm version`, `sm help`, `sm config get`, `sm config list`, `sm config show`.
2263
- - Measurement spans from after arg-parsing to before terminal write.
2264
- - **cli-contract.md** `sm history stats` entry: flags enumerated (`--since`, `--until`, `--period`, `--top`) and schema referenced.
2265
- - **Coverage matrix**: row `29` for `history-stats.schema.json` (blocked by Step 5); artifact row `L` for the elapsed-time reporting invariant (blocked by Step 4).
2266
-
2267
- Classification: minor per §Pre-1.0. The elapsed-time contract introduces a SHOULD-emit line that didn't exist before — no existing consumer breaks, and the line goes to stderr where it doesn't clash with stdout JSON.
2268
-
2269
- - 1455cb1: Normative `priority` for jobs.
2270
-
2271
- The `state_jobs.priority` column (INTEGER, default `0`) existed in the schema and was used by the atomic-claim SQL (`ORDER BY priority DESC, createdAt ASC`), but no surface let the user set it. This release closes the gap:
2272
-
2273
- - **`cli-contract.md` §Jobs**: new flag `sm job submit ... --priority <n>`. Integer; higher runs first; default `0`; negatives permitted (deprioritize).
2274
- - **`job-lifecycle.md` §Submit**: new step 6 resolving priority with precedence `action manifest defaultPriority → user config jobs.perActionPriority.<actionId> → flag`. The resolved value is frozen on submit and immutable for the life of the job. Ties in the claim order break by `createdAt ASC`.
2275
- - Configuration key `jobs.perActionPriority.<actionId>`: optional per-action integer override.
2276
- - Action manifest `defaultPriority`: optional integer; defaults to `0` when omitted.
2277
-
2278
- Classification: minor per `cli-contract.md` §Stability ("adding a flag is a minor bump"). No existing consumer breaks: jobs submitted before this release default to `0`, which is the identity element of the ordering. The claim SQL already read `priority`, so the wire protocol is unchanged.
2279
-
2280
- - 1455cb1: Manifest alignment pass on `spec/index.json`: expose already-normative schemas, rename the payload-shape field, and add a stable version field consumers can rely on.
2281
-
2282
- - **Rename `specVersion` → `indexPayloadVersion`** (breaking). The old name collided semantically with every other use of `specVersion` (compat logic in `versioning.md`, `scan-result.specVersion`, `sm help --format json`). The field describes the shape of `index.json` itself, not the spec a caller implements.
2283
- - **New `specPackageVersion`** top-level field, auto-populated by `scripts/build-spec-index.mjs` from `spec/package.json.version`. This is the source of truth for "which `@skill-map/spec` release is this", previously missing from the manifest — consumers had to read `package.json` separately, and `sm version` was incorrectly reporting the payload-shape version as the spec version.
2284
- - **`schemas.topLevel`** gains `history-stats` (shape for `sm history stats --json`, already referenced in `cli-contract.md` §History).
2285
- - **New `schemas.extensions` subsection** lists the 7 kind-manifest schemas (`base`, `adapter`, `detector`, `rule`, `action`, `audit`, `renderer`) already required by `architecture.md` §Extension kinds for load-time manifest validation.
2286
- - **CHANGELOG fix** on the `[Unreleased]` v0.1.0 line: "10 event types" → "11 canonical event types plus one synthetic `emitter.error`". Text-only correction on a shipped release.
2287
- - **README example** updated to show both fields side-by-side so the distinction is obvious to first-time consumers.
2288
- - **Integrity block** regenerated.
2289
-
2290
- No schema contents change. The schema files and their normative status are unchanged since 0.1.0; the index now enumerates them all and uses unambiguous field names.
2291
-
2292
- **Migration for consumers**: any caller that reads `specIndex.specVersion` MUST switch to `specIndex.specPackageVersion` (for the release) or `specIndex.indexPayloadVersion` (for the manifest shape). The rename is the source of the `minor` bump rather than `patch` — pre-1.0 minors MAY contain breaking changes per `versioning.md` §Pre-1.0.
2293
-
2294
- Classification: minor per §Pre-1.0. One breaking rename + two additive fields + two additive schema subsections. The reference impl's `sm version` is updated in the same release to read `specPackageVersion`, so `sm version` now reports the actual npm package version (was the payload-shape version, a latent bug).
2295
-
2296
- - 1455cb1: New CLI verb `sm orphans undo-rename <new.path> [--force]` to reverse a medium-confidence auto-rename.
2297
-
2298
- The scan's rename heuristic (added in the previous spec release) migrates `state_*` FKs automatically when a deleted path and a newly-seen path share the same `frontmatter_hash` ("medium" confidence, body differs) and emits an `auto-rename-medium` issue for the user to verify. Until now the spec said "revert via `sm orphans reconcile --to <old.path>`", but `sm orphans reconcile` is defined for the forward direction (orphan path → live node) and awkward for the reverse case where both paths exist.
2299
-
2300
- This release closes the gap with a dedicated reverse verb:
2301
-
2302
- - **`cli-contract.md` §Browse**: new row `sm orphans undo-rename <new.path> [--force]`. Requires an active `auto-rename-medium` or `auto-rename-ambiguous` issue targeting `<new.path>`. Reads the prior path from `issue.data_json.from`, migrates `state_*` FKs back, resolves the issue. Exit `5` if no matching active issue.
2303
- - **`db-schema.md` §Rename detection**: issue payload now normative.
2304
- - `auto-rename-medium.data_json` MUST include `{ from, to, confidence: "medium" }`.
2305
- - `auto-rename-ambiguous.data_json` MUST include `{ to, candidates: [from_a, from_b, ...] }`. `sm orphans undo-rename` requires `--from <old.path>` to pick one.
2306
- - **Destructive verb**: prompts for confirmation unless `--force`. After undo, the prior path becomes an `orphan` (file no longer exists), emitting the normal `orphan` issue on next scan.
2307
-
2308
- Rationale: dedicated name makes intent clear (forward = reconcile, reverse = undo-rename), failure is early (no active issue → immediate exit 5 with a helpful message), and the user does not re-type paths the kernel already knows.
2309
-
2310
- Classification: minor per `cli-contract.md` §Stability ("adding a verb is a minor bump"). No existing behavior changes; `sm orphans reconcile` semantics are unaffected.
2311
-
2312
- - 334c51a: **Breaking**: rename two state-zone tables to comply with the normative plural rule in `db-schema.md §Naming conventions`.
2313
-
2314
- - `state_enrichment` → `state_enrichments`
2315
- - `state_plugin_kv` → `state_plugin_kvs`
2316
-
2317
- Index names renamed in lockstep:
2318
-
2319
- - `ix_state_enrichment_stale_after` → `ix_state_enrichments_stale_after`
2320
- - `ix_state_plugin_kv_plugin_id` → `ix_state_plugin_kvs_plugin_id`
2321
-
2322
- The two tables were the only kernel-owned state-zone tables violating the rule "Tables: `snake_case`, plural" — every other catalog entry (`state_jobs`, `state_executions`, `state_summaries`, `config_plugins`, `config_preferences`, `config_schema_versions`, `scan_nodes`, `scan_links`, `scan_issues`) was already plural. The exceptions were historical drift, not intentional.
2323
-
2324
- Updated spec artefacts:
2325
-
2326
- - `spec/db-schema.md` — table section headings, column comments, primary-key footers, index names, and the cross-reference list in §Rename heuristic.
2327
- - `spec/cli-contract.md` — `sm db reset --state` row in §Database.
2328
- - `spec/plugin-kv-api.md` — §Overview opener and every downstream reference.
2329
- - `spec/schemas/plugins-registry.schema.json` — description of the `kv` mode `const`.
2330
-
2331
- **Migration for implementations**: no reference implementation has shipped the SQLite adapter yet (Step 1a lands it), so this is a rename-on-paper change. Any future kernel migration that creates these tables MUST use the plural names. Any third-party implementation already experimenting with the spec against the old names MUST rename before targeting `@skill-map/spec ≥ 0.3.0`.
2332
-
2333
- Classification: **minor with breaking change**, per `spec/versioning.md §Pre-1.0` which allows breaking changes on minor bumps while the spec is `0.y.z`. Reference-impl touch: `src/kernel/ports/plugin-loader.ts` comment updated; no code paths read these names at runtime yet.
2334
-
2335
- Companion prose updates in `ROADMAP.md` (§Persistence, §Plugin system, §Enrichment, §Summarizer pattern, Decision #61) and `AGENTS.md` (§Persistence).
2336
-
2337
- - 93ffe34: Clean up `history.*` in `spec/schemas/project-config.schema.json`.
2338
-
2339
- **Breaking (pre-1.0 minor per `versioning.md` §Pre-1.0):**
2340
-
2341
- - **Remove** `history.retentionDays`. The field promised execution-record GC, but `ROADMAP.md` §Step 7 and the job-retention section make it explicit that `state_executions` is append-only in `v0.1` and that the kernel does not use this key. Declaring a config key whose behaviour is "silently ignored" is worse than not declaring it — consumers would wire it in and never see an effect. The field will be re-introduced in a later minor bump when the GC path actually lands, with a concrete default and enforcement semantics.
2342
-
2343
- **Editorial:**
2344
-
2345
- - `history.share.description` mentioned `./.skill-map/history.json` — an artefact of the pre-SQLite architecture. The actual DB is `./.skill-map/skill-map.db` (see `db-schema.md` §Scope and location). Description corrected; field itself unchanged.
2346
-
2347
- Classification: minor per §Pre-1.0 (`0.Y.Z` may contain breaking changes in a minor bump). Integrity block regenerated via `npm run spec:index`. Companion prose in `ROADMAP.md §Notable config keys` updated in the same change.
2348
-
2349
- **Migration for consumers**: any `.skill-map.json` that set `history.retentionDays` will now fail schema validation (`additionalProperties: false` on `history`). Remove the key; no kernel behaviour changes because nothing was consuming it.
2350
-
2351
- - 93ffe34: Promote the trigger-normalization pipeline (Decision #21) from implicit to normative in `spec/architecture.md`.
2352
-
2353
- Before this change, `link.trigger` carried `originalTrigger` and `normalizedTrigger` fields (defined in `schemas/link.schema.json`), and the `trigger-collision` rule keyed on the normalized value — but no spec prose documented **how** to normalize. The pipeline lived only in `AGENTS.md §Decisions already locked` and in `ROADMAP.md` as a one-line Step 7 bullet. That left implementations free to diverge, which silently breaks the `trigger-collision` rule across implementations (two conforming CLIs could disagree on whether `hacer-review` and `Hacer Review` collide).
2354
-
2355
- Added under `architecture.md §Extension kinds`, paralleling the existing `Adapter · defaultRefreshAction` subsection:
2356
-
2357
- - **Detector · trigger normalization** — field contract, normative 6-step pipeline, and 8 worked examples.
2358
-
2359
- Pipeline (applied in exactly this order):
2360
-
2361
- 1. Unicode NFD.
2362
- 2. Strip Unicode `Mn` (diacritics).
2363
- 3. Lowercase (locale-independent).
2364
- 4. Separator unification: hyphen / underscore / any whitespace run → single ASCII space.
2365
- 5. Collapse whitespace (run of ≥2 spaces → 1 space).
2366
- 6. Trim leading/trailing whitespace.
2367
-
2368
- Non-letter / non-digit characters outside the separator set (`/`, `@`, `:`, `.`, etc.) are **preserved** — stripping them is the detector's concern, not the normalizer's. This keeps namespaced invocations (`/skill-map:explore`, `@my-plugin/foo`) comparable in their intended form.
2369
-
2370
- §Stability in `architecture.md` updated: 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.
2371
-
2372
- Classification: minor. The pipeline was always the intent (Decision #21 existed since the 2026-04-19 session) and `schemas/link.schema.json` already carried the fields, but this is the first time the spec prose binds implementations to a specific algorithm. A strict v0 implementation that did not normalize (or normalized differently) would begin failing conformance at the next spec release; worth a minor bump so plugin authors and alternative impls see it in the changelog.
2373
-
2374
- Companion prose in `ROADMAP.md §Trigger normalization` (Decision #21 now points here for full rationale + examples).
2375
-
2376
- ### Patch Changes
2377
-
2378
- - 334c51a: Clarify `sm orphans undo-rename` signature in `spec/cli-contract.md §Browse` by surfacing the `[--from <old.path>]` flag in the command cell itself.
2379
-
2380
- The flag was already documented prose-only in `spec/db-schema.md §Rename heuristic` ("`auto-rename-ambiguous` issues ... `sm orphans undo-rename` requires the user to pass `--from <old.path>` to disambiguate") but was absent from the signature in the `cli-contract.md` table. A reader consulting only the CLI contract would miss the flag and assume the command took `<new.path>` alone.
2381
-
2382
- The row now:
2383
-
2384
- - Shows `[--from <old.path>] [--force]` in the signature.
2385
- - Explicitly distinguishes the `auto-rename-medium` case (omit `--from`, previous path read from `issue.data_json`) from `auto-rename-ambiguous` (REQUIRES `--from` to pick from `data_json.candidates`).
2386
- - Adds an exit-`5` condition for `--from` referencing a path not in `candidates`.
2387
-
2388
- No behavioural change — the flag was already normative and implementations were already expected to support it. Classification: patch (clarifying drift between two spec prose docs, not a new capability).
2389
-
2390
- - 93ffe34: Split `sm db reset` into three explicit levels of destruction, each with distinct semantics.
2391
-
2392
- Before: `sm db reset` dropped BOTH `scan_*` and `state_*` in one command — so a user who wanted "please rescan from scratch" would wipe their job history, summaries, enrichment, and plugin KV data. The "reset" name suggested a soft operation; the behavior was aggressive.
2393
-
2394
- After:
2395
-
2396
- - `sm db reset` — drops `scan_*` only. Keeps `state_*` and `config_*`. Non-destructive, no prompt. Equivalent to asking for a fresh scan.
2397
- - `sm db reset --state` — also drops `state_*` and every `plugin_<normalized_id>_*` table (mode B) plus `state_plugin_kvs` (mode A). Keeps `config_*`. Destructive; requires confirmation unless `--yes` (or `--force`, kept as an alias).
2398
- - `sm db reset --hard` — deletes the DB file entirely. Keeps the plugins folder on disk. Destructive; requires confirmation unless `--yes`.
2399
-
2400
- Updated files:
2401
-
2402
- - `spec/cli-contract.md` §Database — new table rows and a rewritten confirmation paragraph.
2403
- - `spec/db-schema.md` §Zones — one-liner rewritten to list all three levels.
2404
- - `spec/plugin-kv-api.md` §Scope and lifecycle — three bullets replacing the single prior bullet, explicit about which reset level touches plugin storage.
2405
-
2406
- Classification: patch in intent but **behavior-changing for `sm db reset` without modifier**. Implementations of `v0.x` that currently drop `state_*` on `sm db reset` MUST narrow the behavior; users relying on the old "reset = wipe everything below config" workflow must switch to `sm db reset --state`. Classified as patch because the spec is pre-1.0 and no implementation has shipped the CLI yet (Step 1a lands storage + the `sm db *` verbs together — this is the first time the boundary is normative in code).
2407
-
2408
- Companion prose updates in `ROADMAP.md` §DB management commands and §Step 1a acceptance list.
2409
-
2410
- - 93ffe34: Editorial pass: remove "MVP" terminology from four prose documents.
2411
-
2412
- The project shipped two competing readings of "MVP" — sometimes "CUT 1 / `v0.5.0`", sometimes "the whole product through `v1.0`". That drift produced contradictions in companion docs (e.g. the summarizer pattern: was `v0.8.0` or `v0.5.0` supposed to ship them?). To close the ambiguity once, `ROADMAP.md` and `AGENTS.md` standardised on `CUT 1` / `CUT 2` / `CUT 3` and `post-v1.0` in the same audit window. This change brings the four spec prose touches that still said "MVP" into the same vocabulary.
2413
-
2414
- - **`cli-contract.md` §Jobs**: `sm job run --all` description `(MVP: sequential)` → `(sequential through v1.0; in-runner parallelism deferred)`.
2415
- - **`job-events.md` §Event catalog**: `(post-MVP)` parallel-run note → `(deferred to post-v1.0)`.
2416
- - **`job-lifecycle.md` §Concurrency**: `MVP (v0.x): one job at a time.` → `Through v1.0 (spec v0.x): one job at a time.`
2417
- - **`plugin-kv-api.md` §Backup and retention**: `sm plugins forget <id> (post-MVP)` → `sm plugins forget <id> (deferred to post-v1.0)`.
2418
-
2419
- Classification: patch. Editorial only — no schema, exit code, verb signature, or MUST/SHOULD statement changes meaning. All four replacements preserve the technical content; only the label changes from project-scoped ("MVP") to version-scoped (`v1.0`), which is the convention the rest of the spec already uses. Integrity block regenerated.
2420
-
2421
- - 93ffe34: Refresh the `spec/README.md` §Repo layout tree so it matches reality.
2422
-
2423
- The previous tree was frozen at the Step 0a snapshot and listed only 20 schemas (9 top-level + 6 frontmatter + 5 summaries) plus outdated `(Step 0a phase N)` annotations. The actual spec ships 29 schemas (11 top-level + 7 extension + 6 frontmatter + 5 summaries) and the package adds `index.json` and `package.json`.
2424
-
2425
- Changes:
2426
-
2427
- - Show the full set of 29 JSON Schemas with a brace grouping per bucket, making the counts and the `allOf` inheritance (frontmatter kinds → base; summaries → report-base) legible at a glance.
2428
- - Add the missing top-level schemas `conformance-case.schema.json` and `history-stats.schema.json`.
2429
- - Add the whole `schemas/extensions/` folder (base + one per extension kind) — validated at plugin load.
2430
- - List `package.json` and `index.json` explicitly so external readers know they are published assets.
2431
- - Drop `(Step 0a phase N)` annotations — Step 0a is complete, the marker is noise.
2432
- - Under `conformance/cases/`, note `basic-scan` and `kernel-empty-boot` as the two shipped cases and point at `../ROADMAP.md` for the deferred `preamble-bitwise-match` case.
2433
- - Under `interfaces/`, clarify that `security-scanner.md` is a convention over the Action kind, NOT a 7th extension kind — the six kinds remain locked.
2434
-
2435
- Classification: patch. Editorial prose only — no normative schema, rule, or contract changes. Companion updates to `ROADMAP.md` (repo layout + package layout) ship alongside; they are outside the spec package and do not need a changeset.
2436
-
2437
- - d41b9ae: Promote the casing rule from implicit (stated only in `CHANGELOG.md` §Conventions locked and in individual schema descriptions) to explicit, with a new **Naming conventions** section in `spec/README.md`. Two rules, both normative:
2438
-
2439
- - **Filesystem artefacts in kebab-case**: every file, directory, enum value, and `issue.ruleId` value. Values stay URL/filename/log-key safe without escaping.
2440
- - **JSON content in camelCase**: every key in schemas, frontmatter, configs, manifests, job records, reports, event payloads, API responses. The SQL layer (`snake_case`) is the sole exception, bridged by the storage adapter.
2441
-
2442
- Companion alignment in `spec/db-schema.md` §Rename detection: the prose mixed column names (`body_hash`, `frontmatter_hash`, `rule_id`, `data_json`) with domain-object references. The heuristic is specified against the domain types (`bodyHash`, `frontmatterHash`, `ruleId`, `data`) as defined in `node.schema.json` / `issue.schema.json`; the SQLite columns are the storage shape, not the contract. Added a one-line casing note that points back to §Naming conventions so the bridge is explicit.
2443
-
2444
- Classification: patch. The rule itself is unchanged — it was already enforced by every shipped schema and repeated in `CHANGELOG.md`. The additions are purely documentary so new implementers find the rule without digging through the changelog, and so the rename-detection prose stops looking like it references SQLite-specific identifiers when it means domain-object fields.
2445
-
2446
- - 93ffe34: Clarify the TTL resolution procedure in `spec/job-lifecycle.md`.
2447
-
2448
- The previous text defined the formula as `ttlSeconds = max(expectedDurationSeconds × graceMultiplier, minimumTtlSeconds)` and said the precedence chain was `global default → manifest → user config → flag`. Two problems:
2449
-
2450
- - When `expectedDurationSeconds` is absent from the manifest (typical for `mode: local` actions), the formula is undefined. The existing config key `jobs.ttlSeconds` was documented elsewhere as a "global fallback" but never tied into the formula.
2451
- - The word "precedence" collapsed three distinct mechanisms — base value selection, formula application, and full override — into one list, so `minimumTtlSeconds` (a floor, never a default) appeared as the first entry of a "later wins" chain.
2452
-
2453
- This patch rewrites the §TTL precedence section as §TTL resolution, split into three explicit steps:
2454
-
2455
- 1. **Base duration**: manifest `expectedDurationSeconds` OR config `jobs.ttlSeconds` (default `3600`).
2456
- 2. **Computed TTL**: `max(base × graceMultiplier, minimumTtlSeconds)`.
2457
- 3. **Overrides** (later wins, skips formula): `jobs.perActionTtl.<actionId>`, then `--ttl` flag.
2458
-
2459
- Five worked examples added. Negative / zero overrides are rejected at submit time (exit 2). A Stability note states the procedure is locked going forward — new override sources are minor, formula-shape changes are major. The §Submit checklist step 5 now references the new §TTL resolution section instead of inlining a broken one-liner.
2460
-
2461
- Classification: patch. No field or schema changed. Every existing manifest and config combination resolves to the same TTL except for the previously-undefined case (manifest without `expectedDurationSeconds`), which was silently implementation-defined; the new text makes the `jobs.ttlSeconds` fallback normative. Companion prose updates land in `ROADMAP.md §TTL per action` and §Notable config keys.
2462
-
2463
- ## 0.2.1
2464
-
2465
- ### Patch Changes
126
+ ### Minor
2466
127
 
2467
- - b827431: Clarify the comment in `spec/README.md` §"Use load a schema": `specIndex.specVersion` is the payload shape version baked into `index.json`, not the npm package version. The two may drift bumping the npm package does not bump `specVersion` unless the shape of `index.json` itself changes.
128
+ - **`--all` promoted to a normative universal flag** in `cli-contract.md §Global flags`. Any verb that accepts a target identifier (`-n <node.path>`, `<job.id>`, `<plugin.id>`) MUST accept `--all` as "apply to every eligible target matching the verb's preconditions". Mutually exclusive with a positional target on the same invocation. Verbs where fan-out is nonsensical (`sm record`, `sm init`, `sm version`, `sm help`, `sm config get/set/reset/show`, `sm db *`, `sm serve`) MUST reject `--all` with exit `2`.
2468
129
 
2469
130
  ## 0.2.0
2470
131
 
2471
- ### Minor Changes
2472
-
2473
- - 79aed4d: **Breaking**: rename `dispatch-lifecycle.md` → `job-lifecycle.md`.
2474
-
2475
- ROADMAP decision #30 renamed the domain term "dispatch" to "job" (tables `state_jobs`, artifact "job file"). The spec prose filename had lagged behind; this change closes that gap.
2476
-
2477
- All internal references updated: `architecture.md`, `cli-contract.md`, `db-schema.md`, `prompt-preamble.md`, `versioning.md`, `schemas/job.schema.json`, `README.md`, and `package.json` `files` list. `index.json` regenerated.
2478
-
2479
- **Migration**: any external consumer that links to `spec/dispatch-lifecycle.md` (by URL or filename) MUST update to `spec/job-lifecycle.md`. The canonical URL becomes `https://skill-map.dev/spec/v0/job-lifecycle.md`.
2480
-
2481
- Classification: breaking change on a normative prose doc. Per `versioning.md` §Pre-1.0, minor bumps MAY contain breaking changes while the spec is `0.Y.Z`.
2482
-
2483
- ## 0.1.2
2484
-
2485
- ### Patch Changes
2486
-
2487
- - f4214fe: Expand `spec/README.md` §Distribution with concrete install and usage snippets now that `@skill-map/spec` is live on npm: install command, loading a schema via `exports`, and a small integrity-verification example using the `index.json` sha256 block.
2488
-
2489
- ## 0.1.1
2490
-
2491
- ### Patch Changes
132
+ ### Minor
2492
133
 
2493
- - bc0b217: Update `spec/conformance/README.md` wording: drop the "v0.1.0-alpha.0" label (we shipped `0.1.0`), and reflect that the suite now carries two cases (`basic-scan`, `kernel-empty-boot`) with a shared `minimal-claude` fixture.
134
+ - **`@skill-map/spec` published on npm.** First public release of the spec package.
2494
135
 
2495
136
  ## 0.1.0
2496
137
 
2497
- ### Minor Changes
2498
-
2499
- - 5b3829a: Add conformance case `kernel-empty-boot`:
2500
-
2501
- - New file: `spec/conformance/cases/kernel-empty-boot.json`.
2502
- - Exercises the boot invariant from `architecture.md`: with every adapter, detector, and rule disabled, scanning an empty scope MUST return a valid `ScanResult` with `schemaVersion: 1` and zero-filled stats.
2503
- - Referenced in `conformance/README.md` (§"Cases explicitly referenced elsewhere in the spec"). Entry moved from "pending" to "current" in the case inventory.
2504
- - Registered in `spec/index.json` and the integrity block (SHA256 regenerated).
2505
-
2506
- The second pending case, `preamble-bitwise-match`, is deferred to Step 10 (requires `sm job preview` from the job subsystem).
2507
-
2508
- - 4e0aec4: Initial public spec surface (`v0.1.0`):
2509
-
2510
- - 21 JSON Schemas (draft 2020-12): 10 top-level, 6 frontmatter, 5 summaries.
2511
- - 7 prose contracts (architecture, cli-contract, dispatch-lifecycle, job-events, prompt-preamble, db-schema, plugin-kv-api).
2512
- - 1 interface doc (security-scanner).
2513
- - Conformance stub: `basic-scan` case, `minimal-claude` fixture, verbatim `preamble-v1.txt`.
2514
- - Machine-readable `index.json` with integrity hashes per file.
2515
-
2516
- This is the first tagged release of the skill-map specification.
2517
-
2518
- Changelog for the **skill-map specification**, tracked independently from the reference CLI. See `versioning.md` for the policy that governs what constitutes a patch / minor / major change.
2519
-
2520
- Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html) as refined in `versioning.md`.
2521
-
2522
- Each entry classifies changes into four sections:
2523
-
2524
- - **Added** — new optional fields, schemas, or contracts.
2525
- - **Changed** — modifications to existing normative content. Breaking changes are called out explicitly.
2526
- - **Deprecated** — features scheduled for removal in a future major.
2527
- - **Removed** — features removed in a major bump.
2528
-
2529
- Tag convention: `spec-vX.Y.Z` (distinct from CLI tags `cli-vX.Y.Z`).
2530
-
2531
- ---
2532
-
2533
- ## [Unreleased]
2534
-
2535
- Initial public spec bootstrap (Step 0a phases 1–3).
2536
-
2537
- ### Added
2538
-
2539
- - `cli-contract.md` — new normative section **§Dry-run** between §Exit codes and §Verb catalog. Codifies the contract every verb that exposes `-n` / `--dry-run` MUST honour: no observable side effects (DB / FS / config / network / spawns), no auto-provisioning of scope directories, output mirrors live mode with explicit "would …" framing, exit codes mirror live mode, dry-run MUST short-circuit `--yes` / `--force` confirmation prompts. Per-verb opt-in: the flag is not global and verbs that don't declare it MUST reject it as an unknown option (exit `2`). Verb catalog rows for `sm init`, `sm db reset` (default + `--state` + `--hard`), and `sm db restore` amended to declare and describe their `--dry-run` previews. Pre-1.0 minor (additive normative).
2540
- - `plugin-author-guide.md` — consolidated section on the three-tier frontmatter validation model (default permissive `additionalProperties: true` + always-active `unknown-field` rule emitting `warn` + `scan.strict` / `--strict` promoting warnings to `error`). Includes a worked example through all three tiers and an explicit note on why no "schema-extender" plugin kind exists (the path for custom validation is a deterministic Rule, not a new plugin kind). Editorial only. No normative change — the model already exists implicitly via `base.schema.json`'s permissive `additionalProperties` and `project-config.schema.json#/properties/scan/properties/strict`. Patch.
2541
- - `PluginManifest` gains optional `granularity` field (enum `bundle` / `extension`, default `bundle`). Built-in `claude` bundle is `granularity: bundle` (toggle the whole bundle); built-in `core` bundle is `granularity: extension` (each built-in toggle-able individually under `core/<ext-id>`). `sm plugins enable / disable` validates the supplied id against the bundle's declared granularity (bundle granularity rejects qualified ids; extension granularity rejects bare bundle ids) and persists in `config_plugins` with the appropriate key. `--all` operates only on bundle-granularity plugin ids; the "disable every kernel built-in" intent is served by `--no-built-ins`. `plugin-author-guide.md` adds a §Granularity — bundle vs extension section with the per-verb behaviour table and the built-in mapping; `architecture.md` §`PluginLoaderPort` documents the runtime split (loader's pre-import resolveEnabled is coarse / bundle-level; the CLI's runtime composer drops per-extension disabled extensions before they reach the orchestrator). Closes the spec-vs-impl drift between the spec promise that "no extension is privileged, removable" and the prior implementation where built-ins were always-on. Pre-1.0 minor per `versioning.md` § Pre-1.0; additive on the manifest schema, breaking only for users who relied on the `claude` adapter loading without an explicit `config_plugins` row (none today, since the row had no effect on built-ins before).
2542
- - Detector manifest gains optional `applicableKinds` filter (array, `minItems: 1`, kebab-case strings, `uniqueItems: true`). When declared, the kernel skips invocation for nodes whose `kind` is not in the list — fail-fast, before the detect context is built, so a probabilistic detector wastes zero LLM cost (and a deterministic detector zero CPU) on inapplicable nodes. Absent = applies to every kind (the default); no wildcard syntax. Empty array `[]` is rejected at load time. Unknown kinds (no installed Adapter declares them via `defaultRefreshAction`) load OK with a `sm plugins doctor` warning — the Provider may arrive later — and the doctor exit code is NOT promoted by the warning. `plugin-author-guide.md` adds a §Detector `applicableKinds` — narrow the pipeline section under Granularity with the per-shape behaviour table and a worked example; `architecture.md` adds a §Detector · `applicableKinds` filter subsection above trigger normalization; `schemas/extensions/detector.schema.json` declares the new property. Pre-1.0 minor per `versioning.md` § Pre-1.0; additive, non-breaking for existing detectors (they all behave as if `applicableKinds: undefined`).
2543
-
2544
- ### Changed
2545
-
2546
- - `cli-contract.md`: `--all` is no longer a global flag. It is valid only on verbs that explicitly document fan-out semantics: `sm job submit`, `sm job run`, `sm job cancel`, and `sm plugins enable/disable`.
2547
- - `cli-contract.md`: `sm scan compare-with <dump> [roots...]` is now a sub-verb instead of a `--compare-with <path>` flag on `sm scan`. Read-only delta report against a saved `ScanResult` JSON dump. Same exit codes (`0` empty delta / `1` drift / `2` operational error). Old flag form removed. Pre-1.0 breaking change shipped as minor per `versioning.md` § Pre-1.0.
2548
- - Plugin discovery — directory name MUST equal manifest id (else `invalid-manifest`); cross-root id collisions yield new `id-collision` status (sixth status, both collided plugins blocked, no precedence). `plugin-author-guide.md` Diagnostics table grows from five to six rows; `architecture.md` §`PluginLoaderPort` documents the two enforcement points; `schemas/plugins-registry.schema.json#/$defs/DiscoveredPlugin/status` adds `id-collision` to the enum. Pre-1.0 minor per `versioning.md` § Pre-1.0; breaking for any plugin whose directory name does not match its manifest id, but no real ecosystem affected today.
2549
- - Plugin extensions are now identified by qualified ids `<plugin-id>/<extension-id>`. Built-in extensions adopt the `core/` namespace; the Claude adapter and its kind-aware detectors (frontmatter, slash, at-directive) live under `claude/`. The loader injects `pluginId` from `plugin.json#/id` into every extension at load time; an explicit `pluginId` field on an extension that disagrees with the manifest id is `invalid-manifest`. `architecture.md` §`PluginLoaderPort` documents the qualifier composition; `plugin-author-guide.md` adds a §Qualified extension ids section with the built-in mapping table; `schemas/extensions/base.schema.json` clarifies that extension `id` stays unqualified (single kebab-case segment, no `/`); `schemas/extensions/adapter.schema.json#/properties/defaultRefreshAction` now requires qualified action ids (pattern `^<plugin-id>/<action-id>$`). Pre-1.0 minor per `versioning.md` § Pre-1.0; breaking for any plugin or test that referenced an extension by short id.
2550
- - `cli-contract.md`: exit-code `2` "Operational error" row clarified to mention runtime / environment mismatches (wrong Node version, missing native dependency) explicitly. The "unhandled exception" catch-all already covered the case; this just removes ambiguity for future implementers.
2551
- - `job-events.md`: the common `runId` envelope now explicitly documents the optional mode segment (`r-<mode>-YYYYMMDD-HHMMSS-XXXX`) used by external Skill claims, scan runs, and standalone issue recomputations.
2552
- - `versioning.md` and related prose: replace ambiguous milestone terminology with explicit versioned release language.
2553
-
2554
- ### Added
2555
-
2556
- - Foundation:
2557
- - `README.md` — human-readable introduction and repo layout.
2558
- - `versioning.md` — evolution policy, stability tags, 3-minor deprecation window.
2559
- - `CHANGELOG.md` — this file.
2560
- - JSON Schemas (21 files, all draft 2020-12, camelCase keys):
2561
- - Top-level (10): `node`, `link`, `issue`, `scan-result`, `execution-record`, `project-config`, `plugins-registry`, `job`, `report-base`, `conformance-case`.
2562
- - Frontmatter (6): `base` + per-kind `skill` / `agent` / `command` / `hook` / `note`. Per-kind schemas extend `base` via `allOf`.
2563
- - Summaries (5): per-kind `skill` / `agent` / `command` / `hook` / `note`. All extend `report-base` via `allOf`.
2564
- - Prose contracts:
2565
- - `architecture.md` — hexagonal ports & adapters; 5 ports (`StoragePort`, `FilesystemPort`, `PluginLoaderPort`, `RunnerPort`, `ProgressEmitterPort`); 6 extension kinds (Adapter, Detector, Rule, Action, Audit, Renderer); kernel boundary + forbidden/permitted imports.
2566
- - `cli-contract.md` — CLI surface: global flags, env vars, 30+ verbs (`sm init`, `sm scan`, `sm list`, `sm show`, `sm check`, `sm findings`, `sm graph`, `sm export`, `sm job *`, `sm record`, `sm history`, `sm plugins *`, `sm audit *`, `sm db *`, `sm serve`, `sm help`), exit codes (0–5 defined, 6–15 reserved), `--json` output rules, `--format json|md|human` introspection.
2567
- - `dispatch-lifecycle.md` — job state machine (queued → running → completed | failed), atomic claim (`UPDATE ... RETURNING id`), duplicate prevention via `contentHash`, TTL with auto-reap, nonce authentication for `sm record`, sequential concurrency for MVP, retention and GC.
2568
- - `job-events.md` — canonical event stream: envelope (`type`, `timestamp`, `runId`, `jobId`, `data`), 11 canonical event types (`run.started`, `run.reap.started`, `run.reap.completed`, `job.claimed`, `job.skipped`, `job.spawning`, `model.delta`, `job.callback.received`, `job.completed`, `job.failed`, `run.summary`) plus one synthetic error event (`emitter.error`, emitted only on serialization failure), three output adapters (`pretty`, `stream-output`, `json`), ordering rules.
2569
- - `prompt-preamble.md` — verbatim normative preamble text that the kernel prepends to every rendered job file; `<user-content id="...">` delimiter contract with zero-width-space escaping; `safety` + `confidence` contract on model output; conformance fixture at `conformance/fixtures/preamble-v1.txt`.
2570
- - `db-schema.md` — engine-agnostic table catalog: three zones (`scan_*`, `state_*`, `config_*`), naming conventions (snake*case, zone prefix, `_at` / `_ms` / `_hash` / `_json` / `_count` suffixes, `is*`/`has\_` prefixes), kernel table list per zone, migration rules (`.sql`files,`NNN_snake_case.sql`, up-only, auto-backup), plugin storage modes.
2571
- - `plugin-kv-api.md` — `ctx.store` contract for mode A (`KvStore.get/set/delete/list`, plugin-scoped, optional node-scoped), mode B dedicated-tables rules (prefix injection, DDL validation, scoped Database wrapper), typed errors (`KvKeyInvalidError`, `KvValueNotSerializableError`, `KvValueTooLargeError`, `KvOperationFailedError`, `ScopedDbViolationError`). Mixing modes in a plugin is forbidden.
2572
- - Interfaces:
2573
- - `interfaces/security-scanner.md` — convention over the Action kind (id prefix `security-`) for third-party security scanners (Snyk, Socket, custom). Defines `SecurityReport` shape extending `report-base.schema.json`, normative finding categories, deduplication rules, aggregation via `sm findings --security`. Marked `Stability: experimental` through v0.x.
2574
-
2575
- ### Conventions locked (normative)
2576
-
2577
- - JSON Schema dialect: draft 2020-12.
2578
- - Casing: camelCase for all JSON keys (domain, configs, manifests, reports); kebab-case for filenames.
2579
- - `$id` scheme: `https://skill-map.dev/spec/v<major>/<path>.schema.json`. `v0` throughout pre-1.0; bumps to `v1` at the first stable release.
2580
- - Identity: `node.path` (relative to scope root) is the canonical node identifier in v0. Future UUID-based `node.id` lands with write-back.
2581
- - Required frontmatter: `name`, `description`, `metadata`, `metadata.version`.
2582
- - Frontmatter: `additionalProperties: true` (rules handle unknown fields). Summaries: `additionalProperties: false` (strict).
2583
- - Id prefixes: job `d-`, execution record `e-`, run `r-` (all `PREFIX-YYYYMMDD-HHMMSS-XXXX`).
2584
- - Exit codes: 0 ok / 1 issues / 2 error / 3 duplicate / 4 nonce-mismatch / 5 not-found.
2585
- - Deprecation window: 3 minor releases between `stable → deprecated` and removal.
2586
- - Storage modes: a plugin declares exactly one (`kv` or `dedicated`). Mixing forbidden.
2587
-
2588
- ### Conformance (stub)
2589
-
2590
- - `conformance/README.md` — suite layout, case format, assertion types (`exit-code`, `json-path`, `file-exists`, `file-contains-verbatim`, `file-matches-schema`, `stderr-matches`), runner pseudocode.
2591
- - `conformance/fixtures/minimal-claude/` — 5 MDs (one per kind: skill, agent, command, hook, note) used as the first controlled corpus.
2592
- - `conformance/fixtures/preamble-v1.txt` — verbatim extraction of the preamble from `prompt-preamble.md`, checked byte-for-byte by the future `preamble-bitwise-match` case.
2593
- - `conformance/cases/basic-scan.json` — first declarative case. Scans the `minimal-claude` fixture; asserts `schemaVersion: 1`, 5 nodes, 0 issues.
2594
-
2595
- ### Packaging
2596
-
2597
- - `package.json` at the spec root. Name: `@skill-map/spec`. Version `0.0.1` (first release line; spec versioning is strict pre-1.0 per `versioning.md`). `exports` surfaces `.` → `index.json`, plus every `./schemas/*.json`.
2598
- - `index.json` at the spec root. Machine-readable manifest of schemas, prose, interfaces, and conformance. Carries an `integrity` block with a sha256 per shipped file, deterministically regenerated by `scripts/build-spec-index.mjs`. CI blocks drift via `npm run spec:check`.
2599
- - `schemas/conformance-case.schema.json` — formal schema for entries under `conformance/cases/*.json`. Defines the `invoke` object and the six assertion types (`exit-code`, `json-path`, `file-exists`, `file-contains-verbatim`, `file-matches-schema`, `stderr-matches`) as a discriminated union via `oneOf`.
2600
-
2601
- ### Notes
138
+ ### Minor
2602
139
 
2603
- - Pending for `spec-v0.1.0`: cases `kernel-empty-boot` and `preamble-bitwise-match` (referenced normatively in `architecture.md` and `prompt-preamble.md`). Land alongside Step 0b when the reference implementation exists to run them against.
2604
- - No tagged spec release yet. First tag (`spec-v0.1.0`) lands after Step 0b CI validates the implementation against this stub.
2605
- - Release pipeline: `@skill-map/spec` is published via [changesets](https://github.com/changesets/changesets). Every PR that touches `spec/` includes a `.changeset/*.md` declaring the bump; merging to `main` opens a "Version Packages" PR; merging that PR publishes to npm and tags the release. See `CONTRIBUTING.md`.
140
+ - **Initial public spec bootstrap.** Ships the JSON Schemas (draft 2020-12) for `Node` / `Link` / `Issue` / `ScanResult` / `ExecutionRecord` / `ProjectConfig` / `PluginsRegistry` / `Job` / `ReportBase` / `ConformanceCase` / `HistoryStats` plus the per-kind extension schemas (Provider / Extractor / Rule / Action / Formatter / Hook). Prose normative contracts: `cli-contract.md`, `architecture.md`, `db-schema.md`, `job-lifecycle.md`, `job-events.md`, `prompt-preamble.md`, `plugin-kv-api.md`. Conformance case `kernel-empty-boot` exercises the boot invariant (kernel boots and returns an empty `ScanResult` with zero registered extensions); `preamble-bitwise-match` is deferred to Step 10.