@skill-map/cli 0.32.0 → 0.33.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/dist/cli/tutorial/sm-tutorial/SKILL.md +27 -7
- package/dist/cli.js +851 -318
- package/dist/cli.js.map +1 -1
- package/dist/index.js +206 -15
- package/dist/index.js.map +1 -1
- package/dist/kernel/index.d.ts +198 -3
- package/dist/kernel/index.js +206 -15
- package/dist/kernel/index.js.map +1 -1
- package/dist/migrations/001_initial.sql +2 -2
- package/dist/ui/chunk-2QZDJSJN.js +1 -0
- package/dist/ui/chunk-5CFY2K3Y.js +135 -0
- package/dist/ui/{chunk-YQIWQVJ6.js → chunk-L3OLNVKI.js} +9 -9
- package/dist/ui/chunk-OKFHCQNJ.js +123 -0
- package/dist/ui/{chunk-47OZB7LR.js → chunk-TKV6TXTI.js} +1 -1
- package/dist/ui/{chunk-WJLIYGWJ.js → chunk-UK5YFHL3.js} +1 -1
- package/dist/ui/{chunk-FEPH4VNB.js → chunk-UMCC32EJ.js} +3 -3
- package/dist/ui/{chunk-VDQLDTTR.js → chunk-YZ7KCL3G.js} +1 -1
- package/dist/ui/index.html +1 -1
- package/dist/ui/main-H7FURBYT.js +2 -0
- package/migrations/001_initial.sql +2 -2
- package/package.json +3 -2
- package/dist/ui/chunk-BCQZKYOD.js +0 -1
- package/dist/ui/chunk-LS2NXZQZ.js +0 -135
- package/dist/ui/chunk-WCE7MTK5.js +0 -123
- package/dist/ui/main-LJIHL73M.js +0 -2
package/dist/kernel/index.d.ts
CHANGED
|
@@ -501,7 +501,26 @@ interface IExtensionBase {
|
|
|
501
501
|
*/
|
|
502
502
|
type NodeKind = 'skill' | 'agent' | 'command' | 'markdown';
|
|
503
503
|
type LinkKind = 'invokes' | 'references' | 'mentions' | 'supersedes';
|
|
504
|
-
|
|
504
|
+
/**
|
|
505
|
+
* Extractor's self-assessed confidence, normalized to `[0..1]`. Drives
|
|
506
|
+
* UI edge opacity in the graph view (more confident = more opaque edge).
|
|
507
|
+
* Migrated from the legacy `'high' | 'medium' | 'low'` string union to
|
|
508
|
+
* a numeric range so callers can express finer granularity than three
|
|
509
|
+
* buckets. The named tiers below (`ConfidenceTier`) preserve the
|
|
510
|
+
* legacy buckets as constants for callers that prefer bucket-thinking.
|
|
511
|
+
*
|
|
512
|
+
* Reference scoring (guideline, not contract):
|
|
513
|
+
*
|
|
514
|
+
* `1.0` structured input (sidecar `supersedes`)
|
|
515
|
+
* `0.95` unambiguous syntax (`[text](file.md)`, `https://…`)
|
|
516
|
+
* `0.85` strong signal with one inference (`@file.md`)
|
|
517
|
+
* `0.5` genuine ambiguity (`@bare-handle`)
|
|
518
|
+
*
|
|
519
|
+
* Validation: the orchestrator's `validateLink` rejects values outside
|
|
520
|
+
* `[0..1]` with an `extension.error` event, mirroring the LinkKind
|
|
521
|
+
* enum check. Missing confidence defaults to `ConfidenceTier.MEDIUM`.
|
|
522
|
+
*/
|
|
523
|
+
type Confidence = number;
|
|
505
524
|
type Severity = 'error' | 'warn' | 'info';
|
|
506
525
|
type Stability = 'experimental' | 'stable' | 'deprecated';
|
|
507
526
|
/**
|
|
@@ -569,6 +588,25 @@ interface Node {
|
|
|
569
588
|
* truthy `isFavorite` only ever lands when the BFF set it.
|
|
570
589
|
*/
|
|
571
590
|
isFavorite?: boolean;
|
|
591
|
+
/**
|
|
592
|
+
* When `true`, the node is synthetic / derived: it does not correspond
|
|
593
|
+
* to a single file on disk. Reconstructed on every scan from the
|
|
594
|
+
* file(s) listed in `derivedFrom`. Synthetic nodes use a non-filesystem
|
|
595
|
+
* path scheme (e.g. `mcp://github`) so the identifier is stable and
|
|
596
|
+
* visibly non-physical. See
|
|
597
|
+
* [`node.schema.json`](../../spec/schemas/node.schema.json) for the
|
|
598
|
+
* normative contract. Absent / `false` for ordinary filesystem-backed
|
|
599
|
+
* entities. Stability: experimental.
|
|
600
|
+
*/
|
|
601
|
+
virtual?: boolean;
|
|
602
|
+
/**
|
|
603
|
+
* Paths of the source files this node was derived from. Required (and
|
|
604
|
+
* only meaningful) when `virtual === true`. Drives invalidation: any
|
|
605
|
+
* change to a listed source between scans propagates into the virtual
|
|
606
|
+
* node's hashes. Empty / absent when the node is a regular filesystem
|
|
607
|
+
* entity (the `path` itself is the source).
|
|
608
|
+
*/
|
|
609
|
+
derivedFrom?: string[];
|
|
572
610
|
}
|
|
573
611
|
/**
|
|
574
612
|
* Drift status of a co-located `.sm` sidecar relative to the live
|
|
@@ -621,6 +659,83 @@ interface Link {
|
|
|
621
659
|
location?: LinkLocation | null;
|
|
622
660
|
raw?: string | null;
|
|
623
661
|
}
|
|
662
|
+
/**
|
|
663
|
+
* Scope of a `Signal` within its originating node. Mirrors
|
|
664
|
+
* `signal.schema.json#/properties/scope`.
|
|
665
|
+
*
|
|
666
|
+
* - `body` = markdown body or equivalent prose payload.
|
|
667
|
+
* - `frontmatter` = parsed metadata block at the top of the file.
|
|
668
|
+
* - `sidecar` = co-located `.sm` overlay.
|
|
669
|
+
*/
|
|
670
|
+
type SignalScope = 'body' | 'frontmatter' | 'sidecar';
|
|
671
|
+
/**
|
|
672
|
+
* Surface context for a body-scope `Signal`. Mirrors
|
|
673
|
+
* `signal.schema.json#/properties/context/enum`. Null when the signal is in
|
|
674
|
+
* normal prose or when the context concept does not apply (frontmatter /
|
|
675
|
+
* sidecar scopes).
|
|
676
|
+
*/
|
|
677
|
+
type SignalContext = 'code-block' | 'inline-code' | 'escaped';
|
|
678
|
+
/**
|
|
679
|
+
* Byte-range location for a body-scope `Signal`. `start` is inclusive,
|
|
680
|
+
* `end` is exclusive (one past the last char).
|
|
681
|
+
*/
|
|
682
|
+
interface SignalRange {
|
|
683
|
+
start: number;
|
|
684
|
+
end: number;
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* One alternative interpretation of a `Signal`. The resolver picks the
|
|
688
|
+
* winning candidate per Signal and materialises it as a `Link`; the
|
|
689
|
+
* rejected candidates remain on `IAnalyzerContext.signals` for
|
|
690
|
+
* collision-detection and conflict-visualisation analyzers.
|
|
691
|
+
*
|
|
692
|
+
* `confidence` is numeric `[0..1]`, identical shape to the `Link`'s
|
|
693
|
+
* `Confidence` type after the Phase 4 migration. No conversion needed
|
|
694
|
+
* when the resolver materialises a winning candidate.
|
|
695
|
+
*/
|
|
696
|
+
interface SignalCandidate {
|
|
697
|
+
extractorId: string;
|
|
698
|
+
kind: LinkKind;
|
|
699
|
+
target: string;
|
|
700
|
+
/** `[0..1]`. Reference scoring guideline lives in `signal.schema.json`. */
|
|
701
|
+
confidence: number;
|
|
702
|
+
rationale?: string;
|
|
703
|
+
trigger?: LinkTrigger | null;
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Intermediate Representation (IR) emitted by extractors via
|
|
707
|
+
* `ctx.emitSignal(signal)`. The kernel's resolver phase consumes
|
|
708
|
+
* `Signal[]` and produces final `Link[]` per the active Provider's
|
|
709
|
+
* `resolverRules`. Opt-in: extractors with unambiguous detections keep
|
|
710
|
+
* using `ctx.emitLink(link)` directly. See
|
|
711
|
+
* [`signal.schema.json`](../../spec/schemas/signal.schema.json) for the
|
|
712
|
+
* normative contract.
|
|
713
|
+
*/
|
|
714
|
+
interface Signal {
|
|
715
|
+
/** `node.path` of the originating node. */
|
|
716
|
+
source: string;
|
|
717
|
+
scope: SignalScope;
|
|
718
|
+
/**
|
|
719
|
+
* Byte-range location within the source. Required for `scope: 'body'`,
|
|
720
|
+
* optional otherwise. Powers collision detection between extractors
|
|
721
|
+
* (overlapping ranges) and code-block awareness (the orchestrator can
|
|
722
|
+
* mark ranges that fall inside code spans).
|
|
723
|
+
*/
|
|
724
|
+
range?: SignalRange | null;
|
|
725
|
+
/**
|
|
726
|
+
* Structured-data location for `frontmatter` / `sidecar` scopes. Each
|
|
727
|
+
* entry is a step of the path: object keys are strings, array indices
|
|
728
|
+
* are integers serialised as strings. Example: `['tools', '0']`. Null
|
|
729
|
+
* for body scope or when the extractor does not track field locations.
|
|
730
|
+
*/
|
|
731
|
+
fieldPath?: string[] | null;
|
|
732
|
+
/** Verbatim matched text (body) or stringified value (frontmatter / sidecar). */
|
|
733
|
+
raw: string;
|
|
734
|
+
/** Surface context. Null when in normal prose or when not applicable. */
|
|
735
|
+
context?: SignalContext | null;
|
|
736
|
+
/** One or more alternative interpretations. At least one. */
|
|
737
|
+
candidates: SignalCandidate[];
|
|
738
|
+
}
|
|
624
739
|
interface IssueFix {
|
|
625
740
|
summary?: string;
|
|
626
741
|
autofixable?: boolean;
|
|
@@ -1828,6 +1943,36 @@ interface IProviderReadConfig {
|
|
|
1828
1943
|
* Confidence is per-emit (no manifest-level default).
|
|
1829
1944
|
*/
|
|
1830
1945
|
|
|
1946
|
+
/**
|
|
1947
|
+
* Payload accepted by `IExtractorCallbacks.emitNode`. A loose subset of
|
|
1948
|
+
* `Node` because the kernel fills the rest from the emission context:
|
|
1949
|
+
*
|
|
1950
|
+
* - `bodyHash`, `frontmatterHash` are computed from `derivedFrom` (the
|
|
1951
|
+
* hash of the sources concatenated in declared order, so the
|
|
1952
|
+
* virtual node's hashes drift when any source changes).
|
|
1953
|
+
* - `bytes`, `linksOutCount`, `linksInCount`, `externalRefsCount`
|
|
1954
|
+
* default to zero counts on emission; the orchestrator's
|
|
1955
|
+
* post-extraction recompute pass fills them in once links resolve.
|
|
1956
|
+
*
|
|
1957
|
+
* The emitter MUST supply `path` (canonical id), `kind` (registered in
|
|
1958
|
+
* a Provider's catalog), `derivedFrom` (one or more existing-node paths
|
|
1959
|
+
* the virtual node is derived from), and SHOULD supply `frontmatter`
|
|
1960
|
+
* with the metadata the UI / analyzers will surface.
|
|
1961
|
+
*/
|
|
1962
|
+
interface IEmittedNode {
|
|
1963
|
+
/** Synthetic identifier. Use a non-filesystem scheme (`mcp://`, etc). */
|
|
1964
|
+
path: string;
|
|
1965
|
+
/** Kind declared in some Provider's `kinds` catalog. */
|
|
1966
|
+
kind: string;
|
|
1967
|
+
/** Required for virtual nodes: paths of the source(s). */
|
|
1968
|
+
derivedFrom: string[];
|
|
1969
|
+
/** Always true on this surface; the kernel mirrors it to `Node.virtual`. */
|
|
1970
|
+
virtual: true;
|
|
1971
|
+
/** Provider id the node is attributed to (e.g. `'claude'`). */
|
|
1972
|
+
provider: string;
|
|
1973
|
+
/** Optional structured metadata the UI / analyzers read. */
|
|
1974
|
+
frontmatter?: Record<string, unknown>;
|
|
1975
|
+
}
|
|
1831
1976
|
/**
|
|
1832
1977
|
* Output callbacks supplied by the kernel on the extractor context.
|
|
1833
1978
|
*/
|
|
@@ -1839,6 +1984,35 @@ interface IExtractorCallbacks {
|
|
|
1839
1984
|
* `extension.error` event.
|
|
1840
1985
|
*/
|
|
1841
1986
|
emitLink(link: Link): void;
|
|
1987
|
+
/**
|
|
1988
|
+
* Emit a multi-candidate `Signal` for the kernel's resolver phase to
|
|
1989
|
+
* collapse into a single Link (or reject). Use this instead of
|
|
1990
|
+
* `emitLink` when the detection carries genuine ambiguity (multiple
|
|
1991
|
+
* plausible kinds / targets), needs byte-range awareness for
|
|
1992
|
+
* collision detection, or needs numeric confidence with
|
|
1993
|
+
* sub-tier granularity. Unambiguous detectors should keep using
|
|
1994
|
+
* `emitLink` directly. See
|
|
1995
|
+
* [`signal.schema.json`](../../../spec/schemas/signal.schema.json) for the
|
|
1996
|
+
* normative contract. Validated against the same closed kind enum;
|
|
1997
|
+
* off-spec Signals (no candidates, off-enum kind, confidence outside
|
|
1998
|
+
* `[0..1]`) drop silently with an `extension.error` event.
|
|
1999
|
+
*/
|
|
2000
|
+
emitSignal(signal: Signal): void;
|
|
2001
|
+
/**
|
|
2002
|
+
* Phase 5, emit a synthetic / virtual `Node` derived from the
|
|
2003
|
+
* scanning context (frontmatter, sidecar, config). Used by the
|
|
2004
|
+
* `core/mcp-tools` extractor to materialise an `mcp://<name>` node
|
|
2005
|
+
* out of a `tools: [mcp__<name>__*]` frontmatter entry, and by the
|
|
2006
|
+
* future Cursor / Codex MCP-config extractors that walk
|
|
2007
|
+
* `.cursor/mcp.json` / `~/.codex/config.toml`. The kernel
|
|
2008
|
+
* deduplicates by `node.path` against the walker's nodes AND across
|
|
2009
|
+
* extractor emissions: the FIRST emission of a given path wins,
|
|
2010
|
+
* subsequent emissions are silently ignored (idempotent semantics so
|
|
2011
|
+
* N skills referencing the same MCP collapse into one node). Emitted
|
|
2012
|
+
* nodes carry `virtual: true` and `derivedFrom: [...]` per
|
|
2013
|
+
* [`node.schema.json`](../../../spec/schemas/node.schema.json).
|
|
2014
|
+
*/
|
|
2015
|
+
emitNode(node: IEmittedNode): void;
|
|
1842
2016
|
/**
|
|
1843
2017
|
* Merge canonical, kernel-curated properties onto the current node's
|
|
1844
2018
|
* enrichment layer. The author-supplied frontmatter stays untouched
|
|
@@ -2004,6 +2178,18 @@ interface IAnalyzerContext {
|
|
|
2004
2178
|
* absolute path when present.
|
|
2005
2179
|
*/
|
|
2006
2180
|
cwd?: string;
|
|
2181
|
+
/**
|
|
2182
|
+
* Signals emitted by extractors during the scan, before the resolver
|
|
2183
|
+
* collapsed them into `links`. Populated when at least one extractor
|
|
2184
|
+
* opted into the Signal IR path (`ctx.emitSignal` in
|
|
2185
|
+
* `IExtractorCallbacks`). Empty / absent when every extractor used
|
|
2186
|
+
* `emitLink` directly (legacy and unambiguous paths). Treat as
|
|
2187
|
+
* read-only. Analyzers consume this for collision detection
|
|
2188
|
+
* (overlapping `range` from different extractors), fragmentation
|
|
2189
|
+
* detection, and conflict-visualisation; the resolved `links` remain
|
|
2190
|
+
* the source of truth for graph-level analyses.
|
|
2191
|
+
*/
|
|
2192
|
+
signals?: readonly Signal[];
|
|
2007
2193
|
/**
|
|
2008
2194
|
* Emit a per-node view contribution declared in this analyzer's
|
|
2009
2195
|
* manifest `viewContributions` map. Sync, void return; the
|
|
@@ -2489,6 +2675,8 @@ declare function runExtractorsForNode(opts: {
|
|
|
2489
2675
|
externalLinks: Link[];
|
|
2490
2676
|
enrichments: IEnrichmentRecord[];
|
|
2491
2677
|
contributions: IContributionRecord[];
|
|
2678
|
+
signals: Signal[];
|
|
2679
|
+
virtualNodes: Node[];
|
|
2492
2680
|
}>;
|
|
2493
2681
|
|
|
2494
2682
|
/**
|
|
@@ -2508,7 +2696,14 @@ declare function runExtractorsForNode(opts: {
|
|
|
2508
2696
|
interface RenameOp {
|
|
2509
2697
|
from: string;
|
|
2510
2698
|
to: string;
|
|
2511
|
-
|
|
2699
|
+
/**
|
|
2700
|
+
* Rename-heuristic confidence as a numeric tier. Body-hash matches
|
|
2701
|
+
* use `ConfidenceTier.HIGH` (`0.9`); frontmatter-hash matches use
|
|
2702
|
+
* `ConfidenceTier.MEDIUM` (`0.6`). Consumers that surface the tier
|
|
2703
|
+
* as a string (e.g. issue analyzerId `auto-rename-<tier>`) call
|
|
2704
|
+
* `renameTierLabel(confidence)` to recover the legacy label.
|
|
2705
|
+
*/
|
|
2706
|
+
confidence: number;
|
|
2512
2707
|
}
|
|
2513
2708
|
/**
|
|
2514
2709
|
* Pure rename / orphan classification per `spec/db-schema.md` §Rename
|
|
@@ -2524,7 +2719,7 @@ interface RenameOp {
|
|
|
2524
2719
|
* 2. **Medium-confidence (1:1)**: of the remaining deletions, pair
|
|
2525
2720
|
* each with the *unique* unclaimed `newPath` that shares its
|
|
2526
2721
|
* `frontmatterHash`. Emits `auto-rename-medium` (severity warn)
|
|
2527
|
-
* with `data: { from, to, confidence:
|
|
2722
|
+
* with `data: { from, to, confidence: ConfidenceTier.MEDIUM }`.
|
|
2528
2723
|
* 3. **Ambiguous (N:1)**: when a single `newPath` has more than one
|
|
2529
2724
|
* remaining frontmatter-matching candidate, emit ONE
|
|
2530
2725
|
* `auto-rename-ambiguous` issue per `newPath`, listing all
|
package/dist/kernel/index.js
CHANGED
|
@@ -100,7 +100,7 @@ import cl100k_base from "js-tiktoken/ranks/cl100k_base";
|
|
|
100
100
|
// package.json
|
|
101
101
|
var package_default = {
|
|
102
102
|
name: "@skill-map/cli",
|
|
103
|
-
version: "0.
|
|
103
|
+
version: "0.33.0",
|
|
104
104
|
description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
|
|
105
105
|
license: "MIT",
|
|
106
106
|
type: "module",
|
|
@@ -181,6 +181,7 @@ var package_default = {
|
|
|
181
181
|
"js-yaml": "4.1.1",
|
|
182
182
|
kysely: "0.28.17",
|
|
183
183
|
semver: "7.7.4",
|
|
184
|
+
"smol-toml": "1.6.1",
|
|
184
185
|
typanion: "3.14.0",
|
|
185
186
|
ws: "8.20.0"
|
|
186
187
|
},
|
|
@@ -692,12 +693,22 @@ var ORCHESTRATOR_TEXTS = {
|
|
|
692
693
|
runScanRootMissing: "runScan: root path '{{root}}' does not exist or is not a directory"
|
|
693
694
|
};
|
|
694
695
|
|
|
696
|
+
// kernel/types.ts
|
|
697
|
+
var ConfidenceTier = Object.freeze({
|
|
698
|
+
HIGH: 0.9,
|
|
699
|
+
MEDIUM: 0.6,
|
|
700
|
+
LOW: 0.3
|
|
701
|
+
});
|
|
702
|
+
|
|
695
703
|
// kernel/orchestrator/extractors.ts
|
|
696
704
|
async function runExtractorsForNode(opts) {
|
|
697
705
|
const internalLinks = [];
|
|
698
706
|
const externalLinks = [];
|
|
699
707
|
const enrichmentBuffer = /* @__PURE__ */ new Map();
|
|
700
708
|
const contributions = [];
|
|
709
|
+
const signals = [];
|
|
710
|
+
const virtualNodes = [];
|
|
711
|
+
const virtualNodePaths = /* @__PURE__ */ new Set();
|
|
701
712
|
const validators = loadSchemaValidators();
|
|
702
713
|
for (const extractor of opts.extractors) {
|
|
703
714
|
const qualifiedId = qualifiedExtensionId(extractor.pluginId, extractor.id);
|
|
@@ -769,6 +780,18 @@ async function runExtractorsForNode(opts) {
|
|
|
769
780
|
emittedAt: Date.now()
|
|
770
781
|
});
|
|
771
782
|
};
|
|
783
|
+
const emitSignal = (signal) => {
|
|
784
|
+
const validated = validateSignal(extractor, signal, opts.emitter);
|
|
785
|
+
if (!validated) return;
|
|
786
|
+
signals.push(validated);
|
|
787
|
+
};
|
|
788
|
+
const emitNode = (emitted) => {
|
|
789
|
+
if (virtualNodePaths.has(emitted.path)) return;
|
|
790
|
+
const node = buildVirtualNode(extractor, emitted, opts.emitter);
|
|
791
|
+
if (!node) return;
|
|
792
|
+
virtualNodePaths.add(node.path);
|
|
793
|
+
virtualNodes.push(node);
|
|
794
|
+
};
|
|
772
795
|
const store = opts.pluginStores?.get(extractor.pluginId);
|
|
773
796
|
const ctx = buildExtractorContext(
|
|
774
797
|
extractor,
|
|
@@ -778,6 +801,8 @@ async function runExtractorsForNode(opts) {
|
|
|
778
801
|
emitLink,
|
|
779
802
|
enrichNode,
|
|
780
803
|
emitContribution,
|
|
804
|
+
emitSignal,
|
|
805
|
+
emitNode,
|
|
781
806
|
store
|
|
782
807
|
);
|
|
783
808
|
await extractor.extract(ctx);
|
|
@@ -786,7 +811,9 @@ async function runExtractorsForNode(opts) {
|
|
|
786
811
|
internalLinks,
|
|
787
812
|
externalLinks,
|
|
788
813
|
enrichments: Array.from(enrichmentBuffer.values()),
|
|
789
|
-
contributions
|
|
814
|
+
contributions,
|
|
815
|
+
signals,
|
|
816
|
+
virtualNodes
|
|
790
817
|
};
|
|
791
818
|
}
|
|
792
819
|
function readDeclaredContributions(extension) {
|
|
@@ -811,7 +838,7 @@ function emitExtensionError(emitter, qualifiedId, nodePath, data) {
|
|
|
811
838
|
})
|
|
812
839
|
);
|
|
813
840
|
}
|
|
814
|
-
function buildExtractorContext(extractor, node, body, frontmatter, emitLink, enrichNode, emitContribution, store) {
|
|
841
|
+
function buildExtractorContext(extractor, node, body, frontmatter, emitLink, enrichNode, emitContribution, emitSignal, emitNode, store) {
|
|
815
842
|
const scope = extractor.scope ?? "both";
|
|
816
843
|
const settings = extractor.resolvedSettings ?? {};
|
|
817
844
|
return {
|
|
@@ -822,9 +849,62 @@ function buildExtractorContext(extractor, node, body, frontmatter, emitLink, enr
|
|
|
822
849
|
emitLink,
|
|
823
850
|
enrichNode,
|
|
824
851
|
emitContribution,
|
|
852
|
+
emitSignal,
|
|
853
|
+
emitNode,
|
|
825
854
|
...store !== void 0 ? { store } : {}
|
|
826
855
|
};
|
|
827
856
|
}
|
|
857
|
+
var VIRTUAL_NODE_PLACEHOLDER_HASH = "0".repeat(64);
|
|
858
|
+
function buildVirtualNode(extractor, emitted, emitter) {
|
|
859
|
+
const qualifiedId = qualifiedExtensionId(extractor.pluginId, extractor.id);
|
|
860
|
+
if (typeof emitted.path !== "string" || emitted.path.length === 0) {
|
|
861
|
+
emitter.emit(
|
|
862
|
+
makeEvent("extension.error", {
|
|
863
|
+
kind: "virtual-node-missing-path",
|
|
864
|
+
extensionId: qualifiedId,
|
|
865
|
+
message: `Extractor ${qualifiedId} emitted a virtual node with no path; dropped.`
|
|
866
|
+
})
|
|
867
|
+
);
|
|
868
|
+
return null;
|
|
869
|
+
}
|
|
870
|
+
if (typeof emitted.kind !== "string" || emitted.kind.length === 0) {
|
|
871
|
+
emitter.emit(
|
|
872
|
+
makeEvent("extension.error", {
|
|
873
|
+
kind: "virtual-node-missing-kind",
|
|
874
|
+
extensionId: qualifiedId,
|
|
875
|
+
virtualPath: emitted.path,
|
|
876
|
+
message: `Extractor ${qualifiedId} emitted a virtual node at '${emitted.path}' with no kind; dropped.`
|
|
877
|
+
})
|
|
878
|
+
);
|
|
879
|
+
return null;
|
|
880
|
+
}
|
|
881
|
+
if (!Array.isArray(emitted.derivedFrom) || emitted.derivedFrom.length === 0) {
|
|
882
|
+
emitter.emit(
|
|
883
|
+
makeEvent("extension.error", {
|
|
884
|
+
kind: "virtual-node-missing-derived-from",
|
|
885
|
+
extensionId: qualifiedId,
|
|
886
|
+
virtualPath: emitted.path,
|
|
887
|
+
message: `Extractor ${qualifiedId} emitted a virtual node at '${emitted.path}' with empty derivedFrom; dropped.`
|
|
888
|
+
})
|
|
889
|
+
);
|
|
890
|
+
return null;
|
|
891
|
+
}
|
|
892
|
+
const node = {
|
|
893
|
+
path: emitted.path,
|
|
894
|
+
kind: emitted.kind,
|
|
895
|
+
provider: emitted.provider,
|
|
896
|
+
bodyHash: VIRTUAL_NODE_PLACEHOLDER_HASH,
|
|
897
|
+
frontmatterHash: VIRTUAL_NODE_PLACEHOLDER_HASH,
|
|
898
|
+
bytes: { frontmatter: 0, body: 0, total: 0 },
|
|
899
|
+
linksOutCount: 0,
|
|
900
|
+
linksInCount: 0,
|
|
901
|
+
externalRefsCount: 0,
|
|
902
|
+
virtual: true,
|
|
903
|
+
derivedFrom: [...emitted.derivedFrom]
|
|
904
|
+
};
|
|
905
|
+
if (emitted.frontmatter) node.frontmatter = emitted.frontmatter;
|
|
906
|
+
return node;
|
|
907
|
+
}
|
|
828
908
|
function validateLink(extractor, link, emitter) {
|
|
829
909
|
const knownKinds = ["invokes", "references", "mentions", "supersedes"];
|
|
830
910
|
if (!knownKinds.includes(link.kind)) {
|
|
@@ -845,9 +925,68 @@ function validateLink(extractor, link, emitter) {
|
|
|
845
925
|
);
|
|
846
926
|
return null;
|
|
847
927
|
}
|
|
848
|
-
const
|
|
928
|
+
const c = link.confidence;
|
|
929
|
+
if (c !== void 0 && (typeof c !== "number" || !Number.isFinite(c) || c < 0 || c > 1)) {
|
|
930
|
+
const qualifiedId = `${extractor.pluginId}/${extractor.id}`;
|
|
931
|
+
emitter.emit(
|
|
932
|
+
makeEvent("extension.error", {
|
|
933
|
+
kind: "link-confidence-out-of-range",
|
|
934
|
+
extensionId: qualifiedId,
|
|
935
|
+
confidence: c,
|
|
936
|
+
message: `Extractor ${qualifiedId} emitted a Link with confidence ${String(c)} outside [0..1]; dropped.`
|
|
937
|
+
})
|
|
938
|
+
);
|
|
939
|
+
return null;
|
|
940
|
+
}
|
|
941
|
+
const confidence = c ?? ConfidenceTier.MEDIUM;
|
|
849
942
|
return { ...link, confidence };
|
|
850
943
|
}
|
|
944
|
+
var KNOWN_LINK_KINDS = ["invokes", "references", "mentions", "supersedes"];
|
|
945
|
+
function validateSignal(extractor, signal, emitter) {
|
|
946
|
+
const qualifiedId = qualifiedExtensionId(extractor.pluginId, extractor.id);
|
|
947
|
+
if (!Array.isArray(signal.candidates) || signal.candidates.length === 0) {
|
|
948
|
+
emitter.emit(
|
|
949
|
+
makeEvent("extension.error", {
|
|
950
|
+
kind: "signal-no-candidates",
|
|
951
|
+
extensionId: qualifiedId,
|
|
952
|
+
signal: { source: signal.source, scope: signal.scope },
|
|
953
|
+
message: `Extractor ${qualifiedId} emitted a Signal with no candidates; dropped.`
|
|
954
|
+
})
|
|
955
|
+
);
|
|
956
|
+
return null;
|
|
957
|
+
}
|
|
958
|
+
for (const candidate of signal.candidates) {
|
|
959
|
+
if (!isValidSignalCandidate(qualifiedId, candidate, emitter)) return null;
|
|
960
|
+
}
|
|
961
|
+
return signal;
|
|
962
|
+
}
|
|
963
|
+
function isValidSignalCandidate(qualifiedId, candidate, emitter) {
|
|
964
|
+
if (!KNOWN_LINK_KINDS.includes(candidate.kind)) {
|
|
965
|
+
emitter.emit(
|
|
966
|
+
makeEvent("extension.error", {
|
|
967
|
+
kind: "signal-candidate-kind-not-declared",
|
|
968
|
+
extensionId: qualifiedId,
|
|
969
|
+
candidateKind: candidate.kind,
|
|
970
|
+
declaredKinds: KNOWN_LINK_KINDS,
|
|
971
|
+
message: `Extractor ${qualifiedId} emitted a Signal candidate with off-enum kind '${String(candidate.kind)}'; dropped.`
|
|
972
|
+
})
|
|
973
|
+
);
|
|
974
|
+
return false;
|
|
975
|
+
}
|
|
976
|
+
const c = candidate.confidence;
|
|
977
|
+
if (typeof c !== "number" || !Number.isFinite(c) || c < 0 || c > 1) {
|
|
978
|
+
emitter.emit(
|
|
979
|
+
makeEvent("extension.error", {
|
|
980
|
+
kind: "signal-candidate-confidence-out-of-range",
|
|
981
|
+
extensionId: qualifiedId,
|
|
982
|
+
confidence: candidate.confidence,
|
|
983
|
+
message: `Extractor ${qualifiedId} emitted a Signal candidate with confidence ${String(c)} outside [0..1]; dropped.`
|
|
984
|
+
})
|
|
985
|
+
);
|
|
986
|
+
return false;
|
|
987
|
+
}
|
|
988
|
+
return true;
|
|
989
|
+
}
|
|
851
990
|
function dedupeLinks(links) {
|
|
852
991
|
const out = /* @__PURE__ */ new Map();
|
|
853
992
|
for (const link of links) {
|
|
@@ -894,7 +1033,9 @@ function recomputeExternalRefsCount(nodes, externalLinks, cachedPaths) {
|
|
|
894
1033
|
}
|
|
895
1034
|
}
|
|
896
1035
|
var EXTERNAL_URL_SCHEME_RE = /^[a-z][a-z0-9+\-.]+:/i;
|
|
1036
|
+
var VIRTUAL_NODE_SCHEME_RE = /^mcp:\/\//i;
|
|
897
1037
|
function isExternalUrlLink(link) {
|
|
1038
|
+
if (VIRTUAL_NODE_SCHEME_RE.test(link.target)) return false;
|
|
898
1039
|
return EXTERNAL_URL_SCHEME_RE.test(link.target);
|
|
899
1040
|
}
|
|
900
1041
|
|
|
@@ -1053,13 +1194,9 @@ function originatingNodeOf(link, priorNodePaths) {
|
|
|
1053
1194
|
}
|
|
1054
1195
|
function computeCacheDecision(opts) {
|
|
1055
1196
|
const applicableExtractors = opts.extractors.filter((ex) => {
|
|
1056
|
-
|
|
1057
|
-
if (!
|
|
1058
|
-
return
|
|
1059
|
-
const slashIdx = qualified.indexOf("/");
|
|
1060
|
-
const kindOnly = slashIdx === -1 ? qualified : qualified.slice(slashIdx + 1);
|
|
1061
|
-
return kindOnly === opts.kind;
|
|
1062
|
-
});
|
|
1197
|
+
if (!matchesKindPrecondition(ex, opts.kind)) return false;
|
|
1198
|
+
if (!matchesProviderPrecondition(ex, opts.provider)) return false;
|
|
1199
|
+
return true;
|
|
1063
1200
|
});
|
|
1064
1201
|
const applicableQualifiedIds = new Set(
|
|
1065
1202
|
applicableExtractors.map((ex) => qualifiedExtensionId(ex.pluginId, ex.id))
|
|
@@ -1073,6 +1210,20 @@ function computeCacheDecision(opts) {
|
|
|
1073
1210
|
fullCacheHit: opts.nodeHashCacheEligible && split.missingExtractors.length === 0
|
|
1074
1211
|
};
|
|
1075
1212
|
}
|
|
1213
|
+
function matchesKindPrecondition(ex, kind) {
|
|
1214
|
+
const kinds = ex.precondition?.kind;
|
|
1215
|
+
if (!kinds || kinds.length === 0) return true;
|
|
1216
|
+
return kinds.some((qualified) => {
|
|
1217
|
+
const slashIdx = qualified.indexOf("/");
|
|
1218
|
+
const kindOnly = slashIdx === -1 ? qualified : qualified.slice(slashIdx + 1);
|
|
1219
|
+
return kindOnly === kind;
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
function matchesProviderPrecondition(ex, provider) {
|
|
1223
|
+
const providers = ex.precondition?.provider;
|
|
1224
|
+
if (!providers || providers.length === 0) return true;
|
|
1225
|
+
return providers.includes(provider);
|
|
1226
|
+
}
|
|
1076
1227
|
function splitLegacy(applicableExtractors, applicableQualifiedIds, nodeHashCacheEligible) {
|
|
1077
1228
|
const cachedQualifiedIds = /* @__PURE__ */ new Set();
|
|
1078
1229
|
const missingExtractors = [];
|
|
@@ -1182,7 +1333,7 @@ function findHighConfidenceRenames(opts) {
|
|
|
1182
1333
|
if (opts.claimedNew.has(toPath)) continue;
|
|
1183
1334
|
const toNode = opts.currentByPath.get(toPath);
|
|
1184
1335
|
if (toNode.bodyHash === fromNode.bodyHash) {
|
|
1185
|
-
ops.push({ from: fromPath, to: toPath, confidence:
|
|
1336
|
+
ops.push({ from: fromPath, to: toPath, confidence: ConfidenceTier.HIGH });
|
|
1186
1337
|
opts.claimedDeleted.add(fromPath);
|
|
1187
1338
|
opts.claimedNew.add(toPath);
|
|
1188
1339
|
break;
|
|
@@ -1217,13 +1368,13 @@ function claimSingletonRenames(opts) {
|
|
|
1217
1368
|
const remaining = candidates.filter((p) => !opts.claimedDeleted.has(p));
|
|
1218
1369
|
if (remaining.length === 1) {
|
|
1219
1370
|
const fromPath = remaining[0];
|
|
1220
|
-
ops.push({ from: fromPath, to: toPath, confidence:
|
|
1371
|
+
ops.push({ from: fromPath, to: toPath, confidence: ConfidenceTier.MEDIUM });
|
|
1221
1372
|
opts.issues.push({
|
|
1222
1373
|
analyzerId: "auto-rename-medium",
|
|
1223
1374
|
severity: "warn",
|
|
1224
1375
|
nodeIds: [toPath],
|
|
1225
1376
|
message: `Auto-rename (medium confidence): ${fromPath} \u2192 ${toPath}`,
|
|
1226
|
-
data: { from: fromPath, to: toPath, confidence:
|
|
1377
|
+
data: { from: fromPath, to: toPath, confidence: ConfidenceTier.MEDIUM }
|
|
1227
1378
|
});
|
|
1228
1379
|
opts.claimedDeleted.add(fromPath);
|
|
1229
1380
|
opts.claimedNew.add(toPath);
|
|
@@ -1427,10 +1578,45 @@ var plainParser = {
|
|
|
1427
1578
|
}
|
|
1428
1579
|
};
|
|
1429
1580
|
|
|
1581
|
+
// plugins/core/parsers/toml/index.ts
|
|
1582
|
+
import { parse as parseToml } from "smol-toml";
|
|
1583
|
+
var tomlParser = {
|
|
1584
|
+
id: "toml",
|
|
1585
|
+
parse(raw, _path) {
|
|
1586
|
+
let parsed = {};
|
|
1587
|
+
const issues = [];
|
|
1588
|
+
try {
|
|
1589
|
+
const doc = parseToml(raw);
|
|
1590
|
+
if (doc && typeof doc === "object" && !Array.isArray(doc)) {
|
|
1591
|
+
parsed = stripPrototypePollution(doc);
|
|
1592
|
+
}
|
|
1593
|
+
} catch (err) {
|
|
1594
|
+
issues.push({
|
|
1595
|
+
code: "frontmatter-parse-error",
|
|
1596
|
+
message: sanitiseParseErrorMessage2(err)
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
const out = {
|
|
1600
|
+
frontmatterRaw: raw,
|
|
1601
|
+
frontmatter: parsed,
|
|
1602
|
+
body: ""
|
|
1603
|
+
};
|
|
1604
|
+
if (issues.length > 0) {
|
|
1605
|
+
return { ...out, issues };
|
|
1606
|
+
}
|
|
1607
|
+
return out;
|
|
1608
|
+
}
|
|
1609
|
+
};
|
|
1610
|
+
function sanitiseParseErrorMessage2(err) {
|
|
1611
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
1612
|
+
return raw.replace(/[-]+/g, " ").replace(/\s+/g, " ").trim();
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1430
1615
|
// kernel/scan/parsers/index.ts
|
|
1431
1616
|
var REGISTRY = /* @__PURE__ */ new Map([
|
|
1432
1617
|
[frontmatterYamlParser.id, frontmatterYamlParser],
|
|
1433
|
-
[plainParser.id, plainParser]
|
|
1618
|
+
[plainParser.id, plainParser],
|
|
1619
|
+
[tomlParser.id, tomlParser]
|
|
1434
1620
|
]);
|
|
1435
1621
|
var FROZEN_IDS = new Set(REGISTRY.keys());
|
|
1436
1622
|
function getParser(id) {
|
|
@@ -2043,6 +2229,7 @@ async function processRawNode(raw, provider, wctx, accum, claimedPaths, nextInde
|
|
|
2043
2229
|
const cacheDecision = computeCacheDecision({
|
|
2044
2230
|
extractors: wctx.opts.extractors,
|
|
2045
2231
|
kind,
|
|
2232
|
+
provider: provider.id,
|
|
2046
2233
|
nodePath: raw.path,
|
|
2047
2234
|
bodyHash,
|
|
2048
2235
|
sidecarAnnotationsHash,
|
|
@@ -2145,6 +2332,10 @@ function mergeExtractResult(extractResult, accum) {
|
|
|
2145
2332
|
accum.enrichmentBuffer.set(`${enr.nodePath}\0${enr.extractorId}`, enr);
|
|
2146
2333
|
}
|
|
2147
2334
|
for (const c of extractResult.contributions) accum.contributionsBuffer.push(c);
|
|
2335
|
+
for (const vn of extractResult.virtualNodes) {
|
|
2336
|
+
if (accum.nodes.some((n) => n.path === vn.path)) continue;
|
|
2337
|
+
accum.nodes.push(vn);
|
|
2338
|
+
}
|
|
2148
2339
|
}
|
|
2149
2340
|
function isPartialCacheHit(ctx) {
|
|
2150
2341
|
return ctx.nodeHashCacheEligible && ctx.cacheDecision.cachedQualifiedIds.size > 0 && ctx.priorNode !== void 0;
|