@skill-map/spec 0.16.0 → 0.18.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/cli-contract.md CHANGED
@@ -234,6 +234,115 @@ Exit: 0 on clean (or clean watcher shutdown), 1 if error-severity issues exist (
234
234
 
235
235
  Actions are not invoked via `sm actions`; invocation is via `sm job submit` (see below).
236
236
 
237
+ #### Sidecar bump (Step 9.6.4)
238
+
239
+ The built-in deterministic `core/bump` Action is the canonical write channel for `<basename>.sm` annotation sidecars; the verbs below are its CLI surface plus a few sidecar-management helpers. The `bump` verb stays top-level (high frequency, ROADMAP-named); the administrative helpers live under the `sm sidecar` sub-namespace to avoid colliding with the existing `sm refresh` (which targets the enrichment layer, not sidecars). All sidecar-touching verbs are deterministic — they invoke `core/bump` (or the `FilesystemSidecarStore` directly) in-process and never queue jobs.
240
+
241
+ | Command | Purpose |
242
+ |---|---|
243
+ | `sm bump <node.path> [--force]` | Single-node bump. Wraps `core/bump`. Refuses on a fresh node (`{ ok: false, reason: 'fresh' }`, exit `2`) unless `--force` is passed; with `--force` on a fresh node the verb is a silent no-op (exit `0`, no stdout). On a stale node (or first-time creation) increments `annotations.version`, refreshes `for.{bodyHash, frontmatterHash}`, and stamps the audit block (`audit.lastBumpedAt` + `audit.lastBumpedBy: 'cli'`; on first creation also `audit.createdAt` + `audit.createdBy: 'cli'`). Exit `5` if the node is not in the persisted scan. `--json` emits the report shape declared by `bump-report.schema.json`. |
244
+ | `sm bump --pending [--staged] [--force]` | Batch bump. Walks every node whose sidecar overlay reports drift in `node.path` ASC order and bumps each. `--staged` runs `git add <sidecar-path>` after each successful bump so the new content lands in the same commit; `git add` failure degrades to a stderr warning, the batch keeps running. Empty stale set → exit `0` with a "nothing to do" advisory. `--json` envelope: `{ bumped, refused, skipped, errors[], elapsedMs }`. Exit `0` on a clean run; `1` when at least one per-node error landed in `errors[]`. **Git error matrix for `--staged`**: not inside a git repo (no `.git/` parent of `cwd`) → exit `5`; `git` binary not on PATH (spawn ENOENT) → exit `2`. Both checks run BEFORE any sidecar write so a misconfigured environment never produces partial state. |
245
+ | `sm sidecar refresh <node.path>` | Hash-only update on the sidecar. Refreshes `for.{bodyHash, frontmatterHash}` to match the live node WITHOUT bumping `annotations.version` and WITHOUT touching the audit block. Useful when the user knows a body change is editorial-only and doesn't want to spend a version increment. Distinct from the top-level `sm refresh` (which targets the enrichment layer at Step A.8) — different storage, different concept; the sub-namespace prefix prevents the collision. Exit `5` if the node has no sidecar or is not in the persisted scan. No-op on a fresh node (informational stderr, exit `0`). |
246
+ | `sm sidecar prune [--dry-run] [--yes]` | Delete orphan `.sm` files (sidecars whose accompanying `<basename>.md` does not exist on disk). Destructive — without `--dry-run` prompts for interactive confirmation listing every file to be deleted (per the §Dry-run rule for destructive verbs). `--yes` (alias `--force`) bypasses the prompt for non-interactive callers (CI, the pre-commit hook, scripts). With `--dry-run` reports what would be deleted without touching disk and never prompts. Different domain from `sm orphans` — that verb operates on the node graph (rename heuristic); this one operates on the filesystem layer. `--json` envelope: `{ deleted, wouldDelete, errors, items[], elapsedMs }`. Exit `1` when delete failures landed in `errors`. |
247
+ | `sm sidecar annotate <node.path> [--force]` | Pure scaffolding. Writes a minimal `.sm` next to the `.md` with the identity (`for:`) block populated and an empty `annotations: {}` block, ready for editing. Refuses if the file exists; `--force` overwrites. The optional legacy-frontmatter migration helper (`--from-frontmatter`) is deferred — no released consumer demands it. |
248
+ | `sm hooks install pre-commit-bump [--dry-run]` | Install (or chain into) a git pre-commit hook that runs `sm bump --pending --staged` so any staged drift in `.sm` sidecars auto-bumps before the commit lands. Idempotent: re-running detects the skill-map marker and no-ops. When the repo already has a custom `pre-commit`, the verb appends the skill-map block to the existing file rather than replacing it. `--dry-run` prints the planned content with `--- target: <path> ---` markers and writes nothing. Exit `5` if no `.git/` parent is found at or above `cwd`; exit `2` on write failures or unknown hook flavours. |
249
+
250
+ **`.sm` round-trip contract.** The `bump` verb, `sm sidecar refresh`, and `sm sidecar annotate` write through `FilesystemSidecarStore`, which re-serialises the merged result via `js-yaml` `dump` with `sortKeys: true`. **`.sm` files are managed artifacts; comments and key order are not preserved on round-trip.** Author commentary belongs in the markdown body or in a separate documentation file, not inside `.sm`. The integrity guarantee is that the merged YAML always validates against `sidecar.schema.json` + `annotations.schema.json` and that the file is written atomically (`.tmp + rename`).
251
+
252
+ Concretely, a hand-edited sidecar like this:
253
+
254
+ ```yaml
255
+ for:
256
+ path: agents/reviewer.md
257
+ bodyHash: 3dd7d0...
258
+ frontmatterHash: 271d1e...
259
+
260
+ annotations:
261
+ version: 3
262
+ # Deprecated because v0.6 architecture supersedes this skill.
263
+ # See decision #142 in ROADMAP for context.
264
+ stability: deprecated
265
+ supersededBy: agents/reviewer-v2.md
266
+ tags:
267
+ - review
268
+ - typescript # only TS, not JS
269
+ ```
270
+
271
+ …becomes the following after one `sm bump`:
272
+
273
+ ```yaml
274
+ annotations:
275
+ stability: deprecated
276
+ supersededBy: agents/reviewer-v2.md
277
+ tags:
278
+ - review
279
+ - typescript
280
+ version: 4
281
+ audit:
282
+ createdAt: '2026-05-07T10:00:00.000Z'
283
+ createdBy: cli
284
+ lastBumpedAt: '2026-05-07T10:00:00.000Z'
285
+ lastBumpedBy: cli
286
+ for:
287
+ bodyHash: 3dd7d0...
288
+ frontmatterHash: 271d1e...
289
+ path: agents/reviewer.md
290
+ ```
291
+
292
+ Comments dropped, keys re-sorted alphabetically. **`.sm` files cannot preserve free-form commentary across bumps — narrative documentation lives in the `.md` body, which is never touched.** The `sm sidecar annotate` scaffold prints a banner reminding the author of this contract on first creation; that banner itself is dropped on the first bump.
293
+
294
+ Tracked as **R6** in the §Step 9.6 review queue: open by design, defer the `js-yaml` → `yaml` (eemeli) swap that would preserve comments + key order until a user complaint surfaces. The swap is a small piece of work (one new dep, one Document-aware merge helper); the bias is to ship simple now and add fidelity when there is concrete demand.
295
+
296
+ ##### BFF endpoint — `POST /api/sidecar/bump` (Step 9.6.5, BFF half)
297
+
298
+ The Hono BFF exposes the single-node bump flow over REST so the UI can drive the same Action / Store the CLI uses. Behaviour mirrors `sm bump <node.path> [--force]` 1:1: same `core/bump` Action, same `FilesystemSidecarStore`, same fresh-vs-stale refusal semantics. The only differences from the CLI verb are the invoker label (`'ui'` vs `'cli'` — Decision A5 of 9.6.4 left this as a literal) and the wire shape. Batch (`--pending`) is intentionally CLI-only at 9.6.5 — surfacing it over REST needs a job-style progress channel and lands later.
299
+
300
+ | Field | Value |
301
+ |---|---|
302
+ | Method + path | `POST /api/sidecar/bump` |
303
+ | Request body | `{ "nodePath": <string, required>, "force"?: <boolean, default false> }` (JSON). `nodePath` is the canonical scope-root-relative `Node.path`. |
304
+ | 200 envelope | `{ "schemaVersion": "1", "kind": "sidecar.bumped", "value": { "nodePath": <string>, "version": <int|null>, "status": "fresh" }, "elapsedMs": <int> }`. The `kind` value is part of the canonical `rest-envelope.schema.json#/properties/kind/enum` and validates under the action-result `oneOf` variant (`value` + `elapsedMs` siblings, no `filters` / `counts` / `kindRegistry`). |
305
+ | 409 envelope | `{ "ok": false, "error": { "code": "sidecar-fresh", "message": <string>, "details": null } }`. Returned when the target node is fresh and `force !== true`. The `'sidecar-fresh'` code is added to `app.ts`'s `TErrorCode` union. |
306
+ | 404 envelope | Standard `'not-found'` envelope. Returned when the DB is missing OR `nodePath` is not in the persisted scan. |
307
+ | 400 envelope | Standard `'bad-query'` envelope. Body must be a JSON object with `nodePath` (non-empty string); `force` (when present) must be a boolean. |
308
+ | 200 force-on-fresh | Per the Action spec, `force: true` on a fresh node is a silent no-op — the response carries the existing `version` (read off the sidecar overlay) and `status: 'fresh'`. **No WS broadcast** is emitted in this case (decision: no-op = no event; nothing changed on disk, sending `sidecar.bumped` would tell every connected UI to refresh state that hasn't moved). |
309
+
310
+ **WS event — `sidecar.bumped`** (Step 9.6.5; canonical envelope shape locked in 9.6.7 / R9). After every successful bump that materialises a write, the BFF broadcasts a `sidecar.bumped` event over `/ws` so all connected clients refresh in lockstep. The event uses the canonical `IWsEventEnvelope` wire shape (matches every other kernel→broadcaster bridge — `scan.*`, `watcher.*`, etc.):
311
+
312
+ ```jsonc
313
+ {
314
+ "type": "sidecar.bumped",
315
+ "timestamp": "<ISO 8601 string>", // server wall-clock at broadcast time
316
+ "data": {
317
+ "nodePath": "<scope-root-relative path>",
318
+ "version": <int|null>, // new value of annotations.version (`null` if absent)
319
+ "status": "fresh" // status after the bump
320
+ }
321
+ }
322
+ ```
323
+
324
+ Emission rules:
325
+
326
+ - Emitted on a successful 200 bump (stale → fresh, or first-time-create → fresh).
327
+ - **NOT** emitted on a force-on-fresh no-op 200 (nothing changed on disk).
328
+ - **NOT** emitted on 409 / 404 / 400 (no write happened).
329
+
330
+ The `type` value is a normative addition to the event-type registry — if a future spec section catalogues every WS event type, `sidecar.bumped` joins `scan.started` / `scan.completed` / `watcher.error` / `emitter.error` there.
331
+
332
+ ##### BFF endpoint — `GET /api/annotations/registered` (Step 9.6.6, BFF half)
333
+
334
+ Read-only catalog of plugin-contributed annotation keys. The endpoint is a pure projection of `kernel.getRegisteredAnnotationKeys()` — populated once by `registerEnabledExtensions` at server boot, frozen, surfaced unchanged. Built-in catalog keys (from `annotations.schema.json`) are NOT included; the UI knows the built-in set via the bundled spec. The endpoint exists so a future UI autocomplete can offer plugin-namespaced and root-exclusive contributions the UI can't otherwise discover at runtime.
335
+
336
+ | Field | Value |
337
+ |---|---|
338
+ | Method + path | `GET /api/annotations/registered` |
339
+ | Request | None — no query params, no body, no auth (matches `/api/plugins`, `/api/config`). |
340
+ | 200 envelope | `{ "schemaVersion": "1", "kind": "annotations.registered", "items": IRegisteredAnnotationKey[], "counts": { "total": <int> } }`. The `kind` value is part of the canonical `rest-envelope.schema.json#/properties/kind/enum` and validates under the catalog `oneOf` variant (`items` + `counts.total` only, no `filters` / `kindRegistry` / `returned` — the catalog ships in its entirety on every response and does not paginate). |
341
+ | Item shape | `IRegisteredAnnotationKey` per `src/kernel/types/annotation-catalog.ts`: `{ pluginId: string, key: string, location: 'namespaced' \| 'root', ownership: 'exclusive' \| 'shared', schema: Record<string, unknown> }`. The inline JSON Schema as declared in the contributing plugin's manifest (NOT the AJV-compiled validator). |
342
+ | Invariants | Read-only, no side effects, never throws after kernel boot. The catalog is small (typically 0–50 entries); no pagination, no filters, no caching headers. Mutating the returned `items` array does not affect subsequent calls — the kernel's view is frozen. |
343
+ | Empty case | When the kernel was booted with no plugin contributions (or `--no-plugins`): `{ "items": [], "counts": { "total": 0 } }`. |
344
+ | Refresh policy | Same as the rest of the BFF's plugin surface — discovery happens once at `sm serve` boot. An operator that installs a new plugin restarts the server (matches the watcher's "loaded ONCE at boot" contract). |
345
+
237
346
  ---
238
347
 
239
348
  ### Jobs
@@ -319,6 +428,7 @@ See `db-schema.md` for the table catalog.
319
428
  | `sm db backup [--out <path>]` | WAL checkpoint + file copy. |
320
429
  | `sm db restore <path> [-n / --dry-run]` | Swap the DB. Destructive. `--dry-run` validates the source file (existence, header, schema version) and reports what would be overwritten without touching the live DB. |
321
430
  | `sm db shell` | Interactive SQL shell (implementations backed by SQLite use `sqlite3`; others use equivalent). |
431
+ | `sm db browser [<path>] [--rw]` | Open the DB in DB Browser for SQLite (`sqlitebrowser` GUI). Read-only by default (`-R`) so a concurrent `sm scan` writer is safe; pass `--rw` to enable writes. The `sqlitebrowser` binary MUST be on `PATH`. Non-destructive — no confirmation prompt. Detaches from the terminal so the shell stays usable. |
322
432
  | `sm db dump [--tables ...]` | SQL dump. |
323
433
  | `sm db migrate [--dry-run \| --status \| --to <n> \| --kernel-only \| --plugin <id> \| --no-backup]` | Migration controls. |
324
434
 
@@ -330,7 +440,7 @@ Destructive verbs (`reset --state`, `reset --hard`, `restore`) require interacti
330
440
 
331
441
  | Command | Purpose |
332
442
  |---|---|
333
- | `sm serve [--port N] [--host ...] [--scope project\|global] [--db <path>] [--no-built-ins] [--no-plugins] [--open\|--no-open] [--dev-cors] [--ui-dist <path>] [--no-watcher]` | Start Hono + WebSocket for the Web UI. Single-port mandate: SPA + REST + WS under one listener. Default port 4242, default host 127.0.0.1 (loopback-only through v0.6.0; multi-host deferred — see §Server). The watcher is on by default (Decision #121: a server with stale DB is a footgun); pass `--no-watcher` for CI / read-only deployments. |
443
+ | `sm serve [--port N] [--host ...] [--scope project\|global] [--db <path>] [--no-built-ins] [--no-plugins] [--open\|--no-open] [--dev-cors] [--ui-dist <path>] [--no-ui] [--no-watcher]` | Start Hono + WebSocket for the Web UI. Single-port mandate: SPA + REST + WS under one listener. Default port 4242, default host 127.0.0.1 (loopback-only through v0.6.0; multi-host deferred — see §Server). The watcher is on by default (Decision #121: a server with stale DB is a footgun); pass `--no-watcher` for CI / read-only deployments. `--no-ui` skips the SPA bundle (dev workflow alongside the Angular dev server); see §Server flags. |
334
444
 
335
445
  #### Server
336
446
 
@@ -402,7 +512,8 @@ Error code sources at v14.2:
402
512
  | `--no-plugins` | off | Skip drop-in plugin discovery. |
403
513
  | `--open` / `--no-open` | `--open` | Auto-open the SPA in the user's default browser after listen. |
404
514
  | `--dev-cors` | off | Enable permissive CORS for the Angular dev-server proxy workflow. Loopback-only when set. |
405
- | `--ui-dist <path>` | auto | Override the UI bundle directory. Hidden flag — used by the demo build pipeline + tests; everyday users never need it. |
515
+ | `--ui-dist <path>` | auto | Override the UI bundle directory. Hidden flag — used by the demo build pipeline + tests; everyday users never need it. Mutually exclusive with `--no-ui` (rejected with exit 2). |
516
+ | `--no-ui` | off | Skip serving the Angular SPA bundle. The root `/` (and any SPA fallback) responds with an inline dev-mode placeholder pointing the user at `npm run ui:dev` + `http://localhost:4200/`. Intended for local development alongside the Angular dev server with HMR; pairs with `--no-open` (default `--open` plus `--no-ui` would auto-open the placeholder, so a non-fatal stderr warning is emitted in that combination). Mutually exclusive with `--ui-dist <path>` (rejected with exit 2). The server keeps `/api/*` and `/ws` fully functional; only the static SPA is suppressed. |
406
517
  | `--no-watcher` | off | Disable the chokidar-fed scan-and-broadcast loop. Use only for CI / read-only deployments — without the watcher, `/ws` stays open but no `scan.*` events ever fire. Combining with `--no-built-ins` is rejected (the watcher cannot run with an empty pipeline; would persist empty scans on every batch). |
407
518
 
408
519
  **WebSocket protocol** *(Stability: experimental — locks at v0.6.0)*:
@@ -10,8 +10,10 @@
10
10
  "assertions": [
11
11
  { "type": "exit-code", "value": 0 },
12
12
  { "type": "stderr-matches", "pattern": "plugin bad-provider:.*invalid.*must have required property 'ui'" },
13
- { "type": "json-path", "path": "$.providers.length", "equals": 1 },
13
+ { "type": "json-path", "path": "$.providers.length", "equals": 3 },
14
14
  { "type": "json-path", "path": "$.providers[0]", "equals": "claude" },
15
+ { "type": "json-path", "path": "$.providers[1]", "equals": "gemini" },
16
+ { "type": "json-path", "path": "$.providers[2]", "equals": "agent-skills" },
15
17
  { "type": "json-path", "path": "$.nodes.length", "equals": 1 }
16
18
  ]
17
19
  }
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "https://skill-map.dev/spec/v0/conformance-case.schema.json",
3
+ "id": "sidecar-end-to-end",
4
+ "description": "Step 9.6.6 — co-located `.sm` sidecar end-to-end. Scanning a fixture that carries a stale sidecar (wrong `for.{bodyHash,frontmatterHash}`) plus an orphan sidecar (no sibling `.md`) MUST surface `sidecar_status` queryable on the node, denormalise `annotations.version` into the node row, and emit both `annotation-stale` (per stale node) and `annotation-orphan` (per orphan `.sm`) issues from the built-in core rules.",
5
+ "fixture": "sidecar-end-to-end",
6
+ "invoke": {
7
+ "verb": "scan",
8
+ "flags": ["--json"]
9
+ },
10
+ "assertions": [
11
+ { "type": "exit-code", "value": 0 },
12
+ { "type": "json-path", "path": "$.schemaVersion", "equals": 1 },
13
+ { "type": "json-path", "path": "$.stats.nodesCount", "equals": 1 },
14
+ { "type": "json-path", "path": "$.nodes[0].path", "equals": ".claude/agents/stale.md" },
15
+ { "type": "json-path", "path": "$.nodes[0].version", "equals": 7 },
16
+ { "type": "json-path", "path": "$.nodes[0].sidecar.present", "equals": true },
17
+ { "type": "json-path", "path": "$.nodes[0].sidecar.status", "matches": "^stale-(body|frontmatter|both)$" },
18
+ { "type": "json-path", "path": "$.nodes[0].sidecar.annotations.version", "equals": 7 },
19
+ { "type": "json-path", "path": "$.stats.issuesCount", "equals": 2 },
20
+ { "type": "json-path", "path": "$.issues[0].ruleId", "equals": "annotation-stale" },
21
+ { "type": "json-path", "path": "$.issues[0].severity", "equals": "warn" },
22
+ { "type": "json-path", "path": "$.issues[1].ruleId", "equals": "annotation-orphan" },
23
+ { "type": "json-path", "path": "$.issues[1].severity", "equals": "warn" },
24
+ { "type": "json-path", "path": "$.issues[1].data.expectedMdPath", "equals": ".claude/agents/orphan.md" }
25
+ ]
26
+ }
@@ -18,12 +18,12 @@ This file is hand-maintained. A CI check before spec release compares the schema
18
18
  | 8 | `job.schema.json` | — | 🔴 missing | Blocked by Step 10 (job system). Needs a case that submits a local action (no LLM), inspects `sm job show --json`. |
19
19
  | 9 | `report-base.schema.json` | — | 🔴 missing | Indirect coverage once any summarizer case lands. Direct contract case: validate a handcrafted minimal report ({confidence, safety}) against the base schema. |
20
20
  | 10 | `conformance-case.schema.json` | — | 🔴 missing | Self-referential: every `*.json` under `cases/` MUST validate against this schema. Add a meta-case that enumerates + validates all cases. |
21
- | 11 | `frontmatter/base.schema.json` | — | 🔴 missing | Universal frontmatter shape. Per-kind schemas (`skill` / `agent` / `command` / `hook` / `note`) live with the Provider that emits them (the Claude Provider ships them under `src/extensions/providers/claude/schemas/`) and extend this base via `$ref`-by-`$id`. Direct spec-level case still pending: fixture with min-required frontmatter only, no Provider needed (Provider-disabled mode + a single `notes/<file>.md` with `name: ...` + `description: ...`). |
21
+ | 11 | `frontmatter/base.schema.json` | — | 🔴 missing | Universal frontmatter shape — `name` + `description` only, `additionalProperties: true`. Per-kind schemas (`skill` / `agent` / `command` / `markdown`) live with the Provider that emits them (the Claude Provider ships them under `src/extensions/providers/claude/schemas/`) and extend this base via `$ref`-by-`$id`. Direct spec-level case still pending: fixture with min-required frontmatter only, no Provider needed (Provider-disabled mode + a single `notes/<file>.md` with `name: ...` + `description: ...`). |
22
22
  | 12 | `summaries/skill.schema.json` | — | 🔴 missing | Blocked by Step 10 (`skill-summarizer`). Case: submit summarizer, validate report. |
23
23
  | 13 | `summaries/agent.schema.json` | — | 🔴 missing | Blocked by Step 11. |
24
24
  | 14 | `summaries/command.schema.json` | — | 🔴 missing | Blocked by Step 11. |
25
25
  | 15 | `summaries/hook.schema.json` | — | 🔴 missing | Blocked by Step 11. |
26
- | 16 | `summaries/note.schema.json` | — | 🔴 missing | Blocked by Step 11. |
26
+ | 16 | `summaries/markdown.schema.json` | — | 🔴 missing | Blocked by Step 11. |
27
27
  | 17 | `extensions/base.schema.json` | — | 🔴 missing | Meta-case: every manifest under `src/extensions/` validates against the appropriate kind schema (which extends base via `allOf`). |
28
28
  | 18 | `extensions/provider.schema.json` | `plugin-missing-ui-rejected` | 🟡 partial | A drop-in Provider whose `kinds[*]` entry omits the required `ui` block fails AJV validation with `invalid-manifest` while the rest of the pipeline keeps running (built-in Claude Provider, exit 0). The complementary positive case (canonical Claude Provider manifest validates) lives in `provider:claude` conformance. Direct cases for missing `kinds` / `explorationDir` rejection still pending. |
29
29
  | 19 | `extensions/extractor.schema.json` | — | 🔴 missing | Case: `frontmatter` + `slash` + `at-directive` extractor manifests validate; an extractor emitting a disallowed `emitsLinkKinds` value fails. |
@@ -33,8 +33,12 @@ This file is hand-maintained. A CI check before spec release compares the schema
33
33
  | 23 | `history-stats.schema.json` | — | 🔴 missing | Blocked by Step 5 (history). Case: seed `state_executions` with a deterministic fixture, run `sm history stats --json --since <T0> --until <T1> --period month --top 5`, assert the document validates and that `totals.executionsCount == sum(perAction.executionsCount)` and `errorRates.global == totals.failedCount / totals.executionsCount`. Percentiles (`p95`/`p99`) intentionally omitted in v1 — add later as a minor bump without breaking consumers. |
34
34
  | 24 | `extensions/hook.schema.json` | — | 🔴 missing | Case: a `deterministic` hook manifest with `triggers: ['scan.completed']` validates; a hook declaring an unknown trigger (e.g. `scan.progress`) fails with `invalid-manifest` at load time. |
35
35
  | 25 | `api/rest-envelope.schema.json` | — | 🔴 missing | Step 14.2 BFF list-envelope shape (`{ schemaVersion, kind, items \| item \| value, filters, counts }`). Case: hit `GET /api/nodes` against a primed scope, validate the response against the schema; assert the `oneOf` rejects an envelope that carries both `items` and `item`. Implementation-side coverage exists today (`src/test/server-endpoints.test.ts`) but a kernel-agnostic conformance case is required before v1.0.0 ships. |
36
+ | 26 | `sidecar.schema.json` | `sidecar-end-to-end` | 🟢 covered | Co-located YAML sidecar (`<basename>.sm`) root shape: reserved blocks `for` / `annotations` / `settings` / `audit` plus opt-in plugin namespacing. Step 9.6.2 (2026-05-05) shipped the kernel reader; Step 9.6.3 (2026-05-05) formalised the `audit:` sub-shape populated by the built-in `bump` Action; Step 9.6.6 (2026-05-06) flips this row 🟢 with the end-to-end `sidecar-end-to-end` case (fixture `sidecar-end-to-end/`): a scan over a stale-`.sm` + orphan-`.sm` corpus produces a populated `Node.sidecar` overlay with `present: true` and `status: stale-*`, denormalises `annotations.version` into the node row, and emits both `annotation-stale` and `annotation-orphan` issues from the built-in core rules. Structural sample (untouched) at `fixtures/sidecar-example/agent-example.sm`. |
37
+ | 27 | `annotations.schema.json` | `sidecar-end-to-end` | 🟢 covered | Curated catalog of 15 conventional skill-map annotation fields (versioning, supersession, provenance, lifecycle, taxonomy, display, docs). `additionalProperties: true` so users / plugins extend without coordination; the `unknown-field` Tier-1 rule shipped in Step 9.6.6 emits warnings on truly unrecognized keys. Step 9.6.2 (2026-05-05) wired the kernel reader; Step 9.6.6 (2026-05-06) flips this row 🟢 via `sidecar-end-to-end`, which asserts that an `annotations.version: 7` value round-trips through `state_scan_nodes.annotations_json` and surfaces in the node's `sidecar.annotations` overlay AND in the denormalised `Node.version` column. Structural sample at `fixtures/sidecar-example/agent-example.sm`. Catalog trimmed from 31 to 15 fields on 2026-05-07 after UX review. |
38
+ | 28 | `bump-report.schema.json` | — | 🔴 missing | Report shape produced by the built-in deterministic `bump` Action (Step 9.6.3, Decision #125). Extends `report-base-deterministic.schema.json` (row 29) — the deterministic counterpart to `report-base.schema.json` (which is LLM-specific via `confidence` + `safety`). Three concrete shapes: success-with-write, silent-no-op (under `force`), and refusal (`fresh`). Direct conformance case lands together with the `sm bump` CLI verb in Step 9.6.4 — it'll exercise all three branches via `sm bump --json` against a primed fixture. Implementation tests at `src/test/bump-action.test.ts` cover the runtime behaviour today. |
39
+ | 29 | `report-base-deterministic.schema.json` | — (indirect via row 28) | 🟡 partial | Deterministic counterpart to `report-base.schema.json`; every deterministic Action's report extends this base. Direct contract case still pending — landed when first conformance case directly validates a deterministic report against this schema. |
36
40
 
37
- > **Note on Provider-owned schemas.** Per-kind frontmatter schemas (`skill`, `agent`, `command`, `hook`, `note`) live with the Provider that emits them — for the built-in Claude Provider, under `src/extensions/providers/claude/schemas/`. Those schemas are NOT counted in the spec's coverage matrix above; they belong to the Provider's own conformance suite at `src/extensions/providers/claude/conformance/coverage.md`. The same split applies to the cases that exercise Provider-specific kinds (`basic-scan`, `rename-high`, `orphan-detection`) — they live in the Provider's `cases/` directory.
41
+ > **Note on Provider-owned schemas.** Per-kind frontmatter schemas (`skill`, `agent`, `command`, `note` for the built-in Claude Provider; other Providers MAY declare different kinds) live with the Provider that emits them — for the built-in Claude Provider, under `src/extensions/providers/claude/schemas/`. Those schemas are NOT counted in the spec's coverage matrix above; they belong to the Provider's own conformance suite at `src/extensions/providers/claude/conformance/coverage.md`. The same split applies to the cases that exercise Provider-specific kinds (`basic-scan`, `rename-high`, `orphan-detection`) — they live in the Provider's `cases/` directory.
38
42
 
39
43
  Status legend: 🟢 covered (at least one case asserts the schema end-to-end) · 🟡 partial (covered only indirectly or via a sub-shape) · 🔴 missing.
40
44
 
@@ -61,7 +65,7 @@ These have their own conformance cases even though they are not JSON Schemas.
61
65
 
62
66
  - **spec v0.x**: partial coverage acceptable. Every case added as the reference impl lands the verb that makes it runnable.
63
67
  - **spec v1.0.0 release**: all rows above MUST be 🟢 covered or explicitly 🟠 deferred to v1.1 with a linked issue.
64
- - **CI check**: [`scripts/check-coverage.js`](../../scripts/check-coverage.js) compares `spec/schemas/**/*.schema.json` against the matrix above on every PR. A schema without a row here, or a row pointing at a missing schema, fails CI (exit 1 with a `::error::` annotation). Wired into `ci.yml` §validate and into `npm run spec:check`.
68
+ - **CI check**: [`spec/scripts/check-coverage.js`](../scripts/check-coverage.js) compares `spec/schemas/**/*.schema.json` against the matrix above on every PR. A schema without a row here, or a row pointing at a missing schema, fails CI (exit 1 with a `::error::` annotation). Wired into the spec workspace's `spec:check` script (which is invoked by `npm run validate` from the repo root, picked up by CI).
65
69
 
66
70
  ## Stability
67
71
 
@@ -9,23 +9,23 @@ export default {
9
9
  kind: 'provider',
10
10
  id: 'bad-provider-provider',
11
11
  version: '0.1.0',
12
- description: 'provider whose note kind is missing the ui block',
12
+ description: 'provider whose markdown kind is missing the ui block',
13
13
  stability: 'experimental',
14
14
  explorationDir: '~/.bad',
15
15
  kinds: {
16
- note: {
17
- schema: './schemas/note.schema.json',
16
+ markdown: {
17
+ schema: './schemas/markdown.schema.json',
18
18
  schemaJson: {
19
- $id: 'urn:test:bad-provider/note',
19
+ $id: 'urn:test:bad-provider/markdown',
20
20
  type: 'object',
21
21
  additionalProperties: true,
22
22
  },
23
- defaultRefreshAction: 'bad-provider/summarize-note',
23
+ defaultRefreshAction: 'bad-provider/summarize-markdown',
24
24
  // NOTE: deliberately no `ui` — this is what the case asserts.
25
25
  },
26
26
  },
27
27
  async *walk() {},
28
28
  classify() {
29
- return 'note';
29
+ return 'markdown';
30
30
  },
31
31
  };
@@ -0,0 +1,12 @@
1
+ # Orphan sidecar — no sibling `agents/orphan.md` exists. The kernel walker
2
+ # discovers this via `discoverOrphanSidecars` and the built-in
3
+ # `core/annotation-orphan` rule emits one `warn` issue per orphan.
4
+
5
+ for:
6
+ path: agents/orphan.md
7
+ bodyHash: '1111111111111111111111111111111111111111111111111111111111111111'
8
+ frontmatterHash: '1111111111111111111111111111111111111111111111111111111111111111'
9
+
10
+ annotations:
11
+ version: 1
12
+ stability: deprecated
@@ -0,0 +1,8 @@
1
+ ---
2
+ name: stale-agent
3
+ description: A node with a deliberately stale sidecar — body hash in .sm does not match the live body, so the kernel must emit annotation-stale.
4
+ ---
5
+
6
+ # Stale agent
7
+
8
+ This body has been edited since the sidecar was last bumped. The sidecar's `for.bodyHash` will not match the live sha256 of this content.
@@ -0,0 +1,20 @@
1
+ # End-to-end sidecar fixture (Step 9.6.6 conformance case sidecar-end-to-end).
2
+ # Hashes are intentionally placeholders that do NOT match the live body /
3
+ # frontmatter — the conformance runner asserts that the kernel emits an
4
+ # `annotation-stale` issue when the stored hashes drift from the live ones.
5
+ #
6
+ # `annotations.version: 7` exists so the assertion can verify that the
7
+ # `annotations:` block denormalises through the SQLite scan — the value
8
+ # survives a round-trip through `state_scan_nodes.annotations_json`.
9
+
10
+ for:
11
+ path: agents/stale.md
12
+ bodyHash: '0000000000000000000000000000000000000000000000000000000000000000'
13
+ frontmatterHash: '0000000000000000000000000000000000000000000000000000000000000000'
14
+
15
+ annotations:
16
+ version: 7
17
+ stability: stable
18
+ tags:
19
+ - conformance
20
+ - sidecar
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: code-reviewer
3
+ description: Reviews TypeScript code for clarity, type safety, and idiom drift before PR submission.
4
+ model: sonnet
5
+ tools:
6
+ - Read
7
+ - Grep
8
+ - Bash
9
+ ---
10
+
11
+ # Code reviewer
12
+
13
+ Walks the diff, flags type holes, suggests idiomatic refactors. Pairs with the local lint suite — never duplicates rules a linter already enforces.
14
+
15
+ ## When to invoke
16
+
17
+ After staging changes and before opening a PR. The reviewer reads the diff against `main`, plus any file the diff touches in full.
@@ -0,0 +1,53 @@
1
+ # Sample sidecar matching agent-example.md.
2
+ # Validates against:
3
+ # - https://skill-map.dev/spec/v0/sidecar.schema.json (root shape)
4
+ # - https://skill-map.dev/spec/v0/annotations.schema.json (annotations block)
5
+ # Hashes are sha256 over the kernel's canonical forms (body bytes; canonical YAML
6
+ # of the frontmatter via `js-yaml` dump with sortKeys+noCompatMode). Regenerate
7
+ # when agent-example.md changes (the fixture is meant to be drift-free).
8
+
9
+ for:
10
+ # Scope-root-relative — when this fixture is treated as its own
11
+ # mini-scope (the typical reading), the .md sits in the same
12
+ # directory as this .sm, so `for.path` is just the filename.
13
+ path: agent-example.md
14
+ bodyHash: 3dd7d0dff35d7aa882af1617e675f9100cb815ba6206826a9a8b929b9364f6ce
15
+ frontmatterHash: 271d1ef97087db79d7f3a6d3b27abf96bcc31852d9dbc756cfeb2417dfad55b6
16
+ resolvedAs:
17
+ provider: claude
18
+ kind: agent
19
+
20
+ annotations:
21
+ version: 1
22
+ stability: stable
23
+ authors:
24
+ - skill-map-team
25
+ - crystian
26
+ license: MIT
27
+ source: https://github.com/example/code-reviewer/blob/main/agent.md
28
+ sourceVersion: v1.0.0
29
+ tags:
30
+ - review
31
+ - typescript
32
+ - quality
33
+ hidden: false
34
+ docsUrl: https://skill-map.dev/examples/code-reviewer
35
+ requires:
36
+ - .skill-map/agents/diff-reader.md
37
+ related:
38
+ - .skill-map/agents/security-reviewer.md
39
+
40
+ settings: {}
41
+
42
+ audit:
43
+ createdAt: '2026-05-05T10:00:00Z'
44
+ createdBy: cli
45
+ lastBumpedAt: '2026-05-05T10:30:00Z'
46
+ lastBumpedBy: cli
47
+
48
+ # Plugin-namespaced block (default location for plugin annotation contributions).
49
+ # This is illustrative — no `example-plugin` exists. Plugins may write here without
50
+ # coordination; the schema permits unknown root keys via additionalProperties: true.
51
+ example-plugin:
52
+ reviewedBy: agent-example
53
+ lastReviewedAt: 2026-05-05T10:30:00Z
package/db-schema.md CHANGED
@@ -72,13 +72,12 @@ One row per detected node, matching [`schemas/node.schema.json`](./schemas/node.
72
72
  | Column | Type | Constraint | Notes |
73
73
  |---|---|---|---|
74
74
  | `path` | TEXT | PRIMARY KEY | Relative path from scope root. Canonical node identifier. |
75
- | `kind` | TEXT | NOT NULL | Open-by-design (`node.schema.json#/properties/kind`): the value is whatever the classifying Provider declares. Built-in Claude Provider catalog: `skill` / `agent` / `command` / `hook` / `note`. External Providers MAY emit their own. |
75
+ | `kind` | TEXT | NOT NULL | Open-by-design (`node.schema.json#/properties/kind`): the value is whatever the classifying Provider declares. Built-in Claude Provider catalog: `skill` / `agent` / `command` / `markdown` (the last is the format-named generic fallback). External Providers MAY emit their own. |
76
76
  | `provider` | TEXT | NOT NULL | Provider extension id. |
77
77
  | `title` | TEXT | NULL | |
78
78
  | `description` | TEXT | NULL | |
79
79
  | `stability` | TEXT | CHECK in (`experimental`, `stable`, `deprecated`) OR NULL | Denormalized from frontmatter. |
80
80
  | `version` | TEXT | NULL | Denormalized from frontmatter. |
81
- | `author` | TEXT | NULL | Denormalized. |
82
81
  | `frontmatter_json` | TEXT | NOT NULL | Full parsed frontmatter as JSON. |
83
82
  | `body_hash` | TEXT | NOT NULL | sha256, hex. |
84
83
  | `frontmatter_hash` | TEXT | NOT NULL | sha256, hex. |
@@ -335,6 +334,21 @@ Shared key-value store for plugins that declared storage mode `kv`. See [`plugin
335
334
 
336
335
  Primary key: `(plugin_id, node_id, key)` with `node_id` using a sentinel empty string when NULL to satisfy PK constraints on engines that reject NULL in PK columns. Indexes: `ix_state_plugin_kvs_plugin_id`.
337
336
 
337
+ ### `state_node_favorites`
338
+
339
+ Per-node "favorite" flag set by the local user from the UI. The set is small (typical projects pin a handful of skills/agents/commands), so the table degenerates to one row per favorited node — absence of a row means "not favorited". Exists in zone `state_` because it is user-authored preference, not regenerable scan output: it must survive `sm scan` truncation and `sm db reset` (which drops only `scan_*`).
340
+
341
+ | Column | Type | Constraint |
342
+ |---|---|---|
343
+ | `node_path` | TEXT | PRIMARY KEY |
344
+ | `favorited_at` | INTEGER | NOT NULL | Unix milliseconds when the user marked the node. |
345
+
346
+ No indexes (PK already covers lookup by path; the table is keyed-by-path exclusively).
347
+
348
+ `node_path` is FK-semantic to `scan_nodes.path`. Per `§ Rename detection` below, the rename heuristic MUST migrate rows in this table when a path is renamed (same protocol as `state_jobs` / `state_summaries` / `state_enrichments` / `state_plugin_kvs`). A simple PK update suffices — there is no composite key, so collisions cannot occur (the destination path either has a row already, in which case the migrating row is dropped to preserve the live one, or it does not).
349
+
350
+ The BFF's `/api/nodes` route loads the full set of favorited paths once per request (`SELECT node_path FROM state_node_favorites`) and decorates each emitted `Node` with a derived `isFavorite` boolean by Set membership — no SQL JOIN against `scan_nodes` is required, and the table participates in zero of the per-scan persistence transactions.
351
+
338
352
  ---
339
353
 
340
354
  ## Table catalog: zone `config_`
@@ -453,7 +467,7 @@ Backups include `state_*` + `config_*` only; `scan_*` is regenerated after resto
453
467
 
454
468
  ## Rename detection (automatic)
455
469
 
456
- `scan_nodes.path` is the canonical node identifier in v0. Moving a file therefore rewrites the primary key, which would orphan every `state_*` row referencing the old path (`state_executions.node_ids_json`, `state_jobs.node_id`, `state_summaries.node_id`, `state_enrichments.node_id`).
470
+ `scan_nodes.path` is the canonical node identifier in v0. Moving a file therefore rewrites the primary key, which would orphan every `state_*` row referencing the old path (`state_executions.node_ids_json`, `state_jobs.node_id`, `state_summaries.node_id`, `state_enrichments.node_id`, `state_plugin_kvs.node_id`, `state_node_favorites.node_path`).
457
471
 
458
472
  Implementations MUST apply a rename heuristic at scan time **before** committing the new scan transaction:
459
473
 
package/index.json CHANGED
@@ -47,6 +47,14 @@
47
47
  {
48
48
  "id": "history-stats",
49
49
  "path": "schemas/history-stats.schema.json"
50
+ },
51
+ {
52
+ "id": "sidecar",
53
+ "path": "schemas/sidecar.schema.json"
54
+ },
55
+ {
56
+ "id": "annotations",
57
+ "path": "schemas/annotations.schema.json"
50
58
  }
51
59
  ],
52
60
  "extensions": [
@@ -166,53 +174,63 @@
166
174
  }
167
175
  ]
168
176
  },
169
- "specPackageVersion": "0.16.0",
177
+ "specPackageVersion": "0.18.0",
170
178
  "integrity": {
171
179
  "algorithm": "sha256",
172
180
  "files": {
173
- "CHANGELOG.md": "f2dce379783e8e786304c559f0b109226e587543c31cfb25df4ca63e65e2860f",
174
- "README.md": "97fd3079092182c677669c25f44e08b0f6579faaa3406d8cb5a884e37e9eef97",
175
- "architecture.md": "c14e69faa7ce7f657d6a2790762daaea8a5ff350375de8c254cd870b5494b896",
176
- "cli-contract.md": "e94340f25e781a1131d0902cf368a3675795bf71ffb419e1af789ff8992897aa",
181
+ "CHANGELOG.md": "5cff1602344521d5266635f89d3e778362b7a7b386983b776dfaf6f44125ae83",
182
+ "README.md": "3ffd34a0d55c76d53777d47244c727e2262bf5d8bcde21035e9ec0154640cee7",
183
+ "architecture.md": "ed307b35a0a1cd321c2ecaccd4d77943809cd926d3d39ff9f25123446e610026",
184
+ "cli-contract.md": "8e88126a5dc146e9928141eae61b58348fb9563a7d1e41064a8711810234f1ba",
177
185
  "conformance/README.md": "5f94a6ac637b7c992fcd7e53d32eed1b8887eeef05eb6ca3b5ec8a0b5045cd21",
178
186
  "conformance/cases/kernel-empty-boot.json": "ad4bbe9d637537625025c8bdb61285b1433568a2544b1ce0248f304ccff8c350",
179
- "conformance/cases/plugin-missing-ui-rejected.json": "c6ce8f62a430d662aea33dec8ebf6493be6455037be3114e0d93d0eb57777287",
180
- "conformance/coverage.md": "35bac01af3a922ce580deb1f83fc890c668d3d3cf93747f5097340a09303eb43",
187
+ "conformance/cases/plugin-missing-ui-rejected.json": "192a6f13b7d492e843f5ac3294a03a47585061b36ff41e1d80e5e836cf15e854",
188
+ "conformance/cases/sidecar-end-to-end.json": "dc3409d7bf4ec1d89d458ec49cdf843ca4931f910493e2d4a1939603cf786132",
189
+ "conformance/coverage.md": "1bd1f6e6e681ed23f02a4dad0ad2c23cf9ed2f76bbc647fb229cdec6aed3c945",
181
190
  "conformance/fixtures/plugin-missing-ui/.skill-map/plugins/bad-provider/plugin.json": "4d78af6f12faa9d131e2a19f1dbb8f250baacc525978f3a8c858932b95da4ff6",
182
- "conformance/fixtures/plugin-missing-ui/.skill-map/plugins/bad-provider/provider.js": "da40b134d70f8bc8175cfa9c380ecb55d26b2240c8b467f22f3fcfab750c8747",
191
+ "conformance/fixtures/plugin-missing-ui/.skill-map/plugins/bad-provider/provider.js": "d1f4898b43201d24f048171ce84d433b68694457452fbc64498857f5da3e9bbb",
183
192
  "conformance/fixtures/plugin-missing-ui/notes/example.md": "55767f0aa1b6774546a99f28c58e7b732aa9cfa5dfce8d0326470f7f622f577e",
184
193
  "conformance/fixtures/preamble-v1.txt": "1e0aeef224b64477bdc13a949c3ad402e68249caf499ecdba1302371677c068b",
185
- "db-schema.md": "730b423d844d7b10aecaec59a78f254b600f0e16b9300305c49aa8c01d03d68f",
194
+ "conformance/fixtures/sidecar-end-to-end/.claude/agents/orphan.sm": "53448213a36fe1b645903c025ddaeefc73ad58f41d2d370a7be84e5e0c964154",
195
+ "conformance/fixtures/sidecar-end-to-end/.claude/agents/stale.md": "cb3a95777cba530d47e6040c5601b6dcd34b5fc653dd69f183369eb6bdd956b5",
196
+ "conformance/fixtures/sidecar-end-to-end/.claude/agents/stale.sm": "50b7776099587decbb9f3b243d1314c7e10da8a9d0f0182043c692b637bf630e",
197
+ "conformance/fixtures/sidecar-example/agent-example.md": "1343ebc245e1fde415c16320ce53bcfad366ae2be1bcc7084337cfceaacdad38",
198
+ "conformance/fixtures/sidecar-example/agent-example.sm": "054aca442f9a11b02e5bb38d2847fe8730df1fb5f48e3d296af1592e902a7232",
199
+ "db-schema.md": "1e6480cf312e957619860fda9be9c2bf38de3392ef71eef94e755f987b667a55",
186
200
  "interfaces/security-scanner.md": "4a982667008f233656f44c61ce9948e062432d3debdcbf7a134da03bd4139d7d",
187
201
  "job-events.md": "8f371e0991816eca2e1a55cbd8a50733546ca5e7c861588048c18be1d22dbd57",
188
202
  "job-lifecycle.md": "12bfc27690c92cf93682a3b6fbfeb7e2d252d33f704fd2d7de9a13db713e6281",
189
- "plugin-author-guide.md": "ada6af03cc26439fd890efc9fa6939d1757f4625ea88f4be6f5e07497244f42e",
203
+ "plugin-author-guide.md": "f92e2d767e30bd6da36922105380475ae55831f64106f55b07f27a74389f138e",
190
204
  "plugin-kv-api.md": "04b2178f46fb88adeae9240df9c9e1761b660396072001dac32cd402e11a2d7d",
191
205
  "prompt-preamble.md": "fb40ab510234383326f198dec82cd6d744f28b7432eebac6cbfbb7ca1c483b7d",
192
- "schemas/api/rest-envelope.schema.json": "7d9d74bcb2158019cb6e30306d40b9c7ffc67e9d202fb8210fe4e4a9e8fa4dab",
206
+ "schemas/annotations.schema.json": "a500d61b638b013bc96e51530617e32c043a6e4f04ea39cc2f86bf355fef6fb0",
207
+ "schemas/api/rest-envelope.schema.json": "af631d809a98f9077d25cb5eb5a09d41d7d86d0e553c7ddbc3b3de275d782c2f",
208
+ "schemas/bump-report.schema.json": "c2d853715d5f50098567bc23382a4e81baf78d589c6e1baf67d3b841e7f7d8ae",
193
209
  "schemas/conformance-case.schema.json": "7cd0f3aae5124f24be57cddb213d002d0466f79d06fd3da896075c8b28650410",
194
210
  "schemas/execution-record.schema.json": "9628fa557cb856402f3a5f1d1167c609e46a197c850fe8171abfddd46c1028a8",
195
211
  "schemas/extensions/action.schema.json": "262272175c06a2e33c08f819a45c3ef8260276c91a9d0542fdffc932aeb32db7",
196
- "schemas/extensions/base.schema.json": "e5c3406b88b0496a89791890b05083929429319d96b1f8cea0bec3ec9e3de8af",
212
+ "schemas/extensions/base.schema.json": "1e1ceed58e73155f79128f5f002a0b40982e20ace1f4618d4c5e4098af83aa82",
197
213
  "schemas/extensions/extractor.schema.json": "122d3f81ef91edcde9798e7dc8fcbf442a2996deea65aa4b03c9d5cb01ba2519",
198
214
  "schemas/extensions/formatter.schema.json": "2ab092aa37ae349c69b93071ed4f0e131affb7bb5799516ca82c721262631b36",
199
215
  "schemas/extensions/hook.schema.json": "7465c38e0765edf23e49d4f96c539d04323f1cf564af1c60ee637c79a6d39239",
200
- "schemas/extensions/provider.schema.json": "518d7666841cfef8eb28aab788a6e6dfabf9e12f3f06de1c81be915cbe6c1088",
216
+ "schemas/extensions/provider.schema.json": "599b6dabe26c65f67a53fb1ea59124bbbf52d3337a29303b6b0292a4ad58eb1a",
201
217
  "schemas/extensions/rule.schema.json": "8ff420bde498f50db114c352305d487c71aef2dd746fd0c24976ff6a09865c22",
202
- "schemas/frontmatter/base.schema.json": "dfee192458765b8cb872ef9e7145ec31b9e07ceb19ee44be48af2172329e7a38",
218
+ "schemas/frontmatter/base.schema.json": "88d906ec15e94543b919dd22b4ea4053e40024dc14068e6c7df09662db2f3350",
203
219
  "schemas/history-stats.schema.json": "23f472d1de06d23fc775aabba821f8375f347af4dc8d89ba567980d61a11f9de",
204
220
  "schemas/issue.schema.json": "40f6f8abadcce0fd8eac9df27ffcc20b2fc9fda6970142ddb8e7e56b1760b9b1",
205
221
  "schemas/job.schema.json": "ffbdd51c54b487c44eb57fabd07f624ac1030c14ef69b46933c154092853a84c",
206
222
  "schemas/link.schema.json": "0a95a24849a38b9ef5bad5361519a9f9e012b5bc3001289fad29d0851fceff6b",
207
- "schemas/node.schema.json": "646f2b2c266b5bf1c61b9933c6defdb05565db044a766c4c575c0dcd1e68df57",
223
+ "schemas/node.schema.json": "dea07210a2b97df7f3e5d1f7aa29a0b426ef276c7606e6c2f83192c4954a92c3",
208
224
  "schemas/plugins-registry.schema.json": "5ca1d4970ae64f064f05c237a649d9f82d5edbeb7c121ec50cb4aaca13f4bc51",
209
225
  "schemas/project-config.schema.json": "923aca314c40b3d0026bdf61a5c926a97170cd88e35627c51056e55b5ca6a6e4",
226
+ "schemas/report-base-deterministic.schema.json": "6f8b38c097994ee87e0639935c42b5e85d8ea4244959ca397978171b0d7d2222",
210
227
  "schemas/report-base.schema.json": "a1021e9a59b4df9f99cd92454d797e88469766e7d49f52d231c4645ffdfdad8f",
211
228
  "schemas/scan-result.schema.json": "d1a8782e198bc9bb92dad247437aefa1b02f92ff8dca8562eaf2348fd7c5cf0c",
229
+ "schemas/sidecar.schema.json": "418fe79eecd517df685e202b5ef29cf633256fd9f2d39130cf59033a30d1b270",
212
230
  "schemas/summaries/agent.schema.json": "3d22558eeb170e00c4fc32018a810d27333cc632c9e528ff386100cfdfded087",
213
231
  "schemas/summaries/command.schema.json": "2bffd606b24f7df9ccd13890af8725adfbfb8a2d7782fee1e0ac5250b9059117",
214
232
  "schemas/summaries/hook.schema.json": "36f876f3b1a60d45be97a0848c79fd18744b434dfdcefc366f033b253d56268c",
215
- "schemas/summaries/note.schema.json": "ae510f3ee1b6092c1061625e425c9bb7de9c9caa3f3774770c148f658d505753",
233
+ "schemas/summaries/markdown.schema.json": "df8a4a883fc6674cbae93cbefffa3b115e00c0dd490e113e42e65a09cb0865d2",
216
234
  "schemas/summaries/skill.schema.json": "f01bab92c51d64ee23e61587e42cf0dc5b37a2f518f5b12b3d1d456390338aa8",
217
235
  "versioning.md": "996e62006423edc01151a6f7869605f76c5e1454cc30b38d9f616925b5bcfb64"
218
236
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skill-map/spec",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
4
4
  "description": "JSON Schemas, prose contracts, and conformance suite for the skill-map specification.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -44,6 +44,13 @@
44
44
  "conformance/",
45
45
  "index.json"
46
46
  ],
47
+ "scripts": {
48
+ "spec": "node scripts/build-index.js",
49
+ "spec:check": "node scripts/build-index.js --check && node scripts/check-coverage.js",
50
+ "pin": "node scripts/sync-pin.js",
51
+ "pin:check": "node scripts/sync-pin.js --check",
52
+ "validate": "npm run spec:check && npm run pin:check"
53
+ },
47
54
  "publishConfig": {
48
55
  "access": "public"
49
56
  }