@skill-map/cli 0.21.0 → 0.23.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/README.md +4 -4
- package/bin/sm.js +3 -3
- package/dist/cli/tutorial/sm-tutorial.md +22 -4
- package/dist/cli.js +7958 -6881
- package/dist/cli.js.map +1 -1
- package/dist/conformance/index.d.ts +1 -1
- package/dist/conformance/index.js.map +1 -1
- package/dist/index.js +1612 -1400
- package/dist/index.js.map +1 -1
- package/dist/kernel/index.d.ts +583 -421
- package/dist/kernel/index.js +1612 -1400
- package/dist/kernel/index.js.map +1 -1
- package/dist/ui/{chunk-GXRWH2VL.js → chunk-2TPMJJYQ.js} +1 -1
- package/dist/ui/chunk-4BVLXZO3.js +61 -0
- package/dist/ui/{chunk-MPMBTIUR.js → chunk-BMAKIDAV.js} +30 -30
- package/dist/ui/{chunk-VVOEPDQD.js → chunk-GJJZ5QH6.js} +1 -1
- package/dist/ui/chunk-I7EELB7M.js +1 -0
- package/dist/ui/chunk-K47RR2IO.js +251 -0
- package/dist/ui/{chunk-OPPQMCMQ.js → chunk-KRNW54CI.js} +1 -1
- package/dist/ui/chunk-NJ4PSNK3.js +965 -0
- package/dist/ui/{chunk-W2EFGI3J.js → chunk-OU26UMVW.js} +1 -1
- package/dist/ui/chunk-QGRY6MDS.js +123 -0
- package/dist/ui/chunk-SCSYN7U2.js +1 -0
- package/dist/ui/index.html +3 -3
- package/dist/ui/main-CVCJMGY5.js +2 -0
- package/dist/ui/{styles-M2FETVAG.css → styles-ALBMEXCF.css} +1 -1
- package/package.json +8 -7
- package/dist/ui/chunk-25AWRVIC.js +0 -965
- package/dist/ui/chunk-6FTVUS57.js +0 -123
- package/dist/ui/chunk-MF2M6GYF.js +0 -1
- package/dist/ui/chunk-N366HMME.js +0 -1
- package/dist/ui/chunk-V3SZQETX.js +0 -61
- package/dist/ui/chunk-W62WVNU4.js +0 -251
- package/dist/ui/main-NIYE2VFS.js +0 -2
package/dist/kernel/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Domain types
|
|
2
|
+
* Domain types, byte-aligned with `spec/schemas/{node,link,issue,scan-result}.schema.json`.
|
|
3
3
|
*
|
|
4
4
|
* The kernel is the reference consumer of the spec; these types are therefore
|
|
5
5
|
* derived from the schemas, not invented. When a schema changes, this file
|
|
@@ -11,27 +11,27 @@
|
|
|
11
11
|
* Five categories with distinct prefix rules; the rules are deliberate
|
|
12
12
|
* even though they look mixed at first read:
|
|
13
13
|
*
|
|
14
|
-
* 1. **Domain types
|
|
14
|
+
* 1. **Domain types**, every shape that mirrors a `spec/schemas/*.json`
|
|
15
15
|
* file: `Node`, `Link`, `Issue`, `ScanResult`, `ScanStats`,
|
|
16
16
|
* `ExecutionRecord`, `HistoryStats`, …. **No prefix.** Names track
|
|
17
17
|
* the spec verbatim because the spec is the source of truth.
|
|
18
18
|
* Renaming any of these is a spec change.
|
|
19
19
|
*
|
|
20
|
-
* 2. **Hexagonal ports
|
|
20
|
+
* 2. **Hexagonal ports**, the abstract boundaries the kernel calls
|
|
21
21
|
* out to (`StoragePort`, `RunnerPort`, `ProgressEmitterPort`,
|
|
22
22
|
* `FilesystemPort`, `PluginLoaderPort`). **`Port` suffix.** The
|
|
23
23
|
* suffix calls out the architectural role and avoids name clashes
|
|
24
24
|
* with the concrete adapter classes (`SqliteStorageAdapter`
|
|
25
25
|
* implements `StoragePort`).
|
|
26
26
|
*
|
|
27
|
-
* 3. **Runtime extension contracts
|
|
27
|
+
* 3. **Runtime extension contracts**, what a plugin author
|
|
28
28
|
* implements: `IProvider`, `IExtractor`, `IAnalyzer`, `IFormatter`,
|
|
29
29
|
* `IExtensionBase`. **`I` prefix.** The prefix flags "this is a
|
|
30
|
-
* contract you supply, not a value the kernel hands you"
|
|
30
|
+
* contract you supply, not a value the kernel hands you", same
|
|
31
31
|
* reading as the rest of TypeScript's plugin ecosystems where a
|
|
32
32
|
* shape is implementable.
|
|
33
33
|
*
|
|
34
|
-
* 4. **Internal interfaces
|
|
34
|
+
* 4. **Internal interfaces**, option bags, result records, config
|
|
35
35
|
* slices, anything declared as `interface` and passed across
|
|
36
36
|
* function boundaries inside the kernel / CLI but not part of the
|
|
37
37
|
* spec: `IPluginRuntimeBundle`, `IPruneResult`, `IMigrationFile`,
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
* category 3 because both are "shapes that live in TypeScript
|
|
40
40
|
* only, never in JSON".
|
|
41
41
|
*
|
|
42
|
-
* 5. **Internal type aliases
|
|
42
|
+
* 5. **Internal type aliases**, anything declared as `type` (string-
|
|
43
43
|
* literal unions, function types, mapped/derived types) that lives
|
|
44
44
|
* only in TS: `TLogLevel`, `TLogMethodLevel`, `TProgressListener`,
|
|
45
45
|
* `TLogFormatter`, `TActionWrite`, `TExecutionMode`, `TGranularity`,
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
* for `interface`, `T` prefix for `type` aliases.
|
|
68
68
|
*/
|
|
69
69
|
/**
|
|
70
|
-
* The four node kinds the **built-in Claude Provider** declares
|
|
70
|
+
* The four node kinds the **built-in Claude Provider** declares, `skill`,
|
|
71
71
|
* `agent`, `command`, `note`. **NOT** the kernel-wide kind type.
|
|
72
72
|
*
|
|
73
73
|
* `Node.kind` is `string`. An external Provider (Cursor, Obsidian, …)
|
|
@@ -78,13 +78,13 @@
|
|
|
78
78
|
* (matches `IProvider.kinds` "open by design" docstring).
|
|
79
79
|
*
|
|
80
80
|
* Step 9.5 dropped `hook` from the catalog: `.claude/hooks/*.md` is NOT
|
|
81
|
-
* an Anthropic-defined node type
|
|
81
|
+
* an Anthropic-defined node type, hooks live in `settings.json` or as
|
|
82
82
|
* sub-objects of agent / skill frontmatter (see
|
|
83
83
|
* https://code.claude.com/docs/en/hooks.md). Files at the old path
|
|
84
84
|
* classify as `markdown` via the Provider's fallback. The fallback is
|
|
85
85
|
* named after the *format* because the file is generic markdown with
|
|
86
86
|
* no specific role; format-named kinds apply only as the generic
|
|
87
|
-
* fallback
|
|
87
|
+
* fallback, a file that matches a specific role (agent / command /
|
|
88
88
|
* skill) classifies under that role, not under `markdown`.
|
|
89
89
|
*
|
|
90
90
|
* This alias survives because:
|
|
@@ -108,9 +108,9 @@ type Stability = 'experimental' | 'stable' | 'deprecated';
|
|
|
108
108
|
* Execution mode of an analytical extension. Mirrors the per-kind capability
|
|
109
109
|
* matrix in `spec/architecture.md` §Execution modes:
|
|
110
110
|
*
|
|
111
|
-
* - `deterministic
|
|
111
|
+
* - `deterministic`, pure code, runs synchronously inside `sm scan` /
|
|
112
112
|
* `sm check`. Same input → same output, every run.
|
|
113
|
-
* - `probabilistic
|
|
113
|
+
* - `probabilistic`, calls an LLM through `RunnerPort`, dispatches only
|
|
114
114
|
* as a queued job (`sm job submit <kind>:<id>`); never participates in
|
|
115
115
|
* scan-time pipelines.
|
|
116
116
|
*
|
|
@@ -154,7 +154,7 @@ interface Node {
|
|
|
154
154
|
frontmatter?: Record<string, unknown>;
|
|
155
155
|
tokens?: TripleSplit;
|
|
156
156
|
/**
|
|
157
|
-
* Step 9.6.2
|
|
157
|
+
* Step 9.6.2, sidecar denormalisation surface. Populated by the
|
|
158
158
|
* orchestrator at scan time; absent when the orchestrator did not
|
|
159
159
|
* inspect sidecars (legacy code paths) or when no sidecar accompanies
|
|
160
160
|
* the node. Read by `annotation-stale` rule and the persistence layer.
|
|
@@ -165,7 +165,7 @@ interface Node {
|
|
|
165
165
|
* `/api/nodes/:pathB64` responses via in-memory `Set` lookup against
|
|
166
166
|
* `state_node_favorites`. Absent on emissions that don't carry per-user
|
|
167
167
|
* state (e.g. `sm export --json`); consumers that don't recognise the
|
|
168
|
-
* field MUST treat the absence as "unknown" rather than "false"
|
|
168
|
+
* field MUST treat the absence as "unknown" rather than "false", a
|
|
169
169
|
* truthy `isFavorite` only ever lands when the BFF set it.
|
|
170
170
|
*/
|
|
171
171
|
isFavorite?: boolean;
|
|
@@ -187,25 +187,25 @@ interface ISidecarOverlay {
|
|
|
187
187
|
present: boolean;
|
|
188
188
|
status?: SidecarStatus | null;
|
|
189
189
|
/**
|
|
190
|
-
* Parsed `annotations:` block. Untyped object
|
|
190
|
+
* Parsed `annotations:` block. Untyped object, schema lives in
|
|
191
191
|
* `spec/schemas/annotations.schema.json`. Null when no sidecar or
|
|
192
192
|
* the block is empty/absent.
|
|
193
193
|
*/
|
|
194
194
|
annotations?: Record<string, unknown> | null;
|
|
195
195
|
/**
|
|
196
|
-
* R15 closure (2026-05-07)
|
|
196
|
+
* R15 closure (2026-05-07), full parsed YAML root of the sidecar
|
|
197
197
|
* (the entire `.sm` payload, mirroring `sidecar.schema.json`). Surfaced
|
|
198
198
|
* so the UI inspector can render `for:`, `audit:`, `settings:`, and
|
|
199
199
|
* `<plugin-id>:` namespace blocks without re-reading the file. NULL
|
|
200
200
|
* when no sidecar is present, or when the sidecar exists but failed
|
|
201
|
-
* to parse / validate. The `annotations` field above stays
|
|
201
|
+
* to parse / validate. The `annotations` field above stays, it
|
|
202
202
|
* duplicates `root.annotations` intentionally so existing consumers
|
|
203
203
|
* keep working unchanged.
|
|
204
204
|
*/
|
|
205
205
|
root?: Record<string, unknown> | null;
|
|
206
206
|
}
|
|
207
207
|
interface Link {
|
|
208
|
-
/** The originating node
|
|
208
|
+
/** The originating node, the path of the file the extractor was reading
|
|
209
209
|
* when it emitted this link. Singular, NOT to be confused with
|
|
210
210
|
* `sources` (plural) below. */
|
|
211
211
|
source: string;
|
|
@@ -353,7 +353,7 @@ interface ScanResult {
|
|
|
353
353
|
scope: 'project' | 'global';
|
|
354
354
|
/**
|
|
355
355
|
* Filesystem roots that were walked during this scan. Spec requires
|
|
356
|
-
* `minItems: 1
|
|
356
|
+
* `minItems: 1`, `runScan` throws if `roots: []` is supplied.
|
|
357
357
|
*/
|
|
358
358
|
roots: string[];
|
|
359
359
|
/** Provider ids that participated in classification. Empty if no Provider matched. */
|
|
@@ -367,14 +367,14 @@ interface ScanResult {
|
|
|
367
367
|
}
|
|
368
368
|
|
|
369
369
|
/**
|
|
370
|
-
* Extension registry
|
|
370
|
+
* Extension registry, six kinds, first-class, loaded through a single API.
|
|
371
371
|
*
|
|
372
372
|
* The `Extension` shape is aligned with `spec/schemas/extensions/base.schema.json`.
|
|
373
373
|
* Kind-specific manifests (provider / extractor / analyzer / action / formatter /
|
|
374
374
|
* hook) extend this base structurally; the registry stores the base view
|
|
375
375
|
* and each kind's code carries its own fuller type where needed.
|
|
376
376
|
*
|
|
377
|
-
* **Spec § A.6
|
|
377
|
+
* **Spec § A.6, qualified ids.** Every extension is keyed in the registry
|
|
378
378
|
* by `<pluginId>/<id>` (e.g. `core/annotations`, `core/slash`,
|
|
379
379
|
* `hello-world/greet`). `Extension.id` carries the **short** id as authored;
|
|
380
380
|
* `Extension.pluginId` carries the namespace; the registry composes the
|
|
@@ -429,18 +429,18 @@ declare class Registry {
|
|
|
429
429
|
}
|
|
430
430
|
|
|
431
431
|
/**
|
|
432
|
-
* Step 9.6.6
|
|
432
|
+
* Step 9.6.6, runtime annotation-contribution catalog types.
|
|
433
433
|
*
|
|
434
434
|
* Lives in its own module (rather than `kernel/index.ts`) so consumers
|
|
435
|
-
* deep inside the kernel
|
|
436
|
-
* future Action contexts
|
|
435
|
+
* deep inside the kernel, `IAnalyzerContext`, the BFF route factories,
|
|
436
|
+
* future Action contexts, can depend on the catalog shape without
|
|
437
437
|
* dragging the whole kernel barrel and risking a cycle.
|
|
438
438
|
*/
|
|
439
439
|
/**
|
|
440
440
|
* Single row of the runtime annotation-contribution catalog surfaced by
|
|
441
441
|
* `kernel.getRegisteredAnnotationKeys()`. One row per (plugin × key)
|
|
442
442
|
* tuple. Built-in catalog keys from `annotations.schema.json` are NOT
|
|
443
|
-
* included
|
|
443
|
+
* included, this catalog is plugin-only; the UI knows the built-in
|
|
444
444
|
* catalog via the schema bundle.
|
|
445
445
|
*/
|
|
446
446
|
interface IRegisteredAnnotationKey {
|
|
@@ -453,17 +453,17 @@ interface IRegisteredAnnotationKey {
|
|
|
453
453
|
}
|
|
454
454
|
|
|
455
455
|
/**
|
|
456
|
-
* Step 11.x
|
|
456
|
+
* Step 11.x, runtime view-contribution catalog types.
|
|
457
457
|
*
|
|
458
458
|
* Lives in its own module (rather than `kernel/index.ts`) so consumers
|
|
459
|
-
* deep inside the kernel
|
|
460
|
-
* future Action contexts
|
|
459
|
+
* deep inside the kernel, `IAnalyzerContext`, the BFF route factories,
|
|
460
|
+
* future Action contexts, can depend on the catalog shape without
|
|
461
461
|
* dragging the whole kernel barrel and risking a cycle.
|
|
462
462
|
*
|
|
463
463
|
* Mirrors `annotation-catalog.ts` for the annotation contribution side
|
|
464
464
|
* (Step 9.6.6). The two systems share the "plugin contributes data,
|
|
465
465
|
* kernel exposes catalog, UI renders" pattern but never overlap in
|
|
466
|
-
* storage or routing
|
|
466
|
+
* storage or routing, see `architecture.md` §View contribution system
|
|
467
467
|
* for the comparison table.
|
|
468
468
|
*
|
|
469
469
|
* **Closed catalog by design.** Both `TSlotName` and `TInputTypeName`
|
|
@@ -545,7 +545,7 @@ interface IViewContribution {
|
|
|
545
545
|
* `order: 'priority'` sort contributions ASC by this value, with
|
|
546
546
|
* alphabetical tie-break by qualified id. The plugin uses this to
|
|
547
547
|
* suggest where its contribution belongs relative to others sharing
|
|
548
|
-
* the same slot
|
|
548
|
+
* the same slot, the slot has the final say.
|
|
549
549
|
*/
|
|
550
550
|
priority?: number;
|
|
551
551
|
}
|
|
@@ -556,7 +556,7 @@ interface IViewContribution {
|
|
|
556
556
|
* by `loadPluginRuntime` from every loaded extension's
|
|
557
557
|
* `viewContributions` map.
|
|
558
558
|
*
|
|
559
|
-
* The qualified id is `<pluginId>/<extensionId>/<contributionId
|
|
559
|
+
* The qualified id is `<pluginId>/<extensionId>/<contributionId>`,
|
|
560
560
|
* matches the qualified id pattern used elsewhere in the kernel
|
|
561
561
|
* (`<pluginId>/<extensionId>` for extensions; this adds the third
|
|
562
562
|
* segment for per-contribution identity).
|
|
@@ -662,7 +662,7 @@ interface ISetting_KeyValueList extends ISettingCommon {
|
|
|
662
662
|
}
|
|
663
663
|
/**
|
|
664
664
|
* Discriminated union of every setting declaration shape. The plugin
|
|
665
|
-
* author NEVER writes JSON Schema for settings
|
|
665
|
+
* author NEVER writes JSON Schema for settings, they pick one of
|
|
666
666
|
* these `type` values and supply per-type parameters.
|
|
667
667
|
*
|
|
668
668
|
* Mirror of `input-types.schema.json#/$defs/ISettingDeclaration`.
|
|
@@ -680,7 +680,7 @@ type TSettingValue = string | string[] | boolean | number | ISetting_KeyValueLis
|
|
|
680
680
|
* Base manifest shape shared by every extension kind. Mirrors
|
|
681
681
|
* `spec/schemas/extensions/base.schema.json` at the TypeScript level.
|
|
682
682
|
*
|
|
683
|
-
* Spec § A.6
|
|
683
|
+
* Spec § A.6, every extension is identified in the registry by the
|
|
684
684
|
* qualified id `<pluginId>/<id>`. The `pluginId` field is required at the
|
|
685
685
|
* runtime / TS level: built-ins declare it directly in
|
|
686
686
|
* `src/extensions/built-ins.ts`; user plugins have it injected by the
|
|
@@ -688,7 +688,7 @@ type TSettingValue = string | string[] | boolean | number | ISetting_KeyValueLis
|
|
|
688
688
|
* registry. A plugin author who hand-codes a `pluginId` that disagrees
|
|
689
689
|
* with the manifest's `id` is rejected as `invalid-manifest`.
|
|
690
690
|
*
|
|
691
|
-
* The JSON Schema deliberately does NOT model `pluginId
|
|
691
|
+
* The JSON Schema deliberately does NOT model `pluginId`, the qualifier
|
|
692
692
|
* is a runtime concern composed by the loader, not a manifest field
|
|
693
693
|
* authors are expected to set. Stripping it before AJV validation in
|
|
694
694
|
* the loader keeps the spec contract clean ("authors declare only the
|
|
@@ -696,7 +696,7 @@ type TSettingValue = string | string[] | boolean | number | ISetting_KeyValueLis
|
|
|
696
696
|
*/
|
|
697
697
|
|
|
698
698
|
/**
|
|
699
|
-
* Step 9.6.6
|
|
699
|
+
* Step 9.6.6, single entry of an extension's `annotationContributions`
|
|
700
700
|
* map. Mirrors `spec/schemas/extensions/base.schema.json#/properties/annotationContributions/additionalProperties`.
|
|
701
701
|
*
|
|
702
702
|
* `schema` is an INLINE JSON Schema (object literal in the manifest),
|
|
@@ -707,14 +707,14 @@ interface IAnnotationContribution {
|
|
|
707
707
|
/** Inline JSON Schema describing the value written under this key. */
|
|
708
708
|
schema: Record<string, unknown>;
|
|
709
709
|
/**
|
|
710
|
-
* Conflict policy. `shared` (default)
|
|
711
|
-
* the key; `exclusive
|
|
710
|
+
* Conflict policy. `shared` (default), multiple plugins MAY write
|
|
711
|
+
* the key; `exclusive`, only this plugin may. REQUIRED to be
|
|
712
712
|
* `'exclusive'` when `location: 'root'`.
|
|
713
713
|
*/
|
|
714
714
|
ownership?: 'exclusive' | 'shared';
|
|
715
715
|
/**
|
|
716
|
-
* Where the key lands. `namespaced` (default)
|
|
717
|
-
* `<plugin-id>:` block; `root
|
|
716
|
+
* Where the key lands. `namespaced` (default), under the plugin's
|
|
717
|
+
* `<plugin-id>:` block; `root`, top-level, alongside `for` /
|
|
718
718
|
* `annotations` / `settings` / `audit`. Cross-plugin root-key
|
|
719
719
|
* collisions on `exclusive` are a fatal startup error.
|
|
720
720
|
*/
|
|
@@ -735,7 +735,7 @@ interface IExtensionBase {
|
|
|
735
735
|
preconditions?: string[];
|
|
736
736
|
entry?: string;
|
|
737
737
|
/**
|
|
738
|
-
* Step 9.6.6
|
|
738
|
+
* Step 9.6.6, plugin-contributed annotation keys. Each entry maps a
|
|
739
739
|
* key name to an inline JSON Schema + ownership + location triple.
|
|
740
740
|
* The kernel surfaces the aggregate via `kernel.getRegisteredAnnotationKeys()`.
|
|
741
741
|
* See `IAnnotationContribution` for the field semantics and
|
|
@@ -747,7 +747,7 @@ interface IExtensionBase {
|
|
|
747
747
|
* contribution id (kebab-case, unique within the extension) to a
|
|
748
748
|
* `IViewContribution` declaration that picks a view slot by name
|
|
749
749
|
* from the closed kernel catalog (`view-catalog.ts#TSlotName`).
|
|
750
|
-
* The slot fixes both the renderer and the payload shape
|
|
750
|
+
* The slot fixes both the renderer and the payload shape, there
|
|
751
751
|
* is no separate "contract" abstraction. The kernel validates each
|
|
752
752
|
* `slot` pick at load time (`invalid-manifest` on miss); the plugin
|
|
753
753
|
* emits per-node payloads via `ctx.emitContribution(<contributionId>,
|
|
@@ -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
|
|
@@ -817,7 +768,7 @@ interface ProgressEmitterPort {
|
|
|
817
768
|
* typed DTOs from `@skill-map/spec` is deferred to a future iteration when a
|
|
818
769
|
* third consumer (real providers / extractors / rules) forces a single
|
|
819
770
|
* source of truth. Until then, both `ui/src/models/` and `src/kernel/types/`
|
|
820
|
-
* hand-curate their own local mirror
|
|
771
|
+
* hand-curate their own local mirror, the risk of drift is accepted at
|
|
821
772
|
* this scale (17 schemas) and flagged in the roadmap.
|
|
822
773
|
*/
|
|
823
774
|
|
|
@@ -827,7 +778,7 @@ interface ProgressEmitterPort {
|
|
|
827
778
|
* tables with explicit migrations (mode `dedicated`). Absent = the plugin
|
|
828
779
|
* does not persist state at all.
|
|
829
780
|
*
|
|
830
|
-
* Optional output-schema declarations (spec § A.12
|
|
781
|
+
* Optional output-schema declarations (spec § A.12, opt-in correctness
|
|
831
782
|
* for plugin custom storage):
|
|
832
783
|
* - Mode `kv` → `schema` (single relative path). Validates the value
|
|
833
784
|
* written by `ctx.store.set(key, value)`.
|
|
@@ -851,13 +802,13 @@ type TPluginStorage = {
|
|
|
851
802
|
/**
|
|
852
803
|
* Toggle granularity for a plugin / built-in bundle.
|
|
853
804
|
*
|
|
854
|
-
* - `'bundle'`
|
|
805
|
+
* - `'bundle'` , the plugin id is the only enable/disable key. The whole
|
|
855
806
|
* bundle of extensions follows the toggle; the user cannot
|
|
856
807
|
* enable some extensions of the bundle and disable others.
|
|
857
808
|
* Default for plugins (and for the built-in `claude`
|
|
858
809
|
* bundle, where the provider and its kind-aware extractors
|
|
859
810
|
* form a coherent provider).
|
|
860
|
-
* - `'extension'
|
|
811
|
+
* - `'extension'`, each extension is independently toggle-able under its
|
|
861
812
|
* qualified id `<plugin-id>/<extension-id>`. Used for
|
|
862
813
|
* the built-in `core` bundle (every kernel built-in
|
|
863
814
|
* rule / formatter is removable per spec
|
|
@@ -894,7 +845,7 @@ interface IPluginManifest {
|
|
|
894
845
|
* Plugin user-configurable settings. Each entry picks an `input-type`
|
|
895
846
|
* from the closed catalog at
|
|
896
847
|
* `spec/schemas/input-types.schema.json#/$defs/InputTypeName`.
|
|
897
|
-
* The plugin author NEVER writes JSON Schema
|
|
848
|
+
* The plugin author NEVER writes JSON Schema, they pick `type` by
|
|
898
849
|
* name and supply per-type parameters. The kernel exposes resolved
|
|
899
850
|
* settings to extractors via `ctx.settings.<settingId>`; settings
|
|
900
851
|
* are read once at extractor invocation; changing a setting requires
|
|
@@ -913,29 +864,31 @@ 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
|
|
922
875
|
* `status` enum in `spec/schemas/plugins-registry.schema.json`.
|
|
923
876
|
*
|
|
924
|
-
* - `enabled`
|
|
877
|
+
* - `enabled` , manifest valid, specCompat satisfied, every
|
|
925
878
|
* extension imported and validated.
|
|
926
|
-
* - `disabled`
|
|
879
|
+
* - `disabled` , user-toggled off via `sm plugins disable` or
|
|
927
880
|
* `settings.json#/plugins/<id>/enabled`. Manifest
|
|
928
881
|
* is parsed and surfaced (so `sm plugins list`
|
|
929
882
|
* shows it), but extensions are not imported.
|
|
930
|
-
* - `incompatible-spec`
|
|
931
|
-
* - `invalid-manifest`
|
|
883
|
+
* - `incompatible-spec` , manifest parsed but `semver.satisfies` failed.
|
|
884
|
+
* - `invalid-manifest` , `plugin.json` missing, unparseable, AJV-fails,
|
|
932
885
|
* OR the directory name does not equal the
|
|
933
886
|
* manifest id (a cheap structural rule that
|
|
934
887
|
* rules out same-root collisions by construction:
|
|
935
888
|
* a filesystem cannot contain two siblings with
|
|
936
889
|
* the same name).
|
|
937
|
-
* - `load-error`
|
|
938
|
-
* - `id-collision`
|
|
890
|
+
* - `load-error` , manifest passed, an extension module failed.
|
|
891
|
+
* - `id-collision` , two plugins reachable from different roots
|
|
939
892
|
* (project + global, or any `--plugin-dir`
|
|
940
893
|
* combination) declared the same `id`. Both
|
|
941
894
|
* collided plugins receive this status; no
|
|
@@ -947,7 +900,7 @@ interface ILoadedExtension {
|
|
|
947
900
|
kind: ExtensionKind;
|
|
948
901
|
id: string;
|
|
949
902
|
/**
|
|
950
|
-
* Owning plugin namespace
|
|
903
|
+
* Owning plugin namespace, `manifest.id` of the `plugin.json` that
|
|
951
904
|
* declared this extension. Composed with `id` to form the qualified
|
|
952
905
|
* registry key `<pluginId>/<id>`. Per spec § A.6 the loader injects
|
|
953
906
|
* this from the manifest; an extension that hand-declares a
|
|
@@ -959,7 +912,7 @@ interface ILoadedExtension {
|
|
|
959
912
|
/** Raw module namespace as returned by the dynamic `import()`. */
|
|
960
913
|
module: unknown;
|
|
961
914
|
/**
|
|
962
|
-
* Runtime extension instance ready for the registry / orchestrator
|
|
915
|
+
* Runtime extension instance ready for the registry / orchestrator,
|
|
963
916
|
* the `default` export of `module` (or the module itself when no
|
|
964
917
|
* default), shallow-cloned with `pluginId` injected per spec § A.6.
|
|
965
918
|
*
|
|
@@ -973,7 +926,7 @@ interface ILoadedExtension {
|
|
|
973
926
|
interface IDiscoveredPlugin {
|
|
974
927
|
/** Absolute path to the plugin directory. */
|
|
975
928
|
path: string;
|
|
976
|
-
/** Plugin id
|
|
929
|
+
/** Plugin id, populated from the manifest if it parsed, else a path hint. */
|
|
977
930
|
id: string;
|
|
978
931
|
status: TPluginLoadStatus;
|
|
979
932
|
/** Only present when status === 'enabled' or 'incompatible-spec'. */
|
|
@@ -987,20 +940,20 @@ interface IDiscoveredPlugin {
|
|
|
987
940
|
*/
|
|
988
941
|
granularity?: TGranularity;
|
|
989
942
|
/**
|
|
990
|
-
* Runtime-only
|
|
943
|
+
* Runtime-only, never persisted, never spec-modeled.
|
|
991
944
|
*
|
|
992
|
-
* Spec § A.12
|
|
945
|
+
* Spec § A.12, opt-in JSON Schema validation for plugin custom storage.
|
|
993
946
|
* Populated by the loader when `manifest.storage.schemas` (Mode B) or
|
|
994
947
|
* `manifest.storage.schema` (Mode A) declares schema paths the loader
|
|
995
948
|
* successfully read and AJV-compiled. Consumed by the runtime store
|
|
996
949
|
* wrapper to validate `ctx.store.write(table, row)` (Mode B) and
|
|
997
950
|
* `ctx.store.set(key, value)` (Mode A) before persisting.
|
|
998
951
|
*
|
|
999
|
-
* Mode B layout
|
|
952
|
+
* Mode B layout, keyed by logical table name (without the
|
|
1000
953
|
* `plugin_<normalizedId>_` prefix), matching the manifest's `schemas`
|
|
1001
954
|
* map. Tables not present in the map accept any shape (permissive).
|
|
1002
955
|
*
|
|
1003
|
-
* Mode A layout
|
|
956
|
+
* Mode A layout, uses the sentinel key `__kv__` for the single
|
|
1004
957
|
* value-shape schema. The sentinel survives the runtime contract change
|
|
1005
958
|
* if Mode A ever grows multiple namespaces.
|
|
1006
959
|
*
|
|
@@ -1013,7 +966,7 @@ interface IDiscoveredPlugin {
|
|
|
1013
966
|
reason?: string;
|
|
1014
967
|
}
|
|
1015
968
|
/**
|
|
1016
|
-
* Runtime-only
|
|
969
|
+
* Runtime-only, a single AJV-compiled storage schema attached to a
|
|
1017
970
|
* loaded plugin. The schema path (relative to the plugin directory) is
|
|
1018
971
|
* preserved so error messages can name the offending file. `validate`
|
|
1019
972
|
* is the AJV `ValidateFunction` itself: it returns `true` on shape
|
|
@@ -1035,20 +988,20 @@ interface IPluginStorageSchema {
|
|
|
1035
988
|
}
|
|
1036
989
|
|
|
1037
990
|
/**
|
|
1038
|
-
* Plugin store wrappers
|
|
991
|
+
* Plugin store wrappers, runtime injection for `ctx.store` per spec
|
|
1039
992
|
* § A.12 (opt-in `outputSchema` for plugin custom storage).
|
|
1040
993
|
*
|
|
1041
994
|
* Two shapes, mirroring the manifest's storage modes documented in
|
|
1042
995
|
* `spec/plugin-kv-api.md`:
|
|
1043
996
|
*
|
|
1044
|
-
* - Mode A
|
|
997
|
+
* - Mode A, `KvStore.set(key, value)`. AJV-validates `value` against
|
|
1045
998
|
* the schema declared by `manifest.storage.schema` (single
|
|
1046
999
|
* value-shape) when present. Absent = permissive.
|
|
1047
|
-
* - Mode B
|
|
1000
|
+
* - Mode B, `DedicatedStore.write(table, row)`. AJV-validates `row`
|
|
1048
1001
|
* against the per-table schema declared in `manifest.storage.schemas`
|
|
1049
1002
|
* when present. Tables absent from the map accept any shape.
|
|
1050
1003
|
*
|
|
1051
|
-
* Both wrappers are storage-engine agnostic
|
|
1004
|
+
* Both wrappers are storage-engine agnostic, they accept a `persist`
|
|
1052
1005
|
* callback the caller supplies. The persistence side (SQLite, in-memory,
|
|
1053
1006
|
* mock) is the caller's concern; this wrapper's only job is the
|
|
1054
1007
|
* AJV gate. That separation lets the test suite exercise the validator
|
|
@@ -1057,7 +1010,7 @@ interface IPluginStorageSchema {
|
|
|
1057
1010
|
* unchanged.
|
|
1058
1011
|
*
|
|
1059
1012
|
* Universal validation (`emitLink` against `link.schema.json`,
|
|
1060
|
-
* `enrichNode` against `node.schema.json`) is unaffected
|
|
1013
|
+
* `enrichNode` against `node.schema.json`) is unaffected, it lives on
|
|
1061
1014
|
* the orchestrator side and runs regardless of the plugin's
|
|
1062
1015
|
* `outputSchema` opt-in.
|
|
1063
1016
|
*/
|
|
@@ -1083,7 +1036,7 @@ interface IDedicatedStorePersist {
|
|
|
1083
1036
|
* schema path and AJV errors; persistence is skipped on failure.
|
|
1084
1037
|
*
|
|
1085
1038
|
* `pluginId` is captured for diagnostics (the throw message names the
|
|
1086
|
-
* plugin). The wrapper does NOT itself scope by plugin id
|
|
1039
|
+
* plugin). The wrapper does NOT itself scope by plugin id, that is
|
|
1087
1040
|
* the persistence layer's job (the spec's `state_plugin_kvs` PK includes
|
|
1088
1041
|
* `pluginId` and the kernel-side adapter prepends it before write).
|
|
1089
1042
|
*/
|
|
@@ -1091,7 +1044,7 @@ interface IKvStoreWrapper {
|
|
|
1091
1044
|
set(key: string, value: unknown): Promise<void>;
|
|
1092
1045
|
}
|
|
1093
1046
|
/**
|
|
1094
|
-
* Union shape exposed to extractors via `ctx.store`. Spec § A.12
|
|
1047
|
+
* Union shape exposed to extractors via `ctx.store`. Spec § A.12, Mode A
|
|
1095
1048
|
* (`kv`) returns a `set(key, value)` surface; Mode B (`dedicated`) returns
|
|
1096
1049
|
* `write(table, row)`. Plugin authors narrow at the call site based on
|
|
1097
1050
|
* the storage mode declared in their `plugin.json`.
|
|
@@ -1105,7 +1058,7 @@ declare function makeKvStoreWrapper(opts: {
|
|
|
1105
1058
|
/**
|
|
1106
1059
|
* Mode B wrapper. `write(table, row)` AJV-validates `row` against
|
|
1107
1060
|
* `storageSchemas[table]` when declared, then forwards to `persist`.
|
|
1108
|
-
* Tables absent from the map are permissive
|
|
1061
|
+
* Tables absent from the map are permissive, the wrapper forwards
|
|
1109
1062
|
* straight to `persist` without validation.
|
|
1110
1063
|
*
|
|
1111
1064
|
* The wrapper accepts the full `storageSchemas` map (rather than a
|
|
@@ -1135,7 +1088,7 @@ declare function makePluginStore(opts: {
|
|
|
1135
1088
|
}): IPluginStore | undefined;
|
|
1136
1089
|
|
|
1137
1090
|
/**
|
|
1138
|
-
* `scan_contributions` adapter
|
|
1091
|
+
* `scan_contributions` adapter, replace-all writer used by
|
|
1139
1092
|
* `persistScanResult`, plus read helpers consumed by the BFF
|
|
1140
1093
|
* (`/api/contributions/...`) and rules (`core/contribution-orphan`).
|
|
1141
1094
|
*
|
|
@@ -1148,7 +1101,7 @@ declare function makePluginStore(opts: {
|
|
|
1148
1101
|
* scan is a fresh snapshot, so prior rows are deleted before insert.
|
|
1149
1102
|
* Wrapped in the same transaction `persistScanResult` opens.
|
|
1150
1103
|
*
|
|
1151
|
-
* The rename heuristic does NOT need to migrate `node_path` here
|
|
1104
|
+
* The rename heuristic does NOT need to migrate `node_path` here,
|
|
1152
1105
|
* because of replace-all, every contribution is re-emitted on the new
|
|
1153
1106
|
* path automatically. Keeping the rename path lighter than `state_*`
|
|
1154
1107
|
* (which IS rename-migrated because state survives across scans).
|
|
@@ -1168,7 +1121,7 @@ interface IContributionRecord {
|
|
|
1168
1121
|
contributionId: string;
|
|
1169
1122
|
/**
|
|
1170
1123
|
* Closed enum value mirroring `view-slots.schema.json#/$defs/SlotName`.
|
|
1171
|
-
* Persisted as TEXT (no SQL CHECK by design
|
|
1124
|
+
* Persisted as TEXT (no SQL CHECK by design, see migration comment).
|
|
1172
1125
|
*/
|
|
1173
1126
|
slot: string;
|
|
1174
1127
|
/** Already-validated payload. Serialised via `JSON.stringify` at write. */
|
|
@@ -1192,7 +1145,7 @@ interface IPersistedContribution {
|
|
|
1192
1145
|
}
|
|
1193
1146
|
|
|
1194
1147
|
/**
|
|
1195
|
-
* `loadScanResult
|
|
1148
|
+
* `loadScanResult`, driving inverse of `persistScanResult`. Reads the
|
|
1196
1149
|
* `scan_*` tables and reconstructs a `ScanResult` shape so the
|
|
1197
1150
|
* orchestrator can run an incremental scan (`sm scan --changed`) on
|
|
1198
1151
|
* top of a prior snapshot.
|
|
@@ -1205,7 +1158,7 @@ interface IPersistedContribution {
|
|
|
1205
1158
|
*
|
|
1206
1159
|
* **Documented omission**: external pseudo-links (those whose target is
|
|
1207
1160
|
* an `http://` / `https://` URL emitted by the external-url-counter
|
|
1208
|
-
* extractor) are NEVER persisted to `scan_links
|
|
1161
|
+
* extractor) are NEVER persisted to `scan_links`, only their per-node
|
|
1209
1162
|
* count survives in `scan_nodes.external_refs_count`. Therefore the
|
|
1210
1163
|
* `result.links` returned by `loadScanResult` contains only internal
|
|
1211
1164
|
* graph links, and `node.externalRefsCount` is the authoritative count
|
|
@@ -1232,11 +1185,11 @@ interface IPersistedContribution {
|
|
|
1232
1185
|
* `durationMs`; the three count fields derive from row counts.
|
|
1233
1186
|
*
|
|
1234
1187
|
* Both branches keep `nodesCount` / `linksCount` / `issuesCount` derived
|
|
1235
|
-
* from `COUNT(*)` of the loaded rows
|
|
1188
|
+
* from `COUNT(*)` of the loaded rows, never persisted, always recomputed.
|
|
1236
1189
|
*/
|
|
1237
1190
|
|
|
1238
1191
|
/**
|
|
1239
|
-
* Spec § A.9
|
|
1192
|
+
* Spec § A.9, load the fine-grained Extractor cache as a per-node map
|
|
1240
1193
|
* from qualified extractor id (`<pluginId>/<id>`) to the run-time
|
|
1241
1194
|
* hashes the extractor recorded on its last run. Empty map is the
|
|
1242
1195
|
* default when the table is empty (fresh DB, never-scanned scope, or
|
|
@@ -1252,6 +1205,61 @@ interface IPriorExtractorRun {
|
|
|
1252
1205
|
sidecarAnnotationsHash: string;
|
|
1253
1206
|
}
|
|
1254
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
|
+
|
|
1236
|
+
/**
|
|
1237
|
+
* Diagnostic surfaced by a parser when the raw input was structurally
|
|
1238
|
+
* malformed (e.g. YAML parse error). The parser MUST still return a
|
|
1239
|
+
* usable `{ frontmatter, frontmatterRaw, body }` triple (defaults are
|
|
1240
|
+
* fine) so the scan keeps making progress; this carries the message
|
|
1241
|
+
* the orchestrator translates into a kernel `Issue` with severity
|
|
1242
|
+
* `warn` (and `error` under `--strict`).
|
|
1243
|
+
*
|
|
1244
|
+
* Pure data: parsers never log or throw; they describe the failure
|
|
1245
|
+
* here and let the orchestrator decide how to surface it.
|
|
1246
|
+
*/
|
|
1247
|
+
interface IParseIssue {
|
|
1248
|
+
/**
|
|
1249
|
+
* Stable tag describing the failure class. The only emitter today
|
|
1250
|
+
* is `frontmatter-yaml` reporting a YAML parse error
|
|
1251
|
+
* (`'frontmatter-parse-error'`); the set may grow as new parsers
|
|
1252
|
+
* land.
|
|
1253
|
+
*/
|
|
1254
|
+
code: string;
|
|
1255
|
+
/**
|
|
1256
|
+
* Human-readable message, sanitised. Never includes the raw input
|
|
1257
|
+
* (a hostile YAML could embed multi-line garbage); only the
|
|
1258
|
+
* parser-error string is interpolated.
|
|
1259
|
+
*/
|
|
1260
|
+
message: string;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1255
1263
|
/**
|
|
1256
1264
|
* Provider runtime contract. Walks filesystem roots and emits raw node
|
|
1257
1265
|
* records; classification maps path conventions to a node kind.
|
|
@@ -1286,6 +1294,14 @@ interface IRawNode {
|
|
|
1286
1294
|
frontmatterRaw: string;
|
|
1287
1295
|
/** Parsed frontmatter, or `{}` when absent / unparseable. */
|
|
1288
1296
|
frontmatter: Record<string, unknown>;
|
|
1297
|
+
/**
|
|
1298
|
+
* Parser diagnostics (audit L1). Populated by the walker when the
|
|
1299
|
+
* parser surfaced `IParseIssue` entries (e.g. malformed YAML).
|
|
1300
|
+
* Carried through `processRawNode` and converted into warn-level
|
|
1301
|
+
* kernel `Issue` rows inside `buildFreshNodeAndValidateFrontmatter`.
|
|
1302
|
+
* Empty / undefined on the happy path.
|
|
1303
|
+
*/
|
|
1304
|
+
parseIssues?: readonly IParseIssue[];
|
|
1289
1305
|
}
|
|
1290
1306
|
/**
|
|
1291
1307
|
* One entry in a Provider's `kinds` map. Declares both the per-kind
|
|
@@ -1341,7 +1357,7 @@ interface IProviderKind {
|
|
|
1341
1357
|
* intent (label + base color, optional dark variant + emoji + icon);
|
|
1342
1358
|
* the UI derives `bg`/`fg` tints per theme via a deterministic helper
|
|
1343
1359
|
* and reads the registry from the `kindRegistry` field embedded in REST
|
|
1344
|
-
* envelopes. Single source of truth for what a kind looks like
|
|
1360
|
+
* envelopes. Single source of truth for what a kind looks like, the
|
|
1345
1361
|
* UI never hardcodes presentation for a built-in kind.
|
|
1346
1362
|
*/
|
|
1347
1363
|
interface IProviderKindUi {
|
|
@@ -1395,15 +1411,6 @@ type TProviderKindIcon = {
|
|
|
1395
1411
|
};
|
|
1396
1412
|
interface IProvider extends IExtensionBase {
|
|
1397
1413
|
kind: 'provider';
|
|
1398
|
-
/**
|
|
1399
|
-
* Filesystem directory (relative to user home or project root) where this
|
|
1400
|
-
* Provider's content lives. Required. Examples: `'~/.claude'` for the
|
|
1401
|
-
* Claude Provider, `'~/.cursor'` for a hypothetical Cursor Provider.
|
|
1402
|
-
* The kernel walks this directory during boot/scan to discover nodes;
|
|
1403
|
-
* `sm doctor` validates the directory exists and emits a non-blocking
|
|
1404
|
-
* warning when it does not.
|
|
1405
|
-
*/
|
|
1406
|
-
explorationDir: string;
|
|
1407
1414
|
/**
|
|
1408
1415
|
* Catalog of node kinds this Provider emits. Keyed by kind name. Every
|
|
1409
1416
|
* kind the Provider can `classify()` MUST have an entry; an entry is
|
|
@@ -1426,12 +1433,12 @@ interface IProvider extends IExtensionBase {
|
|
|
1426
1433
|
* per-kind schemas compile, so cross-file `$ref` resolution succeeds.
|
|
1427
1434
|
*
|
|
1428
1435
|
* Use case: when several kinds share a common base (e.g. Anthropic's
|
|
1429
|
-
* merged skill / command frontmatter
|
|
1436
|
+
* merged skill / command frontmatter, both extend a shared
|
|
1430
1437
|
* `skill-base.schema.json`), the Provider declares the base here so
|
|
1431
1438
|
* `skill.schema.json` and `command.schema.json` can `$ref` it without
|
|
1432
1439
|
* duplicating fields.
|
|
1433
1440
|
*
|
|
1434
|
-
* Runtime-only
|
|
1441
|
+
* Runtime-only, does NOT appear in the spec's `provider.schema.json`
|
|
1435
1442
|
* manifest. Manifest-validated schemas remain the per-kind ones in
|
|
1436
1443
|
* `kinds[<kind>].schema`; auxiliary schemas are an implementation
|
|
1437
1444
|
* concern of how the runtime composes those.
|
|
@@ -1449,7 +1456,7 @@ interface IProvider extends IExtensionBase {
|
|
|
1449
1456
|
* so the most common Provider shape needs zero configuration.
|
|
1450
1457
|
*
|
|
1451
1458
|
* Precedence: when both `walk()` (runtime field) and `read` are
|
|
1452
|
-
* declared, `walk()` wins
|
|
1459
|
+
* declared, `walk()` wins, `read` is ignored. The escape-hatch
|
|
1453
1460
|
* relationship is intentional: most Providers should use `read`;
|
|
1454
1461
|
* Providers with non-standard discovery requirements (custom file
|
|
1455
1462
|
* naming, multi-pass walks, dynamic ignore logic) implement `walk()`
|
|
@@ -1466,7 +1473,7 @@ interface IProvider extends IExtensionBase {
|
|
|
1466
1473
|
* Non-matching files are silently skipped. Unreadable files produce
|
|
1467
1474
|
* a diagnostic via the emitter but do not abort the walk.
|
|
1468
1475
|
*
|
|
1469
|
-
* `options.ignoreFilter
|
|
1476
|
+
* `options.ignoreFilter`, when supplied, the Provider MUST
|
|
1470
1477
|
* skip every directory and file whose path-relative-to-root the
|
|
1471
1478
|
* filter reports as ignored. Providers MAY also keep their own
|
|
1472
1479
|
* hard-coded skip list (e.g. `.git`) as a defensive measure, but the
|
|
@@ -1474,14 +1481,14 @@ interface IProvider extends IExtensionBase {
|
|
|
1474
1481
|
*
|
|
1475
1482
|
* Optional. When omitted, the Provider MUST declare `read` (or rely
|
|
1476
1483
|
* on the default config). The orchestrator never calls `walk()`
|
|
1477
|
-
* directly
|
|
1484
|
+
* directly, it goes through `resolveProviderWalk(provider)` which
|
|
1478
1485
|
* picks `walk` over `read`.
|
|
1479
1486
|
*/
|
|
1480
1487
|
walk?(roots: string[], options?: {
|
|
1481
1488
|
ignoreFilter?: IIgnoreFilter;
|
|
1482
1489
|
}): AsyncIterable<IRawNode>;
|
|
1483
1490
|
/**
|
|
1484
|
-
* Given a path and its parsed frontmatter, decide the node kind
|
|
1491
|
+
* Given a path and its parsed frontmatter, decide the node kind, or
|
|
1485
1492
|
* `null` to disclaim the file. The classifier is called after walk()
|
|
1486
1493
|
* yields; with multiple Providers active, every Provider walks every
|
|
1487
1494
|
* file matching its `read.extensions`, so each Provider MUST disclaim
|
|
@@ -1528,18 +1535,18 @@ interface IProviderReadConfig {
|
|
|
1528
1535
|
* Extractors are deterministic-only. They run synchronously inside the
|
|
1529
1536
|
* scan loop; LLM-driven enrichment of a node is an Action concern, not
|
|
1530
1537
|
* an Extractor concern. The Extractor context therefore exposes no
|
|
1531
|
-
* `RunnerPort
|
|
1538
|
+
* `RunnerPort`, see spec `architecture.md` §Execution modes.
|
|
1532
1539
|
*
|
|
1533
1540
|
* Output channels (all on the context):
|
|
1534
1541
|
*
|
|
1535
|
-
* - `ctx.emitLink(link)
|
|
1542
|
+
* - `ctx.emitLink(link)`, persist a link in the kernel's `links` table.
|
|
1536
1543
|
* Validated against `emitsLinkKinds` before insertion; an off-contract
|
|
1537
1544
|
* kind drops the link and surfaces an `extension.error` event.
|
|
1538
|
-
* - `ctx.enrichNode(partial)
|
|
1545
|
+
* - `ctx.enrichNode(partial)`, merge canonical, kernel-curated properties
|
|
1539
1546
|
* onto the node. Strictly separate from the author-supplied frontmatter
|
|
1540
1547
|
* (the latter remains immutable and survives verbatim). Persistence
|
|
1541
1548
|
* is spec'd in § A.8.
|
|
1542
|
-
* - `ctx.store
|
|
1549
|
+
* - `ctx.store`, plugin-scoped persistence. Present only when the
|
|
1543
1550
|
* plugin declares `storage.mode` in `plugin.json`; shape depends on the
|
|
1544
1551
|
* mode (`KvStore` for mode A, scoped `Database` for mode B). See
|
|
1545
1552
|
* `plugin-kv-api.md` for the contract.
|
|
@@ -1578,7 +1585,7 @@ interface IExtractorCallbacks {
|
|
|
1578
1585
|
* extension-local Record key declared under
|
|
1579
1586
|
* `extension.viewContributions[<contributionId>]`; the second is a
|
|
1580
1587
|
* payload that conforms to the slot's payload schema in
|
|
1581
|
-
* `spec/schemas/view-slots.schema.json#/$defs/payloads/<slot
|
|
1588
|
+
* `spec/schemas/view-slots.schema.json#/$defs/payloads/<slot>`,
|
|
1582
1589
|
* where `<slot>` is the slot the manifest declared for this
|
|
1583
1590
|
* contribution. The orchestrator validates the payload against the
|
|
1584
1591
|
* slot's schema before persisting to `scan_contributions`; off-shape
|
|
@@ -1601,7 +1608,7 @@ interface IExtractorContext extends IExtractorCallbacks {
|
|
|
1601
1608
|
* (`write(table, row)`). See `spec/plugin-kv-api.md`.
|
|
1602
1609
|
*
|
|
1603
1610
|
* Typed as `unknown` so this contract module stays free of any
|
|
1604
|
-
* adapter-side imports
|
|
1611
|
+
* adapter-side imports, the concrete `IPluginStore` lives in
|
|
1605
1612
|
* `kernel/adapters/plugin-store.js`. Plugin authors narrow at the
|
|
1606
1613
|
* call site based on the storage mode declared in their manifest.
|
|
1607
1614
|
* The orchestrator looks up the wrapper per-extractor in
|
|
@@ -1618,11 +1625,11 @@ interface IExtractor extends IExtensionBase {
|
|
|
1618
1625
|
/**
|
|
1619
1626
|
* Optional opt-in filter on `node.kind`. When declared, the orchestrator
|
|
1620
1627
|
* skips invocation of `extract()` for any node whose `kind` is NOT in
|
|
1621
|
-
* this list
|
|
1628
|
+
* this list, fail-fast, before context construction, so the extractor
|
|
1622
1629
|
* wastes zero CPU on inapplicable nodes.
|
|
1623
1630
|
*
|
|
1624
1631
|
* Absent (`undefined`) is the default: the extractor applies to every
|
|
1625
|
-
* kind. There are no wildcards
|
|
1632
|
+
* kind. There are no wildcards, the absence of the field already
|
|
1626
1633
|
* encodes "every kind". An empty array (`[]`) is rejected at load
|
|
1627
1634
|
* time by AJV (`minItems: 1` in the schema).
|
|
1628
1635
|
*
|
|
@@ -1647,13 +1654,13 @@ interface IExtractor extends IExtensionBase {
|
|
|
1647
1654
|
* findings into the UI via view contributions. Deterministic analyzers
|
|
1648
1655
|
* are pure (same graph in → same issues out) and run synchronously
|
|
1649
1656
|
* inside `sm scan` / `sm check`. Probabilistic analyzers invoke an LLM
|
|
1650
|
-
* through the kernel's `RunnerPort` and dispatch only as queued jobs
|
|
1657
|
+
* through the kernel's `RunnerPort` and dispatch only as queued jobs,
|
|
1651
1658
|
* they never participate in scan-time pipelines. Mode is declared in
|
|
1652
1659
|
* the manifest (default `deterministic`).
|
|
1653
1660
|
*/
|
|
1654
1661
|
|
|
1655
1662
|
/**
|
|
1656
|
-
* Step 9.6.2
|
|
1663
|
+
* Step 9.6.2, orphan sidecar entry surfaced to analyzers. A `.sm` file
|
|
1657
1664
|
* whose sibling `.md` does not exist on disk; the `annotation-orphan`
|
|
1658
1665
|
* built-in analyzer emits one warning per entry. Other analyzers that
|
|
1659
1666
|
* care about orphan sidecars MAY consume the list too.
|
|
@@ -1668,13 +1675,13 @@ interface IAnalyzerContext {
|
|
|
1668
1675
|
nodes: Node[];
|
|
1669
1676
|
links: Link[];
|
|
1670
1677
|
/**
|
|
1671
|
-
* Step 9.6.2
|
|
1678
|
+
* Step 9.6.2, orphaned sidecars discovered during the scan walk.
|
|
1672
1679
|
* Empty when sidecar discovery did not run (legacy callers) or
|
|
1673
1680
|
* when no orphans exist.
|
|
1674
1681
|
*/
|
|
1675
1682
|
orphanSidecars?: IAnalyzerOrphanSidecar[];
|
|
1676
1683
|
/**
|
|
1677
|
-
* Step 9.6.6
|
|
1684
|
+
* Step 9.6.6, raw parsed sidecar root keyed by `node.path`. Populated
|
|
1678
1685
|
* by the orchestrator alongside the public `Node.sidecar` overlay so
|
|
1679
1686
|
* analyzers that inspect plugin namespaces (e.g. the built-in
|
|
1680
1687
|
* `core/unknown-field` Analyzer) can walk the full tree without
|
|
@@ -1684,7 +1691,7 @@ interface IAnalyzerContext {
|
|
|
1684
1691
|
*/
|
|
1685
1692
|
sidecarRoots?: ReadonlyMap<string, Record<string, unknown>>;
|
|
1686
1693
|
/**
|
|
1687
|
-
* Step 9.6.6
|
|
1694
|
+
* Step 9.6.6, runtime catalog of plugin-contributed annotation keys,
|
|
1688
1695
|
* as exposed by `kernel.getRegisteredAnnotationKeys()`. Threaded
|
|
1689
1696
|
* through so analyzers can reason about the registered-vs-unknown
|
|
1690
1697
|
* split without reaching back into the kernel. Empty array when no
|
|
@@ -1693,20 +1700,20 @@ interface IAnalyzerContext {
|
|
|
1693
1700
|
*/
|
|
1694
1701
|
annotationContributions?: readonly IRegisteredAnnotationKey[];
|
|
1695
1702
|
/**
|
|
1696
|
-
* Step 11.x
|
|
1703
|
+
* Step 11.x, runtime catalog of plugin-contributed view contributions,
|
|
1697
1704
|
* as exposed by `kernel.getRegisteredViewContributions()`. Threaded
|
|
1698
1705
|
* through so analyzers can reason about emissions without reaching
|
|
1699
|
-
* back into the kernel
|
|
1700
|
-
*
|
|
1701
|
-
*
|
|
1702
|
-
*
|
|
1703
|
-
*
|
|
1704
|
-
* through).
|
|
1706
|
+
* back into the kernel (built-in `core/contribution-orphan` joins it
|
|
1707
|
+
* with the live node set to flag dangling emissions). Slot catalog
|
|
1708
|
+
* drift detection is NOT a scan concern, it lives at load time and
|
|
1709
|
+
* surfaces via `sm plugins doctor`. Empty array when no extension
|
|
1710
|
+
* declares view contributions; absent for legacy callers (older
|
|
1711
|
+
* runScan sites that never wired the catalog through).
|
|
1705
1712
|
*/
|
|
1706
1713
|
viewContributions?: readonly IRegisteredViewContribution[];
|
|
1707
1714
|
/**
|
|
1708
1715
|
* Absolute paths of `*.md` files under the project's
|
|
1709
|
-
* `.skill-map/jobs/` that no `state_jobs.filePath` references
|
|
1716
|
+
* `.skill-map/jobs/` that no `state_jobs.filePath` references, the
|
|
1710
1717
|
* built-in `core/job-orphan-file` analyzer projects each as a `warn`
|
|
1711
1718
|
* issue. Pre-computed by the driving adapter (CLI / BFF) inside its
|
|
1712
1719
|
* already-open storage transaction (mirrors the `orphanSidecars`
|
|
@@ -1721,7 +1728,7 @@ interface IAnalyzerContext {
|
|
|
1721
1728
|
* link-validation purposes via `scan.referencePaths`. The driving
|
|
1722
1729
|
* adapter walks each configured path before the scan and collects
|
|
1723
1730
|
* every existing file's absolute path here. Files in this set are
|
|
1724
|
-
* NOT indexed as graph nodes
|
|
1731
|
+
* NOT indexed as graph nodes, the only consumer is
|
|
1725
1732
|
* `core/broken-ref`, which suppresses its `warn` issue when a
|
|
1726
1733
|
* path-style link target falls into the set. Absent / empty when
|
|
1727
1734
|
* the operator left `scan.referencePaths` empty or when the
|
|
@@ -1766,6 +1773,20 @@ interface IAnalyzer extends IExtensionBase {
|
|
|
1766
1773
|
* `deterministic` per `spec/schemas/extensions/analyzer.schema.json`.
|
|
1767
1774
|
*/
|
|
1768
1775
|
mode?: TExecutionMode;
|
|
1776
|
+
/**
|
|
1777
|
+
* Qualified `<pluginId>/<id>` Action ids the analyzer recommends to
|
|
1778
|
+
* resolve its findings. Distinct from `Action.precondition` (which
|
|
1779
|
+
* declares which nodes an Action applies to from the Action side);
|
|
1780
|
+
* this field declares which Actions are relevant when this
|
|
1781
|
+
* Analyzer fires from the Analyzer side. Actions are per-node by
|
|
1782
|
+
* design (project-level cleanup verbs like orphan file prune or
|
|
1783
|
+
* contribution relink are CLI verbs, not Actions) and are NOT
|
|
1784
|
+
* surfaced through this field. The UI consumes it in the node
|
|
1785
|
+
* inspector under "Recommended for issues". Optional; omit when no
|
|
1786
|
+
* Action resolves the finding (e.g. `core/superseded` surfaces
|
|
1787
|
+
* deliberate user declarations, not problems).
|
|
1788
|
+
*/
|
|
1789
|
+
recommendedActions?: readonly string[];
|
|
1769
1790
|
evaluate(ctx: IAnalyzerContext): Issue[] | Promise<Issue[]>;
|
|
1770
1791
|
}
|
|
1771
1792
|
|
|
@@ -1775,9 +1796,9 @@ interface IAnalyzer extends IExtensionBase {
|
|
|
1775
1796
|
*
|
|
1776
1797
|
* Actions operate on one or more nodes in one of two execution modes:
|
|
1777
1798
|
*
|
|
1778
|
-
* - `deterministic
|
|
1799
|
+
* - `deterministic`, code runs in-process; the action computes the
|
|
1779
1800
|
* report synchronously and returns it. No job file, no runner.
|
|
1780
|
-
* - `probabilistic
|
|
1801
|
+
* - `probabilistic`, the kernel renders a prompt + preamble into a
|
|
1781
1802
|
* job file; a runner executes it via `RunnerPort` against an LLM;
|
|
1782
1803
|
* `sm record` closes the job and validates the report against
|
|
1783
1804
|
* `reportSchemaRef`.
|
|
@@ -1787,7 +1808,7 @@ interface IAnalyzer extends IExtensionBase {
|
|
|
1787
1808
|
* probabilistic) lands with the job subsystem (Decision #114 in
|
|
1788
1809
|
* `ROADMAP.md`). Today the loader still validates `kind: 'action'`
|
|
1789
1810
|
* manifests against `extension-action.schema.json` and the registry
|
|
1790
|
-
* holds them
|
|
1811
|
+
* holds them, `sm actions show` and the precondition gating UI consume
|
|
1791
1812
|
* the manifest data. The runtime entry point is intentionally absent
|
|
1792
1813
|
* from `IAction` so plugin authors don't ship a method the kernel will
|
|
1793
1814
|
* not call until the job subsystem is in place; when it ships, the
|
|
@@ -1795,21 +1816,21 @@ interface IAnalyzer extends IExtensionBase {
|
|
|
1795
1816
|
*
|
|
1796
1817
|
* Mirrors `extensions/action.schema.json`:
|
|
1797
1818
|
*
|
|
1798
|
-
* - `mode` (required)
|
|
1799
|
-
* - `reportSchemaRef` (required)
|
|
1819
|
+
* - `mode` (required), discriminator between the two modes.
|
|
1820
|
+
* - `reportSchemaRef` (required), JSON Schema reference the report
|
|
1800
1821
|
* MUST validate against. MUST extend `report-base.schema.json`.
|
|
1801
|
-
* - `promptTemplateRef
|
|
1822
|
+
* - `promptTemplateRef`, REQUIRED when `mode: 'probabilistic'`,
|
|
1802
1823
|
* FORBIDDEN when `mode: 'deterministic'`. The schema's conditional
|
|
1803
1824
|
* `allOf` enforces both directions; the runtime contract simply
|
|
1804
1825
|
* surfaces the field as optional and lets the loader catch shape
|
|
1805
1826
|
* violations at AJV time.
|
|
1806
|
-
* - `expectedDurationSeconds
|
|
1827
|
+
* - `expectedDurationSeconds`, REQUIRED for probabilistic (drives
|
|
1807
1828
|
* TTL); advisory for deterministic.
|
|
1808
|
-
* - `precondition
|
|
1829
|
+
* - `precondition`, declarative filter consumed by `--all` fan-out,
|
|
1809
1830
|
* UI button gating, `sm actions show`.
|
|
1810
|
-
* - `expectedTools
|
|
1831
|
+
* - `expectedTools`, hint to Skill / CLI runners about expected
|
|
1811
1832
|
* tools (no normative enforcement in v0).
|
|
1812
|
-
* - `fanOutPolicy
|
|
1833
|
+
* - `fanOutPolicy`, `'per-node'` (default) vs `'batch'`.
|
|
1813
1834
|
*/
|
|
1814
1835
|
|
|
1815
1836
|
/**
|
|
@@ -1817,10 +1838,10 @@ interface IAnalyzer extends IExtensionBase {
|
|
|
1817
1838
|
* future write kinds (storage rows, plugin KV, etc.) can land additively
|
|
1818
1839
|
* without breaking consumers that only handle `kind: 'sidecar'`.
|
|
1819
1840
|
*
|
|
1820
|
-
* - `path
|
|
1841
|
+
* - `path`, absolute path to the `.sm` file the kernel must materialise
|
|
1821
1842
|
* the change into. Resolved by the Action from the node's absolute
|
|
1822
1843
|
* path via `sidecarPathFor()`.
|
|
1823
|
-
* - `changes
|
|
1844
|
+
* - `changes`, partial sidecar root used as a deep-merge patch (NOT a
|
|
1824
1845
|
* full replacement). Arrays REPLACE; objects RECURSE. Reason:
|
|
1825
1846
|
* sidecars are shared-write between skill-map core and plugins;
|
|
1826
1847
|
* a full replace would clobber `<plugin-id>:` namespaced blocks.
|
|
@@ -1833,7 +1854,7 @@ type TActionWrite = {
|
|
|
1833
1854
|
/**
|
|
1834
1855
|
* Result envelope returned by deterministic Actions. The `report` field
|
|
1835
1856
|
* carries the typed report payload (each Action declares its shape via
|
|
1836
|
-
* `reportSchemaRef`); `writes` is opt-in
|
|
1857
|
+
* `reportSchemaRef`); `writes` is opt-in, Actions that do not mutate
|
|
1837
1858
|
* persistent state simply omit it.
|
|
1838
1859
|
*/
|
|
1839
1860
|
interface IActionResult<TReport = unknown> {
|
|
@@ -1842,20 +1863,20 @@ interface IActionResult<TReport = unknown> {
|
|
|
1842
1863
|
}
|
|
1843
1864
|
/**
|
|
1844
1865
|
* Runtime context passed to a deterministic Action's `invoke()` method.
|
|
1845
|
-
* Minimal surface
|
|
1866
|
+
* Minimal surface, Actions stay pure (no IO inside `invoke`); the kernel
|
|
1846
1867
|
* materialises any returned `writes` after the call.
|
|
1847
1868
|
*
|
|
1848
|
-
* - `node
|
|
1869
|
+
* - `node`, the target `Node` the Action operates on. Open-by-design;
|
|
1849
1870
|
* batch / fan-out flows pick the matching nodes upstream.
|
|
1850
|
-
* - `nodeAbsolutePath
|
|
1871
|
+
* - `nodeAbsolutePath`, absolute path to the node's `.md` file on
|
|
1851
1872
|
* disk. The Action uses this to compute the sidecar path it returns
|
|
1852
1873
|
* in a `TActionWrite`. Surfaced separately from `node.path` (which is
|
|
1853
1874
|
* the relative scope-root form) so Actions never compose absolute
|
|
1854
1875
|
* paths from `node.path` themselves.
|
|
1855
|
-
* - `invoker
|
|
1876
|
+
* - `invoker`, identity of the caller; written into the sidecar's
|
|
1856
1877
|
* `audit.lastBumpedBy` when the Action chooses to. CLI invocations
|
|
1857
1878
|
* pass `'cli'`; plugin-driven invocations pass `'plugin:<plugin-id>'`.
|
|
1858
|
-
* - `now
|
|
1879
|
+
* - `now`, clock function; tests inject a deterministic source.
|
|
1859
1880
|
* Defaults to `() => new Date()` at the composition root.
|
|
1860
1881
|
*/
|
|
1861
1882
|
interface IActionContext {
|
|
@@ -1866,7 +1887,7 @@ interface IActionContext {
|
|
|
1866
1887
|
}
|
|
1867
1888
|
/**
|
|
1868
1889
|
* Declarative filter applied by `--all` fan-out, UI button gating, and
|
|
1869
|
-
* `sm actions show`. All fields optional
|
|
1890
|
+
* `sm actions show`. All fields optional, an empty precondition matches
|
|
1870
1891
|
* every node.
|
|
1871
1892
|
*/
|
|
1872
1893
|
interface IActionPrecondition {
|
|
@@ -1936,14 +1957,14 @@ interface IAction extends IExtensionBase {
|
|
|
1936
1957
|
/**
|
|
1937
1958
|
* Deterministic invocation entry point. OPTIONAL on the runtime
|
|
1938
1959
|
* contract for backward compatibility with the manifest-only era
|
|
1939
|
-
* (Decision #114)
|
|
1960
|
+
* (Decision #114), actions that ship for the future probabilistic
|
|
1940
1961
|
* runner / record path leave it absent and the kernel never calls it.
|
|
1941
1962
|
* Step 9.6.3 (Decision #125) introduces the first concrete consumer:
|
|
1942
1963
|
* the built-in `bump` Action implements `invoke()` and returns a
|
|
1943
1964
|
* `writes: [{ kind: 'sidecar', ... }]` payload that the kernel
|
|
1944
1965
|
* materialises through `ISidecarStore`.
|
|
1945
1966
|
*
|
|
1946
|
-
* Implementations MUST stay pure
|
|
1967
|
+
* Implementations MUST stay pure, no IO inside `invoke()`. The Action
|
|
1947
1968
|
* computes the patch and returns it; the kernel reads the on-disk
|
|
1948
1969
|
* sidecar, deep-merges, validates, and writes back inside its critical
|
|
1949
1970
|
* section.
|
|
@@ -1961,10 +1982,10 @@ interface IAction extends IExtensionBase {
|
|
|
1961
1982
|
*
|
|
1962
1983
|
* Two adjacent names live on the same instance:
|
|
1963
1984
|
*
|
|
1964
|
-
* - `formatId: string
|
|
1985
|
+
* - `formatId: string`, the manifest field consumed by the
|
|
1965
1986
|
* `--format <name>` CLI flag. The kernel's lookup is
|
|
1966
1987
|
* `formatters.find((f) => f.formatId === flag)`.
|
|
1967
|
-
* - `format(ctx) → string
|
|
1988
|
+
* - `format(ctx) → string`, the runtime method. Receives the full
|
|
1968
1989
|
* graph and returns the serialized output. Output MUST be
|
|
1969
1990
|
* byte-deterministic for the same input (the snapshot-test suite
|
|
1970
1991
|
* relies on this).
|
|
@@ -1978,6 +1999,16 @@ interface IFormatterContext {
|
|
|
1978
1999
|
nodes: Node[];
|
|
1979
2000
|
links: Link[];
|
|
1980
2001
|
issues: Issue[];
|
|
2002
|
+
/**
|
|
2003
|
+
* Full persisted scan, when the caller has it on hand. Optional so
|
|
2004
|
+
* existing formatters that only consume (nodes, links, issues) keep
|
|
2005
|
+
* working unchanged; formatters whose output mirrors a `ScanResult`
|
|
2006
|
+
* envelope (today: the built-in `json` formatter under
|
|
2007
|
+
* `built-in-plugins/formatters/json/`) read this to project the
|
|
2008
|
+
* canonical document verbatim. `undefined` when the caller has only
|
|
2009
|
+
* the three primary arrays (back-compat with older drivers).
|
|
2010
|
+
*/
|
|
2011
|
+
scanResult?: ScanResult;
|
|
1981
2012
|
}
|
|
1982
2013
|
interface IFormatter extends IExtensionBase {
|
|
1983
2014
|
kind: 'formatter';
|
|
@@ -1996,7 +2027,7 @@ interface IFormatter extends IExtensionBase {
|
|
|
1996
2027
|
* are notification (Slack on `job.completed`), integration glue (CI
|
|
1997
2028
|
* webhook on `job.failed`), and bookkeeping (per-extractor metrics).
|
|
1998
2029
|
*
|
|
1999
|
-
* The hookable trigger set is INTENTIONALLY SMALL
|
|
2030
|
+
* The hookable trigger set is INTENTIONALLY SMALL, ten events. Eight
|
|
2000
2031
|
* are pipeline-driven (emitted from inside `runScan`); two
|
|
2001
2032
|
* (`boot`, `shutdown`) are CLI-process-driven (emitted by the driving
|
|
2002
2033
|
* binary before / after the verb runs, fire-and-forget so
|
|
@@ -2020,16 +2051,16 @@ interface IFormatter extends IExtensionBase {
|
|
|
2020
2051
|
*
|
|
2021
2052
|
* Curated trigger set (per spec § A.11):
|
|
2022
2053
|
*
|
|
2023
|
-
* 0. `boot`
|
|
2024
|
-
* 1. `scan.started`
|
|
2025
|
-
* 2. `scan.completed`
|
|
2026
|
-
* 3. `extractor.completed`
|
|
2027
|
-
* 4. `analyzer.completed`
|
|
2028
|
-
* 5. `action.completed`
|
|
2029
|
-
* 6. `job.spawning`
|
|
2030
|
-
* 7. `job.completed`
|
|
2031
|
-
* 8. `job.failed`
|
|
2032
|
-
* 9. `shutdown`
|
|
2054
|
+
* 0. `boot` , once per CLI process, before verb routing.
|
|
2055
|
+
* 1. `scan.started` , pre-scan setup (one per scan).
|
|
2056
|
+
* 2. `scan.completed` , post-scan reaction (one per scan).
|
|
2057
|
+
* 3. `extractor.completed` , aggregated per-Extractor outputs.
|
|
2058
|
+
* 4. `analyzer.completed` , aggregated per-Rule outputs.
|
|
2059
|
+
* 5. `action.completed` , Action executed on a node.
|
|
2060
|
+
* 6. `job.spawning` , pre-spawn of runner subprocess.
|
|
2061
|
+
* 7. `job.completed` , most common trigger.
|
|
2062
|
+
* 8. `job.failed` , alerts, retry triggers.
|
|
2063
|
+
* 9. `shutdown` , once per CLI process, after the verb's
|
|
2033
2064
|
* exit code resolves and before
|
|
2034
2065
|
* `process.exit`.
|
|
2035
2066
|
*/
|
|
@@ -2117,7 +2148,7 @@ interface IHookContext {
|
|
|
2117
2148
|
* at load time: when none of the declared triggers carries a given
|
|
2118
2149
|
* filter field, the loader surfaces `invalid-manifest`. The current
|
|
2119
2150
|
* impl performs the basic enum check but defers full payload-shape
|
|
2120
|
-
* cross-validation to a follow-up
|
|
2151
|
+
* cross-validation to a follow-up, the dispatcher is permissive at
|
|
2121
2152
|
* runtime (an unknown field never matches → the hook simply never
|
|
2122
2153
|
* fires for that event, which is a correct interpretation of "filter
|
|
2123
2154
|
* by a field that doesn't exist").
|
|
@@ -2148,29 +2179,205 @@ interface IHook extends IExtensionBase {
|
|
|
2148
2179
|
* Hook entry point. Returns nothing; reactions are side effects.
|
|
2149
2180
|
* Errors are caught by the dispatcher (logged as `extension.error`,
|
|
2150
2181
|
* surfaced via `hook.failed` meta-event) and NEVER block the main
|
|
2151
|
-
* pipeline
|
|
2182
|
+
* pipeline, a buggy hook degrades gracefully.
|
|
2152
2183
|
*/
|
|
2153
2184
|
on(ctx: IHookContext): void | Promise<void>;
|
|
2154
2185
|
}
|
|
2155
2186
|
|
|
2156
2187
|
/**
|
|
2157
|
-
*
|
|
2188
|
+
* `ProgressEmitterPort`, emits progress events during long operations.
|
|
2189
|
+
*
|
|
2190
|
+
* Shape-only today. The full event catalog (`run.started`,
|
|
2191
|
+
* `job.claimed`, `model.delta`, etc.) is normative in
|
|
2192
|
+
* `spec/job-events.md`; this port carries an open `data` payload so
|
|
2193
|
+
* adapters can emit any documented event without type churn.
|
|
2194
|
+
*/
|
|
2195
|
+
interface ProgressEvent {
|
|
2196
|
+
type: string;
|
|
2197
|
+
timestamp: string;
|
|
2198
|
+
runId?: string;
|
|
2199
|
+
jobId?: string;
|
|
2200
|
+
data?: unknown;
|
|
2201
|
+
}
|
|
2202
|
+
type TProgressListener = (event: ProgressEvent) => void;
|
|
2203
|
+
interface ProgressEmitterPort {
|
|
2204
|
+
emit(event: ProgressEvent): void;
|
|
2205
|
+
subscribe(listener: TProgressListener): () => void;
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
/**
|
|
2209
|
+
* Per-node extractor invocation: build a fresh `IExtractorContext` for
|
|
2210
|
+
* each extractor, validate every emitted link / contribution against
|
|
2211
|
+
* the declared catalog, fold enrichment partials into per-`(node,
|
|
2212
|
+
* extractor)` records, and surface emit-time drops as
|
|
2213
|
+
* `extension.error` events.
|
|
2214
|
+
*
|
|
2215
|
+
* Also hosts the post-walk recompute helpers that re-derive
|
|
2216
|
+
* `linksOutCount` / `linksInCount` / `externalRefsCount` on every node
|
|
2217
|
+
* from the final merged link buffer, plus the `IExtractorRunRecord`
|
|
2218
|
+
* and `IEnrichmentRecord` types those records eventually persist as.
|
|
2219
|
+
*/
|
|
2220
|
+
|
|
2221
|
+
/**
|
|
2222
|
+
* Spec § A.9, runs to persist into `scan_extractor_runs`. One entry
|
|
2223
|
+
* per `(nodePath, qualifiedExtractorId)` pair the orchestrator decided
|
|
2224
|
+
* "this extractor is current for this body". Includes both freshly-run
|
|
2225
|
+
* pairs (extractor invoked this scan) and reused pairs (cached node, the
|
|
2226
|
+
* extractor's prior run still applies to the same body hash). Excludes
|
|
2227
|
+
* obsolete pairs, extractors that ran in the prior but are no longer
|
|
2228
|
+
* registered, so a replace-all persist drops them automatically.
|
|
2229
|
+
*/
|
|
2230
|
+
interface IExtractorRunRecord {
|
|
2231
|
+
nodePath: string;
|
|
2232
|
+
extractorId: string;
|
|
2233
|
+
bodyHashAtRun: string;
|
|
2234
|
+
ranAt: number;
|
|
2235
|
+
/**
|
|
2236
|
+
* sha256 of the canonical-form sidecar annotations the Extractor saw
|
|
2237
|
+
* at run time. Always populated (an absent sidecar canonicalises to
|
|
2238
|
+
* `{}` so the hash is stable). Used unconditionally by the cache
|
|
2239
|
+
* decision alongside `bodyHashAtRun`: a sidecar-only edit invalidates
|
|
2240
|
+
* the cached run for every applicable Extractor on that node.
|
|
2241
|
+
*/
|
|
2242
|
+
sidecarAnnotationsHashAtRun: string;
|
|
2243
|
+
}
|
|
2244
|
+
/**
|
|
2245
|
+
* Spec § A.8, universal enrichment layer.
|
|
2246
|
+
*
|
|
2247
|
+
* One entry per `(nodePath, qualifiedExtractorId)` pair an Extractor
|
|
2248
|
+
* produced via `ctx.enrichNode(...)` during the walk. Attribution is
|
|
2249
|
+
* preserved per-Extractor (rather than merged client-side as B.1 did)
|
|
2250
|
+
* so the persistence layer can:
|
|
2251
|
+
*
|
|
2252
|
+
* - upsert a single row per pair (stable PRIMARY KEY conflict on
|
|
2253
|
+
* re-extract);
|
|
2254
|
+
* - feed `mergeNodeWithEnrichments` with `enrichedAt`-sorted partials
|
|
2255
|
+
* for last-write-wins per field at read time.
|
|
2256
|
+
*
|
|
2257
|
+
* `value` is the cumulative merge across every `enrichNode` call that
|
|
2258
|
+
* Extractor made for this node within this scan, multiple
|
|
2259
|
+
* `ctx.enrichNode({...})` calls inside one `extract(ctx)` invocation
|
|
2260
|
+
* fold into a single row, but two different Extractors hitting the
|
|
2261
|
+
* same node yield two distinct rows.
|
|
2262
|
+
*
|
|
2263
|
+
* `isProbabilistic` is reserved: Extractors are deterministic-only, so
|
|
2264
|
+
* every record produced by the orchestrator sets it to `false`. The
|
|
2265
|
+
* field is kept on the record (and the row in `node_enrichments`) so a
|
|
2266
|
+
* future Action-issued enrichment can populate it without reshaping
|
|
2267
|
+
* the persistence contract, see spec `architecture.md`
|
|
2268
|
+
* §Extractor · enrichment layer.
|
|
2269
|
+
*/
|
|
2270
|
+
interface IEnrichmentRecord {
|
|
2271
|
+
nodePath: string;
|
|
2272
|
+
extractorId: string;
|
|
2273
|
+
bodyHashAtEnrichment: string;
|
|
2274
|
+
value: Partial<Node>;
|
|
2275
|
+
enrichedAt: number;
|
|
2276
|
+
isProbabilistic: boolean;
|
|
2277
|
+
}
|
|
2278
|
+
/**
|
|
2279
|
+
* Run a set of extractors against a single node, collecting their link
|
|
2280
|
+
* emissions and node-enrichment partials. Each extractor is invoked
|
|
2281
|
+
* exactly once with a fresh `IExtractorContext`. Caller decides what
|
|
2282
|
+
* to do with the returned arrays (push into per-scan buffers, write to
|
|
2283
|
+
* a focused refresh result, etc.).
|
|
2284
|
+
*
|
|
2285
|
+
* Exported so `cli/commands/refresh.ts` can reuse the same wiring it
|
|
2286
|
+
* needs for re-running a single extractor against a single node, the
|
|
2287
|
+
* pre-extraction code in `refresh.ts` was hand-duplicating this loop
|
|
2288
|
+
* (audit item V4).
|
|
2289
|
+
*
|
|
2290
|
+
* Within this call, multiple `enrichNode(partial)` calls from the same
|
|
2291
|
+
* extractor against the same node fold into one record (last-write-wins
|
|
2292
|
+
* per field), same contract as the in-scan path.
|
|
2293
|
+
*/
|
|
2294
|
+
declare function runExtractorsForNode(opts: {
|
|
2295
|
+
extractors: IExtractor[];
|
|
2296
|
+
node: Node;
|
|
2297
|
+
body: string;
|
|
2298
|
+
frontmatter: Record<string, unknown>;
|
|
2299
|
+
bodyHash: string;
|
|
2300
|
+
emitter: ProgressEmitterPort;
|
|
2301
|
+
/**
|
|
2302
|
+
* Spec § A.12, per-plugin `ctx.store` wrappers keyed by `pluginId`.
|
|
2303
|
+
* The map's lookup is per-extractor inside the loop, so callers that
|
|
2304
|
+
* don't track plugin storage can omit it; the resulting `ctx.store`
|
|
2305
|
+
* stays `undefined` (the existing contract).
|
|
2306
|
+
*/
|
|
2307
|
+
pluginStores?: ReadonlyMap<string, IPluginStore>;
|
|
2308
|
+
}): Promise<{
|
|
2309
|
+
internalLinks: Link[];
|
|
2310
|
+
externalLinks: Link[];
|
|
2311
|
+
enrichments: IEnrichmentRecord[];
|
|
2312
|
+
contributions: IContributionRecord[];
|
|
2313
|
+
}>;
|
|
2314
|
+
|
|
2315
|
+
/**
|
|
2316
|
+
* Rename + orphan classification per `spec/db-schema.md` §Rename
|
|
2317
|
+
* detection. Pure: takes the prior `ScanResult` and the current node
|
|
2318
|
+
* set, mutates the supplied `issues` array in place, and returns the
|
|
2319
|
+
* `RenameOp[]` the persistence layer must apply inside the same tx as
|
|
2320
|
+
* the scan zone replace-all.
|
|
2321
|
+
*/
|
|
2322
|
+
|
|
2323
|
+
/**
|
|
2324
|
+
* Confidence-tagged plan to repoint `state_*` references from one node
|
|
2325
|
+
* path to another. Emitted by the rename heuristic during `runScan` and
|
|
2326
|
+
* consumed by `persistScanResult` so the FK migration runs inside the
|
|
2327
|
+
* same transaction as the scan zone replace-all.
|
|
2328
|
+
*/
|
|
2329
|
+
interface RenameOp {
|
|
2330
|
+
from: string;
|
|
2331
|
+
to: string;
|
|
2332
|
+
confidence: 'high' | 'medium';
|
|
2333
|
+
}
|
|
2334
|
+
/**
|
|
2335
|
+
* Pure rename / orphan classification per `spec/db-schema.md` §Rename
|
|
2336
|
+
* detection. Mutates `issues` in place, caller passes the in-progress
|
|
2337
|
+
* issue list; returns the `RenameOp[]` for the persistence layer to
|
|
2338
|
+
* apply inside its tx.
|
|
2339
|
+
*
|
|
2340
|
+
* Pipeline (1-to-1: a `newPath` claimed by one stage cannot be reused
|
|
2341
|
+
* by another):
|
|
2342
|
+
*
|
|
2343
|
+
* 1. **High-confidence**: pair each `deletedPath` with a `newPath`
|
|
2344
|
+
* that has the same `bodyHash`. No issue, no prompt.
|
|
2345
|
+
* 2. **Medium-confidence (1:1)**: of the remaining deletions, pair
|
|
2346
|
+
* each with the *unique* unclaimed `newPath` that shares its
|
|
2347
|
+
* `frontmatterHash`. Emits `auto-rename-medium` (severity warn)
|
|
2348
|
+
* with `data: { from, to, confidence: 'medium' }`.
|
|
2349
|
+
* 3. **Ambiguous (N:1)**: when a single `newPath` has more than one
|
|
2350
|
+
* remaining frontmatter-matching candidate, emit ONE
|
|
2351
|
+
* `auto-rename-ambiguous` issue per `newPath`, listing all
|
|
2352
|
+
* candidates in `data.candidates`. NO migration.
|
|
2353
|
+
* 4. **Orphan**: every `deletedPath` left after steps 1-3 yields one
|
|
2354
|
+
* `orphan` issue (severity info) with `data: { path: <deletedPath> }`.
|
|
2355
|
+
*
|
|
2356
|
+
* Determinism: `deletedPaths` and `newPaths` are iterated in lex-asc
|
|
2357
|
+
* order so the same input always produces the same matches,
|
|
2358
|
+
* required for reproducible tests and conformance fixtures (the spec
|
|
2359
|
+
* does not prescribe an order, but stability is the obvious contract).
|
|
2360
|
+
*/
|
|
2361
|
+
declare function detectRenamesAndOrphans(prior: ScanResult, current: Node[], issues: Issue[]): RenameOp[];
|
|
2362
|
+
|
|
2363
|
+
/**
|
|
2364
|
+
* Scan orchestrator, runs the Provider → extractor → analyzer pipeline across
|
|
2158
2365
|
* every registered extension and emits `ProgressEmitterPort` events in
|
|
2159
2366
|
* canonical order. The callable extension set is injected via
|
|
2160
|
-
* `RunScanOptions.extensions
|
|
2367
|
+
* `RunScanOptions.extensions`, the Registry holds manifest metadata, the
|
|
2161
2368
|
* callable set holds the runtime instances the orchestrator actually
|
|
2162
2369
|
* invokes. Separating the two lets `sm plugins` and `sm help` introspect
|
|
2163
2370
|
* the graph without loading code.
|
|
2164
2371
|
*
|
|
2165
2372
|
* With zero registered extensions (or a callable set that carries none)
|
|
2166
|
-
* the pipeline still produces a valid zero-filled `ScanResult
|
|
2373
|
+
* the pipeline still produces a valid zero-filled `ScanResult`, the
|
|
2167
2374
|
* kernel-empty-boot invariant.
|
|
2168
2375
|
*
|
|
2169
2376
|
* Roots are validated up front: each entry of `RunScanOptions.roots`
|
|
2170
2377
|
* must exist on disk as a directory. The first failure throws a clear
|
|
2171
2378
|
* `Error` naming the offending path. This guards every caller (CLI,
|
|
2172
2379
|
* server, skill-agent) against silently producing a zero-filled
|
|
2173
|
-
* `ScanResult` when a Provider walks a non-existent path
|
|
2380
|
+
* `ScanResult` when a Provider walks a non-existent path, the bug
|
|
2174
2381
|
* that wiped a populated DB via `sm scan -- --dry-run` (clipanion's
|
|
2175
2382
|
* `--` made `--dry-run` a positional root that did not exist).
|
|
2176
2383
|
*
|
|
@@ -2180,7 +2387,7 @@ interface IHook extends IExtensionBase {
|
|
|
2180
2387
|
* `bodyHash` and `frontmatterHash` match. New / modified files run
|
|
2181
2388
|
* through the full extractor pipeline (including the external-url-counter
|
|
2182
2389
|
* which produces ephemeral pseudo-links). Rules ALWAYS run over the
|
|
2183
|
-
* fully merged graph
|
|
2390
|
+
* fully merged graph, issue state can change even for an unchanged node
|
|
2184
2391
|
* (e.g. a previously broken `references` link now resolves because a new
|
|
2185
2392
|
* node was added). For unchanged nodes the prior `externalRefsCount` is
|
|
2186
2393
|
* preserved as-is (the external pseudo-links were never persisted, so
|
|
@@ -2194,7 +2401,7 @@ interface IHook extends IExtensionBase {
|
|
|
2194
2401
|
* entry per `(node, extractor)` so attribution survives into the DB.
|
|
2195
2402
|
* Persisted into `node_enrichments` (A.8). The author-supplied
|
|
2196
2403
|
* frontmatter on `node.frontmatter` stays immutable from any Extractor
|
|
2197
|
-
*
|
|
2404
|
+
* , the enrichment layer is the only writable surface, and rules /
|
|
2198
2405
|
* formatters consume it via `mergeNodeWithEnrichments`.
|
|
2199
2406
|
* - `ctx.store` → plugin's own KV / dedicated tables (spec § A.12).
|
|
2200
2407
|
* Wired by the driving adapter via `RunScanOptions.pluginStores`,
|
|
@@ -2219,17 +2426,6 @@ interface IScanExtensions {
|
|
|
2219
2426
|
*/
|
|
2220
2427
|
hooks?: IHook[];
|
|
2221
2428
|
}
|
|
2222
|
-
/**
|
|
2223
|
-
* Confidence-tagged plan to repoint `state_*` references from one node
|
|
2224
|
-
* path to another. Emitted by the rename heuristic during `runScan` and
|
|
2225
|
-
* consumed by `persistScanResult` so the FK migration runs inside the
|
|
2226
|
-
* same transaction as the scan zone replace-all.
|
|
2227
|
-
*/
|
|
2228
|
-
interface RenameOp {
|
|
2229
|
-
from: string;
|
|
2230
|
-
to: string;
|
|
2231
|
-
confidence: 'high' | 'medium';
|
|
2232
|
-
}
|
|
2233
2429
|
interface RunScanOptions {
|
|
2234
2430
|
/**
|
|
2235
2431
|
* Filesystem roots to walk. Spec requires `minItems: 1`; passing an
|
|
@@ -2240,13 +2436,13 @@ interface RunScanOptions {
|
|
|
2240
2436
|
/** Runtime extension instances. Absent → empty pipeline. */
|
|
2241
2437
|
extensions?: IScanExtensions;
|
|
2242
2438
|
/**
|
|
2243
|
-
* Step 9.6.6
|
|
2439
|
+
* Step 9.6.6, runtime catalog of plugin-contributed annotation keys
|
|
2244
2440
|
* (the same shape `kernel.getRegisteredAnnotationKeys()` returns).
|
|
2245
2441
|
* Threaded into the rule pass so `core/unknown-field` can
|
|
2246
2442
|
* legitimise registered plugin namespaces / root keys without
|
|
2247
2443
|
* re-walking the manifests. Absent → empty catalog (every plugin
|
|
2248
2444
|
* key is treated as unknown). Built-in catalog from
|
|
2249
|
-
* `annotations.schema.json` is NOT included
|
|
2445
|
+
* `annotations.schema.json` is NOT included, that is hard-coded
|
|
2250
2446
|
* inside the rule.
|
|
2251
2447
|
*/
|
|
2252
2448
|
annotationContributions?: readonly IRegisteredAnnotationKey[];
|
|
@@ -2254,8 +2450,12 @@ interface RunScanOptions {
|
|
|
2254
2450
|
* Runtime catalog of plugin-contributed view contributions (the same
|
|
2255
2451
|
* shape `kernel.getRegisteredViewContributions()` returns). Threaded
|
|
2256
2452
|
* into the rule pass so:
|
|
2257
|
-
* - `core/
|
|
2258
|
-
*
|
|
2453
|
+
* - `core/contribution-orphan` can introspect the catalog
|
|
2454
|
+
* (read-only) and join it with the live node set to flag
|
|
2455
|
+
* dangling emissions. Slot catalog drift is NOT a scan concern,
|
|
2456
|
+
* it lives at load time and surfaces via `sm plugins doctor`
|
|
2457
|
+
* (the kernel rejects unknown slots as `invalid-manifest` first,
|
|
2458
|
+
* doctor catches the catalog-version-skew tail).
|
|
2259
2459
|
* - The orchestrator's per-rule emit closure can look up each
|
|
2260
2460
|
* declared `(contributionId → slot)` pairing for AJV
|
|
2261
2461
|
* payload validation.
|
|
@@ -2326,7 +2526,7 @@ interface RunScanOptions {
|
|
|
2326
2526
|
*/
|
|
2327
2527
|
strict?: boolean;
|
|
2328
2528
|
/**
|
|
2329
|
-
* Spec § A.9
|
|
2529
|
+
* Spec § A.9, fine-grained Extractor cache breadcrumbs from the
|
|
2330
2530
|
* prior scan. Shape: `Map<nodePath, Map<qualifiedExtractorId, IPriorExtractorRun>>`.
|
|
2331
2531
|
* Loaded from the `scan_extractor_runs` table by the CLI before
|
|
2332
2532
|
* invoking `runScan`; absent / empty for a fresh DB or an out-of-band
|
|
@@ -2346,11 +2546,11 @@ interface RunScanOptions {
|
|
|
2346
2546
|
*/
|
|
2347
2547
|
priorExtractorRuns?: Map<string, Map<string, IPriorExtractorRun>>;
|
|
2348
2548
|
/**
|
|
2349
|
-
* Spec § A.12
|
|
2549
|
+
* Spec § A.12, per-plugin storage wrappers exposed to extractors via
|
|
2350
2550
|
* `ctx.store`. Keyed by `pluginId`; absent / missing entry leaves
|
|
2351
2551
|
* `ctx.store` undefined for that extractor (the existing contract).
|
|
2352
2552
|
*
|
|
2353
|
-
* The kernel does not construct these
|
|
2553
|
+
* The kernel does not construct these, the driving adapter (CLI,
|
|
2354
2554
|
* future server) builds them with `makePluginStore` from
|
|
2355
2555
|
* `kernel/adapters/plugin-store.js` and threads them through. This
|
|
2356
2556
|
* keeps the orchestrator persistence-agnostic (the wrapper supplies
|
|
@@ -2367,7 +2567,7 @@ interface RunScanOptions {
|
|
|
2367
2567
|
* its own FS walk. The driving adapter (CLI, BFF) computes this
|
|
2368
2568
|
* inside its already-open storage transaction via
|
|
2369
2569
|
* `findOrphanJobFiles(jobsDir, await port.jobs.listReferencedFilePaths())`
|
|
2370
|
-
*
|
|
2570
|
+
* mirrors the `orphanSidecars` model where detection lives
|
|
2371
2571
|
* outside the rule and the rule only projects. Absent / empty when
|
|
2372
2572
|
* the caller has no jobs context (out-of-band tests, fresh DB,
|
|
2373
2573
|
* `--no-built-ins`).
|
|
@@ -2379,7 +2579,7 @@ interface RunScanOptions {
|
|
|
2379
2579
|
* through to `IAnalyzerContext.referenceablePaths` so the built-in
|
|
2380
2580
|
* `core/broken-ref` rule can suppress its `warn` for path-style
|
|
2381
2581
|
* links whose target lands in the set. Files are NOT walked by
|
|
2382
|
-
* the kernel
|
|
2582
|
+
* the kernel, the driving adapter populates the set before
|
|
2383
2583
|
* calling `runScan`. Absent / empty when the operator left
|
|
2384
2584
|
* `scan.referencePaths` unconfigured.
|
|
2385
2585
|
*/
|
|
@@ -2393,72 +2593,15 @@ interface RunScanOptions {
|
|
|
2393
2593
|
*/
|
|
2394
2594
|
cwd?: string;
|
|
2395
2595
|
}
|
|
2396
|
-
/**
|
|
2397
|
-
* Spec § A.9 — runs to persist into `scan_extractor_runs`. One entry
|
|
2398
|
-
* per `(nodePath, qualifiedExtractorId)` pair the orchestrator decided
|
|
2399
|
-
* "this extractor is current for this body". Includes both freshly-run
|
|
2400
|
-
* pairs (extractor invoked this scan) and reused pairs (cached node, the
|
|
2401
|
-
* extractor's prior run still applies to the same body hash). Excludes
|
|
2402
|
-
* obsolete pairs — extractors that ran in the prior but are no longer
|
|
2403
|
-
* registered — so a replace-all persist drops them automatically.
|
|
2404
|
-
*/
|
|
2405
|
-
interface IExtractorRunRecord {
|
|
2406
|
-
nodePath: string;
|
|
2407
|
-
extractorId: string;
|
|
2408
|
-
bodyHashAtRun: string;
|
|
2409
|
-
ranAt: number;
|
|
2410
|
-
/**
|
|
2411
|
-
* sha256 of the canonical-form sidecar annotations the Extractor saw
|
|
2412
|
-
* at run time. Always populated (an absent sidecar canonicalises to
|
|
2413
|
-
* `{}` so the hash is stable). Used unconditionally by the cache
|
|
2414
|
-
* decision alongside `bodyHashAtRun`: a sidecar-only edit invalidates
|
|
2415
|
-
* the cached run for every applicable Extractor on that node.
|
|
2416
|
-
*/
|
|
2417
|
-
sidecarAnnotationsHashAtRun: string;
|
|
2418
|
-
}
|
|
2419
|
-
/**
|
|
2420
|
-
* Spec § A.8 — universal enrichment layer.
|
|
2421
|
-
*
|
|
2422
|
-
* One entry per `(nodePath, qualifiedExtractorId)` pair an Extractor
|
|
2423
|
-
* produced via `ctx.enrichNode(...)` during the walk. Attribution is
|
|
2424
|
-
* preserved per-Extractor (rather than merged client-side as B.1 did)
|
|
2425
|
-
* so the persistence layer can:
|
|
2426
|
-
*
|
|
2427
|
-
* - upsert a single row per pair (stable PRIMARY KEY conflict on
|
|
2428
|
-
* re-extract);
|
|
2429
|
-
* - feed `mergeNodeWithEnrichments` with `enrichedAt`-sorted partials
|
|
2430
|
-
* for last-write-wins per field at read time.
|
|
2431
|
-
*
|
|
2432
|
-
* `value` is the cumulative merge across every `enrichNode` call that
|
|
2433
|
-
* Extractor made for this node within this scan — multiple
|
|
2434
|
-
* `ctx.enrichNode({...})` calls inside one `extract(ctx)` invocation
|
|
2435
|
-
* fold into a single row, but two different Extractors hitting the
|
|
2436
|
-
* same node yield two distinct rows.
|
|
2437
|
-
*
|
|
2438
|
-
* `isProbabilistic` is reserved: Extractors are deterministic-only, so
|
|
2439
|
-
* every record produced by the orchestrator sets it to `false`. The
|
|
2440
|
-
* field is kept on the record (and the row in `node_enrichments`) so a
|
|
2441
|
-
* future Action-issued enrichment can populate it without reshaping
|
|
2442
|
-
* the persistence contract — see spec `architecture.md`
|
|
2443
|
-
* §Extractor · enrichment layer.
|
|
2444
|
-
*/
|
|
2445
|
-
interface IEnrichmentRecord {
|
|
2446
|
-
nodePath: string;
|
|
2447
|
-
extractorId: string;
|
|
2448
|
-
bodyHashAtEnrichment: string;
|
|
2449
|
-
value: Partial<Node>;
|
|
2450
|
-
enrichedAt: number;
|
|
2451
|
-
isProbabilistic: boolean;
|
|
2452
|
-
}
|
|
2453
2596
|
/**
|
|
2454
2597
|
* Same as `runScan` but also returns the rename heuristic's `RenameOp[]`
|
|
2455
|
-
*
|
|
2598
|
+
* the high- and medium-confidence renames the persistence layer must
|
|
2456
2599
|
* apply to `state_*` rows inside the same tx as the scan zone replace-
|
|
2457
2600
|
* all (per `spec/db-schema.md` §Rename detection). Most callers want
|
|
2458
2601
|
* `runScan` (which returns just `ScanResult`); the CLI's `sm scan`
|
|
2459
2602
|
* uses this variant so it can hand the ops off to `persistScanResult`.
|
|
2460
2603
|
*
|
|
2461
|
-
* Also returns `extractorRuns
|
|
2604
|
+
* Also returns `extractorRuns`, the Spec § A.9 fine-grained cache
|
|
2462
2605
|
* breadcrumbs the CLI persists into `scan_extractor_runs` so the next
|
|
2463
2606
|
* incremental scan can decide per-(node, extractor) whether re-running
|
|
2464
2607
|
* is required.
|
|
@@ -2472,75 +2615,21 @@ declare function runScanWithRenames(_kernel: Kernel, options: RunScanOptions): P
|
|
|
2472
2615
|
freshlyRunTuples: ReadonlySet<string>;
|
|
2473
2616
|
}>;
|
|
2474
2617
|
declare function runScan(_kernel: Kernel, options: RunScanOptions): Promise<ScanResult>;
|
|
2618
|
+
|
|
2475
2619
|
/**
|
|
2476
|
-
*
|
|
2477
|
-
*
|
|
2478
|
-
*
|
|
2479
|
-
*
|
|
2480
|
-
*
|
|
2481
|
-
*
|
|
2482
|
-
* Exported so `cli/commands/refresh.ts` can reuse the same wiring it
|
|
2483
|
-
* needs for re-running a single extractor against a single node — the
|
|
2484
|
-
* pre-extraction code in `refresh.ts` was hand-duplicating this loop
|
|
2485
|
-
* (audit item V4).
|
|
2486
|
-
*
|
|
2487
|
-
* Within this call, multiple `enrichNode(partial)` calls from the same
|
|
2488
|
-
* extractor against the same node fold into one record (last-write-wins
|
|
2489
|
-
* per field) — same contract as the in-scan path.
|
|
2490
|
-
*/
|
|
2491
|
-
declare function runExtractorsForNode(opts: {
|
|
2492
|
-
extractors: IExtractor[];
|
|
2493
|
-
node: Node;
|
|
2494
|
-
body: string;
|
|
2495
|
-
frontmatter: Record<string, unknown>;
|
|
2496
|
-
bodyHash: string;
|
|
2497
|
-
emitter: ProgressEmitterPort;
|
|
2498
|
-
/**
|
|
2499
|
-
* Spec § A.12 — per-plugin `ctx.store` wrappers keyed by `pluginId`.
|
|
2500
|
-
* The map's lookup is per-extractor inside the loop, so callers that
|
|
2501
|
-
* don't track plugin storage can omit it; the resulting `ctx.store`
|
|
2502
|
-
* stays `undefined` (the existing contract).
|
|
2503
|
-
*/
|
|
2504
|
-
pluginStores?: ReadonlyMap<string, IPluginStore>;
|
|
2505
|
-
}): Promise<{
|
|
2506
|
-
internalLinks: Link[];
|
|
2507
|
-
externalLinks: Link[];
|
|
2508
|
-
enrichments: IEnrichmentRecord[];
|
|
2509
|
-
contributions: IContributionRecord[];
|
|
2510
|
-
}>;
|
|
2511
|
-
/**
|
|
2512
|
-
* Pure rename / orphan classification per `spec/db-schema.md` §Rename
|
|
2513
|
-
* detection. Mutates `issues` in place — caller passes the in-progress
|
|
2514
|
-
* issue list; returns the `RenameOp[]` for the persistence layer to
|
|
2515
|
-
* apply inside its tx.
|
|
2516
|
-
*
|
|
2517
|
-
* Pipeline (1-to-1: a `newPath` claimed by one stage cannot be reused
|
|
2518
|
-
* by another):
|
|
2519
|
-
*
|
|
2520
|
-
* 1. **High-confidence**: pair each `deletedPath` with a `newPath`
|
|
2521
|
-
* that has the same `bodyHash`. No issue, no prompt.
|
|
2522
|
-
* 2. **Medium-confidence (1:1)**: of the remaining deletions, pair
|
|
2523
|
-
* each with the *unique* unclaimed `newPath` that shares its
|
|
2524
|
-
* `frontmatterHash`. Emits `auto-rename-medium` (severity warn)
|
|
2525
|
-
* with `data: { from, to, confidence: 'medium' }`.
|
|
2526
|
-
* 3. **Ambiguous (N:1)**: when a single `newPath` has more than one
|
|
2527
|
-
* remaining frontmatter-matching candidate, emit ONE
|
|
2528
|
-
* `auto-rename-ambiguous` issue per `newPath`, listing all
|
|
2529
|
-
* candidates in `data.candidates`. NO migration.
|
|
2530
|
-
* 4. **Orphan**: every `deletedPath` left after steps 1-3 yields one
|
|
2531
|
-
* `orphan` issue (severity info) with `data: { path: <deletedPath> }`.
|
|
2532
|
-
*
|
|
2533
|
-
* Determinism: `deletedPaths` and `newPaths` are iterated in lex-asc
|
|
2534
|
-
* order so the same input always produces the same matches —
|
|
2535
|
-
* required for reproducible tests and conformance fixtures (the spec
|
|
2536
|
-
* does not prescribe an order, but stability is the obvious contract).
|
|
2620
|
+
* Node-construction helpers: hash a body, canonicalise frontmatter /
|
|
2621
|
+
* sidecar annotations, resolve the sidecar overlay for a given relative
|
|
2622
|
+
* path, and produce a fresh `Node` (validating its frontmatter on the
|
|
2623
|
+
* way out). Also hosts `mergeNodeWithEnrichments` + `IPersistedEnrichment`
|
|
2624
|
+
* the read-time merge of author frontmatter with the A.8 enrichment
|
|
2625
|
+
* layer.
|
|
2537
2626
|
*/
|
|
2538
|
-
|
|
2627
|
+
|
|
2539
2628
|
/**
|
|
2540
|
-
* Spec § A.8
|
|
2629
|
+
* Spec § A.8, produce the merged read-time view of a Node.
|
|
2541
2630
|
*
|
|
2542
2631
|
* Rules / `sm check` / `sm export` consume `node.frontmatter` directly
|
|
2543
|
-
* (deterministic CI-safe baseline
|
|
2632
|
+
* (deterministic CI-safe baseline, author intent, byte-stable). UI / future
|
|
2544
2633
|
* rules that opt into enrichment context call this helper to merge the
|
|
2545
2634
|
* author frontmatter with the live enrichment layer.
|
|
2546
2635
|
*
|
|
@@ -2554,18 +2643,18 @@ declare function detectRenamesAndOrphans(prior: ScanResult, current: Node[], iss
|
|
|
2554
2643
|
* belongs to the UI layer next to the value.
|
|
2555
2644
|
* 2. Sort the survivors by `enrichedAt` ASC so iteration order is
|
|
2556
2645
|
* "oldest first". This makes the spread merge below
|
|
2557
|
-
* last-write-wins per field
|
|
2646
|
+
* last-write-wins per field, the freshest Extractor's value
|
|
2558
2647
|
* pisar the older one for any conflicting key.
|
|
2559
2648
|
* 3. Spread-merge each row's `value` over `node.frontmatter`. The
|
|
2560
2649
|
* author's keys are the base; enrichment keys overlay them.
|
|
2561
2650
|
*
|
|
2562
|
-
* The returned object is a fresh shallow copy
|
|
2651
|
+
* The returned object is a fresh shallow copy, mutating it does not
|
|
2563
2652
|
* touch the caller's node. The original `node.frontmatter` reference
|
|
2564
2653
|
* remains accessible via `node.frontmatter` for callers that want the
|
|
2565
2654
|
* pristine author baseline.
|
|
2566
2655
|
*
|
|
2567
2656
|
* @param node Node to merge against; `node.frontmatter` is the base.
|
|
2568
|
-
* @param enrichments Per-(node, extractor) enrichment records
|
|
2657
|
+
* @param enrichments Per-(node, extractor) enrichment records, typically
|
|
2569
2658
|
* loaded via `loadNodeEnrichments(db, node.path)` or
|
|
2570
2659
|
* pre-filtered to this node by the caller.
|
|
2571
2660
|
* @param opts.includeStale When true, include rows flagged stale. Defaults
|
|
@@ -2595,7 +2684,7 @@ interface IPersistedEnrichment {
|
|
|
2595
2684
|
}
|
|
2596
2685
|
|
|
2597
2686
|
/**
|
|
2598
|
-
* In-memory `ProgressEmitterPort` adapter. No network, no DB
|
|
2687
|
+
* In-memory `ProgressEmitterPort` adapter. No network, no DB, just a
|
|
2599
2688
|
* synchronous fan-out to registered listeners. Used by the default scan
|
|
2600
2689
|
* orchestrator; the WebSocket-backed emitter that streams to
|
|
2601
2690
|
* the Web UI lands.
|
|
@@ -2612,7 +2701,7 @@ declare class InMemoryProgressEmitter implements ProgressEmitterPort {
|
|
|
2612
2701
|
*
|
|
2613
2702
|
* Wraps `chokidar` behind a small `IFsWatcher` interface so:
|
|
2614
2703
|
*
|
|
2615
|
-
* 1. The CLI command is impl-agnostic
|
|
2704
|
+
* 1. The CLI command is impl-agnostic, swapping chokidar for a
|
|
2616
2705
|
* different watcher later (Java? Rust port? a future `WatchPort`?)
|
|
2617
2706
|
* doesn't ripple into the command.
|
|
2618
2707
|
* 2. Debouncing, batching, and ignore-filter integration live in one
|
|
@@ -2661,28 +2750,37 @@ interface ICreateFsWatcherOptions {
|
|
|
2661
2750
|
/** Debounce window in milliseconds. `0` triggers `onBatch` synchronously per event. */
|
|
2662
2751
|
debounceMs: number;
|
|
2663
2752
|
/**
|
|
2664
|
-
* Optional ignore filter
|
|
2753
|
+
* Optional ignore filter, same instance the scan walker uses.
|
|
2665
2754
|
*
|
|
2666
2755
|
* Two shapes are accepted:
|
|
2667
2756
|
*
|
|
2668
|
-
* - **`IIgnoreFilter`** (the static one)
|
|
2757
|
+
* - **`IIgnoreFilter`** (the static one), captured by reference at
|
|
2669
2758
|
* construction. Use this when the filter never changes for the
|
|
2670
2759
|
* lifetime of the watcher (the typical CLI `sm watch` flow).
|
|
2671
2760
|
*
|
|
2672
|
-
* - **`() => IIgnoreFilter | undefined`** (a getter)
|
|
2761
|
+
* - **`() => IIgnoreFilter | undefined`** (a getter), re-evaluated
|
|
2673
2762
|
* on EVERY chokidar `ignored` predicate call. Use this when the
|
|
2674
|
-
* filter can change at runtime
|
|
2763
|
+
* filter can change at runtime, e.g. the BFF rebuilds it after
|
|
2675
2764
|
* a `.skillmapignore` or `.skill-map/settings.json` edit and
|
|
2676
2765
|
* wants chokidar to immediately respect the new patterns without
|
|
2677
2766
|
* tearing down and rebuilding the watcher. A getter that returns
|
|
2678
2767
|
* `undefined` disables ignore filtering for that call.
|
|
2679
2768
|
*/
|
|
2680
2769
|
ignoreFilter?: IIgnoreFilter | (() => IIgnoreFilter | undefined) | undefined;
|
|
2770
|
+
/**
|
|
2771
|
+
* Maximum directory traversal depth. `undefined` (default) walks the
|
|
2772
|
+
* tree recursively without bound; `0` limits the watch to the
|
|
2773
|
+
* literal `roots` entries (no descent), which is the right setting
|
|
2774
|
+
* when watching a directory only to catch changes to specific
|
|
2775
|
+
* top-level files (see `subscribeMeta` in `core/watcher/runtime.ts`).
|
|
2776
|
+
* Forwarded verbatim to chokidar's `depth` option.
|
|
2777
|
+
*/
|
|
2778
|
+
depth?: number;
|
|
2681
2779
|
/** Called once per debounced batch. Awaited; concurrent batches are serialised. */
|
|
2682
2780
|
onBatch: (batch: IWatchBatch) => void | Promise<void>;
|
|
2683
2781
|
/**
|
|
2684
2782
|
* Called when the underlying watcher surfaces an error. The watcher
|
|
2685
|
-
* stays open
|
|
2783
|
+
* stays open, callers decide whether to log, keep going, or close.
|
|
2686
2784
|
*/
|
|
2687
2785
|
onError?: (err: Error) => void;
|
|
2688
2786
|
}
|
|
@@ -2691,7 +2789,7 @@ interface ICreateFsWatcherOptions {
|
|
|
2691
2789
|
* returned `ready` promise resolves once chokidar's initial directory
|
|
2692
2790
|
* walk completes, at which point only NEW events fire `onBatch`.
|
|
2693
2791
|
*
|
|
2694
|
-
* The initial directory walk is deliberately silent
|
|
2792
|
+
* The initial directory walk is deliberately silent, we set
|
|
2695
2793
|
* `ignoreInitial: true`. The CLI runs a one-shot scan before flipping
|
|
2696
2794
|
* the watcher on, so re-emitting an `add` for every existing file
|
|
2697
2795
|
* would be redundant churn.
|
|
@@ -2699,20 +2797,20 @@ interface ICreateFsWatcherOptions {
|
|
|
2699
2797
|
declare function createChokidarWatcher(opts: ICreateFsWatcherOptions): IFsWatcher;
|
|
2700
2798
|
|
|
2701
2799
|
/**
|
|
2702
|
-
* Scan delta
|
|
2800
|
+
* Scan delta, pure comparison of two `ScanResult` snapshots. Drives
|
|
2703
2801
|
* `sm scan --compare-with <path>` and is the single place the kernel
|
|
2704
2802
|
* knows how to identify "the same" entity across two scans.
|
|
2705
2803
|
*
|
|
2706
2804
|
* **Identity contract** (mirrors decisions made at earlier sub-steps):
|
|
2707
2805
|
*
|
|
2708
2806
|
* - **Node**: `node.path`. The path is the only field stable across
|
|
2709
|
-
* edits
|
|
2807
|
+
* edits, every other Node field is content-derived (hashes, counts,
|
|
2710
2808
|
* denormalised frontmatter). Two nodes with the same path are the
|
|
2711
2809
|
* "same" node; differences are reported as a `changed` entry with
|
|
2712
2810
|
* a reason narrowing what diverged.
|
|
2713
2811
|
*
|
|
2714
2812
|
* - **Link**: `(source, target, kind, normalizedTrigger ?? '')`. This
|
|
2715
|
-
* mirrors the link-conflict rule and `sm show` aggregation
|
|
2813
|
+
* mirrors the link-conflict rule and `sm show` aggregation,
|
|
2716
2814
|
* two links with identical endpoints, kind, and (optional) trigger
|
|
2717
2815
|
* are the same link, even if emitted by different extractors. The
|
|
2718
2816
|
* `sources[]` union and confidence are NOT part of identity; they
|
|
@@ -2720,13 +2818,13 @@ declare function createChokidarWatcher(opts: ICreateFsWatcherOptions): IFsWatche
|
|
|
2720
2818
|
* "different" for delta purposes.
|
|
2721
2819
|
*
|
|
2722
2820
|
* - **Issue**: `(analyzerId, sorted nodeIds, message)`. Mirrors
|
|
2723
|
-
* `spec/job-events.md` §issue
|
|
2821
|
+
* `spec/job-events.md` §issue.*, same key → same issue, even when
|
|
2724
2822
|
* `data` / `severity` / `linkIndices` shift. A meaningful change in
|
|
2725
2823
|
* `message` (or a different set of node ids) is a different issue.
|
|
2726
2824
|
* This is the same key future job events will use; keep it aligned
|
|
2727
2825
|
* so consumers can reuse logic.
|
|
2728
2826
|
*
|
|
2729
|
-
* No "changed" bucket for links / issues
|
|
2827
|
+
* No "changed" bucket for links / issues, identity already captures
|
|
2730
2828
|
* everything that matters there. Nodes get a "changed" bucket because
|
|
2731
2829
|
* the path stays stable while the body / frontmatter rewrite, and that
|
|
2732
2830
|
* change is meaningful (formatters, summarisers, downstream consumers
|
|
@@ -2772,7 +2870,7 @@ declare function computeScanDelta(prior: ScanResult, current: ScanResult, compar
|
|
|
2772
2870
|
declare function isEmptyDelta(delta: IScanDelta): boolean;
|
|
2773
2871
|
|
|
2774
2872
|
/**
|
|
2775
|
-
* Export query
|
|
2873
|
+
* Export query, minimal filter language for `sm export <query>` (Step 8.3).
|
|
2776
2874
|
*
|
|
2777
2875
|
* Spec contract: `spec/cli-contract.md` line 190 says "Query syntax is
|
|
2778
2876
|
* implementation-defined pre-1.0". This module defines the v0.5.0 syntax.
|
|
@@ -2790,12 +2888,12 @@ declare function isEmptyDelta(delta: IScanDelta): boolean;
|
|
|
2790
2888
|
*
|
|
2791
2889
|
* **Filters**:
|
|
2792
2890
|
*
|
|
2793
|
-
* - `kind=skill` / `kind=skill,agent
|
|
2794
|
-
* - `has=issues
|
|
2891
|
+
* - `kind=skill` / `kind=skill,agent`, node kind whitelist.
|
|
2892
|
+
* - `has=issues`, node must appear in some issue's `nodeIds`. (Future
|
|
2795
2893
|
* expansion: `has=findings` / `has=summary` once Step 10 / 11 land.
|
|
2796
2894
|
* Unknown values are a parse error today; we'll ratchet up the
|
|
2797
2895
|
* accepted set additively.)
|
|
2798
|
-
* - `path=foo/*` / `path=.claude/agents
|
|
2896
|
+
* - `path=foo/*` / `path=.claude/agents/**`, POSIX glob over `node.path`.
|
|
2799
2897
|
* Supports `*` (any chars except `/`) and `**` (any chars including `/`).
|
|
2800
2898
|
*
|
|
2801
2899
|
* **Subset semantics** (`applyExportQuery`):
|
|
@@ -2804,7 +2902,7 @@ declare function isEmptyDelta(delta: IScanDelta): boolean;
|
|
|
2804
2902
|
* OR within values).
|
|
2805
2903
|
* - Links survive only when BOTH endpoints (`source` + `target`) belong
|
|
2806
2904
|
* to the filtered node set. A subset that includes "edges out to
|
|
2807
|
-
* unfiltered nodes" would be confusing
|
|
2905
|
+
* unfiltered nodes" would be confusing, the user asked for a focused
|
|
2808
2906
|
* subgraph, not its boundary. External-URL pseudo-links are already
|
|
2809
2907
|
* stripped by the orchestrator and never reach this layer.
|
|
2810
2908
|
* - Issues survive when ANY of the issue's `nodeIds` is in the filtered
|
|
@@ -2819,7 +2917,7 @@ interface IExportQuery {
|
|
|
2819
2917
|
/** Original query string echoed back so consumers can render the header. */
|
|
2820
2918
|
raw: string;
|
|
2821
2919
|
/**
|
|
2822
|
-
* Whitelist of node kinds (`node.kind` is open string
|
|
2920
|
+
* Whitelist of node kinds (`node.kind` is open string, built-in
|
|
2823
2921
|
* Claude catalog `skill` / `agent` / `command` / `hook` / `note`,
|
|
2824
2922
|
* plus whatever external Providers declare). The query parser does
|
|
2825
2923
|
* not validate values against a closed enum; an unknown kind simply
|
|
@@ -2846,16 +2944,16 @@ declare function applyExportQuery(scan: {
|
|
|
2846
2944
|
}, query: IExportQuery): IExportSubset;
|
|
2847
2945
|
|
|
2848
2946
|
/**
|
|
2849
|
-
* `scan_node_tags` adapter
|
|
2947
|
+
* `scan_node_tags` adapter, tags · dual-source persistence layer.
|
|
2850
2948
|
*
|
|
2851
2949
|
* One row per `(node_path, tag, source)` triple. Projected at persist
|
|
2852
2950
|
* time from BOTH `frontmatter.tags` (with `source='author'`) and
|
|
2853
2951
|
* `sidecar.annotations.tags` (with `source='user'`). The same tag
|
|
2854
|
-
* string MAY appear under both sources for the same node
|
|
2952
|
+
* string MAY appear under both sources for the same node, the PK
|
|
2855
2953
|
* accepts the pair; search returns the node once via DISTINCT, the
|
|
2856
2954
|
* UI renders both chips with their attribution.
|
|
2857
2955
|
*
|
|
2858
|
-
* Belongs to the `scan_*` family
|
|
2956
|
+
* Belongs to the `scan_*` family, replaced wholesale per scan.
|
|
2859
2957
|
* Cached nodes' tag rows are projected from the cached
|
|
2860
2958
|
* `node.frontmatter.tags` / `node.sidecar.annotations.tags` (both
|
|
2861
2959
|
* already in memory at persist time), so the rebuild is cheap
|
|
@@ -2880,17 +2978,17 @@ interface ITagRecord {
|
|
|
2880
2978
|
* Pure helpers for the "update available" notification feature.
|
|
2881
2979
|
*
|
|
2882
2980
|
* Three responsibilities:
|
|
2883
|
-
* - `fetchLatestVersion`
|
|
2981
|
+
* - `fetchLatestVersion` , query `https://registry.npmjs.org/<pkg>/latest`
|
|
2884
2982
|
* with `AbortController` + timeout. Throws on
|
|
2885
2983
|
* non-200 / parse failure / abort.
|
|
2886
|
-
* - `compareVersions`
|
|
2984
|
+
* - `compareVersions` , semver compare (-1 / 0 / 1). Pre-1.0 aware:
|
|
2887
2985
|
* treats prereleases via the standard rules
|
|
2888
2986
|
* (release > prerelease at the same triple).
|
|
2889
|
-
* - `isOutdated`
|
|
2987
|
+
* - `isOutdated` , sugar over `compareVersions` for the common
|
|
2890
2988
|
* "is `latest` strictly greater than `current`"
|
|
2891
2989
|
* check the banner runs against.
|
|
2892
2990
|
*
|
|
2893
|
-
* Pure kernel module
|
|
2991
|
+
* Pure kernel module, NO `process.env` reads, NO Node globals beyond the
|
|
2894
2992
|
* built-in `fetch` / `AbortController` (Node 22+). Every env / settings
|
|
2895
2993
|
* lookup happens in `src/cli/util/update-check-banner.ts`, the CLI-side
|
|
2896
2994
|
* adapter that owns side effects.
|
|
@@ -2898,21 +2996,21 @@ interface ITagRecord {
|
|
|
2898
2996
|
* The shared cache type (`IUpdateCheckCache`) is used by the storage
|
|
2899
2997
|
* helpers under `kernel/storage/update-check.ts` and by the BFF's
|
|
2900
2998
|
* `GET /api/update-status` projection. A second type
|
|
2901
|
-
* (`IUpdateStatus`) shapes the BFF response
|
|
2999
|
+
* (`IUpdateStatus`) shapes the BFF response, it merges `current`
|
|
2902
3000
|
* (from `VERSION`) into the cache so the UI can render without a
|
|
2903
|
-
* second lookup. Both stay flat
|
|
3001
|
+
* second lookup. Both stay flat, no nested objects, so JSON
|
|
2904
3002
|
* serialization is trivial.
|
|
2905
3003
|
*/
|
|
2906
3004
|
interface IUpdateCheckCache {
|
|
2907
3005
|
latestVersion: string;
|
|
2908
|
-
/** Epoch ms
|
|
3006
|
+
/** Epoch ms, when the registry was last successfully probed. */
|
|
2909
3007
|
checkedAt: number;
|
|
2910
|
-
/** Epoch ms
|
|
3008
|
+
/** Epoch ms, when the banner was last printed; null = never shown yet. */
|
|
2911
3009
|
shownAt: number | null;
|
|
2912
3010
|
}
|
|
2913
3011
|
|
|
2914
3012
|
/**
|
|
2915
|
-
* `PluginLoaderPort
|
|
3013
|
+
* `PluginLoaderPort`, discovers plugin directories and loads their
|
|
2916
3014
|
* extensions. The shape mirrors what the concrete loader actually
|
|
2917
3015
|
* exposes (see `kernel/adapters/plugin-loader.ts`); the port exists so
|
|
2918
3016
|
* the CLI consumes the abstract contract via `createPluginLoader(...)`
|
|
@@ -2935,12 +3033,12 @@ interface PluginLoaderPort {
|
|
|
2935
3033
|
discoverPaths(): string[];
|
|
2936
3034
|
/**
|
|
2937
3035
|
* Discover every plugin, attempt to load each, then apply the
|
|
2938
|
-
* cross-root id-collision pass. Never throws
|
|
3036
|
+
* cross-root id-collision pass. Never throws, failures are reported
|
|
2939
3037
|
* via `IDiscoveredPlugin.status`.
|
|
2940
3038
|
*/
|
|
2941
3039
|
discoverAndLoadAll(): Promise<IDiscoveredPlugin[]>;
|
|
2942
3040
|
/**
|
|
2943
|
-
* Load a single plugin from its directory. Never throws
|
|
3041
|
+
* Load a single plugin from its directory. Never throws, failure is
|
|
2944
3042
|
* reported via the returned `status`.
|
|
2945
3043
|
*/
|
|
2946
3044
|
loadOne(pluginPath: string): Promise<IDiscoveredPlugin>;
|
|
@@ -2948,7 +3046,7 @@ interface PluginLoaderPort {
|
|
|
2948
3046
|
|
|
2949
3047
|
/**
|
|
2950
3048
|
* Row-level filter for `port.scans.findNodes(...)` (driven by
|
|
2951
|
-
* `sm list`'s flags). All fields are optional
|
|
3049
|
+
* `sm list`'s flags). All fields are optional, an empty filter
|
|
2952
3050
|
* returns every node sorted by `path` asc.
|
|
2953
3051
|
*/
|
|
2954
3052
|
interface INodeFilter {
|
|
@@ -2972,7 +3070,7 @@ interface INodeFilter {
|
|
|
2972
3070
|
limit?: number;
|
|
2973
3071
|
}
|
|
2974
3072
|
/**
|
|
2975
|
-
* Bundled fetch for `port.scans.findNode(path)
|
|
3073
|
+
* Bundled fetch for `port.scans.findNode(path)`, one node and
|
|
2976
3074
|
* everything `sm show <path>` displays alongside it. Every field is
|
|
2977
3075
|
* computed from `scan_*` zone reads only; per-domain data (history,
|
|
2978
3076
|
* jobs, plugin enrichments) ships through other namespaces.
|
|
@@ -3006,7 +3104,7 @@ interface IPersistOptions {
|
|
|
3006
3104
|
enrichments?: IEnrichmentRecord[];
|
|
3007
3105
|
contributions?: IContributionRecord[];
|
|
3008
3106
|
/**
|
|
3009
|
-
* Phase 3 / View contribution system
|
|
3107
|
+
* Phase 3 / View contribution system, active runtime catalog of
|
|
3010
3108
|
* registered view contributions, keyed by qualified id
|
|
3011
3109
|
* `<pluginId>/<extensionId>/<contributionId>`. Passed to the
|
|
3012
3110
|
* `scan_contributions` upsert so the catalog sweep can drop rows
|
|
@@ -3018,10 +3116,10 @@ interface IPersistOptions {
|
|
|
3018
3116
|
*/
|
|
3019
3117
|
registeredContributionKeys?: ReadonlySet<string>;
|
|
3020
3118
|
/**
|
|
3021
|
-
* Phase 3 / View contribution system
|
|
3119
|
+
* Phase 3 / View contribution system, set of `(plugin, extension,
|
|
3022
3120
|
* node)` tuples where the extension actually RAN against that node
|
|
3023
3121
|
* in this scan. Format: `<pluginId>/<extensionId>/<nodePath>` (no
|
|
3024
|
-
* contribution-id segment
|
|
3122
|
+
* contribution-id segment, the sweep operates at the (plugin,
|
|
3025
3123
|
* extension, node) level and inspects the buffer to decide which
|
|
3026
3124
|
* contribution-ids survive).
|
|
3027
3125
|
*
|
|
@@ -3044,7 +3142,7 @@ interface IPersistOptions {
|
|
|
3044
3142
|
freshlyRunTuples?: ReadonlySet<string>;
|
|
3045
3143
|
}
|
|
3046
3144
|
/**
|
|
3047
|
-
* Issue row as the storage layer sees it
|
|
3145
|
+
* Issue row as the storage layer sees it, paired with its DB-assigned
|
|
3048
3146
|
* id so `port.issues.deleteById(id)` can target it inside a
|
|
3049
3147
|
* transaction. The runtime `Issue` shape (per `issue.schema.json`) does
|
|
3050
3148
|
* not carry `id` because the spec models issues as ephemeral findings
|
|
@@ -3055,6 +3153,54 @@ interface IIssueRow {
|
|
|
3055
3153
|
id: number;
|
|
3056
3154
|
issue: Issue;
|
|
3057
3155
|
}
|
|
3156
|
+
/**
|
|
3157
|
+
* Filter + pagination shape for `port.issues.list(...)`, driven by the
|
|
3158
|
+
* BFF's `/api/issues` route. Every field is optional, an empty filter
|
|
3159
|
+
* returns every issue ordered by `id` ASC (insertion order, stable
|
|
3160
|
+
* across pages so `offset` / `limit` paging is deterministic).
|
|
3161
|
+
*
|
|
3162
|
+
* The three semantic filters mirror `/api/issues`'s query params:
|
|
3163
|
+
*
|
|
3164
|
+
* - `severities`, narrowed list of `Severity` values. Empty / absent
|
|
3165
|
+
* matches every severity.
|
|
3166
|
+
* - `analyzerIds`, accepts qualified (`<plugin>/<id>`) AND short
|
|
3167
|
+
* (`<id>`) forms; the suffix-match semantics live in
|
|
3168
|
+
* `matchesAnalyzerFilter`. Each entry generates two SQL clauses
|
|
3169
|
+
* (`= ?` and `LIKE '%/' || ?`) ORed together so the filter remains
|
|
3170
|
+
* a single SQL pass with parameterised values, no string
|
|
3171
|
+
* interpolation. Empty / absent matches every analyzer id.
|
|
3172
|
+
* - `nodePath`, keeps issues whose `nodeIds` JSON array contains the
|
|
3173
|
+
* given path (correlated EXISTS over `json_each`). Absent / null
|
|
3174
|
+
* skips the filter.
|
|
3175
|
+
*
|
|
3176
|
+
* Pagination is mandatory; the route layer fills the defaults via
|
|
3177
|
+
* `parsePagination`. `total` in `IIssueListResult` reports the total
|
|
3178
|
+
* MATCHING the filters (not just the page slice) so the SPA can
|
|
3179
|
+
* surface a correct page-count without a second round-trip.
|
|
3180
|
+
*/
|
|
3181
|
+
interface IIssueListFilter {
|
|
3182
|
+
/**
|
|
3183
|
+
* Severity tokens to match. Typed as open `string` (not the
|
|
3184
|
+
* `Severity` union) so an unknown value from a URL query string
|
|
3185
|
+
* surfaces as a zero-match SQL query, not a kernel validation
|
|
3186
|
+
* error. The adapter parameterises each entry into the `IN(...)`
|
|
3187
|
+
* clause; unrecognised severities simply match no rows.
|
|
3188
|
+
*/
|
|
3189
|
+
severities?: readonly string[];
|
|
3190
|
+
analyzerIds?: readonly string[];
|
|
3191
|
+
nodePath?: string | null;
|
|
3192
|
+
offset: number;
|
|
3193
|
+
limit: number;
|
|
3194
|
+
}
|
|
3195
|
+
/**
|
|
3196
|
+
* Output of `port.issues.list(...)`. `items` is the page slice (length
|
|
3197
|
+
* ≤ `filter.limit`); `total` is the count of rows matching the filters
|
|
3198
|
+
* before pagination was applied.
|
|
3199
|
+
*/
|
|
3200
|
+
interface IIssueListResult {
|
|
3201
|
+
items: Issue[];
|
|
3202
|
+
total: number;
|
|
3203
|
+
}
|
|
3058
3204
|
/** Output of `port.jobs.pruneTerminal` / `listTerminalCandidates`. */
|
|
3059
3205
|
interface IPruneResult {
|
|
3060
3206
|
/** How many `state_jobs` rows were deleted (or would be, in dry-run). */
|
|
@@ -3101,7 +3247,7 @@ interface IMigrateNodeFksReport {
|
|
|
3101
3247
|
/**
|
|
3102
3248
|
* Collisions encountered when migrating any of the keyed-by-node
|
|
3103
3249
|
* `state_*` tables because a row already existed at the destination
|
|
3104
|
-
* PK. The pre-existing rows are preserved
|
|
3250
|
+
* PK. The pre-existing rows are preserved, the migrating rows are
|
|
3105
3251
|
* dropped (deleted from `fromPath` without a corresponding INSERT).
|
|
3106
3252
|
* One entry per dropped row, with the affected PK fields included
|
|
3107
3253
|
* for diagnostic output. `state_node_favorites` has no composite key
|
|
@@ -3184,7 +3330,7 @@ interface IPluginApplyResult {
|
|
|
3184
3330
|
|
|
3185
3331
|
/**
|
|
3186
3332
|
* Subset of `StoragePort` exposed inside a `transaction(fn)` callback.
|
|
3187
|
-
* Lifecycle methods are intentionally omitted
|
|
3333
|
+
* Lifecycle methods are intentionally omitted, a transaction that
|
|
3188
3334
|
* tries to `init()` the adapter mid-flight is a category error.
|
|
3189
3335
|
*
|
|
3190
3336
|
* Every callable in the subset MUST run on the same underlying
|
|
@@ -3205,7 +3351,7 @@ interface ITransactionalStorage {
|
|
|
3205
3351
|
* Upsert a batch of fresh enrichment records produced by an
|
|
3206
3352
|
* extractor pass. Composite PK is `(nodePath, extractorId)`;
|
|
3207
3353
|
* conflict → replace. Every row lands with `stale = 0` (the
|
|
3208
|
-
* caller just refreshed it; ROADMAP §B.10
|
|
3354
|
+
* caller just refreshed it; ROADMAP §B.10, staleness is
|
|
3209
3355
|
* computed downstream when the body hash changes again).
|
|
3210
3356
|
*/
|
|
3211
3357
|
upsertMany(records: IEnrichmentRecord[]): Promise<void>;
|
|
@@ -3229,23 +3375,23 @@ interface StoragePort {
|
|
|
3229
3375
|
* Persist a fresh `ScanResult` (replace-all on the scan zone).
|
|
3230
3376
|
* Called by `sm scan` after the orchestrator returns. The renames /
|
|
3231
3377
|
* extractor-runs / enrichments side bags ride along inside the
|
|
3232
|
-
* same transaction
|
|
3378
|
+
* same transaction, the call is atomic from the caller's view.
|
|
3233
3379
|
*/
|
|
3234
3380
|
persist(result: ScanResult, opts?: IPersistOptions): Promise<void>;
|
|
3235
3381
|
/**
|
|
3236
3382
|
* Hydrate the persisted `ScanResult`. Returns the snapshot the
|
|
3237
|
-
* scan zone holds today (including external-Provider kinds
|
|
3383
|
+
* scan zone holds today (including external-Provider kinds,
|
|
3238
3384
|
* `node.kind` is open string per `node.schema.json`).
|
|
3239
3385
|
*/
|
|
3240
3386
|
load(): Promise<ScanResult>;
|
|
3241
3387
|
/**
|
|
3242
|
-
* Spec § A.9
|
|
3388
|
+
* Spec § A.9, fine-grained extractor-runs cache breadcrumbs.
|
|
3243
3389
|
* Returns `Map<nodePath, Map<qualifiedExtractorId, IPriorExtractorRun>>`.
|
|
3244
3390
|
* Inner value carries `bodyHash` AND `sidecarAnnotationsHash`; both
|
|
3245
3391
|
* participate in the cache hit condition for every Extractor.
|
|
3246
3392
|
*/
|
|
3247
3393
|
loadExtractorRuns(): Promise<Map<string, Map<string, IPriorExtractorRun>>>;
|
|
3248
|
-
/** Universal enrichment layer
|
|
3394
|
+
/** Universal enrichment layer, every persisted `(node, extractor)` pair. */
|
|
3249
3395
|
loadNodeEnrichments(): Promise<IPersistedEnrichment[]>;
|
|
3250
3396
|
/**
|
|
3251
3397
|
* Row counts for `scan_nodes` / `scan_links` / `scan_issues`.
|
|
@@ -3261,7 +3407,7 @@ interface StoragePort {
|
|
|
3261
3407
|
findNode(path: string): Promise<INodeBundle | null>;
|
|
3262
3408
|
};
|
|
3263
3409
|
/**
|
|
3264
|
-
* Phase 3 / View contribution system
|
|
3410
|
+
* Phase 3 / View contribution system, read access to
|
|
3265
3411
|
* `scan_contributions`, plus the targeted purge used by
|
|
3266
3412
|
* `sm plugins disable` to clear stale rows immediately at toggle time.
|
|
3267
3413
|
* Bulk writes still happen exclusively via
|
|
@@ -3315,6 +3461,22 @@ interface StoragePort {
|
|
|
3315
3461
|
issues: {
|
|
3316
3462
|
/** Every issue from the latest scan, in insertion order. */
|
|
3317
3463
|
listAll(): Promise<Issue[]>;
|
|
3464
|
+
/**
|
|
3465
|
+
* Paginated, filtered issue read. Drives `/api/issues` (the BFF
|
|
3466
|
+
* route used to call `listAll()` and filter in JS, which loaded
|
|
3467
|
+
* every persisted issue into memory before paging; the audit
|
|
3468
|
+
* L6 fix pushes both filtering AND pagination into SQL).
|
|
3469
|
+
*
|
|
3470
|
+
* `total` in the result is the count matching the filters BEFORE
|
|
3471
|
+
* pagination is applied; `items` is the page slice (length ≤
|
|
3472
|
+
* `filter.limit`). Order is `id` ASC (insertion order, stable
|
|
3473
|
+
* across pages so the route's `offset` / `limit` is deterministic).
|
|
3474
|
+
*
|
|
3475
|
+
* Empty filters match every row (the route still passes
|
|
3476
|
+
* `offset` + `limit` so pagination always applies). See
|
|
3477
|
+
* `IIssueListFilter` for the per-field semantics.
|
|
3478
|
+
*/
|
|
3479
|
+
list(filter: IIssueListFilter): Promise<IIssueListResult>;
|
|
3318
3480
|
/**
|
|
3319
3481
|
* Issue rows whose runtime `Issue` shape passes `predicate`.
|
|
3320
3482
|
* `port.issues.findActive((i) => i.analyzerId === 'orphan')` is the
|
|
@@ -3364,19 +3526,19 @@ interface StoragePort {
|
|
|
3364
3526
|
* `path.resolve()`. The CLI's `sm job prune --orphan-files` flow
|
|
3365
3527
|
* pairs this set with `kernel/jobs/orphan-files.ts:findOrphanJobFiles`
|
|
3366
3528
|
* (which walks the directory) to compute the MD files on disk that
|
|
3367
|
-
* no row references
|
|
3529
|
+
* no row references, keeps the storage layer FS-free.
|
|
3368
3530
|
*/
|
|
3369
3531
|
listReferencedFilePaths(): Promise<Set<string>>;
|
|
3370
3532
|
};
|
|
3371
3533
|
/**
|
|
3372
3534
|
* Generic key/value preferences keyed by a stable string. Backs the
|
|
3373
|
-
* `config_preferences` table
|
|
3535
|
+
* `config_preferences` table, one row per `key`, `value_json` is a
|
|
3374
3536
|
* single JSON blob the caller serialises. Keys with the `_kernel.`
|
|
3375
3537
|
* prefix are reserved for kernel-managed entries (today: the
|
|
3376
3538
|
* update-check cache); user-set preferences land under unprefixed
|
|
3377
3539
|
* keys when those ship.
|
|
3378
3540
|
*
|
|
3379
|
-
* Read-only by design at the port level
|
|
3541
|
+
* Read-only by design at the port level, the only writer is the
|
|
3380
3542
|
* CLI's post-run hook (`cli/util/update-check-banner.ts`), which
|
|
3381
3543
|
* reaches the persistence helpers directly. The port surfaces the
|
|
3382
3544
|
* read so the BFF's `GET /api/update-status` projection can stay
|
|
@@ -3386,31 +3548,31 @@ interface StoragePort {
|
|
|
3386
3548
|
/**
|
|
3387
3549
|
* Load the update-check cache row. Returns `null` when the row
|
|
3388
3550
|
* is absent, malformed JSON, or fails the shape guard. Never
|
|
3389
|
-
* throws
|
|
3551
|
+
* throws, read failures degrade silently because the banner is
|
|
3390
3552
|
* a non-essential surface.
|
|
3391
3553
|
*/
|
|
3392
3554
|
loadUpdateCheckCache(): Promise<IUpdateCheckCache | null>;
|
|
3393
3555
|
/**
|
|
3394
3556
|
* Upsert the update-check cache row. Always overwrites the
|
|
3395
3557
|
* existing JSON blob in place. `updated_at` tracks wall-clock
|
|
3396
|
-
* now
|
|
3558
|
+
* now, separate from the embedded `checkedAt` field, which
|
|
3397
3559
|
* the caller controls.
|
|
3398
3560
|
*/
|
|
3399
3561
|
saveUpdateCheckCache(cache: IUpdateCheckCache): Promise<void>;
|
|
3400
3562
|
};
|
|
3401
3563
|
favorites: {
|
|
3402
3564
|
/**
|
|
3403
|
-
* Mark `path` as favorited. Idempotent
|
|
3565
|
+
* Mark `path` as favorited. Idempotent, a second call refreshes
|
|
3404
3566
|
* `favoritedAt` but does not error. The path is FK-semantic to
|
|
3405
3567
|
* `scan_nodes.path`; the route layer is responsible for confirming
|
|
3406
3568
|
* the path exists in the live scan before calling.
|
|
3407
3569
|
*/
|
|
3408
3570
|
set(path: string): Promise<void>;
|
|
3409
|
-
/** Drop the favorite row for `path`. Idempotent
|
|
3571
|
+
/** Drop the favorite row for `path`. Idempotent, no-op when absent. */
|
|
3410
3572
|
unset(path: string): Promise<void>;
|
|
3411
3573
|
/**
|
|
3412
3574
|
* Load every favorited path as a `Set<string>` ready for `O(1)`
|
|
3413
|
-
* membership checks. Used by the BFF's `/api/nodes` decorator
|
|
3575
|
+
* membership checks. Used by the BFF's `/api/nodes` decorator,
|
|
3414
3576
|
* one query per request, no SQL JOIN against `scan_nodes`.
|
|
3415
3577
|
*/
|
|
3416
3578
|
listPaths(): Promise<Set<string>>;
|
|
@@ -3484,7 +3646,7 @@ interface StoragePort {
|
|
|
3484
3646
|
}
|
|
3485
3647
|
|
|
3486
3648
|
/**
|
|
3487
|
-
* `FilesystemPort
|
|
3649
|
+
* `FilesystemPort`, walks roots, reads nodes, writes job files.
|
|
3488
3650
|
*
|
|
3489
3651
|
* Shape-only. The real adapter ships with the scan end-to-end pipeline.
|
|
3490
3652
|
*/
|
|
@@ -3505,7 +3667,7 @@ interface FilesystemPort {
|
|
|
3505
3667
|
}
|
|
3506
3668
|
|
|
3507
3669
|
/**
|
|
3508
|
-
* `RunnerPort
|
|
3670
|
+
* `RunnerPort`, executes an action against a rendered job file.
|
|
3509
3671
|
*
|
|
3510
3672
|
* Shape-only. `ClaudeCliRunner` + `MockRunner` land with the job subsystem
|
|
3511
3673
|
* (job subsystem + first summarizer).
|
|
@@ -3526,7 +3688,7 @@ interface RunnerPort {
|
|
|
3526
3688
|
}
|
|
3527
3689
|
|
|
3528
3690
|
/**
|
|
3529
|
-
* `LoggerPort
|
|
3691
|
+
* `LoggerPort`, structured logging port for the kernel.
|
|
3530
3692
|
*
|
|
3531
3693
|
* The kernel must NOT write to stdout/stderr directly. Anything that
|
|
3532
3694
|
* would historically have been a `console.log` / `console.error` goes
|
|
@@ -3537,7 +3699,7 @@ interface RunnerPort {
|
|
|
3537
3699
|
*
|
|
3538
3700
|
* trace < debug < info < warn < error < silent
|
|
3539
3701
|
*
|
|
3540
|
-
* `silent` is a sentinel for filtering only
|
|
3702
|
+
* `silent` is a sentinel for filtering only, it never appears as a
|
|
3541
3703
|
* `LogRecord.level`. Setting an adapter to `silent` disables every
|
|
3542
3704
|
* method.
|
|
3543
3705
|
*/
|
|
@@ -3574,7 +3736,7 @@ interface LoggerPort {
|
|
|
3574
3736
|
* `InMemoryProgressEmitter`: callers that don't care get a working
|
|
3575
3737
|
* implementation that does nothing.
|
|
3576
3738
|
*
|
|
3577
|
-
* Every method is intentionally empty
|
|
3739
|
+
* Every method is intentionally empty, that IS the contract of this
|
|
3578
3740
|
* class. We disable `no-empty-function` for the whole file because
|
|
3579
3741
|
* adding `// eslint-disable-next-line` to each method would be noise.
|
|
3580
3742
|
*/
|
|
@@ -3599,7 +3761,7 @@ declare class SilentLogger implements LoggerPort {
|
|
|
3599
3761
|
* side-channel concern.
|
|
3600
3762
|
* - The active impl is a pointer; the exported `log` is a stable
|
|
3601
3763
|
* proxy. Imports made before `configureLogger` runs still see the
|
|
3602
|
-
* new impl on every call
|
|
3764
|
+
* new impl on every call, no "captured stale logger" bugs.
|
|
3603
3765
|
*
|
|
3604
3766
|
* Tradeoffs accepted:
|
|
3605
3767
|
* - Tests must call `resetLogger()` (or replace the active impl) in
|
|
@@ -3614,7 +3776,7 @@ declare const log: LoggerPort;
|
|
|
3614
3776
|
declare function configureLogger(impl: LoggerPort): void;
|
|
3615
3777
|
/** Restore the default `SilentLogger`. Call from test teardown. */
|
|
3616
3778
|
declare function resetLogger(): void;
|
|
3617
|
-
/** Inspect the active logger. Test-only
|
|
3779
|
+
/** Inspect the active logger. Test-only, production code uses `log`. */
|
|
3618
3780
|
declare function getActiveLogger(): LoggerPort;
|
|
3619
3781
|
|
|
3620
3782
|
/**
|
|
@@ -3633,7 +3795,7 @@ declare function getActiveLogger(): LoggerPort;
|
|
|
3633
3795
|
* Error policy: a hook that throws is caught here, logged through a
|
|
3634
3796
|
* synthetic `extension.error` event with kind `hook-error`, and the
|
|
3635
3797
|
* caller continues. A buggy hook MUST NOT block the main pipeline (or
|
|
3636
|
-
* the CLI exit path)
|
|
3798
|
+
* the CLI exit path), that would invert the design intent (hooks
|
|
3637
3799
|
* REACT to events, they never steer them).
|
|
3638
3800
|
*
|
|
3639
3801
|
* The module lives under `kernel/extensions/` (alongside the `IHook`
|
|
@@ -3642,7 +3804,7 @@ declare function getActiveLogger(): LoggerPort;
|
|
|
3642
3804
|
* `analyzer.completed`, `action.completed`, `job.*`) and the CLI entry
|
|
3643
3805
|
* for the two CLI-process-driven triggers (`boot`, `shutdown`).
|
|
3644
3806
|
* Pulling the dispatcher out of the orchestrator keeps both consumers
|
|
3645
|
-
* symmetric
|
|
3807
|
+
* symmetric, same indexing, same filter semantics, same error
|
|
3646
3808
|
* policy.
|
|
3647
3809
|
*/
|
|
3648
3810
|
|
|
@@ -3671,20 +3833,20 @@ declare function makeEvent(type: string, data: unknown): ProgressEvent;
|
|
|
3671
3833
|
interface Kernel {
|
|
3672
3834
|
registry: Registry;
|
|
3673
3835
|
/**
|
|
3674
|
-
* Step 9.6.6
|
|
3836
|
+
* Step 9.6.6, read-only catalog of plugin-contributed annotation
|
|
3675
3837
|
* keys, keyed by `(pluginId, key)`. Populated at plugin-load time;
|
|
3676
3838
|
* pure read with no side effects. Built-in catalog (from
|
|
3677
3839
|
* `annotations.schema.json`) is NOT included here.
|
|
3678
3840
|
*/
|
|
3679
3841
|
getRegisteredAnnotationKeys: () => readonly IRegisteredAnnotationKey[];
|
|
3680
3842
|
/**
|
|
3681
|
-
* Internal
|
|
3843
|
+
* Internal, replace the frozen catalog. Called once by the
|
|
3682
3844
|
* plugin runtime composer after every plugin has loaded; consumers
|
|
3683
3845
|
* MUST treat the resulting array as immutable.
|
|
3684
3846
|
*/
|
|
3685
3847
|
setRegisteredAnnotationKeys: (entries: readonly IRegisteredAnnotationKey[]) => void;
|
|
3686
3848
|
/**
|
|
3687
|
-
* Step 11.x
|
|
3849
|
+
* Step 11.x, read-only catalog of plugin-contributed view
|
|
3688
3850
|
* contributions, keyed by `(pluginId, extensionId, contributionId)`.
|
|
3689
3851
|
* Populated at plugin-load time; pure read with no side effects.
|
|
3690
3852
|
* Mirror of `getRegisteredAnnotationKeys` for the view contribution
|
|
@@ -3693,7 +3855,7 @@ interface Kernel {
|
|
|
3693
3855
|
*/
|
|
3694
3856
|
getRegisteredViewContributions: () => readonly IRegisteredViewContribution[];
|
|
3695
3857
|
/**
|
|
3696
|
-
* Internal
|
|
3858
|
+
* Internal, replace the frozen view-contribution catalog. Called
|
|
3697
3859
|
* once by the plugin runtime composer after every plugin has loaded;
|
|
3698
3860
|
* consumers MUST treat the resulting array as immutable.
|
|
3699
3861
|
*/
|