@skill-map/spec 0.51.0 → 0.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Spec changelog
2
2
 
3
+ ## 0.53.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Ship the `core/node-bump` action and the `core/annotation-stale` analyzer as `experimental`, so the sidecar bump/drift surface is disabled by default (Decision #128). Gated as a unit: with the action disabled no Bump button projects, and with the drift analyzer disabled no stale finding fires. The `sidecar-end-to-end` conformance case drops its `annotation-stale` assertion accordingly (a default scan now surfaces only `annotation-orphan`; the node still carries the derived `sidecar.status`).
8
+
9
+ ## User-facing
10
+
11
+ The Bump button and the sidecar drift ("stale") finding are off by default now. Staleness still shows on the node's status; re-enable with `sm plugins enable core/node-bump core/annotation-stale` or the Settings toggles.
12
+
13
+ ## 0.52.0
14
+
15
+ ### Minor Changes
16
+
17
+ - Remove the `supersede` feature end to end. The `supersedes` link kind is dropped from the global link-kind enum, the `annotations.supersedes` and `supersededBy` sidecar fields are removed from the spec, and the three built-ins that powered it (the `core/annotations` extractor, the `core/node-supersede` action, the `core/node-superseded` analyzer) are deleted. Scans no longer produce supersede links, and the inspector drops the Supersede button and the superseded-by banner.
18
+
19
+ ## User-facing
20
+
21
+ The Supersede inspector button, the "superseded by" banner, and supersede links on the map are gone. The `supersedes` and `supersededBy` keys in `.sm` sidecars are no longer recognized, remove them from any sidecar that still declares them.
22
+
3
23
  ## 0.51.0
4
24
 
5
25
  ### Minor Changes
package/README.md CHANGED
@@ -39,7 +39,7 @@ These are implementation decisions. The reference impl picks them (see [`../AGEN
39
39
  Two analyzers govern every identifier in the spec. They are **normative**.
40
40
 
41
41
  - **Filesystem artefacts use kebab-case.** Every file and directory in `spec/` (and in any conforming implementation), `scan-result.schema.json`, `job-lifecycle.md`, `report-base.schema.json`, `auto-rename-medium` (as an `issue.analyzerId` value), `direct-override` (as a `safety.injectionType` enum value), and so on, is kebab-case lowercase. Enum values and issue analyzer ids follow the same convention so they can be echoed back into URLs, filenames, and log keys without escaping.
42
- - **JSON content uses camelCase.** Every key inside a JSON Schema, frontmatter block, config file, plugin manifest, action manifest, job record, report, event payload, or API response is camelCase: `whatItDoes`, `injectionDetected`, `expectedTools`, `supersededBy`, `docsUrl`, `examplesUrl`, `ttlSeconds`, `runId`, `jobId`. This matches the JS/TS ecosystem the reference impl ships in and the Kysely `CamelCasePlugin` that bridges to the `snake_case` SQL layer, but the analyzer is spec-level, not implementation-level: an alternative implementation in any language still exposes camelCase JSON keys.
42
+ - **JSON content uses camelCase.** Every key inside a JSON Schema, frontmatter block, config file, plugin manifest, action manifest, job record, report, event payload, or API response is camelCase: `whatItDoes`, `injectionDetected`, `expectedTools`, `sourceVersion`, `docsUrl`, `examplesUrl`, `ttlSeconds`, `runId`, `jobId`. This matches the JS/TS ecosystem the reference impl ships in and the Kysely `CamelCasePlugin` that bridges to the `snake_case` SQL layer, but the analyzer is spec-level, not implementation-level: an alternative implementation in any language still exposes camelCase JSON keys.
43
43
 
44
44
  The SQL persistence layer is the sole exception: tables, columns, and migration filenames use `snake_case` (see `db-schema.md`). That boundary is crossed only inside a storage adapter; nothing that leaves the kernel should ever be `snake_case`.
45
45
 
package/architecture.md CHANGED
@@ -398,7 +398,7 @@ Default `undefined` ≡ empty map ≡ no reserved names. Links to non-reserved t
398
398
 
399
399
  The `Extractor` runtime contract is `extract(ctx) → void`. The extractor emits its work through three callbacks the kernel binds onto `ctx`:
400
400
 
401
- - `ctx.emitLink(link)`, append a `Link` to the kernel's `links` table. The kernel validates `link.kind` against the **global closed enum** of link kinds (`invokes`, `references`, `mentions`, `supersedes`, `points`) before persistence; off-enum links are dropped and surface as `extension.error` events (the per-extractor `emitsLinkKinds` allowlist was retired with the structure-as-truth refactor; confidence is declared per emit, default `'medium'`). URL-shaped targets (`http(s)://…`) are partitioned out into `node.externalRefsCount` and never persisted.
401
+ - `ctx.emitLink(link)`, append a `Link` to the kernel's `links` table. The kernel validates `link.kind` against the **global closed enum** of link kinds (`invokes`, `references`, `mentions`, `points`) before persistence; off-enum links are dropped and surface as `extension.error` events (the per-extractor `emitsLinkKinds` allowlist was retired with the structure-as-truth refactor; confidence is declared per emit, default `'medium'`). URL-shaped targets (`http(s)://…`) are partitioned out into `node.externalRefsCount` and never persisted.
402
402
  - `ctx.enrichNode(partial)`, merge canonical, kernel-curated properties onto the current node's enrichment layer (persisted into [`node_enrichments`](./db-schema.md#node_enrichments)). **Strictly separate from the author-supplied frontmatter** (the latter remains immutable across scans). The enrichment layer is the right home for kernel-derived facts (computed titles, summaries, signals an Extractor inferred from the body) without polluting what the user wrote on disk. See §Enrichment layer below for the full lifecycle (per-extractor attribution, refresh verbs).
403
403
  - `ctx.store`, plugin-scoped persistence. Optional, present only when the plugin declares `storage.mode` in `plugin.json`. Shape depends on the mode (`KvStore` for mode A, scoped `Database` for mode B). See [`plugin-kv-api.md`](./plugin-kv-api.md). The plugin author MAY opt into shape validation for their own writes by declaring `storage.schema` (Mode A) or `storage.schemas` (Mode B) in the manifest, JSON Schemas the kernel AJV-compiles at load time and runs against every `ctx.store.set(key, value)` / `ctx.store.write(table, row)` call. Absent = permissive (status quo). `emitLink` and `enrichNode` keep their universal validation against `link.schema.json` / `node.schema.json` regardless of this opt-in. See [`plugin-author-guide.md` §`outputSchema`](./plugin-author-guide.md#outputschema--opt-in-correctness-for-custom-storage-writes).
404
404
 
@@ -406,7 +406,7 @@ Extractors are deterministic-only; `ctx.runner` is NOT exposed on the Extractor
406
406
 
407
407
  ### Extractor · Signal IR (opt-in)
408
408
 
409
- In addition to the `emitLink` path, Extractors MAY emit **Signals** via `ctx.emitSignal(signal)`. A Signal is a candidate detection: one or many alternative interpretations of the same body or frontmatter location, each carrying its own kind, target, confidence, and rationale. See [`signal.schema.json`](./schemas/signal.schema.json) for the full contract. The Signal IR is opt-in; an extractor whose detection is unambiguous (sidecar `supersedes`, `[text](file.md)` markdown links, plain `https://…` URLs) is encouraged to keep emitting Links directly with `ctx.emitLink`. Signals exist for the cases the resolver actually helps: detections where a single body token can plausibly mean several things and the active provider's rules need to decide.
409
+ In addition to the `emitLink` path, Extractors MAY emit **Signals** via `ctx.emitSignal(signal)`. A Signal is a candidate detection: one or many alternative interpretations of the same body or frontmatter location, each carrying its own kind, target, confidence, and rationale. See [`signal.schema.json`](./schemas/signal.schema.json) for the full contract. The Signal IR is opt-in; an extractor whose detection is unambiguous (`[text](file.md)` markdown links, plain `https://…` URLs) is encouraged to keep emitting Links directly with `ctx.emitLink`. Signals exist for the cases the resolver actually helps: detections where a single body token can plausibly mean several things and the active provider's rules need to decide.
410
410
 
411
411
  The kernel's **resolver phase** runs after extraction completes and before analysis starts. For each Signal, the resolver:
412
412
 
@@ -670,7 +670,7 @@ Skill-map's own metadata layer (versioning, supersession, provenance, taxonomy,
670
670
  Two schemas describe the wire shape:
671
671
 
672
672
  - [`schemas/sidecar.schema.json`](./schemas/sidecar.schema.json), root shape with reserved blocks `identity` (anchor + drift hashes), `annotations` (the conventional catalog), `settings` (reserved), `audit` (write trail), plus opt-in `<plugin-id>:` namespacing.
673
- - [`schemas/annotations.schema.json`](./schemas/annotations.schema.json), curated 10-field catalog: versioning + supersession (`version`, `stability`, `supersedes`, `supersededBy`), provenance (`authors`, `license`, `source`, `sourceVersion`), taxonomy (`tags`), docs (`docsUrl`). The activity timestamp lives in the reserved `audit:` block (`audit.lastBumpedAt`), not in `annotations:`. `additionalProperties: true` so plugins or users add custom keys without coordination; the built-in `unknown-field` analyzer warns on truly unrecognized keys (typo guard).
673
+ - [`schemas/annotations.schema.json`](./schemas/annotations.schema.json), curated 8-field catalog: versioning (`version`, `stability`), provenance (`authors`, `license`, `source`, `sourceVersion`), taxonomy (`tags`), docs (`docsUrl`). The activity timestamp lives in the reserved `audit:` block (`audit.lastBumpedAt`), not in `annotations:`. `additionalProperties: true` so plugins or users add custom keys without coordination; the built-in `unknown-field` analyzer warns on truly unrecognized keys (typo guard).
674
674
 
675
675
  ### Identity and drift
676
676
 
@@ -682,7 +682,7 @@ At scan time the kernel re-computes the live hashes and compares against the sto
682
682
 
683
683
  The deterministic built-in `core/node-bump` Action produces a sidecar patch:
684
684
 
685
- - Increments `annotations.version` by 1 (or sets to `1` if missing, single integer monotonic, orthogonal to `stability`; major bumps are not a concept, the convention for breaking changes is "create a new node, supersede the old").
685
+ - Increments `annotations.version` by 1 (or sets to `1` if missing, single integer monotonic, orthogonal to `stability`; major bumps are not a concept, the convention for breaking changes is "create a new node and retire the old").
686
686
  - Refreshes `identity.bodyHash` and `identity.frontmatterHash` to the live values.
687
687
  - Stamps `audit.lastBumpedAt` (ISO 8601 datetime) and `audit.lastBumpedBy` (the Git author name from `git config user.name` when the project is a Git repo; otherwise the channel literal `'cli'`, `'ui'`, or `'plugin:<id>'`).
688
688
  - On first-time creation also stamps `audit.createdAt` and `audit.createdBy` (set once, stable thereafter).
@@ -879,7 +879,7 @@ Endpoints under `/api/contributions/*`:
879
879
  - `GET /api/contributions/registered`, runtime catalog. Mirror of `/api/annotations/registered`. Envelope variant `kind: 'contributions.registered'` (see [`schemas/api/rest-envelope.schema.json`](./schemas/api/rest-envelope.schema.json)).
880
880
  - `GET /api/contributions/:pluginId/:extensionId/:contributionId?path=...`, lazy per-node fetch for inspector slots. **Three URL segments** mirror the qualified id `<pluginId>/<extensionId>/<contributionId>`. Filters by qualified id + node path; the BFF enforces `pluginId` ↔ namespace at the route level, no cross-plugin reads via this endpoint.
881
881
 
882
- The `inspector.action.button` contribution is **self-projected by the dispatching Action's own `project(ctx)`** (scan-time, deterministic), not by a separate projector Analyzer. The Action computes the per-node `enabled` / `disabledReason` and the prompt `options` / `defaultValue` from the live graph it receives, emits the button, and is itself the dispatch target. (This reverses the earlier "an Analyzer projects the button" shape; the projector Analyzers `core/supersede` and `core/tags` were removed and `core/annotation-stale` keeps only its badge + issue.) The slot dispatches to a generic Action endpoint, sibling of the single-node `POST /api/sidecar/bump`:
882
+ The `inspector.action.button` contribution is **self-projected by the dispatching Action's own `project(ctx)`** (scan-time, deterministic), not by a separate projector Analyzer. The Action computes the per-node `enabled` / `disabledReason` and the prompt `options` / `defaultValue` from the live graph it receives, emits the button, and is itself the dispatch target. (This reverses the earlier "an Analyzer projects the button" shape; the projector Analyzer `core/tags` was removed and `core/annotation-stale` keeps only its badge + issue.) The slot dispatches to a generic Action endpoint, sibling of the single-node `POST /api/sidecar/bump`:
883
883
 
884
884
  - `POST /api/actions/:id`, dispatch a kernel Action by qualified id (`:id` is the `<plugin>/<action>` from the button payload's `actionId`). Body carries the target `nodePath`, the optional reserved `input` object (Steps 2+), and the consent fields `confirm` / `always` (see §Annotation system → Write consent) for `.sm`-writing Actions. The kernel resolves the Action in its registry (unknown id → 404), runs it against the node, and answers the action-result envelope `kind: 'action.applied'` (`{ value: { actionId, nodePath, report }, elapsedMs }`, see [`schemas/api/rest-envelope.schema.json`](./schemas/api/rest-envelope.schema.json)). `POST /api/sidecar/bump` remains the dedicated single-purpose route for `core/node-bump` (`kind: 'sidecar.bumped'`); the generic dispatch route shares the same action-result envelope variant.
885
885
 
package/cli-contract.md CHANGED
@@ -376,10 +376,9 @@ identity:
376
376
 
377
377
  annotations:
378
378
  version: 3
379
- # Deprecated because v0.6 architecture supersedes this skill.
379
+ # Deprecated because the v0.6 architecture replaced this skill.
380
380
  # See decision #142 in ROADMAP for context.
381
381
  stability: deprecated
382
- supersededBy: agents/reviewer-v2.md
383
382
  tags:
384
383
  - review
385
384
  - typescript # only TS, not JS
@@ -390,7 +389,6 @@ annotations:
390
389
  ```yaml
391
390
  annotations:
392
391
  stability: deprecated
393
- supersededBy: agents/reviewer-v2.md
394
392
  tags:
395
393
  - review
396
394
  - typescript
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://skill-map.ai/spec/v0/conformance-case.schema.json",
3
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 analyzers.",
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 the `annotation-orphan` (per orphan `.sm`) issue from the built-in core analyzers. The companion `annotation-stale` analyzer (drift) ships experimental / disabled by default, so a default scan does not emit a stale issue; the derived `sidecar.status` on the node still reflects the drift.",
5
5
  "fixture": "sidecar-end-to-end",
6
6
  "invoke": {
7
7
  "verb": "scan",
@@ -15,11 +15,9 @@
15
15
  { "type": "json-path", "path": "$.nodes[0].sidecar.present", "equals": true },
16
16
  { "type": "json-path", "path": "$.nodes[0].sidecar.status", "matches": "^stale-(body|frontmatter|both)$" },
17
17
  { "type": "json-path", "path": "$.nodes[0].sidecar.annotations.version", "equals": 7 },
18
- { "type": "json-path", "path": "$.stats.issuesCount", "equals": 2 },
18
+ { "type": "json-path", "path": "$.stats.issuesCount", "equals": 1 },
19
19
  { "type": "json-path", "path": "$.issues[0].analyzerId", "equals": "annotation-orphan" },
20
20
  { "type": "json-path", "path": "$.issues[0].severity", "equals": "warn" },
21
- { "type": "json-path", "path": "$.issues[0].data.expectedMdPath", "equals": ".claude/agents/orphan.md" },
22
- { "type": "json-path", "path": "$.issues[1].analyzerId", "equals": "annotation-stale" },
23
- { "type": "json-path", "path": "$.issues[1].severity", "equals": "info" }
21
+ { "type": "json-path", "path": "$.issues[0].data.expectedMdPath", "equals": ".claude/agents/orphan.md" }
24
22
  ]
25
23
  }
@@ -10,7 +10,7 @@ This file is hand-maintained. A CI check before spec release compares the schema
10
10
  |---|---|---|---|---|
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
- | 3 | `issue.schema.json` |, | 🔴 missing | Needs fixture triggering `name-collision` + `broken-ref` + `superseded`. |
13
+ | 3 | `issue.schema.json` |, | 🔴 missing | Needs fixture triggering `name-collision` + `broken-ref`. |
14
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. |
@@ -27,7 +27,7 @@ This file is hand-maintained. A CI check before spec release compares the schema
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 Provider plugin whose `kinds/<kindName>/kind.json` 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). Since the structure-as-truth refactor, Providers no longer carry a `kinds` map in the manifest; per-kind metadata lives under `kinds/<kindName>/kind.json` and frontmatter schemas under `kinds/<kindName>/schema.json`. The complementary positive case (canonical Claude Provider validates) lives in `provider:claude` conformance. Direct case for missing `kinds/` directory rejection still pending. |
29
29
  | 19 | `extensions/extractor.schema.json` |, | 🔴 missing | Case: `frontmatter` + `slash` + `at-directive` extractor manifests validate; an extractor declaring a `precondition.kind` against an unknown qualified kind emits `precondition-kind-unknown` in `sm plugins doctor`. |
30
- | 20 | `extensions/analyzer.schema.json` | `score-phase-confidence` | 🟡 partial | `score-phase-confidence` exercises the `phase` enum end-to-end: a drop-in analyzer declaring `phase: 'score'` loads from its manifest and composes a confidence op (`delta -0.4`, then a no-op `floor 0.5`) on top of the kernel's 1.0 baseline (a clean resolved link keeps that baseline, no built-in op), landing the folded `scan_links.confidence` at `0.6`. Direct manifest-validation cases for the `detect` / `aggregate` defaults and the `precondition` / `ui` blocks (`name-collision`, `broken-ref`, `superseded`) still pending. |
30
+ | 20 | `extensions/analyzer.schema.json` | `score-phase-confidence` | 🟡 partial | `score-phase-confidence` exercises the `phase` enum end-to-end: a drop-in analyzer declaring `phase: 'score'` loads from its manifest and composes a confidence op (`delta -0.4`, then a no-op `floor 0.5`) on top of the kernel's 1.0 baseline (a clean resolved link keeps that baseline, no built-in op), landing the folded `scan_links.confidence` at `0.6`. Direct manifest-validation cases for the `detect` / `aggregate` defaults and the `precondition` / `ui` blocks (`name-collision`, `broken-ref`) still pending. |
31
31
  | 21 | `extensions/action.schema.json` |, | 🔴 missing | Case: a `deterministic` action manifest validates; a `probabilistic` action without `<action-dir>/prompt.md` surfaces as `load-error` (structure-as-truth: prompt template lives on disk by convention, no `promptTemplateRef` field). |
32
32
  | 22 | `extensions/formatter.schema.json` |, | 🔴 missing | Case: `ascii` formatter manifest validates. |
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. |
package/db-schema.md CHANGED
@@ -105,7 +105,7 @@ One row per detected link, matching [`schemas/link.schema.json`](./schemas/link.
105
105
  | `id` | INTEGER | PRIMARY KEY AUTOINCREMENT | |
106
106
  | `source_path` | TEXT | NOT NULL | FK semantically; MAY be unenforced for performance. |
107
107
  | `target_path` | TEXT | NOT NULL | MAY point to a missing node (broken ref). |
108
- | `kind` | TEXT | NOT NULL, CHECK in (`invokes`, `references`, `mentions`, `supersedes`, `points`) | |
108
+ | `kind` | TEXT | NOT NULL, CHECK in (`invokes`, `references`, `mentions`, `points`) | |
109
109
  | `confidence` | REAL | NOT NULL, CHECK `>= 0.0 AND <= 1.0` | Numeric `[0,1]` (`link.schema.json#/properties/confidence`). The kernel's 1.0 baseline, then the folded result of every `score`-phase `ctx.adjustConfidence` op (the built-in score-phase detectors `core/name-reserved`, `core/reference-broken`, plus any third-party scorer); the per-op attribution lives in `scan_link_scores`. Migrated from the legacy `high`/`medium`/`low` TEXT enum. |
110
110
  | `sources_json` | TEXT | NOT NULL | JSON array of extractor ids. |
111
111
  | `original_trigger` | TEXT | NULL | |
@@ -257,7 +257,7 @@ Per-op confidence-attribution audit trail. One row per attributed `ctx.adjustCon
257
257
  | `extension_id` | TEXT | NOT NULL | Scorer extension id within the plugin. |
258
258
  | `source_path` | TEXT | NOT NULL | The link's `source` (originating node path). Part of the structural identity key, the same tuple `scan_links` dedups on. |
259
259
  | `target` | TEXT | NOT NULL | The link's `target` (MAY be a missing node: broken refs get scored too). |
260
- | `kind` | TEXT | NOT NULL | The link's `kind` (`invokes` / `references` / `mentions` / `supersedes` / `points`). |
260
+ | `kind` | TEXT | NOT NULL | The link's `kind` (`invokes` / `references` / `mentions` / `points`). |
261
261
  | `normalized_trigger` | TEXT | NULL | The link's `trigger.normalizedTrigger`; NULL for path-style links that carry no trigger. Completes the structural identity key. |
262
262
  | `op_kind` | TEXT | NOT NULL | Confidence-algebra bucket: `set` / `delta` / `ceil` / `floor`. Kept open at the SQL layer (no CHECK) so the op catalog can evolve as a kernel + spec change without a DDL migration. |
263
263
  | `op_value` | REAL | NOT NULL | The op's operand. |
package/index.json CHANGED
@@ -174,14 +174,14 @@
174
174
  }
175
175
  ]
176
176
  },
177
- "specPackageVersion": "0.51.0",
177
+ "specPackageVersion": "0.53.0",
178
178
  "integrity": {
179
179
  "algorithm": "sha256",
180
180
  "files": {
181
- "CHANGELOG.md": "fb58994d8696d06837d49189f822ca3cff1c084c78d3bd072448670d7cafcd40",
182
- "README.md": "a7505a7b0672c39a8b011e3c5e7d41826306476ee63768249bba4bdb3c03d4d1",
183
- "architecture.md": "205307d72862c8cd4a58cf06b806390ec42291e98cce49eb7d2a6db2cf008fcd",
184
- "cli-contract.md": "e8cd7b7aac3883317cc994280a6870266e2e7329b9382f5f9747be25079c3311",
181
+ "CHANGELOG.md": "6c4c565708584b07b3e7467a357f62a67fdbedafa80fd25af82258e154192759",
182
+ "README.md": "abb663d0c96c3158ffc501b9d5ef6f58e0db09bdea824cda7f9f70f465793925",
183
+ "architecture.md": "044f7216015fc0629b0e4665e35953f14ab657ca75aaf1cae9ba47d3cd68226c",
184
+ "cli-contract.md": "be13ffcb8c96065f1686443af82324c8175962b53e3777ef42158c1cdc3bb381",
185
185
  "conformance/README.md": "33fef8cea919c1d7440c06c7eaa100e50014f52636490f550720e086cf8b651f",
186
186
  "conformance/cases/backtick-path-extraction.json": "4620e7f8bc161fc57cb44001e9d99879c7e22b4865a0c27a20dc28969cd936d9",
187
187
  "conformance/cases/extractor-collision-detection.json": "179a02c61892f0d26492de0c4e2c327fa6b4986d1265a8f119e871df6afe4658",
@@ -191,11 +191,11 @@
191
191
  "conformance/cases/orphan-markdown-fallback.json": "506119323ddde85c1fb4c986c7f6f40a345d44adb06de8d84002591df0e479ee",
192
192
  "conformance/cases/plugin-missing-ui-rejected.json": "2074fd71937feae136c999f76da81f334f2caf8b65bfe8dc9d7fb800699fb85c",
193
193
  "conformance/cases/score-phase-confidence.json": "aa3a06149d78ff056dd1a47852baaedc200e47b0d5b1e778d3459ae62f65f390",
194
- "conformance/cases/sidecar-end-to-end.json": "0a0d941ab50bd7619e1021a6c6d6dc92918429c2efcf25236b42b5fac9eab901",
194
+ "conformance/cases/sidecar-end-to-end.json": "06374e619df1691f1593b0847b3671299318525d4a7bf4ff9bfda3ab03032a5f",
195
195
  "conformance/cases/view-action-button.json": "51331f725be1c3655351f8fca6fc9d3d301ae68ea1741ff6c79998332ba2dfeb",
196
196
  "conformance/cases/view-contribution-payloads.json": "e8f54ed62e64a2a0f86729866e507abb1f4246683f0e60d538280536f7cd3ecc",
197
197
  "conformance/cases/view-slots-all.json": "05284e0324dd2da72b6b21d397c11b355802229a68053e9dddc323f69b3a1eba",
198
- "conformance/coverage.md": "2a24683ee75d5cf8266d643787140b015637a4d6126e6151b9831a444614f5bc",
198
+ "conformance/coverage.md": "63fed07a979fcc1ce0b3142a5f060d8ad049bef3430996f159a389e0989f3233",
199
199
  "conformance/fixtures/backtick-path/docs/target.md": "a09ae2cb4c96358a2e0692215f172b0f8c48028b6b123e4e83424b28302e644c",
200
200
  "conformance/fixtures/backtick-path/source.md": "217f78b12b3ff47a938a5cc9c1ff7d6989d6a1db82bd1ddf3656787f31efb902",
201
201
  "conformance/fixtures/orphan-markdown/.claude/agents/reviewer.md": "7f062731106f2d9811e4fffcf6ab44b8dfff4cfb16536a469514cc0664e832bf",
@@ -229,14 +229,14 @@
229
229
  "conformance/fixtures/view-contribution-payloads/notes/example.md": "312b1919cd7fd0f233648b053acfb2975662ede3c65dd391cc508204b67ad6fb",
230
230
  "conformance/fixtures/view-slots-all/.skill-map/plugins/all-slots/analyzers/everything/index.js": "ea0022fec7f0fd5a26ba12db1310335f434f2f820682206a3a9542d98db0d346",
231
231
  "conformance/fixtures/view-slots-all/.skill-map/plugins/all-slots/plugin.json": "c48e8a0574947ade0b4eb189d6bc27a48e24f92f616aacdc177f2d22d472a599",
232
- "db-schema.md": "ba0f62e72af71c88f51cbb3e08e663f8347a3b63e2081b6a25862db6aad68631",
232
+ "db-schema.md": "d6af5d26626b692b05e96726d3bdaa77ec1b703bb30e83f752ffcca55e163fa4",
233
233
  "interfaces/security-scanner.md": "e8049712b9cf7a07c786bf19f8f775f8ef9638f063f7fba5c7a8b1431b92f38e",
234
- "job-events.md": "830624b9282aa8d9ee065ead22f56a30e9ab61168262826134452a2686f81eea",
234
+ "job-events.md": "d5dc11cda9e9302d0d9f17d467c14c80ad4011456f574dc87516470aff0323cb",
235
235
  "job-lifecycle.md": "9c429121f98a07c8795f8979ed1abc5e5334e3f89db51585a8da55c527ef855b",
236
- "plugin-author-guide.md": "2b2f0a967508063ab3a71f8b94cc350045ec17751dd66885841c476d0007bf78",
236
+ "plugin-author-guide.md": "a6bca100e8963e74e0299eeaa15819d0734e9aee57af89f7f56707eb3c99dbb9",
237
237
  "plugin-kv-api.md": "1acc69ed82433a74e35ada61d63a6d7379fb61046ff83de1e0facbe884c64704",
238
238
  "prompt-preamble.md": "9dd4f6d1bc6a425f8782fcee10cbe75909e8d64e28781fda56c2fae909b02f40",
239
- "schemas/annotations.schema.json": "8c639b149cad675fdd4e7d6be2b47e920cfdd24087b41361d6e1b8280f646322",
239
+ "schemas/annotations.schema.json": "09fcebc86e3b793bf9f03a35b38e5ca2a08d79ac3504f6f03895ac2ae1c2aded",
240
240
  "schemas/api/rest-envelope.schema.json": "8eeb1c2d79fb69eaef23737a2231d48d67e59b8b19aad816239ab4680e2c4752",
241
241
  "schemas/bump-report.schema.json": "c763e1f89f2665c479d6a4985c1d324c65e5278331ebab82220287a07e4c4429",
242
242
  "schemas/conformance-case.schema.json": "958b316d646d0c64a715a7a28cee66d2c2d2498a60dbfc5ae8970687c2a96954",
@@ -249,13 +249,13 @@
249
249
  "schemas/extensions/formatter.schema.json": "880dc379ad545a62404403533a01eda5171edba0390561fc46ec6e986e0b9bd3",
250
250
  "schemas/extensions/hook.schema.json": "f56aef59e9986ffdf7d86aa2e048dccccf217000a358b8c64737cbd911c48dad",
251
251
  "schemas/extensions/provider-kind.schema.json": "499b2418bbe6d8a84a1608e26c56b52c2652a30ce314bc2989094418797dc1e6",
252
- "schemas/extensions/provider.schema.json": "bea1d73897dc8fa8499ba7c77ce535337473e5ecb3702ebca9966c08afc920f4",
252
+ "schemas/extensions/provider.schema.json": "30f1f001192b3ca9fc1e3aa383b23419f8d6c179d0239f54cb7f41910126a6bb",
253
253
  "schemas/frontmatter/base.schema.json": "cff81510ed94824dfd12ab8b30ce9fbac65e42d61ae0edf3fbb6bbb6bb8bcb8c",
254
254
  "schemas/history-stats.schema.json": "436aa0ffe744bdb699000447e86b45724fbd2cc4642781074eb1527522b9058c",
255
255
  "schemas/input-types.schema.json": "93b27a1cbd1f131d42730eb9a89cf3af6889e9f17b20a48ce36133885503e01b",
256
- "schemas/issue.schema.json": "298bafc7f5787709abce29fac2acf75a889dbcd1d0de4c16120934d416d616ac",
256
+ "schemas/issue.schema.json": "840198acc36ed17f30957733dfaf4463d07d72911b13fb7f58b037a7dcf2d5b1",
257
257
  "schemas/job.schema.json": "dbcedf137de03fde38f74686f594e600c627bf808f2aad23511a26617a663a02",
258
- "schemas/link.schema.json": "3bff6fd926df6a0477f9048276017a423bd6bcfb717b57122450b033b3c9be06",
258
+ "schemas/link.schema.json": "e2f615bff0e569be936acd6ec249795906dfb56084f3a1a9a2a37fd51c0b00e2",
259
259
  "schemas/node.schema.json": "1ebba38e0c0ae022fccbc0cdf7c298da1720a68d4cb375f0baf9f0847998a0d8",
260
260
  "schemas/plugins-doctor.schema.json": "03e2dc51c052a09bf0198c80e2c26e6129734ada4a748e483245de3dd8576c42",
261
261
  "schemas/plugins-registry.schema.json": "211d081691fc83526e1593c79ed9741ad8a5dbd4db1a756f72141b0cced2ea15",
@@ -265,7 +265,7 @@
265
265
  "schemas/report-base.schema.json": "e4d25f055e24f18ae0f77c24661c1bddc87ff2e43b001b6a827fcb14f9753f44",
266
266
  "schemas/scan-result.schema.json": "9fb81f496d6f8bdcb82131d0b2eb532da1addb801e7d27bd192a0c286a28c2c0",
267
267
  "schemas/sidecar.schema.json": "f9d914e61b2d04495b84dc90e55240aca959e6f16137e5bfa4c0e10ada33ecbe",
268
- "schemas/signal.schema.json": "0624341fdb8dcf102fceaa80ab60cf7b8ec85183b6574e4cbe94f10d215cebd9",
268
+ "schemas/signal.schema.json": "c677f04964dcc15e00368c5cc4b0569fb4cf21889d34fa3c29dc21a5cb6b919c",
269
269
  "schemas/summaries/agent.schema.json": "5b26b95fb082b73d302c8aa6489ab09488a155ccfbb8943dfc47079509d35122",
270
270
  "schemas/summaries/command.schema.json": "7f522c682d0fdf5a40172c7fc8fcd23e60a0ab0253354146525bd3a3d417f1f8",
271
271
  "schemas/summaries/hook.schema.json": "6a1ceecda7a7173dfcd8b5f705d84be1792c4bb5a2269ff666088128c02c888a",
package/job-events.md CHANGED
@@ -412,7 +412,7 @@ Emitted once per registered Analyzer, after every issue has been validated.
412
412
  "runId": "...",
413
413
  "jobId": null,
414
414
  "data": {
415
- "analyzerId": "core/node-superseded"
415
+ "analyzerId": "core/node-stability"
416
416
  }
417
417
  }
418
418
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skill-map/spec",
3
- "version": "0.51.0",
3
+ "version": "0.53.0",
4
4
  "description": "JSON Schemas, prose contracts, and conformance suite for the skill-map specification.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -126,13 +126,13 @@ Built-ins split between two namespaces:
126
126
 
127
127
  ### Extension id shape
128
128
 
129
- The convention applied to every built-in extension id is **`<domain>-<detail>`** (general to specific): the leftmost segment names the entity the extension reasons about (`node`, `link`, `annotation`, `reference`, `name`, ...), the rest narrows the behaviour. Examples: `annotation-orphan`, `link-counter`, `node-stability`, `name-reserved`, `reference-broken`. Even Actions live under their entity domain (`node-bump`, `node-supersede`) rather than verb-style ids, so the catalog reads as a structured list.
129
+ The convention applied to every built-in extension id is **`<domain>-<detail>`** (general to specific): the leftmost segment names the entity the extension reasons about (`node`, `link`, `annotation`, `reference`, `name`, ...), the rest narrows the behaviour. Examples: `annotation-orphan`, `link-counter`, `node-stability`, `name-reserved`, `reference-broken`. Even Actions live under their entity domain (`node-bump`, `node-set-tags`) rather than verb-style ids, so the catalog reads as a structured list.
130
130
 
131
131
  Authors are not required to follow this, but it makes `sm plugins list` self-grouping. In the extension file, declare only the short id-bearing **folder name**, not a prefixed id; the loader composes `<plugin-id>/<short-id>` from `plugin.json` (the directory name) and the extension folder. Any other cross-extension reference (`precondition.analyzerIds`, ...) uses the qualified id of the target.
132
132
 
133
133
  ### Toggle model
134
134
 
135
- Every extension is independently toggle-able by its qualified id `<plugin>/<ext-id>` (e.g. `claude/at-directive`, `core/node-superseded`). The **plugin row is a presentational grouping**, not the granular toggle target: the user sees a row per plugin in `sm plugins list` and the Settings UI, with each extension listed underneath with its own enabled / disabled state.
135
+ Every extension is independently toggle-able by its qualified id `<plugin>/<ext-id>` (e.g. `claude/at-directive`, `core/reference-broken`). The **plugin row is a presentational grouping**, not the granular toggle target: the user sees a row per plugin in `sm plugins list` and the Settings UI, with each extension listed underneath with its own enabled / disabled state.
136
136
 
137
137
  Two id shapes resolve at the toggle surface:
138
138
 
@@ -250,7 +250,7 @@ Pure single-node analysis. **Never** read another node, the graph, or the databa
250
250
 
251
251
  `extract(ctx) → void`. Output flows through callbacks the kernel binds onto `ctx`:
252
252
 
253
- - **`ctx.emitLink(link)`**, append a `Link`. The kernel validates `link.kind` against the **global closed enum** (`invokes`, `references`, `mentions`, `supersedes`, `points`); off-enum kinds drop as `extension.error`. Confidence is declared per emit (default `'medium'`). URL-shaped targets are partitioned into `node.externalRefsCount` and never persisted. (There is no per-extractor `emitsLinkKinds` allowlist anymore.)
253
+ - **`ctx.emitLink(link)`**, append a `Link`. The kernel validates `link.kind` against the **global closed enum** (`invokes`, `references`, `mentions`, `points`); off-enum kinds drop as `extension.error`. Confidence is declared per emit (default `'medium'`). URL-shaped targets are partitioned into `node.externalRefsCount` and never persisted. (There is no per-extractor `emitsLinkKinds` allowlist anymore.)
254
254
  - **`ctx.enrichNode(partial)`**, merge kernel-curated properties onto the node's enrichment layer (persisted into `node_enrichments`). **Strictly separate from the author frontmatter**, which is immutable from any Extractor. Use it for inferred facts (computed titles, summaries) the author did not write.
255
255
  - **`ctx.emitContribution(id, payload)`**, view contributions (see [View contributions](#view-contributions)).
256
256
  - **`ctx.store`**, plugin-scoped persistence, present only when `plugin.json` declares `storage.mode`. See [`plugin-kv-api.md`](./plugin-kv-api.md).
@@ -414,7 +414,7 @@ Operate on one or more nodes. Dual-mode (`mode` optional, default `'deterministi
414
414
  An Action has two independent surfaces:
415
415
 
416
416
  - **`invoke(input, ctx)`**, the on-demand executor the user triggers (deterministic in-process code, or a probabilistic rendered prompt the runner executes). Unit-test deterministic ones by calling `invoke(input, ctx)` with a fake context; probabilistic ones still need a live kernel until Step 10 lands the job subsystem.
417
- - **`project(ctx)`** (optional), a deterministic, side-effect-free, scan-time method with read-only graph access (`ctx.nodes`, `ctx.links`) plus `ctx.emitContribution(nodePath, ref, payload)`. Use it to self-project the Action's own UI affordance, typically an `inspector.action.button` declared in the manifest `ui` map (see [View contributions](#view-contributions)), computing the per-node `enabled` / prompt `options` from the live graph. `project()` is always deterministic, even when `invoke` is probabilistic, and runs every scan (same cost as an analyzer's emit). This is how built-in buttons like Supersede / Edit tags / Bump are produced: the dispatching Action owns its button, there is no separate "projector" analyzer. Unit-test it by calling `project(ctx)` with a fake `{ nodes, links, emitContribution }` and asserting the captured payload.
417
+ - **`project(ctx)`** (optional), a deterministic, side-effect-free, scan-time method with read-only graph access (`ctx.nodes`, `ctx.links`) plus `ctx.emitContribution(nodePath, ref, payload)`. Use it to self-project the Action's own UI affordance, typically an `inspector.action.button` declared in the manifest `ui` map (see [View contributions](#view-contributions)), computing the per-node `enabled` / prompt `options` from the live graph. `project()` is always deterministic, even when `invoke` is probabilistic, and runs every scan (same cost as an analyzer's emit). This is how built-in buttons like Edit tags / Bump are produced: the dispatching Action owns its button, there is no separate "projector" analyzer. Unit-test it by calling `project(ctx)` with a fake `{ nodes, links, emitContribution }` and asserting the captured payload.
418
418
 
419
419
  ---
420
420
 
@@ -2,30 +2,20 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://skill-map.ai/spec/v0/annotations.schema.json",
4
4
  "title": "Annotations",
5
- "description": "Catalog of conventional annotation fields skill-map ships out of the box, written into the `annotations:` block of a sidecar (`<basename>.sm`). Every field is OPTIONAL, a sidecar with an empty `annotations: {}` is valid. Schema is `additionalProperties: true` so users / plugins can add custom keys without coordination; the built-in `unknown-field` analyzer emits a warning on unrecognized keys (typo guard). The curated catalog is the load-bearing 10 fields below, versioning + supersession (`version`, `stability`, `supersedes`, `supersededBy`), provenance (`authors`, `license`, `source`, `sourceVersion`), taxonomy (`tags`), docs (`docsUrl`). The activity timestamp lives in the reserved `audit:` block (`audit.lastBumpedAt`), not in `annotations:`. Plugins that want first-class custom keys with their own validation declare `annotationContributions` in their manifest (see Step 9.6.6).",
5
+ "description": "Catalog of conventional annotation fields skill-map ships out of the box, written into the `annotations:` block of a sidecar (`<basename>.sm`). Every field is OPTIONAL, a sidecar with an empty `annotations: {}` is valid. Schema is `additionalProperties: true` so users / plugins can add custom keys without coordination; the built-in `unknown-field` analyzer emits a warning on unrecognized keys (typo guard). The curated catalog is the load-bearing fields below, versioning (`version`, `stability`), provenance (`authors`, `license`, `source`, `sourceVersion`), taxonomy (`tags`), docs (`docsUrl`). The activity timestamp lives in the reserved `audit:` block (`audit.lastBumpedAt`), not in `annotations:`. Plugins that want first-class custom keys with their own validation declare `annotationContributions` in their manifest (see Step 9.6.6).",
6
6
  "type": "object",
7
7
  "additionalProperties": true,
8
8
  "properties": {
9
9
  "version": {
10
10
  "type": "integer",
11
11
  "minimum": 1,
12
- "description": "Monotonic counter. Bumped via the built-in `bump` Action when the underlying node changes meaningfully. Orthogonal to `stability`, `stability` carries the lifecycle stage; `version` is just a counter. There is no major: a change so big it would justify a major bump uses the convention `create a new node, supersede the old one` instead. Default: missing == unversioned."
12
+ "description": "Monotonic counter. Bumped via the built-in `bump` Action when the underlying node changes meaningfully. Orthogonal to `stability`, `stability` carries the lifecycle stage; `version` is just a counter. There is no major: a change so big it would justify a major bump uses the convention `create a new node and retire the old one` instead. Default: missing == unversioned."
13
13
  },
14
14
  "stability": {
15
15
  "type": "string",
16
16
  "enum": ["experimental", "stable", "deprecated"],
17
17
  "description": "Lifecycle stage. Denormalized into `scan_nodes.stability` for fast queries (see `node.schema.json` #/properties/stability). Default: missing == unspecified."
18
18
  },
19
- "supersedes": {
20
- "type": "array",
21
- "items": { "type": "string", "minLength": 1 },
22
- "description": "Paths (relative to scope root) of nodes this node replaces. Consumed by the built-in `superseded` analyzer and surfaces in `sm list --superseded`."
23
- },
24
- "supersededBy": {
25
- "type": "string",
26
- "minLength": 1,
27
- "description": "Path (relative to scope root) of the node that replaces this one. When set, the current node is end-of-life and consumers should migrate."
28
- },
29
19
  "authors": {
30
20
  "type": "array",
31
21
  "items": { "type": "string", "minLength": 1 },
@@ -127,7 +127,7 @@
127
127
  "description": "When present, the resolver ranks candidates whose `kind` appears earlier in this array ABOVE candidates whose `kind` appears later. Candidates whose `kind` is absent from the array drop to the end (after every listed kind). Example: a Provider that wants `invokes` edges to win against `mentions` and `references` of the same range declares `['invokes', 'references', 'mentions']`. Ties inside the same `kindPriority` bucket fall through to the confidence -> range length -> declaration order tiebreaks.",
128
128
  "items": {
129
129
  "type": "string",
130
- "enum": ["invokes", "references", "mentions", "supersedes", "points"]
130
+ "enum": ["invokes", "references", "mentions", "points"]
131
131
  }
132
132
  }
133
133
  }
@@ -10,7 +10,7 @@
10
10
  "analyzerId": {
11
11
  "type": "string",
12
12
  "pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$",
13
- "description": "Kebab-case identifier of the analyzer that emitted this issue (e.g. `name-collision`, `broken-ref`, `superseded`)."
13
+ "description": "Kebab-case identifier of the analyzer that emitted this issue (e.g. `name-collision`, `broken-ref`, `node-stability`)."
14
14
  },
15
15
  "severity": {
16
16
  "type": "string",
@@ -17,8 +17,8 @@
17
17
  },
18
18
  "kind": {
19
19
  "type": "string",
20
- "enum": ["invokes", "references", "mentions", "supersedes", "points"],
21
- "description": "Nature of the relation. `invokes` = execution-level call (e.g. slash command). `references` = explicit link (e.g. wikilink, @-directive). `mentions` = informal textual mention. `supersedes` = replaces another node (from `metadata.supersedes`). `points` = relative file path written inside a code region (backtick span / fenced block); coexists with `references` on the same `(source, target)` pair as a separate Link row (no merge, and `core/link-kind-conflict` does not treat the pair as a conflict)."
20
+ "enum": ["invokes", "references", "mentions", "points"],
21
+ "description": "Nature of the relation. `invokes` = execution-level call (e.g. slash command). `references` = explicit link (e.g. wikilink, @-directive). `mentions` = informal textual mention. `points` = relative file path written inside a code region (backtick span / fenced block); coexists with `references` on the same `(source, target)` pair as a separate Link row (no merge, and `core/link-kind-conflict` does not treat the pair as a conflict)."
22
22
  },
23
23
  "confidence": {
24
24
  "type": "number",
@@ -56,7 +56,7 @@
56
56
  },
57
57
  "kind": {
58
58
  "type": "string",
59
- "enum": ["invokes", "references", "mentions", "supersedes", "points"],
59
+ "enum": ["invokes", "references", "mentions", "points"],
60
60
  "description": "Proposed link kind, matching `link.schema.json#/properties/kind/enum`. Closed enum in v1; provider-specific kinds wait until a concrete need emerges."
61
61
  },
62
62
  "target": {