@skill-map/spec 0.17.0 → 0.19.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 +672 -0
- package/README.md +1 -1
- package/architecture.md +281 -16
- package/cli-contract.md +122 -6
- package/conformance/cases/orphan-markdown-fallback.json +22 -0
- package/conformance/cases/plugin-missing-ui-rejected.json +4 -1
- package/conformance/cases/sidecar-end-to-end.json +25 -0
- package/conformance/coverage.md +9 -3
- package/conformance/fixtures/orphan-markdown/.claude/agents/reviewer.md +6 -0
- package/conformance/fixtures/orphan-markdown/ARCHITECTURE.md +10 -0
- package/conformance/fixtures/plugin-missing-ui/.skill-map/plugins/bad-provider/provider.js +6 -6
- package/conformance/fixtures/sidecar-end-to-end/.claude/agents/orphan.sm +12 -0
- package/conformance/fixtures/sidecar-end-to-end/.claude/agents/stale.md +8 -0
- package/conformance/fixtures/sidecar-end-to-end/.claude/agents/stale.sm +20 -0
- package/conformance/fixtures/sidecar-example/agent-example.md +17 -0
- package/conformance/fixtures/sidecar-example/agent-example.sm +53 -0
- package/db-schema.md +73 -15
- package/index.json +42 -19
- package/package.json +1 -1
- package/plugin-author-guide.md +426 -27
- package/schemas/annotations.schema.json +75 -0
- package/schemas/api/rest-envelope.schema.json +159 -46
- package/schemas/bump-report.schema.json +29 -0
- package/schemas/extensions/base.schema.json +36 -1
- package/schemas/extensions/extractor.schema.json +3 -10
- package/schemas/extensions/provider.schema.json +23 -1
- package/schemas/frontmatter/base.schema.json +6 -1
- package/schemas/input-types.schema.json +260 -0
- package/schemas/node.schema.json +36 -23
- package/schemas/plugins-registry.schema.json +14 -2
- package/schemas/project-config.schema.json +11 -0
- package/schemas/report-base-deterministic.schema.json +15 -0
- package/schemas/sidecar.schema.json +96 -0
- package/schemas/summaries/{note.schema.json → markdown.schema.json} +5 -5
- package/schemas/view-contracts.schema.json +298 -0
package/cli-contract.md
CHANGED
|
@@ -198,8 +198,8 @@ Keys are dot-paths (`jobs.minimumTtlSeconds`, `scan.tokenize`). Unknown keys →
|
|
|
198
198
|
| `sm scan --watch` | Long-running: watch the roots and trigger an incremental scan after each debounced batch of filesystem events. Alias of `sm watch`. |
|
|
199
199
|
| `sm scan compare-with <dump> [roots...]` | Delta report: run a fresh scan in memory and compare against the saved `ScanResult` dump at `<dump>`. Read-only — does not modify the DB. Exit `0` on empty delta, `1` on any drift, `2` on operational error (missing or malformed dump, schema violation). |
|
|
200
200
|
| `sm watch [roots...]` | Long-running watcher. Same semantics as `sm scan --watch`, exposed as a top-level verb because the watcher is a loop, not a one-shot scan. |
|
|
201
|
-
| `sm refresh <node.path>` | Re-run Extractors against a single node and upsert their outputs into the universal enrichment layer (`node_enrichments`, see [`db-schema.md`](./db-schema.md#node_enrichments)).
|
|
202
|
-
| `sm refresh --stale` | Batch form of `sm refresh <node>` — refreshes every node carrying at least one stale
|
|
201
|
+
| `sm refresh <node.path>` | Re-run Extractors against a single node and upsert their outputs into the universal enrichment layer (`node_enrichments`, see [`db-schema.md`](./db-schema.md#node_enrichments)). Extractors are deterministic-only — they run synchronously and persist. Exit `0` on success, `2` on failure, `5` if the node is not in the persisted scan. |
|
|
202
|
+
| `sm refresh --stale` | Batch form of `sm refresh <node>` — refreshes every node carrying at least one stale enrichment row. With Extractors deterministic-only, the stale set is empty in this revision (Extractor writes never set `stale = 1`) so `--stale` always exits `0` with a "nothing to do" advisory. The verb is preserved for the future Action-prob enrichment revision (see [`architecture.md` §Extractor · enrichment layer](./architecture.md#extractor--enrichment-layer)) where queued LLM jobs will populate stale rows. |
|
|
203
203
|
|
|
204
204
|
`--json` output conforms to `schemas/scan-result.schema.json`. `sm watch` (and `sm scan --watch`) emit one ScanResult per batch — under `--json` this is an `ndjson` stream of ScanResult documents.
|
|
205
205
|
|
|
@@ -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:` 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
|
+
identity:
|
|
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
|
+
identity:
|
|
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
|
|
@@ -351,16 +460,19 @@ The reference implementation ships a Hono BFF rooted at `src/server/`. One Node
|
|
|
351
460
|
|
|
352
461
|
| Path | Status | Shape |
|
|
353
462
|
|---|---|---|
|
|
354
|
-
| `GET /api/health` | implemented | `{ ok: true, schemaVersion, specVersion, implVersion, scope: 'project'\|'global', db: 'present'\|'missing' }` |
|
|
463
|
+
| `GET /api/health` | implemented | `{ ok: true, schemaVersion, specVersion, implVersion, scope: 'project'\|'global', db: 'present'\|'missing', cwd: string, dbPath: string }`. `cwd` is the absolute project root the BFF resolves against (`runtimeContext.cwd`); `dbPath` is the absolute project DB path (`IServerOptions.dbPath`). Both are surfaced so the SPA's About panel can show "you are looking at <project>" + the DB location without a second endpoint. |
|
|
355
464
|
| `GET /api/scan` | implemented | latest persisted `ScanResult` (1:1 with `scan-result.schema.json`; byte-equal to `sm scan --json` modulo whitespace). DB absent → empty `ScanResult` shape (zero `nodes` / `links` / `issues`). |
|
|
356
465
|
| `GET /api/scan?fresh=1` | implemented | runs an in-memory scan and returns the produced `ScanResult` without persistence. Rejects with `bad-query` (400) when the server was started with `--no-built-ins` or `--no-plugins` (would yield empty / partial results). |
|
|
466
|
+
| `POST /api/scan` | implemented | Run a fresh scan **and persist it** through the same `runScanWithRenames` + `persistScanResult` pipeline the watcher uses. Body is empty (`{}` or no body). Response: the persisted `ScanResult` inline (same shape as `GET /api/scan`). Side effects: broadcasts `scan.started` then `scan.completed` over `/ws` so other connected clients can refresh — the per-batch sequence is identical to a watcher-driven batch. **Concurrency**: only one scan may run at a time across the whole BFF process. A POST that arrives while a watcher batch is in flight (or while another POST is in flight) is rejected with `409 scan-busy` so the caller can decide whether to retry. **Pipeline gate**: rejected with `400 bad-query` when the server was started with `--no-built-ins` or `--no-plugins` (a partial pipeline would persist a misleading DB the next watcher boot would have to reconcile). **DB gate**: rejected with `500 db-missing` when the project DB file is absent — the read-side `/api/scan` degrades to the empty shape, but a write path cannot, so it fails fast. |
|
|
357
467
|
| `GET /api/nodes?kind=&hasIssues=&path=&limit=&offset=` | implemented | `RestEnvelope` (`kind: 'nodes'`) — paginated, filtered list. Filters share the `kind=` / `has=issues` / `path=<glob>` grammar with `sm export`. `hasIssues=false` is a server-side post-filter (not representable in the kernel grammar). Pagination defaults `offset=0`, `limit=100`; max `limit=1000`. |
|
|
358
468
|
| `GET /api/nodes/:pathB64[?include=body]` | implemented | Single-node detail envelope: `{ schemaVersion, kind: 'node', item: Node, links: { incoming: Link[], outgoing: Link[] }, issues: Issue[] }`. `:pathB64` is base64url (RFC 4648 §5, no padding) of `node.path`. Missing node or malformed `pathB64` → 404 `not-found`. **`?include=body`** (Step 14.5.a) — opt-in flag that adds `item.body: string \| null` to the response. The body is read from disk on demand at request time (the kernel persists `bodyHash` only). `null` indicates the source file was missing / unreadable when the request landed (the watcher will re-emit `scan.completed` when it catches up). Without the flag, `item.body` is `undefined` and the handler does not touch the filesystem. |
|
|
359
469
|
| `GET /api/links?kind=&from=&to=` | implemented | `RestEnvelope` (`kind: 'links'`) — list of links. Filters: `kind` (CSV whitelist of `link.kind`), `from` (exact match on `link.source`), `to` (exact match on `link.target`). No pagination at v14.2. |
|
|
360
470
|
| `GET /api/issues?severity=&ruleId=&node=` | implemented | `RestEnvelope` (`kind: 'issues'`) — list of issues. Filters: `severity` (CSV from `error\|warn\|info`), `ruleId` (CSV; qualified or short suffix per `sm check --rules`), `node` (filter to issues whose `nodeIds` includes the path). No pagination at v14.2. |
|
|
361
471
|
| `GET /api/graph?format=ascii\|json\|md` | implemented | formatter-rendered graph. `Content-Type` per format: `text/plain` (ascii), `application/json` (json), `text/markdown` (md / mermaid). Default `format=ascii`. Unknown format → 400 `bad-query`. |
|
|
362
472
|
| `GET /api/config` | implemented | `RestEnvelope` (`kind: 'config'`) — merged effective config (defaults → user → user-local → project → project-local → override). |
|
|
363
|
-
| `GET /api/plugins` | implemented | `RestEnvelope` (`kind: 'plugins'`) — list of installed plugins (built-in + drop-in) with status. Item shape: `{ id, version, kinds, status, reason, source: 'built-in'\|'project'\|'global' }`. |
|
|
473
|
+
| `GET /api/plugins` | implemented | `RestEnvelope` (`kind: 'plugins'`) — list of installed plugins (built-in + drop-in) with status. Item shape: `{ id, version, kinds, status, reason, source: 'built-in'\|'project'\|'global', granularity: 'bundle'\|'extension', description?: string, extensions?: Array<{ id, kind, version, enabled, description?: string }> }`. The `granularity` field reflects the manifest declaration (built-ins: hardcoded per `built-in-plugins/built-ins.ts`; drop-ins: from `plugin.json#/granularity`, default `'bundle'`). The `description` field on the bundle item carries the manifest-declared description (built-ins: hardcoded on `IBuiltInBundle`; drop-ins: `plugin.json#/description`); each `extensions[]` entry carries its extension manifest's `description` per `IExtensionBase` (`extensions/base.schema.json#/properties/description`). The SPA's Settings list renders the descriptions as muted secondary text and includes them in its substring-search index alongside the ids. The `extensions` array is present **only** when `granularity === 'extension'` AND the plugin loaded successfully; each entry's `enabled` reflects the per-extension override resolution (DB > settings.json > installed default). For `granularity: 'bundle'` plugins the array is omitted (the bundle is the only toggle-able key). |
|
|
474
|
+
| `PATCH /api/plugins/:id` | implemented | Toggle one plugin's user override. `:id` MUST be a top-level bundle id; qualified-id form (`bundle/extension`) is the sibling route below. Body `{ enabled: boolean }` (JSON). Persists to `config_plugins` via `IConfigPluginsPort.set` — same path the CLI's `sm plugins enable\|disable` uses. Response is the canonical `{ id, version, kinds, status, reason, source }` row for the affected plugin (post-write `status` reflects the new override resolution). **Granularity** — rejected with 400 `bad-query` when the target bundle declares `granularity: 'extension'` (only the qualified-id form is toggle-able for those). **Restart required** — the loaded plugin runtime is boot-cached; the new value applies on the next `sm scan` or `sm serve` restart. The endpoint does NOT broadcast a WS event today. |
|
|
475
|
+
| `PATCH /api/plugins/:bundleId/extensions/:extensionId` | implemented | Qualified-id form for `granularity: 'extension'` bundles (today: `core` + any user plugin that opts in). Body `{ enabled: boolean }`. Both segments are URL-path-segment-encoded (no slash inside `:bundleId` or `:extensionId`). Rejected with 400 `bad-query` when the target bundle declares `granularity: 'bundle'` (use the sibling route above). Same persistence + restart-required semantics as the bundle form. |
|
|
364
476
|
| `ALL /api/*` (other) | reserved | structured 404 envelope (see below); future endpoints land in subsequent sub-steps. |
|
|
365
477
|
| `GET /ws` | implemented (v14.4.a) | accepts WebSocket upgrade and registers the client with the BFF broadcaster. Server-push only — the server fans `scan.*` (and forthcoming `issue.*`) events to every connected client. See **WebSocket protocol** below. |
|
|
366
478
|
| `GET *` | implemented | static asset from the resolved UI bundle, falling back to `index.html` for SPA deep links. |
|
|
@@ -382,14 +494,18 @@ List endpoints conform to [`schemas/api/rest-envelope.schema.json`](schemas/api/
|
|
|
382
494
|
}
|
|
383
495
|
```
|
|
384
496
|
|
|
385
|
-
HTTP status mapping: `400` → `bad-query`, `404` → `not-found`, `500` → `internal` / `db-missing`.
|
|
497
|
+
HTTP status mapping: `400` → `bad-query`, `404` → `not-found`, `409` → `sidecar-fresh` (`POST /api/sidecar/bump`) or `scan-busy` (`POST /api/scan`), `500` → `internal` / `db-missing`.
|
|
386
498
|
|
|
387
499
|
Error code sources at v14.2:
|
|
388
500
|
|
|
389
501
|
- `not-found` (404) — unknown `/api/*` path; missing node on `/api/nodes/:pathB64`; malformed `pathB64` (treated as "no such node" so the client UX is uniform).
|
|
390
502
|
- `bad-query` (400) — `ExportQueryError` from `parseExportQuery`; pagination beyond `limit ≤ 1000`; non-integer / negative `limit` / `offset`; unknown formatter on `/api/graph`; `?fresh=1` when the server started with `--no-built-ins` or `--no-plugins`.
|
|
391
503
|
- `internal` (500) — uncaught error during a request (e.g. config-load failure, DB corruption surfacing through `loadScanResult`).
|
|
392
|
-
- `db-missing` (500) —
|
|
504
|
+
- `db-missing` (500) — emitted by mutation endpoints (`PATCH /api/plugins/:id`, `PATCH /api/plugins/:bundleId/extensions/:extensionId`) when the project DB is absent. Read-side routes uniformly degrade to the empty shape (`/api/scan`) or zero items (list endpoints) so they do not emit this code; mutation endpoints cannot persist without a DB so they fail fast instead of silently dropping the write.
|
|
505
|
+
- `not-found` (404) on `PATCH /api/plugins/:id` — unknown plugin id (no built-in bundle, no discovered drop-in matches). The qualified-id form returns the same code when either segment misses.
|
|
506
|
+
- `bad-query` (400) on `PATCH /api/plugins/:id` — granularity mismatch (bundle-level call against a `granularity: 'extension'` bundle, or qualified-id call against a `granularity: 'bundle'` bundle), malformed body (missing `enabled`, wrong type), unknown extension id under a known bundle.
|
|
507
|
+
- `bad-query` (400) on `POST /api/scan` — the server was started with `--no-built-ins` or `--no-plugins` (partial pipeline would persist a misleading DB).
|
|
508
|
+
- `scan-busy` (409) on `POST /api/scan` — another scan (a watcher batch or another POST) is already in flight. Retry once the in-flight scan resolves; the WS `scan.completed` envelope is the unambiguous "now safe" signal.
|
|
393
509
|
|
|
394
510
|
**Flag surface**:
|
|
395
511
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://skill-map.dev/spec/v0/conformance-case.schema.json",
|
|
3
|
+
"id": "orphan-markdown-fallback",
|
|
4
|
+
"description": "spec 0.18.0 universal markdown fallback. A `.md` file no vendor-specific Provider classifies (e.g. `ARCHITECTURE.md` at the project root) MUST be picked up by the built-in `core/markdown` Provider, classified as kind `markdown`, and attributed to the `markdown` provider id. The orchestrator's path-dedup ensures vendor Providers retain priority on files inside their territory (`.claude/agents/reviewer.md` here stays with `claude` as `agent`). Locks the contract that markdown is provider-agnostic and the kernel emits no privileged kinds.",
|
|
5
|
+
"fixture": "orphan-markdown",
|
|
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": 2 },
|
|
14
|
+
{ "type": "json-path", "path": "$.stats.issuesCount", "equals": 0 },
|
|
15
|
+
{ "type": "json-path", "path": "$.nodes[0].path", "equals": ".claude/agents/reviewer.md" },
|
|
16
|
+
{ "type": "json-path", "path": "$.nodes[0].kind", "equals": "agent" },
|
|
17
|
+
{ "type": "json-path", "path": "$.nodes[0].provider", "equals": "claude" },
|
|
18
|
+
{ "type": "json-path", "path": "$.nodes[1].path", "equals": "ARCHITECTURE.md" },
|
|
19
|
+
{ "type": "json-path", "path": "$.nodes[1].kind", "equals": "markdown" },
|
|
20
|
+
{ "type": "json-path", "path": "$.nodes[1].provider", "equals": "markdown" }
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -10,8 +10,11 @@
|
|
|
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":
|
|
13
|
+
{ "type": "json-path", "path": "$.providers.length", "equals": 4 },
|
|
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" },
|
|
17
|
+
{ "type": "json-path", "path": "$.providers[3]", "equals": "markdown" },
|
|
15
18
|
{ "type": "json-path", "path": "$.nodes.length", "equals": 1 }
|
|
16
19
|
]
|
|
17
20
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
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 `identity.{bodyHash,frontmatterHash}`) plus an orphan sidecar (no sibling `.md`) MUST surface `sidecar.status` on the node 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].sidecar.present", "equals": true },
|
|
16
|
+
{ "type": "json-path", "path": "$.nodes[0].sidecar.status", "matches": "^stale-(body|frontmatter|both)$" },
|
|
17
|
+
{ "type": "json-path", "path": "$.nodes[0].sidecar.annotations.version", "equals": 7 },
|
|
18
|
+
{ "type": "json-path", "path": "$.stats.issuesCount", "equals": 2 },
|
|
19
|
+
{ "type": "json-path", "path": "$.issues[0].ruleId", "equals": "annotation-stale" },
|
|
20
|
+
{ "type": "json-path", "path": "$.issues[0].severity", "equals": "warn" },
|
|
21
|
+
{ "type": "json-path", "path": "$.issues[1].ruleId", "equals": "annotation-orphan" },
|
|
22
|
+
{ "type": "json-path", "path": "$.issues[1].severity", "equals": "warn" },
|
|
23
|
+
{ "type": "json-path", "path": "$.issues[1].data.expectedMdPath", "equals": ".claude/agents/orphan.md" }
|
|
24
|
+
]
|
|
25
|
+
}
|
package/conformance/coverage.md
CHANGED
|
@@ -11,19 +11,19 @@ This file is hand-maintained. A CI check before spec release compares the schema
|
|
|
11
11
|
| 1 | `node.schema.json` | `kernel-empty-boot` (indirect) | 🟡 partial | Empty-boot validates the zero-filled ScanResult shape end-to-end. Direct cases that exercise populated `Node` rows are Provider-specific and live in the Provider's own conformance suite (see `provider:claude` for `basic-scan`). |
|
|
12
12
|
| 2 | `link.schema.json` | — | 🔴 missing | Needs fixture with at least one `invokes` + `references` + `mentions` link, both `high`/`medium`/`low` confidence. |
|
|
13
13
|
| 3 | `issue.schema.json` | — | 🔴 missing | Needs fixture triggering `trigger-collision` + `broken-ref` + `superseded`. |
|
|
14
|
-
| 4 | `scan-result.schema.json` | `kernel-empty-boot` |
|
|
14
|
+
| 4 | `scan-result.schema.json` | `kernel-empty-boot`, `orphan-markdown-fallback` | 🟢 covered | Zero-filled case via empty-boot. `orphan-markdown-fallback` (spec 0.18.0) asserts a populated `ScanResult` over a multi-Provider corpus where one node lands via the universal `core/markdown` fallback and another via vendor-specific claude classification — locks the orchestrator's path-dedup contract. Populated rename / orphan cases live under `provider:claude` (`basic-scan` / `rename-high` / `orphan-detection`). |
|
|
15
15
|
| 5 | `execution-record.schema.json` | — | 🔴 missing | Blocked by Step 5 (history). Needs a case that runs a `deterministic` action and inspects `state_executions` via `sm history --json`. |
|
|
16
16
|
| 6 | `project-config.schema.json` | — | 🔴 missing | Case: init a scope, write a partial `.skill-map/settings.json` (optionally with a `.skill-map/settings.local.json` overlay), assert effective config after the layered merge. |
|
|
17
17
|
| 7 | `plugins-registry.schema.json` | — | 🔴 missing | Two sub-cases required: (a) `PluginManifest` validation via `sm plugins show --json`; (b) aggregate `PluginsRegistry` via `sm plugins list --json`. |
|
|
18
18
|
| 8 | `job.schema.json` | — | 🔴 missing | Blocked by Step 10 (job system). Needs a case that submits a local action (no LLM), inspects `sm job show --json`. |
|
|
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` |
|
|
21
|
+
| 11 | `frontmatter/base.schema.json` | `orphan-markdown-fallback` | 🟢 covered | Universal frontmatter shape — `name` + `description` only, `additionalProperties: true`. Per-kind schemas live with the Provider that emits them: vendor kinds (`skill` / `agent` / `command`) under `src/built-in-plugins/providers/{claude,gemini,agent-skills}/schemas/`; the format-named generic `markdown` kind under `src/built-in-plugins/providers/core-markdown/schemas/` (spec 0.18.0 — markdown is provider-agnostic). All extend this base via `$ref`-by-`$id`. `orphan-markdown-fallback` exercises base-only frontmatter end-to-end via the `ARCHITECTURE.md` fixture file (no kind-specific extras). |
|
|
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/
|
|
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,6 +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. |
|
|
40
|
+
| 30 | `view-contracts.schema.json` | — | 🔴 missing | Closed catalog of 10 view contracts + the `IViewContribution` manifest declaration shape + per-contract payload schemas. Cases required (3): (a) `plugin-view-contributions-valid` — a plugin manifest declaring contributions of every contract type validates; (b) `plugin-view-contributions-invalid-contract` — a manifest referencing a contract not in the catalog rejects with `invalid-manifest`; (c) `plugin-view-contributions-payload-mismatch` — an extractor emitting an off-contract payload triggers `extension.error` and drops silently. Implementation lands with the kernel surface in Phase 2 of the UI contributions plan; conformance fixtures land alongside. |
|
|
41
|
+
| 31 | `input-types.schema.json` | — | 🔴 missing | Closed catalog of 10 input-types for plugin settings + the `ISettingDeclaration` discriminated-union manifest shape. Cases required (2): (a) `plugin-settings-valid` — a plugin manifest declaring at least one setting of each input-type validates; (b) `plugin-settings-invalid-type` — a manifest referencing a `type` not in the catalog rejects with `invalid-manifest`. Lands together with the spec/CLI surface for `sm plugins config <id>`. |
|
|
36
42
|
|
|
37
43
|
> **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
44
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: architecture
|
|
3
|
+
description: Top-level markdown that no vendor Provider claims. Picked up by core/markdown's universal fallback classify.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
This file lives at the project root with no platform-specific path
|
|
7
|
+
prefix. The claude / gemini / agent-skills Providers all return null
|
|
8
|
+
on it; the built-in `core/markdown` Provider claims it as kind
|
|
9
|
+
`markdown`. Without the universal fallback it would be silently
|
|
10
|
+
dropped from the scan.
|
|
@@ -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
|
|
12
|
+
description: 'provider whose markdown kind is missing the ui block',
|
|
13
13
|
stability: 'experimental',
|
|
14
14
|
explorationDir: '~/.bad',
|
|
15
15
|
kinds: {
|
|
16
|
-
|
|
17
|
-
schema: './schemas/
|
|
16
|
+
markdown: {
|
|
17
|
+
schema: './schemas/markdown.schema.json',
|
|
18
18
|
schemaJson: {
|
|
19
|
-
$id: 'urn:test:bad-provider/
|
|
19
|
+
$id: 'urn:test:bad-provider/markdown',
|
|
20
20
|
type: 'object',
|
|
21
21
|
additionalProperties: true,
|
|
22
22
|
},
|
|
23
|
-
defaultRefreshAction: 'bad-provider/summarize-
|
|
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 '
|
|
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
|
+
identity:
|
|
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
|
+
identity:
|
|
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
|
+
identity:
|
|
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
|