@skill-map/spec 0.18.0 โ 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +338 -0
- package/architecture.md +220 -24
- package/cli-contract.md +16 -9
- package/conformance/cases/orphan-markdown-fallback.json +22 -0
- package/conformance/cases/plugin-missing-ui-rejected.json +2 -1
- package/conformance/cases/sidecar-end-to-end.json +1 -2
- package/conformance/coverage.md +4 -2
- package/conformance/fixtures/orphan-markdown/.claude/agents/reviewer.md +6 -0
- package/conformance/fixtures/orphan-markdown/ARCHITECTURE.md +10 -0
- package/conformance/fixtures/sidecar-end-to-end/.claude/agents/orphan.sm +1 -1
- package/conformance/fixtures/sidecar-end-to-end/.claude/agents/stale.sm +1 -1
- package/conformance/fixtures/sidecar-example/agent-example.sm +1 -1
- package/db-schema.md +57 -13
- package/index.json +28 -23
- package/package.json +1 -1
- package/plugin-author-guide.md +303 -29
- package/schemas/annotations.schema.json +2 -6
- package/schemas/api/rest-envelope.schema.json +55 -11
- package/schemas/extensions/base.schema.json +11 -1
- package/schemas/extensions/extractor.schema.json +3 -10
- package/schemas/extensions/provider.schema.json +1 -1
- package/schemas/frontmatter/base.schema.json +6 -1
- package/schemas/input-types.schema.json +260 -0
- package/schemas/node.schema.json +1 -19
- package/schemas/plugins-registry.schema.json +14 -2
- package/schemas/project-config.schema.json +11 -0
- package/schemas/sidecar.schema.json +5 -5
- package/schemas/summaries/markdown.schema.json +1 -1
- package/schemas/view-contracts.schema.json +298 -0
package/conformance/coverage.md
CHANGED
|
@@ -11,14 +11,14 @@ This file is hand-maintained. A CI check before spec release compares the schema
|
|
|
11
11
|
| 1 | `node.schema.json` | `kernel-empty-boot` (indirect) | ๐ก partial | Empty-boot validates the zero-filled ScanResult shape end-to-end. Direct cases that exercise populated `Node` rows are Provider-specific and live in the Provider's own conformance suite (see `provider:claude` for `basic-scan`). |
|
|
12
12
|
| 2 | `link.schema.json` | โ | ๐ด missing | Needs fixture with at least one `invokes` + `references` + `mentions` link, both `high`/`medium`/`low` confidence. |
|
|
13
13
|
| 3 | `issue.schema.json` | โ | ๐ด missing | Needs fixture triggering `trigger-collision` + `broken-ref` + `superseded`. |
|
|
14
|
-
| 4 | `scan-result.schema.json` | `kernel-empty-boot` |
|
|
14
|
+
| 4 | `scan-result.schema.json` | `kernel-empty-boot`, `orphan-markdown-fallback` | ๐ข covered | Zero-filled case via empty-boot. `orphan-markdown-fallback` (spec 0.18.0) asserts a populated `ScanResult` over a multi-Provider corpus where one node lands via the universal `core/markdown` fallback and another via vendor-specific claude classification โ locks the orchestrator's path-dedup contract. Populated rename / orphan cases live under `provider:claude` (`basic-scan` / `rename-high` / `orphan-detection`). |
|
|
15
15
|
| 5 | `execution-record.schema.json` | โ | ๐ด missing | Blocked by Step 5 (history). Needs a case that runs a `deterministic` action and inspects `state_executions` via `sm history --json`. |
|
|
16
16
|
| 6 | `project-config.schema.json` | โ | ๐ด missing | Case: init a scope, write a partial `.skill-map/settings.json` (optionally with a `.skill-map/settings.local.json` overlay), assert effective config after the layered merge. |
|
|
17
17
|
| 7 | `plugins-registry.schema.json` | โ | ๐ด missing | Two sub-cases required: (a) `PluginManifest` validation via `sm plugins show --json`; (b) aggregate `PluginsRegistry` via `sm plugins list --json`. |
|
|
18
18
|
| 8 | `job.schema.json` | โ | ๐ด missing | Blocked by Step 10 (job system). Needs a case that submits a local action (no LLM), inspects `sm job show --json`. |
|
|
19
19
|
| 9 | `report-base.schema.json` | โ | ๐ด missing | Indirect coverage once any summarizer case lands. Direct contract case: validate a handcrafted minimal report ({confidence, safety}) against the base schema. |
|
|
20
20
|
| 10 | `conformance-case.schema.json` | โ | ๐ด missing | Self-referential: every `*.json` under `cases/` MUST validate against this schema. Add a meta-case that enumerates + validates all cases. |
|
|
21
|
-
| 11 | `frontmatter/base.schema.json` |
|
|
21
|
+
| 11 | `frontmatter/base.schema.json` | `orphan-markdown-fallback` | ๐ข covered | Universal frontmatter shape โ `name` + `description` only, `additionalProperties: true`. Per-kind schemas live with the Provider that emits them: vendor kinds (`skill` / `agent` / `command`) under `src/built-in-plugins/providers/{claude,gemini,agent-skills}/schemas/`; the format-named generic `markdown` kind under `src/built-in-plugins/providers/core-markdown/schemas/` (spec 0.18.0 โ markdown is provider-agnostic). All extend this base via `$ref`-by-`$id`. `orphan-markdown-fallback` exercises base-only frontmatter end-to-end via the `ARCHITECTURE.md` fixture file (no kind-specific extras). |
|
|
22
22
|
| 12 | `summaries/skill.schema.json` | โ | ๐ด missing | Blocked by Step 10 (`skill-summarizer`). Case: submit summarizer, validate report. |
|
|
23
23
|
| 13 | `summaries/agent.schema.json` | โ | ๐ด missing | Blocked by Step 11. |
|
|
24
24
|
| 14 | `summaries/command.schema.json` | โ | ๐ด missing | Blocked by Step 11. |
|
|
@@ -37,6 +37,8 @@ This file is hand-maintained. A CI check before spec release compares the schema
|
|
|
37
37
|
| 27 | `annotations.schema.json` | `sidecar-end-to-end` | ๐ข covered | Curated catalog of 15 conventional skill-map annotation fields (versioning, supersession, provenance, lifecycle, taxonomy, display, docs). `additionalProperties: true` so users / plugins extend without coordination; the `unknown-field` Tier-1 rule shipped in Step 9.6.6 emits warnings on truly unrecognized keys. Step 9.6.2 (2026-05-05) wired the kernel reader; Step 9.6.6 (2026-05-06) flips this row ๐ข via `sidecar-end-to-end`, which asserts that an `annotations.version: 7` value round-trips through `state_scan_nodes.annotations_json` and surfaces in the node's `sidecar.annotations` overlay AND in the denormalised `Node.version` column. Structural sample at `fixtures/sidecar-example/agent-example.sm`. Catalog trimmed from 31 to 15 fields on 2026-05-07 after UX review. |
|
|
38
38
|
| 28 | `bump-report.schema.json` | โ | ๐ด missing | Report shape produced by the built-in deterministic `bump` Action (Step 9.6.3, Decision #125). Extends `report-base-deterministic.schema.json` (row 29) โ the deterministic counterpart to `report-base.schema.json` (which is LLM-specific via `confidence` + `safety`). Three concrete shapes: success-with-write, silent-no-op (under `force`), and refusal (`fresh`). Direct conformance case lands together with the `sm bump` CLI verb in Step 9.6.4 โ it'll exercise all three branches via `sm bump --json` against a primed fixture. Implementation tests at `src/test/bump-action.test.ts` cover the runtime behaviour today. |
|
|
39
39
|
| 29 | `report-base-deterministic.schema.json` | โ (indirect via row 28) | ๐ก partial | Deterministic counterpart to `report-base.schema.json`; every deterministic Action's report extends this base. Direct contract case still pending โ landed when first conformance case directly validates a deterministic report against this schema. |
|
|
40
|
+
| 30 | `view-contracts.schema.json` | โ | ๐ด missing | Closed catalog of 10 view contracts + the `IViewContribution` manifest declaration shape + per-contract payload schemas. Cases required (3): (a) `plugin-view-contributions-valid` โ a plugin manifest declaring contributions of every contract type validates; (b) `plugin-view-contributions-invalid-contract` โ a manifest referencing a contract not in the catalog rejects with `invalid-manifest`; (c) `plugin-view-contributions-payload-mismatch` โ an extractor emitting an off-contract payload triggers `extension.error` and drops silently. Implementation lands with the kernel surface in Phase 2 of the UI contributions plan; conformance fixtures land alongside. |
|
|
41
|
+
| 31 | `input-types.schema.json` | โ | ๐ด missing | Closed catalog of 10 input-types for plugin settings + the `ISettingDeclaration` discriminated-union manifest shape. Cases required (2): (a) `plugin-settings-valid` โ a plugin manifest declaring at least one setting of each input-type validates; (b) `plugin-settings-invalid-type` โ a manifest referencing a `type` not in the catalog rejects with `invalid-manifest`. Lands together with the spec/CLI surface for `sm plugins config <id>`. |
|
|
40
42
|
|
|
41
43
|
> **Note on Provider-owned schemas.** Per-kind frontmatter schemas (`skill`, `agent`, `command`, `note` for the built-in Claude Provider; other Providers MAY declare different kinds) live with the Provider that emits them โ for the built-in Claude Provider, under `src/extensions/providers/claude/schemas/`. Those schemas are NOT counted in the spec's coverage matrix above; they belong to the Provider's own conformance suite at `src/extensions/providers/claude/conformance/coverage.md`. The same split applies to the cases that exercise Provider-specific kinds (`basic-scan`, `rename-high`, `orphan-detection`) โ they live in the Provider's `cases/` directory.
|
|
42
44
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: architecture
|
|
3
|
+
description: Top-level markdown that no vendor Provider claims. Picked up by core/markdown's universal fallback classify.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
This file lives at the project root with no platform-specific path
|
|
7
|
+
prefix. The claude / gemini / agent-skills Providers all return null
|
|
8
|
+
on it; the built-in `core/markdown` Provider claims it as kind
|
|
9
|
+
`markdown`. Without the universal fallback it would be silently
|
|
10
|
+
dropped from the scan.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# discovers this via `discoverOrphanSidecars` and the built-in
|
|
3
3
|
# `core/annotation-orphan` rule emits one `warn` issue per orphan.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
identity:
|
|
6
6
|
path: agents/orphan.md
|
|
7
7
|
bodyHash: '1111111111111111111111111111111111111111111111111111111111111111'
|
|
8
8
|
frontmatterHash: '1111111111111111111111111111111111111111111111111111111111111111'
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# `annotations:` block denormalises through the SQLite scan โ the value
|
|
8
8
|
# survives a round-trip through `state_scan_nodes.annotations_json`.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
identity:
|
|
11
11
|
path: agents/stale.md
|
|
12
12
|
bodyHash: '0000000000000000000000000000000000000000000000000000000000000000'
|
|
13
13
|
frontmatterHash: '0000000000000000000000000000000000000000000000000000000000000000'
|
|
@@ -6,7 +6,7 @@
|
|
|
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).
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
identity:
|
|
10
10
|
# Scope-root-relative โ when this fixture is treated as its own
|
|
11
11
|
# mini-scope (the typical reading), the .md sits in the same
|
|
12
12
|
# directory as this .sm, so `for.path` is just the filename.
|
package/db-schema.md
CHANGED
|
@@ -72,7 +72,7 @@ One row per detected node, matching [`schemas/node.schema.json`](./schemas/node.
|
|
|
72
72
|
| Column | Type | Constraint | Notes |
|
|
73
73
|
|---|---|---|---|
|
|
74
74
|
| `path` | TEXT | PRIMARY KEY | Relative path from scope root. Canonical node identifier. |
|
|
75
|
-
| `kind` | TEXT | NOT NULL | Open-by-design (`node.schema.json#/properties/kind`): the value is whatever the classifying Provider declares. Built-in
|
|
75
|
+
| `kind` | TEXT | NOT NULL | Open-by-design (`node.schema.json#/properties/kind`): the value is whatever the classifying Provider declares. Built-in catalogs: `claude` ships `skill` / `agent` / `command`; `gemini` ships `agent` / `skill`; `agent-skills` ships `skill`; `core/markdown` ships the format-named generic fallback `markdown` (universal โ picks up any `.md` no vendor Provider claims, see `architecture.md` ยงProvider ยท dispatch order). External Providers MAY emit their own. |
|
|
76
76
|
| `provider` | TEXT | NOT NULL | Provider extension id. |
|
|
77
77
|
| `title` | TEXT | NULL | |
|
|
78
78
|
| `description` | TEXT | NULL | |
|
|
@@ -159,7 +159,7 @@ No indexes (single row).
|
|
|
159
159
|
|
|
160
160
|
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.
|
|
161
161
|
|
|
162
|
-
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 `applicableKinds`) 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
|
|
162
|
+
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 `applicableKinds`) 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.
|
|
163
163
|
|
|
164
164
|
| Column | Type | Constraint |
|
|
165
165
|
|---|---|---|
|
|
@@ -170,32 +170,32 @@ The orchestrator consults this table on `sm scan --changed`: a node-level cache
|
|
|
170
170
|
|
|
171
171
|
Primary key: `(node_path, extractor_id)`. Indexes: `ix_scan_extractor_runs_node`, `ix_scan_extractor_runs_extractor`.
|
|
172
172
|
|
|
173
|
-
**Source-attribution interaction.** `scan_links.sources_json` carries the *short* extractor id the author wrote (e.g. `'slash'`); this table keys on the *qualified* form (`'
|
|
173
|
+
**Source-attribution interaction.** `scan_links.sources_json` carries the *short* extractor id the author wrote (e.g. `'slash'`); this table keys on the *qualified* form (`'core/slash'`). When a cached link is reshaped on reuse the orchestrator strips short ids whose owning Extractor is no longer registered (audit trail accuracy: a removed extractor must not stay attributed); links whose sole source is an uninstalled Extractor disappear; links whose sources include a missing-but-still-registered Extractor are dropped so the missing Extractor can re-emit fresh.
|
|
174
174
|
|
|
175
175
|
### `node_enrichments`
|
|
176
176
|
|
|
177
177
|
Universal enrichment layer (A.8). Stores `ctx.enrichNode(partial)` outputs separately from the author-supplied frontmatter on `scan_nodes.frontmatter_json`, which the Extractor pipeline NEVER mutates.
|
|
178
178
|
|
|
179
|
-
One row per `(node_path, extractor_id)` pair an Extractor enriched.
|
|
179
|
+
One row per `(node_path, extractor_id)` pair an Extractor enriched. Extractors are deterministic-only; rows are simply overwritten via PRIMARY KEY conflict on the next re-extract through the A.9 cache.
|
|
180
180
|
|
|
181
181
|
| Column | Type | Constraint |
|
|
182
182
|
|---|---|---|
|
|
183
183
|
| `node_path` | TEXT | NOT NULL | FK semantically to `scan_nodes.path`; replaced when a rename heuristic fires (mirrors the `state_*` FK migration). |
|
|
184
184
|
| `extractor_id` | TEXT | NOT NULL | Qualified id `<plugin_id>/<id>` per spec ยง A.6. |
|
|
185
|
-
| `body_hash_at_enrichment` | TEXT | NOT NULL | The `node.body_hash` the Extractor saw when it produced this enrichment.
|
|
185
|
+
| `body_hash_at_enrichment` | TEXT | NOT NULL | The `node.body_hash` the Extractor saw when it produced this enrichment. Always equal to the live body hash for Extractor writes; reserved for future Action-issued probabilistic enrichments where stale tracking is meaningful. |
|
|
186
186
|
| `value_json` | TEXT | NOT NULL | JSON-serialised `Partial<Node>` โ the cumulative merge of every `enrichNode(...)` call the Extractor made for this node within its `extract()` invocation. |
|
|
187
|
-
| `stale` | INTEGER | NOT NULL DEFAULT 0, CHECK in (0, 1) | `
|
|
187
|
+
| `stale` | INTEGER | NOT NULL DEFAULT 0, CHECK in (0, 1) | Reserved. Always `0` in this revision (Extractors are deterministic; re-running is free). The flag and its index are kept for the future Action-prob enrichment revision where queued LLM jobs must preserve paid output across body changes. |
|
|
188
188
|
| `enriched_at` | INTEGER | NOT NULL | Unix milliseconds โ when the Extractor produced this enrichment. Drives the read-time merge order (`ASC` โ last-write-wins per field) inside `mergeNodeWithEnrichments`. |
|
|
189
|
-
| `is_probabilistic` | INTEGER | NOT NULL DEFAULT 0, CHECK in (0, 1) |
|
|
189
|
+
| `is_probabilistic` | INTEGER | NOT NULL DEFAULT 0, CHECK in (0, 1) | Reserved. Always `0` for Extractor writes (Extractors are deterministic-only). Reserved for the future Action-prob enrichment revision where the writer's mode is denormalised onto the row so the stale-flag query stays single-table. |
|
|
190
190
|
|
|
191
|
-
Primary key: `(node_path, extractor_id)`. Indexes: `ix_node_enrichments_node`, `ix_node_enrichments_stale`.
|
|
191
|
+
Primary key: `(node_path, extractor_id)`. Indexes: `ix_node_enrichments_node`, `ix_node_enrichments_stale`. The `_stale` index is dormant in this revision (every row has `stale = 0`); it is preserved so the future Action-prob revision can ship without a schema migration.
|
|
192
192
|
|
|
193
193
|
**Persistence flow** (per `sm scan`):
|
|
194
194
|
|
|
195
195
|
1. **Rename migration** โ for every `RenameOp` from the rename heuristic, update `node_enrichments.node_path` from `op.from` to `op.to` so the audit trail tracks the file like `state_*` rows do.
|
|
196
196
|
2. **Drop-on-disappear** โ delete every row whose `node_path` is no longer in the live node set.
|
|
197
|
-
3. **Upsert** โ for every `(node_path, extractor_id)` pair the orchestrator emitted in this scan, upsert with `stale = 0` and the current `body_hash`. The PRIMARY KEY conflict refreshes `body_hash_at_enrichment` / `value_json` / `enriched_at`
|
|
198
|
-
4. **Stale flagging** โ
|
|
197
|
+
3. **Upsert** โ for every `(node_path, extractor_id)` pair the orchestrator emitted in this scan, upsert with `stale = 0`, `is_probabilistic = 0`, and the current `body_hash`. The PRIMARY KEY conflict refreshes `body_hash_at_enrichment` / `value_json` / `enriched_at` on every re-run.
|
|
198
|
+
4. **Stale flagging** โ no-op in this revision (Extractors are deterministic-only; the sweep finds nothing to flag). The step is preserved in the persistence flow so the future Action-prob revision slots in without reshaping the contract.
|
|
199
199
|
|
|
200
200
|
**Read-side `node.merged` view.** Rules / `sm check` / `sm export` consume `node.frontmatter` directly (deterministic CI-safe baseline). UI / future opt-in consumers call `mergeNodeWithEnrichments(node, enrichments)` which:
|
|
201
201
|
|
|
@@ -203,12 +203,56 @@ Primary key: `(node_path, extractor_id)`. Indexes: `ix_node_enrichments_node`, `
|
|
|
203
203
|
2. Sorts by `enriched_at` ASC.
|
|
204
204
|
3. Spread-merges each `value` over the author frontmatter (last-write-wins per field).
|
|
205
205
|
|
|
206
|
-
Stale row visibility is opt-in via `mergeNodeWithEnrichments(node, enrichments, { includeStale: true })`
|
|
206
|
+
Stale row visibility is opt-in via `mergeNodeWithEnrichments(node, enrichments, { includeStale: true })` and is a no-op today (no rows are stale-flagged); the flag is preserved for the future Action-prob revision noted above.
|
|
207
207
|
|
|
208
208
|
**Refresh verbs** (see [`cli-contract.md` ยงScan](./cli-contract.md#scan)):
|
|
209
209
|
|
|
210
|
-
- `sm refresh <node.path>` re-runs Extractors against a single node and upserts their enrichment rows.
|
|
211
|
-
- `sm refresh --stale` batches the granular form across every node carrying at least one stale row
|
|
210
|
+
- `sm refresh <node.path>` re-runs Extractors against a single node and upserts their enrichment rows. Extractors are deterministic-only โ they always run for real and persist.
|
|
211
|
+
- `sm refresh --stale` batches the granular form across every node carrying at least one stale row; in this revision the stale set is always empty so the verb prints a "nothing to do" advisory and exits `0`.
|
|
212
|
+
|
|
213
|
+
### `scan_contributions`
|
|
214
|
+
|
|
215
|
+
Phase 3 / View contribution system. Per-node typed payloads emitted by extractors via `ctx.emitContribution(id, payload)` (and rules via `ctx.emitScopeContribution(id, payload)` for scope-level contracts). One row per `(plugin_id, extension_id, node_path, contribution_id)` tuple.
|
|
216
|
+
|
|
217
|
+
| Column | Type | Constraint |
|
|
218
|
+
|---|---|---|
|
|
219
|
+
| `plugin_id` | TEXT | NOT NULL | Owning plugin namespace per spec ยง A.6. |
|
|
220
|
+
| `extension_id` | TEXT | NOT NULL | Extension id within the plugin. |
|
|
221
|
+
| `node_path` | TEXT | NOT NULL | FK semantically to `scan_nodes.path`; orphan-swept on persist when the parent node disappears. |
|
|
222
|
+
| `contribution_id` | TEXT | NOT NULL | Manifest Record key under `extension.viewContributions[<contributionId>]`. |
|
|
223
|
+
| `contract` | TEXT | NOT NULL | Closed-enum-by-spec contract name; mirror of `view-contracts.schema.json#/$defs/ContractName`. 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. |
|
|
224
|
+
| `payload_json` | TEXT | NOT NULL | JSON-serialised payload, already validated against the contract's payload schema (`view-contracts.schema.json#/$defs/payloads/<contract>`) at emit time. Off-contract payloads emit `extension.error` and drop silently. |
|
|
225
|
+
| `emitted_at` | INTEGER | NOT NULL | Unix milliseconds. |
|
|
226
|
+
|
|
227
|
+
Primary key: `(plugin_id, extension_id, node_path, contribution_id)`. Indexes: `ix_scan_contributions_node_path` (inspector lazy-fetch + orphan sweep), `ix_scan_contributions_plugin_id` (catalog sweep + `purgeByPlugin`).
|
|
228
|
+
|
|
229
|
+
**Persistence โ orphan + catalog sweep + upsert (NOT pure replace-all).** The watcher's cached pass leaves the contributions buffer empty for cached nodes โ the orchestrator skips `extract()` when the per-(node, extractor) cache hits, so no `emitContribution` fires. A naive wipe-all would silently drop the prior valid rows on every watcher boot. The persist runs three passes inside the same tx as the rest of the scan zone:
|
|
230
|
+
|
|
231
|
+
1. **Orphan sweep** โ drops every row whose `node_path` is NOT in the current live node set (`livePaths` derived from `result.nodes`). Disappeared nodes lose their contributions automatically.
|
|
232
|
+
2. **Catalog sweep** โ drops every row whose qualified id `(pluginId, extensionId, contributionId)` is NOT in the registered runtime catalog (`registeredContributionKeys` collected via `collectRegisteredContributionKeys(composed)`). Uninstalled plugins, disabled bundles, and removed contributions lose their rows on the next scan.
|
|
233
|
+
3. **Upsert** โ `INSERT ... ON CONFLICT DO UPDATE SET payload_json = excluded.payload_json` for every row in the buffer. PK conflict refreshes `payload_json` + `emitted_at`.
|
|
234
|
+
|
|
235
|
+
Cached nodes' rows survive untouched โ they're neither orphaned (still in the live set) nor uninstalled (still in the catalog) nor in the buffer (no re-emit). The next time the body changes, the orchestrator re-runs the extractor, fresh contributions land in the buffer, and the upsert refreshes them.
|
|
236
|
+
|
|
237
|
+
**Backwards-compat fallbacks.** `IPersistOptions.livePaths`, `IPersistOptions.registeredContributionKeys` are both optional. Absent / empty `livePaths` falls back to wipe-all (legacy behaviour). Absent / empty `registeredContributionKeys` skips the catalog sweep (rows for disabled plugins linger until next purge).
|
|
238
|
+
|
|
239
|
+
NOT analogous to `state_plugin_kvs` (which is plugin-managed). Belongs to the `scan_*` family โ sweep semantics replace pure replace-all but the data is still scan-derived.
|
|
240
|
+
|
|
241
|
+
### `scan_node_tags`
|
|
242
|
+
|
|
243
|
+
Tags ยท dual-source. One row per `(node_path, tag, source)` triple, projected at persist time from BOTH `frontmatter.tags` (with `source='author'`) and `sidecar.annotations.tags` (with `source='user'`). 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)`.
|
|
244
|
+
|
|
245
|
+
| Column | Type | Constraint |
|
|
246
|
+
|---|---|---|
|
|
247
|
+
| `node_path` | TEXT | NOT NULL | FK semantically to `scan_nodes.path`; orphan-swept on persist when the parent node disappears. |
|
|
248
|
+
| `tag` | TEXT | NOT NULL | Free-form; case-preserving. Empty strings rejected upstream by the schema's `minLength: 1` on each item. |
|
|
249
|
+
| `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. |
|
|
250
|
+
|
|
251
|
+
Primary key: `(node_path, tag, source)`. Indexes: `ix_scan_node_tags_tag` (search by tag), `ix_scan_node_tags_node_path` (per-node lookup, e.g. inspector projection).
|
|
252
|
+
|
|
253
|
+
**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 source state. Cached nodes' tag rows are projected from the cached `node.frontmatter.tags` / `node.sidecar.annotations.tags` (both 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.
|
|
254
|
+
|
|
255
|
+
The wire shape on `/api/nodes` joins this table to project `node.tags = { byAuthor: string[], byUser: string[] }`. The kernel `Node` interface (TypeScript) does NOT carry `tags` โ consumers walking the canonical sources read `node.frontmatter.tags` and `node.sidecar.annotations.tags` directly (consistent with the post-decision-#2 posture).
|
|
212
256
|
|
|
213
257
|
---
|
|
214
258
|
|
package/index.json
CHANGED
|
@@ -174,64 +174,69 @@
|
|
|
174
174
|
}
|
|
175
175
|
]
|
|
176
176
|
},
|
|
177
|
-
"specPackageVersion": "0.
|
|
177
|
+
"specPackageVersion": "0.19.0",
|
|
178
178
|
"integrity": {
|
|
179
179
|
"algorithm": "sha256",
|
|
180
180
|
"files": {
|
|
181
|
-
"CHANGELOG.md": "
|
|
181
|
+
"CHANGELOG.md": "0a4cbbfb66c1f6a68393564645708128daca1e0a6c494da3145cb81b6182834c",
|
|
182
182
|
"README.md": "3ffd34a0d55c76d53777d47244c727e2262bf5d8bcde21035e9ec0154640cee7",
|
|
183
|
-
"architecture.md": "
|
|
184
|
-
"cli-contract.md": "
|
|
183
|
+
"architecture.md": "2f159fad4c03266a766f9c03161ef96b61486d11af8edf727990320e756af3c2",
|
|
184
|
+
"cli-contract.md": "75f6912d3aae5a30bee3f0e3bb5088967a88661a47ca4d83f1d60598af8d6c5d",
|
|
185
185
|
"conformance/README.md": "5f94a6ac637b7c992fcd7e53d32eed1b8887eeef05eb6ca3b5ec8a0b5045cd21",
|
|
186
186
|
"conformance/cases/kernel-empty-boot.json": "ad4bbe9d637537625025c8bdb61285b1433568a2544b1ce0248f304ccff8c350",
|
|
187
|
-
"conformance/cases/
|
|
188
|
-
"conformance/cases/
|
|
189
|
-
"conformance/
|
|
187
|
+
"conformance/cases/orphan-markdown-fallback.json": "8ef6e49b7e6532bd845d9f54974a16e537cf98d355f0c5e4f4fb06abac3adcc5",
|
|
188
|
+
"conformance/cases/plugin-missing-ui-rejected.json": "bdebee810436e6be88edf2fe38ddc6939fd3f53e6a12dc1d66da051c4922f1e9",
|
|
189
|
+
"conformance/cases/sidecar-end-to-end.json": "7caa3dbbdcb8d2ba1017cea37d557f345433977091c51b0f42308df6fab444d8",
|
|
190
|
+
"conformance/coverage.md": "981cf3377b50aa5535d163b6445af27e0408c826fb870ee05896750603f6bf2f",
|
|
191
|
+
"conformance/fixtures/orphan-markdown/.claude/agents/reviewer.md": "7f062731106f2d9811e4fffcf6ab44b8dfff4cfb16536a469514cc0664e832bf",
|
|
192
|
+
"conformance/fixtures/orphan-markdown/ARCHITECTURE.md": "d6b6e18d4b963b26a292de73348c3396fd4710ab4c4bdd6cf094e581f99ec8d6",
|
|
190
193
|
"conformance/fixtures/plugin-missing-ui/.skill-map/plugins/bad-provider/plugin.json": "4d78af6f12faa9d131e2a19f1dbb8f250baacc525978f3a8c858932b95da4ff6",
|
|
191
194
|
"conformance/fixtures/plugin-missing-ui/.skill-map/plugins/bad-provider/provider.js": "d1f4898b43201d24f048171ce84d433b68694457452fbc64498857f5da3e9bbb",
|
|
192
195
|
"conformance/fixtures/plugin-missing-ui/notes/example.md": "55767f0aa1b6774546a99f28c58e7b732aa9cfa5dfce8d0326470f7f622f577e",
|
|
193
196
|
"conformance/fixtures/preamble-v1.txt": "1e0aeef224b64477bdc13a949c3ad402e68249caf499ecdba1302371677c068b",
|
|
194
|
-
"conformance/fixtures/sidecar-end-to-end/.claude/agents/orphan.sm": "
|
|
197
|
+
"conformance/fixtures/sidecar-end-to-end/.claude/agents/orphan.sm": "e68d3cecb39d2da5e0a5fca22568fc264c0b16c0a0f56e199c56deb984210ad9",
|
|
195
198
|
"conformance/fixtures/sidecar-end-to-end/.claude/agents/stale.md": "cb3a95777cba530d47e6040c5601b6dcd34b5fc653dd69f183369eb6bdd956b5",
|
|
196
|
-
"conformance/fixtures/sidecar-end-to-end/.claude/agents/stale.sm": "
|
|
199
|
+
"conformance/fixtures/sidecar-end-to-end/.claude/agents/stale.sm": "cb04f7f3103b4218b09fd4da92f7ea429588b04c1dac6a9547ce362263b11224",
|
|
197
200
|
"conformance/fixtures/sidecar-example/agent-example.md": "1343ebc245e1fde415c16320ce53bcfad366ae2be1bcc7084337cfceaacdad38",
|
|
198
|
-
"conformance/fixtures/sidecar-example/agent-example.sm": "
|
|
199
|
-
"db-schema.md": "
|
|
201
|
+
"conformance/fixtures/sidecar-example/agent-example.sm": "41200387e74a120c554a34dfabc50dd2151067a1c6599695c59412d8eab38bb4",
|
|
202
|
+
"db-schema.md": "ef3de28061957f036c97384fc2cae0308f91624ab2b0940cef6d46d5c1998089",
|
|
200
203
|
"interfaces/security-scanner.md": "4a982667008f233656f44c61ce9948e062432d3debdcbf7a134da03bd4139d7d",
|
|
201
204
|
"job-events.md": "8f371e0991816eca2e1a55cbd8a50733546ca5e7c861588048c18be1d22dbd57",
|
|
202
205
|
"job-lifecycle.md": "12bfc27690c92cf93682a3b6fbfeb7e2d252d33f704fd2d7de9a13db713e6281",
|
|
203
|
-
"plugin-author-guide.md": "
|
|
206
|
+
"plugin-author-guide.md": "ace7b2df6eb54cdc92ef1ab99c0a6a2a0a88f64b229b0b1b94009d9d6458d2c0",
|
|
204
207
|
"plugin-kv-api.md": "04b2178f46fb88adeae9240df9c9e1761b660396072001dac32cd402e11a2d7d",
|
|
205
208
|
"prompt-preamble.md": "fb40ab510234383326f198dec82cd6d744f28b7432eebac6cbfbb7ca1c483b7d",
|
|
206
|
-
"schemas/annotations.schema.json": "
|
|
207
|
-
"schemas/api/rest-envelope.schema.json": "
|
|
209
|
+
"schemas/annotations.schema.json": "4f80b6a2122ecacc6a1dcffa0912ef462c6a082182c0ad179b355b3054f390e8",
|
|
210
|
+
"schemas/api/rest-envelope.schema.json": "db6588cd8b01eef08e1e36ede2f7be8ced8a56965911875399b54687680edaf4",
|
|
208
211
|
"schemas/bump-report.schema.json": "c2d853715d5f50098567bc23382a4e81baf78d589c6e1baf67d3b841e7f7d8ae",
|
|
209
212
|
"schemas/conformance-case.schema.json": "7cd0f3aae5124f24be57cddb213d002d0466f79d06fd3da896075c8b28650410",
|
|
210
213
|
"schemas/execution-record.schema.json": "9628fa557cb856402f3a5f1d1167c609e46a197c850fe8171abfddd46c1028a8",
|
|
211
214
|
"schemas/extensions/action.schema.json": "262272175c06a2e33c08f819a45c3ef8260276c91a9d0542fdffc932aeb32db7",
|
|
212
|
-
"schemas/extensions/base.schema.json": "
|
|
213
|
-
"schemas/extensions/extractor.schema.json": "
|
|
215
|
+
"schemas/extensions/base.schema.json": "84277972d14e35397dde60deb102a375f15ed1487540c909d40780f5a5e250b7",
|
|
216
|
+
"schemas/extensions/extractor.schema.json": "9ce1ac00ce0e193d86c8b0fbddb2e4cf58c420ed105bab500019501cf7037c26",
|
|
214
217
|
"schemas/extensions/formatter.schema.json": "2ab092aa37ae349c69b93071ed4f0e131affb7bb5799516ca82c721262631b36",
|
|
215
218
|
"schemas/extensions/hook.schema.json": "7465c38e0765edf23e49d4f96c539d04323f1cf564af1c60ee637c79a6d39239",
|
|
216
|
-
"schemas/extensions/provider.schema.json": "
|
|
219
|
+
"schemas/extensions/provider.schema.json": "077c0c079e3965cee667019f76ee1e180d6b1f4162767d868bccc912e8dfbf89",
|
|
217
220
|
"schemas/extensions/rule.schema.json": "8ff420bde498f50db114c352305d487c71aef2dd746fd0c24976ff6a09865c22",
|
|
218
|
-
"schemas/frontmatter/base.schema.json": "
|
|
221
|
+
"schemas/frontmatter/base.schema.json": "ec4abde950c31639974fc078e6bdc74ed48da4d2c0a996f5248684406910a178",
|
|
219
222
|
"schemas/history-stats.schema.json": "23f472d1de06d23fc775aabba821f8375f347af4dc8d89ba567980d61a11f9de",
|
|
223
|
+
"schemas/input-types.schema.json": "f1f51ccda746ea3c8a404757f60c89e403619e88ec4137a50af100ec89f8f4b5",
|
|
220
224
|
"schemas/issue.schema.json": "40f6f8abadcce0fd8eac9df27ffcc20b2fc9fda6970142ddb8e7e56b1760b9b1",
|
|
221
225
|
"schemas/job.schema.json": "ffbdd51c54b487c44eb57fabd07f624ac1030c14ef69b46933c154092853a84c",
|
|
222
226
|
"schemas/link.schema.json": "0a95a24849a38b9ef5bad5361519a9f9e012b5bc3001289fad29d0851fceff6b",
|
|
223
|
-
"schemas/node.schema.json": "
|
|
224
|
-
"schemas/plugins-registry.schema.json": "
|
|
225
|
-
"schemas/project-config.schema.json": "
|
|
227
|
+
"schemas/node.schema.json": "2ede4385e796cbf416c494d810dcb6d6036b35e71561efee46f5675bf0a015fe",
|
|
228
|
+
"schemas/plugins-registry.schema.json": "fc5f9a15df94ea2218dadfa2294a9d7ae9c9e6015a80c71e57fbd3ece913ffbd",
|
|
229
|
+
"schemas/project-config.schema.json": "17b109a0843b8345ea91cb7af80d003905689b2ae6bfd5f5db422d4a6ae5972b",
|
|
226
230
|
"schemas/report-base-deterministic.schema.json": "6f8b38c097994ee87e0639935c42b5e85d8ea4244959ca397978171b0d7d2222",
|
|
227
231
|
"schemas/report-base.schema.json": "a1021e9a59b4df9f99cd92454d797e88469766e7d49f52d231c4645ffdfdad8f",
|
|
228
232
|
"schemas/scan-result.schema.json": "d1a8782e198bc9bb92dad247437aefa1b02f92ff8dca8562eaf2348fd7c5cf0c",
|
|
229
|
-
"schemas/sidecar.schema.json": "
|
|
233
|
+
"schemas/sidecar.schema.json": "4fca64f7580384c618915e545f5d24aa823b6a47e2ffec331b1720fa19934365",
|
|
230
234
|
"schemas/summaries/agent.schema.json": "3d22558eeb170e00c4fc32018a810d27333cc632c9e528ff386100cfdfded087",
|
|
231
235
|
"schemas/summaries/command.schema.json": "2bffd606b24f7df9ccd13890af8725adfbfb8a2d7782fee1e0ac5250b9059117",
|
|
232
236
|
"schemas/summaries/hook.schema.json": "36f876f3b1a60d45be97a0848c79fd18744b434dfdcefc366f033b253d56268c",
|
|
233
|
-
"schemas/summaries/markdown.schema.json": "
|
|
237
|
+
"schemas/summaries/markdown.schema.json": "33e2a1a11ec08a860c0c220609235c6fbdfda9ce19b6d65238f467f132ed4e54",
|
|
234
238
|
"schemas/summaries/skill.schema.json": "f01bab92c51d64ee23e61587e42cf0dc5b37a2f518f5b12b3d1d456390338aa8",
|
|
239
|
+
"schemas/view-contracts.schema.json": "4e668f4527bc723187f516dd658587a832af66d0432a769a8aad506817c6c26f",
|
|
235
240
|
"versioning.md": "996e62006423edc01151a6f7869605f76c5e1454cc30b38d9f616925b5bcfb64"
|
|
236
241
|
}
|
|
237
242
|
}
|