@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.
- package/CHANGELOG.md +55 -2307
- package/README.md +8 -11
- package/architecture.md +74 -51
- package/cli-contract.md +38 -9
- package/conformance/README.md +1 -1
- package/conformance/cases/extractor-emits-signal.json +1 -1
- package/conformance/cases/kernel-empty-boot.json +1 -1
- package/conformance/cases/no-global-scope.json +1 -1
- package/conformance/cases/orphan-markdown-fallback.json +1 -1
- package/conformance/cases/plugin-missing-ui-rejected.json +1 -1
- package/conformance/cases/sidecar-end-to-end.json +1 -1
- package/conformance/cases/signal-collision-detection.json +1 -1
- package/conformance/coverage.md +1 -1
- package/conformance/fixtures/sidecar-example/agent-example.sm +3 -3
- package/db-schema.md +21 -7
- package/index.json +56 -55
- package/package.json +3 -2
- package/plugin-author-guide.md +273 -776
- package/schemas/annotations.schema.json +2 -2
- package/schemas/api/rest-envelope.schema.json +1 -1
- package/schemas/bump-report.schema.json +1 -1
- package/schemas/conformance-case.schema.json +1 -1
- package/schemas/conformance-result.schema.json +1 -1
- package/schemas/execution-record.schema.json +1 -1
- package/schemas/extensions/action.schema.json +1 -1
- package/schemas/extensions/analyzer.schema.json +1 -1
- package/schemas/extensions/base.schema.json +1 -1
- package/schemas/extensions/extractor.schema.json +1 -1
- package/schemas/extensions/formatter.schema.json +1 -1
- package/schemas/extensions/hook.schema.json +1 -1
- package/schemas/extensions/provider-kind.schema.json +1 -1
- package/schemas/extensions/provider.schema.json +1 -1
- package/schemas/frontmatter/base.schema.json +2 -7
- package/schemas/history-stats.schema.json +1 -1
- package/schemas/input-types.schema.json +1 -1
- package/schemas/issue.schema.json +1 -1
- package/schemas/job.schema.json +1 -1
- package/schemas/link.schema.json +1 -1
- package/schemas/node.schema.json +1 -1
- package/schemas/plugins-doctor.schema.json +1 -1
- package/schemas/plugins-registry.schema.json +1 -1
- package/schemas/project-config.schema.json +1 -1
- package/schemas/refresh-report.schema.json +1 -1
- package/schemas/report-base-deterministic.schema.json +1 -1
- package/schemas/report-base.schema.json +1 -1
- package/schemas/scan-result.schema.json +1 -1
- package/schemas/sidecar.schema.json +1 -1
- package/schemas/signal.schema.json +1 -1
- package/schemas/summaries/agent.schema.json +1 -1
- package/schemas/summaries/command.schema.json +1 -1
- package/schemas/summaries/hook.schema.json +1 -1
- package/schemas/summaries/markdown.schema.json +1 -1
- package/schemas/summaries/skill.schema.json +1 -1
- package/schemas/user-settings.schema.json +32 -1
- package/schemas/view-slots.schema.json +1 -1
- package/telemetry.md +294 -0
- package/versioning.md +2 -2
package/README.md
CHANGED
|
@@ -60,8 +60,9 @@ spec/ ← published as @skill-map/spec
|
|
|
60
60
|
├── [db-schema.md](./db-schema.md) ← table catalog (kernel-owned)
|
|
61
61
|
├── [plugin-kv-api.md](./plugin-kv-api.md) ← ctx.store contract for storage mode A
|
|
62
62
|
├── [job-lifecycle.md](./job-lifecycle.md) ← queued → running → completed | failed
|
|
63
|
+
├── [telemetry.md](./telemetry.md) ← opt-in error reporting (default OFF)
|
|
63
64
|
│
|
|
64
|
-
├── schemas/ ←
|
|
65
|
+
├── schemas/ ← JSON Schemas, draft 2020-12, camelCase keys (authoritative list + sha256 in index.json)
|
|
65
66
|
│ ├── node.schema.json ┐
|
|
66
67
|
│ ├── link.schema.json │
|
|
67
68
|
│ ├── issue.schema.json │
|
|
@@ -74,13 +75,9 @@ spec/ ← published as @skill-map/spec
|
|
|
74
75
|
│ ├── conformance-case.schema.json │
|
|
75
76
|
│ ├── history-stats.schema.json ┘
|
|
76
77
|
│ │
|
|
77
|
-
│ ├── extensions/ ← one per
|
|
78
|
-
│ │
|
|
79
|
-
│ │
|
|
80
|
-
│ │ ├── extractor.schema.json │ 6 extension schemas
|
|
81
|
-
│ │ ├── analyzer.schema.json │ (base + 5 kinds)
|
|
82
|
-
│ │ ├── action.schema.json │
|
|
83
|
-
│ │ └── formatter.schema.json ┘
|
|
78
|
+
│ ├── extensions/ ← base + one per kind (provider, extractor,
|
|
79
|
+
│ │ analyzer, action, formatter, hook) +
|
|
80
|
+
│ │ provider-kind.schema.json; validated at plugin load
|
|
84
81
|
│ │
|
|
85
82
|
│ ├── frontmatter/ ← user-authored; additionalProperties: true
|
|
86
83
|
│ │ └── base.schema.json ← universal shape; per-kind schemas live with
|
|
@@ -137,10 +134,10 @@ npm i @skill-map/spec
|
|
|
137
134
|
import specIndex from '@skill-map/spec';
|
|
138
135
|
import nodeSchema from '@skill-map/spec/schemas/node.schema.json' with { type: 'json' };
|
|
139
136
|
|
|
140
|
-
console.log(specIndex.specPackageVersion); //
|
|
137
|
+
console.log(specIndex.specPackageVersion); // npm package version; source of truth for `spec` in `sm version`
|
|
141
138
|
console.log(specIndex.indexPayloadVersion); // → "0.0.1" (payload shape of `index.json` itself; bumps only when this manifest's structure changes)
|
|
142
139
|
console.log(specIndex.integrity.algorithm); // → "sha256"
|
|
143
|
-
console.log(nodeSchema.$id); // → "https://skill-map.
|
|
140
|
+
console.log(nodeSchema.$id); // → "https://skill-map.ai/spec/v0/node.schema.json"
|
|
144
141
|
```
|
|
145
142
|
|
|
146
143
|
Every JSON Schema is exported individually via `@skill-map/spec/schemas/*.json`. Prose documents ship in the tarball for reference but are not `exports`-surfaced.
|
|
@@ -161,7 +158,7 @@ console.log(actual === index.integrity.files[file] ? 'ok' : 'drift');
|
|
|
161
158
|
|
|
162
159
|
### JSON Schema Store
|
|
163
160
|
|
|
164
|
-
The schemas will be registered on JSON Schema Store once the canonical URLs under `skill-map.
|
|
161
|
+
The schemas will be registered on JSON Schema Store once the canonical URLs under `skill-map.ai/spec/v0/` are stable (Step 14).
|
|
165
162
|
|
|
166
163
|
## License
|
|
167
164
|
|
package/architecture.md
CHANGED
|
@@ -8,30 +8,57 @@ Any conforming implementation, reference or third-party, MUST respect these boun
|
|
|
8
8
|
|
|
9
9
|
## Layering
|
|
10
10
|
|
|
11
|
+
```mermaid
|
|
12
|
+
flowchart TB
|
|
13
|
+
subgraph DRIVERS["Driving adapters (primary)"]
|
|
14
|
+
direction LR
|
|
15
|
+
CLI["CLI<br/><i>sm command</i>"]
|
|
16
|
+
SERVER["Server<br/><i>Hono BFF (src/server/)</i>"]
|
|
17
|
+
SKILL["Skill<br/><i>agent / IDE</i>"]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
UI["UI · Angular SPA<br/><i>(ui/)</i>"]:::ui
|
|
21
|
+
UI -.->|"HTTP / WS"| SERVER
|
|
22
|
+
|
|
23
|
+
subgraph KERNEL["Kernel (domain-pure, hexagonal)"]
|
|
24
|
+
direction LR
|
|
25
|
+
REG["Registry"]
|
|
26
|
+
ORCH["Orchestrator"]
|
|
27
|
+
UC["Use cases<br/><i>scan · refresh · action · watch</i>"]
|
|
28
|
+
CONFIG["Config layering<br/><i>defaults → project → project-local → override</i>"]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
CLI ==>|"ports"| KERNEL
|
|
32
|
+
SERVER ==>|"ports"| KERNEL
|
|
33
|
+
SKILL ==>|"ports"| KERNEL
|
|
34
|
+
|
|
35
|
+
subgraph DRIVEN["Driven adapters (secondary)"]
|
|
36
|
+
direction LR
|
|
37
|
+
STORAGE["Storage<br/><i>SQLite</i>"]
|
|
38
|
+
FS["FS<br/><i>walker · watcher (chokidar)</i>"]
|
|
39
|
+
subgraph PLUGINS["Plugins (closed catalog, 6 kinds)"]
|
|
40
|
+
direction TB
|
|
41
|
+
EXT["extractors"]
|
|
42
|
+
ANA["analyzers"]
|
|
43
|
+
ACT["actions"]
|
|
44
|
+
HOOK["hooks"]
|
|
45
|
+
FMT["formatters"]
|
|
46
|
+
PROV["providers"]
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
KERNEL ==> STORAGE
|
|
51
|
+
KERNEL ==> FS
|
|
52
|
+
KERNEL ==> PLUGINS
|
|
53
|
+
|
|
54
|
+
classDef ui fill:#bac8ff,stroke:#3b5bdb,stroke-width:1px,color:#000,stroke-dasharray: 5 3
|
|
55
|
+
class CLI,SERVER,SKILL driver
|
|
56
|
+
class REG,ORCH,UC,CONFIG kernel
|
|
57
|
+
class STORAGE,FS adapter
|
|
58
|
+
class EXT,ANA,ACT,HOOK,FMT,PROV plugin
|
|
11
59
|
```
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
┌─────────┐ ┌─────────┐ ┌──────┐
|
|
15
|
-
│ CLI │ │ Server │ │Skill │
|
|
16
|
-
└────┬────┘ └────┬────┘ └───┬──┘
|
|
17
|
-
│ │ │
|
|
18
|
-
└─────────────────┼────────────────┘
|
|
19
|
-
▼
|
|
20
|
-
┌──────────────┐
|
|
21
|
-
│ Kernel │ ← domain core
|
|
22
|
-
│ │
|
|
23
|
-
│ Registry │
|
|
24
|
-
│ Orchestrator│
|
|
25
|
-
│ Use cases │
|
|
26
|
-
└──┬───┬───┬───┘
|
|
27
|
-
│ │ │
|
|
28
|
-
┌─────────────┘ │ └──────────────┐
|
|
29
|
-
▼ ▼ ▼
|
|
30
|
-
┌────────┐ ┌─────────┐ ┌─────────┐
|
|
31
|
-
│ Storage│ │ FS │ │ Plugins │
|
|
32
|
-
└────────┘ └─────────┘ └─────────┘
|
|
33
|
-
Driven adapters (secondary)
|
|
34
|
-
```
|
|
60
|
+
|
|
61
|
+
The UI is **not** a driving adapter; it is an HTTP/WS client of the Server. Exactly one Provider is active per project (see §Active Provider Lens), and config layering is always project-scoped (see §Config layering).
|
|
35
62
|
|
|
36
63
|
- **Driving adapters** call into the kernel. The spec defines three: `CLI`, `Server`, `Skill`. A fourth driving adapter MAY be built by third parties (IDE extension, VSCode command palette, TUI) without spec changes.
|
|
37
64
|
- **Driven adapters** implement ports the kernel declares. An implementation MUST ship adapters for every port, no port may be left unimplemented at runtime.
|
|
@@ -322,13 +349,18 @@ The lookup uses the ACTIVE PROVIDER LENS deliberately, mirroring the extractor g
|
|
|
322
349
|
|
|
323
350
|
Each Provider MAY declare an optional `reservedNames: Record<kind, string[]>` map listing, for each `node.kind` the runtime owns, the set of invocation names the runtime itself consumes. Anthropic's Claude CLI reserves `/help`, `/clear`, `/init`, `/agents`, `/model`, `/cost`, `/compact`, `/login`, `/logout`, … under `command`, and `general-purpose`, `output-style-setup`, `statusline-setup` under `agent`; a user-authored `.claude/commands/help.md` is silently shadowed at runtime (the built-in runs, the file is ignored).
|
|
324
351
|
|
|
325
|
-
The kernel intersects each Provider's `reservedNames[kind]` catalog with the scanned graph at orchestrator time
|
|
352
|
+
The kernel intersects each Provider's `reservedNames[kind]` catalog with the scanned graph at orchestrator time. For every node the post-walk pipeline derives its normalised identifiers via the §Provider · kind identifiers contract, then tests them against a reserved set resolved under **two scopes**:
|
|
353
|
+
|
|
354
|
+
1. **Self scope.** `reservedNames[node.kind]` of the node's OWN Provider (`node.provider`). The self-contained case: Claude classifies `.claude/commands/help.md` as `claude`/`command` and reserves `help` under `command`, so the file is flagged.
|
|
355
|
+
2. **Lens scope.** When a Provider is the active lens (`activeProvider === provider.id`) it ALSO lends its catalog to nodes that a *universal* (ungated) Provider classified, matched by `node.kind`. This is required by runtimes that adopt the open `.agents/skills/` standard instead of a vendor directory: their user invocables are owned by the neutral `agent-skills` Provider (`kind: skill`), not by the vendor Provider, so self scope alone would never reach them. Google's Antigravity is exactly this shape, it is metadata-only (classifies nothing) and reserves its `agy` built-in slash commands under `skill`; when `activeProvider === 'antigravity'`, a user `.agents/skills/goal/SKILL.md` is flagged because `/goal` is a built-in. Lens scope is skipped when `node.provider === activeProvider` (it would duplicate self scope) and when no lens is resolved (`activeProvider === null`).
|
|
356
|
+
|
|
357
|
+
A node's identifiers are always derived from its OWN kind contract; only the reserved-set lookup widens under the lens. A node landing in either scope's set joins a per-scan `Set<nodePath>` consumed by two surfaces:
|
|
326
358
|
|
|
327
359
|
1. **The `core/name-reserved` analyzer projects one `warn` issue per reserved-shadow node** (`severity: 'warn'`, message points at the offending file and suggests renaming). The analyzer is a pure projector, detection lives in the orchestrator so the same set drives the next surface.
|
|
328
360
|
|
|
329
361
|
2. **The post-walk confidence-lift transform downgrades any link that resolves to a reserved target** (by path OR by name match) to `RESERVED_TARGET_CONFIDENCE = 0.1` instead of bumping it to `1.0`. The visual weight in the graph drops well below the `0.5` / `0.8` extractor emit floors so the operator sees at a glance that the edge resolves to a file the runtime ignores. When the trigger has multiple candidates (name index collision) and the strict-kind filter accepts more than one, the resolver picks the first allowed candidate, if it is non-reserved, the link bumps to `1.0` normally; only when EVERY accepted candidate is reserved does the downgrade apply.
|
|
330
362
|
|
|
331
|
-
The lookup normalises both sides through the §Extractor · trigger normalization pipeline, so a literal `Init-Project` in the manifest still matches a user `name: init project` or filename `Init-Project.md`. The catalog is intentionally per-kind, not global: a name reserved for commands (`/help`) MAY legitimately appear as a skill (a "help" skill triggered through a non-command channel).
|
|
363
|
+
The lookup normalises both sides through the §Extractor · trigger normalization pipeline, so a literal `Init-Project` in the manifest still matches a user `name: init project` or filename `Init-Project.md`. The catalog is intentionally per-kind, not global: a name reserved for commands (`/help`) MAY legitimately appear as a skill (a "help" skill triggered through a non-command channel). Lens scope respects the same per-kind boundary: Antigravity declares its reserved names under `skill` (not `command`) precisely because the invocable they shadow is a skill file, so only `skill`-kind nodes are tested against it.
|
|
332
364
|
|
|
333
365
|
**Update policy.** Built-in catalogs drift as vendor runtimes evolve. Each catalog change ships as a kernel patch with a changeset entry; the catalog is considered API surface that users rely on the analyzer to reflect. User-installed Providers MAY declare their own `reservedNames` with the same shape; the analyzer and the downgrade run uniformly across built-in and user-installed Providers.
|
|
334
366
|
|
|
@@ -338,7 +370,7 @@ Default `undefined` ≡ empty map ≡ no reserved names. Path matches against no
|
|
|
338
370
|
|
|
339
371
|
The `Extractor` runtime contract is `extract(ctx) → void`. The extractor emits its work through three callbacks the kernel binds onto `ctx`:
|
|
340
372
|
|
|
341
|
-
- `ctx.emitLink(link)`, append a `Link` to the kernel's `links` table. The kernel validates
|
|
373
|
+
- `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`) 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.
|
|
342
374
|
- `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).
|
|
343
375
|
- `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).
|
|
344
376
|
|
|
@@ -380,9 +412,9 @@ Analyzers / `sm check` / `sm export` consume `node.frontmatter` directly (determ
|
|
|
380
412
|
|
|
381
413
|
Refresh verbs (`sm refresh <node>` and `sm refresh --stale`) re-run the Extractor pipeline against a node or the stale set and upsert fresh enrichment rows, see [`cli-contract.md` §Scan](./cli-contract.md#scan). With Extractors deterministic-only, `--stale` is a no-op today (no rows are stale-flagged); it remains in the contract for the future Action-prob enrichment revision noted above.
|
|
382
414
|
|
|
383
|
-
### Extractor · `
|
|
415
|
+
### Extractor · `precondition` filter
|
|
384
416
|
|
|
385
|
-
Extractors MAY declare an optional `
|
|
417
|
+
Extractors MAY declare an optional `precondition` block (`{ kind?: string[]; provider?: string[] }`, the same shape Analyzers and Actions share). When declared, the kernel filters fail-fast: `extract()` is invoked **only** for nodes that satisfy every declared sub-filter (`kind` lists qualified `<plugin>/<kindName>` ids; `provider` lists plugin ids; both apply as AND). The skip happens BEFORE the extractor context is built so the extractor wastes zero CPU on inapplicable nodes. Absent (`undefined`) is the default and means "applies to every kind"; there is no wildcard syntax. Unknown qualified kinds (no installed Provider declares them) are non-blocking: the extractor keeps `loaded` status and `sm plugins doctor` surfaces an informational `precondition-kind-unknown` warning so the author sees typos and missing-Provider cases, but the doctor's exit code is NOT promoted by this warning. See [`plugin-author-guide.md` §`precondition`](./plugin-author-guide.md#extractor--analyzer--action-precondition-narrow-the-pipeline) for the full author-side contract.
|
|
386
418
|
|
|
387
419
|
### Extractor · fine-grained scan cache
|
|
388
420
|
|
|
@@ -433,16 +465,11 @@ Characters outside the separator set that are not letters or digits (e.g. `/`, `
|
|
|
433
465
|
| `@FooExtractor` | `@fooextractor` |
|
|
434
466
|
| `skill-map:explore` | `skill map:explore` |
|
|
435
467
|
|
|
436
|
-
### Analyzer
|
|
468
|
+
### Analyzer ↔ Action relationship (Modelo B)
|
|
437
469
|
|
|
438
|
-
|
|
470
|
+
The "which Action resolves this analyzer's findings?" relationship is declared from the **Action** side, not the Analyzer side (the `Analyzer.recommendedActions` map was retired with the structure-as-truth refactor). An Action's `precondition.analyzerIds: string[]` lists the qualified ids of the analyzers whose findings it is intended to resolve. The UI joins on this field: when an analyzer emitted against the focused node, the inspector surfaces every Action whose `precondition.analyzerIds` includes that analyzer, under "Recommended for issues", alongside the always-applicable list driven by the rest of the Action's `precondition`.
|
|
439
471
|
|
|
440
|
-
The two surfaces are
|
|
441
|
-
|
|
442
|
-
- **`Action.precondition`**, declared on the Action side. Answers "which nodes does this Action apply to?". Evaluated continuously against the node the inspector is focused on, regardless of any issue.
|
|
443
|
-
- **`Analyzer.recommendedActions`**, declared on the Analyzer side. Answers "when this analyzer fires, which Actions are the natural fix?". Surfaces only on nodes the analyzer emitted against.
|
|
444
|
-
|
|
445
|
-
Each `recommendedActions` entry MUST be the qualified id of a registered Action. The kernel logs an `extension.error` event with `kind: 'recommended-action-missing'` when a referenced action is not loaded; the analyzer stays registered and continues emitting issues, only the recommendation hint is dropped. Project-level cleanup verbs (orphan file prune, contribution relink) are CLI commands, not Actions, and are NOT linked through this field. Analyzers whose issues surface deliberate user declarations rather than fixable problems (e.g. `core/node-superseded`) omit the field.
|
|
472
|
+
The two surfaces stay distinct: the `kind` / `provider` sub-filters answer "which nodes does this Action apply to?" (evaluated continuously against the focused node); `analyzerIds` answers "when which analyzer fires is this Action the natural fix?" (surfaces only on nodes the named analyzer emitted against). Project-level cleanup verbs (orphan file prune, contribution relink) are CLI commands, not Actions, and are NOT linked through this field. Actions resolving deliberate user declarations rather than fixable problems omit `analyzerIds`.
|
|
446
473
|
|
|
447
474
|
### Hook · curated trigger set
|
|
448
475
|
|
|
@@ -457,7 +484,8 @@ Hooks subscribe declaratively to a curated set of kernel lifecycle events and re
|
|
|
457
484
|
| `analyzer.completed` | Once per Analyzer, after every issue has been validated. | `analyzerId: string` (qualified). | Per-Analyzer alerting, downstream tooling. |
|
|
458
485
|
| `action.completed` | Once per Action invocation, after the report has been recorded. | `actionId: string` (qualified), `node`, `jobResult`. | Per-Action notification, integration glue. |
|
|
459
486
|
| `job.spawning` | Pre-spawn of a runner subprocess (job subsystem; Step 10). | `jobId`, `actionId`, spawn metadata. | Pre-flight checks, audit logging. |
|
|
460
|
-
| `job.
|
|
487
|
+
| `job.completed` | Once per job that finishes successfully (job subsystem; Step 10). Same payload shape as the [`job-events.md`](./job-events.md) entry of the same name. | See [`job-events.md` §Event catalog](./job-events.md#event-catalog). | Most common Hook surface (notifications, retries, billing). |
|
|
488
|
+
| `job.failed` | Once per job that fails (job subsystem; Step 10). Same payload shape as the [`job-events.md`](./job-events.md) entry of the same name. | See [`job-events.md` §Event catalog](./job-events.md#event-catalog). | Alerting, retry triggers. |
|
|
461
489
|
| `shutdown` | Once per CLI process invocation, AFTER the verb returns its exit code and BEFORE `process.exit`. The dispatcher awaits subscribed hooks so they finish before the process terminates, but every hook MUST be fast (the user already saw the verb's output and is waiting for the prompt back). The dispatcher catches every hook error so a buggy hook never alters the verb's exit code; it can only delay the exit. | `exitCode: number` (the verb's resolved exit code, `0..5`). | Cleanup, post-run telemetry, the `core/update-check` banner. |
|
|
462
490
|
|
|
463
491
|
A hook MAY narrow further with an optional declarative `filter` map: keys are payload field paths (top-level only in v0.x); values are the literal expected match. The dispatcher walks `event.data` for each declared key and short-circuits the invocation when any value disagrees. Examples:
|
|
@@ -638,9 +666,9 @@ The flag lives in `project-local` (gitignored) so each collaborator consents ind
|
|
|
638
666
|
|
|
639
667
|
### Plugin contributions
|
|
640
668
|
|
|
641
|
-
Plugins extend the annotation surface via the `
|
|
669
|
+
Plugins extend the annotation surface via the optional `annotation` block on an extension manifest (`{ schema, ownership?, location? }`, inline JSON Schema, no `$ref` to external files). It is a **single** declaration per extension and **the contributed key is the extension's id** (its folder name); an extension that needs several keys splits into several extensions, one per key. Two location modes:
|
|
642
670
|
|
|
643
|
-
- `location: 'namespaced'` (default), writes go to the plugin's `<plugin-id>:` block at the sidecar root. Default `ownership: 'shared'`. Plugins write to their own namespace without coordination; AJV validates contributed
|
|
671
|
+
- `location: 'namespaced'` (default), writes go to the plugin's `<plugin-id>:` block at the sidecar root. Default `ownership: 'shared'`. Plugins write to their own namespace without coordination; AJV validates the contributed value against the extension's declared schema.
|
|
644
672
|
- `location: 'root'`, writes go to a top-level key of the sidecar (alongside `identity` / `annotations` / `settings` / `audit`). Requires `ownership: 'exclusive'` (claiming a root key is elevated trust). Two plugins claiming the same root key with `exclusive` is a **hard fatal** at orchestrator startup, the kernel refuses to boot rather than route writes ambiguously.
|
|
645
673
|
|
|
646
674
|
The kernel exposes a runtime catalog (`Kernel.getRegisteredAnnotationKeys()`) listing every plugin-contributed key with its `pluginId`, `location`, `ownership`, and `schema`, consumed by the BFF (`GET /api/annotations/registered`) for UI autocomplete.
|
|
@@ -654,22 +682,17 @@ Two columns on `scan_nodes` source from the sidecar's `annotations:` block when
|
|
|
654
682
|
|
|
655
683
|
A `scan_nodes.annotations_json` column carries the full parsed `annotations:` block; `sidecar_present` and `sidecar_status` carry the drift-detection state. The full sidecar overlay (parsed `annotations`, `status`, `present`) is exposed on `Node.sidecar` so REST and UI consumers see it as part of the canonical wire shape.
|
|
656
684
|
|
|
657
|
-
### Tags
|
|
658
|
-
|
|
659
|
-
Skill-map's tag system is **dual-source** by design:
|
|
685
|
+
### Tags
|
|
660
686
|
|
|
661
|
-
|
|
662
|
-
- **User tags** live in `sidecar.annotations.tags` (in the `.sm`). Curated annotation field declared on [`schemas/annotations.schema.json`](./schemas/annotations.schema.json). These represent the post-hoc tags whoever curates the project assigned to the node from their sidecar.
|
|
687
|
+
Tags are a **skill-map concept**, not a vendor field: no agent format (Claude, Cursor, Obsidian, the Agent Skills open standard, …) carries `tags` in its frontmatter, so skill-map keeps them where it owns the surface, the `.sm` sidecar.
|
|
663
688
|
|
|
664
|
-
|
|
689
|
+
- **Tags** live in `sidecar.annotations.tags` (in the `.sm`). Curated annotation field declared on [`schemas/annotations.schema.json`](./schemas/annotations.schema.json). These are the tags whoever curates the project assigned to the node from their sidecar.
|
|
665
690
|
|
|
666
|
-
|
|
667
|
-
- The optional `--tag-source author|user` flag filters one source.
|
|
668
|
-
- The UI distinguishes them visually so the attribution stays explicit (different chip style; author chips render first, user chips after).
|
|
691
|
+
Search and listings (`sm list --tag <name>`, UI faceted search) match this field: a hit returns the node. The UI renders them as chips on the node card and in the inspector.
|
|
669
692
|
|
|
670
|
-
Persistence layer projects rows into a normalized [`scan_node_tags`](./db-schema.md#scan_node_tags) table at write time, one row per `(node_path, tag
|
|
693
|
+
Persistence layer projects rows into a normalized [`scan_node_tags`](./db-schema.md#scan_node_tags) table at write time, one row per `(node_path, tag)` pair, so SQL queries can index on `(tag)` for `O(log n)` lookup. Replace-all per scan keeps the table in sync with the live sidecar state; deleting a tag from a sidecar removes its row on the next scan.
|
|
671
694
|
|
|
672
|
-
The wire shape (`/api/nodes` and `/api/nodes/:pathB64`) projects `node.tags =
|
|
695
|
+
The wire shape (`/api/nodes` and `/api/nodes/:pathB64`) projects `node.tags = string[]`. The kernel `Node` interface (TypeScript) does NOT carry `tags`, consumers that walk the canonical source read `node.sidecar.annotations.tags` directly (consistent with the post-decision-#2 posture of "no Node-level denormalisations").
|
|
673
696
|
|
|
674
697
|
### Stability
|
|
675
698
|
|
|
@@ -760,7 +783,7 @@ ctx.emitContribution(contributionId, payload);
|
|
|
760
783
|
ctx.emitContribution(nodePath, contributionId, payload);
|
|
761
784
|
```
|
|
762
785
|
|
|
763
|
-
Parallel to `ctx.emitLink(link)`. The kernel buffers the emission, validates the payload against the slot's payload schema in `$defs/payloads/<slot>` (AJV-compiled at boot), and persists the row to `scan_contributions` during `persistScanResult`. Off-shape payloads emit an `extension.error` event and drop silently, same posture as `emitLink` rejecting off
|
|
786
|
+
Parallel to `ctx.emitLink(link)`. The kernel buffers the emission, validates the payload against the slot's payload schema in `$defs/payloads/<slot>` (AJV-compiled at boot), and persists the row to `scan_contributions` during `persistScanResult`. Off-shape payloads emit an `extension.error` event and drop silently, same posture as `emitLink` rejecting off-enum link kinds. Both Extractor and Analyzer emissions land in the same `scan_contributions` rows; the row's `extension_id` records which kind of extension produced it.
|
|
764
787
|
|
|
765
788
|
The Extractor-emit signature binds `nodePath` implicitly (the extractor runs per-node, with `ctx.node.path` available as the only sensible target). The Analyzer-emit signature requires the analyzer to declare the target node explicitly because Analyzers see the full graph at once and may emit for any subset of nodes, the canonical use case is a analyzer that derives per-node values from cross-graph aggregations (`core/link-counter` projects `linksOutCount` / `linksInCount` this way).
|
|
766
789
|
|
package/cli-contract.md
CHANGED
|
@@ -56,8 +56,8 @@ Genuinely per-user, per-machine preferences live in a **single file**
|
|
|
56
56
|
at `~/.skill-map/settings.json`, validated against
|
|
57
57
|
[`user-settings.schema.json`](./schemas/user-settings.schema.json).
|
|
58
58
|
The file holds preferences that have no project meaning (the update-
|
|
59
|
-
check toggle + its throttle bookkeeping
|
|
60
|
-
etc.). Constraints:
|
|
59
|
+
check toggle + its throttle bookkeeping, and the telemetry consent
|
|
60
|
+
flag today; future locale, theme, etc.). Constraints:
|
|
61
61
|
|
|
62
62
|
- **One file, no `.local` partner**: values here are already
|
|
63
63
|
per-machine, so the project / project-local split has no meaning.
|
|
@@ -69,13 +69,41 @@ etc.). Constraints:
|
|
|
69
69
|
(only preferences whose value is meaningless inside a project).
|
|
70
70
|
Anything that belongs to a project goes in
|
|
71
71
|
`<cwd>/.skill-map/settings.json` instead.
|
|
72
|
-
- **Closed list of writers**:
|
|
73
|
-
(`src/cli/util/
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
- **Closed list of writers**: a single user-settings store module
|
|
73
|
+
(`src/cli/util/user-settings-store.ts` in the reference impl) is the
|
|
74
|
+
only reader / writer of the file. Every user-scope feature (the
|
|
75
|
+
update-check toggle, the telemetry consent flag) goes through it
|
|
76
|
+
rather than opening new home access points.
|
|
76
77
|
|
|
77
78
|
Everything else under `$HOME` MUST NOT be touched.
|
|
78
79
|
|
|
80
|
+
### Telemetry consent
|
|
81
|
+
|
|
82
|
+
skill-map sends nothing off the machine by default. Opt-in, anonymous
|
|
83
|
+
**error reporting** is the one documented exception, governed in full by
|
|
84
|
+
[`telemetry.md`](./telemetry.md). The operator-facing contract:
|
|
85
|
+
|
|
86
|
+
- **Default OFF.** The `telemetry.errorsEnabled` flag in
|
|
87
|
+
`~/.skill-map/settings.json` is absent until the operator decides. Absent
|
|
88
|
+
or `false` means no telemetry SDK is loaded and nothing is sent, on every
|
|
89
|
+
surface (CLI, BFF, UI), with zero added latency.
|
|
90
|
+
- **Consent prompt (interactive terminals only, second eligible run).** The
|
|
91
|
+
CLI MAY show a one-time consent prompt (yes (default) / no / details),
|
|
92
|
+
but NOT on the operator's first eligible run: that run only records
|
|
93
|
+
`telemetry.firstRunAt` and stays silent so the prompt does not stack on
|
|
94
|
+
the first-`sm scan` provider-lens prompt. The next eligible run shows it,
|
|
95
|
+
persists the choice, and stamps `telemetry.promptedAt` (so it is never
|
|
96
|
+
shown again). When stdout is not a TTY (CI, pipes), nothing is asked or
|
|
97
|
+
recorded and the state stays OFF.
|
|
98
|
+
- **Kill switch.** `SKILL_MAP_TELEMETRY=0` forces OFF everywhere regardless
|
|
99
|
+
of the persisted flag. There is no env value that forces ON.
|
|
100
|
+
- **No `sm config` key.** The flag is per-machine, so it lives in the
|
|
101
|
+
user-settings file, not in project config. `sm config` writes project-local
|
|
102
|
+
settings only and MUST NOT surface this key. Consent is changed after the
|
|
103
|
+
first run through the Settings UI (persisted via the BFF), mirroring how
|
|
104
|
+
the update-check toggle works. A future `sm telemetry` verb
|
|
105
|
+
family MAY expose CLI status / toggling; it is not part of this level.
|
|
106
|
+
|
|
79
107
|
### Active provider lens
|
|
80
108
|
|
|
81
109
|
The project sees its filesystem through exactly one **active provider lens** at any time. The lens is persisted as `activeProvider` in `<cwd>/.skill-map/settings.json` (see [`project-config.schema.json`](./schemas/project-config.schema.json#/properties/activeProvider) and the [`architecture.md` §Active Provider Lens](./architecture.md#active-provider-lens) section for the full architectural rationale).
|
|
@@ -204,7 +232,7 @@ Exit: 0 if all green, 1 if warnings, 2 if any `error`-level problem.
|
|
|
204
232
|
Self-describing introspection.
|
|
205
233
|
|
|
206
234
|
- `human` (default): pretty terminal output.
|
|
207
|
-
- `md`: canonical markdown for documentation sites. Implementations MUST NOT hand-maintain equivalent markdown;
|
|
235
|
+
- `md`: canonical markdown for documentation sites. Implementations MUST NOT hand-maintain equivalent markdown; it is generated on demand from this output.
|
|
208
236
|
- `json`: structured surface dump. Shape:
|
|
209
237
|
|
|
210
238
|
```json
|
|
@@ -285,6 +313,8 @@ The watcher subscribes to the same roots that `sm scan` walks and respects `.ski
|
|
|
285
313
|
|
|
286
314
|
**Node cap** (`--max-nodes <N>`): on `sm scan` and `sm watch` (alias `sm scan --watch`), a hard cap on the number of files the walker accepts after `.skillmapignore` filtering, before extractors run. Default comes from `scan.maxNodes` (default 256). The flag is a full override of the setting and is **bidirectional**: it can raise the cap (`--max-nodes 1000` on a 312-file repo) or lower it (`--max-nodes 100` cuts deeper than the default). When the walker reaches the cap, additional files are dropped in stable provider-walker order and the scan is marked oversized in `scan_meta` (columns `recommended_node_limit` and `override_max_nodes`), the resulting `ScanResult` envelope carries `recommendedNodeLimit` and `overrideMaxNodes` so the UI raises a persistent banner pointing at the `.skillmapignore` editor in Settings → Project. The CLI prints a human-mode notice naming both escapes: edit `.skillmapignore` (preferred, trims permanently) or re-run with `--max-nodes <N>` (force, graph quality may degrade past the recommended limit). `sm refresh` operates on a single already-classified node, so the cap does not apply there. Validation: integer ≥ 1, anything else exits `2` operational.
|
|
287
315
|
|
|
316
|
+
**Schema-drift rebuild (pre-1.0)**: before persisting, `sm scan` and `sm watch` compare `scan_meta.scanned_by_version` against the running CLI. A minor or major difference means the local cache predates a schema change, so the DB is deleted and rebuilt from scratch by this run (`.sm` sidecars are untouched, they are the source of truth). On an interactive terminal the rebuild is confirmed first; `--yes` (and every non-interactive caller: piped stdin, CI, the BFF, the watcher) rebuilds without prompting. Declining aborts the scan (exit `2`) without deleting anything. Patch-level differences are compatible and never trigger a rebuild. Read-only verbs keep the version-skew advisory instead of rebuilding. See [`db-schema.md` §Schema drift (pre-1.0)](./db-schema.md#schema-drift-pre-10).
|
|
317
|
+
|
|
288
318
|
Exit: 0 on clean (or clean watcher shutdown), 1 if error-severity issues exist (one-shot scan only, the watcher does not flip exit code based on per-batch issues), 2 on operational error.
|
|
289
319
|
|
|
290
320
|
---
|
|
@@ -658,7 +688,7 @@ The `/ws` endpoint is the live-events channel for the SPA. Clients connect once
|
|
|
658
688
|
### Introspection
|
|
659
689
|
|
|
660
690
|
- `sm help --format json`, structured CLI surface dump.
|
|
661
|
-
- `sm help --format md`, canonical markdown,
|
|
691
|
+
- `sm help --format md`, canonical markdown, generated on demand (not a committed artifact).
|
|
662
692
|
|
|
663
693
|
These two formats are NORMATIVE: any change to verbs, flags, or exit codes MUST reflect in `--format json` output immediately. Third-party consumers rely on this.
|
|
664
694
|
|
|
@@ -742,7 +772,6 @@ The `done in …` stderr line, its format grammar, and the `elapsedMs` field con
|
|
|
742
772
|
- [`job-lifecycle.md`](./job-lifecycle.md), state machine behind `sm job` verbs.
|
|
743
773
|
- [`job-events.md`](./job-events.md), event stream emitted via `--json` and `--stream-output`.
|
|
744
774
|
- [`db-schema.md`](./db-schema.md), tables behind `sm db` verbs.
|
|
745
|
-
- [`../context/cli-reference.md`](../context/cli-reference.md), auto-generated reference from `sm help --format md`.
|
|
746
775
|
- [`conformance/`](./conformance/README.md), test suite exercising CLI behavior.
|
|
747
776
|
|
|
748
777
|
---
|
package/conformance/README.md
CHANGED
|
@@ -51,7 +51,7 @@ Fixtures are read-only inputs. Cases declare what to invoke and what to assert.
|
|
|
51
51
|
|
|
52
52
|
## Case format
|
|
53
53
|
|
|
54
|
-
Cases are validated against [`conformance-case.schema.json`](../schemas/conformance-case.schema.json). That file is the normative shape; this section is the human-readable walkthrough. Include `"$schema": "https://skill-map.
|
|
54
|
+
Cases are validated against [`conformance-case.schema.json`](../schemas/conformance-case.schema.json). That file is the normative shape; this section is the human-readable walkthrough. Include `"$schema": "https://skill-map.ai/spec/v0/conformance-case.schema.json"` in every case file for IDE support.
|
|
55
55
|
|
|
56
56
|
A case is a JSON document with this shape:
|
|
57
57
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "https://skill-map.
|
|
2
|
+
"$schema": "https://skill-map.ai/spec/v0/conformance-case.schema.json",
|
|
3
3
|
"id": "extractor-emits-signal",
|
|
4
4
|
"description": "Signal IR resolver phase, end-to-end. A body that contains a single `[text](path)` markdown link MUST flow through the Signal IR resolver (Phase 2 of the active-lens migration): `core/markdown-link` emits a single-candidate Signal, the resolver materialises the winning candidate as a Link, and the result lands in `scan.links` with the same shape a direct `emitLink` call would have produced. Locks the contract that the Signal IR path coexists with the direct-emit path and produces indistinguishable Link rows.",
|
|
5
5
|
"fixture": "signal-ir-single-signal",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "https://skill-map.
|
|
2
|
+
"$schema": "https://skill-map.ai/spec/v0/conformance-case.schema.json",
|
|
3
3
|
"id": "kernel-empty-boot",
|
|
4
4
|
"description": "With every Provider, extractor, and analyzer disabled, scanning an empty scope MUST return a valid, zero-filled ScanResult. Enforces the kernel boot invariant from architecture.md.",
|
|
5
5
|
"setup": {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "https://skill-map.
|
|
2
|
+
"$schema": "https://skill-map.ai/spec/v0/conformance-case.schema.json",
|
|
3
3
|
"id": "no-global-scope",
|
|
4
4
|
"description": "Skill-map operates exclusively on the project scope. Implementations MUST NOT expose a `-g/--global` flag (the historical opt-in for a global / user scope) on any verb. Passing the flag to any verb MUST be rejected as an unknown option (exit `2`, usage error) without writing any state. Guards the principle declared in `cli-contract.md` §Scope is always project-local.",
|
|
5
5
|
"invoke": {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "https://skill-map.
|
|
2
|
+
"$schema": "https://skill-map.ai/spec/v0/conformance-case.schema.json",
|
|
3
3
|
"id": "orphan-markdown-fallback",
|
|
4
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
5
|
"fixture": "orphan-markdown",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "https://skill-map.
|
|
2
|
+
"$schema": "https://skill-map.ai/spec/v0/conformance-case.schema.json",
|
|
3
3
|
"id": "plugin-missing-ui-rejected",
|
|
4
4
|
"description": "A drop-in Provider plugin whose `kinds[*]` entry omits the required `ui` block (Step 14.5.d) MUST be rejected by the loader with a clear `missing required property 'ui'` diagnostic on stderr, AND `sm scan` MUST exit cleanly with the rest of the pipeline (built-in Claude Provider) still running. Locks the contract that one bad plugin does not take down the scan.",
|
|
5
5
|
"fixture": "plugin-missing-ui",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "https://skill-map.
|
|
2
|
+
"$schema": "https://skill-map.ai/spec/v0/conformance-case.schema.json",
|
|
3
3
|
"id": "sidecar-end-to-end",
|
|
4
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.",
|
|
5
5
|
"fixture": "sidecar-end-to-end",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "https://skill-map.
|
|
2
|
+
"$schema": "https://skill-map.ai/spec/v0/conformance-case.schema.json",
|
|
3
3
|
"id": "signal-collision-detection",
|
|
4
4
|
"description": "Signal IR resolver phase, range-overlap collision. A body that contains `[@./api.md](./api.md)` triggers a cross-extractor range overlap: `core/markdown-link` matches the whole bracketed-and-parenthesised span; `claude/at-directive` matches the `@./api.md` token INSIDE the bracket text. The two byte ranges overlap (the at-directive range is a strict subset of the markdown-link range). The kernel resolver picks ONE winner per the four-step tiebreak (`kind-priority` -> `higher-confidence` -> `longer-range` -> `earlier-declaration`); markdown-link wins on confidence (1.0 vs 0.85). The resolver materialises the winner as a Link, marks the loser's `resolution.outcome === 'rejected'` with `rejectedBy` naming the winner, and the built-in `core/signal-collision` analyzer surfaces the rejection as ONE `warn` issue attached to the source node. Locks the contract that range-overlap collisions surface to the operator instead of being silently merged.",
|
|
5
5
|
"fixture": "signal-ir-collision",
|
package/conformance/coverage.md
CHANGED
|
@@ -38,7 +38,7 @@ This file is hand-maintained. A CI check before spec release compares the schema
|
|
|
38
38
|
| 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 analyzer 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. |
|
|
39
39
|
| 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. |
|
|
40
40
|
| 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. |
|
|
41
|
-
| 30 | `view-slots.schema.json` |, | 🔴 missing | Closed catalog of
|
|
41
|
+
| 30 | `view-slots.schema.json` |, | 🔴 missing | Closed catalog of 14 view slots + the `IViewContribution` manifest declaration shape + per-slot payload schemas. Cases required (3): (a) `plugin-view-contributions-valid`, a plugin manifest declaring contributions of every slot validates; (b) `plugin-view-contributions-invalid-slot`, a manifest referencing a slot not in the catalog rejects with `invalid-manifest`; (c) `plugin-view-contributions-payload-mismatch`, an extractor emitting an off-shape 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. |
|
|
42
42
|
| 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>`. |
|
|
43
43
|
| 32 | `refresh-report.schema.json` |, | 🔴 missing | Machine-readable output of `sm refresh <node.path> --json` and `sm refresh --stale --json`. Reports the count of enrichment rows persisted across targeted nodes (universal enrichment layer per `architecture.md` §A.8). Direct conformance case pending: seed a fixture with one Provider-classified node, run `sm refresh <node> --json`, assert the envelope validates and `refreshed >= 0`. Implementation tests at `src/test/node-enrichments.test.ts` cover the runtime behaviour today. |
|
|
44
44
|
| 33 | `plugins-doctor.schema.json` |, | 🔴 missing | Machine-readable output of `sm plugins doctor --json`. Aggregates per-status counts plus structured issue / warning lists. Direct conformance case pending: prime a scope with one healthy + one invalid-manifest drop-in plugin, run `sm plugins doctor --json`, assert the envelope validates and the invalid plugin appears under `issues[]`. Implementation tests at `src/test/plugins-cli.test.ts` cover the runtime behaviour. |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Sample sidecar matching agent-example.md.
|
|
2
2
|
# Validates against:
|
|
3
|
-
# - https://skill-map.
|
|
4
|
-
# - https://skill-map.
|
|
3
|
+
# - https://skill-map.ai/spec/v0/sidecar.schema.json (root shape)
|
|
4
|
+
# - https://skill-map.ai/spec/v0/annotations.schema.json (annotations block)
|
|
5
5
|
# Hashes are sha256 over the kernel's canonical forms (body bytes; canonical YAML
|
|
6
6
|
# of the frontmatter via `js-yaml` dump with sortKeys+noCompatMode). Regenerate
|
|
7
7
|
# when agent-example.md changes (the fixture is meant to be drift-free).
|
|
@@ -31,7 +31,7 @@ annotations:
|
|
|
31
31
|
- typescript
|
|
32
32
|
- quality
|
|
33
33
|
hidden: false
|
|
34
|
-
docsUrl: https://skill-map.
|
|
34
|
+
docsUrl: https://skill-map.ai/examples/code-reviewer
|
|
35
35
|
|
|
36
36
|
settings: {}
|
|
37
37
|
|
package/db-schema.md
CHANGED
|
@@ -161,7 +161,7 @@ No indexes (single row).
|
|
|
161
161
|
|
|
162
162
|
Fine-grained cache breadcrumbs for the incremental scan path. One row per `(node_path, extractor_id)` recording the body hash the Extractor saw the last time it ran against that node. Replace-all on every `sm scan` so rows for Extractors that were uninstalled since the last scan disappear automatically.
|
|
163
163
|
|
|
164
|
-
The orchestrator consults this table on `sm scan --changed`: a node-level cache hit (body+frontmatter unchanged) is upgraded to a full skip ONLY when every currently-registered Extractor (filtered by `
|
|
164
|
+
The orchestrator consults this table on `sm scan --changed`: a node-level cache hit (body+frontmatter unchanged) is upgraded to a full skip ONLY when every currently-registered Extractor (filtered by its `precondition`) has a row matching the prior body hash. A new Extractor registered between scans is detected by the absence of its row and runs over the cached node WITHOUT requiring a full cache invalidation. Without this table the cache silently bypassed any Extractor newly registered between scans, leaving its emissions missing on the next `--changed` pass; the same machinery is what a future Action-issued probabilistic enrichment revision will leverage to reuse paid LLM output across unchanged bodies.
|
|
165
165
|
|
|
166
166
|
| Column | Type | Constraint |
|
|
167
167
|
|---|---|---|
|
|
@@ -222,7 +222,7 @@ Phase 3 / View contribution system. Per-node typed payloads emitted by extractor
|
|
|
222
222
|
| `plugin_id` | TEXT | NOT NULL | Owning plugin namespace per spec § A.6. |
|
|
223
223
|
| `extension_id` | TEXT | NOT NULL | Extension id within the plugin. |
|
|
224
224
|
| `node_path` | TEXT | NOT NULL | FK semantically to `scan_nodes.path`; orphan-swept on persist when the parent node disappears. |
|
|
225
|
-
| `contribution_id` | TEXT | NOT NULL | Manifest Record key under `extension.
|
|
225
|
+
| `contribution_id` | TEXT | NOT NULL | Manifest Record key under `extension.ui[<contributionId>]` (the runtime catalog keeps the historical name `viewContributions`). |
|
|
226
226
|
| `slot` | TEXT | NOT NULL | Closed-enum-by-spec slot name; mirror of `view-slots.schema.json#/$defs/SlotName`. Kept open at the SQL layer (no CHECK) so catalog evolution does not need a DDL migration; `sm plugins upgrade` handles renames at the manifest layer. |
|
|
227
227
|
| `payload_json` | TEXT | NOT NULL | JSON-serialised payload, already validated against the slot's payload schema (`view-slots.schema.json#/$defs/payloads/<slot>`) at emit time. Off-shape payloads emit `extension.error` and drop silently. |
|
|
228
228
|
| `emitted_at` | INTEGER | NOT NULL | Unix milliseconds. |
|
|
@@ -246,19 +246,18 @@ NOT analogous to `state_plugin_kvs` (which is plugin-managed). Belongs to the `s
|
|
|
246
246
|
|
|
247
247
|
### `scan_node_tags`
|
|
248
248
|
|
|
249
|
-
Tags
|
|
249
|
+
Tags. One row per `(node_path, tag)` pair, projected at persist time from `sidecar.annotations.tags`. Tags are a skill-map concept (no vendor carries `tags` in frontmatter), so the sidecar is the single source. Drives `sm list --tag <name>` and the UI's tag-faceted search; the `(tag)` index keeps "find all nodes with tag X" `O(log n)`.
|
|
250
250
|
|
|
251
251
|
| Column | Type | Constraint |
|
|
252
252
|
|---|---|---|
|
|
253
253
|
| `node_path` | TEXT | NOT NULL | FK semantically to `scan_nodes.path`; orphan-swept on persist when the parent node disappears. |
|
|
254
254
|
| `tag` | TEXT | NOT NULL | Free-form; case-preserving. Empty strings rejected upstream by the schema's `minLength: 1` on each item. |
|
|
255
|
-
| `source` | TEXT | NOT NULL CHECK (source IN ('author','user')) | Hard split: `'author'` = `frontmatter.tags`, `'user'` = `sidecar.annotations.tags`. The same tag string MAY appear under both sources for the same node (the PK accepts the pair); `sm list --tag X` returns the node once via DISTINCT, the UI renders both chips with their attribution. |
|
|
256
255
|
|
|
257
|
-
Primary key: `(node_path, tag
|
|
256
|
+
Primary key: `(node_path, tag)`. Indexes: `ix_scan_node_tags_tag` (search by tag), `ix_scan_node_tags_node_path` (per-node lookup, e.g. inspector projection).
|
|
258
257
|
|
|
259
|
-
**Persistence, replace-all per scan.** Every persisted scan rebuilds the table for the live node set: rows whose `node_path` is NOT in `livePaths` are dropped (orphan sweep, same as the contributions table); rows for nodes in the live set are wiped and re-inserted from the projected
|
|
258
|
+
**Persistence, replace-all per scan.** Every persisted scan rebuilds the table for the live node set: rows whose `node_path` is NOT in `livePaths` are dropped (orphan sweep, same as the contributions table); rows for nodes in the live set are wiped and re-inserted from the projected sidecar state. Cached nodes' tag rows are projected from the cached `node.sidecar.annotations.tags` (already in memory), so the rebuild is cheap regardless of cache hit / miss. Storage is small, a 50-node project with avg 3 tags/node is ~150 rows ≈ 7.5 KB.
|
|
260
259
|
|
|
261
|
-
The wire shape on `/api/nodes` joins this table to project `node.tags =
|
|
260
|
+
The wire shape on `/api/nodes` joins this table to project `node.tags = string[]`. The kernel `Node` interface (TypeScript) does NOT carry `tags`, consumers walking the canonical source read `node.sidecar.annotations.tags` directly (consistent with the post-decision-#2 posture).
|
|
262
261
|
|
|
263
262
|
---
|
|
264
263
|
|
|
@@ -464,6 +463,21 @@ The kernel ALSO maintains `PRAGMA user_version` (or the engine equivalent) as a
|
|
|
464
463
|
|
|
465
464
|
---
|
|
466
465
|
|
|
466
|
+
## Schema drift (pre-1.0)
|
|
467
|
+
|
|
468
|
+
The project DB is a derived cache: every `scan_*` row is regenerable, and the operator's authored data lives in `.sm` sidecars, not in the DB. While the kernel stays in `0.Y.Z` (see [`versioning.md` §Pre-1.0](./versioning.md#pre-10)) it does NOT ship incremental migrations to carry an existing DB across a schema change. Instead, a write-side open (`sm scan`, `sm watch`, and the BFF watcher) compares `scan_meta.scanned_by_version` against the running CLI version:
|
|
469
|
+
|
|
470
|
+
- **Same `major.minor`** (patch differences ignored): the cache is compatible. The open proceeds untouched.
|
|
471
|
+
- **Any minor or major difference**: the on-disk schema is treated as drifted. The entire DB file (plus its `-wal` / `-shm` sidecars) is deleted and recreated from the current `001_initial.sql`; the scan then repopulates it. No backup is written (the cache is derived). `state_*` and `config_*` are wiped along with `scan_*`; pre-1.0 they are accepted as transient. `.sm` sidecars are never touched.
|
|
472
|
+
|
|
473
|
+
The rebuild is confirmed interactively on a TTY `sm scan` unless `--yes` is passed; non-interactive callers (piped stdin, CI, the BFF scan route, the watcher) rebuild without prompting. Declining the prompt aborts the scan (exit `2`) without deleting anything.
|
|
474
|
+
|
|
475
|
+
Read-side verbs (`sm check`, `sm list`, `sm show`, ...) do NOT rebuild. They keep the version-skew advisory (warn on an older DB, refuse on a newer or different-major DB) so a read never silently discards the cache.
|
|
476
|
+
|
|
477
|
+
This is a pre-1.0 affordance. The first `1.0.0` replaces it with real up-only migrations (see §Migrations): drift detection by version becomes drift repair by migration, and `state_*` / `config_*` stop being disposable.
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
467
481
|
## Plugin storage
|
|
468
482
|
|
|
469
483
|
Two modes declared in `plugin.json` (see [`schemas/plugins-registry.schema.json`](./schemas/plugins-registry.schema.json)).
|