@skill-map/spec 0.30.0 → 0.31.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 +65 -0
- package/architecture.md +39 -0
- package/cli-contract.md +10 -0
- package/conformance/cases/plugin-missing-ui-rejected.json +4 -3
- package/conformance/coverage.md +1 -0
- package/db-schema.md +2 -0
- package/index.json +11 -10
- package/package.json +1 -1
- package/schemas/link.schema.json +4 -3
- package/schemas/node.schema.json +10 -1
- package/schemas/project-config.schema.json +17 -2
- package/schemas/signal.schema.json +89 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,70 @@
|
|
|
1
1
|
# Spec changelog
|
|
2
2
|
|
|
3
|
+
## 0.31.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 29fb253: Active-provider lens model, Signal IR scaffold, numeric `Confidence`, MCP virtual nodes, OpenAI Codex provider, and the Phase 4b extractor mudanza in one coherent migration.
|
|
8
|
+
|
|
9
|
+
**Spec foundation**
|
|
10
|
+
|
|
11
|
+
- New `activeProvider` field on `project-config.schema.json` (per-project lens id, drives the new drop+rescan lifecycle and the per-extension `precondition.provider` gating).
|
|
12
|
+
- New `signal.schema.json` defining the multi-candidate `Signal` IR emitted by extractors via `ctx.emitSignal()`.
|
|
13
|
+
- `Node.virtual` (boolean) and `Node.derivedFrom` (string[]) on `node.schema.json` so synthetic / derived entities (MCP servers, future Codex AGENTS.md cascade) coexist with filesystem-backed nodes.
|
|
14
|
+
- `link.confidence` migrates from the `'high' | 'medium' | 'low'` string union to `number` in `[0..1]`. The SQL column flips to `REAL` with a range check.
|
|
15
|
+
- `spec/architecture.md` gets a top-level **Active Provider Lens** section and an **Extractor · Signal IR (opt-in)** subsection. `spec/cli-contract.md` documents the lens-change UX. `spec/db-schema.md` notes the scan-zone drop on lens change. Coverage matrix gains a row for the Signal schema.
|
|
16
|
+
|
|
17
|
+
**Kernel**
|
|
18
|
+
|
|
19
|
+
- `Signal` / `SignalCandidate` / `SignalRange` / `SignalContext` types alongside `Link` in `kernel/types.ts`. `ConfidenceTier` constants (`HIGH = 0.9`, `MEDIUM = 0.6`, `LOW = 0.3`) preserve bucket-thinking call sites.
|
|
20
|
+
- New `IExtractorCallbacks.emitSignal` and `IExtractorCallbacks.emitNode` plus the `IEmittedNode` payload. Orchestrator wires both with validators that drop off-spec emissions via `extension.error` events; the walker merges virtual nodes into the accumulator with first-wins dedup.
|
|
21
|
+
- New `orchestrator/resolver.ts` scaffold (first-candidate-wins today; provider `resolverRules` land later).
|
|
22
|
+
- `frontmatter-toml` parser registered in the kernel parser registry (`smol-toml@1.6.1` pinned).
|
|
23
|
+
- `precondition.provider` is now enforced in `computeCacheDecision`: an extractor whose manifest declares `['claude']` only runs on nodes the `claude` provider classified.
|
|
24
|
+
|
|
25
|
+
**Lens model wiring**
|
|
26
|
+
|
|
27
|
+
- `src/core/config/active-provider.ts`: `resolveActiveProvider(cwd)` with filesystem auto-detect (`.claude/` / `.gemini/` / `.codex/` / `AGENTS.md` / `.cursor/`) and `isExtensionActiveUnderLens` predicate.
|
|
28
|
+
- `src/cli/util/scan-zone-drop.ts`: atomic `DELETE FROM scan_*` helper, shared by `sm db reset` and the new `sm config set activeProvider` hook.
|
|
29
|
+
- `sm config set activeProvider <id>` drops the scan zone and prints the lens-switch receipt with a hint to rescan.
|
|
30
|
+
- BFF: `GET / PATCH /api/active-provider` with the same drop-on-switch semantic, registered before the catch-all.
|
|
31
|
+
- Settings UI: new "Active provider" row in the Project tab (now at the top of the section), confirm dialog before the destructive switch, post-switch announcement.
|
|
32
|
+
|
|
33
|
+
**Numeric `Confidence` migration**
|
|
34
|
+
|
|
35
|
+
Atomic across spec / kernel / SQL / extractors / tests:
|
|
36
|
+
|
|
37
|
+
- `Confidence = number` in `kernel/types.ts`; `enum-parsers.ts` range-checks `[0..1]`.
|
|
38
|
+
- `validateLink` rejects out-of-range values with a `link-confidence-out-of-range` extension.error.
|
|
39
|
+
- The 5 universal extractors emit calibrated numeric values (`annotations` 1.0, `markdown-link` 0.95, `external-url-counter` 0.3, `at-directive` 0.85/0.5, `slash` 0.8).
|
|
40
|
+
- `sm show` renders confidence as a percent (`85%`).
|
|
41
|
+
- `link-conflict` `rankConfidence` reduces to identity; resolver's `bucketConfidence` bridge removed.
|
|
42
|
+
- Rename heuristic uses `ConfidenceTier.HIGH/MEDIUM` and exposes `renameTierLabel` for the analyzer-id mapping.
|
|
43
|
+
- DB column flips from `TEXT` (enum check) to `REAL` (range check) in `001_initial.sql`.
|
|
44
|
+
|
|
45
|
+
**MCP virtual nodes**
|
|
46
|
+
|
|
47
|
+
- New `core/mcp-tools` extractor: detects `tools: [mcp__<server>__<tool>]` in agent/skill/command frontmatter and emits one virtual MCP node (`mcp://<server>`, `kind: 'mcp'`, `virtual: true`, `derivedFrom: [source]`) plus a `references` link from the source. Idempotent: N skills referencing the same server collapse into one node.
|
|
48
|
+
- Claude provider registers the `mcp` kind in its catalog (violet, server icon).
|
|
49
|
+
|
|
50
|
+
**OpenAI Codex provider (MVP)**
|
|
51
|
+
|
|
52
|
+
- New `src/plugins/openai/` bundle: declarative `read: { extensions: ['.toml'], parser: 'toml' }`, classifies `.codex/agents/*.toml` as `agent`. Schema mirrors Codex sub-agent fields (`name`, `description`, `model`, `instructions`, `tools`, `mcp_servers`, `approval_policy`, `sandbox_mode`).
|
|
53
|
+
- `BUNDLE_ORDER` extended in the built-ins generator. 5 bundles, 29 built-in extensions total.
|
|
54
|
+
|
|
55
|
+
**Phase 4b extractor mudanza**
|
|
56
|
+
|
|
57
|
+
- `at-directive` and `slash` move from `src/plugins/core/extractors/` to `src/plugins/claude/extractors/`. Both declare `pluginId: 'claude'` and `precondition: { provider: ['claude'] }`. Universal extractors (markdown-link, external-url-counter, annotations, tools-count, mcp-tools) stay in `core/`.
|
|
58
|
+
|
|
59
|
+
**Settings UI polish**
|
|
60
|
+
|
|
61
|
+
- `<sm-settings-project>` reorders to put the Active provider selector first.
|
|
62
|
+
- `<sm-kind-palette>` filters kinds with zero nodes; the left palette no longer shows always-empty buttons for kinds the loaded set has no instances of. New `__tests__/kind-palette.spec.ts` regression-guards the filter.
|
|
63
|
+
|
|
64
|
+
## User-facing
|
|
65
|
+
|
|
66
|
+
Settings → Project: new Active provider dropdown switches the runtime lens (Claude / Gemini / Codex / Cursor). Tools matching `mcp__name__*` surface MCP nodes in the graph. Codex `.toml` sub-agents under `.codex/agents/` are classified. Link confidence renders as percent.
|
|
67
|
+
|
|
3
68
|
## 0.30.0
|
|
4
69
|
|
|
5
70
|
### Minor Changes
|
package/architecture.md
CHANGED
|
@@ -39,6 +39,32 @@ Any conforming implementation, reference or third-party, MUST respect these boun
|
|
|
39
39
|
|
|
40
40
|
---
|
|
41
41
|
|
|
42
|
+
## Active Provider Lens
|
|
43
|
+
|
|
44
|
+
A skill-map project sees its filesystem through exactly one **active provider lens** at any time. The lens is the provider whose extractors, classifiers, and resolution rules apply to the whole project during a scan. All other enabled providers stay registered but their provider-specific extractors are skipped.
|
|
45
|
+
|
|
46
|
+
The lens is project-scope state. It lives in `.skill-map/settings.json` as the `activeProvider` key (see [`project-config.schema.json`](./schemas/project-config.schema.json#/properties/activeProvider)). When absent, the kernel auto-detects on first scan from filesystem heuristics (`.claude/` → `claude`, `.gemini/` → `gemini`, `.codex/` or root `AGENTS.md` → `openai`, etc.) and persists the result; if the heuristic is ambiguous or yields no result, the CLI and UI prompt the user to pick one of the enabled providers.
|
|
47
|
+
|
|
48
|
+
### Consequence: one graph per project at a time
|
|
49
|
+
|
|
50
|
+
The persisted scan graph (`scan_*` zone) reflects the project as the active lens sees it. No cross-provider merging happens at storage time. A repo with both `.claude/` and `.gemini/` does NOT show "everyone's nodes at once"; it shows the active lens's view.
|
|
51
|
+
|
|
52
|
+
### Consequence: lens change is destructive of the scan zone
|
|
53
|
+
|
|
54
|
+
Switching the active provider drops the `scan_*` zone atomically (nodes, links, issues, scan-result meta) and triggers a fresh scan under the new lens. The `state_*` zone (jobs, executions, summaries, enrichments, plugin KV, favorites) and the `config_*` zone survive untouched. Annotations (`.sm` sidecars on disk) are filesystem state and are also unaffected; the next scan re-derives the in-DB overlay from them.
|
|
55
|
+
|
|
56
|
+
This is a deliberate trade-off. Keeping two scan graphs simultaneously persisted (one per lens) would re-introduce the cross-provider coordination complexity the lens model exists to avoid. The drop+rescan UX is honest: changing lens means changing the world the graph represents, and the graph regenerates from the source of truth (the filesystem) under the new rules.
|
|
57
|
+
|
|
58
|
+
### Cross-provider read at the provider level
|
|
59
|
+
|
|
60
|
+
A provider plugin MAY declare it reads source files belonging to ANOTHER provider's territory. The canonical example: Cursor's runtime consumes `.claude/skills/` and `.codex/skills/` natively; a Cursor provider in skill-map can therefore claim those paths from its own classifier so that under the Cursor lens, those files appear as Cursor-managed nodes with Cursor's interpretation rules. This is provider-internal logic, not a kernel feature; the lens model neither encourages nor prevents it.
|
|
61
|
+
|
|
62
|
+
### Universal extractors and per-provider extractors
|
|
63
|
+
|
|
64
|
+
The lens does NOT gate the universal extractors that ship under `core/` (markdown links, external URLs, sidecar annotations). Those run regardless of the active provider because their semantics are provider-agnostic. Provider-specific extractors (Claude's `@`-directive parser, Gemini's three-surface `@`-parsers, Cursor's picker-derived references, the future Codex AGENTS.md walker) declare `precondition: { provider: '<id>' }` on their manifest; the orchestrator only invokes them when the node's provider matches AND the provider is the active lens.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
42
68
|
## Ports
|
|
43
69
|
|
|
44
70
|
An implementation MUST expose these five ports. Each is an interface (TypeScript, in the reference impl; equivalent in other languages).
|
|
@@ -238,6 +264,19 @@ The `Extractor` runtime contract is `extract(ctx) → void`. The extractor emits
|
|
|
238
264
|
|
|
239
265
|
Extractors are deterministic-only; `ctx.runner` is NOT exposed on the Extractor context. LLM-driven enrichment of a node is an Action concern (queued as a job), not an Extractor concern.
|
|
240
266
|
|
|
267
|
+
### Extractor · Signal IR (opt-in)
|
|
268
|
+
|
|
269
|
+
In addition to the `emitLink` path, Extractors MAY emit **Signals** via `ctx.emitSignal(signal)`. A Signal is a candidate detection: one or many alternative interpretations of the same body or frontmatter location, each carrying its own kind, target, confidence, and rationale. See [`signal.schema.json`](./schemas/signal.schema.json) for the full contract. The Signal IR is opt-in; an extractor whose detection is unambiguous (sidecar `supersedes`, `[text](file.md)` markdown links, plain `https://…` URLs) is encouraged to keep emitting Links directly with `ctx.emitLink`. Signals exist for the cases the resolver actually helps: detections where a single body token can plausibly mean several things and the active provider's rules need to decide.
|
|
270
|
+
|
|
271
|
+
The kernel's **resolver phase** runs after extraction completes and before analysis starts. For each Signal, the resolver:
|
|
272
|
+
|
|
273
|
+
1. Filters candidates whose `extractorId` is not enabled (per `plugins.<id>.extensions.<extId>.enabled` overrides).
|
|
274
|
+
2. Applies the active Provider's resolution rules (declared on `IProvider.resolverRules`) to rank surviving candidates: priority order, tie-break by confidence, then by longest range, then by `extractorId` declaration order.
|
|
275
|
+
3. Materialises the winning candidate as a Link (indistinguishable from a Link emitted directly via `emitLink`). The rejected candidates remain accessible to analyzers via `IAnalyzerContext.signals` for collision-detection and conflict-visualization use cases.
|
|
276
|
+
4. Rejects all candidates and emits no Link if every interpretation has confidence below the configured floor.
|
|
277
|
+
|
|
278
|
+
The Signal's `range` field (byte offsets in the source) powers two cross-extractor analyses no Link can support today: collision detection (two extractors emitting Signals with overlapping ranges) and fragmentation detection (an authored intent split across several adjacent Signals). Both surface as analyzer issues, not silent merges.
|
|
279
|
+
|
|
241
280
|
### Extractor · enrichment layer
|
|
242
281
|
|
|
243
282
|
`ctx.enrichNode(partial)` is the only writable surface the Extractor pipeline has on a node. The author's frontmatter on `scan_nodes.frontmatter_json` is read-only from any Extractor. Implementations MUST:
|
package/cli-contract.md
CHANGED
|
@@ -76,6 +76,16 @@ etc.). Constraints:
|
|
|
76
76
|
|
|
77
77
|
Everything else under `$HOME` MUST NOT be touched.
|
|
78
78
|
|
|
79
|
+
### Active provider lens
|
|
80
|
+
|
|
81
|
+
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).
|
|
82
|
+
|
|
83
|
+
CLI surfaces:
|
|
84
|
+
|
|
85
|
+
- **Auto-detect on first scan**: when `activeProvider` is absent, `sm scan` and `sm watch` run a filesystem heuristic (`.claude/` → `claude`, `.gemini/` → `gemini`, `.codex/` or root `AGENTS.md` → `openai`, etc.). On unambiguous match, the result is persisted to `settings.json` and the scan proceeds; on no match, the CLI exits non-zero with a "no provider detected, set `activeProvider` in settings or install a provider plugin" message. On ambiguous match (multiple providers detected), the CLI prompts the user interactively (or fails with exit code 2 under `--yes` if no default is configured).
|
|
86
|
+
- **Manual override**: `sm config set activeProvider <id>` switches the lens. The verb drops the `scan_*` zone atomically (see [`db-schema.md`](./db-schema.md#zones)) and triggers an immediate rescan under the new lens. `state_*` and `config_*` zones survive.
|
|
87
|
+
- **No per-scan flag**: there is no `sm scan --provider=<id>` flag. The lens is a project-level decision, not a per-invocation parameter. The drop+rescan cost makes per-invocation switching the wrong default UX.
|
|
88
|
+
|
|
79
89
|
---
|
|
80
90
|
|
|
81
91
|
## Targeted fan-out flags
|
|
@@ -10,11 +10,12 @@
|
|
|
10
10
|
"assertions": [
|
|
11
11
|
{ "type": "exit-code", "value": 0 },
|
|
12
12
|
{ "type": "stderr-matches", "pattern": "plugin bad-provider:.*invalid.*must have required property 'ui'" },
|
|
13
|
-
{ "type": "json-path", "path": "$.providers.length", "equals":
|
|
13
|
+
{ "type": "json-path", "path": "$.providers.length", "equals": 5 },
|
|
14
14
|
{ "type": "json-path", "path": "$.providers[0]", "equals": "claude" },
|
|
15
15
|
{ "type": "json-path", "path": "$.providers[1]", "equals": "gemini" },
|
|
16
|
-
{ "type": "json-path", "path": "$.providers[2]", "equals": "
|
|
17
|
-
{ "type": "json-path", "path": "$.providers[3]", "equals": "
|
|
16
|
+
{ "type": "json-path", "path": "$.providers[2]", "equals": "openai" },
|
|
17
|
+
{ "type": "json-path", "path": "$.providers[3]", "equals": "agent-skills" },
|
|
18
|
+
{ "type": "json-path", "path": "$.providers[4]", "equals": "markdown" },
|
|
18
19
|
{ "type": "json-path", "path": "$.nodes.length", "equals": 1 }
|
|
19
20
|
]
|
|
20
21
|
}
|
package/conformance/coverage.md
CHANGED
|
@@ -44,6 +44,7 @@ This file is hand-maintained. A CI check before spec release compares the schema
|
|
|
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. |
|
|
45
45
|
| 34 | `conformance-result.schema.json` |, | 🔴 missing | Machine-readable output of `sm conformance run --json`. Self-referential by design (a conformance case would invoke the verb against itself); a direct case is deferred until the runner gains a meta-loopback mode. Implementation tests at `src/test/conformance-cli.test.ts` cover the envelope shape today. |
|
|
46
46
|
| 35 | `user-settings.schema.json` | (indirect via `no-global-scope`) | 🟡 partial | Per-user / per-machine settings file at `~/.skill-map/settings.json` (the narrow `$HOME` exception, see `cli-contract.md` §User-settings file). Direct case is not added because alt-impls MAY choose to not ship an update-check feature, requiring them to produce this file would over-prescribe. The implementation-side AJV round-trip is covered by `src/test/user-settings-store.test.ts` (15 cases: defaults, malformed JSON, schemaVersion mismatch, wrong-type fields, unknown top-level keys, deep-merge writes, off-shape rejection). The behavioral counterpart (no global / user scope) lives at `no-global-scope` in the non-schema table below. |
|
|
47
|
+
| 37 | `signal.schema.json` |, | 🔴 missing | Intermediate Representation (IR) emitted by extractors via `ctx.emitSignal()`; the kernel resolver phase consumes Signals and materialises Links. Opt-in: the existing `ctx.emitLink()` path coexists. Cases required (2): (a) `extractor-emits-signal`, an extractor emits a multi-candidate Signal and the resolver picks the highest-confidence candidate per the active Provider's `resolverRules`; (b) `signal-collision-detection`, two extractors emit Signals with overlapping `range` and the resolver surfaces the collision to analyzers via `IAnalyzerContext.signals`. Blocked by the kernel resolver phase landing in Phase 2 of the active-lens migration. |
|
|
47
48
|
|
|
48
49
|
> **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.
|
|
49
50
|
|
package/db-schema.md
CHANGED
|
@@ -36,6 +36,8 @@ Every kernel table belongs to exactly one zone, identified by a mandatory name p
|
|
|
36
36
|
|
|
37
37
|
`sm db reset` drops `scan_*` only (non-destructive, equivalent to forcing the next scan from a clean slate). `sm db reset --state` also drops `state_*` (destructive to operational history). `sm db reset --hard` deletes the DB file entirely. `sm db backup` preserves `state_*` + `config_*`; `scan_*` is always regenerated on demand and is never included in backups.
|
|
38
38
|
|
|
39
|
+
**Active-provider lens change**: switching the `activeProvider` setting (see [`cli-contract.md` §Active provider lens](./cli-contract.md#active-provider-lens) and [`architecture.md` §Active Provider Lens](./architecture.md#active-provider-lens)) drops the `scan_*` zone atomically and triggers a fresh scan under the new lens. Identical effect to `sm db reset` followed by `sm scan`, but bundled as a single transaction so the user never sees an empty graph between the two. `state_*` and `config_*` are preserved across the switch. The `config_plugins` and `config_preferences` rows survive (including the new `activeProvider` value itself).
|
|
40
|
+
|
|
39
41
|
---
|
|
40
42
|
|
|
41
43
|
## Naming conventions (normative)
|
package/index.json
CHANGED
|
@@ -174,21 +174,21 @@
|
|
|
174
174
|
}
|
|
175
175
|
]
|
|
176
176
|
},
|
|
177
|
-
"specPackageVersion": "0.
|
|
177
|
+
"specPackageVersion": "0.31.0",
|
|
178
178
|
"integrity": {
|
|
179
179
|
"algorithm": "sha256",
|
|
180
180
|
"files": {
|
|
181
|
-
"CHANGELOG.md": "
|
|
181
|
+
"CHANGELOG.md": "6c55bfe4d35bb532342ce36284ce682ff9610a03ccb997c90f95bd41b783ef73",
|
|
182
182
|
"README.md": "54c4649fa9742bf2f74423ea78788a7474ce09649cbe1e72a270b606cf16a0a5",
|
|
183
|
-
"architecture.md": "
|
|
184
|
-
"cli-contract.md": "
|
|
183
|
+
"architecture.md": "d7c01c6de2fb49959056bf01847fc290fafe26e094a8660690a9bb404de5694d",
|
|
184
|
+
"cli-contract.md": "196ad728c41ef5467ccfcdfdc983ef937d25a7aca426f622a70b300cd1d68c66",
|
|
185
185
|
"conformance/README.md": "6871dde25b5770ed945284c9e0f749e0768ec3f5ba4966bdb215985789e43887",
|
|
186
186
|
"conformance/cases/kernel-empty-boot.json": "2a5be9c93143d07a16d998df09dcc8fa4ea2d2f9a0bff6417573ed5a770352c1",
|
|
187
187
|
"conformance/cases/no-global-scope.json": "1284763988026d924c0bd78ba8a9f417dc88f5b7e9f4c2b642ae0c447758bfd4",
|
|
188
188
|
"conformance/cases/orphan-markdown-fallback.json": "8ef6e49b7e6532bd845d9f54974a16e537cf98d355f0c5e4f4fb06abac3adcc5",
|
|
189
|
-
"conformance/cases/plugin-missing-ui-rejected.json": "
|
|
189
|
+
"conformance/cases/plugin-missing-ui-rejected.json": "f2fb673ad01308b018f0a5ed0d3d2085b8ffab25230e60bf31569859c5c583cc",
|
|
190
190
|
"conformance/cases/sidecar-end-to-end.json": "dbb3640f95769a36b881855a261f918481edadea13a7eb0765c6090f2417a142",
|
|
191
|
-
"conformance/coverage.md": "
|
|
191
|
+
"conformance/coverage.md": "feb75cd38ddffa6252ed277dd23b8be4228b1ef2f37ce50871d00283bae4f108",
|
|
192
192
|
"conformance/fixtures/orphan-markdown/.claude/agents/reviewer.md": "7f062731106f2d9811e4fffcf6ab44b8dfff4cfb16536a469514cc0664e832bf",
|
|
193
193
|
"conformance/fixtures/orphan-markdown/ARCHITECTURE.md": "d6b6e18d4b963b26a292de73348c3396fd4710ab4c4bdd6cf094e581f99ec8d6",
|
|
194
194
|
"conformance/fixtures/plugin-missing-ui/.skill-map/plugins/bad-provider/kinds/markdown/kind.json": "6676a89bae5197e23cf50f1c11d596db558ac80f7334a7208fe57d8b92422251",
|
|
@@ -202,7 +202,7 @@
|
|
|
202
202
|
"conformance/fixtures/sidecar-end-to-end/.claude/agents/stale.sm": "cb04f7f3103b4218b09fd4da92f7ea429588b04c1dac6a9547ce362263b11224",
|
|
203
203
|
"conformance/fixtures/sidecar-example/agent-example.md": "741131403e8c9580d0b7a8c2446cb4502d01f80053b7a2092663de92431aaa82",
|
|
204
204
|
"conformance/fixtures/sidecar-example/agent-example.sm": "8329950d49c69a1199bbe6c06e32b8513973e64207b0db8756b67301e6a1f1e2",
|
|
205
|
-
"db-schema.md": "
|
|
205
|
+
"db-schema.md": "3c34768a6ba34f6d77da84e0cdf73b977dca68ef50679decd131f2a5e20fb593",
|
|
206
206
|
"interfaces/security-scanner.md": "e8049712b9cf7a07c786bf19f8f775f8ef9638f063f7fba5c7a8b1431b92f38e",
|
|
207
207
|
"job-events.md": "84206168ac12b536d34470d62f8c8cba95dab181fee66d23203c2cf5dfbee716",
|
|
208
208
|
"job-lifecycle.md": "9c429121f98a07c8795f8979ed1abc5e5334e3f89db51585a8da55c527ef855b",
|
|
@@ -228,16 +228,17 @@
|
|
|
228
228
|
"schemas/input-types.schema.json": "c713b768d0b0e3d0c764afb401189f7fb624a82b4e988b73aab015cf9c67c01f",
|
|
229
229
|
"schemas/issue.schema.json": "fa3344e75f1c3a5304291ca355bb973046552a68871ad6eb4edafca1cd9e1be8",
|
|
230
230
|
"schemas/job.schema.json": "e43e1761c99920beffe1de12ef8f32fe29f97838bd8686742b637c19c4dbb395",
|
|
231
|
-
"schemas/link.schema.json": "
|
|
232
|
-
"schemas/node.schema.json": "
|
|
231
|
+
"schemas/link.schema.json": "2450732829652ece58c853ca97711a8bbb64ac65e52e89e3b51024c073dddc9a",
|
|
232
|
+
"schemas/node.schema.json": "8d0635a80c8e6f22be7fa04071654e857fc052869de15839f4b29593aa4527a3",
|
|
233
233
|
"schemas/plugins-doctor.schema.json": "c1d92f30fdb0080e8cd8f7dc5d43e01aae02a16640bc5eb04811c337a275de58",
|
|
234
234
|
"schemas/plugins-registry.schema.json": "cca7ae65f0c22510ea27ea5ae34e0074f5beb5871a57b005b6b831e6ceaff5c0",
|
|
235
|
-
"schemas/project-config.schema.json": "
|
|
235
|
+
"schemas/project-config.schema.json": "1357e14026b038ac097d0528ee135728b1ddeb383db6a8cb1e345804e62311e5",
|
|
236
236
|
"schemas/refresh-report.schema.json": "54519b8caf86ba84c182f9565be9b5084bc1631ae05e9217ee18f34c0039fff3",
|
|
237
237
|
"schemas/report-base-deterministic.schema.json": "9d318d0181d121097c906ef3af1c52d71c782740bd04cf23418d7627ce2c3ed5",
|
|
238
238
|
"schemas/report-base.schema.json": "a1021e9a59b4df9f99cd92454d797e88469766e7d49f52d231c4645ffdfdad8f",
|
|
239
239
|
"schemas/scan-result.schema.json": "214bc12fbb9946642cbba3b23513dade60e7d6a5b6a9ed3dd0818f135b450185",
|
|
240
240
|
"schemas/sidecar.schema.json": "8856c387477340efbdd0a585d74bfb07a99ba15b9ce593cc67d9efebc67c6bfc",
|
|
241
|
+
"schemas/signal.schema.json": "2540c0014a78ebc902eb71b6815c35fa006c714b57d07dcb7415bd3c3da185b5",
|
|
241
242
|
"schemas/summaries/agent.schema.json": "bf540f9a804f2b43756ab33b7deb0462620d26e88cc9379c75a5f87d3b1b47d8",
|
|
242
243
|
"schemas/summaries/command.schema.json": "c26f6965f77c5058608feb5e7b9f807395de8e015b0dea5efcdb44cb1820551a",
|
|
243
244
|
"schemas/summaries/hook.schema.json": "58420ec485e152fdd21fa3d87337ad74b0d81a48d3b83dd072d4a2d196f78573",
|
package/package.json
CHANGED
package/schemas/link.schema.json
CHANGED
|
@@ -21,9 +21,10 @@
|
|
|
21
21
|
"description": "Nature of the relation. `invokes` = execution-level call (e.g. slash command). `references` = explicit link (e.g. wikilink, @-directive). `mentions` = informal textual mention. `supersedes` = replaces another node (from `metadata.supersedes`)."
|
|
22
22
|
},
|
|
23
23
|
"confidence": {
|
|
24
|
-
"type": "
|
|
25
|
-
"
|
|
26
|
-
"
|
|
24
|
+
"type": "number",
|
|
25
|
+
"minimum": 0,
|
|
26
|
+
"maximum": 1,
|
|
27
|
+
"description": "Extractor's self-assessed confidence `[0..1]`. Drives UI edge opacity (more confident = more opaque). Migrated from the legacy string union `'high' | 'medium' | 'low'` to a numeric range so callers can express finer granularity than three buckets. Reference scoring: `1.0` = structured input (sidecar annotation), `0.95` = unambiguous syntax (`[text](file.md)`), `0.85` = strong signal with one inference (`@file.md`), `0.5` = genuine ambiguity (`@bare-handle`). The named tiers `HIGH = 0.9`, `MEDIUM = 0.6`, `LOW = 0.3` are exposed on the kernel side as the `ConfidenceTier` constants for callers that want to think in buckets. Analyzers MAY filter by confidence threshold."
|
|
27
28
|
},
|
|
28
29
|
"sources": {
|
|
29
30
|
"type": "array",
|
package/schemas/node.schema.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://skill-map.dev/spec/v0/node.schema.json",
|
|
4
4
|
"title": "Node",
|
|
5
|
-
"description": "A single
|
|
5
|
+
"description": "A single entity in the graph. Typically a file on disk (a markdown skill, an agent, a TOML sub-agent definition, a plain-markdown note), but MAY also be a **virtual / derived** entity that lives only in memory and is reconstructed from one or more source files on every scan (e.g. an MCP server node derived from `settings.json` / `mcp.json` / `config.toml`). Virtual nodes carry `virtual: true` and use a synthetic `path` scheme (`mcp://<name>`, etc.). The `kind` is whatever the classifying Provider declares, open by design; the **built-in Claude Provider** emits `skill` / `agent` / `command` / `markdown` today, but external Providers (Cursor, Obsidian, …) MAY emit their own. Format-named kinds (`markdown`, future `toml`, future `json`) are reserved for the generic fallback only, when a file matches a specific role (agent / command / skill) that classification prevails over format naming.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": ["path", "kind", "provider", "bodyHash", "frontmatterHash", "bytes", "linksOutCount", "linksInCount", "externalRefsCount"],
|
|
8
8
|
"additionalProperties": false,
|
|
@@ -66,6 +66,15 @@
|
|
|
66
66
|
"isFavorite": {
|
|
67
67
|
"type": "boolean",
|
|
68
68
|
"description": "Per-node favorite flag set by the local user from the UI. Sourced from `state_node_favorites` (zone `state_`, persistent across scans). Decorated by the BFF on every `/api/nodes` response via in-memory Set lookup against the favorites table, no SQL JOIN against `scan_nodes`. Absent on emissions that don't carry per-user state (e.g. `sm export --json`); consumers that don't recognise the field MUST ignore it."
|
|
69
|
+
},
|
|
70
|
+
"virtual": {
|
|
71
|
+
"type": "boolean",
|
|
72
|
+
"description": "When `true`, this node is synthetic / derived: it does not correspond to a single file on disk. Reconstructed on every scan from the file(s) listed in `derivedFrom`. Use a synthetic `path` scheme (e.g. `mcp://github`) so the identifier is stable and visibly non-filesystem. Examples: MCP server nodes derived from `settings.json` / `mcp.json` / `config.toml`. When absent or `false`, the node is a normal filesystem-backed entity. Stability: experimental."
|
|
73
|
+
},
|
|
74
|
+
"derivedFrom": {
|
|
75
|
+
"type": "array",
|
|
76
|
+
"items": { "type": "string" },
|
|
77
|
+
"description": "Paths of the source files from which this node was derived. Required (and only meaningful) when `virtual: true`. Drives invalidation: if any listed source changes between scans, the virtual node's hashes change and the rename / drift machinery surfaces it. Empty / absent when the node is a regular filesystem-backed entity (the `path` itself is the source)."
|
|
69
78
|
}
|
|
70
79
|
},
|
|
71
80
|
"$defs": {
|
|
@@ -21,9 +21,13 @@
|
|
|
21
21
|
},
|
|
22
22
|
"providers": {
|
|
23
23
|
"type": "array",
|
|
24
|
-
"description": "Provider ids to enable, in priority order when multiple match. Empty/absent = use all registered.",
|
|
24
|
+
"description": "Provider ids to enable, in priority order when multiple match. Empty/absent = use all registered. Note: the `activeProvider` field selects ONE of these as the project's active lens; the rest stay enabled-but-inactive until the user switches.",
|
|
25
25
|
"items": { "type": "string" }
|
|
26
26
|
},
|
|
27
|
+
"activeProvider": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "The active provider lens for this project. Exactly one provider id (from the enabled `providers` list) sees the project at any time. All extractors, classifiers, and resolution rules belonging to other providers are skipped during scan. Changing this triggers an atomic drop of the `scan_*` DB zone followed by a fresh scan under the new lens; `state_*` and `config_*` zones survive the switch. When absent on a fresh project, the kernel auto-detects from filesystem (presence of `.claude/`, `.gemini/`, `.codex/`, AGENTS.md, etc.) and prompts via the CLI / UI if the heuristic is ambiguous. Stability: experimental."
|
|
30
|
+
},
|
|
27
31
|
"roots": {
|
|
28
32
|
"type": "array",
|
|
29
33
|
"description": "Directories (relative to the config file) to scan. Defaults to the scope root.",
|
|
@@ -73,7 +77,18 @@
|
|
|
73
77
|
"additionalProperties": false,
|
|
74
78
|
"properties": {
|
|
75
79
|
"enabled": { "type": "boolean" },
|
|
76
|
-
"config": { "type": "object", "description": "Plugin-specific config passed to extensions at load time. Shape defined by the plugin.", "additionalProperties": true }
|
|
80
|
+
"config": { "type": "object", "description": "Plugin-specific config passed to extensions at load time. Shape defined by the plugin.", "additionalProperties": true },
|
|
81
|
+
"extensions": {
|
|
82
|
+
"type": "object",
|
|
83
|
+
"description": "Per-extension enable/disable overrides within this plugin. Keys are extension ids (the `id` declared by the extractor / analyzer / etc. inside the plugin). Absent = use the extension's manifest default (enabled). Allows a user to keep a provider plugin active but disable an individual extractor whose output is noisy in their project, without disabling the whole bundle.",
|
|
84
|
+
"additionalProperties": {
|
|
85
|
+
"type": "object",
|
|
86
|
+
"additionalProperties": false,
|
|
87
|
+
"properties": {
|
|
88
|
+
"enabled": { "type": "boolean" }
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
77
92
|
}
|
|
78
93
|
}
|
|
79
94
|
},
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://skill-map.dev/spec/v0/signal.schema.json",
|
|
4
|
+
"title": "Signal",
|
|
5
|
+
"description": "Intermediate Representation (IR) emitted by extractors during a scan. A Signal is a *candidate* detection: zero, one, or many interpretations of the same piece of source text or structured data. The kernel's resolver phase consumes `Signal[]` and produces final `Link[]` by selecting a winning candidate per Signal (or rejecting all and emitting none) using the active Provider's resolution rules. Opt-in for plugin authors: an extractor MAY emit `Signal`s via `ctx.emitSignal()` when the detection carries genuine ambiguity (multiple plausible kinds, multiple plausible targets, byte-range awareness for collision detection), OR continue calling `ctx.emitLink()` directly when its detection is unambiguous. The two paths coexist; resolved Link rows look identical regardless of origin. Stability: experimental.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["source", "scope", "candidates"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"source": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "`node.path` of the originating node (the file or virtual entity in which the signal was detected)."
|
|
13
|
+
},
|
|
14
|
+
"scope": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"enum": ["body", "frontmatter", "sidecar"],
|
|
17
|
+
"description": "Where in the source the signal was detected. `body` = markdown body or equivalent prose payload. `frontmatter` = parsed metadata block at the top of the file. `sidecar` = co-located `.sm` overlay. Extractors specialized for non-prose sources (config files, AGENTS.md cascade) emit either `body` (if they treat the whole file as prose) or `frontmatter` (if they read structured fields)."
|
|
18
|
+
},
|
|
19
|
+
"range": {
|
|
20
|
+
"type": ["object", "null"],
|
|
21
|
+
"description": "Byte-range location within the source. Required for `scope: 'body'`, optional otherwise. Powers collision detection between detectors (two extractors emitting Signals with overlapping ranges) and code-block awareness (the orchestrator can mark ranges that fall inside code spans).",
|
|
22
|
+
"required": ["start", "end"],
|
|
23
|
+
"additionalProperties": false,
|
|
24
|
+
"properties": {
|
|
25
|
+
"start": { "type": "integer", "minimum": 0, "description": "Inclusive byte offset of the first character." },
|
|
26
|
+
"end": { "type": "integer", "minimum": 0, "description": "Exclusive byte offset one past the last character." }
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"fieldPath": {
|
|
30
|
+
"type": ["array", "null"],
|
|
31
|
+
"description": "Structured-data location within `frontmatter` or `sidecar` scopes. Each entry is a step of the path: object keys are strings, array indices are integers serialized as strings. Example: `['tools', '0']` points to the first entry of the `tools` array. Null when the signal is body-scoped or when the extractor doesn't track field locations.",
|
|
32
|
+
"items": { "type": "string" }
|
|
33
|
+
},
|
|
34
|
+
"raw": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "Verbatim matched text (for body scope) or stringified value (for frontmatter / sidecar scope). Used for debugging, UI tooltips, and collision-key dedup."
|
|
37
|
+
},
|
|
38
|
+
"context": {
|
|
39
|
+
"type": ["string", "null"],
|
|
40
|
+
"enum": ["code-block", "inline-code", "escaped", null],
|
|
41
|
+
"description": "Provider-determined surface context. `code-block` = inside a fenced code block (most providers ignore these). `inline-code` = inside backticks. `escaped` = preceded by `\\` or otherwise marked literal. Null when the signal is in normal prose or when the context concept doesn't apply (frontmatter / sidecar scopes). Drives both extraction filtering and the resolver's confidence weighting."
|
|
42
|
+
},
|
|
43
|
+
"candidates": {
|
|
44
|
+
"type": "array",
|
|
45
|
+
"minItems": 1,
|
|
46
|
+
"description": "One or more alternative interpretations of the same signal. The resolver picks ONE as the winner (becomes a Link) or rejects all (no Link emitted). Multiple candidates from the same `extractorId` are allowed (e.g. one detector may emit both a `references` and a `mentions` hypothesis for the same `@token` and let the resolver decide).",
|
|
47
|
+
"items": {
|
|
48
|
+
"type": "object",
|
|
49
|
+
"required": ["extractorId", "kind", "target", "confidence"],
|
|
50
|
+
"additionalProperties": false,
|
|
51
|
+
"properties": {
|
|
52
|
+
"extractorId": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"description": "Id of the extractor that contributed this candidate."
|
|
55
|
+
},
|
|
56
|
+
"kind": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"enum": ["invokes", "references", "mentions", "supersedes"],
|
|
59
|
+
"description": "Proposed link kind, matching `link.schema.json#/properties/kind/enum`. Closed enum in v1; provider-specific kinds wait until a concrete need emerges."
|
|
60
|
+
},
|
|
61
|
+
"target": {
|
|
62
|
+
"type": "string",
|
|
63
|
+
"description": "Proposed `node.path` of the destination. MAY refer to a missing node (the resolver does not validate existence); the `broken-ref` analyzer reports the gap downstream."
|
|
64
|
+
},
|
|
65
|
+
"confidence": {
|
|
66
|
+
"type": "number",
|
|
67
|
+
"minimum": 0,
|
|
68
|
+
"maximum": 1,
|
|
69
|
+
"description": "Extractor's self-assessed probability that this interpretation is the correct one. Reference scoring (guideline, not contract): `1.0` = structured input (sidecar annotation, parsed JSON pointer), `0.95` = unambiguous syntax (`[text](file.md)`, `https://...`), `0.85` = strong signal with one degree of inference (`@file.md` with known extension), `0.5` = genuine ambiguity (`@bare-handle` could be agent, file, or generic mention). Drives UI edge opacity downstream."
|
|
70
|
+
},
|
|
71
|
+
"rationale": {
|
|
72
|
+
"type": "string",
|
|
73
|
+
"description": "Optional human-readable explanation of WHY this candidate has the assigned confidence. Surfaced in inspector tooltips and debug output. Keep it short, e.g. `'ends in .md'`, `'no extension, no path prefix'`, `'declared in tools[] array'`."
|
|
74
|
+
},
|
|
75
|
+
"trigger": {
|
|
76
|
+
"type": ["object", "null"],
|
|
77
|
+
"description": "Trigger-style metadata when this candidate represents a textual invocation (`@x`, `/y`). Null otherwise. Mirrors `link.schema.json#/properties/trigger`.",
|
|
78
|
+
"required": ["originalTrigger", "normalizedTrigger"],
|
|
79
|
+
"additionalProperties": false,
|
|
80
|
+
"properties": {
|
|
81
|
+
"originalTrigger": { "type": "string" },
|
|
82
|
+
"normalizedTrigger": { "type": "string" }
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|