@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/CHANGELOG.md
CHANGED
|
@@ -1,173 +1,79 @@
|
|
|
1
1
|
# Spec changelog
|
|
2
2
|
|
|
3
|
-
## 0.
|
|
3
|
+
## 0.41.0
|
|
4
4
|
|
|
5
5
|
### Minor Changes
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
**Spec (`spec/schemas/extensions/provider.schema.json`)**: Providers gain a required `presentation` block (`label`, `color`, optional `colorDark` / `icon` / `emoji` / `hideChip`) describing the Provider's own identity, and an optional `detect.markers` array. Named `presentation` (not `ui`) because the shared extension `ui` key is the view-contributions map. Pre-1.0 breaking change for external Provider plugins: a manifest without `presentation` is rejected at load with a clear `must have required property 'presentation'` diagnostic (locked by the `plugin-missing-ui-rejected` conformance case).
|
|
7
|
+
- Reserved-name detection gains a lens scope: when a Provider is the active lens, its `reservedNames` catalog also applies to the `agent-skills` skill nodes its runtime consumes, matched by kind. This activates Google Antigravity's catalog, refreshed from `agy /help` (v1.0.3) and now declared under `skill`, so a `.agents/skills/<name>` skill shadowing a built-in like `/goal` is flagged by `core/name-reserved` under the antigravity lens. Claude is unchanged.
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
## User-facing
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
Under the Antigravity lens, `sm scan` now warns when a `.agents/skills` skill shadows a built-in `agy` slash command (e.g. a skill named `goal` collides with `/goal`), so you can rename it before the runtime silently ignores the file.
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
- Add opt-in, anonymous error reporting (Sentry) across the CLI, BFF, and UI, OFF by default. Consent lives in `~/.skill-map/settings.json` (`telemetry.errorsEnabled`), surfaced through `GET/PATCH /api/preferences` and a new Settings Privacy toggle; `SKILL_MAP_TELEMETRY=0` force-disables every surface. A pure, deny-by-default scrubber strips home paths and host identity from every event before it leaves the machine. The normative contract is `spec/telemetry.md`.
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
## User-facing
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
skill-map can now report crashes anonymously to help fix bugs, and it is OFF by default. Turn it on or off in Settings, or set `SKILL_MAP_TELEMETRY=0` to force it off. File contents, paths, and your settings are never sent.
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
- Add opt-in, anonymous usage analytics (PostHog) for the CLI and UI, OFF by default. Three independent toggles in `~/.skill-map/settings.json` (`telemetry.usageCliEnabled`, `usageUiEnabled`, alongside `errorsEnabled`); one shared first-run prompt consents to all and mints an anonymous install id used as the PostHog `distinct_id`, exposed read-only via `GET/PATCH /api/preferences`. `SKILL_MAP_TELEMETRY=0` force-disables every surface. Contract: `spec/telemetry.md`.
|
|
22
20
|
|
|
23
21
|
## User-facing
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
## 0.38.0
|
|
28
|
-
|
|
29
|
-
### Minor Changes
|
|
23
|
+
skill-map can now share anonymous usage (which commands and views you use) to guide development, OFF by default. Toggle CLI usage, UI usage, and error reports independently in Settings, or set `SKILL_MAP_TELEMETRY=0` to force all off. Files and paths are never sent.
|
|
30
24
|
|
|
31
|
-
|
|
25
|
+
### Patch Changes
|
|
32
26
|
|
|
33
|
-
|
|
27
|
+
- Sync the plugin author guide and architecture spec to the structure-as-truth manifest model (`annotation` singular, `ui` view map, on-disk Provider kinds, `precondition` filter, deterministic-only hooks); the guide now delegates instead of duplicating. Fix stale field names and the slot count (14) across architecture.md, db-schema.md and the conformance coverage, and fold the architecture diagram into architecture.md, dropping the generated CLI-reference mirror for `sm help --format md`.
|
|
34
28
|
|
|
35
|
-
|
|
29
|
+
## 0.40.0
|
|
36
30
|
|
|
37
|
-
|
|
31
|
+
### Minor Changes
|
|
38
32
|
|
|
39
|
-
|
|
33
|
+
- dc5c115: Migrate the canonical domain from `skill-map.dev` to `skill-map.ai` everywhere: schema `$id` / `$ref` and the `spec/index.json` canonical URL prefix, the bundled plugin schemas and validators, the public site (canonical URLs, Open Graph, Twitter, JSON-LD, the `/demo/` deploy), and the UI's Settings about-link and demo banner. No shape or behavior change; the spec scheme stays `v0`.
|
|
40
34
|
|
|
41
|
-
|
|
35
|
+
## User-facing
|
|
42
36
|
|
|
43
|
-
|
|
37
|
+
The skill-map website and in-app links (Settings, About and the demo banner) now point to **skill-map.ai** (previously skill-map.dev). Spec schema URLs are now `https://skill-map.ai/spec/v0/...`.
|
|
44
38
|
|
|
45
|
-
|
|
39
|
+
- 43eb1e5: Frontmatter coverage pass for Claude and the Agent Skills open standard, plus a breaking revert of dual-source tags to single-source. Claude's `skill-base` gains the `disallowed-tools` denylist; the `agent-skills` Provider declares the open-standard `license` / `compatibility` / `metadata` / `allowed-tools` fields; and `tags` now live only in the `.sm` sidecar, dropping the universal `tags` field, the `scan_node_tags.source` column, and the `sm list --tag-source` flag.
|
|
46
40
|
|
|
47
|
-
-
|
|
48
|
-
- **overLimit** (yellow), `nodesCount > recommendedNodeLimit` with an override above the recommendation. Graph is bigger than recommended, allowed through.
|
|
49
|
-
- **atLimit** (yellow), `nodesCount >= recommendedNodeLimit` without an override above. Soft warning at the recommended cap.
|
|
41
|
+
## User-facing
|
|
50
42
|
|
|
51
|
-
|
|
43
|
+
Claude skills and commands now show their `disallowed-tools` in the inspector. Tags come only from `.sm` sidecars now: the `sm list --tag-source` flag is removed and cards show a single tag style. Agent Skills `license` / `compatibility` / `metadata` fields are recognized.
|
|
52
44
|
|
|
53
|
-
|
|
45
|
+
- e953f9f: Pre-1.0 schema-drift rebuild: `sm scan`, `sm watch`, and the BFF watcher compare `scan_meta.scanned_by_version` against the running CLI and, on any `major.minor` difference, delete and recreate the project DB from `001_initial.sql` instead of failing on the stale schema. The DB is a derived cache (`.sm` sidecars hold the authored data) so no backup is taken; patch differences stay compatible and read verbs keep the version-skew advisory.
|
|
54
46
|
|
|
55
47
|
## User-facing
|
|
56
48
|
|
|
57
|
-
|
|
49
|
+
After updating skill-map, the next `sm scan` rebuilds the local database when it was created by an older version (your `.sm` sidecar files are never touched). On a terminal it asks first; pass `--yes` to skip the prompt.
|
|
58
50
|
|
|
59
|
-
## 0.
|
|
51
|
+
## 0.39.0
|
|
60
52
|
|
|
61
53
|
### Minor Changes
|
|
62
54
|
|
|
63
|
-
-
|
|
64
|
-
|
|
65
|
-
**What changed**
|
|
66
|
-
|
|
67
|
-
- **Manifest schema**: `granularity` is removed from `spec/schemas/plugins-registry.schema.json`. A user plugin manifest that still declares it is rejected as `invalid-manifest` via AJV's `additionalProperties: false`. Built-in `plugin.json` files dropped the field; the generator (`scripts/generate-built-ins.js`) no longer emits it.
|
|
68
|
-
- **Kernel**: `TGranularity` deleted from `src/kernel/types/plugin.ts`; `IDiscoveredPlugin.granularity` and `IPluginManifest.granularity` gone. The runtime resolver (`src/core/runtime/plugin-runtime/resolver.ts`) keys every gate on the qualified extension id.
|
|
69
|
-
- **CLI** (`src/cli/commands/plugins/`): the bare bundle id (`sm plugins disable claude`) is now a **macro** that fans the toggle out across every extension inside the bundle. Single-extension bundles (`openai`, `antigravity`, `agent-skills`) apply without prompting. Multi-extension bundles (`claude`, `core`, multi-extension user plugins) require `--yes` OR an interactive TTY confirm; non-TTY contexts must pass `--yes` or the verb refuses with a directed message and the list of affected extensions. `--all` cascades through every bundle's extensions under the same gate. Qualified-id form (`sm plugins disable claude/at-directive`) toggles exactly that extension with no prompt. The `--yes` / `-y` flag is added to `enable` and `disable`. Granularity-mismatch error messages (`'claude' has granularity=bundle`, `'core' has granularity=extension`) are removed; the new error path is "unknown id" / "macro requires confirmation". `sm plugins doctor` summary reverts to `N enabled extensions · …` and counts every extension independently (built-ins and loaded user plugins alike).
|
|
70
|
-
- **BFF** (`src/server/routes/plugins.ts`): `PATCH /api/plugins/:id` becomes the **cascade endpoint** that persists one `config_plugins` row per child extension. Granularity-mismatch rejections are gone. The qualified-id sibling (`PATCH /api/plugins/:bundleId/extensions/:extensionId`) is unchanged and remains the canonical per-extension surface. `PATCH /api/plugins` (bulk) accepts bare bundle ids and qualified ids in the same batch; bare entries cascade at write time. The `granularity` field is removed from `IPluginListItem` on the wire; the bundle row's `status` aggregates child enablement (`enabled` when ≥1 extension is enabled).
|
|
71
|
-
- **SPA** (`ui/src/app/components/settings-modal/`): the bundle-level `<p-toggleswitch>` is removed (`canToggleBundle()` and `onBundleToggle()` deleted); bundle rows render as labelled headers with their per-extension list underneath. The "kind filter" chip now narrows the extensions array universally (it used to leave bundle-granularity bundles unfiltered, leaking extractors / analyzers when the user clicked "provider"). `IPluginItemApi.granularity` and `TPluginGranularityApi` removed from `ui/src/models/api.ts`.
|
|
72
|
-
- **Spec prose**: `spec/architecture.md` §Plugin loader, `spec/cli-contract.md` §Endpoints + §Error code sources, `spec/plugin-author-guide.md` §Toggle model (replacing the old §Granularity), `spec/db-schema.md` §scan_contributions are all rewritten to reflect the per-extension toggle model and the macro form on bare ids.
|
|
55
|
+
- f2b59c5: Makes the registered Provider set the single source of truth for the UI's provider surfaces (active-lens dropdown, topbar lens chip, per-node provider chip) and for active-lens auto-detection. Removes four divergent hardcoded provider lists that no longer matched the real built-in Providers (the lens dropdown offered phantom `gemini` / `cursor` entries and hid the real `antigravity` / `agent-skills`; the card chip did not know `openai` / `antigravity`; the detection table still listed `cursor`).
|
|
73
56
|
|
|
74
|
-
|
|
57
|
+
## 0.38.0
|
|
75
58
|
|
|
76
|
-
|
|
77
|
-
- CLI plugins spec rewrites the granularity describe block as bundle-macro semantics (`--yes` required for multi-extension bundles, single-extension bundles apply directly, qualified-id form flips exactly that extension).
|
|
78
|
-
- SPA settings-plugins spec updates the helper to call `onExtensionToggle` (the bundle has no toggle method anymore) and asserts the kind filter narrows extensions.
|
|
79
|
-
- Conformance fixture (`spec/conformance/fixtures/plugin-missing-ui/.skill-map/plugins/bad-provider/plugin.json`) drops the field.
|
|
59
|
+
### Minor Changes
|
|
80
60
|
|
|
81
|
-
|
|
61
|
+
- d3c47b2: Adds a hard cap on the number of files `sm scan` and `sm watch` accept after `.skillmapignore` filtering, plus a persistent UI banner that fires when the graph crosses the recommended limit. Default cap is **256 nodes**. Override per invocation with `--max-nodes <N>` (bidirectional: raises OR lowers the cap).
|
|
82
62
|
|
|
83
|
-
|
|
63
|
+
## 0.37.0
|
|
84
64
|
|
|
85
|
-
|
|
65
|
+
### Minor Changes
|
|
86
66
|
|
|
87
|
-
|
|
67
|
+
- d852217: Eliminate the bundle-level toggle entirely. Every plugin extension is now independently toggle-able by its qualified `<bundle>/<ext>` id; the bundle itself is a presentational grouping only.
|
|
88
68
|
|
|
89
69
|
### Patch Changes
|
|
90
70
|
|
|
91
71
|
- f66dbfe: Decouple built-in extensions from per-extension semver. Built-ins ship inside the CLI bundle, so authors no longer declare a `version` literal in each `<plugin>/<kind>s/<name>/index.ts` manifest under `src/plugins/`. The codegen at `scripts/generate-built-ins.js` now reads the CLI version from `src/package.json` and stamps it onto every built-in (alongside the existing `pluginId` stamp) when emitting `src/plugins/built-ins.ts`. The resulting runtime objects still satisfy the full kind interface (`IAnalyzer`, `IExtractor`, ...) and every downstream consumer continues to see `ext.version: string`, so `state_executions.extension_version` keeps recording a meaningful value (= CLI version) for reproducibility.
|
|
92
72
|
|
|
93
|
-
New kernel type `IBuiltInManifest<T extends IExtensionBase> = Omit<T, 'version'>` exported from `kernel/extensions/index.js`. Built-in manifest authors type their export as `IBuiltInManifest<I<Kind>>` and omit `version`; the codegen does the rest. The 34 built-in extensions across the 5 first-party bundles (`core`, `claude`, `antigravity`, `openai`, `agent-skills`) were migrated in-tree, removing 32 cargo-cult `'1.0.0'` literals and the 2 `'0.0.0'` "stub" sentinels.
|
|
94
|
-
|
|
95
|
-
External (user-authored) plugins are unaffected: the AJV check at load time still requires `version` on every extension manifest per `spec/schemas/extensions/base.schema.json#/required`. The schema's `required` list is unchanged; only the `version` field description was updated to document the built-in / external asymmetry.
|
|
96
|
-
|
|
97
|
-
Two kernel API signatures widen from `IProvider` to `IBuiltInManifest<IProvider>` so test files that import raw built-in manifests directly (bypassing the codegen) keep type-checking without a runtime workaround. The widened functions are `resolveProviderWalk` and `buildProviderFrontmatterValidator` (plus the `IProviderFrontmatterValidator.validate` method shape); both only read `id` / `kinds` / `walk` / `read` / `schemas` and never touch `version`, so the widening is structurally safe and production callers passing fully-loaded `IProvider` values continue to type-check (subtype-passes-supertype).
|
|
98
|
-
|
|
99
|
-
The AGENTS.md "stub extensions ship as `version: '0.0.0'`" convention is retired (the chip it surfaced was hidden from the UI / CLI in the previous release). If we later want a visible placeholder signal we'll add a dedicated `stability: 'stub'` field instead of overloading `version`.
|
|
100
|
-
|
|
101
73
|
- 457a60d: Reserve the `graph.node.alert` slot for special-case signals; disconnect every built-in core analyzer from it. Define the **chip-vs-issue policy** for plugin authors and align `reference-broken` to it. The corner badge on the NE tip of each graph card is no longer a generic "this node has a problem" surface. Routine findings (`reference-broken`, `annotation-field-unknown`, `schema-violation`) now ship only as `card.footer.right` chips, the slot's natural home for paired-icon-and-count signals.
|
|
102
74
|
|
|
103
|
-
**What changed**
|
|
104
|
-
|
|
105
|
-
- **Analyzer manifests + emit**: `reference-broken`, `annotation-field-unknown`, and `schema-violation` (under `src/plugins/core/analyzers/`) dropped their `alert` ui declaration and the matching `ctx.emitContribution(nodePath, 'alert', ...)` call. Each analyzer keeps its `card.footer.right` chip with the same tooltip + severity + count semantics. `schema-violation`'s severity-from-worst-finding logic stays (introduced in this same change set), now applied to the chip exclusively (`warn` for missing base fields, `danger` as soon as one schema check returns error).
|
|
106
|
-
- **Icon swaps**: `schema-violation` chip moved from `fa-solid fa-triangle-exclamation` to `fa-solid fa-circle-exclamation`. `reference-broken` chip moved from `fa-regular fa-circle-xmark` to `fa-solid fa-circle-xmark` (the outlined regular variant existed in FA Free for `circle-xmark` but the chip lost its alert sibling that motivated the visual contrast). Both choices documented inline next to the manifest entry: in FA Free `circle-exclamation` ships only in `solid` (`icons.yml`, `styles: [solid]`), so a `fa-regular` declaration would render as a missing-glyph tofu.
|
|
107
|
-
- **Slot kept in the catalog**: `graph.node.alert` stays in `view-slots.schema.json`, `kernel/types/view-catalog.ts`, `ui/src/app/slots/slot-config.ts`, and the renderer map. The mount in `graph-view.html` and the `NodeAlert` renderer are untouched. The slot is now reserved for genuinely independent signals (a future plugin that wants a corner decoration tied to a one-off condition); the slot-config comment documents the bar.
|
|
108
|
-
- **`sm plugins slots list` summary**: the `graph.node.alert` row in `src/cli/commands/plugins/slots-catalog.ts` now reads "Reserved corner badge ... special-case signals only" so plugin authors browsing the catalog see the policy without digging.
|
|
109
|
-
- **Drive-by, `sm init` warning formatting**: `activeProviderNoMarkerWarning` (under `src/core/runtime/i18n/scan-runner.texts.ts`) used to glue itself onto the next stderr line because the catalog string had no trailing newline and the message ran as a single sentence wall. Refactored to the §3.1b "glyph + dim hint" two-line block (mirrors the drift warn next door): yellow `⚠` headline + dim hint indented at column 3. `active-provider-bootstrap.ts` threads `opts.style.warnGlyph` + `opts.style.dim` through `tx(...)` like the drift path already did.
|
|
110
|
-
|
|
111
|
-
**Tests**
|
|
112
|
-
|
|
113
|
-
- Each affected analyzer's spec asserts `chip only, no alert` and lists the surviving slot via `deepStrictEqual(analyzer.ui, { chip: { slot: 'card.footer.right', ... } })`. Locks the new shape and fails fast if a future refactor re-wires the corner.
|
|
114
|
-
- `schema-violation.spec.ts` adds an "escalates severity to danger as soon as one finding is error-level" case that asserts the chip's severity follows the worst underlying finding.
|
|
115
|
-
- The existing `active-provider-bootstrap.spec.ts` test that regex-matched `/no provider markers detected/i` still passes against the new two-line block (the substring is preserved).
|
|
116
|
-
- `e2e/live-bff/` gains a `graph-node-alert.spec.ts` regression: with a fixture node carrying a broken `@mention` (would have triggered the `reference-broken` corner badge under the prior contract), the SPA must render zero `[data-testid="renderer-node-alert"]` elements while still surfacing the footer chip. The fixture (`e2e/live-bff/fixture.ts`) was extended with the broken-ref body line; the bump happy-path spec is unaffected (stale-badge state + version increment do not depend on link findings).
|
|
117
|
-
|
|
118
|
-
**`reference-broken` Issue severity raised from `warn` to `error`**
|
|
119
|
-
|
|
120
|
-
Per the chip-vs-issue policy below, a `danger` chip MUST be backed by an `error` Issue for the same node. `reference-broken` was emitting chip `danger` (red) + Issue `warn`, the only mismatch in the built-in catalog. Bumping the Issue aligns the visual signal with the exit code: any unresolved `@` / `/` link or markdown reference now escalates `sm scan` to exit 1 by default (was exit 0 with a yellow finding). CI pipelines that ran `sm scan` and treated exit 0 as "clean" will now see broken-ref runs fail, the operator was already seeing the red chip on the card; the change makes the exit code match.
|
|
121
|
-
|
|
122
|
-
`scan-readers.spec.ts` gains a `plantWarnOnlyFixture` helper (stale-sidecar based) for the "no error-severity → exit 0" contract tests that previously relied on `reference-broken` being a warn-level finding.
|
|
123
|
-
|
|
124
|
-
**Chip-vs-issue policy (new doc)**
|
|
125
|
-
|
|
126
|
-
Two new sections, one in `context/view-slots.md` ("Chip vs Issue, what counts and what only shows") and a shorter mirror in `spec/plugin-author-guide.md`, articulate the two-channel model:
|
|
127
|
-
|
|
128
|
-
- An `Issue` returned by `evaluate(ctx)` feeds the card's aggregated stats AND the scan / check exit code.
|
|
129
|
-
- A view contribution to `card.footer.right` is purely presentational, its `severity` controls only the chip's own colour.
|
|
130
|
-
|
|
131
|
-
The two channels are independent. The doc lists the 4 combinations (issue × chip) and codifies the colour rule: a chip MAY paint `warn` (yellow) or `danger` (red) only when the same analyzer emits a matching Issue at the same level. Decorative chips use `info`, `success`, or omit the severity field (neutral). Compliance audited across the built-in catalog: every analyzer now follows the rule.
|
|
132
|
-
|
|
133
|
-
**Drive-by, view-slots annex**
|
|
134
|
-
|
|
135
|
-
`context/view-slots.md` table row for `graph.node.alert` now flags the slot as Reserved with a pointer to the policy comment in `slot-config.ts`. The new chip-vs-issue section sits next to it as the cross-channel policy for the rest of the card surface.
|
|
136
|
-
|
|
137
|
-
`spec/index.json` regenerated for the prose addition (no schema changes, just the guide).
|
|
138
|
-
|
|
139
|
-
## User-facing
|
|
140
|
-
|
|
141
|
-
Graph cards drop the corner badge for routine warnings; count + tooltip stay on the footer chip. Broken refs now escalate `sm scan` to exit 1 (were exit 0). `sm init` prints the "no provider markers" advisory as a two-line yellow `⚠` block.
|
|
142
|
-
|
|
143
75
|
- d66bc71: Three findings from a second `sm-tutorial` external-tester session (Adolfo, 2026-05-25).
|
|
144
76
|
|
|
145
|
-
**Finding 1, `sm check --analyzers` silently accepts unknown ids** (`src/cli/commands/check.ts`)
|
|
146
|
-
|
|
147
|
-
`parseAnalyzersFlag` trimmed tokens and dropped empties, then `matchesAnalyzerFilter` compared them against the persisted `analyzerId` set. A typo like `broken-ref` (the real id is `core/reference-broken`, short form `reference-broken`) matched nothing, the verb returned `✓ No issues.` in green with exit 0, identical to a clean run; the planted broken-reference warning was invisible. The tutorial copy itself used `broken-ref` in the example commands, so following the walkthrough verbatim hid the fixture.
|
|
148
|
-
|
|
149
|
-
Fix: load the live Analyzer catalog when `--analyzers` is set, validate every token against both qualified (`core/reference-broken`) and short (`reference-broken`) forms, and on the first unknown id exit `ExitCode.Error` (2) with a stderr message naming the unknown id(s) and listing every valid qualified id. The catalog load is shared with the existing `--include-prob` path so the verb still pays for the runtime exactly once when both flags are present. Tutorial `.claude/skills/sm-tutorial/SKILL.md` updated to use the real ids (`reference-broken`, `core/name-reserved`, `core/link-self-loop`, `core/reference-redundant`).
|
|
150
|
-
|
|
151
|
-
**Finding 2, trigger-style links from universal-provider bodies never resolved** (`src/kernel/orchestrator/lift-resolved-link-confidence.ts`, `spec/architecture.md`)
|
|
152
|
-
|
|
153
|
-
The extractor gate already keys on the **active provider lens** (§Universal extractors and per-provider extractors): `claude/slash-command` under the `claude` lens emits `/handle` links from every node, including `notes/todo.md` classified by `core/markdown`. But the post-walk confidence-lift transform keyed on the **source node's provider id** (`markdown`), which declares no `resolution` map; the lookup short-circuited, the link stayed at `confidence: 0.8`, and `link.resolvedTarget` never got populated. Effect: even after the prior denormalised-`linksInCount` fix (be116dd) read `resolvedTarget ?? target`, markdown-sourced trigger links still incremented `linksInCount` against the authored trigger string (`/demo-command`) instead of the resolved node, and `sm list` IN stayed at 0 for the resolved command / skill node. The UI drew the arrow correctly (it walks `scan_links` directly), so the inconsistency surfaced as "arrow lands but IN=0".
|
|
154
|
-
|
|
155
|
-
Fix: align resolver authority with extractor authority by keying the `resolution` lookup on `ctx.activeProvider` instead of `sourceNode.provider`. `IPostWalkTransformCtx` gains a new `activeProvider: string | null` field; `buildPostWalkTransformCtx` in the orchestrator threads the lens through from `RunScanOptions.activeProvider`. `spec/architecture.md` §Provider · resolution rules updated to match (the prior wording was internally inconsistent with §Universal extractors and per-provider extractors, which already established the lens-driven principle). Existing test that asserted the old behaviour inverted to assert the new contract; a regression test for the exact sm-tutorial fixture (`/demo-command` from `notes/todo.md` under `claude` lens) and a complementary unlensed-project case (`activeProvider === null` short-circuits the name path) added.
|
|
156
|
-
|
|
157
|
-
**Finding 3, `sm plugins doctor` summary count looked off-by-N against `sm plugins list`** (`src/cli/commands/plugins/doctor.ts`)
|
|
158
|
-
|
|
159
|
-
Doctor's `enabled` count adds bundle-granularity bundles (count once) + extension-granularity extensions (count per extension). With a fresh install that totals 4 + 27 = 31. `sm plugins list` lists every individual extension under each bundle, so its surface count is 33 (3 claude + 1 antigravity + 1 openai + 1 agent-skills + 27 core). The two numbers were correct but unexplained; the tester read the doctor header `31 enabled` and the list count `33` and assumed a bug.
|
|
160
|
-
|
|
161
|
-
Fix: extend the doctor summary line to spell out the math: `plugins doctor: 31 enabled (4 bundles + 27 extensions) · 0 issues · 0 warnings`. New `countEnabledByGranularity` helper walks the same shape as `countByStatus` but tracks bundles and extensions separately so the breakdown reflects the project's actual granularity mix.
|
|
162
|
-
|
|
163
|
-
**Drive-by: tutorial wrap-up safer cleanup** (`.claude/skills/sm-tutorial/SKILL.md`)
|
|
164
|
-
|
|
165
|
-
The wrap-up advised `cd ~ && rm -rf <cwd>` with a single "if the cwd was a dedicated dir" caveat. The tester ran the tutorial in their day-to-day work dir; the bulk command would have nuked unrelated files. Wrap-up now branches on whether the cwd looks dedicated and surfaces the explicit per-file list (same shape as the "start over" branch already uses) when it does not.
|
|
166
|
-
|
|
167
|
-
## User-facing
|
|
168
|
-
|
|
169
|
-
`sm check --analyzers <id>` now errors with the valid id list when mistyped, instead of silently saying "No issues." `/invoke` and `@mention` links from any markdown body now contribute to the target's `IN`. `sm plugins doctor` summary spells out its bundle + extension split.
|
|
170
|
-
|
|
171
77
|
## 0.36.0
|
|
172
78
|
|
|
173
79
|
### Minor Changes
|
|
@@ -179,253 +85,41 @@
|
|
|
179
85
|
emits a warn issue plus the same `alert` + `chip` view contributions
|
|
180
86
|
on `graph.node.alert` / `card.footer.right`.
|
|
181
87
|
|
|
182
|
-
`contribution-orphan` is intentionally NOT renamed: the `contribution`
|
|
183
|
-
namespace refers to view-slot rows in `scan_contributions` (runtime
|
|
184
|
-
data the analyzers emit for the UI), not to annotation fields in
|
|
185
|
-
sidecars. The two namespaces are distinct.
|
|
186
|
-
|
|
187
|
-
Pre-1.0 minor per `spec/versioning.md`: breaking rename of a public
|
|
188
|
-
qualified id referenced from `settings.json`, `--analyzers <id>` flags,
|
|
189
|
-
and the `analyzerId` filter on `GET /api/issues`. No behavioural
|
|
190
|
-
change, no DB schema change, no event payload shape change. Persisted
|
|
191
|
-
scans created with the old id regenerate cleanly on the next
|
|
192
|
-
`sm scan`.
|
|
193
|
-
|
|
194
|
-
## User-facing
|
|
195
|
-
|
|
196
|
-
Renamed `core/field-unknown` to `core/annotation-field-unknown` so the
|
|
197
|
-
sidecar typo-guard rule groups with the other `core/annotation-*`
|
|
198
|
-
rules. Update references in `settings.json` or
|
|
199
|
-
`sm check --analyzers <id>` to the new name.
|
|
200
|
-
|
|
201
88
|
- 880fe3e: Rename 14 built-in extension ids to a consistent `<domain>-<detail>` pattern. The naming was inconsistent: 10 ids already followed the "area first, attribute after" shape (e.g. `annotation-orphan`, `link-conflict`) while 14 were inverted, redundant, or vague. All built-ins now agree.
|
|
202
89
|
|
|
203
|
-
Full rename map (`old qualified id` → `new qualified id`):
|
|
204
|
-
|
|
205
|
-
| Kind | Old | New |
|
|
206
|
-
| --------- | --------------------------------- | -------------------------- |
|
|
207
|
-
| action | `core/bump` | `core/node-bump` |
|
|
208
|
-
| action | `core/mark-superseded` | `core/node-supersede` |
|
|
209
|
-
| extractor | `core/tools-count` | `core/tools-counter` |
|
|
210
|
-
| extractor | `claude/slash` | `claude/slash-command` |
|
|
211
|
-
| analyzer | `core/broken-ref` | `core/reference-broken` |
|
|
212
|
-
| analyzer | `core/job-orphan-file` | `core/job-file-orphan` |
|
|
213
|
-
| analyzer | `core/link-counts` | `core/link-counter` |
|
|
214
|
-
| analyzer | `core/redundant-target-reference` | `core/reference-redundant` |
|
|
215
|
-
| analyzer | `core/reserved-name` | `core/name-reserved` |
|
|
216
|
-
| analyzer | `core/self-loop` | `core/link-self-loop` |
|
|
217
|
-
| analyzer | `core/stability` | `core/node-stability` |
|
|
218
|
-
| analyzer | `core/superseded` | `core/node-superseded` |
|
|
219
|
-
| analyzer | `core/unknown-field` | `core/field-unknown` |
|
|
220
|
-
| analyzer | `core/validate-all` | `core/schema-violation` |
|
|
221
|
-
|
|
222
|
-
The convention is now documented in `spec/plugin-author-guide.md` §Extension id shape. Counter-style extensions standardise on the `-counter` suffix (`link-counter`, `tools-counter`, `external-url-counter`).
|
|
223
|
-
|
|
224
|
-
CLI verb `sm bump` is **unchanged** (it remains the user-facing verb; the internal action id is what flipped to `core/node-bump`). The `BumpReport` JSON schema title also stays as `BumpReport`, the wire shape is unchanged.
|
|
225
|
-
|
|
226
|
-
Pre-1.0 minor per `spec/versioning.md`: breaking rename of public qualified ids referenced from `settings.json`, `--analyzers <id>` flags, `core/<id>` strings in plugin manifests, and the `analyzerId` filter on `GET /api/issues`. No behavioural change, no DB schema change, no event payload shape change. Persisted scans created with the old ids regenerate cleanly on the next `sm scan`.
|
|
227
|
-
|
|
228
|
-
## User-facing
|
|
229
|
-
|
|
230
|
-
Renamed 14 built-in extension ids to a `<area>-<detail>` shape (e.g. `core/broken-ref` is now `core/reference-broken`). If you reference these by qualified id in `settings.json` or via `sm check --analyzers <id>`, update to the new names.
|
|
231
|
-
|
|
232
90
|
- 1b6e368: Honour per-extension toggles inside bundle-granularity plugins end-to-end. Closes the Phase 4b follow-up (commit `e45d2fd`) gap: BFF + Settings UI started accepting per-extension toggles for any granularity, but three call sites still treated bundle granularity as "one knob, every extension follows", so flipping an individual extension off (e.g. `claude/at-directive`) persisted to `config_plugins` and then did nothing on the next scan.
|
|
233
91
|
|
|
234
|
-
**Runtime (`src/`)**
|
|
235
|
-
|
|
236
|
-
- `core/runtime/plugin-runtime/resolver.ts`: `isBundleEntryEnabled` (built-ins) and `isPluginExtensionEnabled` (drop-ins) now compose the bundle id as a coarse kill-switch with the per-extension override layered on top. When the bundle row resolves to `false` every extension stays disabled regardless of per-extension overrides; when the bundle row resolves to `true` each extension respects its qualified-id override (default `true`). Doc rewritten, the stale "silently ignored, the granularity says this bundle is one knob" wording was the symptom that pointed at the bug.
|
|
237
|
-
- `cli/commands/plugins/shared.ts`: `extensionRowFromBuiltIn` (the row builder behind `sm plugins list / show / doctor`) now reports the same composed effective state instead of mirroring the bundle's `enabled` field verbatim.
|
|
238
|
-
- `core/runtime/__tests__/plugin-runtime-branches.spec.ts`: two new composer cases lock the contract, `(e)` `claude` enabled + `claude/at-directive=false` drops only the extractor, and `(f)` `claude=false` overrides any per-extension `true` override.
|
|
239
|
-
|
|
240
|
-
**UI (`ui/`)**
|
|
241
|
-
|
|
242
|
-
- `app/components/settings-modal/settings-plugins.utils.ts`: `buildStateFromPlugins` now seeds extension keys for both granularities (was: bundle-only seeded the bundle id and skipped the extensions, so the per-extension toggles in the Phase 4b modal defaulted to OFF in the buffer regardless of what the wire shape said about `ext.enabled`, then reverted to OFF on every apply round-trip).
|
|
243
|
-
- `models/api.ts`: `IPluginItemApi.extensions` doc updated, the comment still said "only when granularity === 'extension'" which the BFF stopped honouring in commit `e45d2fd`.
|
|
244
|
-
- `__tests__/settings-plugins.utils.spec.ts`: five new cases cover bundle+extensions, extension-only, bundle-disabled-with-ext-enabled, and failure-row exclusion.
|
|
245
|
-
|
|
246
|
-
**Spec (`spec/`)**
|
|
247
|
-
|
|
248
|
-
- `cli-contract.md`: `GET /api/plugins` row shape doc rewritten, `extensions[]` is emitted for any granularity; the per-extension `enabled` reflects the **preference** axis (DB > settings > default true) and the runtime composition with the bundle row is documented explicitly. `PATCH /api/plugins/:bundleId/extensions/:extensionId` now accepts any granularity and returns 404 (not 400) on an unknown extension id. The 400 `bad-query` enumeration in the error-codes section narrowed to the conditions that still apply.
|
|
249
|
-
- `plugin-author-guide.md` § Resolution order: rewritten to describe bundle-as-kill-switch + per-extension refinement explicitly, including the deliberate asymmetry between the CLI surface (`sm plugins enable/disable <bare-id>` stays coarse) and the UI / direct config-edit surface (qualified ids accepted, refine inside a bundle).
|
|
250
|
-
- `index.json` regenerated.
|
|
251
|
-
|
|
252
|
-
## User-facing
|
|
253
|
-
|
|
254
|
-
In Settings, expanding a bundle plugin (claude, antigravity, openai, agent-skills) now shows the correct per-extension state and the toggles persist, the next scan honours them. `sm plugins list` reflects effective state too.
|
|
255
|
-
|
|
256
92
|
## 0.35.0
|
|
257
93
|
|
|
258
94
|
### Minor Changes
|
|
259
95
|
|
|
260
96
|
- de68f09: Soft-warn drift detection for the active provider lens. When `activeProvider` is set (whether by auto-detect on first scan, the interactive prompt for ambiguous markers, or `sm config set activeProvider <id>`), the runtime now persists the set of provider markers that existed on disk at the moment of the choice as `activeProviderMarkers` in `.skill-map/settings.json`. On every subsequent scan the bootstrap re-detects markers and diffs against this snapshot; when the diff is non-empty (new markers appeared, recorded markers disappeared), it emits ONE soft warn before the scan and continues with the cached lens.
|
|
261
97
|
|
|
262
|
-
**Motivation.** Today `activeProvider` wins silently forever, even when the project grows a new provider directory (e.g. adds `.codex/` after the choice was made under `claude`) or loses one (`.cursor/` deleted in a cleanup). The operator should at least notice. The friction of a soft warn is right: it surfaces the drift, points at the fix (`sm config set activeProvider <id>`), and gets out of the way. The warn is informational and never blocks the scan.
|
|
263
|
-
|
|
264
|
-
**Spec.** `spec/schemas/project-config.schema.json` declares the new optional `activeProviderMarkers` string array as internal-state, NOT normally hand-edited. `spec/architecture.md` §"Active-lens drift detection" documents the snapshot + diff + soft-warn contract.
|
|
265
|
-
|
|
266
|
-
**Backfill.** Legacy projects (existing `activeProvider` without a snapshot) lazily backfill on the next scan: the runtime writes the current detected set as the snapshot and stays silent (there is nothing to compare against the first time), so the warn only fires when markers actually drift relative to a known-good snapshot.
|
|
267
|
-
|
|
268
|
-
**Atomicity.** The two writes (`activeProvider` + `activeProviderMarkers`) go through the same `writeConfigValue` helper as every other config mutation; each is atomic on its own, the pair is not transactional. A failure between the two writes leaves the file in the legacy "lens but no snapshot" shape, which the lazy backfill handles cleanly on the next scan.
|
|
269
|
-
|
|
270
|
-
**Tests.** `src/core/runtime/__tests__/active-provider-bootstrap-drift.spec.ts` covers snapshot persistence on auto-detect (single + ambiguous picks), drift detection from config (no-drift, added marker, removed marker, both-direction drift), legacy backfill, snapshot stickiness on repeat drift, and the `style.warnGlyph` / `style.dim` plumbing. `src/cli/commands/__tests__/config-cli.spec.ts` adds two cases for `sm config set activeProvider`: snapshot refresh on set, and full-set capture (not just the picked id).
|
|
271
|
-
|
|
272
|
-
Pre-1.0 minor per `spec/versioning.md`: additive optional field on the project-config schema (`@skill-map/spec`) plus an additive runtime behaviour on `@skill-map/cli`. No removed surface.
|
|
273
|
-
|
|
274
|
-
## User-facing
|
|
275
|
-
|
|
276
|
-
`sm scan` now warns once when provider markers on disk drifted since `activeProvider` was set (e.g. you added `.codex/` after picking the `claude` lens). Run `sm config set activeProvider <id>` to switch the lens, or ignore the warn and keep going, it never blocks the scan.
|
|
277
|
-
|
|
278
98
|
- a58989f: Lens-gated classification for vendor providers. Vendor Providers (`claude`, `openai`, `antigravity`) now opt into being gated by the active lens via a new `gatedByActiveLens: true` field on their manifest. The walker (`src/kernel/orchestrator/walk.ts`) pre-filters `opts.providers` before the walk loop: a gated Provider runs only when `provider.id === opts.activeProvider`, so vendor providers no longer attempt to classify files outside their lens. Universal providers (`core/markdown`, future `agent-skills` open standard) leave the flag absent / `false` and run unconditionally.
|
|
279
99
|
|
|
280
|
-
**Motivation.** The real runtimes never cross-read each other's on-disk formats: Claude Code does not consume `.codex/`, Codex CLI does not consume `.claude/`, Antigravity has no on-disk kind beyond the open standard yet. Offering every file to every provider during classification fabricated cross-vendor graph edges the runtimes themselves reject, the operator saw `openai/agent` nodes for `.codex/agents/*.toml` in a `claude`-lensed project even though Claude Code would never resolve them. The pre-filter in the walker is the cheap path: a gated-off Provider does NOT walk its territory at all, no per-file cost.
|
|
281
|
-
|
|
282
|
-
**Spec.** `spec/schemas/extensions/provider.schema.json` mirrors the new optional boolean field with the full normative description (vendor MUST opt in, universal SHOULD omit, `null` lens bypasses the gate). The matching prose lives in `spec/architecture.md` §"Active-lens scope for providers (classification gate)" (landing alongside drift-detection in a follow-up commit; the changeset for that commit owns the architecture.md prose bump).
|
|
283
|
-
|
|
284
|
-
**`null` lens semantics.** When `activeProvider === null` (a project with no provider markers, no setting), the walker bypasses the gate entirely and every Provider runs. This matches the extractor-side fallback for unlensed projects: a plain-markdown repo keeps classifying with every Provider, no gates fire.
|
|
285
|
-
|
|
286
|
-
**Backward compatibility.** Providers without the field default to `gatedByActiveLens === undefined ≡ false`, the universal behaviour. Existing third-party providers keep working unchanged; only providers that explicitly opt in change classification semantics.
|
|
287
|
-
|
|
288
|
-
**Tests.** `src/kernel/orchestrator/__tests__/walk-lens-gate.spec.ts` covers the walker filter at the unit level (3 cases: claude lens excludes openai territory, openai lens excludes claude territory, `null` lens admits both). `src/__tests__/integration/lens-gated-classification.spec.ts` covers the end-to-end shape across a 4-file fixture per lens (2 cases).
|
|
289
|
-
|
|
290
|
-
Pre-1.0 minor per `spec/versioning.md`: additive optional field on the Provider manifest schema (`@skill-map/spec`) plus an additive walker behaviour change on `@skill-map/cli`. No removed surface, no breaking change for universal providers.
|
|
291
|
-
|
|
292
|
-
## User-facing
|
|
293
|
-
|
|
294
|
-
Cross-provider files (e.g. a `.codex/agents/*.toml` while the lens is `claude`) are no longer claimed by the foreign provider. They surface as plain markdown / unclassified instead, matching how the agent itself would see them at runtime.
|
|
295
|
-
|
|
296
100
|
- d207cfa: Observable link analysis. The link-matrix walkthrough surfaced a recurring complaint, "the inspector tells me there is an edge but not where, why, or whether it overlaps with another", and a small cluster of detection bugs that were hiding real problems and inventing fake ones. This changeset is the drain pass.
|
|
297
101
|
|
|
298
|
-
**Kernel domain shape, additive.** Three new fields on `Link` / `Node`:
|
|
299
|
-
|
|
300
|
-
- `Link.occurrences[]` (`LinkOccurrence` = `{ extractor, originalTrigger, location? }`) accumulates every syntactic site in the source body that contributed to an edge. Populated by extractors at emit time, concatenated by `dedupeLinks` across extractor merges (with `(extractor, originalTrigger, line)` dedup inside the array to defend against double-emit). Frontmatter / sidecar-derived synthetic links carry it empty.
|
|
301
|
-
- `Link.resolvedTarget` is the node path the post-walk `liftResolvedLinkConfidence` transform bound the link to. Equal to `target` for path-style links; differs for trigger-style links (`@foo`, `/cmd`) where `target` keeps the authored trigger and `resolvedTarget` carries the resolved node path. `null` when unresolved (broken).
|
|
302
|
-
- `Node.externalRefs[]` (`IExternalRef` = `{ url, line?, originalTrigger? }`) is the list of distinct http(s) URLs the body references, in extractor-order, deduped by normalised URL. Populated by `recomputeExternalRefsCount` (renamed in role from "count-only" to "count + list"); the denormalised `externalRefsCount` rides alongside and must equal the array length when both are present.
|
|
303
|
-
|
|
304
|
-
All three exported from `src/kernel/index.ts`; matching JSON-Schema additions in `spec/schemas/link.schema.json` and `spec/schemas/node.schema.json` (additive, `additionalProperties: false` preserved); `spec/index.json` regenerated.
|
|
305
|
-
|
|
306
|
-
**SQL, edited in place (greenfield rule).** `src/migrations/001_initial.sql` gains three columns: `scan_links.occurrences_json`, `scan_links.resolved_target`, `scan_nodes.external_refs_json`; one new index `ix_scan_links_resolved_target`. Matching types in `src/kernel/adapters/sqlite/schema.ts`; `linkToRow` / `rowToLink` / `nodeToRow` / `rowToNode` round-trip the new columns (round-trip tests already cover the shape).
|
|
307
|
-
|
|
308
|
-
**Two new analyzers.**
|
|
309
|
-
|
|
310
|
-
- `core/redundant-target-reference` flags `(source, resolved-target)` pairs reached via two or more syntactic surfaces, whether cross-extractor (same kind, multiple authored triggers) or cross-kind (multi-edge to one target). Walks `Link.occurrences[]` plus `Link.resolvedTarget` to detect the redundancy. Severity `warn`. Tests at `src/plugins/core/analyzers/redundant-target-reference/__tests__/redundant-target-reference.spec.ts`.
|
|
311
|
-
- `core/self-loop` flags links whose source is its own resolved target (a body heading like `# /deploy` inside the file that defines `/deploy`). Severity `warn`. The UI hides self-loops by default; this analyzer is the authoritative detector so the count is still visible in `sm scan` output and SARIF exports. Tests at `src/plugins/core/analyzers/self-loop/__tests__/self-loop.spec.ts`.
|
|
312
|
-
|
|
313
|
-
**Existing analyzer extended.** `core/reserved-name` now emits both target-side (the file shadowing a built-in, behaviour preserved) and source-side (one `warn` per link the lift downgraded to `RESERVED_TARGET_CONFIDENCE`). Source-side issues carry `data.target` matching the link so UIs can correlate per-row instead of bleeding "any issue on source" onto every outgoing edge.
|
|
314
|
-
|
|
315
|
-
**Extractor fixes.**
|
|
316
|
-
|
|
317
|
-
- `core/markdown-link` and `core/external-url-counter` now run their regex over `stripCodeBlocks(ctx.body)` instead of raw body, matching the guard `claude/at-directive` and `claude/slash` already had. Author-written examples like `[label](path)` or `https://example.com` inside backticks or fenced blocks stop emitting spurious `references` edges (which were feeding `core/broken-ref` false positives) and stop inflating the external-URL count. Three new test cases per extractor (inline-code, fenced, mixed).
|
|
318
|
-
- `claude/at-directive` and `claude/slash` extractors now track line numbers per occurrence (the `core/redundant-target-reference` analyzer needs every occurrence to know its line). Both compute `lineStarts` once per body via the new shared util `src/kernel/util/line-tracking.ts` (extracted from `markdown-link`'s previously-local helper) and attach `location: { line }` to every emit.
|
|
319
|
-
|
|
320
|
-
**BFF.** `/api/links?to=X` now matches via `target` OR `resolvedTarget`; the storage-layer companion in `getNodeBundle` does the same. Without this, a Claude `@real-agent` mention stayed invisible in the incoming list of `.claude/agents/real-agent.md` because the row's `target_path` carried the trigger, not the resolved path.
|
|
321
|
-
|
|
322
|
-
**UI overhaul, `LinkedNodesPanel`.**
|
|
323
|
-
|
|
324
|
-
- Numeric confidence value shown in the tag, was qualitative `high` / `medium` / `low`. The tier survives as the tag's tooltip and severity colour, so `0.85` and `1.00` are now visually distinguishable on the same row.
|
|
325
|
-
- New "Findings" section at the top of the panel, lists every issue whose `nodeIds[]` includes the focused path.
|
|
326
|
-
- Inline issue chip per outgoing / incoming row. Correlation rules tightened: source-side issue with `data.target` matching the link's `target` / `resolvedTarget` / current path (the original "any issue on source" fallback bled unrelated `broken-ref` findings onto every row).
|
|
327
|
-
- Per-row "Occurs at:" sub-list when `link.occurrences.length > 0`, shows each line + original trigger + extractor id.
|
|
328
|
-
- New "External references" section above Findings when `node.externalRefs` is populated, clickable URLs that open in a new tab.
|
|
329
|
-
- Self-loops hidden by default from outgoing + incoming via a client-side `isSelfLoop` filter. The `core/self-loop` analyzer remains the authoritative detector; the panel just respects it.
|
|
330
|
-
- Texts catalog (`linked-nodes-panel.texts.ts`) and CSS updated.
|
|
331
|
-
- `ui/src/models/api.ts` gained `ILinkOccurrenceApi`, `IExternalRefApi`, `Link.occurrences`, `Link.resolvedTarget`, `Node.externalRefs` shapes mirroring the kernel domain types.
|
|
332
|
-
|
|
333
|
-
**Plus an out-of-band AGENTS.md operating rule.** A new analyzer queues mid-execution user messages (do not abort an in-flight tool sequence to handle an interrupt unless the interrupt is an unambiguous abort verb). Lands in this commit because it surfaced during the same walkthrough.
|
|
334
|
-
|
|
335
|
-
Pre-1.0 minor on both workspaces per `spec/versioning.md` (additive shape changes, no breakage).
|
|
336
|
-
|
|
337
|
-
## User-facing
|
|
338
|
-
|
|
339
|
-
**Inspector overhaul.** Links show numeric confidence, a Findings list, per-row issue chips, and per-site "Occurs at" lines. New "External references" section. Self-loops hidden by default. Two new analyzers flag redundant multi-form references and self-loops.
|
|
340
|
-
|
|
341
102
|
- 5a12e5c: Phase 2.D of the Signal IR migration: new `core/signal-collision` built-in analyzer surfaces resolver rejections as operator-visible `warn` issues. The analyzer reads `IAnalyzerContext.signals`, finds every Signal whose `resolution.outcome === 'rejected'`, and emits one issue per rejection naming the loser extractor + matched text + byte range, the winner extractor + range, and the tiebreak reason (`kind-priority` / `higher-confidence` / `longer-range` / `earlier-declaration`). Phase 4+ stubs (`extractorDisabled`, `belowFloor`) are handled with their own message templates so the surface stays forward-compatible.
|
|
342
103
|
|
|
343
|
-
Closes spec conformance coverage row 37 (`signal.schema.json`) with the two required cases:
|
|
344
|
-
|
|
345
|
-
- `extractor-emits-signal`: a body with a single `[text](path)` markdown link materialises as one Link via the Signal IR resolver path; `sources[0] === 'markdown-link'`.
|
|
346
|
-
- `signal-collision-detection`: a body with `[@./api.md](./api.md)` triggers a cross-extractor range overlap (markdown-link's range contains at-directive's range); markdown-link wins on confidence; the loser surfaces as exactly one `core/signal-collision` warn issue.
|
|
347
|
-
|
|
348
|
-
## User-facing
|
|
349
|
-
|
|
350
|
-
`sm scan` now warns when two extractors detect overlapping byte ranges. The graph keeps the winner; the issue panel explains which detection lost and why, so a markdown link wrapping an `@`-directive no longer looks like silent disappearing intent.
|
|
351
|
-
|
|
352
104
|
- 3ca095b: Wire the Signal IR resolver end-to-end (Phase 2.A of the active-lens migration). The kernel's `resolveSignals` runs after extraction and before analysis: filters disabled extractors (Phase 4+ stub), ranks intra-Signal candidates via `IProvider.resolverRules.kindPriority` (when declared) + confidence + extractor declaration order, builds overlap clusters from body-scoped Signals sharing a source, picks a cluster winner per the four-step tiebreak chain (`kind-priority` -> `higher-confidence` -> `longer-range` -> `earlier-declaration`), materialises winners as Links indistinguishable from `emitLink`-emitted ones, and annotates each Signal's new `resolution` field with the outcome + reason. Rejected (losing) Signals remain accessible to analyzers via `IAnalyzerContext.signals` so a future `core/signal-collision` analyzer can surface them as `warn` issues naming WHO won and WHY.
|
|
353
105
|
|
|
354
|
-
Spec changes: `signal.schema.json` gains the `resolution` object property (outcome / winnerIndex / rejectedBy / phase 4+ stubs); `extensions/provider.schema.json` gains `resolverRules.kindPriority`; `architecture.md` §Resolver phase rewritten to reflect the wired contract; `conformance/coverage.md` row 37 flipped to in-progress.
|
|
355
|
-
|
|
356
|
-
Kernel changes: extend `Signal` type with `resolution?: ISignalResolution`; add `IResolverRules` + `IProvider.resolverRules`; rewrite `resolveSignals` (87-line first-candidate scaffold -> full algorithm); thread `signals` through `walkAndExtract` accumulators -> `runAnalyzers` -> per-analyzer context; export `isExternalUrlLink` for the caller's routing of materialised Links between internal / external arrays.
|
|
357
|
-
|
|
358
|
-
No extractor uses `emitSignal` yet (Phases 2.B and 2.C migrate them). With zero Signals emitted today the wiring is a no-op pass-through that returns empty arrays; 18 new resolver unit tests cover intra-Signal ranking, cross-Signal overlap, the four tiebreak reasons, kindPriority interaction, external-URL cluster skip, frontmatter / sidecar scope pass-through, and materialised Link shape parity.
|
|
359
|
-
|
|
360
106
|
### Patch Changes
|
|
361
107
|
|
|
362
108
|
- 1362de9: Phase 2.B of the Signal IR migration: `claude/at-directive` extractor now routes through `ctx.emitSignal` instead of `ctx.emitLink`. Each `@<token>` match emits a single-candidate Signal carrying the byte range, scope (`body`), and a candidate with the same kind / target / confidence / trigger / rationale shape the extractor used to embed directly into a Link. The resolver phase materialises the winning candidate as a Link indistinguishable from the prior direct-emit shape, including `occurrences[]` round-tripping; full `pnpm validate` stays green with 1734 tests passing and zero behaviour change.
|
|
363
109
|
|
|
364
|
-
Why through Signals: byte ranges now flow into the kernel resolver, which unlocks cross-extractor range-overlap collision detection (a future `core/signal-collision` analyzer will surface losers as `warn` issues). The single-candidate shape keeps the migration narrow; multi-candidate emissions for cases of genuine intra-Signal ambiguity stay deferred until a real case demands it.
|
|
365
|
-
|
|
366
|
-
Spec: `signal.schema.json` gains an optional `range.line` field so extractors that already compute line tracking (via `computeLineStarts` / `lineFor`) thread the line number through to the materialised `Link.location.line` without the resolver re-walking the body.
|
|
367
|
-
|
|
368
|
-
Kernel: resolver's `materialise()` synthesises a one-entry `occurrences[]` from the winning candidate's trigger + range so multi-extractor `dedupeLinks` merges accumulate occurrences through the same code path as direct emissions. `extractorOrder` and `link.sources` now both use short extractor ids (e.g. `'at-directive'`) to match the cache layer's lookup contract.
|
|
369
|
-
|
|
370
|
-
Test harness: `src/plugins/core/extractors/__tests__/extractors.spec.ts` `extract()` helper auto-flushes Signals via the resolver so tests that assert on the resulting `links` array see identical shape regardless of whether the extractor went through `emitLink` directly or routed through `emitSignal`.
|
|
371
|
-
|
|
372
110
|
## 0.34.0
|
|
373
111
|
|
|
374
112
|
### Minor Changes
|
|
375
113
|
|
|
376
114
|
- 2593664: Retire the `gemini` Provider and onboard the `antigravity` Provider. Google released the Antigravity CLI on 2026-05-19 as the replacement for the Gemini CLI (which sunsets 2026-06-18 for consumer tiers). Antigravity preserved the four pillars of Gemini CLI (Agent Skills, Hooks, Subagents, Extensions/plugins) but adopted the open-standard `.agents/` layout instead of carrying forward a vendor-specific `.gemini/` directory, so the old Provider classified obsolete paths.
|
|
377
115
|
|
|
378
|
-
Three coordinated changes ship together:
|
|
379
|
-
|
|
380
|
-
1. **`gemini` bundle deleted in full.** The provider, schemas, conformance fixtures, and tests under `src/plugins/gemini/` are gone. Any project relying on `.gemini/` classification routes Antigravity skills through the existing `agent-skills` Provider (open standard, dirname-based identifier) and AGENTS.md through the universal `core/markdown` fallback.
|
|
381
|
-
|
|
382
|
-
2. **New `antigravity` bundle (metadata-only).** `src/plugins/antigravity/providers/antigravity/` ships an empty-kinds Provider whose `classify()` always returns `null`. It contributes lens identity and a seed `reservedNames` catalog (Antigravity TUI built-in slash commands: `/agents`, `/help`, `/quit`, `/exit`, `/skills`, `/hooks`). When Google formalises subagent / hook on-disk paths the Provider will gain `kinds` and `classify()` accordingly.
|
|
383
|
-
|
|
384
|
-
3. **Active-lens auto-detect drops the `.gemini/` marker.** No replacement marker (Antigravity has no vendor-specific workspace directory). The lens is set manually via `sm config set activeProvider antigravity`.
|
|
385
|
-
|
|
386
|
-
Spec edits: `spec/architecture.md`, `spec/cli-contract.md`, `spec/plugin-author-guide.md`, `spec/db-schema.md`, `spec/README.md`, schemas in `spec/schemas/` updated to remove `gemini` references and add Antigravity context. `spec/index.json` regenerated.
|
|
387
|
-
|
|
388
|
-
## User-facing
|
|
389
|
-
|
|
390
|
-
**Gemini CLI support retired.** Antigravity CLI projects (Google's May 2026 replacement) scan via the open-standard `.agents/skills/` paths under the existing `agent-skills` lens. Run `sm config set activeProvider antigravity` to flag a project as Antigravity-flavoured.
|
|
391
|
-
|
|
392
116
|
- ee919da: Reserved-name catalog per Provider. Each Provider runtime owns a set of invocation names its built-ins consume (Claude reserves `/help`, `/clear`, `/init`, `/agents`, `/model`, … under `command`, and `general-purpose`, `output-style-setup`, `statusline-setup` under `agent`). User files declaring one of these names are silently shadowed at runtime, the kernel now surfaces the collision.
|
|
393
117
|
|
|
394
|
-
Two changes ship together:
|
|
395
|
-
|
|
396
|
-
1. **New `IProvider.reservedNames?: Record<kind, string[]>`**. Each Provider declares the names its runtime reserves per kind. Claude ships the documented built-in catalog (command + agent today); Gemini, OpenAI, and agent-skills declare none yet (no `reservedNames` field). User plugins MAY declare their own with the same shape.
|
|
397
|
-
|
|
398
|
-
2. **Two consumers share the catalog through a single per-scan `Set<nodePath>`**:
|
|
399
|
-
- **New `core/reserved-name` analyzer** emits one `warn` issue per user node whose normalised identifiers intersect its Provider's `reservedNames[kind]`. The issue carries `data: { provider, kind }` and a message pointing at the file with a rename hint.
|
|
400
|
-
- **The post-walk confidence-lift transform downgrades** any link that resolves to a reserved target (path or name match) to `RESERVED_TARGET_CONFIDENCE = 0.1` instead of bumping to `1.0`. When the same trigger has both a reserved and a non-reserved candidate accepted by the strict-kind filter, the non-reserved one wins and the bump goes to `1.0` normally.
|
|
401
|
-
|
|
402
|
-
The detection runs once per scan in the orchestrator (`buildReservedNodePaths`) so the analyzer and the transform share identical truth, the two surfaces cannot drift.
|
|
403
|
-
|
|
404
|
-
New spec section: `§Provider · reservedNames` in `spec/architecture.md`.
|
|
405
|
-
|
|
406
|
-
## User-facing
|
|
407
|
-
|
|
408
|
-
**Files whose name shadows a built-in are flagged.** A file like `.claude/commands/help.md` now emits a `warn` (Claude's runtime ignores it for its own `/help`), and incoming `/help` edges resolve to it at confidence `0.1` instead of `1.0`.
|
|
409
|
-
|
|
410
118
|
## 0.33.0
|
|
411
119
|
|
|
412
120
|
### Minor Changes
|
|
413
121
|
|
|
414
|
-
- da26519: Provider-aware confidence bump for resolved invocation links. Three changes ship together
|
|
415
|
-
|
|
416
|
-
1. **`core/markdown-link` references emit at confidence `1.0`** (previously `0.95`). The `[text](path)` syntax is unambiguous, there is no degree of inference left to discount; whether the path resolves is a separate concern owned by the `core/broken-ref` analyzer.
|
|
417
|
-
|
|
418
|
-
2. **New `IProviderKind.identifiers`** declaring how to derive a kind's canonical invocation handle(s). Closed set: `'frontmatter.name'`, `'filename-basename'`, `'dirname'`. Multiple sources accumulate (built-in Anthropic skills declare `['frontmatter.name', 'dirname']` so a skill without an explicit `name:` still resolves via the directory between `.claude/skills/` and `/SKILL.md`, matching https://code.claude.com/docs/en/skills.md).
|
|
419
|
-
|
|
420
|
-
3. **New `IProvider.resolution: Record<linkKind, targetKind[]>`** declaring the strict per-link-kind matrix the post-walk transform consults. `claude` ships `{ mentions: ['agent'], invokes: ['command', 'skill'] }`; `gemini` ships `{ mentions: ['agent'], invokes: ['skill'] }`; `openai` `{ mentions: ['agent'] }`; `agent-skills` `{ invokes: ['skill'] }`. A `/foo` slash matching an agent named `foo` does NOT bump because `invokes` excludes `agent` (the link-conflict / kind-mismatch analyzers handle that case separately).
|
|
421
|
-
|
|
422
|
-
The kernel renames `liftMentionConfidence` → `liftResolvedLinkConfidence` and generalises the rule to cover `mentions`, `invokes`, and `references` uniformly. Path-match (target equals a node path) still applies universally; name-match goes through the source Provider's resolution map. `broken-ref`'s scope (kind-agnostic "name exists somewhere") stays unchanged.
|
|
423
|
-
|
|
424
|
-
Spec wording: new `§Provider · kind identifiers` and `§Provider · resolution rules` sections in `spec/architecture.md`.
|
|
425
|
-
|
|
426
|
-
## User-facing
|
|
427
|
-
|
|
428
|
-
**Invocation links to resolved targets now render at full confidence.** `@reviewer` mentions and `/explore` slash commands that match an existing agent / skill / command in your project show up at confidence `1.0` (previously `0.5` / `0.8`), so the graph no longer dims them.
|
|
122
|
+
- da26519: Provider-aware confidence bump for resolved invocation links. Three changes ship together.
|
|
429
123
|
|
|
430
124
|
## 0.32.1
|
|
431
125
|
|
|
@@ -433,93 +127,18 @@
|
|
|
433
127
|
|
|
434
128
|
- 4af662b: Loosen the active-provider lens gate to lens-only: per-provider extractors run on every visited node when the active lens is in the extractor's declared `precondition.provider` allowlist, regardless of which provider classified the node.
|
|
435
129
|
|
|
436
|
-
The previous gate (shipped in 0.34.0) double-checked `nodeProvider AND activeProvider`. That broke a real surface: a `@handle` in `CLAUDE.md` or `notes/todo.md` (files the `claude` provider disclaims to `core/markdown` because markdown is provider-agnostic) never got parsed under the `claude` lens, because the node's provider was `core`, not `claude`. The runtime grammar the lens represents applies across every markdown surface, not only the files the provider's `classify()` owns, so the lens is the single discriminator. Cross-lens isolation is preserved by the lens half alone: under `gemini`, claude extractors are silent on every node (including `.claude/*`), because the lens authorisation is missing.
|
|
437
|
-
|
|
438
|
-
Spec wording in `spec/architecture.md` §Universal extractors and per-provider extractors updated to match. `matchesProviderPrecondition` signature simplified to `(ex, activeProvider)`; the `provider` field is removed from `computeCacheDecision` opts. Unit tests rewritten with the lens-only matrix.
|
|
439
|
-
|
|
440
|
-
## User-facing
|
|
441
|
-
|
|
442
|
-
**`@handle` and `/command` now resolve outside `.claude/`.** Under the Claude lens, mentions and invokes in `CLAUDE.md`, `notes/*.md`, and any markdown across the project are picked up as Claude edges. Switching lens hides them so the graph mirrors the active runtime.
|
|
443
|
-
|
|
444
130
|
## 0.32.0
|
|
445
131
|
|
|
446
132
|
### Minor Changes
|
|
447
133
|
|
|
448
134
|
- a5d6f12: `sm plugins enable` and `sm plugins disable` now accept multiple plugin ids in one invocation, e.g. `sm plugins disable gemini openai agent-skills`. The single-id form and `--all` keep working unchanged.
|
|
449
135
|
|
|
450
|
-
Batches are all-or-nothing: a single unknown or granularity-mismatched id aborts the call before any `config_plugins` write, so the user never lands in a partial state. Repeated ids in the same call are deduped. Locked plugins inside a batch are silently skipped (matching `--all` semantics), while in single-id mode a locked target still surfaces a directed exit-5 error.
|
|
451
|
-
|
|
452
|
-
Internals: only `#validateArgs` and `#pickTargets` in `src/cli/commands/plugins/toggle.ts` changed; `#persistTargets` and `#renderSuccess` already iterated over `string[]` and reused the existing multi-row i18n. `spec/cli-contract.md` documents the new `<id>...` shape on both verbs.
|
|
453
|
-
|
|
454
|
-
## User-facing
|
|
455
|
-
|
|
456
|
-
`sm plugins enable` / `sm plugins disable` now take multiple plugins at once, e.g. `sm plugins disable gemini openai agent-skills`. Unknown id rejects the whole batch (no partial writes); repeated ids are deduped; locked plugins in a batch are skipped silently.
|
|
457
|
-
|
|
458
136
|
## 0.31.0
|
|
459
137
|
|
|
460
138
|
### Minor Changes
|
|
461
139
|
|
|
462
140
|
- 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.
|
|
463
141
|
|
|
464
|
-
**Spec foundation**
|
|
465
|
-
|
|
466
|
-
- 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).
|
|
467
|
-
- New `signal.schema.json` defining the multi-candidate `Signal` IR emitted by extractors via `ctx.emitSignal()`.
|
|
468
|
-
- `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.
|
|
469
|
-
- `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.
|
|
470
|
-
- `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.
|
|
471
|
-
|
|
472
|
-
**Kernel**
|
|
473
|
-
|
|
474
|
-
- `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.
|
|
475
|
-
- 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.
|
|
476
|
-
- New `orchestrator/resolver.ts` scaffold (first-candidate-wins today; provider `resolverRules` land later).
|
|
477
|
-
- `frontmatter-toml` parser registered in the kernel parser registry (`smol-toml@1.6.1` pinned).
|
|
478
|
-
- `precondition.provider` is now enforced in `computeCacheDecision`: an extractor whose manifest declares `['claude']` only runs on nodes the `claude` provider classified.
|
|
479
|
-
|
|
480
|
-
**Lens model wiring**
|
|
481
|
-
|
|
482
|
-
- `src/core/config/active-provider.ts`: `resolveActiveProvider(cwd)` with filesystem auto-detect (`.claude/` / `.gemini/` / `.codex/` / `AGENTS.md` / `.cursor/`) and `isExtensionActiveUnderLens` predicate.
|
|
483
|
-
- `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.
|
|
484
|
-
- `sm config set activeProvider <id>` drops the scan zone and prints the lens-switch receipt with a hint to rescan.
|
|
485
|
-
- BFF: `GET / PATCH /api/active-provider` with the same drop-on-switch semantic, registered before the catch-all.
|
|
486
|
-
- 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.
|
|
487
|
-
|
|
488
|
-
**Numeric `Confidence` migration**
|
|
489
|
-
|
|
490
|
-
Atomic across spec / kernel / SQL / extractors / tests:
|
|
491
|
-
|
|
492
|
-
- `Confidence = number` in `kernel/types.ts`; `enum-parsers.ts` range-checks `[0..1]`.
|
|
493
|
-
- `validateLink` rejects out-of-range values with a `link-confidence-out-of-range` extension.error.
|
|
494
|
-
- 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).
|
|
495
|
-
- `sm show` renders confidence as a percent (`85%`).
|
|
496
|
-
- `link-conflict` `rankConfidence` reduces to identity; resolver's `bucketConfidence` bridge removed.
|
|
497
|
-
- Rename heuristic uses `ConfidenceTier.HIGH/MEDIUM` and exposes `renameTierLabel` for the analyzer-id mapping.
|
|
498
|
-
- DB column flips from `TEXT` (enum check) to `REAL` (range check) in `001_initial.sql`.
|
|
499
|
-
|
|
500
|
-
**MCP virtual nodes**
|
|
501
|
-
|
|
502
|
-
- 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.
|
|
503
|
-
- Claude provider registers the `mcp` kind in its catalog (violet, server icon).
|
|
504
|
-
|
|
505
|
-
**OpenAI Codex provider (MVP)**
|
|
506
|
-
|
|
507
|
-
- 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`).
|
|
508
|
-
- `BUNDLE_ORDER` extended in the built-ins generator. 5 bundles, 29 built-in extensions total.
|
|
509
|
-
|
|
510
|
-
**Phase 4b extractor mudanza**
|
|
511
|
-
|
|
512
|
-
- `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/`.
|
|
513
|
-
|
|
514
|
-
**Settings UI polish**
|
|
515
|
-
|
|
516
|
-
- `<sm-settings-project>` reorders to put the Active provider selector first.
|
|
517
|
-
- `<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.
|
|
518
|
-
|
|
519
|
-
## User-facing
|
|
520
|
-
|
|
521
|
-
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.
|
|
522
|
-
|
|
523
142
|
## 0.30.0
|
|
524
143
|
|
|
525
144
|
### Minor Changes
|
|
@@ -534,52 +153,6 @@
|
|
|
534
153
|
its own changesets, validate phases, and narrative outweighed the value
|
|
535
154
|
of a thin packaged helper layer.
|
|
536
155
|
|
|
537
|
-
**`spec/plugin-author-guide.md`:**
|
|
538
|
-
|
|
539
|
-
- §Testing rewritten as "Testing your plugin": shows the fake-`ctx`
|
|
540
|
-
pattern inline (extractor + analyzer + formatter + probabilistic
|
|
541
|
-
runner), with the public types coming from `@skill-map/cli`.
|
|
542
|
-
- §Stability footer updated to reference Step 10 for future
|
|
543
|
-
Action / Hook testing patterns instead of testkit coverage.
|
|
544
|
-
- §Providers / Actions advisory wording no longer references the
|
|
545
|
-
testkit roadmap.
|
|
546
|
-
|
|
547
|
-
**`spec/architecture.md`:**
|
|
548
|
-
|
|
549
|
-
- `src/` directory tree drops the `testkit/` row.
|
|
550
|
-
- Qualified-id example list swaps `hello-world/greet` for the
|
|
551
|
-
generic `my-plugin/my-extractor`.
|
|
552
|
-
|
|
553
|
-
**Monorepo plumbing** (no end-user impact):
|
|
554
|
-
|
|
555
|
-
- `pnpm-workspace.yaml`, root `package.json`, `Dockerfile`, and
|
|
556
|
-
`scripts/check-changeset.js` drop the `testkit/` and
|
|
557
|
-
`examples/hello-world/` entries.
|
|
558
|
-
- `context/scripts.md`, `context/kernel.md`, `context/notebooklm.md`,
|
|
559
|
-
`ROADMAP.md`, `CONTRIBUTING.md`, `AGENTS.md`, `.claude/agents/commit.md`,
|
|
560
|
-
and `scripts/build-user-changelog.js` updated to reflect the
|
|
561
|
-
two-public-package surface (`@skill-map/spec` + `@skill-map/cli`).
|
|
562
|
-
- `src/__tests__/integration/dockerfile-demo-assets.spec.ts` drops
|
|
563
|
-
the obsolete `COPY` assertions for both removed workspaces.
|
|
564
|
-
- JSDoc in `src/kernel/registry.ts` replaces the `hello-world/greet`
|
|
565
|
-
example with `my-plugin/my-extractor`.
|
|
566
|
-
|
|
567
|
-
**`web/modules/roadmap.js`:**
|
|
568
|
-
|
|
569
|
-
- Step 9 card (EN + ES, release tag + brief) drops the
|
|
570
|
-
`@skill-map/testkit` mention.
|
|
571
|
-
|
|
572
|
-
**Post-merge action required**: run
|
|
573
|
-
`/usr/bin/npm deprecate "@skill-map/testkit@*" "Subsumed: plugin authors
|
|
574
|
-
test against @skill-map/cli types directly. See
|
|
575
|
-
https://github.com/crystian/skill-map/blob/main/spec/plugin-author-guide.md."`
|
|
576
|
-
against the real `npm` binary (NOT the `pnpm`-aliased `npm` in the
|
|
577
|
-
maintainer's shell, which fails with `ERR_PNPM_REGISTRY_ERROR: 404 Not
|
|
578
|
-
Found` on the deprecate endpoint). `/usr/bin/` bypasses the zsh alias;
|
|
579
|
-
`command npm` and `\npm` are equivalent escapes. Latest published
|
|
580
|
-
version is `0.5.2`; the wildcard range covers every prior tag so anyone
|
|
581
|
-
with the package pinned sees the deprecation notice.
|
|
582
|
-
|
|
583
156
|
- d95e5b8: Remove the `scan.extraFolders` config key. Project-local persistent
|
|
584
157
|
extension of the indexed scan no longer exists; to walk a directory
|
|
585
158
|
outside the project root pass it as a positional argument to
|
|
@@ -587,89 +160,6 @@ Found` on the deprecate endpoint). `/usr/bin/` bypasses the zsh alias;
|
|
|
587
160
|
`scan.referencePaths` key (validate links against on-disk files
|
|
588
161
|
without indexing them) is unaffected.
|
|
589
162
|
|
|
590
|
-
**Spec (`spec/`):**
|
|
591
|
-
|
|
592
|
-
- `spec/schemas/project-config.schema.json`: `extraFolders` block
|
|
593
|
-
deleted. `scan.referencePaths` description trimmed of cross-
|
|
594
|
-
references and now reads stand-alone.
|
|
595
|
-
- `spec/architecture.md` §Config layering: `PROJECT_LOCAL_ONLY_KEYS`
|
|
596
|
-
catalogue drops `scan.extraFolders`.
|
|
597
|
-
- `spec/plugin-author-guide.md`: the "the only way to scan paths
|
|
598
|
-
outside the project is `scan.extraFolders`" sentence rewrites to
|
|
599
|
-
point at positional roots.
|
|
600
|
-
- `spec/index.json` regenerated.
|
|
601
|
-
|
|
602
|
-
**Kernel + config (`src/kernel/`, `src/config/`, `src/core/config/`):**
|
|
603
|
-
|
|
604
|
-
- `IScanConfig` drops `extraFolders: string[]`.
|
|
605
|
-
- `PROJECT_LOCAL_ONLY_KEYS` and `PRIVACY_SENSITIVE_KEYS` lose the
|
|
606
|
-
entry.
|
|
607
|
-
- `projectPathExposure` collapses the two-branch list-check to one.
|
|
608
|
-
- `defaults.json` drops the `extraFolders: []` line.
|
|
609
|
-
|
|
610
|
-
**Runtime (`src/core/runtime/`):**
|
|
611
|
-
|
|
612
|
-
- `resolveScanRoots(inputs)` simplifies to `{ positionalRoots } =>
|
|
613
|
-
string[]`; no more `IScanRootsInputs.extraFolders`,
|
|
614
|
-
`IScanRootsResolution.fromExtra`, or `emitRootsAdvisory()`.
|
|
615
|
-
- The `includingExtraFoldersAdvisory` text catalog entry is removed.
|
|
616
|
-
|
|
617
|
-
**CLI (`src/cli/`):**
|
|
618
|
-
|
|
619
|
-
- `sm scan` help text loses the extraFolders sentence; positional
|
|
620
|
-
roots are now the documented way to extend the scan.
|
|
621
|
-
- `sm serve` boot banner reads only `scan.referencePaths` from the
|
|
622
|
-
effective config; the banner row labelled `Extras` (and the
|
|
623
|
-
matching shape on `IBannerInput` / `IFigletInput`) is removed.
|
|
624
|
-
`Refs` stays.
|
|
625
|
-
- `sm config set --yes` description trimmed to reflect the single
|
|
626
|
-
privacy-sensitive key remaining.
|
|
627
|
-
|
|
628
|
-
**Server (`src/server/`):**
|
|
629
|
-
|
|
630
|
-
- `WatcherService` no longer reads config to compute roots; it walks
|
|
631
|
-
`['.']` unconditionally. `loadConfig` and `resolveScanRoots`
|
|
632
|
-
imports drop. `restart()` is still useful (and still wired by
|
|
633
|
-
`PATCH /api/project-preferences`) so the side-set walk picks up
|
|
634
|
-
fresh `scan.referencePaths` on the next batch.
|
|
635
|
-
- `PATCH /api/project-preferences`: AJV body schema, `IPatchBody`,
|
|
636
|
-
`IProjectPreferencesEnvelope`, `IPlannedWrite.key`, `collectWrites`
|
|
637
|
-
all collapse to a single `referencePaths` branch.
|
|
638
|
-
- Catalog strings adjusted (the `extraFolders` example dropped from
|
|
639
|
-
`projectPrefsScanNotObject` etc).
|
|
640
|
-
|
|
641
|
-
**UI (`ui/src/`):**
|
|
642
|
-
|
|
643
|
-
- Settings → Project drops the entire `extraFolders` row (HTML, TS
|
|
644
|
-
signal + computed + add/remove handlers, i18n strings, mocks).
|
|
645
|
-
- `IProjectPreferencesApi` and `IProjectPreferencesPatchApi` lose
|
|
646
|
-
`extraFolders`.
|
|
647
|
-
- Test mocks (`app.spec.ts`, `graph-view.spec.ts`,
|
|
648
|
-
`inspector-view.spec.ts`) updated.
|
|
649
|
-
|
|
650
|
-
**Tests:**
|
|
651
|
-
|
|
652
|
-
- `server/routes/__tests__/project-preferences-route.spec.ts`: 5
|
|
653
|
-
PATCH cases remapped from `extraFolders` to `referencePaths`.
|
|
654
|
-
- `kernel/config/__tests__/config-loader.spec.ts`: strip-test
|
|
655
|
-
renamed and split.
|
|
656
|
-
- `core/runtime/__tests__/scan-roots.spec.ts`: drops 3 cases that
|
|
657
|
-
passed `extraFolders`; keeps the positional + default cases.
|
|
658
|
-
- `core/config/__tests__/config-helper.spec.ts`:
|
|
659
|
-
`PROJECT_LOCAL_ONLY_KEYS` catalogue assertion narrowed; the
|
|
660
|
-
`target=project` rejection test now targets `scan.referencePaths`.
|
|
661
|
-
|
|
662
|
-
**Backward compatibility note**: existing `settings.local.json` files
|
|
663
|
-
that still carry `scan.extraFolders` keep loading without error. The
|
|
664
|
-
loader's per-key resilience drops the unknown key with a generic
|
|
665
|
-
"unknown key ignored" warning; nothing crashes, the rest of the file
|
|
666
|
-
takes effect. Operators who relied on the key should switch to
|
|
667
|
-
positional roots on `sm scan`.
|
|
668
|
-
|
|
669
|
-
## User-facing
|
|
670
|
-
|
|
671
|
-
We removed `scan.extraFolders`. To extend the scan beyond the project root, pass folders as positional arguments to `sm scan [roots...]`. The `scan.referencePaths` key (validates links against on-disk files without indexing) is unchanged. Existing entries are silently ignored.
|
|
672
|
-
|
|
673
163
|
## 0.29.0
|
|
674
164
|
|
|
675
165
|
### Minor Changes
|
|
@@ -696,230 +186,10 @@ string[]`; no more `IScanRootsInputs.extraFolders`,
|
|
|
696
186
|
into the manifest schemas themselves so the only fields that survive
|
|
697
187
|
are the ones the kernel cannot derive from disk.
|
|
698
188
|
|
|
699
|
-
**Plugin manifest (`plugin.json`):**
|
|
700
|
-
|
|
701
|
-
- Drop `id` (the directory name is the id; AJV rejects manifests that
|
|
702
|
-
declare it).
|
|
703
|
-
- `description` and `catalogCompat` are now required (were optional).
|
|
704
|
-
- `granularity` is now optional with a default of `'extension'` (was
|
|
705
|
-
required). Most plugins drop the field entirely.
|
|
706
|
-
- Drop `settings` at the plugin level; settings move to the extension
|
|
707
|
-
manifests that actually consume them.
|
|
708
|
-
|
|
709
|
-
**Extension base (every kind):**
|
|
710
|
-
|
|
711
|
-
- Drop `id`, `kind`, `stability`, `preconditions` (free-form). The
|
|
712
|
-
loader injects `id` / `kind` / `pluginId` from the folder layout;
|
|
713
|
-
the other two were display-only and free-form respectively, and
|
|
714
|
-
the kernel never consumed them.
|
|
715
|
-
- `description` is now required.
|
|
716
|
-
- Rename `annotationContributions` (map) to `annotation` (singular):
|
|
717
|
-
one extension contributes at most one annotation key, and the key
|
|
718
|
-
is the extension's folder name. Use multiple extensions to
|
|
719
|
-
contribute multiple keys.
|
|
720
|
-
- Rename `viewContributions` to `ui` on the manifest. The
|
|
721
|
-
runtime-aggregated catalog (`Kernel.getRegisteredViewContributions()`,
|
|
722
|
-
`IPluginRuntimeBundle.viewContributions`) keeps its name.
|
|
723
|
-
- Add `settings: Record<id, ISettingDeclaration>` (moved from the
|
|
724
|
-
plugin manifest).
|
|
725
|
-
|
|
726
|
-
**Provider:**
|
|
727
|
-
|
|
728
|
-
- Drop the inline `kinds` map. The kind catalog now lives under
|
|
729
|
-
`<plugin>/kinds/<kindName>/` with two files per kind:
|
|
730
|
-
`schema.json` (frontmatter schema) and `kind.json` carrying the
|
|
731
|
-
`{ ui }` block. The loader walks the directory and projects each
|
|
732
|
-
entry into the runtime `kinds` descriptor.
|
|
733
|
-
- New schema `extensions/provider-kind.schema.json` validates the
|
|
734
|
-
`kind.json` shape.
|
|
735
|
-
- Drop `defaultRefreshAction`. The UI's `🧠 prob` refresh button is
|
|
736
|
-
retired; a replacement UX is TBD.
|
|
737
|
-
- `roots` is enforcement-grade: a Provider with declared `roots`
|
|
738
|
-
only sees files matching at least one glob; a Provider without
|
|
739
|
-
`roots` acts as the fallback for files unmatched by any other
|
|
740
|
-
Provider. Supported patterns: `prefix/**` (deep), `prefix/*`
|
|
741
|
-
(shallow), exact path. Two Providers whose roots both match the
|
|
742
|
-
same file produce `provider-ambiguous` (already in spec) and the
|
|
743
|
-
file stays unclassified.
|
|
744
|
-
|
|
745
|
-
**Extractor:**
|
|
746
|
-
|
|
747
|
-
- Drop `emitsLinkKinds` (the global closed enum of link kinds is the
|
|
748
|
-
contract; off-enum emissions drop with `extension.error`).
|
|
749
|
-
- Drop `defaultConfidence` (declare confidence per-emit on
|
|
750
|
-
`ctx.emitLink({ ..., confidence })`).
|
|
751
|
-
- Drop `applicableKinds` (array). Use `precondition.kind` instead with
|
|
752
|
-
qualified ids like `'claude/agent'`. The same `precondition` shape
|
|
753
|
-
is shared with Analyzer and Action.
|
|
754
|
-
|
|
755
|
-
**Analyzer:**
|
|
756
|
-
|
|
757
|
-
- Drop `emitsAnalyzerIds` (the qualified extension id is the default
|
|
758
|
-
`analyzer_id`).
|
|
759
|
-
- Drop `defaultSeverity` (declare severity per-emit on
|
|
760
|
-
`ctx.emitIssue({ ..., severity })`).
|
|
761
|
-
- Drop `consumes`, `configurable`, `recommendedActions`. The
|
|
762
|
-
analyzer↔action relationship is now declared from the Action side
|
|
763
|
-
via `precondition.analyzerIds` (Modelo B): one Action says "I
|
|
764
|
-
resolve these analyzer findings", instead of one Analyzer saying
|
|
765
|
-
"these actions help".
|
|
766
|
-
- Add `precondition: { kind?, provider? }` (same shape as Extractor).
|
|
767
|
-
|
|
768
|
-
**Action:**
|
|
769
|
-
|
|
770
|
-
- Drop `reportSchemaRef` and `promptTemplateRef`. The kernel now
|
|
771
|
-
resolves these by convention from the action folder:
|
|
772
|
-
`<action-dir>/report.schema.json` (always required) and
|
|
773
|
-
`<action-dir>/prompt.md` (required when `mode='probabilistic'`,
|
|
774
|
-
forbidden when `mode='deterministic'`).
|
|
775
|
-
- Drop `expectedTools`, `fanOutPolicy`, `precondition.stability`,
|
|
776
|
-
`precondition.custom`.
|
|
777
|
-
- Add `precondition.analyzerIds` (Modelo B).
|
|
778
|
-
- Rename `expectedDurationSeconds` to `probExpectedDurationSeconds`
|
|
779
|
-
to mark it as probabilistic-only via the `prob*` prefix convention.
|
|
780
|
-
- `mode` is now optional with default `'deterministic'` (was
|
|
781
|
-
required).
|
|
782
|
-
|
|
783
|
-
**Formatter:**
|
|
784
|
-
|
|
785
|
-
- Drop `formatId` (comes from the folder name; the loader injects it
|
|
786
|
-
into the runtime instance).
|
|
787
|
-
- Drop `supportsFilter` (every formatter supports `--filter`).
|
|
788
|
-
|
|
789
|
-
**Hook:**
|
|
790
|
-
|
|
791
|
-
- Drop `mode`. Hooks are deterministic-only; LLM-dependent reactions
|
|
792
|
-
are modeled as a deterministic hook that enqueues a probabilistic
|
|
793
|
-
Action via `ctx.queue('<plugin>/<action>', payload)`.
|
|
794
|
-
|
|
795
|
-
**Loader changes (`src/kernel/adapters/plugin-loader/`):**
|
|
796
|
-
|
|
797
|
-
- The exported manifest is stripped of any `id` / `kind` / `pluginId`
|
|
798
|
-
/ `kinds` / `formatId` keys before AJV validation; the loader
|
|
799
|
-
injects the canonical values from the folder layout. Legacy
|
|
800
|
-
manifests that still inline these fields load cleanly.
|
|
801
|
-
- New `discoverProviderKinds(...)` reads `<plugin>/kinds/<k>/{schema.json,
|
|
802
|
-
kind.json}` and merges the result into the runtime Provider
|
|
803
|
-
instance. Failure modes: missing or unparseable `schema.json`
|
|
804
|
-
→ `load-error`; missing, unparseable, or AJV-invalid `kind.json`
|
|
805
|
-
→ `invalid-manifest`.
|
|
806
|
-
- New `validateActionFileConventions(...)` enforces the
|
|
807
|
-
`report.schema.json` / `prompt.md` conventions.
|
|
808
|
-
- New `matchesAnyRoot(...)` powers Provider `roots` enforcement
|
|
809
|
-
inside `processRawNode`.
|
|
810
|
-
|
|
811
|
-
**Spec docs:**
|
|
812
|
-
|
|
813
|
-
- `architecture.md` §Extension kinds table, §Provider · `kinds`
|
|
814
|
-
catalog, §View contribution system updated.
|
|
815
|
-
- `plugin-author-guide.md` §Manifest section rewritten (id from
|
|
816
|
-
folder; description/catalogCompat required), §Extractor section
|
|
817
|
-
reworked around `precondition.kind`, drop guidance for
|
|
818
|
-
`emitsLinkKinds` / `defaultConfidence`.
|
|
819
|
-
- `view-slots.md` references `ui` map.
|
|
820
|
-
|
|
821
|
-
**Built-ins migration:**
|
|
822
|
-
|
|
823
|
-
- `core/bump/report.schema.json` and `core/mark-superseded/report.schema.json`
|
|
824
|
-
added (file conventions).
|
|
825
|
-
- `core/tools-count` extractor uses
|
|
826
|
-
`precondition: { kind: ['claude/agent'] }`.
|
|
827
|
-
- All built-in extensions drop `stability`, `preconditions`,
|
|
828
|
-
`emitsLinkKinds`, `defaultConfidence`, `emitsAnalyzerIds`,
|
|
829
|
-
`defaultSeverity`, `consumes`, `configurable`, `recommendedActions`,
|
|
830
|
-
`defaultRefreshAction`, `formatId` (formatter), `supportsFilter`,
|
|
831
|
-
`mode` (hook).
|
|
832
|
-
- `scripts/generate-built-ins.js` updated: `id` from bundle folder,
|
|
833
|
-
`granularity ?? 'extension'`, `toExtensionRow` drops the retired
|
|
834
|
-
display fields.
|
|
835
|
-
|
|
836
|
-
**Testkit:**
|
|
837
|
-
|
|
838
|
-
- `makeExtractorContext` populates `settings: {}` so test fixtures
|
|
839
|
-
satisfy the new required field on `IExtractorContext`.
|
|
840
|
-
|
|
841
|
-
## User-facing
|
|
842
|
-
|
|
843
|
-
**Plugin manifests are smaller.** `plugin.json` drops `id`; every extension declares only `version` + `description` plus kind-specific fields. View contributions move to `ui:`. Provider kinds live under `kinds/<kindName>/`. Run `sm plugins doctor` after upgrading.
|
|
844
|
-
|
|
845
189
|
- 8b7abbf: Structure-as-truth refactor for plugin extensions. The filesystem
|
|
846
190
|
layout (rather than declarative manifest fields) is now the single
|
|
847
191
|
source of truth for bundle / kind / extension id.
|
|
848
192
|
|
|
849
|
-
**Schema changes:**
|
|
850
|
-
|
|
851
|
-
- `PluginManifest` drops the required `extensions: string[]` array;
|
|
852
|
-
the kernel now auto-discovers extensions by walking
|
|
853
|
-
`<plugin-dir>/<kind>s/<name>/index.{js,mjs,ts}` for each known
|
|
854
|
-
kind. `granularity` is now required (no implicit default).
|
|
855
|
-
- `ExtensionBase` drops the `entry` field (it was an override for
|
|
856
|
-
the now-gone `extensions[]` path; the loader computes the entry
|
|
857
|
-
path from the discovered file).
|
|
858
|
-
- `viewContributions` moves from `base.schema.json` to
|
|
859
|
-
`extractor.schema.json` and `analyzer.schema.json`. Runtime
|
|
860
|
-
`ctx.emitContribution` only exists for those two kinds; declaring
|
|
861
|
-
view contributions on other kinds used to be a silent no-op and
|
|
862
|
-
is now rejected at manifest load.
|
|
863
|
-
|
|
864
|
-
**Loader changes:**
|
|
865
|
-
|
|
866
|
-
- `<plugin-dir>/<kind>s/<name>/` is the unit of discovery; the
|
|
867
|
-
loader walks `providers`, `extractors`, `analyzers`, `actions`,
|
|
868
|
-
`formatters`, `hooks` in canonical order, looking for an
|
|
869
|
-
`index.{js,mjs,ts}` inside each name directory.
|
|
870
|
-
- A manifest whose `kind` disagrees with the folder it lives under
|
|
871
|
-
(e.g. an `extractor` placed under `analyzers/`) is rejected as
|
|
872
|
-
`invalid-manifest` with a directed reason.
|
|
873
|
-
- Containment is enforced by construction: the loader never reads
|
|
874
|
-
paths the manifest could redirect, so `..`-escape and
|
|
875
|
-
absolute-path lanes are closed without runtime checks.
|
|
876
|
-
|
|
877
|
-
**Built-ins reorganization:**
|
|
878
|
-
|
|
879
|
-
- Source tree renamed `src/built-in-plugins/` → `src/plugins/` and
|
|
880
|
-
reorganized to `src/plugins/<bundle>/<kind>s/<name>/index.ts`.
|
|
881
|
-
Each bundle (`core`, `claude`, `gemini`, `agent-skills`) gains a
|
|
882
|
-
`plugin.json` with its metadata.
|
|
883
|
-
- `src/plugins/built-ins.ts` is now generated by
|
|
884
|
-
`scripts/generate-built-ins.js` (runs as `prebuild`, checked for
|
|
885
|
-
drift by `built-ins:check` in CI). The generator walks the
|
|
886
|
-
filesystem, reads each bundle's `plugin.json`, and emits static
|
|
887
|
-
imports + the legacy API surface (`builtIns()`,
|
|
888
|
-
`listBuiltIns()`, `builtInBundles`).
|
|
889
|
-
|
|
890
|
-
**Scaffolder (`sm plugins create`):**
|
|
891
|
-
|
|
892
|
-
- Emits the new layout: `extractors/<plugin-id>-extractor/index.js`
|
|
893
|
-
plus a `plugin.json` without `extensions` and without
|
|
894
|
-
`pluginId` on the extension export (the loader injects it from
|
|
895
|
-
`plugin.json#/id`). The legacy `mode: 'deterministic'` field on
|
|
896
|
-
the extractor stub was a no-op holdover from when extractors had
|
|
897
|
-
a mode and has been removed.
|
|
898
|
-
|
|
899
|
-
**Per-extension co-located files convention:**
|
|
900
|
-
|
|
901
|
-
Files that share the extension's folder with `index.{js,mjs,ts}`
|
|
902
|
-
are author-owned siblings. Two blessed names so consumers know
|
|
903
|
-
where to look:
|
|
904
|
-
|
|
905
|
-
- `text.ts` for externalised user-facing strings (one per
|
|
906
|
-
extension, imported by `index.ts` as `./text.js`).
|
|
907
|
-
- `<extension-name>.test.{ts,mjs,js}` for the colocated test
|
|
908
|
-
suite (picked up by the workspace's `plugins/**/*.test.ts`
|
|
909
|
-
glob).
|
|
910
|
-
|
|
911
|
-
Both are optional; the loader ignores anything that is not
|
|
912
|
-
`index.{js,mjs,ts}`, so future schemas / fixtures / conformance
|
|
913
|
-
scopes can live next to the code without manifest plumbing. The
|
|
914
|
-
in-tree built-ins under `src/plugins/` were migrated to this
|
|
915
|
-
shape: each analyzer's user-facing strings now live at
|
|
916
|
-
`<bundle>/analyzers/<name>/text.ts` instead of a centralised
|
|
917
|
-
`i18n/` directory.
|
|
918
|
-
|
|
919
|
-
## User-facing
|
|
920
|
-
|
|
921
|
-
**Plugin layout changed.** Extensions now live at `<kind>s/<name>/index.js` (e.g. `extractors/keyword-counter/index.js`); `plugin.json` no longer lists `extensions[]` and requires `granularity`. Run `sm plugins doctor` after migrating, or use `sm plugins create` for new plugins.
|
|
922
|
-
|
|
923
193
|
## 0.27.0
|
|
924
194
|
|
|
925
195
|
### Minor Changes
|
|
@@ -930,148 +200,20 @@ kind.json}` and merges the result into the runtime Provider
|
|
|
930
200
|
`SKILL_MAP_SCOPE` env var, no silent merge of user-level config or
|
|
931
201
|
plugins.
|
|
932
202
|
|
|
933
|
-
The user extends the scan beyond the project root via the existing
|
|
934
|
-
`scan.extraFolders` setting in project-local config (privacy-gated
|
|
935
|
-
through `sm config set --yes` or the Settings UI confirm dialog).
|
|
936
|
-
Plugins outside the project install per-project at
|
|
937
|
-
`<cwd>/.skill-map/plugins/` or load via the `--plugin-dir <path>`
|
|
938
|
-
escape hatch on the `sm plugins …` verb family.
|
|
939
|
-
|
|
940
|
-
**Narrow documented exception**: a single `~/.skill-map/settings.json`
|
|
941
|
-
file (validated by `user-settings.schema.json`) holds genuinely
|
|
942
|
-
per-machine preferences. Today it carries the update-check toggle +
|
|
943
|
-
its throttle bookkeeping; future per-machine settings (locale, theme)
|
|
944
|
-
extend it under their own sub-keys. There is no `.local` partner.
|
|
945
|
-
The file is NOT part of the project config layer system; it is read
|
|
946
|
-
directly by the module that owns each feature. `src/cli/util/user-settings-store.ts`
|
|
947
|
-
is the only module that calls `os.homedir()` for this file. The two
|
|
948
|
-
remaining `os.homedir()` callsites (`core/config/helper.ts`,
|
|
949
|
-
`core/runtime/reference-paths-walker.ts`) handle user-typed `~/foo`
|
|
950
|
-
expansion inside `scan.extraFolders` / `scan.referencePaths`, the
|
|
951
|
-
read is user-authored per invocation, not skill-map's own default.
|
|
952
|
-
|
|
953
|
-
Removed surface (`@skill-map/cli`):
|
|
954
|
-
|
|
955
|
-
- `-g/--global` flag inherited by every `SmCommand` verb (`bump`,
|
|
956
|
-
`check`, `config`, `export`, `graph`, `history`, `init`, `jobs`,
|
|
957
|
-
`list`, `orphans`, `refresh`, `scan`, `serve`, `show`, `sidecar`,
|
|
958
|
-
`watch`, every `plugins` subcommand). Calling any verb with
|
|
959
|
-
`-g/--global` now exits 2 with Clipanion's "unknown option" error.
|
|
960
|
-
- `SKILL_MAP_SCOPE=global` env var translation.
|
|
961
|
-
- `sm serve --scope project|global` flag.
|
|
962
|
-
- `sm config --source global` literal in `--source` outputs (the
|
|
963
|
-
source set is now `default | project | project-local | env | flag`).
|
|
964
|
-
- `IRuntimeContext.homedir` field.
|
|
965
|
-
- `IDbLocationOptions.global` field; `resolveDbPath` reduces to
|
|
966
|
-
`db ?? defaultProjectDbPath(ctx)`.
|
|
967
|
-
- `defaultUserPluginsDir` helper.
|
|
968
|
-
- `loadConfig` `scope: 'project' | 'global'` parameter and the
|
|
969
|
-
`user` / `user-local` file-pair iteration; the layer list is now
|
|
970
|
-
`defaults` → `project` → `project-local` → `override`.
|
|
971
|
-
- `USER_ONLY_KEYS` constant and the per-key locality enforcement
|
|
972
|
-
pinned to it. `updateCheck.enabled` is no longer part of the
|
|
973
|
-
config layer system; its toggle lives alongside the throttle
|
|
974
|
-
cache.
|
|
975
|
-
- `GET /api/health` response field `scope: 'project'|'global'`.
|
|
976
|
-
- `GET /api/plugins` item field `source: 'built-in'|'project'|'global'`
|
|
977
|
-
reduces to `'built-in'|'project'`.
|
|
978
|
-
- `scan_meta.scope` SQLite column and the matching `IScanResult.scope`
|
|
979
|
-
kernel field.
|
|
980
|
-
|
|
981
|
-
Removed surface (`@skill-map/spec`):
|
|
982
|
-
|
|
983
|
-
- `spec/cli-contract.md` § Global flags row for `-g/--global` and
|
|
984
|
-
the `SKILL_MAP_SCOPE` row in the env-var table.
|
|
985
|
-
- `spec/cli-contract.md` § serve flag table `--scope project|global`
|
|
986
|
-
row.
|
|
987
|
-
- `spec/architecture.md` § Config layering layers `user` and
|
|
988
|
-
`user-local`; `USER_ONLY_KEYS` set.
|
|
989
|
-
- `spec/db-schema.md` two-scope diagram; `scan_meta.scope` column;
|
|
990
|
-
`scope: 'global'` from `--source` enum text.
|
|
991
|
-
- `spec/schemas/scan-result.schema.json` `scope` property (was in
|
|
992
|
-
`required`).
|
|
993
|
-
- `spec/schemas/project-config.schema.json` `updateCheck`
|
|
994
|
-
description rewritten as the documented exception.
|
|
995
|
-
- `spec/schemas/plugins-registry.schema.json` status description's
|
|
996
|
-
`project / global / --plugin-dir` reference.
|
|
997
|
-
|
|
998
|
-
Added surface:
|
|
999
|
-
|
|
1000
|
-
- `spec/cli-contract.md` § "Scope is always project-local"
|
|
1001
|
-
normative paragraph at the top of the file, stating the
|
|
1002
|
-
no-`$HOME`-reads principle and the update-check exception.
|
|
1003
|
-
- `AGENTS.md` § Analyzers gains the matching operating rule for
|
|
1004
|
-
agents working in the repo, "Skill-map MUST NEVER read `$HOME`
|
|
1005
|
-
by default…".
|
|
1006
|
-
- Regression test at `src/test/global-flag-removed.test.ts`
|
|
1007
|
-
asserting Clipanion's "unknown option" error on `sm scan -g`.
|
|
1008
|
-
|
|
1009
|
-
Migration (no compat shim): pre-1.0, greenfield. Users who relied
|
|
1010
|
-
on `~/.skill-map/skill-map.db`, `~/.skill-map/settings*.json`, or
|
|
1011
|
-
`~/.skill-map/plugins/` move the files into their project
|
|
1012
|
-
(`<cwd>/.skill-map/`) or pass `--plugin-dir <path>` per invocation.
|
|
1013
|
-
Older DBs are not migrated, a fresh `sm init` regenerates without
|
|
1014
|
-
the `scope` column.
|
|
1015
|
-
|
|
1016
|
-
## User-facing
|
|
1017
|
-
|
|
1018
|
-
`-g/--global` is gone. `sm` reads only the current project
|
|
1019
|
-
(`<cwd>/.skill-map/`). To scan outside the project, add paths via
|
|
1020
|
-
`scan.extraFolders` in Settings. User-scope plugins move to
|
|
1021
|
-
`<cwd>/.skill-map/plugins/` or load with `--plugin-dir <path>`.
|
|
1022
|
-
|
|
1023
203
|
## 0.26.0
|
|
1024
204
|
|
|
1025
205
|
### Minor Changes
|
|
1026
206
|
|
|
1027
207
|
- 48800d4: Drop `requires`, `related`, and `conflictsWith` from the curated annotation catalog.
|
|
1028
208
|
|
|
1029
|
-
The three fields collapsed into the same edge kind (`references`), which made it impossible to tell from the graph whether an arrow meant "depends on", "is in conflict with", or "soft-related". The catalog now ships 10 fields instead of 13: versioning + supersession (`version`, `stability`, `supersedes`, `supersededBy`), provenance (`authors`, `license`, `source`, `sourceVersion`), taxonomy (`tags`), and docs (`docsUrl`).
|
|
1030
|
-
|
|
1031
|
-
The extractor `core/annotations` now declares `emitsLinkKinds: ['supersedes']` (no longer emits `references` from the sidecar). Path-style `references` edges still surface from `core/markdown-link` over `[text](path)` syntax in the body.
|
|
1032
|
-
|
|
1033
|
-
The schema keeps `additionalProperties: true`, so sidecars that still carry `requires` / `related` / `conflictsWith` continue to parse without errors, but those keys produce no edges and the built-in `unknown-field` analyzer surfaces them as warnings.
|
|
1034
|
-
|
|
1035
|
-
## User-facing
|
|
1036
|
-
|
|
1037
|
-
The `.sm` annotation catalog shrinks from 13 to 10 fields. `requires`, `related`, and `conflictsWith` were dropped, their edges all rendered as plain `references` and added no extra meaning. Existing sidecars keep working; the three keys are now flagged by `unknown-field`.
|
|
1038
|
-
|
|
1039
209
|
## 0.25.0
|
|
1040
210
|
|
|
1041
211
|
### Minor Changes
|
|
1042
212
|
|
|
1043
213
|
- a53532b: Replace BYTES with TOKENS in the human-mode output of `sm list` and `sm show`. Tokens are the metric users actually care about for LLM budgeting; bytes were a leftover from the early file-size mental model.
|
|
1044
214
|
|
|
1045
|
-
**CLI changes (`@skill-map/cli`)**:
|
|
1046
|
-
|
|
1047
|
-
- `sm list` table swaps the `BYTES` column for `TOKENS`. The value comes from `node.tokens?.total` (cl100k_base counts already populated by the kernel during `sm scan`). Nodes scanned with `--no-tokens` render the cell as `-`.
|
|
1048
|
-
- `sm list --sort-by bytes_total` is **removed**, renamed to `--sort-by tokens_total`. Passing the old key now fails fast with the standard "invalid sort field" error listing the allowed values. The defensive whitelist in `kernel/adapters/sqlite/storage-adapter.ts` (`SORT_BY_COLUMNS` / `SORT_BY_DEFAULT_DIRECTION`) follows the same rename.
|
|
1049
|
-
- `sm show` no longer renders the `Bytes:` field. The `Tokens:` field is now always present (`-` when the scan ran with `--no-tokens`) instead of being conditional on token availability. Field-block doc comments updated.
|
|
1050
|
-
- Help text and the `examples` array on `sm list` reworded ("Top 5 by total tokens").
|
|
1051
|
-
|
|
1052
|
-
**Untouched surfaces** (DB shape, JSON output, internal tie-breakers):
|
|
1053
|
-
|
|
1054
|
-
- `scan_nodes.bytes_*` columns stay in the schema, no migration.
|
|
1055
|
-
- `node-build.ts` still computes both `bytes` and `tokens` on every scan.
|
|
1056
|
-
- `sm list --json` and `sm show --json` keep emitting Node objects conforming to `node.schema.json`, which still carries both `bytes` and `tokens`. Only the human-mode rendering changed.
|
|
1057
|
-
- `sm export` keeps using `bytes` as the deterministic internal tie-breaker (invisible to the user).
|
|
1058
|
-
|
|
1059
|
-
**Spec change (`@skill-map/spec`)**:
|
|
1060
|
-
|
|
1061
|
-
- `spec/cli-contract.md` (`sm show` row): "weight (bytes/tokens triple-split)" → "weight (tokens triple-split)". A conforming implementation no longer has to render the `Bytes:` field on `sm show`. Pre-1.0 breaking, treated as minor per `spec/versioning.md` § Pre-1.0.
|
|
1062
|
-
|
|
1063
|
-
Tests updated: `src/test/scan-readers.test.ts` swaps `sortBy: 'bytes_total'` for `'tokens_total'` and asserts `\bTokens\b` (instead of `\bBytes\b`) in the `sm show` human output.
|
|
1064
|
-
|
|
1065
|
-
## User-facing
|
|
1066
|
-
|
|
1067
|
-
**`sm list` and `sm show` now report tokens, not bytes.** The `BYTES` column on `sm list` is now `TOKENS` (cl100k_base, frontmatter + body), and `sm show` lists `Tokens:` instead of `Bytes:`. Sort with `--sort-by tokens_total`. `--json` is unchanged.
|
|
1068
|
-
|
|
1069
215
|
- 2129b40: Add an optional positional `variant` argument to `sm tutorial`. Default (no argument) keeps the previous behaviour and materializes `<cwd>/sm-tutorial.md` (the basic walkthrough). Passing `master` materializes `<cwd>/sm-master.md` (the advanced walkthrough: plugin tour, plugin authoring, settings + view-slots) through the same channel. The value is validated against the closed set `{ tutorial, master }`; anything else exits with code 2 and an `invalidVariant` error pointing at the valid values. The build pipeline (`tsup.config.ts → onSuccess`) now copies both SKILL.md sources into `dist/cli/tutorial/`, and the runtime resolver caches each variant independently. CLI i18n strings under `tutorial.texts.ts` were parameterized with a `{{filename}}` placeholder so the success block points the tester at whichever file was materialised. Spec § `sm tutorial` was rewritten to document the new positional and exit-code rule.
|
|
1070
216
|
|
|
1071
|
-
## User-facing
|
|
1072
|
-
|
|
1073
|
-
**`sm tutorial master`** materializes the advanced tester walkthrough (`sm-master.md`) in your cwd. Trigger it from Claude Code with `ejecutá @sm-master.md`. Bare `sm tutorial` keeps its previous behaviour and writes `sm-tutorial.md`.
|
|
1074
|
-
|
|
1075
217
|
## 0.24.3
|
|
1076
218
|
|
|
1077
219
|
### Patch Changes
|
|
@@ -1090,21 +232,6 @@ kind.json}` and merges the result into the runtime Provider
|
|
|
1090
232
|
|
|
1091
233
|
- fb52d17: Migrate the monorepo's package manager from npm to pnpm 11.
|
|
1092
234
|
|
|
1093
|
-
**Motivation**: the 2025 wave of npm supply-chain attacks (Shai-Hulud worm, Qix / chalk-debug compromise, s1ngularity / Nx, axios / Glassworm) all rode on the default-on `postinstall` script behavior plus npm's flat hoisted `node_modules`. pnpm 11 ships the inverse defaults: install scripts are blocked unless explicitly allowlisted, transitive dependencies are confined to per-package symlinks, a 24-hour `minimumReleaseAge` rejects freshly published versions, and `blockExoticSubdeps` rejects git / tarball sources sneaking in through patch bumps.
|
|
1094
|
-
|
|
1095
|
-
**What changed**:
|
|
1096
|
-
|
|
1097
|
-
- `pnpm-workspace.yaml` replaces the root `workspaces` array. `.npmrc` pins the supply-chain hardening flags (`save-exact`, `strict-dep-builds`, `minimum-release-age`, `block-exotic-subdeps`). `packageManager: pnpm@11.1.1` activates pnpm via corepack on every Node 24 install.
|
|
1098
|
-
- Every package script across the 7 workspaces moves from `npm run X --workspace=Y` to `pnpm --filter Y X`. The `bff:dev`, `demo:build`, `validate:*`, `release:*` chains follow the same pattern.
|
|
1099
|
-
- `Dockerfile` swaps `npm ci` for `pnpm install --frozen-lockfile`, activates pnpm via corepack inside the Alpine image, and copies `pnpm-lock.yaml` + `pnpm-workspace.yaml` + `.npmrc`.
|
|
1100
|
-
- All three GitHub Actions workflows (`ci.yml`, `release.yml`, `deploy-web.yml`) gain a `pnpm/action-setup@v4` step and feed `cache: 'pnpm'` to `actions/setup-node`. The smoke-install job still uses `npm i -g` to verify the published tarball through the path a real end user takes.
|
|
1101
|
-
- `examples/hello-world` and `testkit` workspace deps switch from the loose `"*"` range (which pnpm resolved against the registry) to `workspace:*` so the local source is always linked.
|
|
1102
|
-
- Two phantom dependencies that npm's flat hoist had been hiding surfaced and got fixed: `src/scripts/build-reference.js` now spawns from the `src/` workspace so it finds the locally-declared `tsx`; `e2e/live-bff/server.ts` passes an absolute `file://` URL for the tsx loader since the spawn cwd is a fixture tempdir.
|
|
1103
|
-
- `ui/angular.json` rewrites the `styles` paths from `../node_modules/X` (root-flat assumption) to `node_modules/X` (workspace-local under pnpm).
|
|
1104
|
-
- The pre-commit hook, dev-reset script, contributor docs (`AGENTS.md`, `CONTRIBUTING.md`, `context/scripts.md`, `context/lint.md`, `context/spec.md`, both READMEs, `ROADMAP.md`) all reflect the new commands.
|
|
1105
|
-
|
|
1106
|
-
No user-observable change. End users still install with `npm i -g @skill-map/cli`; the CLI surface, BFF responses, and UI are byte-identical with the previous release.
|
|
1107
|
-
|
|
1108
235
|
- 56fef3b: Verify the release pipeline end-to-end after the pnpm 11 migration: `release.yml` boots through `pnpm install --frozen-lockfile`, `release:version` bumps versions and refreshes the lockfile in one shot, `release:publish` propagates the four versioned packages to npm, and `deploy-web.yml` rolls out the new public site on the post-migration `pnpm/action-setup` chain. No functional or contract change in any of the four packages, this exists purely so the next "chore: version packages" PR exercises every moving part of the new pipeline at least once.
|
|
1109
236
|
|
|
1110
237
|
## 0.24.0
|
|
@@ -1113,1553 +240,174 @@ kind.json}` and merges the result into the runtime Provider
|
|
|
1113
240
|
|
|
1114
241
|
- 2b09ce8: Restrict `node.kind` to `^[a-zA-Z][a-zA-Z0-9_-]{0,63}$` in `spec/schemas/node.schema.json`.
|
|
1115
242
|
|
|
1116
|
-
Reason (audit `app-hacker`, finding H1): the UI uses the kind name as a fragment of CSS custom-property identifiers (`--sm-kind-<name>`) injected into a global `<style>` tag. The previous `minLength: 1` floor let a Provider declare a kind containing `;`, `{`, `}`, or whitespace, which would close the declaration context and inject arbitrary CSS rules (defacement, redress, and CSS-based exfiltration via `url()`). The new pattern is a security boundary at the kernel and matches every kind declared by the built-in Claude / Gemini Providers; external Providers that already use ASCII letter / digit / `_` / `-` names are unaffected.
|
|
1117
|
-
|
|
1118
|
-
Breaking (minor pre-1.0): any external Provider emitting a kind name with characters outside this pattern is now rejected by AJV validation. Affected plugins must rename the kind to a conforming identifier.
|
|
1119
|
-
|
|
1120
243
|
## 0.23.0
|
|
1121
244
|
|
|
1122
245
|
### Minor Changes
|
|
1123
246
|
|
|
1124
247
|
- c1ed77a: Add `IAnalyzer.recommendedActions` so an Analyzer can declare which per-node Actions resolve its findings.
|
|
1125
248
|
|
|
1126
|
-
|
|
249
|
+
### Patch Changes
|
|
1127
250
|
|
|
1128
|
-
|
|
251
|
+
- 608e6ae: BFF compliance audit follow-ups (`bff-ruler` on `src/server/`).
|
|
1129
252
|
|
|
1130
|
-
|
|
253
|
+
- c2152cc: Add `--json` output to four verbs that previously emitted only human-formatted text: `sm refresh` (and `sm refresh --stale`), `sm plugins doctor`, `sm conformance run`, plus `--format json` on `sm graph` (`sm graph` uses the formatter catalog rather than the global `--json` flag). Closes the spec drift where the global `--json` flag was advertised but ignored on these verbs, and unblocks CI / scripting consumers that parse the output.
|
|
1131
254
|
|
|
1132
|
-
|
|
255
|
+
- 5f4de1c: Security audit sweep (cli-hacker follow-up). Three highs, three mediums, three lows, plus the shared prototype-pollution helper and a plugin-author doc note.
|
|
1133
256
|
|
|
1134
|
-
|
|
257
|
+
- 639a95b: Strip em dashes (`—`) from spec prose and schema descriptions.
|
|
1135
258
|
|
|
1136
|
-
|
|
259
|
+
## 0.22.0
|
|
1137
260
|
|
|
1138
|
-
###
|
|
261
|
+
### Minor Changes
|
|
1139
262
|
|
|
1140
|
-
-
|
|
263
|
+
- 39a61e9: Remove the implicit "scan HOME" surface and consolidate every out-of-project scan path under a single, explicit `scan.extraFolders` setting. Privacy-by-default: the CLI / BFF / UI never read the user's home automatically anymore; every path outside the project root must be listed by the operator.
|
|
1141
264
|
|
|
1142
|
-
|
|
265
|
+
### Patch Changes
|
|
1143
266
|
|
|
1144
|
-
|
|
1145
|
-
- `routes/plugins.ts` (`db-missing` on bulk + project list): same `DbMissingError` path.
|
|
1146
|
-
- `routes/contributions.ts` (`missing-path` 400, `unknown-contribution` 404): `HTTPException` throws with externalized messages.
|
|
1147
|
-
- `loopback-gate.ts` (`host-not-allowed` / `origin-not-allowed` 403): now `throw new LoopbackGateError({ code, message })`. `formatError` shapes it to the canonical envelope with `details: null`. The pre-baked terse message keeps the gate opaque to probes.
|
|
1148
|
-
- `routes/plugins.ts` bulk PATCH: `details: { id: <offender> }` now lives on `BulkValidationError` and is stamped centrally in `formatError` instead of inlined at each call site.
|
|
267
|
+
- 1e48d2e: Follow-up sweep on the cli-architect spec-drift audit. Three pieces.
|
|
1149
268
|
|
|
1150
|
-
|
|
269
|
+
## 0.21.0
|
|
1151
270
|
|
|
1152
|
-
|
|
271
|
+
### Minor Changes
|
|
1153
272
|
|
|
1154
|
-
|
|
273
|
+
- f72dbfc: Card body + topbar polish, plus catalog rename of the topbar scope slot.
|
|
1155
274
|
|
|
1156
|
-
|
|
1157
|
-
- `IContributionsRegistryEntry` declared twice with drift on `priority?`. One canonical declaration in `envelope.ts` with the field; `contributions-registry.ts` re-exports it.
|
|
1158
|
-
- `ServerHandle` → `IServerHandle` (consistency with the rest of the `I*` interface convention).
|
|
275
|
+
- 5ed14cb: Disabling a plugin now wipes its `scan_contributions` rows immediately, instead of waiting for the next `sm scan` to sweep them. Without the eager purge, the catalog sweep documented in `db-schema.md` § scan_contributions only ran on the next scan, so the UI kept rendering the plugin's footer / card chips even though the toggle showed `enabled: false`.
|
|
1159
276
|
|
|
1160
|
-
|
|
277
|
+
- fe13254: Tighten the manifest `icon` grammar on `viewContributions[].icon` from "single emoji-or-PrimeIcons-bare-name" to a prefix-discriminated string with four explicit shapes. Greenfield migration: no compat shim, no `catalogCompat` bump, bare names now fail at manifest load.
|
|
1161
278
|
|
|
1162
|
-
-
|
|
279
|
+
- 4f89a84: Plugin toggles in the Settings modal now apply at the next scan instead of needing an `sm serve` restart. The "Restart required" banner is gone for the common case; only plugins that were disabled at server boot keep a per-row warning because their handlers were never loaded into memory.
|
|
1163
280
|
|
|
1164
|
-
|
|
281
|
+
- b840302: Rename the view slot `card.footer.left.counter` to `card.footer.left`.
|
|
1165
282
|
|
|
1166
|
-
|
|
1167
|
-
- `plugins-doctor.schema.json`, `{ ok: true, kind: 'plugins.doctor', counts, issues[], warnings[], elapsedMs }`. `counts` collapses the raw discovery enum into the four error buckets (`loaded` / `incompatible` / `invalid` / `loadError`) so consumers do not have to track the kernel-side label catalog.
|
|
1168
|
-
- `conformance-result.schema.json`, `{ ok: true, kind: 'conformance.result', totals, scopes[], elapsedMs }`. Error envelope codes: `bad-query` (unknown scope), `internal` (missing binary). A run that surfaces failing cases still returns `ok: true`; failures live under `scopes[].cases[].status === 'fail'` and gate the exit code.
|
|
283
|
+
- a96c257: Add a per-project consent gate for `.sm` sidecar writes, generalise the "privacy-sensitive, must not be committed" idea to a closed set of project-local-only keys, and cache config on the daemon so repeated reads in `sm serve` no longer re-walk six file layers.
|
|
1169
284
|
|
|
1170
|
-
|
|
285
|
+
## 0.20.0
|
|
1171
286
|
|
|
1172
|
-
|
|
287
|
+
### Minor Changes
|
|
1173
288
|
|
|
1174
|
-
|
|
289
|
+
- a1bfe15: Eliminate the view-contribution `contract` abstraction — plugin authors now pick `slot` directly.
|
|
1175
290
|
|
|
1176
|
-
|
|
291
|
+
- 5600a60: Hook trigger set grows from 8 to 10: add CLI-process-driven `boot` and `shutdown`. First built-in concrete consumer: `core/update-check` (the once-per-day update banner moves from an inline call site to a hook subscribing to `boot`).
|
|
1177
292
|
|
|
1178
|
-
-
|
|
293
|
+
- 802e64f: Rename the `rule` plugin extension kind to `analyzer`.
|
|
1179
294
|
|
|
1180
|
-
|
|
1181
|
-
- **H2 / L2** — Sidecar `deepMerge` + `readSidecarFor` parse strip `__proto__` / `constructor` / `prototype` keys at every depth. Shared helper in `kernel/util/strip-prototype-pollution.ts` (also adopted by `kernel/config/loader.ts`).
|
|
1182
|
-
- **H3** — Bumped `hono` to 4.12.18 and `kysely` to 0.28.17. Added a root `overrides.fast-uri: 3.1.2` to lift the transitive past the path-traversal advisories. Lockfile regenerated.
|
|
1183
|
-
- **M1** — Settings + sidecar atomic writes now land mode 0o600 (matches `db restore`'s discipline).
|
|
1184
|
-
- **M2** — `sm job prune` rejects `unlink()` on paths that don't stay inside `<scope>/.skill-map/jobs/`.
|
|
1185
|
-
- **M3** — Orphan-files walker skips symlinks (parity with the scan + reference walkers).
|
|
1186
|
-
- **L1** — Sidecar temp filename embeds `pid` + timestamp (cross-process race window).
|
|
1187
|
-
- **L3** — `fetchLatestVersion` rejects registry responses whose `version` is not a semver-shaped string.
|
|
1188
|
-
- **L5** — Two BFF error envelopes on `/api/contributions/*` sanitize URL params before interpolation.
|
|
1189
|
-
- **L4** — Plugin author guide spells out that module top-level side effects survive an `import()` timeout, so plugins must do their work inside lifecycle methods.
|
|
1190
|
-
|
|
1191
|
-
## User-facing
|
|
1192
|
-
|
|
1193
|
-
`sm serve` now rejects browser requests whose `Host` or `Origin` is not a loopback name. Closes a DNS-rebinding lane where a malicious page could trigger scans or settings writes. `--dev-cors` still works for Vite-style dev UIs on a different loopback port.
|
|
1194
|
-
|
|
1195
|
-
- 639a95b: Strip em dashes (`—`) from spec prose and schema descriptions.
|
|
1196
|
-
|
|
1197
|
-
Stylistic sweep across `spec/*.md` (architecture, cli-contract, db-schema, job-events, job-lifecycle, plugin-author-guide, plugin-kv-api, prompt-preamble, versioning, view-slots, input-types, interfaces/security-scanner, conformance/README.md, conformance/coverage.md, README.md) and `spec/schemas/**/*.json` description fields. Each em dash is replaced with a comma, colon, semicolon, or parenthetical pair chosen to read naturally in context.
|
|
1198
|
-
|
|
1199
|
-
`spec/index.json` regenerated so the integrity hashes line up with the new content. No normative changes: schema keys, enum values, type definitions, required-field sets are all unchanged. Conformance fixtures and `CHANGELOG.md` historical snapshots are deliberately untouched.
|
|
1200
|
-
|
|
1201
|
-
## 0.22.0
|
|
1202
|
-
|
|
1203
|
-
### Minor Changes
|
|
1204
|
-
|
|
1205
|
-
- 39a61e9: Remove the implicit "scan HOME" surface and consolidate every out-of-project scan path under a single, explicit `scan.extraFolders` setting. Privacy-by-default: the CLI / BFF / UI never read the user's home automatically anymore; every path outside the project root must be listed by the operator.
|
|
1206
|
-
|
|
1207
|
-
**Removed**
|
|
1208
|
-
|
|
1209
|
-
- `scan.includeHome` (project config boolean). The toggle that appended every Provider's HOME path is gone.
|
|
1210
|
-
- `explorationDir` on the Provider manifest. Built-in providers (`claude`, `gemini`, `agent-skills`, `core-markdown`) no longer declare it; the field is dropped from `spec/schemas/extensions/provider.schema.json`. Each Provider's walker hardcodes the project-relative paths it cares about (e.g. `.claude/`, `.gemini/`, `.agents/`).
|
|
1211
|
-
- `sm scan -g` / `sm scan --global`. The scan verb no longer accepts the global scope flag (there is no global scan surface once HOME auto-inclusion is gone). Other verbs (`config`, `db`, `plugins`, `init`, …) keep their `-g` flag — those point at `~/.skill-map/` (skill-map's own data dir), not at scanned content.
|
|
1212
|
-
- `sm plugins doctor` no longer emits the `explorationDir missing` warning.
|
|
1213
|
-
|
|
1214
|
-
**Renamed**
|
|
1215
|
-
|
|
1216
|
-
- `scan.extraRoots` → `scan.extraFolders` (same shape `string[]`, same semantics — clearer name in the Settings UI and config). Privacy-sensitive: writes that add out-of-project paths still require `--yes` on the CLI and a confirm dialog in the UI.
|
|
1217
|
-
|
|
1218
|
-
**BFF**
|
|
1219
|
-
|
|
1220
|
-
- `GET /api/project-preferences` response now returns `{ scan: { extraFolders, referencePaths } }` (dropped `includeHome`, renamed `extraRoots`).
|
|
1221
|
-
- `PATCH /api/project-preferences` accepts the same shape; `additionalProperties: false` still applies.
|
|
1222
|
-
|
|
1223
|
-
**UI**
|
|
1224
|
-
|
|
1225
|
-
- Settings → Project section drops the "Include HOME folders" toggle; only the "Extra folders to scan" list and "Folders for link validation" list remain.
|
|
1226
|
-
|
|
1227
|
-
**Greenfield migration**
|
|
1228
|
-
|
|
1229
|
-
No backwards-compat shim. Users with `scan.includeHome: true` or `scan.extraRoots: [...]` in `<cwd>/.skill-map/settings.local.json` (or `~/.skill-map/settings.json`) need to manually rename `extraRoots` → `extraFolders` and, if they want to keep HOME scanning, list the specific paths they care about (e.g. `~/.claude/agents`) in `scan.extraFolders` — instead of opting into "everything under HOME" at once.
|
|
1230
|
-
|
|
1231
|
-
## User-facing
|
|
1232
|
-
|
|
1233
|
-
The "include HOME" toggle is gone. To scan paths outside the project, list them in **Extra folders to scan** (renamed from _Extra roots_). If you had `scan.includeHome: true`, add the paths you actually need (e.g. `~/.claude/agents`) — not one click anymore.
|
|
1234
|
-
|
|
1235
|
-
### Patch Changes
|
|
1236
|
-
|
|
1237
|
-
- 1e48d2e: Follow-up sweep on the cli-architect spec-drift audit. Three pieces:
|
|
1238
|
-
|
|
1239
|
-
- **5a — plugin loader status alignment.** The loader now returns `invalid-manifest` (not `load-error`) when the exported extension shape fails its kind-specific AJV schema. Aligns with `spec/architecture.md` §Plugin discovery: "AJV rejects unknown `slot` names with `invalid-manifest`". The module imported fine; only the declared shape is wrong, so `invalid-manifest` is the semantically correct status (`load-error` is for genuine module-load failures: import threw, timeout, unknown kind). Renames `PLUGIN_LOADER_TEXTS.loadErrorManifestInvalid` → `invalidManifestExtensionShape` to match. 4 tests updated.
|
|
1240
|
-
|
|
1241
|
-
- **7 — `emitScopeContribution` docs alignment.** Added a "pending, not yet implemented" status note to `spec/view-slots.md` and `spec/plugin-author-guide.md`. The two author-facing docs previously showed the callback as if it existed; `spec/architecture.md` already says it's "reserved, lands when the first scope-level adopter arrives". A plugin author who copies the example now sees the caveat upfront instead of hitting `TypeError: ctx.emitScopeContribution is not a function` at runtime.
|
|
1242
|
-
|
|
1243
|
-
- **P2 cosmetic prose sweep.** Slot-count references corrected ("15 slots" → "14" — the closed enum has 14 entries since the topbar scope-slot rename); `IViewContribution` field count corrected ("six fields" → "seven" — `priority?` was declared in the schema since the beginning but never documented in prose). Three spec docs swept; `spec/index.json` regenerated.
|
|
1244
|
-
|
|
1245
|
-
`catalogCompat` (5b in the audit) — schema field declared but loader check not implemented — is deferred until catalog v2 evolution demands it. No catalog evolution is pending pre-1.0, so the gap is acceptable; flagged in audit follow-ups, not in this changeset.
|
|
1246
|
-
|
|
1247
|
-
## 0.21.0
|
|
1248
|
-
|
|
1249
|
-
### Minor Changes
|
|
1250
|
-
|
|
1251
|
-
- f72dbfc: Card body + topbar polish, plus catalog rename of the topbar scope slot.
|
|
1252
|
-
|
|
1253
|
-
**New extractor (`core/tools-count`)** — `src/built-in-plugins/extractors/tools-count/`. Reads `frontmatter.tools[]` on agent-kind nodes (Claude + Gemini share the field shape) and emits a `card.footer.left` counter chip with a wrench icon. Replaces the hardcoded wrench block previously rendered straight from `<sm-node-card>` (`toolsCount()` computed + `effectiveToolsCount` / `effectiveToolsBreakdown` helpers, all removed). `applicableKinds: ['agent']` gates the run at load time so skill / command / markdown nodes pay zero cost. Tooltip carries the joined tool names (capped at the 256-char slot limit).
|
|
1254
|
-
|
|
1255
|
-
**Provider kind visuals normalised** — `src/built-in-plugins/providers/gemini/index.ts` and `agent-skills/index.ts`. Every Provider that contributes `agent` / `skill` / `command` now declares the same label + color + icon as Claude. The declaration STAYS per-Provider (the shape allows divergence the day a Provider wants its own identity for a kind), but today the values mirror Claude so the visual vocabulary is uniform regardless of where a node was sourced from. `<sm-kind-icon>` gains an optional `provider` input that resolves the icon per-Provider when the call site supplies one (today a no-op, ready to diverge tomorrow).
|
|
1256
|
-
|
|
1257
|
-
**Slot catalog rename + relocate** — `topbar.actions.indicator` → `topbar.nav.start`. The slot moved from the topbar actions cluster (right side, between refresh / theme / settings) to the start of the topbar nav (left of the view-switcher links). The rename is a catalog-major-bump for any external plugin that emitted to the old name (pre-1.0 → ships as a `@skill-map/spec` minor per the versioning policy). Sweep covers `spec/schemas/view-slots.schema.json` (closed enum), `spec/view-slots.md`, `spec/architecture.md`, `spec/plugin-author-guide.md`, `src/kernel/types/view-catalog.ts`, `src/kernel/adapters/schema-validators.ts`, `src/built-in-plugins/analyzers/unknown-slot/index.ts`, `src/cli/commands/plugins.ts`, `ui/src/app/slots/slot-config.ts`, `ui/src/app/slots/slot-renderer-map.ts`, `ui/src/app/app.html`, `ui/src/app/renderers/scope-stat/scope-stat.ts`, `ui/src/app/debug-slots.css`, `context/view-slots.md`, `ROADMAP.md`. Spec integrity regenerated.
|
|
1258
|
-
|
|
1259
|
-
**View-contribution wrapper transparent to layout** — `ui/src/app/debug-slots.css`. `.sm-debug-slot` and `<sm-view-contributions-host>` are `display: contents` in production mode, so a slot that has no contributions takes zero space (no flex gap, no empty box). Debug mode flips both back to `inline-flex` for the visual ring + label.
|
|
1260
|
-
|
|
1261
|
-
**Provider chip in card subtitle** — `ui/src/services/provider-ui.ts` (new) + render in `<sm-node-card>`. Hardcoded chip carrying the provider's display label, color-coded per Provider so the platform a node came from reads at a glance. Unlike kind visuals (normalised), provider visuals are deliberately distinct. The `markdown` Provider is hidden (universal fallback — every generic `.md` lands there, painting the chip would be visual noise). Today the registry is a static UI-side map; promotes to a kernel-side `IProvider.ui` field the day a user-plugin Provider needs to declare its own chip.
|
|
1262
|
-
|
|
1263
|
-
**Path row in expanded card** — `ui/src/app/components/node-card/node-card.html`. Mono row at the top of `.sm-gnode__panel`, above the description and the LLM cluster. Subtle background, ellipsis on the leading segments (RTL trick) so the file name stays visible on long paths.
|
|
1264
|
-
|
|
1265
|
-
**Stat chip colors decoupled from `--sm-kind-*`** — `ui/src/styles.css` declares `--sm-stat-tokens-bg` / `--sm-stat-bytes-bg` / `--sm-stat-date-bg` (light + dark). Previously the chip backgrounds borrowed `--sm-kind-agent` / `--sm-kind-command` / `--sm-kind-skill`, which evaporate when their primary Provider plugin is disabled. Physical stats are plugin-independent — the new tokens keep the chips colored regardless of which plugins contribute kinds.
|
|
1266
|
-
|
|
1267
|
-
**Favorite star (was heart)** — every favorite affordance flips from `pi-heart` / `pi-heart-fill` to `pi-star` / `pi-star-fill`: `<sm-node-card>`, `<sm-inspector-view>`, `<app-kind-palette>` (favorites toggle), `<app-filter-bar>` (favorites toggle). Spec describes match updated.
|
|
1268
|
-
|
|
1269
|
-
**Author tag chips inherit the card's kind accent** — `node-card.css`. Outline color + text color come from `var(--accent)` (the kind's primary color, overridden per-Provider by `providerAccent`) instead of the theme's violet primary. Each card paints author tags in its own kind color.
|
|
1270
|
-
|
|
1271
|
-
## User-facing
|
|
1272
|
-
|
|
1273
|
-
Expanded node cards now show the file path above the description and a provider chip (Claude, Gemini, Open Skills). Favorite toggle uses a star instead of a heart.
|
|
1274
|
-
|
|
1275
|
-
- 5ed14cb: Disabling a plugin now wipes its `scan_contributions` rows immediately, instead of waiting for the next `sm scan` to sweep them. Without the eager purge, the catalog sweep documented in `db-schema.md` § scan_contributions only ran on the next scan, so the UI kept rendering the plugin's footer / card chips even though the toggle showed `enabled: false`.
|
|
1276
|
-
|
|
1277
|
-
Both toggle paths converge on the same purge:
|
|
1278
|
-
|
|
1279
|
-
- CLI — `sm plugins disable <id>` and `sm plugins disable --all` (`TogglePluginsBase.toggle` in `src/cli/commands/plugins.ts`).
|
|
1280
|
-
- BFF — `PATCH /api/plugins/:id` and `PATCH /api/plugins/:bundleId/extensions/:extensionId` (the UI's Settings → Plugins toggle).
|
|
1281
|
-
|
|
1282
|
-
Each call to `pluginConfig.set(id, false)` is followed by `adapter.contributions.purgeByPlugin(pluginId, extensionId?)`. `extensionId` is omitted for bundle-granularity ids (`claude`) and supplied for qualified ids (`core/slash`), mirroring how the catalog sweep groups rows. Re-enabling does NOT restore the rows — the next scan re-emits them, same as a cold start.
|
|
1283
|
-
|
|
1284
|
-
Plugin-managed state (`state_plugin_kvs`, dedicated `plugin_<id>_*` tables) is **not** touched. The asymmetry is intentional: contributions are scan-derived (cheap to recompute, must reflect the live catalog), KV / dedicated-table state is plugin-managed and must survive toggle cycles. See `spec/plugin-kv-api.md` and `spec/db-schema.md` for the contract.
|
|
1285
|
-
|
|
1286
|
-
**Spec changes** (`@skill-map/spec` minor — new method on `StoragePort.contributions`):
|
|
1287
|
-
|
|
1288
|
-
- `spec/architecture.md` § View contribution system → Persistence — catalog sweep now narrowed to "uninstalled-on-disk plugins, removed contributions"; eager-purge-on-disable documented as the primary path for disabled bundles.
|
|
1289
|
-
- `spec/db-schema.md` § `scan_contributions` — same narrowing; new "Eager purge on disable" subsection describing `purgeByPlugin(pluginId, extensionId?)`.
|
|
1290
|
-
- `spec/cli-contract.md` § Plugins — `sm plugins disable` row mentions the immediate purge.
|
|
1291
|
-
- `spec/plugin-author-guide.md` § Plugin states — `disabled` row mentions the immediate purge.
|
|
1292
|
-
- `spec/plugin-kv-api.md` § Backup and retention — clarifies the asymmetry between `scan_contributions` (purged) and KV / dedicated tables (preserved).
|
|
1293
|
-
|
|
1294
|
-
**Implementation** (`@skill-map/cli` patch):
|
|
1295
|
-
|
|
1296
|
-
- `src/kernel/adapters/sqlite/contributions.ts` — `purgeContributionsByPlugin(db, pluginId, extensionId?)` now optionally narrows by extension.
|
|
1297
|
-
- `src/kernel/ports/storage.ts` — `StoragePort.contributions.purgeByPlugin(pluginId, extensionId?)` added to the contract.
|
|
1298
|
-
- `src/kernel/adapters/sqlite/storage-adapter.ts` — wires the namespace method to the helper.
|
|
1299
|
-
- `src/cli/commands/plugins.ts` — toggle base class calls the purge when `enabled === false`.
|
|
1300
|
-
- `src/server/routes/plugins.ts` — `persistAndProject` calls the purge when `enabled === false`.
|
|
1301
|
-
- `ui/src/app/components/settings-modal/settings-plugins.ts` — after a successful UI toggle, calls `CollectionLoaderService.load()` so the cached in-memory `node.contributions[]` is refreshed against the just-purged DB and the card chips disappear without the user pressing Refresh. The loader's existing `pendingRefresh` collapsing semantics handle back-to-back toggles cheaply.
|
|
1302
|
-
|
|
1303
|
-
**Tests**:
|
|
1304
|
-
|
|
1305
|
-
- `src/test/view-contributions.test.ts` — new unit test asserting `purgeByPlugin` narrows by `extensionId` when supplied.
|
|
1306
|
-
- `src/test/plugins-cli.test.ts` — new end-to-end test asserting `sm plugins disable <id>` drops the plugin's `scan_contributions` rows while leaving unrelated plugin rows untouched.
|
|
1307
|
-
- `ui/src/app/components/settings-modal/settings-plugins.spec.ts` — new test asserting the toggle handler calls `CollectionLoaderService.load()` so the card chips reflect the BFF purge. (The pre-existing `settings-plugins.spec.ts` suite is currently broken on `main` for unrelated reasons — `verifySemanticsOfNgModuleDef` Angular DI failure across 24 UI test files — but the new test is correctly written and will activate once that suite is fixed.)
|
|
1308
|
-
|
|
1309
|
-
## User-facing
|
|
1310
|
-
|
|
1311
|
-
Disabling a plugin now removes its card chips from the UI immediately. Previously the chips lingered until the next `sm scan`, making the toggle look broken.
|
|
1312
|
-
|
|
1313
|
-
- fe13254: Tighten the manifest `icon` grammar on `viewContributions[].icon` from "single emoji-or-PrimeIcons-bare-name" to a prefix-discriminated string with four explicit shapes. Greenfield migration: no compat shim, no `catalogCompat` bump, bare names now fail at manifest load.
|
|
1314
|
-
|
|
1315
|
-
**Spec (`@skill-map/spec`) — `view-slots.schema.json#/$defs/IconString`**
|
|
1316
|
-
|
|
1317
|
-
The `IconString` `$def` gains a `pattern` enforcing the new grammar and an updated `description`:
|
|
1318
|
-
|
|
1319
|
-
```
|
|
1320
|
-
^(?:pi pi-[a-z0-9-]+|pi-[a-z0-9-]+|fa-(?:solid|regular|brands) fa-[a-z0-9-]+|fa-[a-z0-9-]+|[^a-zA-Z].*)$
|
|
1321
|
-
```
|
|
1322
|
-
|
|
1323
|
-
Four valid shapes:
|
|
1324
|
-
|
|
1325
|
-
1. **Emoji** — any value starting with a non-ASCII-letter codepoint (`'🔍'`, `'@'`) renders as text.
|
|
1326
|
-
2. **PrimeIcons** — `'pi-foo'` or `'pi pi-foo'` (both accepted) → `<i class="pi pi-foo">`.
|
|
1327
|
-
3. **FontAwesome explicit family** — `'fa-solid fa-foo'` / `'fa-regular fa-foo'` / `'fa-brands fa-foo'` → pass-through.
|
|
1328
|
-
4. **FontAwesome shorthand** — `'fa-foo'` → defaults to `<i class="fa-solid fa-foo">`.
|
|
1329
|
-
|
|
1330
|
-
Bare class names without a `pi-` / `fa-` prefix (`'star-fill'`, `'search'`, `'arrow-down'`) are **rejected at manifest load with `invalid-manifest`**. Prose contract in `spec/view-slots.md` §Icon string and `spec/plugin-author-guide.md` (icon row of the field reference table + new "Icon string forms" subsection) updated to match. `spec/index.json` regenerated.
|
|
1331
|
-
|
|
1332
|
-
**Greenfield path — no shim, no version flag**
|
|
1333
|
-
|
|
1334
|
-
Per `AGENTS.md` `Greenfield = no schema versioning`: no released external plugin uses the bare-name shape (the built-ins are the only consumers and ship in the same repo), so we tighten the contract in place. No `catalogCompat` bump on the catalog, no migration step registered in `sm plugins upgrade`. The bare-name rejection is documented inline in `IconString.description`.
|
|
1335
|
-
|
|
1336
|
-
**Kernel (`@skill-map/cli`) — built-in migration**
|
|
1337
|
-
|
|
1338
|
-
Every built-in extractor / analyzer that declared a bare-name icon is rewritten to `pi-foo` so it passes the new pattern at load:
|
|
1339
|
-
|
|
1340
|
-
- `src/built-in-plugins/extractors/stability/index.ts` — `bolt`/`ban` → `pi-bolt`/`pi-ban`
|
|
1341
|
-
- `src/built-in-plugins/extractors/tools-count/index.ts` — `wrench` → `pi-wrench`
|
|
1342
|
-
- `src/built-in-plugins/extractors/slash/index.ts` — `arrow-down` → `pi-arrow-down`
|
|
1343
|
-
- `src/built-in-plugins/extractors/at-directive/index.ts` — `arrow-down` → `pi-arrow-down`
|
|
1344
|
-
- `src/built-in-plugins/extractors/markdown-link/index.ts` — `arrow-down` → `pi-arrow-down`
|
|
1345
|
-
- `src/built-in-plugins/extractors/external-url-counter/index.ts` — `link` → `pi-link`
|
|
1346
|
-
- `src/built-in-plugins/analyzers/broken-ref/index.ts` — `times-circle` ×3 → `pi-times-circle` (manifest `alert` + `chip` + runtime payload)
|
|
1347
|
-
- `src/built-in-plugins/analyzers/unknown-field/index.ts` — `info-circle` ×3 → `pi-info-circle` (same shape)
|
|
1348
|
-
- `src/built-in-plugins/analyzers/annotation-stale/index.ts` — `clock` → `pi-clock`
|
|
1349
|
-
|
|
1350
|
-
Sibling test assertions updated in lock-step (`stability.test.ts`, `tools-count.test.ts`, `broken-ref.test.ts`, `unknown-field.test.ts`, `annotation-stale.test.ts`).
|
|
1351
|
-
|
|
1352
|
-
**UI — resolver + rename**
|
|
1353
|
-
|
|
1354
|
-
The shared icon component is renamed and the inline resolver pulled out into a pure function:
|
|
1355
|
-
|
|
1356
|
-
- `ui/src/app/slots/icon-glyph.ts` → DELETED.
|
|
1357
|
-
- `ui/src/app/slots/icon.ts` — new file: exports `resolveIcon(raw: string | undefined): TResolvedIcon | null` (pure, no Angular deps) and the `Icon` component (`selector: 'sm-icon'`). The resolver routes on the same prefix grammar the AJV pattern enforces (emoji / `pi-foo` / `pi pi-foo` / `fa-{family} fa-foo` / `fa-foo`); unknown shapes return `null`, which renders nothing and emits a `console.warn` naming the offending value (covers runtime corruption from a legacy persisted row or a hand-edited sidecar that bypassed the load-time AJV gate). Template emits `<span>` for emoji and `<i class="<resolved cls>">` for `pi` / `fa`; the same 1px `transform: translateY` nudge from the previous `IconGlyph` survives unchanged.
|
|
1358
|
-
- `ui/src/app/slots/icon.spec.ts` — new spec, 21 vitest tests over the branch matrix: empty / nullish input, emoji (single + ZWJ + ASCII punctuation), PrimeIcons shorthand + full class, FontAwesome explicit family (solid / regular / brands), FontAwesome shorthand, and rejected inputs (bare names, family-only, missing space, uppercase prefix, trim semantics). Pure function tested directly — no TestBed, because the existing TestBed setup is broken upstream of this work.
|
|
1359
|
-
- `ui/src/app/renderers/{node-counter,node-icon,node-alert,scope-stat}/*.ts` — import + selector update: `IconGlyph` → `Icon`, `<sm-icon-glyph>` → `<sm-icon>`. No template logic changed.
|
|
1360
|
-
|
|
1361
|
-
**Why one commit**
|
|
1362
|
-
|
|
1363
|
-
The spec, built-ins, and UI changes form one contract change. Splitting puts the spec ahead of the built-ins (AJV would reject every built-in manifest at load) or the UI ahead of the spec (UI would resolve shapes the spec hasn't sanctioned yet). Single commit keeps the tree green at every hash.
|
|
1364
|
-
|
|
1365
|
-
**Verification**
|
|
1366
|
-
|
|
1367
|
-
- `npm test` in `src/` → 1333/1333 pass (every built-in test asserts the new `pi-foo` shape).
|
|
1368
|
-
- `npx vitest run src/app/slots/icon.spec.ts` in `ui/` → 21/21 pass.
|
|
1369
|
-
- `npx tsc --noEmit -p tsconfig.app.json` in `ui/` → exit 0 (renamed selector + import wired through every renderer).
|
|
1370
|
-
- `npm run validate --workspace=@skill-map/spec` → spec OK, integrity OK.
|
|
1371
|
-
|
|
1372
|
-
## User-facing
|
|
1373
|
-
|
|
1374
|
-
**Plugin manifest icons are now prefix-discriminated.** Use `pi-foo` (PrimeIcons), `fa-solid fa-foo` / `fa-regular fa-foo` / `fa-brands fa-foo` (FontAwesome), `fa-foo` shorthand (defaults to solid), or any emoji. Bare names like `"search"` are rejected at load.
|
|
1375
|
-
|
|
1376
|
-
- 4f89a84: Plugin toggles in the Settings modal now apply at the next scan instead of needing an `sm serve` restart. The "Restart required" banner is gone for the common case; only plugins that were disabled at server boot keep a per-row warning because their handlers were never loaded into memory.
|
|
1377
|
-
|
|
1378
|
-
**Two issues addressed:**
|
|
1379
|
-
|
|
1380
|
-
1. **Latent bug — `POST /api/scan` ignored mid-session toggles.** `runScanForCommand` reused the BFF's boot-cached `pluginRuntime.resolveEnabled`. A user who disabled a plugin and pressed the topbar refresh saw the plugin's contributions reappear. The watcher had the same problem on every chokidar batch (it loads its own bundle once at boot).
|
|
1381
|
-
2. **No way to cancel.** Each toggle wrote to `config_plugins` immediately and purged `scan_contributions`. Five quick toggles meant five DB round-trips and five purges even if the net state was unchanged.
|
|
1382
|
-
|
|
1383
|
-
**Approach** — four layered changes:
|
|
1384
|
-
|
|
1385
|
-
- **Fresh resolver per scan.** `composeScanExtensions` / `composeFormatters` / `registerEnabledExtensions` now accept an optional `resolveEnabled` override. The BFF's `POST /api/scan` and the watcher's per-batch loop build a fresh resolver from `config_plugins` via the shared `core/runtime/fresh-resolver.ts` helper before composing extensions, so a toggle made mid-session is honoured on the next scan without restarting the server. Plugin user extensions are now filtered by the same resolver (previously only built-ins were filtered) so disabling a previously-enabled drop-in plugin actually silences it.
|
|
1386
|
-
- **Boot-time registries cover every built-in.** `kindRegistry` and `contributionsRegistry` (the catalogs embedded in every payload-bearing envelope) used to be seeded from the boot-time `composeScanExtensions(...)` result, which excluded any built-in that started disabled. Re-enabling such a built-in mid-session left its kinds / footer icons unrenderable because the UI's lookup tables never knew about them. Both registries now seed unconditionally from every built-in declaration (their module code is always in memory via `built-in-bundles.ts`); the enabled / disabled axis stays enforced at scan-time by the fresh resolver. Drop-in user plugins still respect boot-time filtering at the registry level — their modules weren't imported and aren't reachable mid-session (the `startsAsDisabled` exception below).
|
|
1387
|
-
- **Bulk endpoint `PATCH /api/plugins`.** Body `{ "changes": [{ id, enabled }, ...] }`. Validates the entire batch up-front (404 / 400 / 403 with `error.details.id` pointing at the offending entry); applies in one SQLite transaction with one grouped contributions purge. The per-id `PATCH /api/plugins/:id` and qualified-id sibling stay available for CLI / external automation.
|
|
1388
|
-
- **Buffered Settings modal.** Toggles mutate an in-memory `pendingState` only; rows show a dirty dot, a "N unsaved changes" banner appears above the list, and the footer exposes `[Discard] [Apply]` plus an italic warning when the dirty set re-enables a `startsAsDisabled` plugin. Closing the modal with pending edits opens a confirm dialog (`Discard` / `Keep editing` / `Apply`). Apply ships the bulk PATCH and triggers a scan via the new shared `ScanTriggerService`. A successful apply emits the panel's `applied` output, which the modal host translates into `visibleChange(false)` so the dialog closes once the work is done; a failed apply keeps the modal open with the error visible.
|
|
1389
|
-
|
|
1390
|
-
**`startsAsDisabled` wire flag.** `GET /api/plugins` rows now carry `startsAsDisabled?: boolean` for drop-in plugins whose discovery-time `status === 'disabled'`. The SPA renders a per-row hint when the user re-enables such a row, since those plugins' handlers were never loaded into memory at boot and re-engaging needs an `sm serve` restart. Built-ins always omit the flag (their handlers are statically known).
|
|
1391
|
-
|
|
1392
|
-
**Spec changes** (`@skill-map/spec` minor):
|
|
1393
|
-
|
|
1394
|
-
- `spec/cli-contract.md` § `GET /api/plugins` — adds `startsAsDisabled?: boolean` to the item shape.
|
|
1395
|
-
- `spec/cli-contract.md` § `PATCH /api/plugins/:id` and the qualified-id sibling — "Restart required" is gone; replaced by an "Apply window" sentence documenting the per-scan-fresh-resolver behaviour and the `startsAsDisabled` exception.
|
|
1396
|
-
- `spec/cli-contract.md` § Endpoints — new `PATCH /api/plugins` row documenting the bulk endpoint (body, error mapping, transactional semantics).
|
|
1397
|
-
- `spec/cli-contract.md` § Error code sources — `not-found` / `bad-query` / `locked` rows updated to mention the bulk endpoint's `error.details.id` payload.
|
|
1398
|
-
- `spec/cli-contract.md` § `kindRegistry` envelope field — clarifies that built-in Providers are listed unconditionally regardless of boot-time enabled state, and adds a parallel `contributionsRegistry` envelope-field section with the same discipline.
|
|
1399
|
-
|
|
1400
|
-
**Implementation** (`@skill-map/cli` minor):
|
|
1401
|
-
|
|
1402
|
-
- `src/core/runtime/fresh-resolver.ts` — **NEW**. Shared `buildFreshResolver` + `composeResolver` helpers used by `routes/plugins.ts`, `routes/scan.ts`, and `core/watcher/runtime.ts`.
|
|
1403
|
-
- `src/core/runtime/plugin-runtime.ts` — `composeScanExtensions`, `composeFormatters`, `registerEnabledExtensions` accept `resolveEnabled?`; user-plugin extensions, manifests, annotation contributions, and view contributions are filtered by the resolver.
|
|
1404
|
-
- `src/core/runtime/scan-runner.ts` — `IScanRunOpts.resolveEnabledOverride?` threaded into the compose call.
|
|
1405
|
-
- `src/server/routes/scan.ts` — builds the fresh resolver per `POST` / `?fresh=1`.
|
|
1406
|
-
- `src/server/routes/plugins.ts` — new `PATCH /api/plugins` bulk handler with `validateBulkChange` + `persistBulkAndProject`; `IPluginListItem` gains `startsAsDisabled`; `applyChangeToAdapter` shared between single-id and bulk paths.
|
|
1407
|
-
- `src/server/index.ts` — `assembleBootBundle` seeds the `kindRegistry` from every built-in Provider (new `collectBuiltInProviders` helper) and `mergeBuiltInViewContributions` now walks `builtInBundles` directly instead of the composed scan extension set, so both registries cover the full built-in surface regardless of boot-time enabled state.
|
|
1408
|
-
- `src/core/watcher/runtime.ts` — fresh resolver built per chokidar batch.
|
|
1409
|
-
- `ui/src/app/services/scan-trigger.ts` — **NEW**. Owns the manual-scan trigger (in-flight signal, `dataSource.runScan()` + `loader.load()`). Consumed by `App` and `SettingsPlugins`.
|
|
1410
|
-
- `ui/src/services/data-source/{port,rest-data-source,static-data-source}.ts` — new `applyPluginChanges(changes)` method.
|
|
1411
|
-
- `ui/src/app/components/settings-modal/settings-plugins.ts/.html/.css` — buffered state (`originalState` / `pendingState`), dirty markers, `[Discard] [Apply]` footer, per-row + footer italic `startsAsDisabled` hints, removal of the persistent "Restart required" banner, `applied` output for parent-driven close. Two-zone layout (`.settings-plugins__scroll` + footer outside the scroll container) so the footer doesn't expose scroll-through gaps.
|
|
1412
|
-
- `ui/src/app/components/settings-modal/settings-modal.ts/.html` — intercepts dialog close; opens `<p-confirmDialog>` with three actions when pending edits exist; bridges the panel's `applied` event to `visibleChange(false)` so footer Apply also closes.
|
|
1413
|
-
|
|
1414
|
-
**Tests**:
|
|
1415
|
-
|
|
1416
|
-
- `src/test/server-endpoints.test.ts` — new bulk PATCH suite (happy path, partial-failure, lock, body shape errors, `db-missing`) + a regression test asserting that `POST /api/scan` no longer re-populates a freshly-disabled plugin's contributions.
|
|
1417
|
-
|
|
1418
|
-
## User-facing
|
|
1419
|
-
|
|
1420
|
-
Plugin toggles in Settings now stage edits in the modal — click Apply (or confirm at close) to commit and refresh the graph; X discards. Changes apply on the next scan, no `sm serve` restart needed (except plugins disabled at boot, marked per-row).
|
|
1421
|
-
|
|
1422
|
-
- b840302: Rename the view slot `card.footer.left.counter` to `card.footer.left`.
|
|
1423
|
-
|
|
1424
|
-
After the `card.footer.left.tag` sub-slot was dropped (see prior CHANGELOGs), the counter became the only shape on the left footer of the card. The `.counter` suffix was a leftover of the dual-shape sub-slot scheme — the slot is now symmetrical with `card.footer.right` and consistent with the bare-base names used for `card.title.right` and `card.subtitle.left`.
|
|
1425
|
-
|
|
1426
|
-
**Wire format (breaking)**
|
|
1427
|
-
|
|
1428
|
-
- The `SlotName` enum in `spec/schemas/view-slots.schema.json` lists `card.footer.left` instead of `card.footer.left.counter`. The `$defs.payloads` map and the `IViewContribution.allOf` icon-required guard are updated to match.
|
|
1429
|
-
- Plugin manifests that declare `viewContributions[*].slot: 'card.footer.left.counter'` need to update the literal to `'card.footer.left'`. Greenfield rename: no compatibility shim, no `catalogCompat` bump (no released external plugin uses this slot).
|
|
1430
|
-
|
|
1431
|
-
**Kernel + built-ins (breaking)**
|
|
1432
|
-
|
|
1433
|
-
- TypeScript: `TSlotName` in `src/kernel/types/view-catalog.ts` and the `KNOWN_SLOTS` set in `src/kernel/adapters/schema-validators.ts` now use `'card.footer.left'`. The `unknown-slot` analyzer's catalog mirror is updated.
|
|
1434
|
-
- Built-in extractors: `at-directive`, `markdown-link`, and `slash` now declare `slot: 'card.footer.left'` in their `viewContributions.count` entry.
|
|
1435
|
-
- Scaffolder: the `VIEW_SLOTS_CATALOG` array and the `plugins create` stub default in `src/cli/commands/plugins.ts` emit `card.footer.left`. Help / tip text updated.
|
|
1436
|
-
|
|
1437
|
-
**UI**
|
|
1438
|
-
|
|
1439
|
-
- `ui/src/app/slots/slot-config.ts` — `TSlotId` and `SLOT_REGISTRY` rekeyed.
|
|
1440
|
-
- `ui/src/app/slots/slot-renderer-map.ts` — renderer mapping rekeyed.
|
|
1441
|
-
- `ui/src/app/components/node-card/node-card.html` — debug-slot data attribute and host slot literal renamed.
|
|
1442
|
-
- `ui/src/app/debug-slots.css` — debug-outline selector renamed.
|
|
1443
|
-
|
|
1444
|
-
**Migration**
|
|
1445
|
-
|
|
1446
|
-
User plugins (when any exist outside this repo) update the literal in their `plugin.json#/viewContributions[*]/slot` field. The doctor verb (`sm plugins doctor`) flags the old name as `unknown-slot` after upgrading.
|
|
1447
|
-
|
|
1448
|
-
## User-facing
|
|
1449
|
-
|
|
1450
|
-
The view slot `card.footer.left.counter` was renamed to `card.footer.left` — symmetrical with `card.footer.right`. Plugin authors using the old literal in `plugin.json` need to update it; the scaffolder emits the new name automatically.
|
|
1451
|
-
|
|
1452
|
-
- a96c257: Add a per-project consent gate for `.sm` sidecar writes, generalise the "privacy-sensitive, must not be committed" idea to a closed set of project-local-only keys, and cache config on the daemon so repeated reads in `sm serve` no longer re-walk six file layers.
|
|
1453
|
-
|
|
1454
|
-
**Per-key locality — new `PROJECT_LOCAL_ONLY_KEYS` set**
|
|
1455
|
-
|
|
1456
|
-
Four config keys are now classified as **project-local only**: `allowEditSmFiles` (new), `scan.includeHome`, `scan.extraRoots`, `scan.referencePaths`. Valid layers for these values are `defaults`, `user`, `user-local`, `project-local`, `override`. **The committed `project` layer (`<cwd>/.skill-map/settings.json`) is forbidden** — values found there are stripped (with a warning) at load time. `writeConfigValue(...)` with `target: 'project'` for any of the four throws `ProjectLocalOnlyKeyError`.
|
|
1457
|
-
|
|
1458
|
-
Sister concept to the existing `USER_ONLY_KEYS` (still scoped to `updateCheck.enabled`):
|
|
1459
|
-
|
|
1460
|
-
| Set | Valid layers | Forbidden layer(s) |
|
|
1461
|
-
| ------------------------- | ------------------------------------------------------------- | -------------------------- |
|
|
1462
|
-
| `USER_ONLY_KEYS` | `defaults`, `user`, `user-local`, `override` | `project`, `project-local` |
|
|
1463
|
-
| `PROJECT_LOCAL_ONLY_KEYS` | `defaults`, `user`, `user-local`, `project-local`, `override` | `project` |
|
|
1464
|
-
|
|
1465
|
-
Enforcement lives in `src/kernel/config/loader.ts` (loader-side strip + warning) and `src/core/config/helper.ts` (writer-side reject). The schema stays additive — older installs that wrote one of these keys to `settings.json` keep validating; the value is silently dropped at read time and the warning surfaces via `sm config show --source`.
|
|
1466
|
-
|
|
1467
|
-
**Sidecar write consent (`allowEditSmFiles`)**
|
|
1468
|
-
|
|
1469
|
-
Every `.sm` write — scaffold (`sm sidecar annotate`), hash-only refresh (`sm sidecar refresh`), bump (`sm bump`, `POST /api/sidecar/bump`) — now flows through `FilesystemSidecarStore.applyPatch`, the **single chokepoint** for sidecar writes. `applyPatch` consults `allowEditSmFiles` (default `false`) via `ensureSidecarWritesAllowed` before touching disk:
|
|
1470
|
-
|
|
1471
|
-
- `true` → write proceeds.
|
|
1472
|
-
- `false` AND caller passes `confirm: true` (CLI `--yes` / BFF `{ "confirm": true }` body) → kernel persists `allowEditSmFiles: true` to `.skill-map/settings.local.json` and performs the write.
|
|
1473
|
-
- `false` AND no confirm → `EConsentRequiredError`. CLI on TTY prompts via the existing `confirm()` util; CLI without TTY exits 2 with a hint; BFF returns 412 `confirm-required` with `details: { key: 'allowEditSmFiles' }` so the UI can open a `ConfirmationService` dialog.
|
|
1474
|
-
|
|
1475
|
-
Decline never persists — the next attempt re-asks. The flag lives in `project-local` (gitignored) so each collaborator consents independently.
|
|
1476
|
-
|
|
1477
|
-
`sm sidecar annotate` was the one writer that bypassed the store (direct `writeFileSync`); it's now refactored to route through `FilesystemSidecarStore.applyPatch` so the gate is impossible to bypass. The "exists + !force" UX check stays at the command level (preserves the legacy refusal semantics).
|
|
1478
|
-
|
|
1479
|
-
**Daemon config cache (`ConfigService`)**
|
|
1480
|
-
|
|
1481
|
-
New `src/core/config/service.ts` exposes a lazy, reloadable wrapper around `loadConfig()`. The Hono server instantiates one at boot and threads it through `IRouteDeps`; routes consume `deps.configService.get()` / `.effective()` instead of calling `loadConfig` per request. Mutating routes (`PATCH /api/project-preferences`, future config writers) call `.reload()` after a successful write so the next read sees the new state.
|
|
1482
|
-
|
|
1483
|
-
The watcher already had its own per-batch reload pattern (`core/watcher/runtime.ts:320-326`); the daemon now shares the same principle via a single service. CLI verbs remain stateless (short-lived process; caching adds no value).
|
|
1484
|
-
|
|
1485
|
-
**`project-preferences` route persistence target switched to `project-local`**
|
|
1486
|
-
|
|
1487
|
-
With `scan.includeHome` / `scan.extraRoots` / `scan.referencePaths` joining `PROJECT_LOCAL_ONLY_KEYS`, the PATCH route now writes to `target: 'project-local'` (`<cwd>/.skill-map/settings.local.json`). The existing 412 `confirm-required` privacy gate (for writes that EXPAND the disk-access surface) is unchanged.
|
|
1488
|
-
|
|
1489
|
-
**New spec sections**
|
|
1490
|
-
|
|
1491
|
-
- `architecture.md` §IO discipline — plugins (Provider / Extractor / Analyzer / Action / Formatter / Hook) are pure: they consume context and emit data via returns or `ctx.*` callbacks. They MUST NOT write to the filesystem. All materialisation flows through kernel Ports. The consent gate at the kernel boundary is sufficient precisely because no extension has the means to write.
|
|
1492
|
-
- `architecture.md` §Config layering — explicit table of the six layers + the two locality sets (`USER_ONLY_KEYS`, `PROJECT_LOCAL_ONLY_KEYS`) with members and enforcement semantics.
|
|
1493
|
-
- `architecture.md` §Annotation system · Write consent — the consent flow normatively documented.
|
|
1494
|
-
- `cli-contract.md` §`.sm` write consent — describes the CLI / BFF surfaces; `cli-contract.md` §Project-local-only config — describes `sm config set` behaviour for the four keys.
|
|
1495
|
-
- `schemas/project-config.schema.json` — new `allowEditSmFiles` boolean (default `false`); the three privacy-sensitive scan keys' descriptions updated to flag PROJECT_LOCAL_ONLY membership and stripping behaviour.
|
|
1496
|
-
|
|
1497
|
-
**Tests**
|
|
1498
|
-
|
|
1499
|
-
- New: `src/test/sidecar-consent.test.ts`, `src/test/config-service.test.ts`, `ui/src/services/sidecar.spec.ts` (3 new cases), `ui/src/app/views/inspector-view/inspector-view.spec.ts` (4 new cases).
|
|
1500
|
-
- Extended: `src/test/config-loader.test.ts` (locality stripping), `src/test/config-helper.test.ts` (PROJECT_LOCAL_ONLY guards), `src/test/sidecar-store.test.ts` (consent gate), `src/test/bump-action.test.ts`, `src/test/bump-cli.test.ts`, `src/test/sidecar-cli.test.ts`, `src/test/server-sidecar-endpoint.test.ts`, `src/test/project-preferences-route.test.ts`.
|
|
1501
|
-
- `npm test` (src) — 1302 / 1302 green. `npm test -w ui` — 320 pass (3 pre-existing failures in `node-card.spec.ts` from a prior commit, unrelated).
|
|
1502
|
-
|
|
1503
|
-
## User-facing
|
|
1504
|
-
|
|
1505
|
-
Skill-map asks before creating `.sm` sidecars. Pass `--yes` (CLI) or accept the dialog (UI); your consent saves to `.skill-map/settings.local.json` (gitignored). Privacy scan paths (`scan.includeHome`, etc.) no longer load from committed `settings.json`.
|
|
1506
|
-
|
|
1507
|
-
## 0.20.0
|
|
1508
|
-
|
|
1509
|
-
### Minor Changes
|
|
1510
|
-
|
|
1511
|
-
- a1bfe15: Eliminate the view-contribution `contract` abstraction — plugin authors now pick `slot` directly.
|
|
1512
|
-
|
|
1513
|
-
The previous model exposed two layers to the plugin author: a closed catalog of 11 "contracts" (`node-counter`, `node-tag`, `node-breakdown`, ...) plus an internal UI map from contract → N compatible slots. Picking a contract caused the same data to render in EVERY compatible slot (e.g. `node-counter` broadcast to four surfaces simultaneously). The 2026-05-10 collapse drops the contract layer: the plugin author picks ONE slot from a closed catalog of 14 slots; the slot fixes both the renderer and the payload shape; nothing renders implicitly. Smaller mental model, no surprise duplication, slot ids that map 1:1 to a payload.
|
|
1514
|
-
|
|
1515
|
-
**Spec changes** (`@skill-map/spec`):
|
|
1516
|
-
|
|
1517
|
-
- `spec/schemas/view-contracts.schema.json` renamed to `spec/schemas/view-slots.schema.json`. `$defs.ContractName` (11-entry closed enum) replaced by `$defs.SlotName` (14-entry closed enum). `$defs.IViewContribution.contract` field renamed to `slot`. `$defs.payloads` re-keyed by slot id; slots that share a payload shape (`card.subtitle.left`, `card.footer.right`, `card.footer.left.counter`, `inspector.header.badge.counter` all use the counter shape) `$ref` a shared internal definition. The conditional `allOf` discriminators that mandated `icon` on `node-counter` and `node-icon` now mandate `icon` on every counter slot and on `card.title.right`.
|
|
1518
|
-
- The three previously-polymorphic slots are split via dotted suffix:
|
|
1519
|
-
- `card.footer.left` → `card.footer.left.counter` (single sub-slot — the `card.footer.left.tag` sub-slot was considered and dropped: the counter sub-slot is multi-element, no built-in adopter wanted a tag here, and the `inspector.header.badge.tag` slot covers the remaining tag-shaped use case)
|
|
1520
|
-
- `inspector.header.badge` → `inspector.header.badge.counter`, `inspector.header.badge.tag`
|
|
1521
|
-
- `inspector.body.panel` → `inspector.body.panel.breakdown`, `.records`, `.tree`, `.key-values`, `.link-list`, `.markdown` (one per shape, narrative order in the inspector body)
|
|
1522
|
-
- The five monomorphic slots (`card.title.right`, `card.subtitle.left`, `card.footer.right`, `graph.node.alert`, `topbar.actions.indicator`) keep their ids unchanged.
|
|
1523
|
-
- `spec/view-contracts.md` renamed to `spec/view-slots.md` and rewritten as a 14-slot catalog (one section per slot: payload shape, manifest declaration, emit example, where it renders).
|
|
1524
|
-
- `spec/architecture.md` § View contribution system: rewritten to reflect the two-layer model. The "Plugin author NEVER picks a slot" guidance is inverted; the comparison table's "Plugin author writes" row now says "`slot` name from a closed catalog"; the "Surfaces in" row now says "fixed renderer per slot, mounted at exactly the slot the author declared".
|
|
1525
|
-
- `spec/plugin-author-guide.md` § View contributions: rewritten tutorial. Manifest example uses `slot:`; the slot-catalog table replaces the contract-catalog table; new "Multi-slot rendering" sub-section explains that the same data in two surfaces requires two declarations (intentional).
|
|
1526
|
-
- `spec/db-schema.md` § `scan_contributions`: column `contract TEXT NOT NULL` renamed to `slot TEXT NOT NULL`; comment now references `view-slots.schema.json#/$defs/SlotName`.
|
|
1527
|
-
- `spec/schemas/extensions/base.schema.json`, `spec/schemas/api/rest-envelope.schema.json`, `spec/schemas/plugins-registry.schema.json`: `contract` field references swept to `slot`; doc strings re-pointed at `view-slots.schema.json`. `contributionsRegistry` envelope entries now carry `slot` (not `contract`).
|
|
1528
|
-
- `spec/conformance/coverage.md` row 30 re-pointed at `view-slots.schema.json` and the renamed conformance case.
|
|
1529
|
-
|
|
1530
|
-
**Implementation changes** (`@skill-map/cli`):
|
|
1531
|
-
|
|
1532
|
-
- `src/kernel/types/view-catalog.ts`: `TContractName` (11 entries) renamed to `TSlotName` (14 entries). `IViewContribution.contract` and `IRegisteredViewContribution.contract` renamed to `slot`.
|
|
1533
|
-
- `src/kernel/orchestrator.ts`: extractor + rule emit paths read `declared.slot`, validate via `validateContributionPayload(declared.slot, payload)`, persist with `slot:` field. Also threads a new `freshlyRunTuples` set down through `walkAndExtract` → `runScanInternal` → caller (see Persistence-fix block below).
|
|
1534
|
-
- `src/kernel/adapters/schema-validators.ts`: `SUPPORTING_SCHEMAS` reads `view-slots.schema.json`. `validateContributionPayload(slot, payload)` keys validators by slot id (14 keys); error code renamed from `'unknown-contract'` to `'unknown-slot'`. The validator filters out internal `$ref` targets (`_counter`, `_tag`, `_TreeNode`) so they cannot be queried by accident.
|
|
1535
|
-
- `src/migrations/001_initial.sql`: `scan_contributions.contract` column renamed to `slot`. No migration script — pre-1.0 greenfield, fixtures purge on next scan.
|
|
1536
|
-
- `src/kernel/adapters/sqlite/contributions.ts`, `src/kernel/adapters/sqlite/schema.ts`: field rename in record types and SQL queries.
|
|
1537
|
-
- `src/built-in-plugins/extractors/external-url-counter/index.ts`: `contract: 'node-counter'` → `slot: 'card.footer.right'`.
|
|
1538
|
-
- `src/built-in-plugins/extractors/at-directive/index.ts`: `contract: 'node-counter'` → `slot: 'card.footer.left.counter'`.
|
|
1539
|
-
- `src/built-in-plugins/rules/link-counts/index.ts`: `linksOut.contract` → `slot: 'card.footer.right'`; `linksIn.contract` → `slot: 'card.footer.left.counter'`.
|
|
1540
|
-
- `src/built-in-plugins/rules/unknown-contract/` renamed (via `git mv`) to `src/built-in-plugins/rules/unknown-slot/`. Export `unknownContractRule` → `unknownSlotRule`. Internal id `'unknown-contract'` → `'unknown-slot'`. Message "declares unknown contract" → "declares unknown slot". `KNOWN_CONTRACTS` set replaced by `KNOWN_SLOTS` (14 entries).
|
|
1541
|
-
- `src/built-in-plugins/rules/link-counts/index.ts`: rule paused — view-contributions block stripped, `evaluate()` is now a no-op `return []`. The `linksOut` chip duplicated the per-extractor counters living next to it (`@N` from at-directive, `📎N` from markdown-link, `/N` from slash); `linksIn` was unique but kept here for symmetry. Rule remains registered (no-op) so re-enabling is a single-file change.
|
|
1542
|
-
- `src/built-in-plugins/extractors/markdown-link/index.ts`, `src/built-in-plugins/extractors/slash/index.ts`: gain a `card.footer.left.counter` view contribution each (`📎N` and `/N` chips), aligning with `at-directive`'s existing `@N` chip and removing the rationale for the paused `link-counts` `linksOut`.
|
|
1543
|
-
- `src/built-in-plugins/built-ins.ts`: import path updated.
|
|
1544
|
-
- `src/cli/commands/plugins.ts`: `VIEW_CONTRACTS_CATALOG` (11 entries) renamed to `VIEW_SLOTS_CATALOG` (14 entries with summaries derived from `view-slots.md`). `PluginsContractsListCommand` renamed to `PluginsSlotsListCommand`; verb path `['plugins', 'contracts', 'list']` → `['plugins', 'slots', 'list']`. `PluginsCreateCommand` scaffolder emits manifest stubs with `slot:` (default `card.footer.left.counter`); help text and tip lines now reference `sm plugins slots list`. `plugins show` qualifies extension names with `<bundleId>/<extensionId>` for `granularity=extension` so shadowed siblings stay distinguishable in the listing.
|
|
1545
|
-
- `src/server/contributions-registry.ts`, `src/server/routes/contributions.ts`, `src/server/envelope.ts`: registry entries and lookup items use `slot:` field.
|
|
1546
|
-
- `src/core/runtime/plugin-runtime.ts`: `collectViewContributions` reads `entry.slot` and pushes `slot: entry.slot as TSlotName`.
|
|
1547
|
-
- `context/cli-reference.md` regenerated to absorb the verb rename.
|
|
1548
|
-
|
|
1549
|
-
**Persistence fix — per-tuple sweep on `scan_contributions`** (`@skill-map/cli`):
|
|
1550
|
-
|
|
1551
|
-
The pre-fix persist layer ran three passes (orphan → catalog → upsert) keyed at the `(plugin, extension, node, contributionId)` level, and that wasn't enough to catch the case "extractor used to emit for node X, body change removes the trigger, prior row stays stale". A 4th pass — a per-tuple sweep keyed by `(pluginId, extensionId, nodePath)` — now drops rows whose key is absent from the current scan's contribution buffer, but ONLY for tuples that actually ran this scan.
|
|
1552
|
-
|
|
1553
|
-
- `src/kernel/types/storage.ts`: `IPersistOptions` gains an optional `freshlyRunTuples?: ReadonlySet<string>` field (format `<pluginId>/<extensionId>/<nodePath>`). Empty / absent set = no per-tuple sweep (legacy callers preserve the pre-fix behaviour where stale rows linger).
|
|
1554
|
-
- `src/kernel/orchestrator.ts`: `walkAndExtract` accumulates a `freshlyRunTuples: Set<string>`. Extractor + cache miss → tuple INCLUDED. Extractor + cache hit → tuple OMITTED (prior rows must survive). After `applyRules`, `runScanInternal` folds in `(rule × node)` for every rule that declares `viewContributions` (rules always run and see the full graph, no per-(rule, node) cache like extractors have). The set is returned alongside `contributions` and threaded into the persist call.
|
|
1555
|
-
- `src/kernel/adapters/sqlite/contributions.ts` + `src/kernel/adapters/sqlite/scan-persistence.ts` + `src/kernel/adapters/sqlite/storage-adapter.ts`: persist accepts the set, runs the sweep DELETE before the upsert, scoped to keys whose `(plugin, extension, node)` is in the set but whose `(plugin, extension, node, contributionId)` is NOT in the buffer. Cached-extractor tuples remain absent from the set, so their rows are untouched.
|
|
1556
|
-
- `src/core/runtime/scan-runner.ts` + `src/core/watcher/runtime.ts`: thread `freshlyRunTuples` from the orchestrator return into the persist call.
|
|
1557
|
-
- Backwards-compat: the field is optional. The persist layer treats an absent / empty set as "skip the sweep", matching pre-fix behaviour bit-for-bit.
|
|
1558
|
-
|
|
1559
|
-
**UI changes** (private `ui/` workspace, ships bundled in `@skill-map/cli`):
|
|
1560
|
-
|
|
1561
|
-
- `ui/src/app/contracts/contract-renderer-map.ts` renamed (via `git mv`) to `ui/src/app/slots/slot-renderer-map.ts`. The `CONTRACT_RENDERERS` + `CONTRACT_SLOTS` two-map structure is replaced by a single `SLOT_RENDERERS: Record<TSlotId, ComponentType>` (14 entries, 1:1 slot → renderer); `isKnownContract` renamed to `isKnownSlot`.
|
|
1562
|
-
- `ui/src/app/slots/slot-config.ts`: `TSlotId` union expanded to 14 entries; `SLOT_REGISTRY` rebuilt with sub-slots inheriting `maxItems` / `order` / `respectSeverity` from their former polymorphic parent.
|
|
1563
|
-
- `ui/src/app/slots/icon-glyph.ts` (new): tiny shared `<sm-icon-glyph>` component that resolves a manifest-declared `icon` per spec (`Extended_Pictographic` → emoji text; otherwise → `<i class="pi pi-{icon}">`). Adopted by `node-counter`, `node-alert`, `node-icon`, `scope-stat` — fixes the regression where `arrow-up` rendered as the literal three-character string instead of the PrimeIcons class.
|
|
1564
|
-
- `ui/src/app/components/view-contributions-host/view-contributions-host.ts`: dispatch simplified — `contractMatchesSlot(c.contract, slot)` replaced by `c.slot === slot`; renderer lookup is `SLOT_RENDERERS[slot]`.
|
|
1565
|
-
- `ui/src/models/api.ts`: `IContributionApi.contract` and `IContributionsRegistryEntryApi.contract` renamed to `slot`.
|
|
1566
|
-
- HTML templates: the polymorphic mounts split into per-shape hosts. `node-card.html` mounts `card.footer.left.counter` (single sub-slot, no `.tag`). `inspector-view.html` mounts `inspector.header.badge.counter` + `.tag` adjacent and the six `inspector.body.panel.*` sub-slots stacked in narrative order (breakdown → records → tree → key-values → link-list → markdown). `graph-view.html`, `app.html`, and the monomorphic mounts are unchanged.
|
|
1567
|
-
- `ui/src/app/debug-slots.css`: 10 new entries for the sub-slots (varied hue tones for visual distinction); 3 obsolete entries removed.
|
|
1568
|
-
- 11 renderer components had their `IRendererInputs` import path updated to the new `slots/slot-renderer-map`; doc strings refreshed.
|
|
1569
|
-
|
|
1570
|
-
**Tests**:
|
|
1571
|
-
|
|
1572
|
-
- `src/test/view-contributions.test.ts`: helper interfaces and fixtures swapped to `slot:`. Validation tests now call `validateContributionPayload(<slot-id>, ...)`. Negative test "rejects unknown contract names" renamed to "rejects unknown slot names" with assertion `result.errors === 'unknown-slot'`.
|
|
1573
|
-
- `src/test/server-annotations-endpoint.test.ts`, `src/test/server-sidecar-endpoint.test.ts`: schema path strings updated.
|
|
1574
|
-
- `src/test/plugin-runtime-branches.test.ts`: rule-id list assertion updated (`'unknown-contract'` → `'unknown-slot'`).
|
|
1575
|
-
- `src/built-in-plugins/rules/link-counts/link-counts.test.ts`: manifest assertions reflect the new slot ids.
|
|
1576
|
-
|
|
1577
|
-
**Breaking** (per the pre-1.0 minor convention — see `CONTRIBUTING.md` / `spec/versioning.md` §Pre-1.0):
|
|
1578
|
-
|
|
1579
|
-
- Plugin manifests declaring `viewContributions[*].contract: 'node-counter'` (or any of the other 10 contract names) now load as `invalid-manifest`. Migration is mechanical: rename the field to `slot` and pick one of the 14 slot ids that matches the prior contract's payload shape. Recommended mapping: `node-counter` → `card.footer.right` (or another counter slot), `node-tag` → `inspector.header.badge.tag` (the only tag slot in the catalog now), `node-breakdown/records/tree/key-values/link-list/markdown` → `inspector.body.panel.<shape>`, `node-alert` → `graph.node.alert`, `node-icon` → `card.title.right`, `scope-stat` → `topbar.actions.indicator`.
|
|
1580
|
-
- The CLI verb `sm plugins contracts list` is removed and replaced by `sm plugins slots list`.
|
|
1581
|
-
- The built-in soft-warning rule `core/unknown-contract` is removed and replaced by `core/unknown-slot` (same semantics, slot-keyed walk).
|
|
1582
|
-
- The database column `scan_contributions.contract` is renamed to `slot`. No migration script ships — purge fixture DBs and re-run `sm scan` after upgrading. The pre-1.0 greenfield posture (no schema versioning) holds.
|
|
1583
|
-
|
|
1584
|
-
## User-facing
|
|
1585
|
-
|
|
1586
|
-
**The view-contribution model is simpler.** Plugin authors now pick **one slot** from a closed catalog of 14; the slot decides where the data renders, what payload shape is expected, and which renderer draws it. The previous model required learning two catalogs (contracts and slots) and accepted that the same data would broadcast to multiple surfaces automatically — that broadcast is gone.
|
|
1587
|
-
|
|
1588
|
-
Visible changes in the SPA:
|
|
1589
|
-
|
|
1590
|
-
- The URL-counter chip from `core/external-url-counter` now renders only in the card's footer-right cluster (was visible in four surfaces simultaneously).
|
|
1591
|
-
- The `@-mention` chip from `core/at-directive`, plus new `📎` (markdown links) and `/` (slash directives) counter chips from `core/markdown-link` and `core/slash`, render only in the card's footer-left cluster.
|
|
1592
|
-
- The `core/link-counts` rule is paused — its `linksOut` / `linksIn` chips are temporarily off the card. `linksOut` duplicated the new per-extractor counters; `linksIn` will return when the chip surface is reinstated. The rule stays registered as a no-op so re-enabling is a single-file change.
|
|
1593
|
-
- The CLI verb to browse the catalog is now `sm plugins slots list` (was `sm plugins contracts list`).
|
|
1594
|
-
- **Stale view contributions are cleaned up.** Editing a node so an extractor stops emitting a chip (e.g. removing the last `@mention` from a doc) now removes the chip on the next scan. Previously the chip would linger until the row was clobbered by an unrelated edit.
|
|
1595
|
-
- Renderer icons resolve correctly across emoji and PrimeIcons names (an icon like `arrow-up` no longer leaks as the literal three-character string when the renderer expected a class name).
|
|
1596
|
-
|
|
1597
|
-
- 5600a60: Hook trigger set grows from 8 to 10: add CLI-process-driven `boot` and `shutdown`. First built-in concrete consumer: `core/update-check` (the once-per-day update banner moves from an inline call site to a hook subscribing to `boot`).
|
|
1598
|
-
|
|
1599
|
-
**Spec changes** (`@skill-map/spec`):
|
|
1600
|
-
|
|
1601
|
-
- `spec/schemas/extensions/hook.schema.json` — `triggers[].enum` grows from 8 to 10 entries (`boot` first, `shutdown` last). Top-level description updated to reflect the new size and the pipeline-driven vs CLI-process-driven split.
|
|
1602
|
-
- `spec/architecture.md` § Hook · curated trigger set — table grows by two rows. `boot` documents the pre-verb dispatch (await semantics, fire-time, payload `{ argv }`); `shutdown` documents the post-verb dispatch (await semantics, payload `{ exitCode }`). The "Eight" wording flips to "ten" in the §Hook one-liner and the §Locality count of bundled built-ins (`one Provider, four extractors, five rules, one formatter, one hook` — the first built-in hook is `core/update-check`). The `## Stability and versioning` clause updates: trigger-set size goes from 8 to 10; adding an eleventh is a minor bump, removing or renaming any of the ten is a major bump.
|
|
1603
|
-
- `spec/index.json` regenerated.
|
|
1604
|
-
|
|
1605
|
-
**Implementation changes** (`@skill-map/cli`):
|
|
1606
|
-
|
|
1607
|
-
- `src/kernel/extensions/hook.ts` — `THookTrigger` union and the frozen `HOOK_TRIGGERS` array grow from 8 to 10 entries (`boot` first, `shutdown` last so a debug log of the array reads in lifecycle order). Doc comment updated.
|
|
1608
|
-
- `src/kernel/extensions/hook-dispatcher.ts` (new) — `IHookDispatcher`, `makeHookDispatcher`, and `makeEvent` extracted from `kernel/orchestrator.ts` so two callers can share the indexing / filter / error-handling semantics: the orchestrator for the eight pipeline-driven triggers (inside `runScan`), and `cli/entry.ts` for `boot` / `shutdown`. The orchestrator now imports the helpers; the duplicated inline definitions and `matchesFilter` / `buildHookContext` helpers are gone.
|
|
1609
|
-
- `src/kernel/index.ts` — re-exports `makeHookDispatcher`, `makeEvent`, and `IHookDispatcher` so the CLI entry (and future drivers) can build their own dispatcher without crossing into orchestrator internals.
|
|
1610
|
-
- `src/built-in-plugins/hooks/update-check/index.ts` (new) — first built-in concrete `IHook`. Subscribes to `boot`, deterministic mode. Imports `maybeRunUpdateCheck` from `cli/util/update-check-banner.js` and forwards the contracted `event.data: { dbPath, cwd, homedir, stderr, noColorFlag }` payload. Defensive: a `boot` event missing any contracted field is a no-op (rather than a throw), so a misconfigured driver degrades gracefully. The lint config does not restrict `built-in-plugins/**` from importing CLI helpers (built-ins are bundled in the same binary), so the cross-layer import is intentional — `cli/util/update-check-banner.ts` is the only legal home for the env / config reads (`SM_NO_UPDATE_CHECK`, `CI`, `loadConfig`, ANSI / TTY checks) per the kernel-boundary lint rules.
|
|
1611
|
-
- `src/built-in-plugins/built-ins.ts` — imports `updateCheckHook` and pushes it into the `core` bundle (last entry). The `bucketBuiltIn` dispatch table already routed `kind: 'hook'` to `out.hooks`; no per-kind code change.
|
|
1612
|
-
- `src/cli/entry.ts` — the inline `await maybeRunUpdateCheck(...)` post-`cli.run()` block is gone. Instead: the entry now imports `builtIns()` and `makeHookDispatcher`, builds a single dispatcher over `builtIns().hooks`, dispatches `boot` BEFORE `cli.process()` (so the banner lands above the verb's output, per the Phase 3 design call), and dispatches `shutdown` AFTER `cli.run()` and BEFORE `process.exit(exitCode)`. `boot` payload carries `{ argv, dbPath, cwd, homedir, stderr, noColorFlag }`; `shutdown` payload carries `{ exitCode }`. Both dispatches await; the dispatcher catches every hook error so a buggy hook can only delay the verb / exit, never alter the resolved exit code. User-plugin hooks subscribing to `boot` / `shutdown` are loaded but not yet dispatched on this path (built-in only) — documented as a follow-up in the README.
|
|
1613
|
-
- `src/core/runtime/plugin-runtime.ts` — `composeScanExtensions` "kernel-empty-boot" check no longer counts hooks. A hook subscribing only to `boot` / `shutdown` (the new CLI-driven triggers) reaches the composer through the built-in bundle but the orchestrator dispatcher would never invoke it; preserving the empty-boot shape regardless of hook presence keeps the conformance case honest while letting `core/update-check` ride along for the entry-side dispatcher to pick up.
|
|
1614
|
-
- `src/built-in-plugins/README.md` — adds the `core/update-check` row and a paragraph on the two dispatch entry points (orchestrator vs CLI entry) sharing the same dispatcher module.
|
|
1615
|
-
- `src/test/update-check-hook.test.ts` (new) — manifest-shape assertions and defensive-payload coverage for the hook (no-op when `dbPath` / `cwd` / `homedir` / `stderr` are absent; clean forward when contracted; DB missing → silent bail). Pre-existing unit + integration tests for `maybeRunUpdateCheck` (in `src/test/update-check.test.ts`) keep covering the cache + bail + banner behaviour end-to-end — the hook is a thin wrapper.
|
|
1616
|
-
- Two pre-existing tests updated for the new built-in count: `src/test/built-ins-modes.test.ts` (`listBuiltIns().length`: 23 → 24, comment updated to call out the new hook).
|
|
1617
|
-
|
|
1618
|
-
**ROADMAP changes**:
|
|
1619
|
-
|
|
1620
|
-
- §Plugin system · Hook trigger set — list grows from 8 to 10 entries; new paragraph documents the dispatcher module split (`kernel/extensions/hook-dispatcher.ts`) and points at `core/update-check` as the first built-in consumer.
|
|
1621
|
-
- §Glossary · Hook — one-liner updated from "one of eight" → "one of ten" with the pipeline vs CLI-process split.
|
|
1622
|
-
|
|
1623
|
-
**Pre-1.0 minor bumps** per `spec/versioning.md` § Pre-1.0 — both surfaces grow additively (two new triggers, one new built-in hook, one new internal kernel module). No existing surface is removed or renamed; old hooks subscribing only to the eight pre-existing triggers keep working byte-for-byte. Pre-1.0 lets us land additive contract growth as `minor` without flipping to 1.0.0.
|
|
1624
|
-
|
|
1625
|
-
- 802e64f: Rename the `rule` plugin extension kind to `analyzer`.
|
|
1626
|
-
|
|
1627
|
-
The kind formerly known as `rule` not only finds issues but also projects findings into the UI via `viewContributions` (cards, badges, tabs). "Rule" undersold the breadth of the contract; **Analyzer** captures both axes — graph analysis and visual projection. Pre-1.0, no released consumers depend on the old name, so this ships as a sweep without compatibility shims.
|
|
1628
|
-
|
|
1629
|
-
**Wire format (breaking)**
|
|
1630
|
-
|
|
1631
|
-
- `kind` enum in `extensions/base.schema.json` now lists `analyzer` instead of `rule`.
|
|
1632
|
-
- `extensions/rule.schema.json` is renamed to `extensions/analyzer.schema.json`.
|
|
1633
|
-
- The const value of `kind` on the kind-specific schema is `"analyzer"`.
|
|
1634
|
-
- The manifest array field `emitsRuleIds` is now `emitsAnalyzerIds`.
|
|
1635
|
-
|
|
1636
|
-
**Issue model + REST + DB (breaking)**
|
|
1637
|
-
|
|
1638
|
-
- `Issue.ruleId` is now `Issue.analyzerId` in the JSON wire and the TS shape.
|
|
1639
|
-
- `GET /api/issues?ruleId=<id>` becomes `GET /api/issues?analyzerId=<id>`.
|
|
1640
|
-
- The SQL column `scan_issues.rule_id` is now `scan_issues.analyzer_id`; the index `ix_scan_issues_rule_id` becomes `ix_scan_issues_analyzer_id`.
|
|
1641
|
-
|
|
1642
|
-
**Events (breaking)**
|
|
1643
|
-
|
|
1644
|
-
- The hook trigger `rule.completed` is now `analyzer.completed`. The payload field renames from `ruleId` to `analyzerId`.
|
|
1645
|
-
|
|
1646
|
-
**CLI (breaking)**
|
|
1647
|
-
|
|
1648
|
-
- `sm check --rules <ids>` becomes `sm check --analyzers <ids>`.
|
|
1649
|
-
- The conformance kill-switch env var is `SKILL_MAP_DISABLE_ALL_ANALYZERS` (was `SKILL_MAP_DISABLE_ALL_RULES`); the corresponding `conformance-case.schema.json` field is `disableAllAnalyzers`.
|
|
1650
|
-
- The advisory placeholder `{{ruleIds}}` in `--include-prob` output is now `{{analyzerIds}}`.
|
|
1651
|
-
|
|
1652
|
-
**Kernel + built-ins (breaking)**
|
|
1653
|
-
|
|
1654
|
-
- TypeScript symbols: `IRule` → `IAnalyzer`, `IRuleContext` → `IAnalyzerContext`, `IRuleOrphanSidecar` → `IAnalyzerOrphanSidecar`.
|
|
1655
|
-
- The 11 built-in extensions previously under `src/built-in-plugins/rules/` now live under `src/built-in-plugins/analyzers/`. Each `*Rule` symbol (e.g. `triggerCollisionRule`) is renamed to its `*Analyzer` form (`triggerCollisionAnalyzer`).
|
|
1656
|
-
- `IBuiltIns.rules` → `IBuiltIns.analyzers`; `IPluginRuntimeBundle.extensions.rules` → `analyzers`; `IScanExtensions.rules` → `analyzers`.
|
|
1657
|
-
- The kernel filter utility `kernel/util/rule-filter.ts` (`matchesRuleFilter`) is renamed to `analyzer-filter.ts` (`matchesAnalyzerFilter`).
|
|
1658
|
-
|
|
1659
|
-
**Testkit (breaking, public)**
|
|
1660
|
-
|
|
1661
|
-
- `runRuleOnGraph` → `runAnalyzerOnGraph`.
|
|
1662
|
-
- `makeRuleContext` → `makeAnalyzerContext`.
|
|
1663
|
-
- `IRunRuleOptions` → `IRunAnalyzerOptions`.
|
|
1664
|
-
- Re-exports `IAnalyzer`, `IAnalyzerContext` instead of the `IRule` variants.
|
|
1665
|
-
|
|
1666
|
-
**Migration**
|
|
1667
|
-
|
|
1668
|
-
Greenfield rename — no fallback. Existing user plugins with `kind: "rule"` and `emitsRuleIds` need to update their manifests. The scaffolder (`sm plugins create`) emits `kind: 'analyzer'` automatically; a future `sm plugins upgrade <id>` will rewrite legacy manifests.
|
|
1669
|
-
|
|
1670
|
-
## User-facing
|
|
1671
|
-
|
|
1672
|
-
The plugin extension kind was renamed from **Rule** to **Analyzer** to better reflect what these plugins do — they analyze the graph AND project findings into the UI. End-user-visible changes:
|
|
1673
|
-
|
|
1674
|
-
- The CLI flag `sm check --rules <ids>` is now `sm check --analyzers <ids>`.
|
|
1675
|
-
- The `sm check --json` output's per-issue `ruleId` field is now `analyzerId`.
|
|
1676
|
-
- Hook triggers in plugin manifests rename from `rule.completed` to `analyzer.completed`; the event payload field `ruleId` is now `analyzerId`.
|
|
1677
|
-
- The Settings → Plugins page lists plugins of kind "analyzer".
|
|
1678
|
-
- The marketing site shows the satellite as "Analyzer plugin kind" instead of "Rule plugin kind".
|
|
1679
|
-
|
|
1680
|
-
If you maintain a custom plugin with `kind: "rule"`, update the manifest to `kind: "analyzer"`, rename `emitsRuleIds` to `emitsAnalyzerIds`, and rename any imported `IRule` / `IRuleContext` symbols to `IAnalyzer` / `IAnalyzerContext`. The directory name and `id` rules remain unchanged.
|
|
1681
|
-
|
|
1682
|
-
- 5600a60: Add `sm scan -g` (global scan) plus three privacy-sensitive project scan settings: `scan.includeHome`, `scan.extraRoots`, `scan.referencePaths`. Settings UI exposes them in a new "Project" section.
|
|
1683
|
-
|
|
1684
|
-
**Spec changes** (`@skill-map/spec`, minor):
|
|
1685
|
-
|
|
1686
|
-
- `spec/cli-contract.md` § Scan — `sm scan -g/--global` flag documented: with `-g` the scan walks every active Provider's `explorationDir` resolved against `~` (typically `~/.claude`, `~/.gemini`, `~/.agents`) instead of the cwd; config + DB resolve from the global scope. Mutually exclusive with positional roots (exit `2`). New §Effective roots subsection enumerates how the resolver composes `cwd` + `includeHome` + `extraRoots` + `-g`.
|
|
1687
|
-
- `spec/cli-contract.md` § Config — `sm config set` gains an optional `--yes` flag and a new §Privacy-sensitive config subsection: writes that EXPAND disk access outside the project (toggling `scan.includeHome` `false`→`true`, adding out-of-project paths to `scan.extraRoots` / `scan.referencePaths`) require `--yes` to confirm. Writes that NARROW the surface need no flag.
|
|
1688
|
-
- `spec/schemas/project-config.schema.json` — `scan` block grows three keys:
|
|
1689
|
-
- `includeHome: boolean` (default `false`).
|
|
1690
|
-
- `extraRoots: string[]` (default `[]`).
|
|
1691
|
-
- `referencePaths: string[]` (default `[]`).
|
|
1692
|
-
Every key carries a "privacy-sensitive" warning in its description so the schema-as-doc stays honest.
|
|
1693
|
-
- `spec/index.json` regenerated.
|
|
1694
|
-
|
|
1695
|
-
**Implementation changes** (`@skill-map/cli`, minor):
|
|
1696
|
-
|
|
1697
|
-
- `src/config/defaults.json` — three new defaults under `scan` (`includeHome: false`, `extraRoots: []`, `referencePaths: []`).
|
|
1698
|
-
- `src/kernel/config/loader.ts` — `IScanConfig` gains `includeHome`, `extraRoots`, `referencePaths`. Each documented inline as privacy-sensitive.
|
|
1699
|
-
- `src/kernel/extensions/rule.ts` — `IRuleContext` gains optional `referenceablePaths?: ReadonlySet<string>` (the side index `core/broken-ref` consults) and `cwd?: string` (absolute project root, threaded so rules can resolve relative `link.target`s without heuristics).
|
|
1700
|
-
- `src/kernel/orchestrator.ts` — `RunScanOptions.referenceablePaths?` and `RunScanOptions.cwd?` propagate through `runScanInternal` → `runRules` → per-rule `evaluate()`.
|
|
1701
|
-
- `src/core/runtime/scan-roots.ts` (new) — `resolveScanRoots({ positionalRoots, scope, cwd, homedir, providers, includeHome, extraRoots })`. Centralises the spec's § Effective roots rules: positional roots win verbatim; otherwise compose cwd + (includeHome ? HOME provider dirs : []) + extraRoots for project scope, or HOME provider dirs only for global scope. `-g` + positional roots throws.
|
|
1702
|
-
- `src/core/runtime/reference-paths-walker.ts` (new) — `walkReferencePaths(rawRoots, cwd, homedir)` returns `{ paths: Set<absolute>, truncated, missingRoots }`. Recursive walk that skips symlinks + `node_modules`/`.git`/`.skill-map`; capped at `REFERENCE_WALK_MAX_FILES` (50_000) for safety.
|
|
1703
|
-
- `src/core/runtime/scan-runner.ts` — `IScanRunOpts.scope?: 'project' | 'global'` (default `'project'`). Resolves DB via `resolveDbPath({ global: scope === 'global', ... })`, `loadConfig` honours the scope, roots resolve via `resolveScanRoots`, reference paths walk via `walkReferencePaths`, and the resolved `cwd` + `referenceablePaths` thread into `RunScanOptions`. Emits stderr advisories for HOME inclusions and reference-walk truncation / missing roots. The `runOptions` assembly extracted to a `buildRunScanOptions` helper to stay under the cyclomatic-complexity cap.
|
|
1704
|
-
- `src/core/runtime/i18n/scan-runner.texts.ts` — three new strings (`includingHomeAdvisory`, `includingExtraRootsAdvisory`, `referenceWalkTruncated`, `referenceWalkMissingRoot`).
|
|
1705
|
-
- `src/built-in-plugins/rules/broken-ref/index.ts` — refactored to consult `ctx.referenceablePaths` after the in-graph lookup misses. A path-style link target whose absolute resolution (`resolve(ctx.cwd, link.target)`) is in the side index is treated as resolved (file exists outside the indexed graph). Trigger-style links (`/foo`, `@bar`) skip the side-index lookup. The orchestrator's helper extracted to keep the rule under the complexity cap.
|
|
1706
|
-
- `src/cli/commands/scan.ts` — wires `-g/--global` to `runScanForCommand`'s new `scope` option. Mutex with positional roots is rejected up front with a directed message (`SCAN_TEXTS.globalWithRoots`).
|
|
1707
|
-
- `src/cli/commands/config.ts` — `ConfigSetCommand` gains `--yes`. When the key is in `PRIVACY_SENSITIVE_KEYS` and the new value would expand the surface, the verb prints the list of paths the change would expose and exits `2` unless `--yes` is set; with `--yes` it prints the same list as a confirmation receipt.
|
|
1708
|
-
- `src/cli/i18n/config.texts.ts` — `privacyGateRequired` / `privacyGateRequiredHint` / `privacyGateConfirmed`.
|
|
1709
|
-
- `src/cli/i18n/scan.texts.ts` — `globalWithRoots`.
|
|
1710
|
-
- `src/core/config/helper.ts` — adds `PRIVACY_SENSITIVE_KEYS` (a `ReadonlySet<string>`) and `projectPathExposure({ key, value, cwd, homedir })` that returns `{ expandsSurface, exposedPaths }`. Same predicate is consumed by both the CLI verb and the BFF route so the wire-side and CLI-side behaviour stay symmetric.
|
|
1711
|
-
|
|
1712
|
-
**BFF additions**:
|
|
1713
|
-
|
|
1714
|
-
- `src/server/routes/project-preferences.ts` (new) — `GET /api/project-preferences` returns `{ scan: { includeHome, extraRoots, referencePaths } }`; `PATCH /api/project-preferences` writes via `core/config/helper:writeConfigValue` with `target: 'project'`. Privacy-sensitive writes that expand the surface require `confirm: true` in the body — otherwise the route returns 412 `confirm-required` with the list of paths the change would expose.
|
|
1715
|
-
- `src/server/i18n/server.texts.ts` — eight new strings under the project-preferences section.
|
|
1716
|
-
- `src/server/app.ts` — registers the new route + adds `'confirm-required'` to `TErrorCode`; `codeForStatus(412)` maps to it.
|
|
1717
|
-
|
|
1718
|
-
**UI additions** (private `ui/` workspace):
|
|
1719
|
-
|
|
1720
|
-
- `ui/src/app/components/settings-modal/settings-project.{ts,html,css}` (new) — Project section. Renders the `includeHome` toggle plus two editable path lists (`extraRoots`, `referencePaths`) with add / remove controls. A `<p-confirmdialog>` enumerates the paths a privacy-sensitive change would expose; on accept the patch is re-issued with `confirm: true`.
|
|
1721
|
-
- `ui/src/app/components/settings-modal/settings-modal.{ts,html}` — `Project` added to the sidebar between `General` and `Plugins`. New `projectVisible` computed signal mirrors the General / Plugins lifecycle.
|
|
1722
|
-
- `ui/src/i18n/settings.texts.ts` — `sections.project` + `project: { heading, intro, includeHomeLabel, includeHomeDescription, extraRootsLabel, extraRootsDescription, extraRootsPlaceholder, referencePathsLabel, referencePathsDescription, referencePathsPlaceholder, addPathLabel, removePathLabel, confirmDialogHeader, confirmDialogIntro, confirmDialogAccept, confirmDialogReject }`.
|
|
1723
|
-
- `ui/src/models/api.ts` — new `IProjectPreferencesApi` and `IProjectPreferencesPatchApi` types (mirroring the BFF shape).
|
|
1724
|
-
- `ui/src/services/data-source/data-source.port.ts` — `IDataSourcePort` gains `getProjectPreferences()` / `setProjectPreferences(patch)`. `RestDataSource` and `StaticDataSource` implementations updated.
|
|
1725
|
-
- Two pre-existing test stubs (`ui/src/app/app.spec.ts`, `ui/src/app/views/graph-view/graph-view.spec.ts`) extended with the two new methods.
|
|
1726
|
-
|
|
1727
|
-
**Tests**:
|
|
1728
|
-
|
|
1729
|
-
- New `src/test/scan-roots.test.ts` — exhaustive coverage of `resolveScanRoots` permutations (positional verbatim, `-g` mutex throw, project / global derivations, dedup).
|
|
1730
|
-
- New `src/test/reference-paths-walker.test.ts` — recursive walk, missing roots, symlinks skipped, skip-list dirs, multi-root.
|
|
1731
|
-
- New `src/test/project-preferences-route.test.ts` — boots `createServer()` against a tempdir cwd / homedir; covers default `GET`, the `confirm-required` 412 on expansion, the `confirm: true` round-trip, and 400 body-shape errors.
|
|
1732
|
-
|
|
1733
|
-
**Pre-1.0 minor bumps** per `spec/versioning.md` § Pre-1.0 — both surfaces grow additively (one new flag on `sm scan`, three new optional config keys, one new BFF route, one new UI section). Existing `scan` invocations behave identically with the new defaults (every new key defaults to the historical zero-state).
|
|
1734
|
-
|
|
1735
|
-
## User-facing
|
|
1736
|
-
|
|
1737
|
-
**`sm scan -g` now scans your HOME directory.** Run `sm scan -g` (without positional roots) to walk every active provider's HOME dir — typically `~/.claude`, `~/.gemini`, `~/.agents` — using the global config + DB.
|
|
1738
|
-
|
|
1739
|
-
**Three new privacy-sensitive project settings.** Open Settings → Project to:
|
|
1740
|
-
|
|
1741
|
-
- **Include HOME provider directories** — when on, `sm scan` (without `-g`) also walks `~/.claude`, `~/.gemini`, `~/.agents` alongside your project content.
|
|
1742
|
-
- **Extra scan roots** — paths you can add to the scan (indexed as nodes alongside the project root).
|
|
1743
|
-
- **Reference paths (link validation)** — paths walked only to validate links; files there aren't indexed but `core/broken-ref` won't warn when a link target exists in one of them.
|
|
1744
|
-
|
|
1745
|
-
Every change that expands disk access beyond your project root requires explicit confirmation: a confirm dialog in the UI listing the paths that will be read, or `sm config set <key> <value> --yes` on the CLI. Writes that narrow the surface (toggling off, removing paths) need no confirmation.
|
|
295
|
+
- 5600a60: Add `sm scan -g` (global scan) plus three privacy-sensitive project scan settings: `scan.includeHome`, `scan.extraRoots`, `scan.referencePaths`. Settings UI exposes them in a new "Project" section.
|
|
1746
296
|
|
|
1747
297
|
- 825dce4: View-contribution slot expansion + new `node-icon` contract + host-enforced plugin lock.
|
|
1748
298
|
|
|
1749
|
-
**Spec changes** (`@skill-map/spec`):
|
|
1750
|
-
|
|
1751
|
-
- New contract `node-icon` in the closed catalog (`spec/view-contracts.md`, `spec/schemas/view-contracts.schema.json`). Single icon per node — small standalone marker rendered next to the card title. Manifest requires `icon`; payload optionally overrides per-node and may add `severity` (color tint, reusing the closed `Severity` palette) and `tooltip`. No counts, no labels — for chip + number use `node-counter`; for label + severity use `node-tag`; for an alert badge on the graph node corner use `node-alert`. The schema's `allOf` discriminator gains the `node-icon` branch (mirrors the existing `node-counter` rule that requires `icon`); the `Severity` `$def` description now lists `node-icon` alongside the other severity-aware contracts.
|
|
1752
|
-
- New BFF error code `locked` (HTTP 403) on `PATCH /api/plugins/:id` and `PATCH /api/plugins/:bundleId/extensions/:extensionId` — emitted when the target id is in the host's hardcoded lock-list. `GET /api/plugins` mirrors the same rule by stamping an optional `locked: true` flag on the affected items so UIs can render the toggle disabled. The flag is omitted when false. Documented in `spec/cli-contract.md` (item shape, error code source table, restart-required note).
|
|
1753
|
-
|
|
1754
|
-
**Implementation changes** (`@skill-map/cli`):
|
|
1755
|
-
|
|
1756
|
-
- New `src/kernel/config/locked-plugins.ts` — single source of truth for the host lock-list (today: `core/markdown`). Three layers enforce it: the CLI (`sm plugins enable|disable` rejects with exit 5 + a directed message; `--all` quietly skips locked targets), the BFF (`PATCH /api/plugins/...` returns 403 `locked`), and the runtime resolver (`plugin-resolver.ts` ignores any persisted `config_plugins` row or `settings.json` entry against a locked id and returns the installed default — defense in depth so "lock" stays unbreakable regardless of stored state). Lives under `src/kernel/config/` so all three layers share the import without breaking the kernel's "no driver knows about other drivers" rule. The lock is host-only and not user-editable by design — to remove an entry, edit the file.
|
|
1757
|
-
- `src/server/app.ts` — `TErrorCode` gains `'locked'`; `codeForStatus` maps HTTP 403 → `locked`. `src/server/i18n/server.texts.ts` — new `pluginsLocked` / `pluginsExtensionLocked` messages. `src/server/routes/plugins.ts` — `IPluginExtensionItem` and `IPluginListItem` gain optional `locked?: boolean`; both PATCH handlers reject locked targets with HTTPException 403 before the persistence step.
|
|
1758
|
-
- `src/built-in-plugins/rules/unknown-contract/index.ts`, `src/kernel/types/view-catalog.ts`, `src/cli/commands/plugins.ts` (`VIEW_CONTRACTS_CATALOG`) — new `node-icon` entry registered in every catalog the kernel/CLI publishes. The `unknown-contract` lint rule now considers `node-icon` known (no warning).
|
|
1759
|
-
- `src/cli/commands/plugins.ts` — bundle-detail rendering now qualifies extension names with `<bundleId>/` only when `granularity: 'extension'` (the toggle-able id surface); for `granularity: 'bundle'` the per-extension names stay bare since they are informational rather than user-tippable.
|
|
1760
|
-
- `src/cli/i18n/plugins.texts.ts` — new `pluginLocked` / `pluginLockedHint` strings.
|
|
1761
|
-
- `src/test/server-endpoints.test.ts` — two new cases: PATCH against `core/markdown` returns 403 `locked`, and `GET /api/plugins` stamps `locked: true` on the same row.
|
|
1762
|
-
- `src/built-in-plugins/extractors/at-directive/index.ts` — gains a `node-counter` view contribution (`count` / icon `@` / label `mentions` / `emitWhenEmpty: false`) and a one-line `ctx.emitContribution('count', ...)` after the extractor's main loop. First built-in extractor to emit a real contribution end-to-end, exercising the new card slots without any user plugin installed.
|
|
1763
|
-
|
|
1764
|
-
**UI changes** (private `ui/` workspace, ships bundled in `@skill-map/cli`):
|
|
1765
|
-
|
|
1766
|
-
- Three new slot ids in the closed UI catalog (`ui/src/app/slots/slot-config.ts`): `card.title.right` (cap 2, sits next to the node title), `card.subtitle.left` (cap 3, sits in the date stat row), `card.footer.right` (cap 5, sits alongside the hardcoded status icons in a new `.sm-gnode__footer-right-cluster` wrapper that owns the right-alignment). All three are `multi`/`priority`/`append`/`respectSeverity: true`. The card template (`ui/src/app/components/node-card/node-card.html` + `.css`) wires the three host instances; no slot is empty-collapsed (the host stays silent when no contribution targets it).
|
|
1767
|
-
- New `node-icon` renderer (`ui/src/app/renderers/node-icon/node-icon.ts`) — sized to match `.sm-gnode__chevron` (22×22, glyph 0.7rem) so the marker reads as a sibling of the chevron when both sit on the title row. Severity classes map to the same theme tokens the alert renderer uses.
|
|
1768
|
-
- The `node-counter` contract now also targets `card.footer.right` and `card.subtitle.left` (its informative slot list grows), so existing `node-counter` plugins automatically light up the new card slots without manifest changes.
|
|
1769
|
-
- `ui/src/app/components/view-contributions-host/view-contributions-host.ts` — the `DemoContributionsService` injection and "decorate" wiring are gone (the demo service was deleted; production sources only).
|
|
1770
|
-
- `ui/src/app/services/demo-contributions.ts` — **deleted**. The synthetic chips-for-slot-validation service finished its purpose now that real contributions land in the new slots.
|
|
1771
|
-
- `ui/src/app/views/graph-view/graph-view.{html,css,ts}` — the `.sm-gnode__marker-stub` placeholder svg + its CSS stub are dropped (the host underneath is the production surface; with `node-alert` plugins now demo-ready and `node-icon` shipping, the placeholder is redundant). The `resetLayout()` confirm dialog upgrades from `window.confirm()` to PrimeNG's `<p-confirmdialog>` (header / message / typed accept-and-reject buttons; mask gets the same global blur as the public site's cookie-consent banner).
|
|
1772
|
-
- Settings → Plugins (`ui/src/app/components/settings-modal/settings-plugins.{ts,html,css}`) — locked rows render an amber "Locked" pill with a `pi-lock` glyph next to the existing source/version/granularity tags; the `<p-toggleswitch>` stays mounted but disabled, so the user sees the current enabled state and a tooltip explaining why it cannot move. Both bundle and extension rows participate. New helpers `bundleToggleInteractive` / `extensionToggleInteractive` gate the row-click and sub-row-click handlers.
|
|
1773
|
-
- `ui/src/models/api.ts` — `IPluginExtensionApi` and `IPluginItemApi` gain optional `locked?: boolean` (mirrors the BFF wire shape).
|
|
1774
|
-
- `ui/src/i18n/settings.texts.ts` — new `lockedLabel` / `lockedTooltip`. `ui/src/i18n/graph-view.texts.ts` — `resetLayoutConfirm` reshapes from a single string into `{ header, message, accept, reject }` to feed the PrimeNG dialog.
|
|
1775
|
-
- `ui/src/app/debug-slots.css` — three new debug outline colors (orange / teal / purple) for the new slots.
|
|
1776
|
-
- `ui/src/styles.css` — global `.p-dialog-mask` style (blur + dim) so the new `<p-confirmdialog>` and any future `<p-dialog [modal]>` get the same glass look the public site uses for its cookie-consent banner.
|
|
1777
|
-
|
|
1778
|
-
**Repo plumbing**:
|
|
1779
|
-
|
|
1780
|
-
- `package.json` — `bff:dev` gets a `prebff:dev` step (`npm run bff:scan`) that runs `sm scan` against `fixtures/local-scope` first, so the dev BFF always boots with a populated DB.
|
|
1781
|
-
- `fixtures/local-scope/` — the curated demo-content directory shrinks to a minimal `DOC1.md` + `DOC2.md` pair (slim surface for testing the new view-contribution slots and the locked-plugin behaviour). The full curated content (claude / gemini agents, skills, commands, GEMINI.md, README.md, plus the `.gitignore` / `.skillmapignore`) is preserved as `fixtures/local-scope.full/` for cases that need the kitchen-sink fixture.
|
|
1782
|
-
|
|
1783
|
-
**ROADMAP changes**:
|
|
1784
|
-
|
|
1785
|
-
- §UI contribution system — Slot catalog list grows from 5 to 8; Contract catalog count flips from 10 to 11 with a one-line note on `node-icon`'s niche relative to `node-alert` and `node-counter`. Last-updated marker bumped to 2026-05-10.
|
|
1786
|
-
|
|
1787
|
-
**Pre-1.0 minor bump** per `spec/versioning.md` § Pre-1.0 — the spec change is additive (new contract entry + new optional field on the wire shape; existing `view-contracts.schema.json` consumers keep validating), the CLI change is additive (new error code, new slots, new contract, new lock surface — nothing removed), so both ride a normal minor.
|
|
1788
|
-
|
|
1789
|
-
## User-facing
|
|
1790
|
-
|
|
1791
|
-
**Three new card slots and a small per-node icon contract.** The graph card now reserves room for plugin-emitted markers in three new spots: a tiny icon next to the node title (right side), a small chip in the date row, and an extra cluster on the right of the footer alongside the status icons. Existing `node-counter` plugins automatically light up the new footer-right and subtitle slots — no manifest changes needed. Plugin authors can also pick a new contract, `node-icon`, for a single-glyph marker (e.g. language flag, "has audio", platform badge) when a counter or tag would be too noisy. See [`spec/view-contracts.md`](https://github.com/crystian/skill-map/blob/main/spec/view-contracts.md#node-icon) for the full schema.
|
|
1792
|
-
|
|
1793
|
-
**Plugins can now be locked by the host.** Settings → Plugins shows a "Locked" pill on plugins that the host marks as mandatory — today only `core/markdown` (the universal `.md` fallback). The toggle stays visible but disabled so it is obvious the lock is intentional, with a tooltip explaining why. `sm plugins disable core/markdown` now rejects the call with a clear message instead of writing a no-op override.
|
|
1794
|
-
|
|
1795
|
-
**Reset Layout uses a proper dialog.** The "Reset all node positions" action used to fire a browser-native `confirm()` popup; it now uses the same in-app dialog style as the rest of the UI (with a destructive-styled "Reset" button and a "Cancel" escape).
|
|
1796
|
-
|
|
1797
299
|
### Patch Changes
|
|
1798
300
|
|
|
1799
301
|
- 5600a60: Move `updateCheck.enabled` to user scope and add a reusable typed config helper. Settings UI's General section now exposes the toggle.
|
|
1800
302
|
|
|
1801
|
-
**Spec changes** (`@skill-map/spec`, patch):
|
|
1802
|
-
|
|
1803
|
-
- `spec/schemas/project-config.schema.json` — `updateCheck` description gains a "user-scope only" note: this key SHOULD live in `~/.skill-map/settings.json`; the reference implementation forces user-scope reads via `core/config/helper:USER_ONLY_KEYS` and `sm config set` rejects writes to the project layer. Project-layer entries from older installs continue to validate but are silently ignored at read time. Schema itself stays additive (no breaking change).
|
|
1804
|
-
- `spec/index.json` regenerated.
|
|
1805
|
-
|
|
1806
|
-
**Implementation changes** (`@skill-map/cli`, minor):
|
|
1807
|
-
|
|
1808
|
-
- New `src/core/config/dot-path.ts` — promoted from `cli/commands/config.ts`. Exports `getAtPath` / `setAtPath` / `deleteAtPath` / `assertSafeSegments` / `enumerateConfigPaths` / `FORBIDDEN_SEGMENTS` / `ForbiddenSegmentError`. Same prototype-pollution guards as before.
|
|
1809
|
-
- New `src/core/config/atomic-write.ts` — promoted `writeJsonAtomic` + `readJsonObjectOrEmpty` so any settings-mutating code path shares one implementation (atomic temp-then-rename, no half-written files on crash).
|
|
1810
|
-
- New `src/core/config/helper.ts` — typed read / write surface composed over `loadConfig` + the promoted helpers + AJV revalidation:
|
|
1811
|
-
- `readConfigValue<T>(key, { scope, cwd, homedir, default?, strict? })`
|
|
1812
|
-
- `writeConfigValue(key, value, { target, cwd, homedir })` — AJV-revalidates the post-mutation file before atomic write
|
|
1813
|
-
- `removeConfigValue(key, opts)` — returns `boolean` indicating whether a write happened
|
|
1814
|
-
- `getValueSource(key, opts)` — wrap of `loadConfig().sources` for "who set this"
|
|
1815
|
-
- `USER_ONLY_KEYS` — a small set (today: `updateCheck.enabled`) the helper hard-pins to the user / global layer regardless of caller intent. Reads force `scope: 'global'`; writes throw `UserOnlyKeyError` on `target: 'project'`.
|
|
1816
|
-
- `src/cli/util/update-check-banner.ts` — `isUpdateCheckEnabled` now calls `readConfigValue<boolean>('updateCheck.enabled', { scope: 'global', ..., default: true })`. A project-layer override is silently ignored (the helper forces scope:'global' for the key); the previous "project wins by precedence" behavior is gone for this key only.
|
|
1817
|
-
- `src/cli/commands/config.ts` — refactored to use `core/config/helper` + the promoted helpers. `ConfigSetCommand` and `ConfigResetCommand` surface `UserOnlyKeyError` and `ConfigValidationError` as exit-2 errors with directed messages (`CONFIG_TEXTS.userOnlyKeyRejection` / `userOnlyKeyRejectionHint`). ~150 lines of inlined dot-path / atomic-write / forbidden-segments code deleted.
|
|
1818
|
-
- `src/cli/i18n/config.texts.ts` — new `userOnlyKeyRejection` / `userOnlyKeyRejectionHint` strings.
|
|
1819
|
-
|
|
1820
|
-
**BFF additions** (`@skill-map/cli`):
|
|
1821
|
-
|
|
1822
|
-
- New `src/server/routes/preferences.ts` — `GET /api/preferences` returns the user-scope envelope `{ updateCheck: { enabled: boolean } }`; `PATCH /api/preferences` accepts a partial patch and writes through `writeConfigValue` with `target: 'user'`. Manual body validation (no Zod, mirroring `routes/plugins.ts`); errors flow through `app.onError` as `HTTPException(400)` with the existing `bad-query` envelope code. Mounted in `src/server/app.ts`.
|
|
1823
|
-
- `src/server/i18n/server.texts.ts` — six new strings for the preferences route's 400 envelopes (`preferencesBodyNotJson`, `preferencesBodyNotObject`, `preferencesBodyEmpty`, `preferencesUpdateCheckNotObject`, `preferencesUpdateCheckEnabledNotBoolean`, `preferencesPersistFailed`).
|
|
1824
|
-
|
|
1825
|
-
**UI additions** (private `ui/` workspace, ships bundled in `@skill-map/cli`):
|
|
1826
|
-
|
|
1827
|
-
- New `ui/src/app/components/settings-modal/settings-general.{ts,html,css}` — General section of the Settings modal. Today renders a single `Check for updates` toggle wired to `updateCheck.enabled`, but the component is built around a declarative `GENERAL_TOGGLES: ReadonlyArray<IGeneralToggleDef>` array — adding a future user-only preference (locale, theme, …) is one entry there plus one nested key in `SETTINGS_TEXTS.general.toggles`, no template / component change.
|
|
1828
|
-
- `ui/src/app/components/settings-modal/settings-modal.ts` — `general` section flips from `coming-soon` placeholder to `available`; registers `SettingsGeneral` in the imports list. The modal HTML adds the corresponding `@case ('general')` branch.
|
|
1829
|
-
- `ui/src/i18n/settings.texts.ts` — new `general` block with heading / intro / load-error / save-error prefixes + per-toggle label & description.
|
|
1830
|
-
- `ui/src/models/api.ts` — new `IPreferencesApi` and `IPreferencesPatchApi` types mirroring the BFF wire shape.
|
|
1831
|
-
- `ui/src/services/data-source/data-source.port.ts` — `IDataSourcePort` gains `getPreferences()` / `setPreferences(patch)`. `RestDataSource` implements them via the new BFF route; `StaticDataSource` returns the shipped default for `getPreferences()` and rejects `setPreferences()` with `code: 'demo-readonly'`.
|
|
1832
|
-
- Two pre-existing test stubs (`ui/src/app/app.spec.ts`, `ui/src/app/views/graph-view/graph-view.spec.ts`) extended with the two new methods so the `IDataSourcePort` mock satisfies the contract.
|
|
1833
|
-
|
|
1834
|
-
**Tests**:
|
|
1835
|
-
|
|
1836
|
-
- New `src/test/config-helper.test.ts` — coverage for `readConfigValue` / `writeConfigValue` / `removeConfigValue` / `getValueSource`: regular precedence, `USER_ONLY_KEYS` ignoring project layer, `UserOnlyKeyError` rejection on project-target writes, idempotent remove, schema-violation rejection (`ConfigValidationError`), prototype-pollution guard.
|
|
1837
|
-
- New `src/test/preferences-route.test.ts` — boots `createServer()` against a tempdir cwd / homedir; covers default `GET` envelope, `PATCH` round-trip writes to user layer (NOT project), and 400 responses for bad body / empty body / wrong type.
|
|
1838
|
-
- `src/test/update-check.test.ts` — extended with one case asserting a project-layer `updateCheck.enabled: false` is ignored at read time (banner still prints).
|
|
1839
|
-
|
|
1840
|
-
**Pre-1.0 minor bump on `@skill-map/cli`** — the read-behavior change for `updateCheck.enabled` is observable to any user who previously wrote the key into a project file. Documented in the "user-facing" section below. The spec change is a doc-only patch (description text only; schema unchanged).
|
|
1841
|
-
|
|
1842
|
-
## User-facing
|
|
1843
|
-
|
|
1844
|
-
**Update-check is now a user preference.** Whether you see "Update available" notifications no longer depends on the project you are scanning. The toggle moved to **Settings → General** in the UI; the CLI equivalent is `sm config set -g updateCheck.enabled <bool>`. `sm config set` (without `-g`) now rejects this key with a clear "rerun with -g" error so you never write it to the wrong file by accident.
|
|
1845
|
-
|
|
1846
|
-
If you previously had `updateCheck.enabled: false` in `<project>/.skill-map/settings.json`, that override is now **ignored** — re-set the value with `-g` (or untick the toggle in Settings → General) to make it stick across projects.
|
|
1847
|
-
|
|
1848
303
|
## 0.19.0
|
|
1849
304
|
|
|
1850
305
|
### Minor Changes
|
|
1851
306
|
|
|
1852
307
|
- 3376a75: spec 0.18.0 — universal markdown fallback as a built-in Provider. The format-named generic kind `markdown` moves out of the per-vendor Provider catalogs (claude / gemini) into a dedicated built-in `core/markdown` Provider. Markdown is provider-agnostic — no vendor owns the universal `.md` format — and bundling the fallback as a regular Provider under the `core` group preserves the spec invariant that no extension is privileged. The kernel orchestrator now dedups files across the multi-Provider walk so each path is offered to AT MOST one `classify`: vendor Providers retain priority on files inside their territory, and `core/markdown` (registered LAST) picks up exactly the orphan `.md` files no vendor claimed — files at the project root, under `.claude/hooks/`, `notes/`, `CLAUDE.md`, `GEMINI.md`, or anywhere else outside a known vendor path. The fallback can be disabled via `sm plugins disable core/markdown` (consistent with every other extension under `core`); orphan markdown then becomes silently invisible, matching pre-0.18.0 behaviour.
|
|
1853
308
|
|
|
1854
|
-
**Spec changes** (`spec/architecture.md`): new §Provider · dispatch order and the universal markdown fallback documents the iteration contract (vendor Providers first → `core/markdown` LAST), the path-dedup invariant, and the user-disable escape hatch. `spec/db-schema.md` `Node.kind` row updated to reflect the new ownership map. `spec/conformance/cases/orphan-markdown-fallback.json` (new) locks the contract end-to-end via a multi-Provider fixture asserting that `.claude/agents/reviewer.md` lands as kind `agent` (claude) and `ARCHITECTURE.md` lands as kind `markdown` (core-markdown). `spec/conformance/coverage.md` rows 4 (`scan-result.schema.json`) and 11 (`frontmatter/base.schema.json`) flip 🟢 covered via the new case.
|
|
1855
|
-
|
|
1856
|
-
**Implementation changes** (`@skill-map/cli`): new `src/built-in-plugins/providers/core-markdown/` (provider + schema). `markdown` kind removed from claude and gemini provider catalogs; their `classify` no longer returns `'markdown'` for any path. `src/kernel/orchestrator.ts` adds a per-scan `Set<path>` to dedup across the multi-Provider walk. The `core` bundle gains `coreMarkdownProvider` (granularity stays `extension` — disable-able like every other core item).
|
|
1857
|
-
|
|
1858
|
-
**Breaking** (per the pre-1.0 minor convention — see CONTRIBUTING.md / `spec/versioning.md` §Pre-1.0): the `Node.provider` value for files at `notes/`, `.claude/hooks/`, `CLAUDE.md`, and arbitrary root-level `.md` files changes from `'claude'` (or `'gemini'` for `GEMINI.md`) to `'markdown'`. Downstream consumers that filtered nodes by `provider === 'claude' && kind === 'markdown'` need to query `kind === 'markdown'` only.
|
|
1859
|
-
|
|
1860
309
|
- f0ddae0: Move the cross-vendor Extractors out of the `claude` plugin bundle and into `core`, and rename `frontmatter` → `annotations` to reflect the post-Step 9.6 reality that the canonical home for those structured references is the sidecar `.sm` `annotations:` block (Decision #125), not the markdown frontmatter.
|
|
1861
310
|
|
|
1862
|
-
**Qualified-id changes**
|
|
1863
|
-
|
|
1864
|
-
- `claude/frontmatter` → `core/annotations`
|
|
1865
|
-
- `claude/slash` → `core/slash`
|
|
1866
|
-
- `claude/at-directive` → `core/at-directive`
|
|
1867
|
-
|
|
1868
|
-
The `claude` bundle now contains only `claudeProvider` (path classification + frontmatter parser). The Extractors moved into `core` (`granularity: 'extension'`), so each is now independently toggleable via `sm plugins disable core/<id>`. Previously these extractors lived under the `claude` bundle (`granularity: 'bundle'`) and could only be removed by disabling the whole Claude integration — the same `gemini` and `agent-skills` Provider bundles already reused them implicitly with an apologetic comment in `built-ins.ts`.
|
|
1869
|
-
|
|
1870
|
-
**Why now.** The three Extractors are universal:
|
|
1871
|
-
|
|
1872
|
-
- `slash` matches `/<command>` (every coding-agent platform — Claude, Gemini, Cursor, Aider — uses slash commands).
|
|
1873
|
-
- `at-directive` matches `@<handle>` with both GitHub-style (`@scope/name`) and namespace-style (`@ns:verb`) forms.
|
|
1874
|
-
- `annotations` (née `frontmatter`) reads `requires` / `related` / `supersedes` / `supersededBy` / `conflictsWith`, all defined in the skill-map spec, not in Claude's conventions; the canonical source moved to the sidecar in Step 9.6 with a transitional fallback to legacy frontmatter `metadata:`.
|
|
1875
|
-
|
|
1876
|
-
Keeping them under `claude/` was deuda histórica from when Claude was the only Provider. Moving them to `core` resolves the apologetic Gemini comment and matches the architectural reality.
|
|
1877
|
-
|
|
1878
|
-
**Surface changes**
|
|
1879
|
-
|
|
1880
|
-
- `src/built-in-plugins/extractors/frontmatter/` → `src/built-in-plugins/extractors/annotations/`. Module export `frontmatterExtractor` → `annotationsExtractor`. `pluginId: 'claude'` → `'core'`. Docstring rewritten so the sidecar is the canonical surface and the legacy fallback is documented as transitional.
|
|
1881
|
-
- `src/built-in-plugins/extractors/{slash,at-directive}/index.ts` — `pluginId: 'claude'` → `'core'`.
|
|
1882
|
-
- `src/built-in-plugins/built-ins.ts` — three Extractors moved out of the `claude` bundle (now Provider-only) into `core`. The apologetic comment in the `gemini` bundle is gone (reuse is now structural). Top-level docstring rewritten to describe the new bundle layout.
|
|
1883
|
-
- `spec/architecture.md` § A.6 — namespace description updated to make `core/` the home of cross-vendor Extractors and vendor bundles strictly the Provider home.
|
|
1884
|
-
- `spec/plugin-author-guide.md` § Qualified extension ids — built-in inventory table reflects the new ids; § Granularity table updated to use `claude/claude` as the bundle-granularity rejection example.
|
|
1885
|
-
- `spec/db-schema.md` § `scan_extractor_runs` — example qualified id updated.
|
|
1886
|
-
- `spec/schemas/extensions/base.schema.json` — qualified-id description example updated.
|
|
1887
|
-
- `src/built-in-plugins/README.md` — bundle table + descriptions updated.
|
|
1888
|
-
- `ROADMAP.md` and `.changeset/view-contributions-system.md` — adopter mentions cross-reference the rename.
|
|
1889
|
-
- Tests: `src/test/built-ins-modes.test.ts`, `src/test/plugin-runtime-branches.test.ts`, `src/test/plugins-cli.test.ts`, `src/test/kernel.test.ts`, `src/built-in-plugins/extractors/extractors.test.ts`, `src/built-in-plugins/rules/rules.test.ts`, `src/built-in-plugins/formatters/ascii/ascii.test.ts`, `src/built-in-plugins/rules/validate-all/validate-all.test.ts`, `ui/src/app/components/linked-nodes-panel/linked-nodes-panel.spec.ts`, `ui/src/services/data-source/static-data-source.spec.ts` — qualified-id catalogue, `pluginId` assertions, fixture `sources` arrays, and the bundle-granularity rejection test all updated to the new ids and describe-block names.
|
|
1890
|
-
|
|
1891
|
-
**Migration**
|
|
1892
|
-
|
|
1893
|
-
- Persisted `config_plugins` rows referencing the old qualified ids (none of the moved Extractors had a useful bundle-granularity disable target, but if any user explicitly enabled / disabled `claude/<id>` it now no-ops; redo the toggle against `core/<id>`).
|
|
1894
|
-
- The scan caches (`scan_extractor_runs`, `node_enrichments`, `scan_contributions`) self-revalidate: rows keyed by the old qualified id `claude/<id>` quietly become orphan and are swept on the next scan; new rows land under `core/<id>`. No migration code required.
|
|
1895
|
-
|
|
1896
|
-
**Out of scope.** The legacy `metadata:` frontmatter fallback inside the `annotations` Extractor stays in this bump to keep the diff to "rename + move". A follow-up bump removes it and tightens the docstring once the migration is confirmed complete across observed projects.
|
|
1897
|
-
|
|
1898
|
-
**Pre-1.0 minor bump.** Per `spec/versioning.md` § Pre-1.0 and `AGENTS.md`, breaking changes ship as minors while a workspace is in `0.Y.Z`.
|
|
1899
|
-
|
|
1900
311
|
- b3ba3de: Drop the four denormalised fields (`title`, `description`, `stability`, `version`) from the public `Node` surface. The DB columns survive as indexing surface; the JSON wire shape and TypeScript `Node` interface no longer carry them.
|
|
1901
312
|
|
|
1902
|
-
The kernel used to project those four into `Node.{title,description,stability,version}` from their canonical sources (`frontmatter.{name,description}` and `sidecar.annotations.{stability,version}`) so consumers had a single flat read surface. With the inspector slot redesign incoming and the explicit decision to read directly from the canonical surfaces, the alias became redundant: same data, two paths, one of them unnecessary indirection.
|
|
1903
|
-
|
|
1904
|
-
The DB columns (`scan_nodes.{title,description,stability,version}`) stay so SQL-backed verbs (`sm list --sort-by`, faceted listings) keep their indexing fast path. The persistence layer projects the columns at write time from the canonical sources rather than from kernel-set Node fields. That keeps SQL ergonomic without polluting the API.
|
|
1905
|
-
|
|
1906
|
-
**Surface changes**
|
|
1907
|
-
|
|
1908
|
-
- `spec/schemas/node.schema.json` — `title` / `description` / `stability` / `version` removed from the property list. The schema's curated public shape now matches the runtime `Node` interface.
|
|
1909
|
-
- `src/kernel/types.ts` — `Node` interface drops the four fields. `Stability` type stays (used by extension manifests).
|
|
1910
|
-
- `src/kernel/orchestrator.ts` — `buildNode()` no longer populates the dropped fields; `applyAnnotationsOverlay()` removed (its only job was to set `node.{stability,version}` from the sidecar, now done at persistence-projection time).
|
|
1911
|
-
- `src/kernel/adapters/sqlite/scan-persistence.ts` — `nodeToRow()` projects the four columns from `node.frontmatter` and `node.sidecar?.annotations` via three small helpers (`pickString`, `pickStability`, `pickIntegerVersion`).
|
|
1912
|
-
- `src/kernel/adapters/sqlite/scan-load.ts` — `rowToNode()` no longer rehydrates the four fields onto Node. Storage adapter consumers that need them read the row directly.
|
|
1913
|
-
- `src/cli/commands/show.ts` — `collectNodeFields()` projects render-time via a new `projectAnnotationFields(node)` helper. Trio of single-purpose pickers added (`pickNonEmptyString`, `pickStabilityFromAnnotation`, `pickIntegerVersionFromAnnotation`) keep complexity ≤ 8.
|
|
1914
|
-
- `src/cli/commands/export.ts`, `src/built-in-plugins/formatters/ascii/index.ts` — `pickTitle()` reads `frontmatter.name` directly.
|
|
1915
|
-
- `src/built-in-plugins/rules/validate-all/index.ts` — `toNodeForSchema()` projection drops the four fields (they're no longer in `node.schema.json`).
|
|
1916
|
-
- `ui/src/models/api.ts` — `INodeApi` drops the four fields. The unused `TStability` import is gone.
|
|
1917
|
-
- `ui/src/services/collection-loader.ts` — `projectNode()` no longer falls back to `api.{title,description}`; reads directly from `frontmatter.{name,description}`.
|
|
1918
|
-
|
|
1919
|
-
**Tests** — fixtures and assertions across `node-enrichments.test.ts`, `render-sanitize-invariant.test.ts`, `scan-incremental.test.ts`, `server-query-adapter.test.ts`, `sidecar-reader.test.ts`, and the conformance case `sidecar-end-to-end.json` updated. The `node-enrichments` test uses the dropped fields as opaque sentinels to verify enrichment buffer mechanics; those sites cast through `unknown as Partial<Node>` with an explanatory comment — the persistence layer JSON-serialises the bag verbatim, so the round-trip works regardless of the strict Node typing.
|
|
1920
|
-
|
|
1921
|
-
**Migration** — consumers that read `node.title` migrate to `node.frontmatter?.name`; same shape for `description` (`frontmatter.description`), `stability` (`sidecar.annotations.stability`), and `version` (`sidecar.annotations.version`). DB queries that filter or sort by these columns work unchanged.
|
|
1922
|
-
|
|
1923
|
-
Pre-1.0 minor bump per `spec/versioning.md` § Pre-1.0.
|
|
1924
|
-
|
|
1925
313
|
- 22f4439: Reduce the Extractor extension kind to **deterministic-only**. The `mode` field is removed from `extractor.schema.json`; `IExtractor` no longer carries `mode`; `IExtractorContext` no longer exposes `ctx.runner`. `Extractor` joins `Provider` and `Formatter` as an extension that sits on the deterministic scan path; LLM-driven enrichment of a node is now strictly an **Action** concern, queued through the job subsystem.
|
|
1926
314
|
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
**Surface changes**
|
|
1930
|
-
|
|
1931
|
-
- `spec/schemas/extensions/extractor.schema.json` — `mode` removed.
|
|
1932
|
-
- `spec/architecture.md` — capability matrix updated (Extractor → deterministic-only); `§Extractor · enrichment layer` rewritten; the stability note documents that pre-1.0 narrowing a kind from dual-mode to single-mode is permitted as a minor bump.
|
|
1933
|
-
- `spec/plugin-author-guide.md` — probabilistic tag-inferrer example replaced with a deterministic frontmatter-tag example; six-categories table updated; `ctx.runner` mention removed for Extractors.
|
|
1934
|
-
- `spec/db-schema.md` — `node_enrichments.{stale, body_hash_at_enrichment, is_probabilistic}` documented as **reserved-but-inert** (always `0` for Extractor writes); kept on the row for a future Action-issued probabilistic enrichment revision so the persistence contract does not need a migration when that revision lands.
|
|
1935
|
-
- `spec/cli-contract.md` — `sm refresh <node>` and `sm refresh --stale` no longer reference the prob-stub state; `--stale` is a no-op in this revision.
|
|
1936
|
-
- `src/kernel/extensions/extractor.ts`, `src/kernel/orchestrator.ts` — `mode` and `runner` removed; the orchestrator's enrichment record always sets `isProbabilistic: false`.
|
|
1937
|
-
- `src/cli/commands/refresh.ts`, `src/cli/i18n/refresh.texts.ts` — prob-skip path removed; `Persisted N enrichment row(s)` replaces `Persisted N deterministic enrichment row(s)`.
|
|
1938
|
-
- `src/built-in-plugins/extractors/*/index.ts` — five built-in extractors no longer declare `mode: 'deterministic'`.
|
|
1939
|
-
- `src/migrations/001_initial.sql`, `src/kernel/adapters/sqlite/schema.ts` — comments updated; columns retained (greenfield, no migration; the row shape is forward-compatible with the future revision).
|
|
1940
|
-
- `src/test/built-ins-modes.test.ts` — invariant flips: extractors must NOT declare `mode` (matching Provider / Formatter).
|
|
1941
|
-
- `src/test/node-enrichments.test.ts` — Test (d) removed (prob-extractor body-change → stale-flag), `buildProbEnricher` helper removed; the merge contract test (e) keeps hand-built stale rows so the helper's filter behaviour stays pinned for the future revision.
|
|
1942
|
-
|
|
1943
|
-
**Pre-1.0 minor bump.** Per `spec/versioning.md` §Pre-1.0 and `AGENTS.md`, breaking changes ship as minors while a workspace is in `0.Y.Z`. No released consumer depended on Extractor `mode: 'probabilistic'` (zero in built-ins, fixtures, conformance, e2e); the future Action-issued enrichment revision opens a clean path for the same use case from inside the job lifecycle.
|
|
1944
|
-
|
|
1945
|
-
**Out of scope (deferred to Phase B / Step 11).** How a probabilistic Action writes data persistent to a node (enrichment, sidecar, etc.). Today an Action emits a `report_json` plus an optional `TActionWrite[]` array (`{ kind: 'sidecar' }` is the only variant); the future revision will extend the discriminated union with `{ kind: 'enrichment' }` so a probabilistic Action can populate `node_enrichments` directly. That change is independent of this one and lands when the first real probabilistic Action (skill-summarizer or equivalent) needs it.
|
|
1946
|
-
|
|
1947
|
-
- 40d0a81: Two small wire enrichments that the new Settings modal needs:
|
|
1948
|
-
|
|
1949
|
-
**`GET /api/plugins` items now carry `description?: string`** — both at the bundle level and inside each `extensions[]` entry. The bundle's value is sourced from `IBuiltInBundle.description` for built-ins (now a required field on the type — every built-in bundle declares its summary inline at `built-in-plugins/built-ins.ts`) and from `plugin.json#/description` for user plugins. Each extension entry's value comes from its own manifest's `description` per `IExtensionBase` (`extensions/base.schema.json#/properties/description`). The SPA's Settings list renders the descriptions as muted secondary text and folds them into the substring-search index alongside the ids, so authors can ship discoverable copy without needing a separate docs round-trip.
|
|
1950
|
-
|
|
1951
|
-
**`GET /api/health` now carries `cwd: string` and `dbPath: string`** — both absolute. `cwd` is the project root the BFF resolves against (`runtimeContext.cwd`); `dbPath` mirrors `IServerOptions.dbPath`. The companion `db: 'present' | 'missing'` field still reports whether the file exists; the new fields tell the operator where to find it. Surfaced so the SPA's About panel can render "you are looking at <project>" plus the DB location without a second endpoint.
|
|
1952
|
-
|
|
1953
|
-
Both additions are forward-compatible: existing health clients ignore the new fields, and existing plugins UI consumers tolerate the absence of `description` (it's optional on the wire).
|
|
315
|
+
- 40d0a81: Two small wire enrichments that the new Settings modal needs.
|
|
1954
316
|
|
|
1955
317
|
- 40d0a81: Add `POST /api/scan` so the SPA's topbar refresh button can trigger a manual scan + persist without dropping the user back to the CLI. The same `runScanWithRenames` + `persistScanResult` pipeline the watcher uses runs end-to-end inside the BFF, broadcasting `scan.started` then `scan.completed` over `/ws` so every connected client refreshes — `CollectionLoaderService`'s reactive subscription already handles the SPA side.
|
|
1956
318
|
|
|
1957
|
-
**Mutex**
|
|
1958
|
-
|
|
1959
|
-
A process-level latch (`src/server/scan-mutex.ts`) prevents two POSTs from racing each other. Only the manual POST holds the latch; the watcher's debounced batches stay outside it because `createWatcherRuntime` already serializes its own batches and SQLite WAL serializes the persist transactions, so a watcher × POST race is benign at the storage layer. The latch's job is honest user feedback ("Scan in progress, retry shortly") when their second click arrives before the first scan resolves, not global serialization.
|
|
1960
|
-
|
|
1961
|
-
**Errors**
|
|
1962
|
-
|
|
1963
|
-
- `409 scan-busy` (new envelope code) — another POST is already in flight. The 409 status is shared with `POST /api/sidecar/bump`'s `sidecar-fresh`, so `app.onError` discriminates by message prefix (`scan-busy:` vs `sidecar-fresh:`); both prefixes were already conventions in the catalog.
|
|
1964
|
-
- `400 bad-query` — server booted with `--no-built-ins` or `--no-plugins`. Same gate the existing `?fresh=1` GET applies, for the same reason: a partial pipeline would persist a misleading DB.
|
|
1965
|
-
- `500 db-missing` — project DB absent. Read paths degrade to the empty shape; mutations cannot.
|
|
1966
|
-
|
|
1967
|
-
**UI** (private workspace, no separate version bump)
|
|
1968
|
-
|
|
1969
|
-
- Topbar refresh button (`pi pi-refresh`) sits between the theme toggle and the settings gear. Tooltip carries the same `X nodes · Y links` counts as the previous info icon. Click → `dataSource.runScan()`; the icon spins (`pi-spin`) and the button is `disabled` while the scan is in flight. Test id: `shell-refresh`.
|
|
1970
|
-
- New port method `IDataSourcePort.runScan(): Promise<IScanResultApi>` — `RestDataSource` posts to `/api/scan`; `StaticDataSource` rejects with `code: 'demo-readonly'` (the static bundle is immutable).
|
|
1971
|
-
- The button does NOT manually re-fetch from the loader after the response — the route's WS broadcast already triggers the loader's reactive refresh. The `await this.loader.load()` in the click handler is a belt-and-suspenders fallback for the demo path (no WS) and for races where the WS event fires before the POST promise resolves.
|
|
1972
|
-
|
|
1973
|
-
**Internal**
|
|
1974
|
-
|
|
1975
|
-
- `IScanRunOpts.emitterFactory` (new optional field on `core/runtime/scan-runner.ts`) — when set, the runner threads the supplied emitter into `runScanWithRenames` instead of building a stderr-bound progress emitter. The watcher already uses the same pattern; the BFF's `POST /api/scan` route now reuses it to plug the broadcaster.
|
|
1976
|
-
- `buildBroadcasterEmitter` in `src/server/watcher.ts` is now exported so the new route can wire the same emitter the watcher uses.
|
|
1977
|
-
|
|
1978
319
|
- 496fb72: Complete the `IAnalyzerContext.emitContribution` runtime channel and add `core/link-counts` built-in rule.
|
|
1979
320
|
|
|
1980
|
-
The view-contribution surface had a half-implemented seam: any extension's manifest could declare `viewContributions`, the catalog (`kernel.getRegisteredViewContributions()`) recognised Rule declarations, but `IAnalyzerContext` had no `emitContribution` callback so a Rule's `evaluate()` had no way to actually emit. Extending `IAnalyzerContext` with `emitContribution(nodePath, contributionId, payload)` completes the seam.
|
|
1981
|
-
|
|
1982
|
-
The first adopter is `core/link-counts` — a built-in Rule that emits two `node-counter` contributions per node (`linksOut`, `linksIn`) based on the post-merge graph. The data lives on `node.linksOutCount` / `node.linksInCount` already; the Rule projects it into the view contribution system so slot-aware UI surfaces (graph cards, inspector chips) render the counts uniformly with any plugin contribution. Skips emit when count is 0 to avoid empty panels.
|
|
1983
|
-
|
|
1984
|
-
External URL counts (`core/external-url-counter`) keep their existing extractor-emit path; this change adds a sibling Rule, not a refactor.
|
|
1985
|
-
|
|
1986
|
-
**Surface changes**
|
|
1987
|
-
|
|
1988
|
-
- `src/kernel/extensions/rule.ts` — `IAnalyzerContext.emitContribution(nodePath, contributionId, payload)` added.
|
|
1989
|
-
- `src/kernel/orchestrator.ts` — `runRules()` builds a per-rule emission buffer with the same validator + persist semantics as the Extractor path; `RunScanOptions` adds `viewContributions?` (parallel to `annotationContributions?`). The `readDeclaredContributions` helper is generalised from `IExtractor` to any extension that carries `viewContributions` (structural typing).
|
|
1990
|
-
- `src/built-in-plugins/rules/link-counts/index.ts` — new built-in.
|
|
1991
|
-
- `src/built-in-plugins/built-ins.ts` — `linkCountsRule` registered under `core` bundle; built-in count rises from 21 to 22 (and rules from 10 to 11).
|
|
1992
|
-
- `spec/architecture.md` § View contribution system → Emit path — Rule-emit signature documented alongside the Extractor signature; both routed to the same `scan_contributions` rows. The reserved `emitScopeContribution` for scope-stat is noted as still pending.
|
|
1993
|
-
|
|
1994
|
-
**Tests**
|
|
1995
|
-
|
|
1996
|
-
- `src/built-in-plugins/rules/link-counts/link-counts.test.ts` — unit tests for the rule's evaluate logic + integration test that runs the orchestrator end-to-end and asserts the persisted contribution rows.
|
|
1997
|
-
- `src/test/built-ins-modes.test.ts` — total built-ins count bumped 21 → 22.
|
|
1998
|
-
- `src/test/plugin-runtime-branches.test.ts` — composed.rules.length asserts bumped 10 → 11; rule id list updated.
|
|
1999
|
-
- `src/built-in-plugins/rules/rules.test.ts`, `src/built-in-plugins/rules/validate-all/validate-all.test.ts`, `src/test/unknown-field-rule.test.ts` — test contexts now supply a noop `emitContribution` (required field on the new `IAnalyzerContext`).
|
|
2000
|
-
|
|
2001
|
-
**Persistence**: no SQL migration. The `scan_contributions` table is agnostic to the emitting kind; Rule emissions land in the same rows as Extractor emissions. The orphan sweep + catalog sweep semantics keep working unchanged.
|
|
2002
|
-
|
|
2003
|
-
Pre-1.0 minor bump per `spec/versioning.md` § Pre-1.0.
|
|
2004
|
-
|
|
2005
321
|
- 40d0a81: Add a global Settings modal in the SPA with a Plugins section — the first user-facing surface for toggling installed plugins from the UI. Backed by two new BFF mutation endpoints and an enriched `GET /api/plugins` shape.
|
|
2006
322
|
|
|
2007
|
-
**BFF**
|
|
2008
|
-
|
|
2009
|
-
- `PATCH /api/plugins/:id` — toggle a granularity=`bundle` plugin's user override. Body `{ enabled: boolean }`. Persists to `config_plugins` via the same `IConfigPluginsPort.set` path the CLI's `sm plugins enable / disable` uses. Response: the projected list (same shape as `GET /api/plugins`) so callers replace state in one shot.
|
|
2010
|
-
- `PATCH /api/plugins/:bundleId/extensions/:extensionId` — qualified-id form for granularity=`extension` bundles (today: `core` plus any user plugin that opts in).
|
|
2011
|
-
- Granularity is enforced symmetrically: bundle-form against an extension-only bundle returns 400 `bad-query`; qualified-form against a bundle-only target returns the same. Unknown plugin / extension ids return 404 `not-found`. Missing project DB returns 500 `db-missing` (read-side endpoints still degrade to empty shapes; mutations cannot persist without a DB so they fail fast).
|
|
2012
|
-
- `GET /api/plugins` items now carry `granularity: 'bundle' | 'extension'` and an optional `extensions[]` array (present only for granularity=`extension` plugins) so the UI can render expandable per-extension toggles for `core` without a second round-trip.
|
|
2013
|
-
|
|
2014
|
-
**Restart caveat**
|
|
2015
|
-
|
|
2016
|
-
The loaded plugin runtime is boot-cached; toggle changes apply on the next `sm scan` or `sm serve` restart. The endpoint does NOT broadcast a WS event today. The Settings modal renders a persistent `<p-message severity="warn">` banner ("Restart required") so users aren't surprised when their toggle doesn't immediately re-render the graph.
|
|
2017
|
-
|
|
2018
|
-
**UI** (private workspace, no separate version bump)
|
|
2019
|
-
|
|
2020
|
-
- Gear icon in the topbar (`shell__actions`) opens a PrimeNG `p-dialog` modal. The modal is `@defer`-loaded so the Dialog + ToggleSwitch + Message chunks (~57 KB) only ride the wire on first open.
|
|
2021
|
-
- Each plugin row is one `p-toggleswitch` for granularity=`bundle`; granularity=`extension` rows expand to reveal per-extension toggles. Failure-mode plugins (`incompatible-spec`, `invalid-manifest`, `load-error`, `id-collision`) render with their reason and no toggle (toggling enabled doesn't unbreak a broken plugin).
|
|
2022
|
-
- Test ids per the project convention: `action-settings`, `settings-modal`, `settings-banner-restart`, `settings-row-<id>`, `settings-toggle-<id>`, `settings-bundle-expand-<id>`, `settings-extrow-<bundle>-<ext>`, `settings-ext-toggle-<bundle>-<ext>`.
|
|
2023
|
-
|
|
2024
|
-
**Decision: no hot-reload**
|
|
2025
|
-
|
|
2026
|
-
Toggling does not recompose the plugin runtime in-process. A hot-reload path would need to invalidate the kind registry, contributions registry, route-level decorators, and any in-flight scan; all for a modal that's used once or twice per session. The restart caveat is the spec'd contract; revisit if and when watcher-driven toggles become a common workflow.
|
|
2027
|
-
|
|
2028
323
|
- 68709b9: Sidecar schema cleanup: rename root block `for:` → `identity:` and drop the unused `hidden` field from the curated annotations catalog.
|
|
2029
324
|
|
|
2030
|
-
**Mental model.** A `.sm` sidecar is, conceptually, the annotations file for its `.md` node — every key under it is an annotation. The YAML root organises those annotations into structural blocks: `identity` (anchor + drift hashes), `annotations` (curated catalog), `audit` (timestamps), `settings` (reserved), and `<plugin-id>:` namespaces. The schema and docs now lead with that framing.
|
|
2031
|
-
|
|
2032
|
-
**`for:` → `identity:`.** The block was always semantically about anchoring the sidecar to its node and tracking drift hashes — `for:` was concise but cryptic and got mistaken for "metadata about the node". Renamed to `identity:` everywhere: schema, parser, store, bump action, scaffold helper, fixtures, docs, UI debug panel.
|
|
2033
|
-
|
|
2034
|
-
**`hidden` removed.** The curated catalog declared `annotations.hidden` for "exclude from default listings" but nothing in the runtime ever consumed it (no `--include-hidden` flag, no list filter). Dead spec surface. Dropped from the schema; the catalog now stands at **13 fields**. The matching UI rendering is gone too.
|
|
2035
|
-
|
|
2036
|
-
**Surface changes**
|
|
2037
|
-
|
|
2038
|
-
- `spec/schemas/sidecar.schema.json` — top-level `for` property renamed to `identity`; `required: ['for']` → `required: ['identity']`. Root description updated to lead with the "annotations file" mental model. `$defs.identity` was already named correctly; only the property reference moved.
|
|
2039
|
-
- `spec/schemas/annotations.schema.json` — `hidden` property removed. Description bumped from "load-bearing 14 fields" to "13 fields".
|
|
2040
|
-
- `spec/schemas/node.schema.json` — `Node.sidecar.root` description updated: reserved blocks list now reads `identity / annotations / settings / audit`; example sub-paths use `root.identity.*`.
|
|
2041
|
-
- `spec/architecture.md` — § Annotation system rewritten to lead with the mental model; identity contract uses `identity.path` / `identity.bodyHash` / `identity.frontmatterHash`. `display (hidden)` dropped from the curated-catalog enumeration.
|
|
2042
|
-
- `spec/cli-contract.md`, `spec/plugin-author-guide.md` — example sidecars use `identity:` blocks.
|
|
2043
|
-
- `spec/conformance/fixtures/**/*.sm` — three fixture sidecars updated.
|
|
2044
|
-
- `src/kernel/sidecar/parse.ts` — reads `root['identity']`; `IParsedSidecar` fields `forBodyHash` / `forFrontmatterHash` / `forPath` renamed to `identityBodyHash` / `identityFrontmatterHash` / `identityPath`.
|
|
2045
|
-
- `src/kernel/orchestrator.ts` — drift detection consumes the renamed fields.
|
|
2046
|
-
- `src/built-in-plugins/actions/bump/index.ts` — patch object emits `identity:` instead of `for:`.
|
|
2047
|
-
- `src/built-in-plugins/rules/unknown-field/index.ts` — `RESERVED_ROOT_BLOCKS` set updated.
|
|
2048
|
-
- `src/cli/commands/sidecar.ts` — `sm sidecar refresh` and `sm sidecar annotate` write the renamed block.
|
|
2049
|
-
- `ui/src/app/components/inspector-debug-panel/*` — `forBlock` / `IForBlock` renamed to `identityBlock` / `IIdentityBlock`.
|
|
2050
|
-
- `ui/src/app/components/annotations-panel/*` — `hidden` rendering removed (template, taxonomy section, texts catalog, spec).
|
|
2051
|
-
- All test fixtures (`src/test/**`, UI specs, e2e) updated to use `identity:` blocks.
|
|
2052
|
-
|
|
2053
|
-
**Migration**: every `.sm` file in the wild that uses the old `for:` block is now invalid against the schema. The right fix per node:
|
|
2054
|
-
|
|
2055
|
-
- Open the `.sm`.
|
|
2056
|
-
- Rename the top-level key from `for:` to `identity:` (no value changes).
|
|
2057
|
-
- Save.
|
|
2058
|
-
|
|
2059
|
-
A future `sm migrate` action could automate this; for now manual edit is the path. The kernel's parser will fail closed (`invalid-sidecar` issue) on a non-renamed file, so missed migrations surface at scan time.
|
|
2060
|
-
|
|
2061
|
-
Pre-1.0 minor bump per `spec/versioning.md` § Pre-1.0.
|
|
2062
|
-
|
|
2063
325
|
- 9f04fc2: Tags · Phase 1 (spec only): declare the dual-source tag system.
|
|
2064
326
|
|
|
2065
|
-
Skill-map's tag system is dual-source by design — author tags live in `frontmatter.tags` (in the `.md`, intrinsic categories the file's author wrote) and user tags live in `sidecar.annotations.tags` (in the `.sm`, the post-hoc tags whoever curates the project assigned). Both surfaces are first-class; searches and listings match the union, and consumers distinguish them with explicit attribution.
|
|
2066
|
-
|
|
2067
|
-
This phase lands the spec changes. Persistence (`scan_node_tags` table), BFF projection (`node.tags = { byAuthor, byUser }`), CLI (`sm list --tag <name> [--tag-source author|user]`), and UI rendering follow in subsequent phases.
|
|
2068
|
-
|
|
2069
|
-
**Surface changes**
|
|
2070
|
-
|
|
2071
|
-
- `spec/schemas/frontmatter/base.schema.json` — `tags` declared as a universal optional field (array of non-empty strings). Per-vendor schemas extend the base via `allOf` + `$ref` so every Provider's per-kind schema accepts it without redeclaration. The intent: author tags belong in the markdown frontmatter, recognised across every vendor.
|
|
2072
|
-
- `spec/schemas/annotations.schema.json` — `tags` description rewritten to clarify "user-supplied" semantics, point at `frontmatter.tags` as the sibling author surface, and document the dual-source posture (search union, optional `--tag-source` filter).
|
|
2073
|
-
- `spec/architecture.md` § Annotation system → new "Tags · dual-source" subsection — explains the split, the persistence projection into `scan_node_tags`, the wire shape `node.tags = { byAuthor, byUser }`, and the deliberate omission from the kernel `Node` interface (consistent with the post-decision-#2 posture of "no Node-level denormalisations").
|
|
2074
|
-
- `spec/db-schema.md` § Table catalog → new `scan_node_tags` entry — defines the table schema (`node_path`, `tag`, `source`), the PK, the `(tag)` index for search, and the replace-all-per-scan persistence semantics. Storage estimate documented (~7.5 KB for a 50-node project with avg 3 tags/node).
|
|
2075
|
-
- `spec/index.json` regenerated.
|
|
2076
|
-
|
|
2077
|
-
No code changes in this phase. The tag system is entirely declarative on the spec side until the next phases land the persistence + query implementation.
|
|
2078
|
-
|
|
2079
|
-
Pre-1.0 minor bump per `spec/versioning.md` § Pre-1.0.
|
|
2080
|
-
|
|
2081
327
|
- 89c1c17: Add an "update available" notification surface (CLI banner + UI chip).
|
|
2082
328
|
|
|
2083
|
-
A passive background check now compares the running `@skill-map/cli` against the latest version published on the npm registry (`https://registry.npmjs.org/@skill-map/cli/latest`). When a newer release is available the CLI prints a one-line banner at the END of every command (after the verb's own output, on stderr), and the UI shows a chip next to the existing "Beta" badge in the topbar that opens the npm package page in a new tab.
|
|
2084
|
-
|
|
2085
|
-
The check is throttled aggressively so it never feels intrusive:
|
|
2086
|
-
|
|
2087
|
-
- Banner fires **at most once per 24h** — `shownAt` is persisted alongside the cached latest version.
|
|
2088
|
-
- Registry probe fires **at most once per 24h** — `checkedAt` drives the refresh decision; the fetch runs AFTER the verb's output with a 1500ms `AbortController` timeout, so a slow / unreachable registry never delays a command.
|
|
2089
|
-
- Probe + banner are skipped entirely when ANY of the following hold (cheap short-circuits, evaluated in order):
|
|
2090
|
-
1. `process.env.SM_NO_UPDATE_CHECK === '1'`
|
|
2091
|
-
2. `process.env.CI` truthy (catches GitHub Actions, GitLab, CircleCI, Travis, etc.)
|
|
2092
|
-
3. `process.stderr.isTTY !== true` (pipes / redirects / non-interactive shells)
|
|
2093
|
-
4. project DB missing (`./.skill-map/skill-map.db` not present — no scope to read from)
|
|
2094
|
-
5. `updateCheck.enabled === false` in the effective settings
|
|
2095
|
-
|
|
2096
|
-
**Storage**
|
|
2097
|
-
|
|
2098
|
-
Cache state lives in the project DB on `config_preferences` under the key `_kernel.update-check`. Value is a JSON blob `{ latestVersion, checkedAt, shownAt }`. No new table, no migration. The `_kernel.` prefix marks the row as kernel-managed (not a `sm config set` user preference). Per-project scope was an explicit decision: the cache lives wherever the verb's project DB lives; users who only run `sm -g …` against a global DB get the same behaviour scoped to that DB.
|
|
2099
|
-
|
|
2100
|
-
**User opt-out**
|
|
2101
|
-
|
|
2102
|
-
`spec/schemas/project-config.schema.json` gains a top-level optional block:
|
|
2103
|
-
|
|
2104
|
-
```json
|
|
2105
|
-
"updateCheck": {
|
|
2106
|
-
"enabled": false
|
|
2107
|
-
}
|
|
2108
|
-
```
|
|
2109
|
-
|
|
2110
|
-
Default is `true`. Set in either `.skill-map/settings.json` (project) or `~/.skill-map/settings.json` (user) via the existing layered loader.
|
|
2111
|
-
|
|
2112
|
-
**BFF**
|
|
2113
|
-
|
|
2114
|
-
New route `GET /api/update-status` returns the cached payload:
|
|
2115
|
-
|
|
2116
|
-
```json
|
|
2117
|
-
{
|
|
2118
|
-
"current": "0.18.0",
|
|
2119
|
-
"latest": "0.19.0",
|
|
2120
|
-
"isOutdated": true,
|
|
2121
|
-
"checkedAt": 1715212345678,
|
|
2122
|
-
"shownAt": 1715212345678
|
|
2123
|
-
}
|
|
2124
|
-
```
|
|
2125
|
-
|
|
2126
|
-
The route is read-only — it never triggers a probe; it reflects whatever the CLI cached on its last run. Always returns 200; missing-cache shape is `{ current, latest: null, isOutdated: false, checkedAt: null, shownAt: null }`.
|
|
2127
|
-
|
|
2128
|
-
**UI**
|
|
2129
|
-
|
|
2130
|
-
A new chip rendered next to the existing "Beta" stamp in the shell topbar (`ui/src/app/app.html`), gated by `updateCheck.isOutdated()`. The chip is an `<a>` to the npm package page (target `_blank`, `rel="noopener noreferrer"`), with a tooltip showing the upgrade command. Service is one-shot at boot — no polling, no dismiss button.
|
|
2131
|
-
|
|
2132
|
-
**Surface changes**
|
|
2133
|
-
|
|
2134
|
-
- `src/core/update-check/index.ts` — pure helpers (`fetchLatestVersion`, `compareVersions`, `isOutdated`) + types. No `process.env` reads.
|
|
2135
|
-
- `src/kernel/storage/update-check.ts` — Kysely-backed cache helpers against `config_preferences`.
|
|
2136
|
-
- `src/kernel/ports/storage.ts` — `preferences` namespace added to `StoragePort` (`loadUpdateCheckCache` / `saveUpdateCheckCache`).
|
|
2137
|
-
- `src/kernel/adapters/sqlite/storage-adapter.ts` — wires the namespace into the adapter.
|
|
2138
|
-
- `src/cli/util/update-check-banner.ts` — `maybeRunUpdateCheck` glue. Owns every env / settings read.
|
|
2139
|
-
- `src/cli/i18n/update-check.texts.ts` — texts catalog for the banner (two-line block per `context/cli-output-style.md` §3.1b).
|
|
2140
|
-
- `src/cli/entry.ts` — post-`cli.run()` hook between the verb's exit code resolution and `process.exit`.
|
|
2141
|
-
- `src/server/routes/update-status.ts` — read-only BFF route.
|
|
2142
|
-
- `src/server/app.ts` — registers the route after `registerContributionsRoutes`.
|
|
2143
|
-
- `spec/schemas/project-config.schema.json` — `updateCheck.enabled` block (additive, optional).
|
|
2144
|
-
- `spec/index.json` — regenerated by `npm run spec`.
|
|
2145
|
-
- `ui/src/app/services/update-check.ts` — signal-based service; one-shot fetch.
|
|
2146
|
-
- `ui/src/i18n/update-check.texts.ts` — UI catalog.
|
|
2147
|
-
- `ui/src/models/api.ts` — `IUpdateStatusResponseApi` next to the existing BFF DTO mirrors.
|
|
2148
|
-
- `ui/src/app/app.ts`, `ui/src/app/app.html`, `ui/src/app/app.css` — chip wiring.
|
|
2149
|
-
|
|
2150
|
-
**Tests**
|
|
2151
|
-
|
|
2152
|
-
- `src/test/update-check.test.ts` — 29 tests covering semver compare, fetch (with stubbed `globalThis.fetch` + AbortError), storage round-trip, and end-to-end `maybeRunUpdateCheck` matrix (banner emits / refresh fires / each bail condition).
|
|
2153
|
-
- `src/test/server-update-status-endpoint.test.ts` — 2 BFF integration tests (populated cache + missing DB).
|
|
2154
|
-
- `ui/src/app/app.spec.ts` — 2 chip tests (rendered when outdated, absent otherwise).
|
|
2155
|
-
|
|
2156
|
-
**Persistence**: no SQL migration. The `config_preferences` table is already in `001_initial.sql`.
|
|
2157
|
-
|
|
2158
|
-
Pre-1.0 minor bump per `spec/versioning.md` § Pre-1.0 — schema additions are minor.
|
|
2159
|
-
|
|
2160
329
|
- 5624143: view contribution catalog reorg + `node-counter` narrowing + `priority` field. Pre-1.0 minor per `spec/versioning.md`; covers what would otherwise be a catalog-major bump.
|
|
2161
330
|
|
|
2162
|
-
**Slot rename to `surface.location.name` pattern** — `card.chip` → `card.footer.left`, `inspector.body` → `inspector.body.panel`, `topbar.indicator` → `topbar.actions.indicator`, `graph.node.marker` → `graph.node.alert`. `inspector.header.badge` already conformed. The closed slot enum stays the same shape (5 entries) but every id now self-describes its surface and position; mounts in the UI moved to match where ambiguous (e.g. `card.footer.left` now lives inside `.sm-gnode__footer` next to the hardcoded stats, the position the new name promises).
|
|
2163
|
-
|
|
2164
|
-
**Contract rename to `<scope>-<form>` pattern** — the catalog drops the `per-` prefix on per-node entries and tightens semantics on two: `per-node-counter` → `node-counter`, `per-node-tag` → `node-tag`, `per-node-breakdown` → `node-breakdown`, `per-node-records` → `node-records`, `per-node-tree` → `node-tree`, `per-node-key-values` → `node-key-values`, `per-node-link-list` → `node-link-list`, `per-node-summary` → `node-markdown` (semantic narrowing — was always the LLM-style markdown text, name now says so), `node-marker` → `node-alert`, `scope-summary` → `scope-stat`. Catalog size unchanged (still 10 contracts). `spec/view-contracts.md`, the per-contract payload schemas in `spec/schemas/view-contracts.schema.json`, the prose references in `spec/architecture.md` and `spec/plugin-author-guide.md` all renamed in lockstep.
|
|
2165
|
-
|
|
2166
|
-
**`node-counter` contract narrowed** — payload is now `{ value, severity?, tooltip? }`; the inline `label` is gone (manifest `label` is metadata only — used by docs / `sm plugins doctor` and as `aria-label` for screen readers). `icon` is now REQUIRED on the manifest declaration via JSON-Schema `if/then` on `contract === 'node-counter'`. Renderers align with the host card's stat row (icon + value, no separate label line).
|
|
2167
|
-
|
|
2168
|
-
**New `priority` field on `IViewContribution`** — optional number, default 100. Slots configured with `order: 'priority'` sort contributions ASC by this value with alphabetical tie-break by qualified id. Plugins use it to suggest where their contribution belongs relative to others sharing the same slot; the slot has the final say (it can keep `'alphabetical'` / `'fifo'` ordering and ignore the field). Kernel publishes the value through `IRegisteredViewContribution.priority` so the UI can pick it up at lookup time.
|
|
2169
|
-
|
|
2170
|
-
**Pre-1.0 breaking note**: every plugin manifest authored against the v1 catalog needs the contract / slot ids retyped, plus `icon` if it declared a `node-counter`. `sm plugins upgrade` is the structural migration verb; no automatic rename rules are registered (the renames are mechanical search-and-replace).
|
|
2171
|
-
|
|
2172
331
|
- 0702381: spec 0.19.0 — view contribution system. Plugin extensions can now surface per-node typed data in the UI by picking a `contract` name from a closed kernel-published catalog (10 contracts: `per-node-counter`, `per-node-tag`, `per-node-breakdown`, `per-node-records`, `per-node-tree`, `per-node-key-values`, `per-node-link-list`, `per-node-summary`, `node-marker`, `scope-summary`) and emitting payloads at scan time via `ctx.emitContribution(id, payload)`. Plugin authors NEVER ship UI code, never write JSON Schema, and never pick UI slots — they declare intent via `viewContributions: Record<string, IViewContribution>` on each extension manifest, and the closed catalog of input-types (10 entries: `string-list`, `single-string`, `boolean-flag`, `integer`, `enum-pick`, `enum-multipick`, `path-glob`, `regex`, `secret`, `key-value-list`) drives the `settings:` declarations on the plugin manifest root. New CLI verbs `sm plugins create`, `sm plugins contracts list`, `sm plugins upgrade` make scaffolding the canonical entry point.
|
|
2173
332
|
|
|
2174
|
-
**Spec additions**: `spec/view-contracts.md` + `spec/input-types.md` (catalog references); `spec/schemas/view-contracts.schema.json` + `spec/schemas/input-types.schema.json` (closed-enum AJV catalogs with per-contract payload schemas); `spec/architecture.md` § View contribution system (kernel surface, persistence semantics, BFF surface, isolation rules, soft-warning rules, catalog versioning); `spec/plugin-author-guide.md` § View contributions (tutorial); `spec/db-schema.md` § `scan_contributions` (orphan + catalog sweep + upsert semantics, NOT pure replace-all). `spec/schemas/extensions/base.schema.json` extended with `viewContributions` map; `spec/schemas/plugins-registry.schema.json` extended with manifest-root `settings` + `catalogCompat` semver field + `incompatible-catalog` plugin status; `spec/schemas/api/rest-envelope.schema.json` extended with `contributionsRegistry` field on payload-bearing variants + `contributions.registered` envelope kind. `spec/schemas/extensions/extractor.schema.json` relaxes `emitsLinkKinds` minItems so pure-contributions extractors (`emitsLinkKinds: []`) load cleanly.
|
|
2175
|
-
|
|
2176
|
-
**Implementation additions** (`@skill-map/cli`): kernel surface (`IExtensionBase.viewContributions`, `IExtractorCallbacks.emitContribution`, `IAnalyzerContext.viewContributions`, `kernel.{get,set}RegisteredViewContributions`); orchestrator emit-time wiring with AJV per-contract payload validation (off-contract → `extension.error` event + silent drop, mirror of `emitLink`); persistence layer (`scan_contributions` table in `src/migrations/001_initial.sql` per the migrations-consolidation greenfield fold, `src/kernel/adapters/sqlite/contributions.ts` adapter, sweep semantics in `replaceAllScanContributions`); BFF (3 endpoints under `/api/contributions/*`, `contributionsRegistry` on every payload-bearing envelope, `contributions[]` per node on `/api/scan` + `/api/nodes`); CLI verbs (`PluginsCreateCommand` scaffolder + `PluginsContractsListCommand` + `PluginsUpgradeCommand` migration shell); two built-in adopters (`core/annotations` — landed here as `claude/frontmatter` and renamed during the cross-vendor extractor move to `core/` — → `per-node-key-values`; `core/external-url-counter` → `per-node-counter`); two soft-warning rules (`core/unknown-contract`, `core/contribution-orphan`).
|
|
2177
|
-
|
|
2178
|
-
**UI additions** (private `ui/` workspace): closed slot catalog (`ui/src/app/slots/slot-config.ts`) + closed renderer catalog (`ui/src/app/contracts/contract-renderer-map.ts`) + 10 renderer Angular components + slot host (`<sm-view-contributions-host>`) + contributions registry service. Mounts in inspector header badge + body + node card chip slots. Data path extensions: `IContributionApi` + `IContributionsRegistryApi`; `INodeApi.contributions[]`; `INodeView.contributions[]` (projection layer); `IDataSourcePort.lookupContribution`; rest data source ingests `contributionsRegistry` on every fetch + lazy lookup endpoint.
|
|
2179
|
-
|
|
2180
|
-
**AGENTS.md** gained two new rules: "Externalized texts, not internationalized" (the project text-externalizes via per-component `*.texts.ts` catalogs, no Transloco / locale dictionaries; plugin manifests follow the same posture — `label`/`emptyText` are plain English strings, not `{ en, es }` records) and "Plugins are scaffolded, not hand-written" (`sm plugins create` is the canonical entry point, hand-writing supported but discouraged because the scaffolder catches invalid contract picks at author time vs at load).
|
|
2181
|
-
|
|
2182
|
-
**Persistence semantics — important behavioral change for `scan_contributions`**: NOT pure replace-all. The watcher's cached pass leaves the buffer empty for cached nodes (no `extract()` → no `emitContribution`), so a wipe-all would drop valid prior rows on every watcher boot. The persist runs three passes inside the same transaction: (1) orphan sweep — drops rows whose `node_path` is NOT in `livePaths`; (2) catalog sweep — drops rows whose qualified id is NOT in `registeredContributionKeys`; (3) upsert — `INSERT ... ON CONFLICT DO UPDATE SET payload_json = excluded.payload_json` for every buffer row. Cached nodes' rows survive. Disabled-plugin rows are swept on next scan once the catalog reflects the disable. See `spec/db-schema.md` § `scan_contributions` for the full contract.
|
|
2183
|
-
|
|
2184
|
-
**Breaking** (per the pre-1.0 minor convention): plugins that hand-rolled an extension manifest with `viewContributions: {...}` against a now-deprecated contract name will surface as `incompatible-catalog` and need `sm plugins upgrade <id>` (no migrations registered for catalog v1.0.0; the verb is structural). New plugin-load status `'incompatible-catalog'` joins the existing six.
|
|
2185
|
-
|
|
2186
333
|
## 0.18.0
|
|
2187
334
|
|
|
2188
335
|
### Minor Changes
|
|
2189
336
|
|
|
2190
337
|
- 305e75a: Step 9.6.3 — built-in `bump` Action + sidecar write channel. Adds the deterministic `core/bump` Action and the new `ISidecarStore` port (with the `FilesystemSidecarStore` impl) that materialises Action-returned `{ kind: 'sidecar', path, changes }` payloads against on-disk `.sm` files. The Action stays pure — `invoke()` computes a deep-merge patch and returns it; the Store re-reads the on-disk sidecar, deep-merges (objects RECURSE; arrays REPLACE), revalidates the merged result against `sidecar.schema.json` + `annotations.schema.json`, and writes back inside a path-keyed critical section using the standard atomic `.tmp + rename` pattern.
|
|
2191
338
|
|
|
2192
|
-
**Runtime contract extension.** `IAction` gains an optional `invoke<TInput, TReport>(input, ctx): IActionResult<TReport>` method (additive — actions that don't implement it keep working). `IActionResult` carries `report: TReport` plus an optional `writes?: TActionWrite[]` array; today `TActionWrite` is the discriminated union `{ kind: 'sidecar'; path; changes }`, with future write kinds (storage rows, plugin KV) landing additively. `IActionContext` introduces `{ node, nodeAbsolutePath, invoker, now }` so Actions can stamp `audit.lastBumpedBy` from a CLI-supplied `'cli'` (or `'plugin:<id>'`) value without doing any IO themselves.
|
|
2193
|
-
|
|
2194
|
-
**`bump` Action behaviour matrix** (Decision #1 of the brief): stale node (or no sidecar yet) → patch increments `annotations.version`, refreshes `for.{bodyHash, frontmatterHash}`, populates `audit.lastBumpedAt` + `lastBumpedBy` (and on first-time creation also `audit.createdAt` + `audit.createdBy`); fresh node without `force` → refusal (`{ ok: false, reason: 'fresh' }`, no writes); fresh node with `force: true` → silent no-op (`{ ok: true, noop: true }`, no writes — intended for the upcoming batch flow `sm bump --pending --staged`).
|
|
2195
|
-
|
|
2196
|
-
**Spec.** `sidecar.schema.json` now formalises the `audit:` sub-shape (`lastBumpedAt` / `lastBumpedBy` / `bumpReason` / `createdAt` / `createdBy`, all optional at the property level, `additionalProperties: true`); the `bump` Action atomically fills `lastBumpedAt` + `lastBumpedBy` on every bump and `createdAt` + `createdBy` on first creation. The conformance fixture at `spec/conformance/fixtures/sidecar-example/agent-example.sm` now carries a populated audit block. New `spec/schemas/bump-report.schema.json` declares the deterministic report shape — distinct from `report-base.schema.json` which carries LLM-specific `confidence` + `safety` and is therefore wrong for deterministic Actions.
|
|
2197
|
-
|
|
2198
|
-
**Greenfield + pre-1.0 versioning.** The `audit:` block formalisation is technically a breaking surface (a previously-permissive `additionalProperties: true` block now declares typed properties), but per the greenfield-no-versioning policy and the pre-1.0 versioning rule (every breaking change ships as a minor while the workspace is `0.Y.Z`), this lands as a minor on both `@skill-map/spec` and `@skill-map/cli`. No released consumer depended on the prior shape; the empty `audit: {}` documented in 9.6.2 is forward-compatible with the new declarations.
|
|
2199
|
-
|
|
2200
|
-
Coverage matrix row 26 stays 🟡 partial (notes updated to mention the audit-block formalisation); row 28 lands as 🔴 missing — direct conformance case for `bump-report.schema.json` ships together with the `sm bump --json` CLI verb in Step 9.6.4. Implementation tests at `src/test/sidecar-store.test.ts` and `src/test/bump-action.test.ts` cover the runtime behaviour today.
|
|
2201
|
-
|
|
2202
339
|
- 79dfdea: Step 9.6 catalog curation. The annotation surface settled in Steps 9.6.1 → 9.6.7 went through a UX review on 2026-05-07; 16 fields with no clear value or that duplicated other surfaces were dropped from the curated catalog, and the per-bump rationale field `audit.bumpReason` was rolled back together with its CLI / BFF inputs.
|
|
2203
340
|
|
|
2204
|
-
**Annotations dropped (16).** `spec/schemas/annotations.schema.json` no longer documents `provides`, `type`, `author`, `created`, `updated`, `category`, `keywords`, `icon`, `color`, `priority`, `readme`, `examplesUrl`, `github`, `homepage`, `linkedin`, `twitter`. The schema stays `additionalProperties: true`, so legacy / opaque keys still ride through; the built-in `unknown-field` rule warns on any of them as a typo. Greenfield, no migration: no released consumer depended on these in `annotations.*`.
|
|
2205
|
-
|
|
2206
|
-
**Annotations kept (15).** `version`, `stability`, `supersedes`, `supersededBy`, `requires`, `conflictsWith`, `related`, `authors`, `license`, `source`, `sourceVersion`, `released`, `tags`, `hidden`, `docsUrl`. The load-bearing versioning + supersession block is unchanged.
|
|
2207
|
-
|
|
2208
|
-
**`audit.bumpReason` rolled back.** Removed from `spec/schemas/sidecar.schema.json#/$defs/audit/properties`. CLI: `--reason` flag dropped from `sm bump`; `IBumpInput.reason` removed; `buildAudit` no longer emits the field. BFF: `reason` removed from the `POST /api/sidecar/bump` JSON body schema. Tests assert the audit block surfaces `lastBumpedAt` / `lastBumpedBy` only on a bump-without-reason path. The audit block stays `additionalProperties: true` so the field can ride opaquely if a legacy sidecar carries it; the schema just doesn't curate it anymore. R6's mitigation set drops the bumpReason reference — the contract is now "bump rewrites the file; narrative goes in the `.md` body, which is never touched".
|
|
2209
|
-
|
|
2210
|
-
**deepMerge null-as-delete primitive retained.** The kernel's `FilesystemSidecarStore.deepMerge` still treats a `null` patch value as a delete sentinel. No current caller after the bumpReason rollback, but the primitive is architecturally sound for future Actions that need per-write erase semantics. JSDoc updated to flag this; the unit tests stay (renamed the example field name from `bumpReason` to a neutral placeholder).
|
|
2211
|
-
|
|
2212
|
-
**Fixtures + conformance.** All `.sm` files in `fixtures/local-scope/` and `fixtures/demo-scope/` trimmed to the curated set; the kitchen-sink reference fixture trimmed to 15 annotations + the load-bearing supersession block (kept the `example-plugin:` namespace). Conformance fixture `spec/conformance/fixtures/sidecar-end-to-end/agents/stale.sm` trimmed (removed `type` + `author`) so the `unknown-field` rule's expected warning count matches the case file's `issuesCount: 2` assertion. Structural sample at `spec/conformance/fixtures/sidecar-example/agent-example.sm` trimmed to the curated catalog.
|
|
2213
|
-
|
|
2214
|
-
**Spec docs.** `spec/architecture.md` `## Annotation system` section: catalog list updated, `audit.bumpReason` line dropped, bump-field-set stability clause rewritten to enumerate the four current audit fields with `additionalProperties: true` documented. `spec/cli-contract.md`: `--reason` removed from the two `sm bump` rows; the worked `.sm` round-trip example trailing line replaced; `POST /api/sidecar/bump` body shape no longer carries `reason`. `spec/conformance/coverage.md` row 27 updated. `spec/index.json` regenerated.
|
|
2215
|
-
|
|
2216
|
-
**ROADMAP.md.** §Step 9.6 carries a `Catalog curation 2026-05-07` note enumerating the dropped + kept sets; R6's mitigation list drops the bumpReason mention; the abridged decisions and §Frontmatter standard catalog descriptors updated.
|
|
2217
|
-
|
|
2218
|
-
**Out of scope.** UI display tiering (4-tier vendor/plugin layout, inspector sections) is a separate task delegated to app-agent later. Kernel `Node.author` denormalization stays untouched — `author` rides on `additionalProperties: true` for users who want to keep writing it informally; the read path persists the value but the field is no longer curated.
|
|
2219
|
-
|
|
2220
341
|
- 79dfdea: Step 9.6 catalog-curation follow-up (2026-05-07): remove the vestigial `Node.author` denormalisation end-to-end. The 9.6.2 migration sourced `Node.author` from `annotations.author`; the 2026-05-07 catalog curation dropped `author` from `annotations.schema.json`, leaving the column without a canonical source. The earlier curation changeset said `Node.author` would stay untouched; this follow-up reverses that — keeping a denorm path for an opaque `additionalProperties: true` rider was inconsistent with the curated catalog and added persistence + display surface for a field the schema no longer documents.
|
|
2221
342
|
|
|
2222
|
-
**Spec.** `spec/schemas/node.schema.json` no longer documents the `author` property. `spec/architecture.md` § "Read path (denormalization)" lists two columns instead of three (`stability`, `version`). `spec/db-schema.md` § scan_nodes drops the `author` row. `spec/index.json` regenerated.
|
|
2223
|
-
|
|
2224
|
-
**Kernel.** `Node.author` removed from the runtime type and `IScanNodesTable.author` removed from the SQLite schema. `applyAnnotationsOverlay` no longer reads `annotations['author']`; the cache-hit reset in `runScan` no longer clears `node.author`; `buildNode` no longer initialises the field. New migration `003_drop_node_author.sql` issues `ALTER TABLE scan_nodes DROP COLUMN author;` (SQLite 3.35+ — node:sqlite ships ≥ 3.45). `scan-persistence.ts` and `scan-load.ts` no longer write or read the column.
|
|
2225
|
-
|
|
2226
|
-
**CLI.** `sm show` no longer renders an `author:` row in the node header. `SHOW_TEXTS.nodeFieldAuthor` removed. The built-in `validate-all` rule's `toNodeForSchema` no longer copies `author` over to the wire shape it validates against.
|
|
2227
|
-
|
|
2228
|
-
**Tests.** `sidecar-reader.test.ts`, `storage.test.ts`, `node-enrichments.test.ts`, `server-query-adapter.test.ts` updated. The fresh-sidecar fixture in `sidecar-reader.test.ts` no longer writes an `author:` annotation (rides on `additionalProperties: true` if anyone keeps writing it informally; not a denorm-source anymore).
|
|
2229
|
-
|
|
2230
|
-
**Greenfield.** No automatic salvage path. Pre-9.6.2 rows had the column reset to NULL by migration 002. Anyone who later wrote `author:` in their `.sm` keeps the value verbatim under `scan_nodes.annotations_json`; the `unknown-field` rule warns on the key as a typo guard.
|
|
2231
|
-
|
|
2232
|
-
**Out of scope.** UI display tiering (4-tier vendor/plugin layout, inspector sections) remains a separate task; the UI's `INodeApi.author` optional field is not consumed by any service / view, and the BFF will simply never produce it after this change. Rip-out lands with the inspector tiering pass.
|
|
2233
|
-
|
|
2234
343
|
- 670eaa4: Catalog refinement: drop `released` from the curated annotation catalog. The catalog now stands at **14 fields**.
|
|
2235
344
|
|
|
2236
|
-
**Rationale.** `released` (lifecycle "officially released") was redundant with `audit.lastBumpedAt` (activity timestamp written by every `bump`) for this project's flow — the spec doesn't distinguish official release from bump, so a separate lifecycle field added confusion without unique semantics. Activity timestamp now lives exclusively in the reserved `audit:` block.
|
|
2237
|
-
|
|
2238
|
-
**Spec.** `spec/schemas/annotations.schema.json` removes the `released` property; description updated to "load-bearing 14 fields" and clarifies that the activity timestamp lives in `audit.lastBumpedAt`. `spec/architecture.md` listing updated. `spec/index.json` regenerated.
|
|
2239
|
-
|
|
2240
|
-
**Fixtures.** `fixtures/local-scope/.claude/agents/kitchen-sink.sm` drops the `released:` line (only fixture that carried it). Hashes unaffected — `for.bodyHash` and `for.frontmatterHash` are over the `.md`, not the `.sm`.
|
|
2241
|
-
|
|
2242
|
-
**UI.** Card `daysAgo` (`ui/src/app/components/node-card/node-card.ts`) and inspector `headerDays` (`ui/src/app/views/inspector-view/inspector-view.ts`) both switch to reading `sidecar.root.audit.lastBumpedAt` — the canonical activity timestamp now flowing on the wire after R15. Annotations panel drops the `released` row from the lifecycle section (`ILifecycleSection.released` field, parsing, render, and the `texts.fields.released` strings in both `inspector-view.texts.ts` and `annotations-panel.texts.ts`).
|
|
2243
|
-
|
|
2244
|
-
**Backward compatibility.** `additionalProperties: true` stays — sidecars carrying `released:` continue to validate (the field rides through as an unknown opt-in key). The built-in `unknown-field` rule will warn on it post-curation, matching the pattern for the 16 fields dropped in the 2026-05-07 catalog curation.
|
|
2245
|
-
|
|
2246
|
-
Greenfield-permitted breaking surface (no released consumers depend on the prior shape) shipping as a `@skill-map/spec` minor per the pre-1.0 rule.
|
|
2247
|
-
|
|
2248
345
|
- d12f7d2: Two new built-in Providers — `gemini` and the vendor-neutral `agent-skills` — plus a tighter `IProvider.classify()` contract so multiple Providers can scan the same roots without colliding.
|
|
2249
346
|
|
|
2250
|
-
**`gemini`**
|
|
2251
|
-
|
|
2252
|
-
- Walks Google's Gemini CLI on-disk conventions: `.gemini/agents/*.md` → `agent`, `.gemini/skills/<name>/SKILL.md` → `skill`, `.gemini/**/*.md` and `GEMINI.md` → `markdown` (the format-named generic fallback).
|
|
2253
|
-
- Per-kind frontmatter schemas absorb Google's documented contracts verbatim:
|
|
2254
|
-
- `agent.schema.json` — 7 vendor-specific fields (`kind: local|remote`, `tools`, `mcpServers`, `model`, `temperature`, `max_turns`, `timeout_mins`) per https://geminicli.com/docs/core/subagents/. `name` + `description` come from spec base.
|
|
2255
|
-
- `skill.schema.json` — thin `allOf` extension of base; Google's documented Skill format requires only `name` + `description`.
|
|
2256
|
-
- `markdown.schema.json` — fallback, base only.
|
|
2257
|
-
- UI: Gemini purple + Google blue palette; `pi-sparkles` icon for agents.
|
|
2258
|
-
- Conformance: `basic-scan` case + `minimal-gemini` fixture (agent + skill + GEMINI.md).
|
|
2259
|
-
- Bundle granularity: `bundle` (the Provider is the bundle's only extension today; future Gemini-namespaced extractors land here).
|
|
2260
|
-
|
|
2261
|
-
**`agent-skills`**
|
|
2262
|
-
|
|
2263
|
-
- Vendor-neutral Provider that owns the open-standard path `.agents/skills/<name>/SKILL.md` jointly adopted by Anthropic, OpenAI (Codex), and Google (Gemini). Single kind: `skill`. Reclaims the path so vendor-specific Providers don't have to — the day a Codex Provider lands, the spec's `provider-ambiguous` rule fires zero times because the open-standard path already has a home.
|
|
2264
|
-
- UI: deliberately neutral slate (`#64748b` / `#94a3b8`) so the kind reads as "vendor-agnostic" at a glance.
|
|
2265
|
-
- Conformance: `basic-scan` case + `minimal-agent-skills` fixture.
|
|
2266
|
-
|
|
2267
|
-
**`IProvider.classify()` returns `string | null`**
|
|
2268
|
-
|
|
2269
|
-
- Old contract: `classify(path, fm): string` — must return a kind name. Old Claude returned `'markdown'` for non-`.claude/` paths; with one Provider this was fine, with multiple Providers it doubles up the same path (SQLite UNIQUE on `scan_nodes.path` violation).
|
|
2270
|
-
- New contract: `classify(...) → string | null`. `null` means "not my file"; the orchestrator skips it. Each Provider claims its own conventions and disclaims the rest.
|
|
2271
|
-
- Claude: claims `.claude/{agents,commands,skills}/`, `.claude/**/*.md` (catch-all under `.claude/`), `notes/**/*.md`, and `CLAUDE.md`. Disclaims everything else.
|
|
2272
|
-
- Gemini: claims `.gemini/{agents,skills}/`, `.gemini/**/*.md`, and `GEMINI.md`. Disclaims everything else.
|
|
2273
|
-
- agent-skills: claims `.agents/skills/<name>/SKILL.md` only.
|
|
2274
|
-
|
|
2275
|
-
**Per-Provider node painting (consumer-side fix from Phase A)**
|
|
2276
|
-
|
|
2277
|
-
- `node-card` now binds `[style.--accent]="providerAccent()"` so a node sourced from a non-primary Provider paints with its own Provider's color (e.g. a Gemini-sourced `agent` renders in `#9b72cb` even when Claude is the primary contributor to the `agent` kind). Primary Providers fall through to the existing `--sm-kind-<kind>` CSS var without an inline override.
|
|
2278
|
-
- `KindRegistryService.providersOf(kind)` returns the per-Provider sub-map; `node-card.providerAccent()` reads `entry.providers[node.provider]?.color`.
|
|
2279
|
-
|
|
2280
|
-
**Conformance fixture migration**
|
|
2281
|
-
|
|
2282
|
-
- All Claude conformance fixtures (`minimal-claude`, `rename-high-{before,after}`, `orphan-{before,after}`) move from project-relative `agents/` / `commands/` / `skills/` paths to `.claude/agents/` / `.claude/commands/` / `.claude/skills/` so the Claude Provider's strict `classify()` claims them.
|
|
2283
|
-
- `spec/conformance/fixtures/sidecar-end-to-end/agents/` → `.claude/agents/`. The matching `sidecar-end-to-end.json` case asserts the new paths.
|
|
2284
|
-
- `spec/conformance/cases/plugin-missing-ui-rejected.json` updated to assert all 3 built-in providers in the result (was 1).
|
|
2285
|
-
- `spec/conformance/fixtures/plugin-missing-ui/.skill-map/plugins/bad-provider/provider.js` now declares the `markdown` kind to mirror Claude's catalog.
|
|
2286
|
-
- The bad-provider fixture is unchanged in intent — still rejects manifests missing `ui` — but uses the `markdown` kind to align with the Provider's current catalog.
|
|
2287
|
-
|
|
2288
|
-
**Tests**
|
|
2289
|
-
|
|
2290
|
-
- 8 new Gemini provider tests, 6 new agent-skills tests, 2 new node-card per-Provider painting tests. The bulk of the existing tests update to the new fixture paths; built-in modes / pluginId tests now allow the `gemini` and `agent-skills` pluginIds; the cross-provider count assertions in `plugin-runtime-branches.test.ts` (3 providers when no toggles) pick up the two new bundles.
|
|
2291
|
-
- Total: 1098 cli tests + 307 ui tests, all green.
|
|
2292
|
-
|
|
2293
|
-
**Backward compatibility**
|
|
2294
|
-
|
|
2295
|
-
Greenfield (`feedback_greenfield_no_versioning.md`): the `classify()` signature change is breaking for any plugin Provider in the wild — no released consumer holds a Provider implementation today. Stays minor pre-1.0 per `versioning.md` § Pre-1.0. Existing local DBs rescan to pick up the new kind layout (no migration ships).
|
|
2296
|
-
|
|
2297
347
|
- e17ff6a: Per-user favorites. The UI gains a subtle heart button on every node card (stacked under the chevron in the actions cluster) plus a "Favorites only" toggle in the filter-bar that hides while the user has zero favorites. State persists across `sm scan` and `sm db reset` because favorites live in a new `state_node_favorites` table (zone `state_`).
|
|
2298
348
|
|
|
2299
|
-
**Spec.** New table in `spec/db-schema.md`: `state_node_favorites(node_path PRIMARY KEY, favorited_at INTEGER NOT NULL)`. Listed in the rename heuristic's FK migration set so renaming a favorited file preserves the mark. New optional `Node.isFavorite: boolean` field in `spec/schemas/node.schema.json` — decorated by the BFF on every `/api/nodes` and `/api/nodes/:pathB64` response; consumers that don't recognise it MUST ignore it.
|
|
2300
|
-
|
|
2301
|
-
**BFF.** Two new endpoints, both idempotent:
|
|
2302
|
-
|
|
2303
|
-
- `PUT /api/favorites/:pathB64` — 204 on success, 404 when the path is not in the persisted scan.
|
|
2304
|
-
- `DELETE /api/favorites/:pathB64` — 204 always (un-favoriting an already-unmarked path is a no-op).
|
|
2305
|
-
|
|
2306
|
-
The `/api/nodes` route loads the favorites set once per request via a tiny `SELECT node_path FROM state_node_favorites` query and decorates each emitted node with `isFavorite` by `Set` membership in memory — no SQL JOIN against `scan_nodes`. Cost is `O(favorites)` per request (typical projects pin a handful of nodes).
|
|
2307
|
-
|
|
2308
|
-
**Storage.** New `port.favorites.{ set, unset, listPaths }` namespace on `StoragePort`. `migrateNodeFks` (rename heuristic) updates `state_node_favorites.node_path` alongside the other `state_*` tables; `findStrandedStateOrphans` scans it too. New `IMigrateNodeFksReport.nodeFavorites` counter; `sm orphans reconcile` summary line includes the count.
|
|
2309
|
-
|
|
2310
|
-
**Migration `005_node_favorites.sql`** creates the table. No backfill — fresh installs and existing scopes alike start with zero favorites.
|
|
2311
|
-
|
|
2312
|
-
**UI.** New `<sm-node-card>` `[isFavorite]` input + `(favoriteToggle)` output (path + new value). The graph view wires the output to `CollectionLoaderService.toggleFavorite(path, value)` which (a) flips the local store optimistically, (b) fires the BFF call, (c) rolls back on failure. The filter-bar's "Favorites only" toggle is gated by a `hasAnyFavorites` computed signal so the row stays uncluttered for first-time users; the toggle stays visible if the filter is currently active so the user can disable it after un-favoriting the last node.
|
|
2313
|
-
|
|
2314
|
-
**Out of scope (deliberate).**
|
|
2315
|
-
|
|
2316
|
-
- No CLI verb (`sm fav`). Favoriting is a visual / personal preference; the CLI surface stays focused on lifecycle verbs.
|
|
2317
|
-
- No WebSocket broadcast on favorite toggle. Multi-tab sync (`favorite.set` / `favorite.unset` events) can land later if the use case surfaces.
|
|
2318
|
-
- Demo (`StaticDataSource`) rejects favorite mutations with `code: 'demo-readonly'` — the optimistic flip rolls back, surfacing the read-only stance to the user.
|
|
2319
|
-
|
|
2320
|
-
Tests: `src/test/favorites-storage.test.ts` (CRUD + rename heuristic + collision report — 6 cases), `src/test/server-favorites-endpoint.test.ts` (PUT/DELETE happy paths, 404, idempotency, isFavorite decoration on the list and single-node routes — 9 cases). UI: 5 new cases in `node-card.spec.ts` and 4 in `collection-loader.spec.ts`.
|
|
2321
|
-
|
|
2322
349
|
- 864e373: Phase 0 of the multi-provider rollout: rename the Claude Provider's fallback kind `note` → `markdown`.
|
|
2323
350
|
|
|
2324
|
-
The fallback kind classifies any markdown file under a Claude scope that does not match a more specific path (`.claude/agents/`, `.claude/commands/`, `.claude/skills/`). The previous name `note` overcommitted to a content role; the file is really just "generic markdown without a specific role". The new name reflects the _format_. Convention going forward: format-named kinds (`markdown`, future `toml`, future `json`) apply ONLY as the generic fallback. A file that IS a specific role (e.g. a Codex agent in TOML) classifies as `agent`, not `toml` — specific roles prevail over format naming.
|
|
2325
|
-
|
|
2326
|
-
This rename is mechanical and pure. No behavior, validation, or persistence change beyond the kind identifier.
|
|
2327
|
-
|
|
2328
|
-
**`@skill-map/spec`**
|
|
2329
|
-
|
|
2330
|
-
- `schemas/extensions/provider.schema.json` description updated (the spec doesn't hardcode kind names; only prose mentions changed).
|
|
2331
|
-
- `schemas/node.schema.json` prose updated.
|
|
2332
|
-
- `schemas/summaries/note.schema.json` → `schemas/summaries/markdown.schema.json` (renamed file, `$id` updated, `title: SummaryNote` → `SummaryMarkdown`, prose updated).
|
|
2333
|
-
- `db-schema.md`, `README.md`, `conformance/coverage.md` — prose updates.
|
|
2334
|
-
- `spec/index.json` regenerated (new file path + hash, old entry removed).
|
|
2335
|
-
|
|
2336
|
-
**`@skill-map/cli`**
|
|
2337
|
-
|
|
2338
|
-
- `built-in-plugins/providers/claude/index.ts` — `kinds.note` → `kinds.markdown`. `defaultRefreshAction` `claude/summarize-note` → `claude/summarize-markdown`. `ui.label: 'Notes'` → `'Markdown'`. Color and icon unchanged. `classify()` fallback `'note'` → `'markdown'`.
|
|
2339
|
-
- `built-in-plugins/providers/claude/schemas/note.schema.json` → `markdown.schema.json` (renamed file, `$id` updated, `title: FrontmatterNote` → `FrontmatterMarkdown`).
|
|
2340
|
-
- `kernel/types.ts` — `NodeKind` union: `'note'` → `'markdown'`.
|
|
2341
|
-
- `built-in-plugins/formatters/ascii/index.ts` and `cli/commands/export.ts` — `KIND_ORDER` updated.
|
|
2342
|
-
- All hardcoded `'note'` test fixtures and assertions across `src/test/`, `src/built-in-plugins/`, and the Claude conformance suite (`basic-scan.json`, `coverage.md`) flipped to `'markdown'`.
|
|
2343
|
-
- Conformance fixture `spec/conformance/fixtures/plugin-missing-ui/.skill-map/plugins/bad-provider/provider.js` (the negative-test fixture mirroring Claude shape) renamed alongside.
|
|
2344
|
-
|
|
2345
|
-
**UI (`ui/`, private workspace, no version bump per AGENTS.md `ui/` policy)**
|
|
2346
|
-
|
|
2347
|
-
- `models/node.ts` — `ISummaryNote` → `ISummaryMarkdown` with `kind: 'markdown'`. Union member updated.
|
|
2348
|
-
- `node-card.ts/.html`, `graph-layout.ts/.spec.ts`, `collection-loader.ts/.spec.ts`, `static-data-source.spec.ts`, `node-card.spec.ts`, `vendor-frontmatter.spec.ts`, `inspector-view.html` — kind literal + class binding renames.
|
|
2349
|
-
- CSS classes `.sm-gnode--note` → `.sm-gnode--markdown`, `.inspector__header--note` → `.inspector__header--markdown`. CSS variables `--sm-kind-note*` → `--sm-kind-markdown*` across `node-card.css`, `kind-palette.css`, `inspector-view.css`. The variables are runtime-injected from the Provider's `ui.color` value, so no static color value changed.
|
|
2350
|
-
- i18n comments in `i18n/node-card.texts.ts` updated.
|
|
2351
|
-
|
|
2352
|
-
**Web (public site, `web/`)**
|
|
2353
|
-
|
|
2354
|
-
- `app.js` color map and `STR` label map: `note` → `markdown`.
|
|
2355
|
-
- `index.html` demo SVG `data-type="note"` → `"markdown"`. Provider description prose dropped the legacy `hook` mention while we were there (out-of-date since spec 0.17.0; not a Phase 0 goal but cheap to fix in the same prose pass).
|
|
2356
|
-
- `i18n.json` key `graph.legend.note` → `graph.legend.markdown` with EN/ES values `Markdown`/`Markdown` (dev-facing audience; the technical kind name reads cleaner than the prose word "Note").
|
|
2357
|
-
|
|
2358
|
-
**No data migration required.** Greenfield (per `feedback_greenfield_no_versioning.md`); existing local DBs rescan to pick up the new kind value. Historical CHANGELOG entries that reference `note` are intentionally left untouched — they document past behavior (precedent: the `.skill-mapignore` rename in spec 0.16.0).
|
|
2359
|
-
|
|
2360
|
-
**Demo data.** `web/demo/data.meta.json` is a generated artifact (regenerates on next demo build); the source changes drive it.
|
|
2361
|
-
|
|
2362
|
-
Breaking but greenfield-permitted per `versioning.md` § Pre-1.0: ships as a minor bump because both `@skill-map/spec` and `@skill-map/cli` are still 0.x and no released consumer mandates the prior kind name. The first 1.0.0 is a deliberate stabilization moment, not a side-effect of this PR.
|
|
2363
|
-
|
|
2364
351
|
- c47c131: Closes review-queue item R4 (Step 9.6) — introduce a shared deterministic report base so the deterministic / probabilistic split is explicit at the schema level, symmetric with the existing `report-base.schema.json` (LLM-only `confidence` + `safety`).
|
|
2365
352
|
|
|
2366
|
-
`spec/schemas/report-base-deterministic.schema.json` declares the universal shape every deterministic Action's report MUST extend: `ok` (boolean — did the Action complete its logical work?) plus action-specific keys via `additionalProperties: true`. `report-base.schema.json` (probabilistic) and `report-base-deterministic.schema.json` (deterministic) are the two endpoints of the report hierarchy; an Action's manifest `mode` field picks the side.
|
|
2367
|
-
|
|
2368
|
-
`spec/schemas/bump-report.schema.json` migrates to extend the new base via `allOf` + relative `$ref` (per `context/spec.md` rule 7). The redundant inline declaration of `ok` is dropped — the base provides it. The bump-specific keys (`version`, `noop`, `reason`, `createdSidecar`) stay; `additionalProperties: true` mirrors the base so the report shape stays open across both layers.
|
|
2369
|
-
|
|
2370
|
-
Coverage matrix: row 28 (`bump-report.schema.json`) notes updated to point at the new base; row 29 (`report-base-deterministic.schema.json`) lands as 🟡 partial — covered indirectly via every deterministic Action conformance case (e.g. the upcoming Step 9.6.4 `sm bump --json` case for row 28), flipping 🟢 when the first conformance case directly validates a deterministic report against this base.
|
|
2371
|
-
|
|
2372
|
-
`spec/index.json` regenerated. No `@skill-map/cli` bump — the bump Action's runtime report shape (`IBumpReport` in `src/built-in-plugins/actions/bump/index.ts`) is unchanged. Greenfield + pre-1.0: breaking surface ships as a minor per the pre-1.0 versioning rule (no released consumers depended on the prior `bump-report.schema.json` shape).
|
|
2373
|
-
|
|
2374
353
|
- 305e75a: Step 9.6.1 — sidecar + annotation schemas. Closes the deferred portion of Decision #124 (where skill-map's own annotation fields live) by introducing two new schemas that lock the shape of the co-located YAML sidecars (`<basename>.sm`) the kernel will start reading in Step 9.6.2.
|
|
2375
354
|
|
|
2376
|
-
`spec/schemas/sidecar.schema.json` declares the root shape: required `for` block (`path` + `bodyHash` + `frontmatterHash`, optional `resolvedAs` for ambiguous-classification overrides) plus reserved sibling blocks `annotations`, `settings`, `audit`. Schema is `additionalProperties: true` at every level so plugins write to their own `<plugin-id>:` namespace without coordination; the built-in `unknown-field` rule (Tier 1, always-on) warns on unrecognized root keys to catch typos.
|
|
2377
|
-
|
|
2378
|
-
`spec/schemas/annotations.schema.json` lists 25 conventional annotation fields with full descriptions for editor autocomplete and IDE doc-on-hover. The load-bearing core covers versioning + supersession (`version`, `stability`, `supersedes`, `supersededBy`, `requires`, `conflictsWith`, `provides`, `related`); provenance and lifecycle dates (`type`, `author`, `authors`, `license`, `source`, `sourceVersion`, `created`, `updated`, `released`); taxonomy (`tags`, `category`, `keywords`); display (`icon`, `color`, `priority`, `hidden`); and docs (`docsUrl`). Every field is optional; an empty `annotations: {}` is valid. `version` is a single integer monotonic counter, orthogonal to `stability` — there is no major bump concept; the convention for breaking changes is to create a new node and supersede the old.
|
|
2379
|
-
|
|
2380
|
-
Conformance fixture `spec/conformance/fixtures/sidecar-example/` ships a structural sample (one `.md` + matching `.sm`); coverage matrix gains rows 26 and 27 marked 🟠 deferred — direct end-to-end conformance cases land in Step 9.6.6 alongside plugin contributions.
|
|
2381
|
-
|
|
2382
|
-
This changeset is greenfield-permitted breaking surface (no released consumers depend on the prior shape) but ships as a minor per the pre-1.0 versioning policy. No code changes — Step 9.6.2 (kernel reader + drift detection) is the next sub-step. The previous "annotation home — pending decision" section in ROADMAP is rewritten to describe the sidecar shape; Decision #125 carries the formal record.
|
|
2383
|
-
|
|
2384
355
|
- 305e75a: Step 9.6.6 (BFF half) — `GET /api/annotations/registered` over the Hono BFF. Read-only catalog of plugin-contributed annotation keys, surfaced so a future UI autocomplete can offer plugin-namespaced and root-exclusive contributions the UI can't otherwise discover at runtime. The endpoint is a pure projection of `kernel.getRegisteredAnnotationKeys()` — populated once by `registerEnabledExtensions` after every plugin loads at server boot, frozen, surfaced unchanged. Built-in catalog keys (from `annotations.schema.json`) are NOT included; the UI knows the built-in set via the bundled spec.
|
|
2385
356
|
|
|
2386
|
-
**Wire contract.** Method + path: `GET /api/annotations/registered`. No query params, no body, no auth (matches `/api/plugins`, `/api/config`). 200 envelope: `{ "schemaVersion": "1", "kind": "annotations.registered", "items": IRegisteredAnnotationKey[], "counts": { "total": <int> } }`. Item shape per `src/kernel/types/annotation-catalog.ts`: `{ pluginId, key, location: 'namespaced' | 'root', ownership: 'exclusive' | 'shared', schema: Record<string, unknown> }` — the inline JSON Schema as declared in the contributing plugin's manifest, not the AJV-compiled validator. Catalog is small (typically 0–50 entries) so no pagination, no filters, no caching headers; mutating the returned `items` array does not affect subsequent calls (kernel view stays frozen).
|
|
2387
|
-
|
|
2388
|
-
**Composition.** `server/index.ts` now instantiates a kernel at boot (`createKernel()`), stamps `pluginRuntime.annotationContributions` onto it via `setRegisteredAnnotationKeys`, and threads the kernel through `IAppDeps.kernel` to the route factory. Routes that need the catalog read it off this kernel via closure — no shared mutable state, no DI container, factory only.
|
|
2389
|
-
|
|
2390
|
-
**Refresh policy.** Same as the rest of the BFF's plugin surface — discovery happens once at `sm serve` boot. An operator that installs a new plugin restarts the server, matching the watcher's documented "loaded ONCE at boot" contract.
|
|
2391
|
-
|
|
2392
|
-
**Spec contract.** Documented in `spec/cli-contract.md` §Sidecar bump → BFF endpoint subsection (sibling of `POST /api/sidecar/bump` from 9.6.5). The new `kind` discriminator (`annotations.registered`) is reserved at 9.6.6 and joins R7 alongside `sidecar.bumped` as the canonical `rest-envelope.schema.json#/properties/kind/enum` gap to close in one batch — same divergence stance as 9.6.5; closing the enum is part of the §Step 9.6 review-queue walk.
|
|
2393
|
-
|
|
2394
|
-
Tests at `src/test/server-annotations-endpoint.test.ts`: empty catalog (real `createServer()` boot with `--no-plugins`), populated catalog with a `namespaced` + a `root + exclusive` contribution surfaced through `createApp` directly (bypasses the loader's `process.cwd()` resolution which `loadPluginRuntime` reads via `defaultRuntimeContext()`), and a mutation guard that asserts the second call still sees the original frozen view. 3 cases pass.
|
|
2395
|
-
|
|
2396
|
-
UI half (autocomplete dropdown wired into the annotation editor) is post-Step-9.6 work and lands once the parent step's review queue walks to ✅.
|
|
2397
|
-
|
|
2398
357
|
- 305e75a: Step 9.6.5 (BFF half) — `POST /api/sidecar/bump` over the Hono BFF. The endpoint mirrors the `sm bump <node.path> [--force]` CLI verb 1:1: same built-in `core/bump` Action, same `FilesystemSidecarStore`, same fresh-vs-stale refusal semantics. The only differences from the CLI verb are the invoker label (`'ui'` vs `'cli'`) and the wire shape. Batch (`--pending`) stays CLI-only at 9.6.5 — surfacing it over REST needs a job-style progress channel and lands later.
|
|
2399
358
|
|
|
2400
|
-
**Wire contract.** Request body: `{ "nodePath": <string, required>, "force"?: <boolean>, "reason"?: <string> }`. Successful (200) envelope: `{ "schemaVersion": "1", "kind": "sidecar.bumped", "value": { "nodePath", "version", "status": "fresh" }, "elapsedMs": <int> }`. Refusal (409) on fresh + no force: `{ "ok": false, "error": { "code": "sidecar-fresh", "message": <string>, "details": null } }`. 404 on unknown `nodePath`; 400 on malformed body. Force-on-fresh is a 200 silent no-op (per the Action spec) carrying the existing version, with no on-disk change. The BFF's global `app.onError` gains a new `'sidecar-fresh'` `TErrorCode` mapped from HTTP 409.
|
|
2401
|
-
|
|
2402
|
-
**WS event — `sidecar.bumped`.** After every successful 200 bump that materialises a write, the BFF broadcasts `{ "type": "sidecar.bumped", "nodePath", "version", "status": "fresh" }` over `/ws` so all connected clients refresh in lockstep. Force-on-fresh no-op responses do **not** broadcast (decision: no-op = no event — nothing changed on disk, sending the event would tell every UI to refresh state that has not moved).
|
|
2403
|
-
|
|
2404
|
-
**Spec contract.** Documented in `spec/cli-contract.md` §Sidecar bump → BFF endpoint subsection. Two new review-queue items surfaced in `ROADMAP.md` §Step 9.6: R7 (REST envelope `kind: 'sidecar.bumped'` is not in the canonical `rest-envelope.schema.json#/properties/kind/enum` — close before flipping 9.6.5 ✅) and R8 (force-on-fresh broadcast policy — keep no-op = no event, or always broadcast on a successful 200).
|
|
2405
|
-
|
|
2406
|
-
Tests at `src/test/server-sidecar-endpoint.test.ts`: 200 stale path with broadcaster receipt assertion; 409 refusal with on-disk untouched + no broadcast; 200 force-on-fresh no-op with no broadcast; 404 unknown path; 400 missing `nodePath` / wrong type / malformed JSON; round-trip parity (the on-disk `.sm` after a UI-driven bump is byte-equal to what the CLI verb would produce). 8 cases pass.
|
|
2407
|
-
|
|
2408
|
-
UI half (Angular components, e2e) is the next agent's task and will flip 9.6.5 to ✅.
|
|
2409
|
-
|
|
2410
359
|
- 305e75a: Step 9.6.4 — sidecar CLI verbs. Six new verbs split between `sm bump` (top-level, ROADMAP-named per Decision #125) and the `sm sidecar` sub-namespace (administrative helpers; the existing `sm refresh` from Step A.8 — enrichment-layer — stays untouched). Plus `sm hooks install pre-commit-bump` for the opt-in commit-time auto-bump.
|
|
2411
360
|
|
|
2412
|
-
**`sm bump <node-path> [--force]`** — single-node mode. Wraps the built-in deterministic `core/bump` Action: refusal on a fresh node (`{ ok: false, reason: 'fresh' }`, exit 2) unless `--force`; with `--force` on a fresh node the verb is a silent no-op (exit 0, no stdout). On a stale or first-time node increments `annotations.version`, refreshes `for.{bodyHash, frontmatterHash}`, stamps `audit.lastBumpedAt` + `lastBumpedBy: 'cli'` (and `audit.createdAt` + `createdBy: 'cli'` on first creation). `--json` emits the report shape declared by `bump-report.schema.json`.
|
|
2413
|
-
|
|
2414
|
-
**`sm bump --pending [--staged] [--force]`** — batch mode. Walks every node whose sidecar overlay reports drift in `node.path` ASC order. `--json` envelope: `{ bumped, refused, skipped, errors[], elapsedMs }`. `--staged` runs `git add <sidecar-path>` after each successful bump (failures degrade to a stderr warning, batch keeps running); preflight enforces the spec error matrix — not in a git repo (no `.git/` parent) → exit 5; `git` binary missing on PATH → exit 2.
|
|
2415
|
-
|
|
2416
|
-
**`sm sidecar refresh <node-path>`** — hash-only update. Refreshes `for.{bodyHash, frontmatterHash}` to match the live node WITHOUT bumping `annotations.version` and WITHOUT touching the audit block. Useful when a body change is editorial and the user doesn't want to spend a version increment. Distinct from the top-level `sm refresh` (enrichment-layer verb at Step A.8) — different storage, different concept; the sub-namespace prefix prevents the collision.
|
|
2417
|
-
|
|
2418
|
-
**`sm sidecar prune [--dry-run]`** — delete orphan `.sm` files (sidecars whose accompanying `<basename>.md` is missing on disk). Different domain from `sm orphans` (which operates on the node graph via the rename heuristic). `--json` envelope: `{ deleted, wouldDelete, errors, items[], elapsedMs }`.
|
|
2419
|
-
|
|
2420
|
-
**`sm sidecar annotate <node-path> [--force]`** — pure scaffolding. Writes a minimal `.sm` next to the `.md` with the `for:` block populated and `annotations: {}` empty, ready for editing. The `--from-frontmatter` legacy-import helper is deferred (no released consumer demands it).
|
|
2421
|
-
|
|
2422
|
-
**`sm hooks install pre-commit-bump [--dry-run]`** — install (or chain into) a git pre-commit hook running `sm bump --pending --staged` so any staged drift in `.sm` sidecars auto-bumps before the commit lands. Idempotent: re-running detects the embedded skill-map marker and no-ops. When the repo already has a `pre-commit` hook, the verb appends the skill-map block rather than replacing it. `--dry-run` prints the planned content with `--- target: <path> ---` markers and writes nothing. Exit 5 if no `.git/` parent exists; exit 2 on write failures or unknown hook flavours.
|
|
2423
|
-
|
|
2424
|
-
**Spec.** `cli-contract.md` §Actions gains a "Sidecar bump (Step 9.6.4)" subsection documenting all six verbs verbatim, the `--staged` git-error matrix, and the explicit `.sm` round-trip contract: **"`.sm` files are managed artifacts; comments and key order are not preserved on round-trip. Author commentary belongs in the markdown body or in a separate documentation file, not inside `.sm`."** R6 stays open in the Step 9.6 review queue — the UI work in 9.6.5 may force a revisit before closing the whole step.
|
|
2425
|
-
|
|
2426
|
-
**Tests.** New CLI test suites at `src/test/{bump-cli,sidecar-cli,hooks-cli}.test.ts` cover the refusal / first-time-creation / batch (with real git) / staged / dry-run / chained-hook / idempotent-reinstall / scaffold paths. File-based SQLite under `.tmp/<scope>/`, never `:memory:`. CLI reference regenerated.
|
|
2427
|
-
|
|
2428
361
|
- 305e75a: Step 9.6.6 — plugin annotation contributions + Tier-1 `unknown-field` rule. Closes the last sub-step of the Step 9.6 annotation system.
|
|
2429
362
|
|
|
2430
|
-
**Manifest extension.** `spec/schemas/extensions/base.schema.json` gains an optional `annotationContributions` map keyed by annotation key. Each entry declares an inline JSON Schema for the value plus two policy fields: `location` (`'namespaced'` default, `'root'` opt-in) and `ownership` (`'shared'` default, `'exclusive'` opt-in). Defaults route a contribution into the plugin's `<plugin-id>:` block at the sidecar root; `location: 'root'` lifts it to a top-level reserved key alongside `for` / `annotations` / `settings` / `audit` and REQUIRES `ownership: 'exclusive'`.
|
|
2431
|
-
|
|
2432
|
-
**Loader validation.** `kernel/adapters/plugin-loader.ts` rejects two single-plugin invariants as `invalid-manifest`: `location: 'root'` with non-`exclusive` ownership, and inline `schema`s that fail to AJV-compile. After every plugin has loaded, the runtime composer (`core/runtime/plugin-runtime.ts:loadPluginRuntime`) walks the aggregated catalog and **hard-fails** when two plugins claim the same `(key, location: 'root', ownership: 'exclusive')` tuple — `loadPluginRuntime` throws a new `AnnotationContributionConflictError` and the kernel does NOT boot. Stricter than the per-plugin `invalid-manifest` path because annotation-namespace conflicts are non-recoverable: annotated `.sm` files would otherwise be non-deterministically routed.
|
|
2433
|
-
|
|
2434
|
-
**Runtime catalog.** `Kernel` gains `getRegisteredAnnotationKeys(): readonly IRegisteredAnnotationKey[]`, populated once by `registerEnabledExtensions` after every plugin loads. Pure read; no side effects. Built-in catalog fields from `annotations.schema.json` are NOT included — this catalog is plugin-only. The BFF endpoint that wraps the catalog for UI autocomplete lands separately.
|
|
2435
|
-
|
|
2436
|
-
**`core/unknown-field` rule.** New built-in Tier-1 typo guard (`severity: warn`). Walks parsed `.sm` sidecars and emits a warning for: (1) keys inside `annotations:` not in the curated catalog, (2) top-level keys outside the four reserved blocks that are not a registered plugin namespace nor a registered root contribution, (3) plugin-namespaced values that fail their contributing plugin's schema. The orchestrator threads parsed sidecar roots into the rule pass via `IAnalyzerContext.sidecarRoots` plus the runtime catalog via `IAnalyzerContext.annotationContributions`.
|
|
2437
|
-
|
|
2438
|
-
**Conformance.** New end-to-end case `sidecar-end-to-end` with fixture `spec/conformance/fixtures/sidecar-end-to-end/`. Flips coverage rows 26 + 27 (`sidecar.schema.json` + `annotations.schema.json`) from 🟡 partial to 🟢 covered. Asserts a populated `Node.sidecar` overlay, `status: stale-*` drift, denormalised `annotations.version`, and both `annotation-stale` + `annotation-orphan` issues from the built-in core rules.
|
|
2439
|
-
|
|
2440
|
-
**Side-fix.** `core/annotation-orphan` now emits `nodeIds: [<expectedMdRelative>]` instead of an empty array, closing the pre-existing `issue.schema.json#/properties/nodeIds/minItems: 1` violation latent until the conformance corpus exercised it.
|
|
2441
|
-
|
|
2442
|
-
**Plugin author guide.** New section `## Annotation contributions` in `spec/plugin-author-guide.md` covers the manifest shape, namespacing default vs root opt-in, ownership rules, hard-fail collision behaviour, the Tier-1 typo guard, and the runtime catalog accessor with worked examples. The full guide rewrite for agent-first readability is deferred to a post-Step-9.6 follow-up.
|
|
2443
|
-
|
|
2444
363
|
- 305e75a: Step 9.6.2 — kernel sidecar reader + drift detection. The walker now reads `<basename>.sm` next to every `<basename>.md` it finds, validates against `spec/schemas/sidecar.schema.json` + `spec/schemas/annotations.schema.json` via the kernel AJV stack, and computes drift versus the live body / canonical-frontmatter hashes. Stale state surfaces through a new built-in Rule `core/annotation-stale` (`warn` severity); orphan `.sm` files (no matching `.md`) surface through `core/annotation-orphan` (`warn`). Schema-invalid or YAML-malformed sidecars produce an `invalid-sidecar` warning and the scan continues — drift detection is soft-mode, never blocking.
|
|
2445
364
|
|
|
2446
|
-
**Storage extension.** Migration `002_sidecar_columns.sql` extends `scan_nodes` with three new columns: `sidecar_present` (INTEGER 0/1, default 0), `sidecar_status` (TEXT, NULL when absent or unparseable; one of `fresh` / `stale-body` / `stale-frontmatter` / `stale-both` otherwise), and `annotations_json` (TEXT, JSON-encoded `annotations:` block, NULL when absent or empty). The `Node` domain type gains a `sidecar` overlay that round-trips through `node.schema.json`; clients consume it as authoritative for the snapshot but never persist it across scans.
|
|
2447
|
-
|
|
2448
|
-
**Breaking change — `Node.version` type flip.** The denormalised version column was a `TEXT` semver string sourced from `frontmatter.metadata.version`; it is now an `INTEGER` monotonic counter sourced from sidecar `annotations.version` (Decision #125 — single integer, orthogonal to `stability`, no major-bump concept). Pre-9.6.2 rows reset to NULL on migration — greenfield, no automatic semver→integer conversion. `node.schema.json#/properties/version` updated accordingly.
|
|
2449
|
-
|
|
2450
|
-
**Source-of-truth shift for stability / version / author.** The three Node columns previously sourced from `frontmatter.metadata.*` / `frontmatter.author` now source from sidecar `annotations.{stability, version, author}`. Hard cut — the fallback through `pickMetadata` for these three fields is removed in `orchestrator.ts`. Other consumers of `metadata.*` (e.g. broken-ref's `metadata.related`) keep working; their migration lands in Step 9.6.4.
|
|
2451
|
-
|
|
2452
|
-
Coverage matrix rows 26 + 27 (sidecar + annotations schemas) flip from 🟠 deferred to 🟡 partial — kernel reader is covered; full bump-end-to-end (scan → annotation queryable → drift detection → bump) still lands in Step 9.6.6. New tests under `src/test/sidecar-reader.test.ts` cover fresh / stale-body / stale-frontmatter / orphan / malformed-YAML / schema-invalid / unknown-key paths and a persistence round-trip through `scan_nodes`.
|
|
2453
|
-
|
|
2454
365
|
- 687823d: R15 closure (Step 9.6 review queue): extend `Node.sidecar` overlay with the full parsed `.sm` root.
|
|
2455
366
|
|
|
2456
|
-
**Spec.** `spec/schemas/node.schema.json#/$defs/sidecarOverlay` gains an optional `root` property (`type: ['object', 'null']`, `additionalProperties: true`). It carries the entire parsed YAML payload of the matching `.sm` sidecar — every reserved block (`for`, `annotations`, `settings`, `audit`) plus any opt-in `<plugin-id>:` namespace. NULL when no sidecar accompanies the node, or when the sidecar exists but failed to parse / validate. The existing top-level `annotations` field stays — `root.annotations` duplicates it by design so pre-R15 consumers reading `sidecar.annotations` keep working unchanged. `spec/index.json` regenerated.
|
|
2457
|
-
|
|
2458
|
-
**Kernel.** `ISidecarOverlay` (in `src/kernel/types.ts`) gains `root?: Record<string, unknown> | null`. The orchestrator's `resolveAndApplySidecar` site stamps `root: result.parsed.raw` (the full root that `parseSidecar()` already builds for the rule pass — no extra YAML reads). On parse failure the overlay ships `{ present: true, status: null, annotations: null, root: null }`; on absent sidecar `{ present: false }` (root absent).
|
|
2459
|
-
|
|
2460
|
-
**Persistence.** Additive sibling column `scan_nodes.sidecar_root_json` (migration `004_sidecar_root_json.sql`) stores the JSON-encoded root alongside the existing `annotations_json`. Option (b) per the R15 brief — no rewrite of the existing `annotations_json` read path. `scan-persistence.ts` writes the column; `scan-load.ts` rehydrates `sidecar.root` from it.
|
|
2461
|
-
|
|
2462
|
-
**BFF.** No route changes: `/api/nodes`, `/api/nodes/:pathB64`, and `/api/graph` are pass-through serializers — the new field flows through automatically once the kernel populates it.
|
|
2463
|
-
|
|
2464
|
-
**UI wire model.** `ISidecarOverlayApi` (in `ui/src/models/api.ts`) gains `root?: Record<string, unknown> | null`. The internal `ISidecarOverlay` (in `ui/src/models/node.ts`) declared the field forward-compat-ready since the inspector-tiering pass; the `projectNode` mapper spreads `api.sidecar` as-is so the field propagates into `INodeView.sidecar.root` unchanged. The WS `sidecar.bumped` patcher (`CollectionLoaderService.patchSidecarFromBump`) preserves `root` across the bump-driven re-render so the inspector audit / debug / plugin-contributions panels stay populated after a bump.
|
|
2465
|
-
|
|
2466
|
-
**Tests.** `src/test/sidecar-reader.test.ts`: fresh-sidecar case asserts `sidecar.root.for.{path,bodyHash}` and `sidecar.root.annotations.{stability,version}`; absent-sidecar case asserts `sidecar.root` is null/absent; persistence round-trip case adds the new `sidecar_root_json` column to the selected projection and asserts the persisted JSON rehydrates correctly. `src/test/server-endpoints.test.ts`: fixture now plants a `.sm` co-located with `architect.md` (pinned to baseline hashes for `status: fresh`); new test case `R15 — surfaces sidecar.root with the full parsed .sm payload` asserts `item.sidecar.root.for.path === target` and `item.sidecar.root.audit.lastBumpedBy === 'cli'` on the `/api/nodes/:pathB64` response.
|
|
2467
|
-
|
|
2468
|
-
**Backward compatibility.** Pre-R15 consumers reading `sidecar.annotations` keep working unchanged — the field is preserved, just duplicates `root.annotations`. New consumers reading structured sub-fields (`root.for.*`, `root.audit.*`, plugin namespaces) light up automatically once their BFF / persistence layer ships this minor.
|
|
2469
|
-
|
|
2470
367
|
- 305e75a: Step 9.6.7 — wire-shape cleanup. Closes two §Step 9.6 review-queue items in one batch (R7 + R9) so the BFF's REST and WS surfaces match the canonical contracts every other route already follows.
|
|
2471
368
|
|
|
2472
|
-
**R7 — REST envelope `kind` enum gap (`sidecar.bumped` + `annotations.registered`).** `spec/schemas/api/rest-envelope.schema.json` grew from four `oneOf` variants to six. `'sidecar.bumped'` (action-result variant: `value` + `elapsedMs`, no `filters` / `counts` / `kindRegistry`) covers `POST /api/sidecar/bump`. `'annotations.registered'` (catalog variant: `items` + `counts.total` only, no `filters` / `kindRegistry` / `returned`) covers `GET /api/annotations/registered`. The list variant re-imposes `counts.required: ['total', 'returned']` via per-variant override so its tally shape stays strict. `elapsedMs` is now a top-level optional integer property, present only on action-result envelopes.
|
|
2473
|
-
|
|
2474
|
-
**R9 — WS event shape asymmetry.** `src/server/routes/sidecar.ts` now wraps the `sidecar.bumped` payload in the canonical `IWsEventEnvelope` shape `{ type, timestamp, data: { nodePath, version, status } }` (matches every kernel→broadcaster bridge — `scan.*`, `watcher.*`). `timestamp` serialises as an ISO 8601 string via `new Date().toISOString()`, matching the kernel orchestrator's `makeEvent`. The prior flat shape (`{ type, nodePath, version, status }`) forced the UI to accept two shapes in `isWsEvent`; that relaxation is now obsolete (the UI half lands in a follow-up `ui/` PR).
|
|
2475
|
-
|
|
2476
|
-
**Tests.** `src/test/server-sidecar-endpoint.test.ts` and `src/test/server-annotations-endpoint.test.ts` each gain an AJV-compile + validate pass against `rest-envelope.schema.json` over the live 200 responses, so any future drift in the route or in the schema fails immediately. The sidecar test's broadcaster-receipt assertion now checks the canonical envelope (timestamp ISO regex, `data.{nodePath,version,status}`, no flat siblings).
|
|
2477
|
-
|
|
2478
|
-
**Spec doc.** `spec/cli-contract.md` BFF subsections (`POST /api/sidecar/bump`, `GET /api/annotations/registered`) updated — both `kind` values are now part of the canonical enum, the WS event documents the wrapped envelope. `spec/index.json` regenerated.
|
|
2479
|
-
|
|
2480
|
-
No new dependencies; AJV is already on the path (`Ajv2020` from `ajv/dist/2020.js`, used by the unknown-field rule). No CLI-verb surface changes.
|
|
2481
|
-
|
|
2482
369
|
- 1019d5f: Pluggable kernel walker + parser registry. Provider manifests gain a declarative `read: { extensions, parser }` field; the kernel owns the file walker and a closed registry of built-in parsers. The Claude Provider drops its hand-rolled `walk()` (~70 lines of fs walking + frontmatter parsing) and becomes pure metadata + classification.
|
|
2483
370
|
|
|
2484
|
-
Cross-provider kind sharing via a restructured `kindRegistry`: when two Providers declare the same kind name (e.g. `agent` for both Claude and a future Gemini Provider), every contribution is kept. Per-node painting can pick the matching Provider's color — the data shape supports it without forcing a kernel-side rename of every shared kind.
|
|
2485
|
-
|
|
2486
|
-
**`@skill-map/spec`**
|
|
2487
|
-
|
|
2488
|
-
- `extensions/provider.schema.json` — new optional `read` field. Validates `extensions: string[]` (each starting with a dot, matching `^\.[a-z0-9]+$`) and `parser: string`. Defaults at the call site (`{ extensions: ['.md'], parser: 'frontmatter-yaml' }`); not silently injected at manifest load. Precedence: when a Provider also declares the runtime `walk()` field, `walk()` wins and `read` is ignored — the runtime field is the escape hatch for non-standard discovery.
|
|
2489
|
-
- `api/rest-envelope.schema.json` — `kindRegistry.additionalProperties` restructured. Old shape `{ providerId, label, color, ... }` becomes `{ primaryProviderId, providers: { <providerId>: { label, color, colorDark, emoji, icon } } }`. The primary drives the kind's visible label / color / icon and the `--sm-kind-<kind>` CSS var; secondary contributors live under `providers` so per-node painting can pick the matching Provider's contribution.
|
|
2490
|
-
- `index.json` regenerated.
|
|
2491
|
-
|
|
2492
|
-
**`@skill-map/cli` — kernel walker + parser registry**
|
|
2493
|
-
|
|
2494
|
-
- New `src/kernel/scan/walk-content.ts` — `walkContent(roots, options)` async generator. Owns the audit-cleared defences (M7 symlink skip, TOCTOU stat re-check, ignore filter integration, bundled-defaults fallback) so every Provider that uses `read` inherits them.
|
|
2495
|
-
- New `src/kernel/scan/parsers/{types,frontmatter-yaml,plain,index}.ts` — closed registry. Built-ins: `frontmatter-yaml` (YAML frontmatter inside `--- … ---` fences, prototype-pollution-safe, `js-yaml` `JSON_SCHEMA` pinned), `plain` (entire body, empty frontmatter — for files carrying no frontmatter convention). `getParser(id)` resolves by id; `registerParser` is kernel-internal (not re-exported from `src/kernel/index.ts`) and rejects collisions with frozen built-in ids.
|
|
2496
|
-
- `IProvider` extended: optional `read?: IProviderReadConfig`, `walk` becomes optional. `resolveProviderWalk(provider)` returns `provider.walk` when defined, else closes over `walkContent` with `provider.read ?? defaults`. The orchestrator at `kernel/orchestrator.ts:1035` flips to `resolveProviderWalk(provider)(...)` — single-line edit.
|
|
2497
|
-
- `built-in-plugins/providers/claude/index.ts` migrates to declarative form. Drops `walk()`, `walkMarkdown`, `splitFrontmatter`, `FRONTMATTER_RE`, `FORBIDDEN_FRONTMATTER_KEYS`, plus the `fs/promises`, `path`, `js-yaml`, and `IIgnoreFilter` imports. Adds `read: { extensions: ['.md'], parser: 'frontmatter-yaml' }`. File shrinks from 270 to 158 lines. Behaviour identical (the audit-cleared defences live in the kernel walker / parser).
|
|
2498
|
-
- Tests for `frontmatter-yaml.test.ts`, `plain.test.ts`, `parsers/index.test.ts`, `walk-content.test.ts` — 28 new cases covering happy paths, malformed input, prototype-pollution strip, registry resolution + freeze semantics, M7 symlink skip, TOCTOU re-check, custom extensions, default-applied path. Existing `claude.test.ts` and `pollution-defence.test.ts` migrate to `resolveProviderWalk(claudeProvider)(...)`.
|
|
2499
|
-
|
|
2500
|
-
**`@skill-map/cli` — kindRegistry refactor**
|
|
2501
|
-
|
|
2502
|
-
- `src/server/kind-registry.ts` rewrites `buildKindRegistry`: per kind, first Provider in iteration order populates `primaryProviderId` and seeds `providers`; later Providers append to `providers[provider.id]` without overwriting the primary. The kernel separately surfaces `provider-ambiguous` issues for files matched by multiple Providers; the registry stays coherent during the conflict window.
|
|
2503
|
-
- `src/server/envelope.ts` types updated to match the wire shape (`IKindRegistryEntry` carries `primaryProviderId` + `providers`; new `IKindRegistryProviderUi` for the per-Provider sub-entry).
|
|
2504
|
-
- New `src/server/kind-registry.test.ts` — 4 cases covering single-provider entries, cross-provider sharing, ordering, and the empty case. The `test:ci` glob picks up `server/**/*.test.ts` going forward (was kernel + built-in-plugins + test/ only).
|
|
2505
|
-
|
|
2506
|
-
**UI (`ui/`, private workspace)**
|
|
2507
|
-
|
|
2508
|
-
- `models/api.ts` adds `IKindRegistryProviderUiApi` and reshapes `IKindRegistryEntryApi` to match the new wire shape.
|
|
2509
|
-
- `services/kind-registry.ts` — ingest now flattens the primary Provider's visuals onto the entry so existing `lookup` / `labelOf` / `colorOf` / `iconOf` keep working unchanged. New `providersOf(name)` returns the full per-Provider map for surfaces that paint per-Provider. `applyCssVars` keeps emitting `--sm-kind-<kind>` from the primary — every static CSS reference (`node-card.css`, `kind-palette.css`, `inspector-view.css`) survives without changes.
|
|
2510
|
-
- 3 spec files updated to construct the new wire shape in fixtures (`kind-registry.spec.ts`, `graph-view.spec.ts`, `list-view.spec.ts`, `filter-url-sync.spec.ts`); `kind-registry.spec.ts` adds 2 new cases for cross-provider sharing and CSS-var derivation.
|
|
2511
|
-
|
|
2512
|
-
**Demo dataset (`web/scripts/build-demo-dataset.js`)**
|
|
2513
|
-
|
|
2514
|
-
- The hardcoded `DEMO_KIND_REGISTRY` is updated to the new shape and regenerated as part of `web:build`. The legacy `hook` entry (already obsolete since spec 0.17.0) is dropped to keep the demo aligned with the active built-in catalog.
|
|
2515
|
-
|
|
2516
|
-
**Known limitation (deferred to Phase B).** With shared kind names possible, a node sourced from a non-primary Provider currently renders in the primary's color — the data shape (`entry.providers[node.provider]`) supports per-Provider painting, but the consumer-side fix (node-card / inspector reading `node.provider` to pick the matching color) ships in Phase B alongside the new Providers, when shared kind names are actually produced. During this release window no Provider produces shared kind names, so the tradeoff has zero user-visible impact.
|
|
2517
|
-
|
|
2518
|
-
**Backward compatibility.** Greenfield (`feedback_greenfield_no_versioning.md`): no released consumer holds the prior `kindRegistry` shape or relies on a Provider's hand-rolled `walk()`. Stays minor pre-1.0 per `versioning.md` § Pre-1.0.
|
|
2519
|
-
|
|
2520
371
|
## 0.17.0
|
|
2521
372
|
|
|
2522
373
|
### Minor Changes
|
|
2523
374
|
|
|
2524
375
|
- 77579b3: Add a `sm db browser` sub-command that opens the project's SQLite DB in DB Browser for SQLite (sqlitebrowser GUI). Read-only by default; pass `--rw` to enable writes. Replaces the previous `scripts/open-sqlite-browser.js` standalone script.
|
|
2525
376
|
|
|
2526
|
-
The root `npm run sqlite` shortcut now invokes the project-built CLI binary (`node src/bin/sm.js db browser`) instead of the standalone script. This guarantees the locally compiled CLI is used, not whichever `sm` resolves on PATH (a globally installed `@skill-map/cli` would otherwise shadow the in-development version).
|
|
2527
|
-
|
|
2528
|
-
Spec: `cli-contract.md` documents the new sub-command in the verb table and the §Database section.
|
|
2529
|
-
|
|
2530
377
|
- 696008a: Add a `--no-ui` flag to `sm serve`. With it, the BFF stops serving the Angular bundle (stale or otherwise) and the root `/` renders an inline dev-mode placeholder pointing the user at `npm run ui:dev` + `http://localhost:4200/`. Used by the root `bff:dev` shortcut so iterating on the BFF alongside the Angular dev server doesn't surface a stale UI by accident.
|
|
2531
378
|
|
|
2532
|
-
Mutually exclusive with `--ui-dist <path>` (rejected with exit 2). Combining `--no-ui` with the default `--open` emits a non-fatal stderr warning suggesting `--no-open` (the auto-opened tab would land on the placeholder rather than the live UI). `/api/*` and `/ws` remain fully functional; only the static SPA is suppressed.
|
|
2533
|
-
|
|
2534
|
-
Spec impact: `spec/cli-contract.md` documents the new flag in the `sm serve` signature and the §Server flags table, including the mutual-exclusion + warning rules.
|
|
2535
|
-
|
|
2536
379
|
- bd5e360: Trim `frontmatter/base.schema.json` to the truly universal contract: `name` + `description` are the only required fields, every node on every Provider, and `additionalProperties: true` lets vendor-specific keys flow through silently.
|
|
2537
380
|
|
|
2538
|
-
The previous base inadvertently curated a Claude-flavored shape (`tools`, `allowedTools`, full `metadata` block with `version` required, etc.). skill-map AGGREGATES vendor specs, it does not curate them — so per-vendor frontmatter shapes belong in the Provider that emits the kind. The Anthropic-specific catalog now lives entirely under `src/built-in-plugins/providers/claude/schemas/` and absorbs Anthropic's documented frontmatter verbatim (see the matching `@skill-map/cli` changeset).
|
|
2539
|
-
|
|
2540
|
-
The future home for skill-map-only annotation fields (provenance, cross-vendor metadata, source URL, supersedes/supersededBy) is a deferred decision — sidecar file vs in-frontmatter block — tracked separately. Existing files that carry `metadata: { version, ... }` continue to validate without any change because of `additionalProperties: true`; nothing breaks at the consumer edge.
|
|
2541
|
-
|
|
2542
|
-
Decision #55 (full metadata block in the universal base) is superseded by this change.
|
|
2543
|
-
|
|
2544
|
-
Breaking but greenfield-permitted per `versioning.md` § Pre-1.0: ships as a minor bump because `@skill-map/spec` is still 0.x and Decision #55 had not reached any released consumer that mandates the prior shape. Stays minor; the first 1.0.0 is a deliberate stabilization moment, not a side-effect of this PR.
|
|
2545
|
-
|
|
2546
381
|
## 0.16.0
|
|
2547
382
|
|
|
2548
383
|
### Minor Changes
|
|
2549
384
|
|
|
2550
385
|
- c981430: Rename the project ignore file from `.skill-mapignore` to `.skillmapignore` (no dash).
|
|
2551
386
|
|
|
2552
|
-
Rationale: drop the dash for consistency with `.gitignore` / `.npmignore` / `.dockerignore` and friends — those tools use a contiguous lowercase token, and adopting the same shape removes the visual stutter when listing dotfiles. The rename also avoids confusion between the public artifact and the package id `@skill-map/*` which uses a dash by convention.
|
|
2553
|
-
|
|
2554
|
-
Breaking change pre-1.0:
|
|
2555
|
-
|
|
2556
|
-
- `sm init` now scaffolds `.skillmapignore` instead of `.skill-mapignore`. Existing projects must `mv .skill-mapignore .skillmapignore` manually — no compat reader (greenfield rule, see `feedback_greenfield_no_versioning.md`).
|
|
2557
|
-
- The bundled defaults asset moved from `src/config/defaults/skill-mapignore` to `src/config/defaults/skillmapignore`.
|
|
2558
|
-
- `sm serve` and `sm watch` now watch `.skillmapignore` (not `.skill-mapignore`) for live filter rebuilds.
|
|
2559
|
-
- Spec and JSON Schema (`spec/cli-contract.md` § `sm init`, `spec/schemas/project-config.schema.json` § `ignore`) updated; `spec/index.json` regenerated.
|
|
2560
|
-
- All in-repo fixtures, docs (ROADMAP, context/\*, AGENTS.md, web/app.js), tests, and skills (sm-tutorial, foblex-flow indirectly) updated in the same commit.
|
|
2561
|
-
|
|
2562
|
-
Historical CHANGELOG entries that reference `.skill-mapignore` are intentionally left untouched — they document past behaviour.
|
|
2563
|
-
|
|
2564
387
|
## 0.15.0
|
|
2565
388
|
|
|
2566
389
|
### Minor Changes
|
|
2567
390
|
|
|
2568
391
|
- d7e8dd9: Rename the tester onboarding verb and its companion Claude Code skill from `sm-guide` to `sm-tutorial` across spec, CLI, bundled materialised payload, runtime state file, and report file. Breaking change to the public CLI surface (`sm guide` is gone — no compat shim); pre-1.0 so it ships as a minor bump per the project's pre-1.0 policy (no major while a workspace stays in `0.Y.Z`).
|
|
2569
392
|
|
|
2570
|
-
Spec: `spec/cli-contract.md` — the `sm guide` verb section is renamed to `sm tutorial`. Same shape, same exit codes, same `--force` semantics — only the identifier flips. Materialised file becomes `<cwd>/sm-tutorial.md`; integrity block in `spec/index.json` regenerated.
|
|
2571
|
-
|
|
2572
|
-
CLI (`@skill-map/cli`): `sm guide` → `sm tutorial`; `src/cli/commands/guide.ts` → `tutorial.ts` (`GuideCommand` → `TutorialCommand`, `SM_GUIDE_FILENAME` → `SM_TUTORIAL_FILENAME`); `src/cli/i18n/guide.texts.ts` → `tutorial.texts.ts` (`GUIDE_TEXTS` → `TUTORIAL_TEXTS`, all string templates updated to mention `sm-tutorial.md` and `@sm-tutorial.md`); `src/tsup.config.ts` build step `copyGuideSkill()` → `copyTutorialSkill()` writing the bundled payload to `dist/cli/tutorial/sm-tutorial.md` instead of `dist/cli/guide/sm-guide.md`. Test file `src/test/guide-cli.test.ts` → `tutorial-cli.test.ts` with updated regex assertions and SKILL.md byte-match anchor pointing at `.claude/skills/sm-tutorial/SKILL.md`.
|
|
2573
|
-
|
|
2574
|
-
Skill: `.claude/skills/sm-guide/` → `.claude/skills/sm-tutorial/`. Frontmatter `name: sm-guide` → `sm-tutorial`. Triggers list updated (`"tutorial", "sm-tutorial", "tutorial me", "start the tutorial"`). Internal whitelist updated (`sm-tutorial.md`, `tutorial-state.yml`, `sm-tutorial-report.md`). Runtime state file renamed `guide-state.yml` → `tutorial-state.yml` (top-level YAML key `guide:` → `tutorial:`). Report file renamed `sm-guide-report.md` → `sm-tutorial-report.md`. Colloquial Spanish "guía" inside tester-facing prose stays where it reads naturally — only identifiers (path names, command names, frontmatter, technical references) flip to `tutorial`.
|
|
2575
|
-
|
|
2576
|
-
ROADMAP: setup-and-state verb table updated to `sm tutorial [--force]`.
|
|
2577
|
-
|
|
2578
|
-
No backwards-compat alias is shipped: the tester base for this verb is tiny and a clean break is safer than maintaining two names.
|
|
2579
|
-
|
|
2580
393
|
## 0.14.1
|
|
2581
394
|
|
|
2582
395
|
### Patch Changes
|
|
2583
396
|
|
|
2584
397
|
- 34d57db: Doc-only fix to remove a misleading reading of "built-in kinds" in the Node schema and one test, plus a small batch of internal CLI refactors and tightened null checks. No external surface change.
|
|
2585
398
|
|
|
2586
|
-
Spec / docs:
|
|
2587
|
-
|
|
2588
|
-
- `spec/schemas/node.schema.json` — the top-level `description` previously read "built-in kinds today are skill, agent, command, hook, note", which suggested those kinds were a kernel-level concept. They are not — the kernel treats `kind` as an open string, and the five names are emitted by the **built-in Claude Provider**. Re-worded to attribute the catalog to the Claude Provider, matching the wording already used on the `kind` field, in `spec/README.md`, in `src/kernel/types.ts`, and in `src/kernel/adapters/sqlite/schema.ts`.
|
|
2589
|
-
- `src/test/extractor-applicable-kinds.test.ts` — three comments tightened from "built-in kind" to "built-in Claude Provider kind" for consistency.
|
|
2590
|
-
|
|
2591
|
-
Internal CLI refactors (no behaviour change):
|
|
2592
|
-
|
|
2593
|
-
- `src/cli/commands/config.ts` — extracted an `isPlainObject` predicate (replaces the duplicated `!!v && typeof v === 'object' && !Array.isArray(v)` check inside `enumerateConfigPaths`) and a `safeGetAtPath` helper that wraps `getAtPath` + `ForbiddenSegmentError` handling so each read verb's `run()` no longer repeats the try/catch + instanceof shape.
|
|
2594
|
-
- `src/cli/commands/db.ts` — pulled the SQL number serialiser into `formatSqlNumber` (NaN / ±Infinity collapse to NULL) so `formatSqlValue` reads as a flat dispatcher.
|
|
2595
|
-
- `src/cli/util/parse-error.ts` — moved the verb-scoped error formatting (incl. the missing-positionals special case) into a `formatVerbScopedError` helper so the top-level dispatcher in `formatParseError` stays flat. Removed the now-stale "dispatcher pattern" eslint-disable comment.
|
|
2596
|
-
- `src/kernel/adapters/sqlite/scan-load.ts` — tightened `parseJsonObject` / `parseJsonArray` null checks from `s == null` to `s === null || s === undefined` to remove the implicit-coercion pattern flagged by lint.
|
|
2597
|
-
|
|
2598
|
-
No contract change (no field/type/required edits). `spec/index.json` regenerated.
|
|
2599
|
-
|
|
2600
399
|
## 0.14.0
|
|
2601
400
|
|
|
2602
401
|
### Minor Changes
|
|
2603
402
|
|
|
2604
403
|
- 8f2a66d: Bare `sm` defaults to `sm serve` instead of printing help
|
|
2605
404
|
|
|
2606
|
-
`sm` invoked with no arguments now starts the Web UI server when a
|
|
2607
|
-
`.skill-map/` project exists in the current working directory
|
|
2608
|
-
(equivalent to `sm serve`). When no project is found, it prints a
|
|
2609
|
-
one-line hint pointing to `sm init` and `sm --help` on stderr and
|
|
2610
|
-
exits with code `2`. `sm --help` and `sm -h` continue to print
|
|
2611
|
-
top-level help — help is now reserved for explicit flags.
|
|
2612
|
-
|
|
2613
|
-
**Spec change** (`spec/cli-contract.md` §Binary): the prior wording —
|
|
2614
|
-
_"`sm`, `sm --help`, `sm -h` MUST all print top-level help"_ — is
|
|
2615
|
-
replaced by two separate clauses. Help invocation requires `--help` or
|
|
2616
|
-
`-h`; bare invocation routes to the server with the hint-and-exit
|
|
2617
|
-
fallback when no project exists.
|
|
2618
|
-
|
|
2619
|
-
**CLI change** (`src/cli/entry.ts`): empty argv is intercepted before
|
|
2620
|
-
Clipanion sees it. If `defaultProjectDbPath(cwd)` exists, the args
|
|
2621
|
-
are rewritten to `['serve']`. Otherwise the hint is printed via the
|
|
2622
|
-
`tx()` i18n shim and the process exits `2`. `RootHelpCommand` no
|
|
2623
|
-
longer carries `Command.Default`; it remains the handler for `--help`
|
|
2624
|
-
and `-h` only.
|
|
2625
|
-
|
|
2626
|
-
**Why pre-1.0 minor instead of major**: `spec/` and `src/` are both
|
|
2627
|
-
in `0.Y.Z`. Per `spec/versioning.md` §Pre-1.0, breaking changes ship
|
|
2628
|
-
as minor bumps until the deliberate 1.0 stabilization. The conformance
|
|
2629
|
-
suite required no updates (no case asserted bare-sm = help).
|
|
2630
|
-
|
|
2631
405
|
## 0.13.1
|
|
2632
406
|
|
|
2633
407
|
### Patch Changes
|
|
2634
408
|
|
|
2635
409
|
- 103fc1a: Doc revision pass — greenfield framing across READMEs, spec prose, ROADMAP, AGENTS, web, and workspace landing pages.
|
|
2636
410
|
|
|
2637
|
-
Pure documentation changes; no normative schema or code changes.
|
|
2638
|
-
|
|
2639
|
-
`@skill-map/spec`:
|
|
2640
|
-
|
|
2641
|
-
- `architecture.md` — terse rewrite of §Provider · `kinds` catalog (now lists three required fields: `schema`, `defaultRefreshAction`, `ui`); new §Provider · `ui` presentation section documenting the label / color / colorDark / emoji / icon contract; §Stability section updated for the six extension kinds + Hook trigger set.
|
|
2642
|
-
- `plugin-author-guide.md` — Provider section gains the `ui` block documentation alongside `schema` and `defaultRefreshAction`; example manifest carries both icon variants (`pi` + `svg`); migration notes stripped under greenfield framing.
|
|
2643
|
-
- `cli-contract.md` — §Server documents the `kindRegistry` envelope field on every payload-bearing variant (sentinel envelopes — health/scan/graph — exempt).
|
|
2644
|
-
- `conformance/coverage.md` — row 18 (`extensions/provider.schema.json`) flipped 🔴 → 🟡, points at the new `plugin-missing-ui-rejected` case; new §Stability section.
|
|
2645
|
-
- `conformance/README.md` — drop "(Phase 5 / A.13 of spec 0.8.0)" historical phase markers.
|
|
2646
|
-
- `db-schema.md`, `plugin-author-guide.md` — fix `pisar` typo (Spanish leaked into English) → "are simply overwritten".
|
|
2647
|
-
- `CHANGELOG.md` — aggressive sweep: 2114 → 77 lines (96% reduction). Every release gets a 1–3 line greenfield summary. Drops the `Files touched`, `Migration for consumers`, `Out of scope`, `Why`, and per-step decision sub-sections. Drops commit-hash prefixes and `Pre-1.0 minor per versioning.md` boilerplate from every entry. The `[Unreleased]` section preserves the three in-flight Step 14 entries.
|
|
2648
|
-
- `conformance/fixtures/plugin-missing-ui/.skill-map/plugins/bad-provider/{plugin.json,provider.js}` — recovered (lost in the merge from `main` due to `.gitignore` masking gitignored-but-tracked files; `git add -f` brings them back into the index).
|
|
2649
|
-
|
|
2650
|
-
`@skill-map/cli`:
|
|
2651
|
-
|
|
2652
|
-
- `src/README.md` — Status section greenfield (terse: pre-1.0, what's next, what's after); usage examples expanded with `sm serve` + monorepo dev scripts.
|
|
2653
|
-
- `src/built-in-plugins/README.md` — drop the contradictory "empty on purpose" framing; document the actual built-in inventory (Claude Provider + Extractors + Rules + Formatter + `validate-all`).
|
|
2654
|
-
|
|
2655
|
-
`@skill-map/testkit`:
|
|
2656
|
-
|
|
2657
|
-
- `testkit/README.md` — rewrite end-to-end against the actual exported helper names (`runExtractorOnFixture` instead of the long-renamed `runDetectorOnFixture`); align example with the `extract(ctx) → void` Extractor shape and the `enabled` plugin status enum.
|
|
2658
|
-
|
|
2659
|
-
Plus `ui/` README rewrite, root README + ES mirror Status / badge bumps + `sm serve` mention + Star History embed, AGENTS.md greenfield BFF section, CONTRIBUTING.md refresh, ROADMAP.md greenfield sweep (`Earlier prose` blocks stripped, decision log reframed without rename history, 14.6+ content preserved), web copy revision (How-it-works section), examples/hello-world rewritten to the Extractor model with passing tests, and the spec/index.json regeneration that goes with it.
|
|
2660
|
-
|
|
2661
|
-
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
2662
|
-
|
|
2663
411
|
## [Unreleased]
|
|
2664
412
|
|
|
2665
413
|
### Minor
|