@skill-map/spec 0.39.0 → 0.41.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +55 -2307
  2. package/README.md +8 -11
  3. package/architecture.md +74 -51
  4. package/cli-contract.md +38 -9
  5. package/conformance/README.md +1 -1
  6. package/conformance/cases/extractor-emits-signal.json +1 -1
  7. package/conformance/cases/kernel-empty-boot.json +1 -1
  8. package/conformance/cases/no-global-scope.json +1 -1
  9. package/conformance/cases/orphan-markdown-fallback.json +1 -1
  10. package/conformance/cases/plugin-missing-ui-rejected.json +1 -1
  11. package/conformance/cases/sidecar-end-to-end.json +1 -1
  12. package/conformance/cases/signal-collision-detection.json +1 -1
  13. package/conformance/coverage.md +1 -1
  14. package/conformance/fixtures/sidecar-example/agent-example.sm +3 -3
  15. package/db-schema.md +21 -7
  16. package/index.json +56 -55
  17. package/package.json +3 -2
  18. package/plugin-author-guide.md +273 -776
  19. package/schemas/annotations.schema.json +2 -2
  20. package/schemas/api/rest-envelope.schema.json +1 -1
  21. package/schemas/bump-report.schema.json +1 -1
  22. package/schemas/conformance-case.schema.json +1 -1
  23. package/schemas/conformance-result.schema.json +1 -1
  24. package/schemas/execution-record.schema.json +1 -1
  25. package/schemas/extensions/action.schema.json +1 -1
  26. package/schemas/extensions/analyzer.schema.json +1 -1
  27. package/schemas/extensions/base.schema.json +1 -1
  28. package/schemas/extensions/extractor.schema.json +1 -1
  29. package/schemas/extensions/formatter.schema.json +1 -1
  30. package/schemas/extensions/hook.schema.json +1 -1
  31. package/schemas/extensions/provider-kind.schema.json +1 -1
  32. package/schemas/extensions/provider.schema.json +1 -1
  33. package/schemas/frontmatter/base.schema.json +2 -7
  34. package/schemas/history-stats.schema.json +1 -1
  35. package/schemas/input-types.schema.json +1 -1
  36. package/schemas/issue.schema.json +1 -1
  37. package/schemas/job.schema.json +1 -1
  38. package/schemas/link.schema.json +1 -1
  39. package/schemas/node.schema.json +1 -1
  40. package/schemas/plugins-doctor.schema.json +1 -1
  41. package/schemas/plugins-registry.schema.json +1 -1
  42. package/schemas/project-config.schema.json +1 -1
  43. package/schemas/refresh-report.schema.json +1 -1
  44. package/schemas/report-base-deterministic.schema.json +1 -1
  45. package/schemas/report-base.schema.json +1 -1
  46. package/schemas/scan-result.schema.json +1 -1
  47. package/schemas/sidecar.schema.json +1 -1
  48. package/schemas/signal.schema.json +1 -1
  49. package/schemas/summaries/agent.schema.json +1 -1
  50. package/schemas/summaries/command.schema.json +1 -1
  51. package/schemas/summaries/hook.schema.json +1 -1
  52. package/schemas/summaries/markdown.schema.json +1 -1
  53. package/schemas/summaries/skill.schema.json +1 -1
  54. package/schemas/user-settings.schema.json +32 -1
  55. package/schemas/view-slots.schema.json +1 -1
  56. package/telemetry.md +294 -0
  57. package/versioning.md +2 -2
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/annotations.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/annotations.schema.json",
4
4
  "title": "Annotations",
5
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).",
6
6
  "type": "object",
@@ -49,7 +49,7 @@
49
49
  "tags": {
50
50
  "type": "array",
51
51
  "items": { "type": "string", "minLength": 1 },
52
- "description": "**User-supplied** taxonomy tags. Skill-map's tag system is dual-source: this field carries the user's post-hoc tags (curator's view of the node from the `.sm` sidecar); `frontmatter.tags` carries the author's tags (intrinsic categories written in the `.md`). Both surfaces are first-class, `sm list --tag <name>` and UI faceted search match the union and the UI distinguishes them visually (`sm list --tag-source author|user` filters one source). Empty array and missing field are equivalent."
52
+ "description": "Taxonomy tags for this node. Tags are a **skill-map concept**, not a vendor field: no agent format carries `tags` in frontmatter, so they live here in the `.sm` sidecar. Written by whoever curates the project. `sm list --tag <name>` and the UI's faceted search match this field; the UI renders them as chips on the node card and inspector. Empty array and missing field are equivalent."
53
53
  },
54
54
  "docsUrl": {
55
55
  "type": "string",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/api/rest-envelope.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/api/rest-envelope.schema.json",
4
4
  "title": "RestEnvelope",
5
5
  "description": "Wrapper shape for REST responses under `/api/*` (Step 14.2). Five variants distinguished by the `kind` discriminator and which payload field is present (`items` for list kinds AND `'annotations.registered'`, `item` for single-resource kinds, `value` for `kind: 'config'` and `'sidecar.bumped'`). The `/api/scan` and `/api/health` responses are exempt, they carry the underlying `ScanResult` / `IHealthResponse` shape directly. The `/api/graph` response is also exempt, it returns the formatter's native textual output (text/plain or text/markdown). Step 14.5.d adds the required `kindRegistry` field on every payload-bearing list / single / config variant so the UI can render Provider-declared kinds (label, color, icon) without hardcoding visuals; the sibling `providerRegistry` field carries the registered Providers' own identity (label, color, chip visibility) so the UI renders the active-lens dropdown and the per-node provider chip from the real Provider set instead of a hardcoded list. Sentinel kinds (`health`, `scan`, `graph`) stay exempt because they don't carry an envelope payload. Step 9.6 closes the `'sidecar.bumped'` (R7) and `'annotations.registered'` (R7) gaps, both are payload-bearing but carry their own variant shapes (sidecar.bumped: `value` + `elapsedMs`, no `filters`/`counts`/`kindRegistry`; annotations.registered: `items` + `counts.total`, no `filters`/`kindRegistry`) because they project read-only kernel surfaces orthogonal to the kindRegistry. The change keeps `schemaVersion` at `'1'`, the BFF is greenfield (no released consumers depend on the prior shape), so a versioned migration buys nothing.",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/bump-report.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/bump-report.schema.json",
4
4
  "title": "BumpReport",
5
5
  "description": "Report shape produced by the built-in deterministic `node-bump` Action (Step 9.6.3, Decision #125). Extends `report-base-deterministic.schema.json` (the deterministic counterpart to `report-base.schema.json`, which carries the LLM-only `confidence` + `safety` fields). The `node-bump` Action returns one of three concrete shapes, distinguished by `ok` / `noop` / `reason`: success-with-write (`{ ok: true, version }`), silent-no-op under `force` (`{ ok: true, noop: true }`), or refusal (`{ ok: false, reason: 'fresh' }`).",
6
6
  "allOf": [{ "$ref": "report-base-deterministic.schema.json" }],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/conformance-case.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/conformance-case.schema.json",
4
4
  "title": "ConformanceCase",
5
5
  "description": "Shape of a single declarative test case under `spec/conformance/cases/<id>.json`. Consumed by language-neutral conformance runners. See `spec/conformance/README.md` for the runner contract.",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/conformance-result.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/conformance-result.schema.json",
4
4
  "title": "ConformanceResult",
5
5
  "description": "Machine-readable output of `sm conformance run --json`. Aggregates pass / fail totals across the selected scope set plus per-scope and per-case breakdowns. The `elapsedMs` top-level field is the command's own wall-clock (see `cli-contract.md` §Elapsed time).",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/execution-record.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/execution-record.schema.json",
4
4
  "title": "ExecutionRecord",
5
5
  "description": "A single row in the execution history (`state_executions`). One record per action invocation, regardless of whether the runner was CLI, Skill, or in-process.",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/extensions/action.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/extensions/action.schema.json",
4
4
  "title": "ExtensionAction",
5
5
  "description": "Manifest shape for an `Action` extension. An action operates on one or more nodes in one of two modes: `deterministic` (code runs in-process, returns a report JSON directly) or `probabilistic` (kernel renders a prompt, a runner executes it against an LLM, the callback closes the job). **Structure-as-truth files**: every Action carries `<action-dir>/report.schema.json` (the JSON Schema for the report, MUST extend `report-base.schema.json`); probabilistic Actions additionally carry `<action-dir>/prompt.md` (the prompt template). The kernel resolves both by convention; missing or mis-placed files surface as `load-error`. A deterministic Action with a `prompt.md` in its folder is also `load-error` (config inconsistent). **`prob*` prefix convention**: manifest fields that only apply when `mode=probabilistic` start with `prob`; if a deterministic-only field ever appears, it starts with `det`.",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/extensions/analyzer.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/extensions/analyzer.schema.json",
4
4
  "title": "ExtensionAnalyzer",
5
5
  "description": "Manifest shape for an `Analyzer` extension. An analyzer consumes the full graph (nodes + links) after all extractors have run, emits `Issue[]`, and MAY emit view contributions to project findings into the UI. Analyzers are dual-mode: `deterministic` analyzers MUST be byte-for-byte reproducible (same graph in → same issues out; time, random, and network are forbidden) and run synchronously inside `sm check` / `sm scan`; `probabilistic` analyzers invoke an LLM through the kernel's `RunnerPort` and execute only as queued jobs (`sm job submit analyzer:<id>`); their output MAY vary across runs and they NEVER participate in `sm scan`. Each issue emitted is tagged with `analyzer_id = <plugin-id>/<extension-id>` by default (the extension's qualified id, derived from structure); analyzers that need to discriminate sub-types append `:<sub-id>` at emit time. Severity is set per-emit (no manifest-level default).",
6
6
  "allOf": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/extensions/base.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/extensions/base.schema.json",
4
4
  "title": "ExtensionBase",
5
5
  "description": "Base manifest shape common to every extension kind. Kind-specific schemas (`provider`, `extractor`, `analyzer`, `action`, `formatter`, `hook`) extend this via `allOf` and add a kind-specific shape. Both `id` and `kind` are derived from the filesystem structure (`<plugin>/<kind-plural>/<id>/index.ts`, where the parent folder dictates the kind and the leaf folder dictates the id), so they are NOT manifest fields. Manifests carrying `id` or `kind` are rejected as `invalid-manifest`. Closed-content enforcement (unknown keys = bug) lives on the kind schemas via `unevaluatedProperties: false`; those see base's evaluated keys through the `allOf` composition.",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/extensions/extractor.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/extensions/extractor.schema.json",
4
4
  "title": "ExtensionExtractor",
5
5
  "description": "Manifest shape for an `Extractor` extension. An extractor consumes a parsed node (frontmatter + body) and emits output through three context-supplied callbacks rather than returning a value: `ctx.emitLink(link)` writes to the kernel's `links` table (validated against the global closed enum of link kinds before persistence; per-extractor whitelisting was retired with structure-as-truth, the global enum is the contract), `ctx.enrichNode(partial)` merges author-canonical properties into the kernel's enrichment layer (separate from the author-supplied frontmatter), `ctx.emitContribution(id, payload)` emits per-node view contributions validated against the slot payload schema, and `ctx.store` persists into the plugin's own KV namespace or dedicated tables. The runtime method is `extract(ctx) → void`. Extractors run in isolation: they MUST NOT read other nodes, the graph, or the DB. Cross-node reasoning lives in Analyzers. Extractors are deterministic-only: pure code, runs synchronously inside `sm scan`, same input → same output every run. LLM-driven enrichment of a node is an Action concern (queued as a job), not an Extractor concern.",
6
6
  "allOf": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/extensions/formatter.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/extensions/formatter.schema.json",
4
4
  "title": "ExtensionFormatter",
5
5
  "description": "Manifest shape for a `Formatter` extension. A formatter serializes the graph (or a filtered subgraph) into a string in a declared format. The format id comes from the formatter's folder name (structure-as-truth, `<plugin>/formatters/<formatId>/index.ts`), it is NOT a manifest field. Invoked by `sm graph --format <formatId>` and `sm export`. Formatters are deterministic-only, they sit at the graph-to-string boundary and their output MUST be byte-deterministic for the same input graph (the snapshot-test suite relies on this). The `mode` field MUST NOT appear in formatter manifests. Probabilistic narrators of the graph are a valid product but they live in jobs and emit Findings, not in formatters. All formatters accept the `--filter` expression; opting out is no longer supported.",
6
6
  "allOf": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/extensions/hook.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/extensions/hook.schema.json",
4
4
  "title": "ExtensionHook",
5
5
  "description": "Manifest shape for a `Hook` extension. Subscribes declaratively to a curated set of kernel lifecycle events. **Hooks are deterministic-only** since the structure-as-truth refactor: the `mode` field was removed; LLM-dependent lifecycle behaviour is modeled as a deterministic hook that enqueues a probabilistic Action via `ctx.queue('<plugin>/<action>', payload)`. Hooks react to events; they cannot block or alter the main pipeline. The set of hookable triggers is intentionally small, ten events out of the full job-events catalog. Eight are pipeline-driven (emitted from inside `runScan`); two (`boot`, `shutdown`) are CLI-process-driven (emitted by the driving binary before / after the verb runs, fire-and-forget so `process.exit` is never blocked). Other events (per-node `scan.progress`, `model.delta`, `run.*`, internal job lifecycle) are deliberately not hookable: too verbose for a reactive surface, internal to the runner, or covered elsewhere. Declaring a trigger outside the hookable set yields `invalid-manifest` at load time.",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/extensions/provider-kind.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/extensions/provider-kind.schema.json",
4
4
  "title": "ProviderKindMetadata",
5
5
  "description": "Per-kind UI metadata written as `<plugin>/kinds/<kindName>/kind.json`. Lives next to the kind's frontmatter `schema.json` under the kind folder; together they are the structure-as-truth replacement for the old `kinds` map inside the Provider manifest. Reaches the UI via the `kindRegistry` field embedded in REST envelopes (`api/rest-envelope.schema.json`). The kind name is the folder name; it is NOT repeated as a field here.",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/extensions/provider.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/extensions/provider.schema.json",
4
4
  "title": "ExtensionProvider",
5
5
  "description": "Manifest shape for a `Provider` extension. A Provider declares its own universe: the platform it recognises (Claude Code, Codex, Antigravity, Obsidian vault, generic MD), the catalog of node `kind`s it emits, and the per-kind frontmatter schema each kind follows. **Structure-as-truth**: exactly one Provider lives in each plugin that carries one, declared as `<plugin>/provider.ts`. The kinds catalog lives as folders under `<plugin>/kinds/<kindName>/` and the loader discovers each entry by walking that directory; the manifest itself NO LONGER carries a `kinds` map. Each kind folder MUST contain `schema.json` (the kind's frontmatter JSON Schema, extending `frontmatter/base.schema.json` via `allOf` + `$ref`) and `kind.json` (UI metadata under `{ ui: {...} }`). The kernel resolves these at boot time and registers each schema with AJV for scan-time validation. Exactly zero or one Provider MUST match any given file; multiple matches → `provider-ambiguous` issue, file unclassified. **`roots` is enforcement-grade**: a Provider declaring `roots` only receives files matching at least one glob; a Provider without `roots` acts as a fallback for files unmatched by any other Provider's roots. Providers are deterministic-only, they sit at the filesystem boundary and run during boot; probabilistic classification would make boot slow, costly, and non-reproducible. The `mode` field MUST NOT appear in Provider manifests. If you need LLM-assisted classification, write a probabilistic Action that runs as a queued job and writes back through the enrichment layer; Extractors are deterministic-only and Providers stay on the deterministic boot path. Distinct from the **hexagonal-architecture** 'adapter' (`RunnerPort.adapter`, `StoragePort.adapter`, etc.), which is an internal driven-adapter implementing a port, Providers live in the extension surface, hexagonal adapters live in `src/kernel/adapters/`.",
6
6
  "allOf": [
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/frontmatter/base.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/frontmatter/base.schema.json",
4
4
  "title": "FrontmatterBase",
5
- "description": "Universal frontmatter shape every Provider's per-kind schema extends via `allOf` + `$ref` to this `$id`. `name` and `description` are the two universally required fields; `tags` is a universally accepted optional field for author-supplied taxonomy. Per-vendor schemas (Anthropic Claude, Cursor, Obsidian, …) declare additional fields on top via the per-kind extension. `additionalProperties: true` is intentional: skill-map AGGREGATES vendor specs, it does not curate them. Vendor-specific fields (`tools`, `allowedTools`, `model`, etc.) flow through validation silently because the per-kind extension declares them.",
5
+ "description": "Universal frontmatter shape every Provider's per-kind schema extends via `allOf` + `$ref` to this `$id`. `name` and `description` are the only universal fields, confirmed by cross-vendor research: `description` is the single field every format carries, and `name` is universal among formats with explicit identifiers. Everything else is vendor idiosyncrasy and lives on the per-vendor per-kind schema, NOT here. (Taxonomy `tags`, for example, is a skill-map concept with no vendor frontmatter analog, so it lives in the `.sm` sidecar `annotations.tags`, not here.) Per-vendor schemas (Anthropic Claude, Cursor, Obsidian, the Agent Skills open standard, …) declare their own fields on top via the per-kind extension. `additionalProperties: true` is intentional: skill-map AGGREGATES vendor specs, it does not curate them. Vendor-specific fields (`tools`, `allowedTools`, `model`, etc.) flow through validation silently because the per-kind extension declares them.",
6
6
  "type": "object",
7
7
  "required": ["name", "description"],
8
8
  "additionalProperties": true,
@@ -16,11 +16,6 @@
16
16
  "type": "string",
17
17
  "minLength": 1,
18
18
  "description": "One-to-three-sentence description. REQUIRED."
19
- },
20
- "tags": {
21
- "type": "array",
22
- "items": { "type": "string", "minLength": 1 },
23
- "description": "**Author-supplied** taxonomy tags written in the markdown frontmatter, what the file's author considers the node's intrinsic categories. Skill-map's tag system is **dual-source**: this field carries author tags; `sidecar.annotations.tags` carries user tags (post-hoc, written by whoever curates the project from their `.sm` sidecar). Both surfaces are first-class, searches and listings (`sm list --tag`, UI faceted search) match the union and the UI distinguishes them visually so the attribution stays explicit. Empty array and missing field are equivalent."
24
19
  }
25
20
  }
26
21
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/history-stats.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/history-stats.schema.json",
4
4
  "title": "HistoryStats",
5
5
  "description": "Machine-readable output of `sm history stats --json`. Aggregates over `state_executions` within a time window. camelCase keys throughout. The `elapsedMs` top-level field is the command's own wall-clock (see `cli-contract.md` §Elapsed time), distinct from `totals.durationMsTotal`, which is the sum of every execution record's duration.",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/input-types.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/input-types.schema.json",
4
4
  "title": "InputTypes",
5
5
  "description": "Closed catalog of input-types for plugin settings. The plugin author declares each user-configurable setting in the manifest's `settings` map by picking an `input-type` from this catalog; the kernel knows the schema for each type, the UI ships a generated form per type, and the CLI's `sm plugins config <id>` command exposes the same surface. Plugin authors NEVER write JSON Schema for settings, they pick a type by name and supply per-type parameters (label, default, min/max, options for enums, etc.). Closed catalog by design: every new input-type requires spec + UI form + CLI prompter + tests. Versioned via the manifest field `catalogCompat` (semver against the catalog as a whole). For the rationale and open issues, see ROADMAP.md §UI contribution system.",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/issue.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/issue.schema.json",
4
4
  "title": "Issue",
5
5
  "description": "Deterministic finding emitted by a analyzer when evaluating the graph. Not to be confused with `Finding`, which is probabilistic (LLM-produced).",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/job.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/job.schema.json",
4
4
  "title": "Job",
5
5
  "description": "Row in `state_jobs`. Non-terminal state until it reaches `completed` or `failed`, at which point an `ExecutionRecord` is also written.",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/link.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/link.schema.json",
4
4
  "title": "Link",
5
5
  "description": "Directed relation between two nodes, produced by one or more extractors during a scan.",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/node.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/node.schema.json",
4
4
  "title": "Node",
5
5
  "description": "A single entity in the graph. Typically a file on disk (a markdown skill, an agent, a TOML sub-agent definition, a plain-markdown note), but MAY also be a **virtual / derived** entity that lives only in memory and is reconstructed from one or more source files on every scan (e.g. an MCP server node derived from `settings.json` / `mcp.json` / `config.toml`). Virtual nodes carry `virtual: true` and use a synthetic `path` scheme (`mcp://<name>`, etc.). The `kind` is whatever the classifying Provider declares, open by design; the **built-in Claude Provider** emits `skill` / `agent` / `command` / `markdown` today, but external Providers (Cursor, Obsidian, …) MAY emit their own. Format-named kinds (`markdown`, future `toml`, future `json`) are reserved for the generic fallback only, when a file matches a specific role (agent / command / skill) that classification prevails over format naming.",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/plugins-doctor.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/plugins-doctor.schema.json",
4
4
  "title": "PluginsDoctorReport",
5
5
  "description": "Machine-readable output of `sm plugins doctor --json`. Aggregates per-status counts across built-in and drop-in plugins plus the structured issue / warning lists the human renderer produces. The `elapsedMs` top-level field is the command's own wall-clock (see `cli-contract.md` §Elapsed time).",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/plugins-registry.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/plugins-registry.schema.json",
4
4
  "title": "PluginsRegistry",
5
5
  "description": "Two shapes in one file: (1) the per-plugin manifest that authors ship as `plugin.json` (see `$defs/PluginManifest`); (2) the aggregate registry the implementation produces on disk (`<cwd>/.skill-map/plugins.json`), which lists all discovered plugins with their compat status. Both shapes are normative. camelCase keys throughout.",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/project-config.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/project-config.schema.json",
4
4
  "title": "ProjectConfig",
5
5
  "description": "Shape of `.skill-map/settings.json` (and its `.skill-map/settings.local.json` partner) inside a scope. Loaded by the layered config hierarchy (library defaults → user → user-local → project → project-local → env/flags) and deep-merged per key. All fields optional; defaults apply when absent. camelCase keys throughout, consistent with the rest of the spec.",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/refresh-report.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/refresh-report.schema.json",
4
4
  "title": "RefreshReport",
5
5
  "description": "Machine-readable output of `sm refresh <node.path> --json` and `sm refresh --stale --json`. Reports the count of enrichment rows persisted across the targeted node set (universal enrichment layer per `architecture.md` §A.8). The `elapsedMs` top-level field is the command's own wall-clock (see `cli-contract.md` §Elapsed time).",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/report-base-deterministic.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/report-base-deterministic.schema.json",
4
4
  "title": "ReportBaseDeterministic",
5
5
  "description": "Universal base for deterministic Action reports. Every deterministic Action's report MUST extend this base via `allOf` + `$ref`. Symmetric with `report-base.schema.json` (the probabilistic / LLM base, which carries `confidence` + `safety`); deterministic vs probabilistic is the orthogonal axis declared by the Action manifest's `mode` field. Fields: `ok` (boolean, did the Action complete its logical work?), plus action-specific keys via `additionalProperties: true`. Action-specific shapes (e.g. bump's `version` / `noop` / `reason`) ride on the open extension.",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/report-base.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/report-base.schema.json",
4
4
  "title": "ReportBase",
5
5
  "description": "Base shape for any probabilistic report produced by an LLM-backed action (summarizers, `sm what`, `sm cluster-triggers`, etc.). All per-kind summary schemas under `summaries/` extend this. Kernel validates the `confidence` and `safety` fields regardless of action-specific extensions.",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/scan-result.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/scan-result.schema.json",
4
4
  "title": "ScanResult",
5
5
  "description": "Canonical output of `sm scan --json` (and the data shape sent over WebSocket scan events). Self-describing and versioned; consumers MUST check `schemaVersion` before parsing.",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/sidecar.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/sidecar.schema.json",
4
4
  "title": "Sidecar",
5
5
  "description": "Root shape of a co-located YAML sidecar (`<basename>.sm` next to `<basename>.md`). The `.sm` file IS the annotations file, every key under it is, conceptually, an annotation on the node. The YAML root organizes those annotations into structural blocks: `identity` (anchor + drift-detection hashes), `annotations` (the curated catalog of conventional fields), `audit` (timestamps), `settings` (reserved), and arbitrary `<plugin-id>:` namespaces for plugin-contributed data. Vendor file (`<basename>.md`) stays untouched. Schema is `additionalProperties: true` so plugins can add namespaces without coordination; the built-in `unknown-field` analyzer warns on truly unrecognized root keys (typo guard). Format is YAML, comments via `#`, multiline strings via `|` / `>`, permissive types per the YAML 1.2 spec. See `architecture.md` §Annotation system and ROADMAP §Step 9.6 for the design rationale.",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/signal.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/signal.schema.json",
4
4
  "title": "Signal",
5
5
  "description": "Intermediate Representation (IR) emitted by extractors during a scan. A Signal is a *candidate* detection: zero, one, or many interpretations of the same piece of source text or structured data. The kernel's resolver phase consumes `Signal[]` and produces final `Link[]` by selecting a winning candidate per Signal (or rejecting all and emitting none) using the active Provider's resolution rules. Opt-in for plugin authors: an extractor MAY emit `Signal`s via `ctx.emitSignal()` when the detection carries genuine ambiguity (multiple plausible kinds, multiple plausible targets, byte-range awareness for collision detection), OR continue calling `ctx.emitLink()` directly when its detection is unambiguous. The two paths coexist; resolved Link rows look identical regardless of origin. Stability: experimental.",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/summaries/agent.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/summaries/agent.schema.json",
4
4
  "title": "SummaryAgent",
5
5
  "description": "Report produced by `agent-summarizer` for a single `agent` node. Extends `report-base.schema.json`. Stability: experimental.",
6
6
  "allOf": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/summaries/command.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/summaries/command.schema.json",
4
4
  "title": "SummaryCommand",
5
5
  "description": "Report produced by `command-summarizer` for a single `command` node. Extends `report-base.schema.json`. Stability: experimental.",
6
6
  "allOf": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/summaries/hook.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/summaries/hook.schema.json",
4
4
  "title": "SummaryHook",
5
5
  "description": "Report produced by `hook-summarizer` for a single `hook` node. Extends `report-base.schema.json`. Stability: experimental.",
6
6
  "allOf": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/summaries/markdown.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/summaries/markdown.schema.json",
4
4
  "title": "SummaryMarkdown",
5
5
  "description": "Report produced by `markdown-summarizer` for a single `markdown` node (the format-named generic fallback owned by the built-in `core/markdown` Provider, see `architecture.md` §Provider · dispatch order). Extends `report-base.schema.json`. Stability: experimental.",
6
6
  "allOf": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/summaries/skill.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/summaries/skill.schema.json",
4
4
  "title": "SummarySkill",
5
5
  "description": "Report produced by `skill-summarizer` for a single `skill` node. Extends `report-base.schema.json`. Stability: experimental, field set may tighten as real summarizer output stabilizes.",
6
6
  "allOf": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/user-settings.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/user-settings.schema.json",
4
4
  "title": "UserSettings",
5
5
  "description": "Per-user, per-machine settings file persisted at `~/.skill-map/settings.json`. Holds the small set of preferences that genuinely belong to the operator (not to a project) plus the bookkeeping each one needs. The file is NOT part of the project config layer system (no merge, no PROJECT_LOCAL_ONLY_KEYS interaction); it is read directly by the few modules that own a user-scope feature. See `spec/cli-contract.md` §Scope is always project-local for the broader principle: skill-map never reads `$HOME` by default, this file is the narrow, documented exception. There is intentionally no `.local` partner; values here are already per-machine, so the project / project-local split would have no meaning.",
6
6
  "type": "object",
@@ -34,6 +34,37 @@
34
34
  "description": "Unix milliseconds of the last banner emission to stderr. `null` (or absent) when never shown. Used so a single probe does not re-emit the banner across back-to-back `sm` invocations."
35
35
  }
36
36
  }
37
+ },
38
+ "telemetry": {
39
+ "type": "object",
40
+ "additionalProperties": false,
41
+ "description": "User consent + prompt bookkeeping for the opt-in, anonymous telemetry surfaces (see `spec/telemetry.md`). All surfaces default OFF and run only after explicit consent. Three independent toggles (`errorsEnabled` for Sentry error reporting, `usageCliEnabled` / `usageUiEnabled` for PostHog usage analytics) plus the usage `distinct_id` (`anonymousId`) and the shared prompt bookkeeping (`firstRunAt`, `promptedAt`) the CLI maintains so it asks once, at the right time.",
42
+ "properties": {
43
+ "errorsEnabled": {
44
+ "type": "boolean",
45
+ "description": "Operator opt-in toggle for error reporting (Sentry). **Default OFF**: when absent or `false`, no Sentry SDK is initialised and no event leaves the machine. Set to `true` only after explicit consent (the consent prompt or Settings UI). The `SKILL_MAP_TELEMETRY=0` env var forces OFF regardless of this value."
46
+ },
47
+ "usageCliEnabled": {
48
+ "type": "boolean",
49
+ "description": "Operator opt-in toggle for CLI usage analytics (PostHog). **Default OFF**: when absent or `false`, no PostHog SDK is initialised in the CLI and no usage event leaves the machine. Independent of `errorsEnabled` and `usageUiEnabled`; the `SKILL_MAP_TELEMETRY=0` env var forces OFF regardless."
50
+ },
51
+ "usageUiEnabled": {
52
+ "type": "boolean",
53
+ "description": "Operator opt-in toggle for UI usage analytics (PostHog). **Default OFF**: when absent or `false`, the browser never loads the PostHog SDK. Independent of `errorsEnabled` and `usageCliEnabled`; the `SKILL_MAP_TELEMETRY=0` env var forces OFF regardless."
54
+ },
55
+ "anonymousId": {
56
+ "type": ["string", "null"],
57
+ "description": "Random UUID v4 used as the PostHog `distinct_id` for the usage surface, shared by the CLI and UI so both are attributed to one install. Carries no personal data. Minted exactly once, the first time any usage toggle becomes `true`, and never regenerated. The single anonymous correlation id the contract permits, scoped to usage only; the BFF exposes it read-only and it is never writable over the wire. `null` (or absent) until usage is first enabled."
58
+ },
59
+ "firstRunAt": {
60
+ "type": ["integer", "null"],
61
+ "description": "Unix milliseconds of the first run on which the consent prompt was eligible to appear (interactive TTY, at least one carrier configured, not opted-out by env, not yet answered). The prompt is intentionally deferred to the NEXT eligible run so it does not stack on top of the first-run provider-lens prompt. `null` (or absent) before any eligible run."
62
+ },
63
+ "promptedAt": {
64
+ "type": ["integer", "null"],
65
+ "description": "Unix milliseconds of the moment the shared consent prompt was shown. `null` (or absent) when the user has never been prompted. Once set, the prompt is never shown again, the persisted toggles are authoritative."
66
+ }
67
+ }
37
68
  }
38
69
  }
39
70
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://skill-map.dev/spec/v0/view-slots.schema.json",
3
+ "$id": "https://skill-map.ai/spec/v0/view-slots.schema.json",
4
4
  "title": "ViewSlots",
5
5
  "description": "Closed catalog of view slots. A view slot is a kernel-published handle that names a visual surface in the UI, fixes the renderer that draws there, and fixes the payload shape the plugin emits. The plugin author picks ONE slot per view contribution; the kernel validates `ctx.emitContribution(id, payload)` against that slot's payload schema in `$defs.payloads`. There is no separate notion of a 'contract', the slot IS the contract. Closed catalog by design: every new slot requires a spec change + UI renderer mount + scaffolder support + conformance fixtures + tests. Compounds catalog evolution cost; see ROADMAP.md §UI contribution system → 'Known limitations carried forward'. Slots are versioned via the manifest field `catalogCompat` (semver against the catalog as a whole), not per-slot.",
6
6
  "type": "object",