@skill-map/cli 0.20.1 → 0.22.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.md +93 -14
- package/dist/cli.js +7660 -6354
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1244 -1065
- package/dist/index.js.map +1 -1
- package/dist/kernel/index.d.ts +300 -194
- package/dist/kernel/index.js +1244 -1065
- package/dist/kernel/index.js.map +1 -1
- package/dist/migrations/001_initial.sql +13 -0
- package/dist/ui/chunk-25AWRVIC.js +965 -0
- package/dist/ui/chunk-GETTEQ3S.js +123 -0
- package/dist/ui/{chunk-4NLC7QD2.js → chunk-GXRWH2VL.js} +1 -1
- package/dist/ui/chunk-HC6PNQMW.js +251 -0
- package/dist/ui/chunk-HJHWJTFH.js +1 -0
- package/dist/ui/chunk-MF2M6GYF.js +1 -0
- package/dist/ui/{chunk-EZZF5RL5.js → chunk-MPMBTIUR.js} +2 -2
- package/dist/ui/{chunk-6GUHSAP5.js → chunk-OPPQMCMQ.js} +1 -1
- package/dist/ui/chunk-V3SZQETX.js +61 -0
- package/dist/ui/{chunk-E4ALROJS.js → chunk-VVOEPDQD.js} +1 -1
- package/dist/ui/{chunk-6BZZQV42.js → chunk-W2EFGI3J.js} +1 -1
- package/dist/ui/index.html +2 -10
- package/dist/ui/main-Q2WC254P.js +2 -0
- package/dist/ui/media/fa-brands-400-AHOAZHCU.woff2 +0 -0
- package/dist/ui/media/fa-regular-400-VRZYIBIZ.woff2 +0 -0
- package/dist/ui/media/fa-solid-900-MDEYK55F.woff2 +0 -0
- package/dist/ui/media/fa-v4compatibility-ETEVP6IB.woff2 +0 -0
- package/dist/ui/styles-M2FETVAG.css +1 -0
- package/migrations/001_initial.sql +13 -0
- package/package.json +6 -5
- package/dist/ui/chunk-FWX4RRDF.js +0 -125
- package/dist/ui/chunk-GGMXMGRJ.js +0 -1
- package/dist/ui/chunk-K5PULFK7.js +0 -1
- package/dist/ui/chunk-OJ6W6OIB.js +0 -61
- package/dist/ui/chunk-PTCD42GB.js +0 -247
- package/dist/ui/chunk-ZSRIBCAW.js +0 -965
- package/dist/ui/main-5FJWWH5I.js +0 -1
- package/dist/ui/styles-VJ5Q6D2X.css +0 -1
package/dist/kernel/index.d.ts
CHANGED
|
@@ -484,7 +484,7 @@ interface IRegisteredAnnotationKey {
|
|
|
484
484
|
* validates each pick at load time (`invalid-manifest` on miss); the
|
|
485
485
|
* slot fixes both the renderer and the payload shape.
|
|
486
486
|
*/
|
|
487
|
-
type TSlotName = 'card.title.right' | 'card.subtitle.left' | 'card.footer.left
|
|
487
|
+
type TSlotName = 'card.title.right' | 'card.subtitle.left' | 'card.footer.left' | 'card.footer.right' | 'graph.node.alert' | 'inspector.header.badge.counter' | 'inspector.header.badge.tag' | 'inspector.body.panel.breakdown' | 'inspector.body.panel.records' | 'inspector.body.panel.tree' | 'inspector.body.panel.key-values' | 'inspector.body.panel.link-list' | 'inspector.body.panel.markdown' | 'topbar.nav.start';
|
|
488
488
|
/**
|
|
489
489
|
* Closed enum of input-type names for plugin settings. Mirror of
|
|
490
490
|
* `spec/schemas/input-types.schema.json#/$defs/InputTypeName`.
|
|
@@ -759,55 +759,6 @@ interface IExtensionBase {
|
|
|
759
759
|
viewContributions?: Record<string, IViewContribution>;
|
|
760
760
|
}
|
|
761
761
|
|
|
762
|
-
/**
|
|
763
|
-
* `.skillmapignore` parser + filter facade. Wraps `ignore` (kaelzhang)
|
|
764
|
-
* with the project-local layering: bundled defaults → `config.ignore`
|
|
765
|
-
* (from `.skill-map/settings.json`) → `.skillmapignore` file content.
|
|
766
|
-
*
|
|
767
|
-
* Why a wrapper instead of exposing `ignore` directly:
|
|
768
|
-
*
|
|
769
|
-
* 1. Single-source defaults — `src/config/defaults/skillmapignore` is
|
|
770
|
-
* the canonical default list, loaded once at module init (or at
|
|
771
|
-
* explicit build time, depending on bundling). The runtime never
|
|
772
|
-
* re-reads it per scan.
|
|
773
|
-
* 2. Stable interface — Providers and the orchestrator depend on a
|
|
774
|
-
* minimal `IIgnoreFilter` shape, so the underlying library can be
|
|
775
|
-
* swapped without touching every consumer.
|
|
776
|
-
* 3. Path normalization — every consumer passes the path RELATIVE to
|
|
777
|
-
* the scan root (POSIX separators); the wrapper guarantees that
|
|
778
|
-
* contract before delegating to `ignore`.
|
|
779
|
-
*/
|
|
780
|
-
interface IIgnoreFilter {
|
|
781
|
-
/**
|
|
782
|
-
* Returns `true` when `relativePath` should be skipped. The caller
|
|
783
|
-
* MUST pass paths relative to the scan root, with POSIX separators
|
|
784
|
-
* (forward slashes), no leading `/`. Directories MAY be passed with
|
|
785
|
-
* or without trailing `/`; the wrapper does not require it.
|
|
786
|
-
*/
|
|
787
|
-
ignores(relativePath: string): boolean;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
/**
|
|
791
|
-
* `ProgressEmitterPort` — emits progress events during long operations.
|
|
792
|
-
*
|
|
793
|
-
* Shape-only today. The full event catalog (`run.started`,
|
|
794
|
-
* `job.claimed`, `model.delta`, etc.) is normative in
|
|
795
|
-
* `spec/job-events.md`; this port carries an open `data` payload so
|
|
796
|
-
* adapters can emit any documented event without type churn.
|
|
797
|
-
*/
|
|
798
|
-
interface ProgressEvent {
|
|
799
|
-
type: string;
|
|
800
|
-
timestamp: string;
|
|
801
|
-
runId?: string;
|
|
802
|
-
jobId?: string;
|
|
803
|
-
data?: unknown;
|
|
804
|
-
}
|
|
805
|
-
type TProgressListener = (event: ProgressEvent) => void;
|
|
806
|
-
interface ProgressEmitterPort {
|
|
807
|
-
emit(event: ProgressEvent): void;
|
|
808
|
-
subscribe(listener: TProgressListener): () => void;
|
|
809
|
-
}
|
|
810
|
-
|
|
811
762
|
/**
|
|
812
763
|
* Plugin-surface types, hand-written to mirror
|
|
813
764
|
* `spec/schemas/plugins-registry.schema.json#/$defs/PluginManifest` and the
|
|
@@ -913,9 +864,11 @@ interface IPluginManifest {
|
|
|
913
864
|
*
|
|
914
865
|
* - `incompatible-spec`: manifest parsed fine but `semver.satisfies` failed
|
|
915
866
|
* against the installed `@skill-map/spec` version.
|
|
916
|
-
* - `invalid-manifest`: `plugin.json` missing, unparseable,
|
|
917
|
-
*
|
|
918
|
-
*
|
|
867
|
+
* - `invalid-manifest`: `plugin.json` missing, unparseable, failing AJV on
|
|
868
|
+
* the base manifest schema, OR the exported extension shape failed its
|
|
869
|
+
* kind-specific schema (per spec/architecture.md §Plugin discovery —
|
|
870
|
+
* "AJV rejects unknown `slot` names with `invalid-manifest`").
|
|
871
|
+
* - `load-error`: manifest parsed but an extension module failed to import.
|
|
919
872
|
*/
|
|
920
873
|
/**
|
|
921
874
|
* Possible outcomes after the loader sees a plugin.json. Mirrors the
|
|
@@ -1191,6 +1144,95 @@ interface IPersistedContribution {
|
|
|
1191
1144
|
emittedAt: number;
|
|
1192
1145
|
}
|
|
1193
1146
|
|
|
1147
|
+
/**
|
|
1148
|
+
* `loadScanResult` — driving inverse of `persistScanResult`. Reads the
|
|
1149
|
+
* `scan_*` tables and reconstructs a `ScanResult` shape so the
|
|
1150
|
+
* orchestrator can run an incremental scan (`sm scan --changed`) on
|
|
1151
|
+
* top of a prior snapshot.
|
|
1152
|
+
*
|
|
1153
|
+
* The reconstruction is faithful for everything that was actually
|
|
1154
|
+
* persisted: nodes (with triple-split bytes / tokens, denormalised
|
|
1155
|
+
* counts, JSON frontmatter), internal links (with regrouped
|
|
1156
|
+
* `trigger` / `location`, parsed `sources[]`), and issues
|
|
1157
|
+
* (with parsed `nodeIds` / `linkIndices` / `fix` / `data`).
|
|
1158
|
+
*
|
|
1159
|
+
* **Documented omission**: external pseudo-links (those whose target is
|
|
1160
|
+
* an `http://` / `https://` URL emitted by the external-url-counter
|
|
1161
|
+
* extractor) are NEVER persisted to `scan_links` — only their per-node
|
|
1162
|
+
* count survives in `scan_nodes.external_refs_count`. Therefore the
|
|
1163
|
+
* `result.links` returned by `loadScanResult` contains only internal
|
|
1164
|
+
* graph links, and `node.externalRefsCount` is the authoritative count
|
|
1165
|
+
* carried over from the prior scan. The orchestrator's incremental path
|
|
1166
|
+
* preserves that count for "unchanged" nodes and re-derives it for
|
|
1167
|
+
* new / modified nodes from a fresh extractor pass.
|
|
1168
|
+
*
|
|
1169
|
+
* Meta envelope: the `scan_meta` table persists `scope` / `roots` /
|
|
1170
|
+
* `scannedAt` / `scannedBy` / `providers` / `stats.filesWalked` /
|
|
1171
|
+
* `stats.filesSkipped` / `stats.durationMs`. When the row exists,
|
|
1172
|
+
* those fields come back authoritatively. When it does not (DB
|
|
1173
|
+
* freshly migrated but never scanned, or a legacy DB never
|
|
1174
|
+
* re-persisted), the loader degrades to a synthetic envelope:
|
|
1175
|
+
*
|
|
1176
|
+
* - `scannedAt` ← max(`scan_nodes.scanned_at`); falls back to `Date.now()`
|
|
1177
|
+
* for empty snapshots so the field stays a positive integer.
|
|
1178
|
+
* - `scope` ← `'project'`.
|
|
1179
|
+
* - `roots` ← `['.']` to satisfy spec's `minItems: 1`. NOT
|
|
1180
|
+
* load-bearing: the orchestrator's incremental path only reads
|
|
1181
|
+
* `nodes` / `links` / `issues` from the prior; it never reuses the
|
|
1182
|
+
* prior `roots`.
|
|
1183
|
+
* - `providers` ← `[]`.
|
|
1184
|
+
* - `stats` ← zeros for `filesWalked` / `filesSkipped` /
|
|
1185
|
+
* `durationMs`; the three count fields derive from row counts.
|
|
1186
|
+
*
|
|
1187
|
+
* Both branches keep `nodesCount` / `linksCount` / `issuesCount` derived
|
|
1188
|
+
* from `COUNT(*)` of the loaded rows — never persisted, always recomputed.
|
|
1189
|
+
*/
|
|
1190
|
+
|
|
1191
|
+
/**
|
|
1192
|
+
* Spec § A.9 — load the fine-grained Extractor cache as a per-node map
|
|
1193
|
+
* from qualified extractor id (`<pluginId>/<id>`) to the run-time
|
|
1194
|
+
* hashes the extractor recorded on its last run. Empty map is the
|
|
1195
|
+
* default when the table is empty (fresh DB, never-scanned scope, or
|
|
1196
|
+
* every extractor has been uninstalled since the last scan).
|
|
1197
|
+
*
|
|
1198
|
+
* Returned shape: `Map<nodePath, Map<extractorId, IPriorExtractorRun>>`.
|
|
1199
|
+
* The inner value carries the body hash AND the sidecar-annotations
|
|
1200
|
+
* hash so the orchestrator can apply the widened cache key (both must
|
|
1201
|
+
* match for a cache hit).
|
|
1202
|
+
*/
|
|
1203
|
+
interface IPriorExtractorRun {
|
|
1204
|
+
bodyHash: string;
|
|
1205
|
+
sidecarAnnotationsHash: string;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
/**
|
|
1209
|
+
* `.skillmapignore` parser + filter facade. Wraps `ignore` (kaelzhang)
|
|
1210
|
+
* with the project-local layering: bundled defaults → `config.ignore`
|
|
1211
|
+
* (from `.skill-map/settings.json`) → `.skillmapignore` file content.
|
|
1212
|
+
*
|
|
1213
|
+
* Why a wrapper instead of exposing `ignore` directly:
|
|
1214
|
+
*
|
|
1215
|
+
* 1. Single-source defaults — `src/config/defaults/skillmapignore` is
|
|
1216
|
+
* the canonical default list, loaded once at module init (or at
|
|
1217
|
+
* explicit build time, depending on bundling). The runtime never
|
|
1218
|
+
* re-reads it per scan.
|
|
1219
|
+
* 2. Stable interface — Providers and the orchestrator depend on a
|
|
1220
|
+
* minimal `IIgnoreFilter` shape, so the underlying library can be
|
|
1221
|
+
* swapped without touching every consumer.
|
|
1222
|
+
* 3. Path normalization — every consumer passes the path RELATIVE to
|
|
1223
|
+
* the scan root (POSIX separators); the wrapper guarantees that
|
|
1224
|
+
* contract before delegating to `ignore`.
|
|
1225
|
+
*/
|
|
1226
|
+
interface IIgnoreFilter {
|
|
1227
|
+
/**
|
|
1228
|
+
* Returns `true` when `relativePath` should be skipped. The caller
|
|
1229
|
+
* MUST pass paths relative to the scan root, with POSIX separators
|
|
1230
|
+
* (forward slashes), no leading `/`. Directories MAY be passed with
|
|
1231
|
+
* or without trailing `/`; the wrapper does not require it.
|
|
1232
|
+
*/
|
|
1233
|
+
ignores(relativePath: string): boolean;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1194
1236
|
/**
|
|
1195
1237
|
* Provider runtime contract. Walks filesystem roots and emits raw node
|
|
1196
1238
|
* records; classification maps path conventions to a node kind.
|
|
@@ -1334,15 +1376,6 @@ type TProviderKindIcon = {
|
|
|
1334
1376
|
};
|
|
1335
1377
|
interface IProvider extends IExtensionBase {
|
|
1336
1378
|
kind: 'provider';
|
|
1337
|
-
/**
|
|
1338
|
-
* Filesystem directory (relative to user home or project root) where this
|
|
1339
|
-
* Provider's content lives. Required. Examples: `'~/.claude'` for the
|
|
1340
|
-
* Claude Provider, `'~/.cursor'` for a hypothetical Cursor Provider.
|
|
1341
|
-
* The kernel walks this directory during boot/scan to discover nodes;
|
|
1342
|
-
* `sm doctor` validates the directory exists and emits a non-blocking
|
|
1343
|
-
* warning when it does not.
|
|
1344
|
-
*/
|
|
1345
|
-
explorationDir: string;
|
|
1346
1379
|
/**
|
|
1347
1380
|
* Catalog of node kinds this Provider emits. Keyed by kind name. Every
|
|
1348
1381
|
* kind the Provider can `classify()` MUST have an entry; an entry is
|
|
@@ -2092,6 +2125,182 @@ interface IHook extends IExtensionBase {
|
|
|
2092
2125
|
on(ctx: IHookContext): void | Promise<void>;
|
|
2093
2126
|
}
|
|
2094
2127
|
|
|
2128
|
+
/**
|
|
2129
|
+
* `ProgressEmitterPort` — emits progress events during long operations.
|
|
2130
|
+
*
|
|
2131
|
+
* Shape-only today. The full event catalog (`run.started`,
|
|
2132
|
+
* `job.claimed`, `model.delta`, etc.) is normative in
|
|
2133
|
+
* `spec/job-events.md`; this port carries an open `data` payload so
|
|
2134
|
+
* adapters can emit any documented event without type churn.
|
|
2135
|
+
*/
|
|
2136
|
+
interface ProgressEvent {
|
|
2137
|
+
type: string;
|
|
2138
|
+
timestamp: string;
|
|
2139
|
+
runId?: string;
|
|
2140
|
+
jobId?: string;
|
|
2141
|
+
data?: unknown;
|
|
2142
|
+
}
|
|
2143
|
+
type TProgressListener = (event: ProgressEvent) => void;
|
|
2144
|
+
interface ProgressEmitterPort {
|
|
2145
|
+
emit(event: ProgressEvent): void;
|
|
2146
|
+
subscribe(listener: TProgressListener): () => void;
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
/**
|
|
2150
|
+
* Per-node extractor invocation: build a fresh `IExtractorContext` for
|
|
2151
|
+
* each extractor, validate every emitted link / contribution against
|
|
2152
|
+
* the declared catalog, fold enrichment partials into per-`(node,
|
|
2153
|
+
* extractor)` records, and surface emit-time drops as
|
|
2154
|
+
* `extension.error` events.
|
|
2155
|
+
*
|
|
2156
|
+
* Also hosts the post-walk recompute helpers that re-derive
|
|
2157
|
+
* `linksOutCount` / `linksInCount` / `externalRefsCount` on every node
|
|
2158
|
+
* from the final merged link buffer, plus the `IExtractorRunRecord`
|
|
2159
|
+
* and `IEnrichmentRecord` types those records eventually persist as.
|
|
2160
|
+
*/
|
|
2161
|
+
|
|
2162
|
+
/**
|
|
2163
|
+
* Spec § A.9 — runs to persist into `scan_extractor_runs`. One entry
|
|
2164
|
+
* per `(nodePath, qualifiedExtractorId)` pair the orchestrator decided
|
|
2165
|
+
* "this extractor is current for this body". Includes both freshly-run
|
|
2166
|
+
* pairs (extractor invoked this scan) and reused pairs (cached node, the
|
|
2167
|
+
* extractor's prior run still applies to the same body hash). Excludes
|
|
2168
|
+
* obsolete pairs — extractors that ran in the prior but are no longer
|
|
2169
|
+
* registered — so a replace-all persist drops them automatically.
|
|
2170
|
+
*/
|
|
2171
|
+
interface IExtractorRunRecord {
|
|
2172
|
+
nodePath: string;
|
|
2173
|
+
extractorId: string;
|
|
2174
|
+
bodyHashAtRun: string;
|
|
2175
|
+
ranAt: number;
|
|
2176
|
+
/**
|
|
2177
|
+
* sha256 of the canonical-form sidecar annotations the Extractor saw
|
|
2178
|
+
* at run time. Always populated (an absent sidecar canonicalises to
|
|
2179
|
+
* `{}` so the hash is stable). Used unconditionally by the cache
|
|
2180
|
+
* decision alongside `bodyHashAtRun`: a sidecar-only edit invalidates
|
|
2181
|
+
* the cached run for every applicable Extractor on that node.
|
|
2182
|
+
*/
|
|
2183
|
+
sidecarAnnotationsHashAtRun: string;
|
|
2184
|
+
}
|
|
2185
|
+
/**
|
|
2186
|
+
* Spec § A.8 — universal enrichment layer.
|
|
2187
|
+
*
|
|
2188
|
+
* One entry per `(nodePath, qualifiedExtractorId)` pair an Extractor
|
|
2189
|
+
* produced via `ctx.enrichNode(...)` during the walk. Attribution is
|
|
2190
|
+
* preserved per-Extractor (rather than merged client-side as B.1 did)
|
|
2191
|
+
* so the persistence layer can:
|
|
2192
|
+
*
|
|
2193
|
+
* - upsert a single row per pair (stable PRIMARY KEY conflict on
|
|
2194
|
+
* re-extract);
|
|
2195
|
+
* - feed `mergeNodeWithEnrichments` with `enrichedAt`-sorted partials
|
|
2196
|
+
* for last-write-wins per field at read time.
|
|
2197
|
+
*
|
|
2198
|
+
* `value` is the cumulative merge across every `enrichNode` call that
|
|
2199
|
+
* Extractor made for this node within this scan — multiple
|
|
2200
|
+
* `ctx.enrichNode({...})` calls inside one `extract(ctx)` invocation
|
|
2201
|
+
* fold into a single row, but two different Extractors hitting the
|
|
2202
|
+
* same node yield two distinct rows.
|
|
2203
|
+
*
|
|
2204
|
+
* `isProbabilistic` is reserved: Extractors are deterministic-only, so
|
|
2205
|
+
* every record produced by the orchestrator sets it to `false`. The
|
|
2206
|
+
* field is kept on the record (and the row in `node_enrichments`) so a
|
|
2207
|
+
* future Action-issued enrichment can populate it without reshaping
|
|
2208
|
+
* the persistence contract — see spec `architecture.md`
|
|
2209
|
+
* §Extractor · enrichment layer.
|
|
2210
|
+
*/
|
|
2211
|
+
interface IEnrichmentRecord {
|
|
2212
|
+
nodePath: string;
|
|
2213
|
+
extractorId: string;
|
|
2214
|
+
bodyHashAtEnrichment: string;
|
|
2215
|
+
value: Partial<Node>;
|
|
2216
|
+
enrichedAt: number;
|
|
2217
|
+
isProbabilistic: boolean;
|
|
2218
|
+
}
|
|
2219
|
+
/**
|
|
2220
|
+
* Run a set of extractors against a single node, collecting their link
|
|
2221
|
+
* emissions and node-enrichment partials. Each extractor is invoked
|
|
2222
|
+
* exactly once with a fresh `IExtractorContext`. Caller decides what
|
|
2223
|
+
* to do with the returned arrays (push into per-scan buffers, write to
|
|
2224
|
+
* a focused refresh result, etc.).
|
|
2225
|
+
*
|
|
2226
|
+
* Exported so `cli/commands/refresh.ts` can reuse the same wiring it
|
|
2227
|
+
* needs for re-running a single extractor against a single node — the
|
|
2228
|
+
* pre-extraction code in `refresh.ts` was hand-duplicating this loop
|
|
2229
|
+
* (audit item V4).
|
|
2230
|
+
*
|
|
2231
|
+
* Within this call, multiple `enrichNode(partial)` calls from the same
|
|
2232
|
+
* extractor against the same node fold into one record (last-write-wins
|
|
2233
|
+
* per field) — same contract as the in-scan path.
|
|
2234
|
+
*/
|
|
2235
|
+
declare function runExtractorsForNode(opts: {
|
|
2236
|
+
extractors: IExtractor[];
|
|
2237
|
+
node: Node;
|
|
2238
|
+
body: string;
|
|
2239
|
+
frontmatter: Record<string, unknown>;
|
|
2240
|
+
bodyHash: string;
|
|
2241
|
+
emitter: ProgressEmitterPort;
|
|
2242
|
+
/**
|
|
2243
|
+
* Spec § A.12 — per-plugin `ctx.store` wrappers keyed by `pluginId`.
|
|
2244
|
+
* The map's lookup is per-extractor inside the loop, so callers that
|
|
2245
|
+
* don't track plugin storage can omit it; the resulting `ctx.store`
|
|
2246
|
+
* stays `undefined` (the existing contract).
|
|
2247
|
+
*/
|
|
2248
|
+
pluginStores?: ReadonlyMap<string, IPluginStore>;
|
|
2249
|
+
}): Promise<{
|
|
2250
|
+
internalLinks: Link[];
|
|
2251
|
+
externalLinks: Link[];
|
|
2252
|
+
enrichments: IEnrichmentRecord[];
|
|
2253
|
+
contributions: IContributionRecord[];
|
|
2254
|
+
}>;
|
|
2255
|
+
|
|
2256
|
+
/**
|
|
2257
|
+
* Rename + orphan classification per `spec/db-schema.md` §Rename
|
|
2258
|
+
* detection. Pure: takes the prior `ScanResult` and the current node
|
|
2259
|
+
* set, mutates the supplied `issues` array in place, and returns the
|
|
2260
|
+
* `RenameOp[]` the persistence layer must apply inside the same tx as
|
|
2261
|
+
* the scan zone replace-all.
|
|
2262
|
+
*/
|
|
2263
|
+
|
|
2264
|
+
/**
|
|
2265
|
+
* Confidence-tagged plan to repoint `state_*` references from one node
|
|
2266
|
+
* path to another. Emitted by the rename heuristic during `runScan` and
|
|
2267
|
+
* consumed by `persistScanResult` so the FK migration runs inside the
|
|
2268
|
+
* same transaction as the scan zone replace-all.
|
|
2269
|
+
*/
|
|
2270
|
+
interface RenameOp {
|
|
2271
|
+
from: string;
|
|
2272
|
+
to: string;
|
|
2273
|
+
confidence: 'high' | 'medium';
|
|
2274
|
+
}
|
|
2275
|
+
/**
|
|
2276
|
+
* Pure rename / orphan classification per `spec/db-schema.md` §Rename
|
|
2277
|
+
* detection. Mutates `issues` in place — caller passes the in-progress
|
|
2278
|
+
* issue list; returns the `RenameOp[]` for the persistence layer to
|
|
2279
|
+
* apply inside its tx.
|
|
2280
|
+
*
|
|
2281
|
+
* Pipeline (1-to-1: a `newPath` claimed by one stage cannot be reused
|
|
2282
|
+
* by another):
|
|
2283
|
+
*
|
|
2284
|
+
* 1. **High-confidence**: pair each `deletedPath` with a `newPath`
|
|
2285
|
+
* that has the same `bodyHash`. No issue, no prompt.
|
|
2286
|
+
* 2. **Medium-confidence (1:1)**: of the remaining deletions, pair
|
|
2287
|
+
* each with the *unique* unclaimed `newPath` that shares its
|
|
2288
|
+
* `frontmatterHash`. Emits `auto-rename-medium` (severity warn)
|
|
2289
|
+
* with `data: { from, to, confidence: 'medium' }`.
|
|
2290
|
+
* 3. **Ambiguous (N:1)**: when a single `newPath` has more than one
|
|
2291
|
+
* remaining frontmatter-matching candidate, emit ONE
|
|
2292
|
+
* `auto-rename-ambiguous` issue per `newPath`, listing all
|
|
2293
|
+
* candidates in `data.candidates`. NO migration.
|
|
2294
|
+
* 4. **Orphan**: every `deletedPath` left after steps 1-3 yields one
|
|
2295
|
+
* `orphan` issue (severity info) with `data: { path: <deletedPath> }`.
|
|
2296
|
+
*
|
|
2297
|
+
* Determinism: `deletedPaths` and `newPaths` are iterated in lex-asc
|
|
2298
|
+
* order so the same input always produces the same matches —
|
|
2299
|
+
* required for reproducible tests and conformance fixtures (the spec
|
|
2300
|
+
* does not prescribe an order, but stability is the obvious contract).
|
|
2301
|
+
*/
|
|
2302
|
+
declare function detectRenamesAndOrphans(prior: ScanResult, current: Node[], issues: Issue[]): RenameOp[];
|
|
2303
|
+
|
|
2095
2304
|
/**
|
|
2096
2305
|
* Scan orchestrator — runs the Provider → extractor → analyzer pipeline across
|
|
2097
2306
|
* every registered extension and emits `ProgressEmitterPort` events in
|
|
@@ -2158,17 +2367,6 @@ interface IScanExtensions {
|
|
|
2158
2367
|
*/
|
|
2159
2368
|
hooks?: IHook[];
|
|
2160
2369
|
}
|
|
2161
|
-
/**
|
|
2162
|
-
* Confidence-tagged plan to repoint `state_*` references from one node
|
|
2163
|
-
* path to another. Emitted by the rename heuristic during `runScan` and
|
|
2164
|
-
* consumed by `persistScanResult` so the FK migration runs inside the
|
|
2165
|
-
* same transaction as the scan zone replace-all.
|
|
2166
|
-
*/
|
|
2167
|
-
interface RenameOp {
|
|
2168
|
-
from: string;
|
|
2169
|
-
to: string;
|
|
2170
|
-
confidence: 'high' | 'medium';
|
|
2171
|
-
}
|
|
2172
2370
|
interface RunScanOptions {
|
|
2173
2371
|
/**
|
|
2174
2372
|
* Filesystem roots to walk. Spec requires `minItems: 1`; passing an
|
|
@@ -2266,7 +2464,7 @@ interface RunScanOptions {
|
|
|
2266
2464
|
strict?: boolean;
|
|
2267
2465
|
/**
|
|
2268
2466
|
* Spec § A.9 — fine-grained Extractor cache breadcrumbs from the
|
|
2269
|
-
* prior scan. Shape: `Map<nodePath, Map<qualifiedExtractorId,
|
|
2467
|
+
* prior scan. Shape: `Map<nodePath, Map<qualifiedExtractorId, IPriorExtractorRun>>`.
|
|
2270
2468
|
* Loaded from the `scan_extractor_runs` table by the CLI before
|
|
2271
2469
|
* invoking `runScan`; absent / empty for a fresh DB or an out-of-band
|
|
2272
2470
|
* caller that does not maintain a cache. Decoupled from `priorSnapshot`
|
|
@@ -2278,11 +2476,12 @@ interface RunScanOptions {
|
|
|
2278
2476
|
* registered extractor that applies to this kind has a matching
|
|
2279
2477
|
* row → full skip, all prior outbound links reused.
|
|
2280
2478
|
* - some applicable extractor lacks a matching row (newly registered,
|
|
2281
|
-
* or its prior run targeted a different body hash
|
|
2282
|
-
*
|
|
2283
|
-
*
|
|
2479
|
+
* or its prior run targeted a different body hash or sidecar
|
|
2480
|
+
* annotations hash) → run only the missing extractors, drop prior
|
|
2481
|
+
* links whose `sources` map to any missing extractor or to an
|
|
2482
|
+
* extractor that is no longer registered.
|
|
2284
2483
|
*/
|
|
2285
|
-
priorExtractorRuns?: Map<string, Map<string,
|
|
2484
|
+
priorExtractorRuns?: Map<string, Map<string, IPriorExtractorRun>>;
|
|
2286
2485
|
/**
|
|
2287
2486
|
* Spec § A.12 — per-plugin storage wrappers exposed to extractors via
|
|
2288
2487
|
* `ctx.store`. Keyed by `pluginId`; absent / missing entry leaves
|
|
@@ -2331,55 +2530,6 @@ interface RunScanOptions {
|
|
|
2331
2530
|
*/
|
|
2332
2531
|
cwd?: string;
|
|
2333
2532
|
}
|
|
2334
|
-
/**
|
|
2335
|
-
* Spec § A.9 — runs to persist into `scan_extractor_runs`. One entry
|
|
2336
|
-
* per `(nodePath, qualifiedExtractorId)` pair the orchestrator decided
|
|
2337
|
-
* "this extractor is current for this body". Includes both freshly-run
|
|
2338
|
-
* pairs (extractor invoked this scan) and reused pairs (cached node, the
|
|
2339
|
-
* extractor's prior run still applies to the same body hash). Excludes
|
|
2340
|
-
* obsolete pairs — extractors that ran in the prior but are no longer
|
|
2341
|
-
* registered — so a replace-all persist drops them automatically.
|
|
2342
|
-
*/
|
|
2343
|
-
interface IExtractorRunRecord {
|
|
2344
|
-
nodePath: string;
|
|
2345
|
-
extractorId: string;
|
|
2346
|
-
bodyHashAtRun: string;
|
|
2347
|
-
ranAt: number;
|
|
2348
|
-
}
|
|
2349
|
-
/**
|
|
2350
|
-
* Spec § A.8 — universal enrichment layer.
|
|
2351
|
-
*
|
|
2352
|
-
* One entry per `(nodePath, qualifiedExtractorId)` pair an Extractor
|
|
2353
|
-
* produced via `ctx.enrichNode(...)` during the walk. Attribution is
|
|
2354
|
-
* preserved per-Extractor (rather than merged client-side as B.1 did)
|
|
2355
|
-
* so the persistence layer can:
|
|
2356
|
-
*
|
|
2357
|
-
* - upsert a single row per pair (stable PRIMARY KEY conflict on
|
|
2358
|
-
* re-extract);
|
|
2359
|
-
* - feed `mergeNodeWithEnrichments` with `enrichedAt`-sorted partials
|
|
2360
|
-
* for last-write-wins per field at read time.
|
|
2361
|
-
*
|
|
2362
|
-
* `value` is the cumulative merge across every `enrichNode` call that
|
|
2363
|
-
* Extractor made for this node within this scan — multiple
|
|
2364
|
-
* `ctx.enrichNode({...})` calls inside one `extract(ctx)` invocation
|
|
2365
|
-
* fold into a single row, but two different Extractors hitting the
|
|
2366
|
-
* same node yield two distinct rows.
|
|
2367
|
-
*
|
|
2368
|
-
* `isProbabilistic` is reserved: Extractors are deterministic-only, so
|
|
2369
|
-
* every record produced by the orchestrator sets it to `false`. The
|
|
2370
|
-
* field is kept on the record (and the row in `node_enrichments`) so a
|
|
2371
|
-
* future Action-issued enrichment can populate it without reshaping
|
|
2372
|
-
* the persistence contract — see spec `architecture.md`
|
|
2373
|
-
* §Extractor · enrichment layer.
|
|
2374
|
-
*/
|
|
2375
|
-
interface IEnrichmentRecord {
|
|
2376
|
-
nodePath: string;
|
|
2377
|
-
extractorId: string;
|
|
2378
|
-
bodyHashAtEnrichment: string;
|
|
2379
|
-
value: Partial<Node>;
|
|
2380
|
-
enrichedAt: number;
|
|
2381
|
-
isProbabilistic: boolean;
|
|
2382
|
-
}
|
|
2383
2533
|
/**
|
|
2384
2534
|
* Same as `runScan` but also returns the rename heuristic's `RenameOp[]`
|
|
2385
2535
|
* — the high- and medium-confidence renames the persistence layer must
|
|
@@ -2402,70 +2552,16 @@ declare function runScanWithRenames(_kernel: Kernel, options: RunScanOptions): P
|
|
|
2402
2552
|
freshlyRunTuples: ReadonlySet<string>;
|
|
2403
2553
|
}>;
|
|
2404
2554
|
declare function runScan(_kernel: Kernel, options: RunScanOptions): Promise<ScanResult>;
|
|
2555
|
+
|
|
2405
2556
|
/**
|
|
2406
|
-
*
|
|
2407
|
-
*
|
|
2408
|
-
*
|
|
2409
|
-
*
|
|
2410
|
-
*
|
|
2411
|
-
*
|
|
2412
|
-
* Exported so `cli/commands/refresh.ts` can reuse the same wiring it
|
|
2413
|
-
* needs for re-running a single extractor against a single node — the
|
|
2414
|
-
* pre-extraction code in `refresh.ts` was hand-duplicating this loop
|
|
2415
|
-
* (audit item V4).
|
|
2416
|
-
*
|
|
2417
|
-
* Within this call, multiple `enrichNode(partial)` calls from the same
|
|
2418
|
-
* extractor against the same node fold into one record (last-write-wins
|
|
2419
|
-
* per field) — same contract as the in-scan path.
|
|
2420
|
-
*/
|
|
2421
|
-
declare function runExtractorsForNode(opts: {
|
|
2422
|
-
extractors: IExtractor[];
|
|
2423
|
-
node: Node;
|
|
2424
|
-
body: string;
|
|
2425
|
-
frontmatter: Record<string, unknown>;
|
|
2426
|
-
bodyHash: string;
|
|
2427
|
-
emitter: ProgressEmitterPort;
|
|
2428
|
-
/**
|
|
2429
|
-
* Spec § A.12 — per-plugin `ctx.store` wrappers keyed by `pluginId`.
|
|
2430
|
-
* The map's lookup is per-extractor inside the loop, so callers that
|
|
2431
|
-
* don't track plugin storage can omit it; the resulting `ctx.store`
|
|
2432
|
-
* stays `undefined` (the existing contract).
|
|
2433
|
-
*/
|
|
2434
|
-
pluginStores?: ReadonlyMap<string, IPluginStore>;
|
|
2435
|
-
}): Promise<{
|
|
2436
|
-
internalLinks: Link[];
|
|
2437
|
-
externalLinks: Link[];
|
|
2438
|
-
enrichments: IEnrichmentRecord[];
|
|
2439
|
-
contributions: IContributionRecord[];
|
|
2440
|
-
}>;
|
|
2441
|
-
/**
|
|
2442
|
-
* Pure rename / orphan classification per `spec/db-schema.md` §Rename
|
|
2443
|
-
* detection. Mutates `issues` in place — caller passes the in-progress
|
|
2444
|
-
* issue list; returns the `RenameOp[]` for the persistence layer to
|
|
2445
|
-
* apply inside its tx.
|
|
2446
|
-
*
|
|
2447
|
-
* Pipeline (1-to-1: a `newPath` claimed by one stage cannot be reused
|
|
2448
|
-
* by another):
|
|
2449
|
-
*
|
|
2450
|
-
* 1. **High-confidence**: pair each `deletedPath` with a `newPath`
|
|
2451
|
-
* that has the same `bodyHash`. No issue, no prompt.
|
|
2452
|
-
* 2. **Medium-confidence (1:1)**: of the remaining deletions, pair
|
|
2453
|
-
* each with the *unique* unclaimed `newPath` that shares its
|
|
2454
|
-
* `frontmatterHash`. Emits `auto-rename-medium` (severity warn)
|
|
2455
|
-
* with `data: { from, to, confidence: 'medium' }`.
|
|
2456
|
-
* 3. **Ambiguous (N:1)**: when a single `newPath` has more than one
|
|
2457
|
-
* remaining frontmatter-matching candidate, emit ONE
|
|
2458
|
-
* `auto-rename-ambiguous` issue per `newPath`, listing all
|
|
2459
|
-
* candidates in `data.candidates`. NO migration.
|
|
2460
|
-
* 4. **Orphan**: every `deletedPath` left after steps 1-3 yields one
|
|
2461
|
-
* `orphan` issue (severity info) with `data: { path: <deletedPath> }`.
|
|
2462
|
-
*
|
|
2463
|
-
* Determinism: `deletedPaths` and `newPaths` are iterated in lex-asc
|
|
2464
|
-
* order so the same input always produces the same matches —
|
|
2465
|
-
* required for reproducible tests and conformance fixtures (the spec
|
|
2466
|
-
* does not prescribe an order, but stability is the obvious contract).
|
|
2557
|
+
* Node-construction helpers: hash a body, canonicalise frontmatter /
|
|
2558
|
+
* sidecar annotations, resolve the sidecar overlay for a given relative
|
|
2559
|
+
* path, and produce a fresh `Node` (validating its frontmatter on the
|
|
2560
|
+
* way out). Also hosts `mergeNodeWithEnrichments` + `IPersistedEnrichment`
|
|
2561
|
+
* — the read-time merge of author frontmatter with the A.8 enrichment
|
|
2562
|
+
* layer.
|
|
2467
2563
|
*/
|
|
2468
|
-
|
|
2564
|
+
|
|
2469
2565
|
/**
|
|
2470
2566
|
* Spec § A.8 — produce the merged read-time view of a Node.
|
|
2471
2567
|
*
|
|
@@ -3170,9 +3266,11 @@ interface StoragePort {
|
|
|
3170
3266
|
load(): Promise<ScanResult>;
|
|
3171
3267
|
/**
|
|
3172
3268
|
* Spec § A.9 — fine-grained extractor-runs cache breadcrumbs.
|
|
3173
|
-
* Returns `Map<nodePath, Map<qualifiedExtractorId,
|
|
3269
|
+
* Returns `Map<nodePath, Map<qualifiedExtractorId, IPriorExtractorRun>>`.
|
|
3270
|
+
* Inner value carries `bodyHash` AND `sidecarAnnotationsHash`; both
|
|
3271
|
+
* participate in the cache hit condition for every Extractor.
|
|
3174
3272
|
*/
|
|
3175
|
-
loadExtractorRuns(): Promise<Map<string, Map<string,
|
|
3273
|
+
loadExtractorRuns(): Promise<Map<string, Map<string, IPriorExtractorRun>>>;
|
|
3176
3274
|
/** Universal enrichment layer — every persisted `(node, extractor)` pair. */
|
|
3177
3275
|
loadNodeEnrichments(): Promise<IPersistedEnrichment[]>;
|
|
3178
3276
|
/**
|
|
@@ -3190,9 +3288,10 @@ interface StoragePort {
|
|
|
3190
3288
|
};
|
|
3191
3289
|
/**
|
|
3192
3290
|
* Phase 3 / View contribution system — read access to
|
|
3193
|
-
* `scan_contributions
|
|
3194
|
-
* `
|
|
3195
|
-
*
|
|
3291
|
+
* `scan_contributions`, plus the targeted purge used by
|
|
3292
|
+
* `sm plugins disable` to clear stale rows immediately at toggle time.
|
|
3293
|
+
* Bulk writes still happen exclusively via
|
|
3294
|
+
* `scans.persist({ contributions })` (replace-all semantics).
|
|
3196
3295
|
*/
|
|
3197
3296
|
contributions: {
|
|
3198
3297
|
/** Every contribution row for a single node. Stable order. */
|
|
@@ -3208,6 +3307,13 @@ interface StoragePort {
|
|
|
3208
3307
|
* `GET /api/contributions/:pluginId/:contributionId?path=...`.
|
|
3209
3308
|
*/
|
|
3210
3309
|
lookup(pluginId: string, contributionId: string, nodePath: string, extensionId?: string): Promise<IPersistedContribution[]>;
|
|
3310
|
+
/**
|
|
3311
|
+
* Drop rows for a plugin (optionally narrowed to a single
|
|
3312
|
+
* extension within the bundle). Returns the number of deleted
|
|
3313
|
+
* rows. Called by `sm plugins disable` so the UI stops rendering
|
|
3314
|
+
* the disabled plugin's chips before the next scan.
|
|
3315
|
+
*/
|
|
3316
|
+
purgeByPlugin(pluginId: string, extensionId?: string): Promise<number>;
|
|
3211
3317
|
};
|
|
3212
3318
|
/**
|
|
3213
3319
|
* Read-only access to `scan_node_tags`. Writes happen exclusively
|