@skill-map/spec 0.63.0 → 0.64.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 CHANGED
@@ -1,5 +1,27 @@
1
1
  # Spec changelog
2
2
 
3
+ ## 0.64.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Fix the OpenAI Codex connector model, which cloned Claude's grammar and was wrong per the official docs. Under the codex lens, skills are now invoked with `$name` (new `dollar-skill` extractor) not `/name`, `@` is a path-resolved file reference (new `at-file` extractor) not an agent mention, and codex plus the neutral `agent-skills` lens no longer flag skill names as reserved (a `$`-skill cannot shadow a `/` command). Claude and Antigravity are unchanged.
8
+
9
+ ## User-facing
10
+
11
+ Codex projects: a skill now connects via `$name` (not `/name`), `@file.md` references a file, and a skill named like a built-in (e.g. `model`) is no longer wrongly flagged as a reserved-name collision. `/` is left to Codex's own built-in commands.
12
+
13
+ - Lens auto-detection now gives a vendor marker precedence over the open-standard `agent-skills` fallback. The `agent-skills` provider declares `detect.fallback`, so its `.agents/` marker resolves a lens only when no vendor marker is present. A project carrying `.codex/` (or `.agent/workflows/`) alongside the shared `.agents/skills/` home now resolves to that vendor outright instead of prompting `codex` vs `agent-skills`. Several vendor markers together still surface an ambiguous prompt.
14
+
15
+ ## User-facing
16
+
17
+ Codex and Antigravity projects no longer hit a spurious "which lens?" prompt on first scan: a `.codex/` (or `.agent/workflows/`) project is detected as that lens even though it also uses the shared `.agents/skills/` folder. `/` is left to the vendor's own behavior.
18
+
19
+ - Add an optional `presentation.invocationSigil` to the Provider manifest: the single glyph a lens's runtime uses to invoke a skill (`/` for Claude and Antigravity, `$` for Codex). The BFF projects it into `providerRegistry`, and the link-kind palette now paints the `invokes` edge-kind glyph (and its tooltip example) for the active lens instead of a hardcoded `/`. Lenses with no `/`/`$` invocation channel (`agent-skills`, `markdown`) omit it.
20
+
21
+ ## User-facing
22
+
23
+ Under the Codex lens, the Invokes connector filter on the graph now shows a `$` glyph, matching how Codex invokes skills, instead of a `/`.
24
+
3
25
  ## 0.63.0
4
26
 
5
27
  ### Minor Changes
package/architecture.md CHANGED
@@ -70,7 +70,7 @@ The UI is **not** a driving adapter; it is an HTTP/WS client of the Server. Exac
70
70
 
71
71
  A skill-map project sees its filesystem through exactly one **active provider lens** at any time: the provider whose extractors, classifiers, and resolution rules apply to the whole project during a scan. All other enabled providers stay registered but their provider-specific extractors are skipped.
72
72
 
73
- The lens is project-scope state, living in `.skill-map/settings.json` as the `activeProvider` key (see [`project-config.schema.json`](./schemas/project-config.schema.json#/properties/activeProvider)). When absent, the kernel auto-detects on first scan from filesystem markers and persists the result; if the heuristic is ambiguous (several markers), the CLI and UI prompt the user to pick one enabled provider. There is no unlensed state: when no vendor marker is present at all, the lens resolves to the open-standard `agent-skills` view, the universal default lens, which is NOT persisted, so a vendor marker added later still auto-detects on the next scan. The non-gated `core/markdown` base still classifies every unclaimed `.md` underneath, but it is not itself a selectable lens (see below). **The marker set is provider-owned**: each Provider declares its detection markers in its manifest `detect.markers` block (see [`provider.schema.json`](./schemas/extensions/provider.schema.json#/properties/detect)), e.g. `claude` → `.claude/`, `codex` → `.codex/`, `agent-skills` → `.agents/`, `antigravity` → `.agent/workflows/` (`AGENTS.md` is deliberately NOT an `codex` marker: it is the open agents.md standard, common in non-Codex repos and alongside `.claude/`, so keying detection off it would mis-route plain-markdown repos and force ambiguous prompts; a genuine Codex project is identified by `.codex/`). No central hardcoded detection table; the detectable set derives from registered Providers, so adding a Provider with a marker makes it auto-detectable without touching the resolver. When several markers match, the resolver returns the full candidate list in Provider iteration order, first match the default suggestion. A Provider with no `detect` block is never auto-suggested but can be selected manually. Google's Antigravity CLI (which replaced the retired Gemini CLI on 2026-05-19) uses the open-standard `.agents/skills/` for skills but its OWN `.agent/workflows/` (singular `.agent`) for workflows; the latter is its `detect` marker. `antigravity` ships `beta` (enabled by default), so its `.agent/workflows/` marker auto-detects the antigravity lens; a project that ALSO carries `.agents/` surfaces an ambiguous prompt (antigravity vs the `agent-skills` open default). `agent-skills` is `stable` (the locked open default lens), so a project's shared `.agents/` marker auto-detects it (a project with no vendor marker falls back to it), and a Google project's `.agents/skills/` files are owned by `agent-skills` for auto-detect, not by antigravity.
73
+ The lens is project-scope state, living in `.skill-map/settings.json` as the `activeProvider` key (see [`project-config.schema.json`](./schemas/project-config.schema.json#/properties/activeProvider)). When absent, the kernel auto-detects on first scan from filesystem markers and persists the result; if the heuristic is ambiguous (several VENDOR markers; the open `agent-skills` fallback never competes with a vendor, see Fallback precedence below), the CLI and UI prompt the user to pick one enabled provider. There is no unlensed state: when no vendor marker is present at all, the lens resolves to the open-standard `agent-skills` view, the universal default lens, which is NOT persisted, so a vendor marker added later still auto-detects on the next scan. The non-gated `core/markdown` base still classifies every unclaimed `.md` underneath, but it is not itself a selectable lens (see below). **The marker set is provider-owned**: each Provider declares its detection markers in its manifest `detect.markers` block (see [`provider.schema.json`](./schemas/extensions/provider.schema.json#/properties/detect)), e.g. `claude` → `.claude/`, `codex` → `.codex/`, `agent-skills` → `.agents/`, `antigravity` → `.agent/workflows/` (`AGENTS.md` is deliberately NOT an `codex` marker: it is the open agents.md standard, common in non-Codex repos and alongside `.claude/`, so keying detection off it would mis-route plain-markdown repos and force ambiguous prompts; a genuine Codex project is identified by `.codex/`). No central hardcoded detection table; the detectable set derives from registered Providers, so adding a Provider with a marker makes it auto-detectable without touching the resolver. When several markers match, the resolver returns the full candidate list in Provider iteration order, first match the default suggestion. **Fallback precedence**: the open default lens `agent-skills` declares `detect.fallback: true`, so its `.agents/` marker produces a candidate ONLY when no vendor marker is present; a project carrying a vendor marker alongside the shared `.agents/` home resolves to that vendor outright (the `.agents/skills/` directory is just where the vendor stores its skills, not a sign the project is a generic open-standard one). This is exactly what the scaffold `marker` field promises (`provider.schema.json#/properties/scaffold/properties/marker`): `sm tutorial --for codex` drops `.codex/` so the project resolves `codex`, never an ambiguous `codex` vs `agent-skills` pair. Several VENDOR markers together still surface a genuine ambiguous prompt. A Provider with no `detect` block is never auto-suggested but can be selected manually. Google's Antigravity CLI (which replaced the retired Gemini CLI on 2026-05-19) uses the open-standard `.agents/skills/` for skills but its OWN `.agent/workflows/` (singular `.agent`) for workflows; the latter is its `detect` marker. `antigravity` ships `beta` (enabled by default), so its `.agent/workflows/` marker auto-detects the antigravity lens; a project that ALSO carries `.agents/` still resolves to `antigravity` (its vendor marker outranks the `agent-skills` fallback, no ambiguous prompt). `agent-skills` is `stable` (the locked open default lens) and the sole `detect.fallback` Provider, so its shared `.agents/` marker auto-detects it only when no vendor marker is present (a project with no vendor marker falls back to it), and a Google project's `.agents/skills/` files are owned by `agent-skills` for auto-detect, not by antigravity.
74
74
 
75
75
  **Not-ready Providers ship disabled.** A Provider that is registered but not yet ready for end users declares `stability: 'experimental'` (see [`base.schema.json`](./schemas/extensions/base.schema.json#/properties/stability)), which ships it **disabled by default**: it does not classify, does not register, is never auto-detected, and is absent from the `selectable` set served by `GET /api/active-provider` until the operator opts in (`sm plugins enable <id>`, the Settings toggle, or a config override). There is no separate `comingSoon` flag: enabled/disabled is the single availability axis, and `stability: 'experimental'` is just the installed default flipped off. Today all four lenses ship enabled and selectable, `claude` (stable), `antigravity` (beta), `codex` (beta), and `agent-skills` (stable, the locked open default); no built-in Provider currently ships `experimental` (the flag's live built-in examples are extractors / analyzers, e.g. `core/mcp-tools` and `core/annotation-stale`). The non-gated `core/markdown` base is locked-enabled but is NOT a selectable lens, it is the substrate beneath whatever lens is active (see §Active-lens scope for providers). `stability: 'beta'` ships ENABLED like `stable` but renders a maturity badge (it is NOT a disabled state); this is distinct from `hideChip`, which only suppresses the per-card badge.
76
76
 
@@ -90,7 +90,7 @@ A provider plugin MAY declare it reads source files belonging to ANOTHER provide
90
90
 
91
91
  ### Universal extractors and per-provider extractors
92
92
 
93
- The lens does NOT gate the universal extractors under `core/` (markdown links, code-region file paths, external URLs, sidecar annotations); their semantics are provider-agnostic, so they run regardless of the active provider. Provider-specific extractors (Claude's `@`-directive parser, Cursor's picker-derived references, the future Codex AGENTS.md walker, future Antigravity parsers) declare `precondition: { provider: '<id>' }` on their manifest; the orchestrator invokes them on every node visited during the scan as long as the **active lens** is in the declared provider list, regardless of which provider's `classify()` claimed the node. The declared list MAY name more than one lens: the claude `@`-directive extractor runs under `claude` and `codex` (`precondition: { provider: ['claude', 'codex'] }`), because OpenAI Codex sub-agents share the same `@<name>` mention grammar; the `/command` (slash) extractor additionally runs under `antigravity` (`precondition: { provider: ['claude', 'codex', 'antigravity'] }`), since Antigravity invokes both skills and workflows by the same `/<name>` slash. codex declares `invokes: ['skill']`, so under the codex lens a `/name` slash signal resolves to the provider's open-standard `.agents/skills/<name>/SKILL.md` skills (Codex reads its skills from the open `.agents/skills/` layout); antigravity declares `invokes: ['skill', 'workflow']`, so under its lens a `/name` resolves to either a `.agents/skills/<name>/SKILL.md` skill or a `.agent/workflows/<name>.md` workflow. A body-scoped extractor reads whatever the walker yielded as the node body: for most providers the text after the frontmatter fence, but for a provider declaring `read.bodyField` (see [`provider.schema.json`](./schemas/extensions/provider.schema.json#/properties/read)) the named frontmatter field instead, since Codex sub-agents are pure TOML whose markdown prompt is the `developer_instructions` field, the codex provider sets `bodyField: 'developer_instructions'` so that prompt flows through the same body pipeline (body hash, markdown-link / backtick-path / external-url, and the lens-gated `@` / `/`).
93
+ The lens does NOT gate the universal extractors under `core/` (markdown links, code-region file paths, external URLs, sidecar annotations); their semantics are provider-agnostic, so they run regardless of the active provider. Provider-specific extractors (Claude's `@`-directive and `/command` parsers, OpenAI Codex's `$skill` and `@`-file parsers, future Antigravity parsers) declare `precondition: { provider: '<id>' }` on their manifest; the orchestrator invokes them on every node visited during the scan as long as the **active lens** is in the declared provider list, regardless of which provider's `classify()` claimed the node. The declared list MAY name more than one lens ONLY when the runtimes genuinely share a grammar: the claude `/command` (slash) extractor runs under `claude` AND `antigravity` (`precondition: { provider: ['claude', 'antigravity'] }`), since both invoke by `/<name>` (Antigravity declares `invokes: ['skill', 'workflow']`, so a `/name` resolves to either a `.agents/skills/<name>/SKILL.md` skill or a `.agent/workflows/<name>.md` workflow). But a runtime whose grammar DIFFERS owns its OWN extractor rather than borrowing one: OpenAI Codex reserves `/` for its built-in commands and invokes a user skill with `$`, so it ships a codex-only `dollar-skill` extractor (`$name` `invokes`, resolved via codex's `invokes: ['skill']` to a `.agents/skills/<name>/SKILL.md` skill) instead of the claude slash parser; and Codex's `@` is a file picker, not an agent-mention grammar, so it ships a codex-only `at-file` extractor (a path- or extension-shaped `@foo.md` → a path-resolved `references` link) instead of claude's `@`-directive (whose bare-`@handle` → `mentions` grammar does not apply to Codex). The claude `@`-directive thus stays `claude`-only. A body-scoped extractor reads whatever the walker yielded as the node body: for most providers the text after the frontmatter fence, but for a provider declaring `read.bodyField` (see [`provider.schema.json`](./schemas/extensions/provider.schema.json#/properties/read)) the named frontmatter field instead, since Codex sub-agents are pure TOML whose markdown prompt is the `developer_instructions` field, the codex provider sets `bodyField: 'developer_instructions'` so that prompt flows through the same body pipeline (body hash, markdown-link / backtick-path / external-url, and the lens-gated grammar extractors: `@` / `/` under claude, `$` / `@`-file under codex).
94
94
 
95
95
  The gate is the active lens, not the node's provider. A `@handle` token in `CLAUDE.md` or `notes/todo.md` (files the `claude` provider disclaims to `core/markdown`) still gets parsed by `claude/at-directive` under the `claude` lens, because the lens represents the runtime grammar and the runtime reads markdown across the whole project, not only files it owns. The earlier double-check ("node's provider matches AND the lens") silently dropped that surface; dropping the node side restores it. Cross-lens isolation holds via the lens half alone: under `codex`, claude extractors are silent on every node (including `.claude/*`) because lens authorisation is missing. Under the open-standard `agent-skills` default lens (a project with no vendor marker), the `claude` / `codex`-gated extractors stay silent because `agent-skills` is not in their declared provider allowlist; only the universal extractors run, alongside the open-standard `skill` classifier.
96
96
 
@@ -321,7 +321,7 @@ The `ui` block is required (not optional) by design: making it optional would fo
321
321
 
322
322
  The kernel ships every Provider's per-kind `ui` block to the BFF at boot; the BFF aggregates them into a `kindRegistry` map embedded in every payload-bearing REST envelope (see [`cli-contract.md` §Server](./cli-contract.md#server)). The UI consumes `kindRegistry` directly; built-in and user-plugin kinds render identically.
323
323
 
324
- Each Provider ALSO declares a top-level `presentation` block (`provider.schema.json#/properties/presentation`: `label`, `color`, optional `colorDark` / `icon` / `emoji` / `hideChip`) describing the Provider's own identity, distinct from its kinds' visuals. (Named `presentation`, not `ui`, because the shared extension `ui` key is the view-contributions map declared only by extractor / analyzer kinds.) The BFF aggregates these into a sibling `providerRegistry` map (keyed by Provider id) on the same envelopes. The UI consumes `providerRegistry` to render the active-lens dropdown, topbar lens chip, and per-node provider chip on cards from the real registered-Provider set, never a hardcoded list. Each entry carries an `isLens` flag projected from the Provider's `gatedByActiveLens`: the dropdown lists only lens entries (gated Providers), so the non-gated `core/markdown` base never appears there even though it keeps a registry entry for chip lookups. `hideChip: true` (set by the universal `markdown` base) suppresses the per-card chip; combined with `isLens: false` the base shows on no lens surface at all. Unlike kind colors (normalised across Providers so every `agent` paints the same), Provider colors are deliberately distinct so the chip tells the user which platform a node came from.
324
+ Each Provider ALSO declares a top-level `presentation` block (`provider.schema.json#/properties/presentation`: `label`, `color`, optional `colorDark` / `icon` / `emoji` / `hideChip` / `invocationSigil`) describing the Provider's own identity, distinct from its kinds' visuals. (Named `presentation`, not `ui`, because the shared extension `ui` key is the view-contributions map declared only by extractor / analyzer kinds.) The BFF aggregates these into a sibling `providerRegistry` map (keyed by Provider id) on the same envelopes. The UI consumes `providerRegistry` to render the active-lens dropdown, topbar lens chip, and per-node provider chip on cards from the real registered-Provider set, never a hardcoded list. Each entry carries an `isLens` flag projected from the Provider's `gatedByActiveLens`: the dropdown lists only lens entries (gated Providers), so the non-gated `core/markdown` base never appears there even though it keeps a registry entry for chip lookups. `hideChip: true` (set by the universal `markdown` base) suppresses the per-card chip; combined with `isLens: false` the base shows on no lens surface at all. Unlike kind colors (normalised across Providers so every `agent` paints the same), Provider colors are deliberately distinct so the chip tells the user which platform a node came from. The optional `invocationSigil` is the single glyph the lens's runtime uses to invoke a skill / command (`/` for the slash-invoking `claude` / `antigravity`, `$` for `codex`); the UI's link-kind palette joins it against the active lens to paint the `invokes` edge-kind glyph (and its tooltip example) so the toggle mirrors the lens's source syntax instead of a hardcoded `/`. Omitted for lenses with no `/`/`$` invocation channel (`agent-skills`, `core/markdown`), under which no `invokes` edge arises, so the glyph is never painted.
325
325
 
326
326
  ### Provider · dispatch order and the universal markdown fallback
327
327
 
@@ -381,7 +381,7 @@ Each Provider MAY declare an optional `reservedNames: Record<kind, string[]>` ma
381
381
 
382
382
  The kernel intersects each Provider's `reservedNames[kind]` catalog with the scanned graph at orchestrator time. For every node the post-walk pipeline derives its normalised identifiers via the §Provider · kind identifiers contract, then tests them against the reserved set of the node's OWN Provider (**self scope**): `reservedNames[node.kind]` of `node.provider`. Claude classifies `.claude/commands/help.md` as `claude`/`command` and reserves `help` under `command`, so the file is flagged.
383
383
 
384
- A runtime that adopts the open `.agents/skills/` standard instead of a vendor directory **reuses the `agent-skills` classifier + `skill` kind in its OWN manifest** (plain manifest composition, no kernel rule), and with them the **base reserved-name catalog the neutral `agent-skills` Provider owns** under `skill`: the universal slash commands an agent CLI ships built-in regardless of vendor (`help`, `config`, `mcp`, `model`, `clear`, `exit`, …). The open standard itself documents no reserved names, so this base is skill-map's curated cross-vendor common subset, not a clause of the standard; the neutral `agent-skills` lens enforces exactly this base, so a user `.agents/skills/help/SKILL.md` is flagged under it. A vendor that adopts the standard **spreads the base and appends its OWN runtime-specific verbs**: Google's Antigravity reuses the classifier and, on top of the inherited base, reserves the rest of `agy`'s built-in slash commands under `skill`, so when `activeProvider === 'antigravity'` a user `.agents/skills/goal/SKILL.md` (classified as `antigravity`/`skill`) is flagged because `/goal` is a built-in, while the neutral base never carries `agy`-specific verbs (a future Codex lens that adopts the standard inherits the same base, not Antigravity's extras). There is no cross-provider "lens scope": each lens classifies its own territory and self scope tests it against that Provider's own (base + extras) catalog.
384
+ A runtime that adopts the open `.agents/skills/` standard **reuses the `agent-skills` classifier + `skill` kind in its OWN manifest** (plain manifest composition, no kernel rule). Whether it ALSO reserves skill names depends on its **invocation channel**: a reserved name is the name of a built-in the runtime consumes through a particular sigil, so reserving a `skill` name only makes sense for a runtime that can invoke a skill through the **`/` command channel**, where a user skill could shadow a built-in `/` command. The `agent-skills` Provider exports a shared `COMMONS_RESERVED_NAMES` catalog (the universal cross-vendor slash commands an agent CLI ships built-in: `help`, `config`, `model`, `clear`, …), but it is applied ONLY by such `/`-invoking lenses. Google's Antigravity is one (its skills + workflows are `/`-invoked): it spreads the base and appends its own verbs (`goal`, …) under `skill` AND `workflow`, so when `activeProvider === 'antigravity'` a user `.agents/skills/goal/SKILL.md` is flagged because `/goal` is a built-in. The neutral `agent-skills` lens reserves **nothing**: the open Agent Skills standard documents no `/`-invocation (a skill activates by its `description` and connects by markdown links), so a skill name cannot shadow a `/` command and a `.agents/skills/help/SKILL.md` is NOT flagged under it. **OpenAI Codex likewise reserves no skill names**: it invokes skills with `$` (`$skill`, parsed by the codex `dollar-skill` extractor), a namespace disjoint from its built-in `/` commands, so a `$`-skill named `model` cannot shadow `/model`. There is no cross-provider "lens scope": each lens classifies its own territory and self scope tests it against that Provider's OWN catalog (empty for `agent-skills` and `codex`, base + extras for `antigravity`).
385
385
 
386
386
  A node landing in the reserved set joins a per-scan `Set<nodePath>` consumed by the score-phase `core/name-reserved` analyzer, which co-locates two effects in one pass (detection still lives in the orchestrator, so the same set drives both):
387
387
 
@@ -389,7 +389,7 @@ A node landing in the reserved set joins a per-scan `Set<nodePath>` consumed by
389
389
 
390
390
  2. **It downgrades any link resolving to a reserved target** (by path OR name match) by subtracting `RESERVED_PENALTY = 0.9` (a `delta` op) from the 1.0 baseline, folding it to `RESERVED_TARGET = 0.1`, emitting the `delta -0.9` in the same score-phase pass as its reserved warns. The reserved-target set is computed by the post-walk lift and surfaced via `ctx.reservedNodePaths`. The visual weight drops well below the broken floor (`0.5`) so the operator sees the edge resolves to a file the runtime ignores. When the trigger has multiple candidates (name index collision) and the strict-kind filter accepts more than one, the resolver picks the first allowed candidate; if non-reserved, the link keeps the 1.0 baseline, and only when EVERY accepted candidate is reserved does the penalty apply. With `core/name-reserved` disabled, a reserved-resolving link gets no `delta -0.9` and no warn, falling back to the 1.0 baseline (symmetric disable).
391
391
 
392
- The lookup normalises both sides through the §Extractor · trigger normalization pipeline, so a literal `Init-Project` in the manifest still matches a user `name: init project` or filename `Init-Project.md`. The catalog is intentionally per-kind, not global: a name reserved for commands (`/help`) MAY legitimately appear as a skill (a "help" skill triggered through a non-command channel). Lens scope respects the same per-kind boundary: Antigravity declares its reserved names under `skill` AND `workflow` (not `command`) because the invocables they shadow are skill files (`.agents/skills/`) and workflow files (`.agent/workflows/`), both invoked by `/<name>`, so skill- and workflow-kind nodes are tested.
392
+ The lookup normalises both sides through the §Extractor · trigger normalization pipeline, so a literal `Init-Project` in the manifest still matches a user `name: init project` or filename `Init-Project.md`. The catalog is intentionally per-kind AND per-channel, not global: a name is reserved only for a kind the active runtime invokes through the channel that owns that name. `/help` is reserved for a claude `command` or an antigravity `/`-invoked `skill` / `workflow`, but the same `help` is free as an OpenAI Codex `$`-skill or an `agent-skills` description-activated skill, because neither is reachable through the `/` channel (the "help skill triggered through a non-command channel" case). Antigravity declares its reserved names under `skill` AND `workflow` (both invoked by `/<name>`), not `command`, because the invocables they shadow are skill files (`.agents/skills/`) and workflow files (`.agent/workflows/`).
393
393
 
394
394
  **Update policy.** Built-in catalogs drift as vendor runtimes evolve. Each catalog change ships as a kernel patch with a changeset entry; the catalog is API surface users rely on the analyzer to reflect. User-installed Providers MAY declare their own `reservedNames` with the same shape; the analyzer and penalty run uniformly across built-in and user-installed Providers.
395
395
 
package/cli-contract.md CHANGED
@@ -119,7 +119,7 @@ The project sees its filesystem through exactly one **active provider lens** at
119
119
 
120
120
  CLI surfaces:
121
121
 
122
- - **Auto-detect on first scan**: when `activeProvider` is absent, `sm scan` and `sm watch` run a filesystem heuristic driven by each Provider's manifest `detect.markers` (e.g. `.claude/` → `claude`, `.codex/` → `codex`, `.agents/` → `agent-skills`; `AGENTS.md` is deliberately not a marker, it is the vendor-neutral agents.md standard). The marker set is provider-owned, not hardcoded. On unambiguous match, the result is persisted to `settings.json` and the scan proceeds; on no match, the lens defaults to the open-standard `agent-skills` view (the universal default lens) without persisting it, and the scan proceeds silently (a vendor marker added later still auto-detects on the next scan); on ambiguous match (multiple detected), it prompts interactively (or fails with exit code 2 under `--yes` if no default is configured). Google's Antigravity CLI declares no vendor-specific marker (it adopted the open-standard `.agents/` layout, auto-detected as `agent-skills`); the `antigravity` lens is set manually via `sm config set activeProvider antigravity`.
122
+ - **Auto-detect on first scan**: when `activeProvider` is absent, `sm scan` and `sm watch` run a filesystem heuristic driven by each Provider's manifest `detect.markers` (e.g. `.claude/` → `claude`, `.codex/` → `codex`, `.agents/` → `agent-skills`; `AGENTS.md` is deliberately not a marker, it is the vendor-neutral agents.md standard). The marker set is provider-owned, not hardcoded. On unambiguous match, the result is persisted to `settings.json` and the scan proceeds; on no match, the lens defaults to the open-standard `agent-skills` view (the universal default lens) without persisting it, and the scan proceeds silently (a vendor marker added later still auto-detects on the next scan); on ambiguous match (multiple VENDOR markers detected), it prompts interactively (or fails with exit code 2 under `--yes` if no default is configured). **Fallback precedence**: the open default `agent-skills` declares `detect.fallback`, so its `.agents/` marker never competes with a vendor; a project carrying `.codex/` (or `.agent/workflows/`) alongside the shared `.agents/` skill home resolves to that vendor outright, no prompt. Google's Antigravity CLI auto-detects from its own `.agent/workflows/` marker (it stores skills under the open-standard `.agents/skills/` but keeps workflows under `.agent/workflows/`); a project with only `.agents/` and no vendor marker auto-detects as `agent-skills`.
123
123
  - **Manual override**: `sm config set activeProvider <id>` switches the lens, drops the `scan_*` zone atomically (see [`db-schema.md`](./db-schema.md#zones)), and triggers an immediate rescan under the new lens. `state_*` and `config_*` zones survive.
124
124
  - **No per-scan flag**: there is no `sm scan --provider=<id>` flag. The lens is a project-level decision; the drop+rescan cost makes per-invocation switching the wrong default UX.
125
125
 
package/index.json CHANGED
@@ -174,14 +174,14 @@
174
174
  }
175
175
  ]
176
176
  },
177
- "specPackageVersion": "0.63.0",
177
+ "specPackageVersion": "0.64.0",
178
178
  "integrity": {
179
179
  "algorithm": "sha256",
180
180
  "files": {
181
- "CHANGELOG.md": "3b206cc04092e6a60b720983546117e6a76ef564ae4cee9672d3428e58048d6e",
181
+ "CHANGELOG.md": "44f2ede341c9c7affd4aaed8ce0aa0a2145feed820bb050e03b3261ff31ba9e3",
182
182
  "README.md": "a790cd010b46d47883d1f37e3893cea9d7aa69ec4750c0202e6a0c99991e7980",
183
- "architecture.md": "f536b1a660958f701fd356858ca352a0929ac7454c6d59de71644f3693d9f21a",
184
- "cli-contract.md": "07a1b15ec07a9bbcbe9d1eccba3ca7234043e52bbb584c25470f66d571e6b2ae",
183
+ "architecture.md": "5c4da8eba842994654ba112debe0fc3271228fe9b7f289c17aa8f3ecc0d5a648",
184
+ "cli-contract.md": "198469bab4a9a1cc31870136bf9a5fb90ea9bc80372c2926a65564c2f6c5b217",
185
185
  "conformance/README.md": "dcbef7249f161acf597552a05dcadc813cd0ced430dcd3f813fcf5e1c876335d",
186
186
  "conformance/cases/backtick-path-extraction.json": "4620e7f8bc161fc57cb44001e9d99879c7e22b4865a0c27a20dc28969cd936d9",
187
187
  "conformance/cases/extractor-collision-detection.json": "179a02c61892f0d26492de0c4e2c327fa6b4986d1265a8f119e871df6afe4658",
@@ -234,12 +234,12 @@
234
234
  "interfaces/security-scanner.md": "0996dd782e2d39d4791f2e290da4bb1a68a5b30c1f79187977188ec8e3fe6ef2",
235
235
  "job-events.md": "2c7017f5f0003b19653424111a07043487173cbe88b51e961598bb1693987059",
236
236
  "job-lifecycle.md": "ce33bc8bb5090ea183f860e495bfccc2a4a0ac2e23f6ebad83b9c28aad59124e",
237
- "plugin-author-guide.md": "1b5980a76621c9aba7587b213d648e33f4a6c428ac8b40a5ee6e2378229c3fa4",
237
+ "plugin-author-guide.md": "75ffa1ca20a46b1097fd85f0f99d43875f50b1c9a4f31fad5962ccae31c7c64d",
238
238
  "plugin-kv-api.md": "5e095581020043af73ff028e272f56d42ca9eb6e506dd777d45703f9db796a5b",
239
239
  "plugin-quickstart.md": "19092b278d80df357ea623dc3bd9f833d059582ee1356f317621913d91e50512",
240
240
  "prompt-preamble.md": "5d0f836688aa23eafc32104c3174132340b268361f6060326eec84da17c6ad6d",
241
241
  "schemas/annotations.schema.json": "09fcebc86e3b793bf9f03a35b38e5ca2a08d79ac3504f6f03895ac2ae1c2aded",
242
- "schemas/api/rest-envelope.schema.json": "28b9155476dbeca18b51ba019deb5cfa687503574f8b1d691830c60746ede9d4",
242
+ "schemas/api/rest-envelope.schema.json": "994bee11c3809210416489a97d9a2d1ba418f80366b4130adf0568e7afa9fd03",
243
243
  "schemas/bump-report.schema.json": "c763e1f89f2665c479d6a4985c1d324c65e5278331ebab82220287a07e4c4429",
244
244
  "schemas/conformance-case.schema.json": "958b316d646d0c64a715a7a28cee66d2c2d2498a60dbfc5ae8970687c2a96954",
245
245
  "schemas/conformance-result.schema.json": "14f983a8f4e62cd4ff964688c9b2b026a3bee3a0b762b64091c8c34db5b75777",
@@ -251,7 +251,7 @@
251
251
  "schemas/extensions/formatter.schema.json": "880dc379ad545a62404403533a01eda5171edba0390561fc46ec6e986e0b9bd3",
252
252
  "schemas/extensions/hook.schema.json": "f56aef59e9986ffdf7d86aa2e048dccccf217000a358b8c64737cbd911c48dad",
253
253
  "schemas/extensions/provider-kind.schema.json": "499b2418bbe6d8a84a1608e26c56b52c2652a30ce314bc2989094418797dc1e6",
254
- "schemas/extensions/provider.schema.json": "33f189cb6d6e987d7af1d5a8968834f92ba674f5621953aaa1f84d3a079a53e3",
254
+ "schemas/extensions/provider.schema.json": "a45361a5a3c5fabeb5c4881ebfecc8cbb8a83daf232e949f8735c748a66e4016",
255
255
  "schemas/frontmatter/base.schema.json": "47f05ffa2a51f465f1b8df70cc7a1e7afe2c40f8d37826cd8a569977e9036b8d",
256
256
  "schemas/history-stats.schema.json": "436aa0ffe744bdb699000447e86b45724fbd2cc4642781074eb1527522b9058c",
257
257
  "schemas/input-types.schema.json": "93b27a1cbd1f131d42730eb9a89cf3af6889e9f17b20a48ce36133885503e01b",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skill-map/spec",
3
- "version": "0.63.0",
3
+ "version": "0.64.0",
4
4
  "description": "JSON Schemas, prose contracts, and conformance suite for the skill-map specification.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -94,7 +94,7 @@ Examples from the reference impl's built-in extensions:
94
94
  Built-ins split between two namespaces:
95
95
 
96
96
  - **`core/`**, kernel-internal primitives, platform-agnostic: every built-in analyzer, the ASCII formatter, the cross-vendor extractors (`annotations`, `markdown-link`, `backtick-path`, `external-url-counter`), the universal `markdown` Provider fallback, and the `update-check` hook.
97
- - **`claude/`**, the Claude Code Provider plugin: the Provider plus the Claude-flavoured extractors (`slash-command`, `at-directive`). Other vendor plugins (`antigravity`, `codex`, `agent-skills`) follow the same shape (Provider only).
97
+ - **`claude/`**, the Claude Code Provider plugin: the Provider plus the Claude-flavoured extractors (`slash-command`, `at-directive`). **`codex/`** ships the Provider plus its OWN grammar extractors (`dollar-skill` for `$skill` invocation, `at-file` for `@`-file references), because Codex's invocation grammar differs from Claude's (`/` is a built-in command, `@` is a file picker). The other vendor plugins (`antigravity`, `agent-skills`) are Provider-only: Antigravity reuses claude's `/command` parser via its precondition list (it shares the `/`-invoke grammar), and the neutral `agent-skills` lens relies on the universal `core/` extractors only.
98
98
 
99
99
  ### Extension id shape
100
100
 
@@ -183,6 +183,12 @@
183
183
  "type": "string",
184
184
  "minLength": 1,
185
185
  "description": "Name of the parsed-frontmatter field that carries this Provider's node body, projected from its `read.bodyField` (see `provider.schema.json`). Present only for Providers whose prompt lives inside structured frontmatter rather than after a fence (OpenAI Codex sub-agents are pure TOML whose markdown prompt is `developer_instructions`). The UI uses it to render that field as the node body and to omit it from the metadata dump; absent for ordinary frontmatter-fence Providers."
186
+ },
187
+ "invocationSigil": {
188
+ "type": "string",
189
+ "minLength": 1,
190
+ "maxLength": 1,
191
+ "description": "Single glyph this lens's runtime uses to invoke a skill / command, projected from its `presentation.invocationSigil` (see `provider.schema.json`). The UI joins it against the active lens to paint the `invokes` edge-kind glyph (and tooltip example) in the link-kind palette: `/` for `claude` / `antigravity`, `$` for `codex`. Absent for lenses with no invocation channel (`agent-skills`, `markdown`), under which no `invokes` edge arises."
186
192
  }
187
193
  }
188
194
  }
@@ -71,6 +71,12 @@
71
71
  "hideChip": {
72
72
  "type": "boolean",
73
73
  "description": "When `true`, the UI does NOT paint this Provider's chip on node cards. Reserved for the universal base Provider (`markdown`): the majority of nodes in any project carry it, so badging every generic `.md` would be visual noise and dilute the chip's purpose (signalling when a node came from a NON-default platform). The markdown base is non-gated (`gatedByActiveLens: false`, `isLens: false`), so it is not a selectable lens and never appears in the active-lens dropdown or topbar lens chip either; `hideChip` additionally suppresses its per-card badge. Defaults to `false` (chip shown)."
74
+ },
75
+ "invocationSigil": {
76
+ "type": "string",
77
+ "minLength": 1,
78
+ "maxLength": 1,
79
+ "description": "Single glyph the lens's runtime uses to invoke a skill / command, surfaced as the `invokes` edge-kind glyph (and its tooltip example) in the link-kind palette so the operator recognises this lens's source syntax instantly. `/` for the slash-invoking lenses (`claude` commands + skills, `antigravity` skills + workflows), `$` for `codex` (whose skills are invoked `$skill`, with `/` reserved for Codex's own built-in commands). Omitted for lenses with no `/`/`$` invocation channel (the open-standard `agent-skills`, where skills activate by `description`, and the non-lens `markdown` base): under those lenses no `invokes` edge arises, so the palette never paints the glyph. Mirrors `IProviderUi.invocationSigil`; projected into `providerRegistry` (`api/rest-envelope.schema.json`) and joined client-side against the active lens."
74
80
  }
75
81
  }
76
82
  },
@@ -78,13 +84,17 @@
78
84
  "type": "object",
79
85
  "required": ["markers"],
80
86
  "additionalProperties": false,
81
- "description": "Auto-detection markers for the active-provider lens. The lens resolver checks each marker path (relative to the scope root) and, when present, suggests this Provider as a candidate lens. Replaces the former hardcoded detection table: the set of detectable Providers now derives from the registered Providers themselves. Optional, a Provider with no `detect` block is never auto-suggested (it can still be selected manually). When several Providers match, the resolver returns the full candidate list in Provider iteration order and the first match is the default suggestion.",
87
+ "description": "Auto-detection markers for the active-provider lens. The lens resolver checks each marker path (relative to the scope root) and, when present, suggests this Provider as a candidate lens. Replaces the former hardcoded detection table: the set of detectable Providers now derives from the registered Providers themselves. Optional, a Provider with no `detect` block is never auto-suggested (it can still be selected manually). When several Providers match, the resolver returns the full candidate list in Provider iteration order and the first match is the default suggestion. **Fallback precedence**: a Provider whose `detect.fallback` is `true` (the open-standard `agent-skills` lens, whose `.agents/` marker is also the shared skill home that vendor lenses populate) is dropped from the candidate list whenever any non-fallback (vendor) Provider also matched, so a project carrying `.codex/` alongside `.agents/` resolves to `codex` outright instead of prompting `codex` vs `agent-skills`. The fallback only stands when no vendor marker is present; several vendor markers still produce a genuine ambiguous list.",
82
88
  "properties": {
83
89
  "markers": {
84
90
  "type": "array",
85
91
  "minItems": 1,
86
92
  "description": "Paths relative to the scope root whose existence signals this Provider's presence (e.g. `['.claude']`, `['.codex', 'AGENTS.md']`). A directory or a file both count; existence is the only test.",
87
93
  "items": { "type": "string", "minLength": 1 }
94
+ },
95
+ "fallback": {
96
+ "type": "boolean",
97
+ "description": "When `true`, this Provider is the open-standard FALLBACK lens: its markers produce a detection candidate ONLY when no non-fallback (vendor) Provider matched under the same scope. Reserved for `agent-skills`, whose `.agents/` marker is the shared open-standard skill home that vendor lenses (`codex`, `antigravity`) also populate; without this flag a `.codex/` + `.agents/` project would falsely read as an ambiguous `codex` vs `agent-skills` pair. Vendor Providers omit it (default `false`) so two vendor markers still surface a real ambiguous prompt. Mirrors `IProviderDetect.fallback`."
88
98
  }
89
99
  }
90
100
  },