@skill-map/cli 0.22.0 → 0.23.1
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 +1450 -686
- 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 +155 -60
- package/dist/index.js.map +1 -1
- package/dist/kernel/index.d.ts +383 -247
- package/dist/kernel/index.js +155 -60
- package/dist/kernel/index.js.map +1 -1
- package/dist/ui/{chunk-GXRWH2VL.js → chunk-2TPMJJYQ.js} +1 -1
- package/dist/ui/chunk-3RAME7PF.js +251 -0
- package/dist/ui/chunk-4BVLXZO3.js +61 -0
- package/dist/ui/{chunk-MPMBTIUR.js → chunk-BMAKIDAV.js} +30 -30
- package/dist/ui/chunk-C7PRRCVD.js +123 -0
- package/dist/ui/{chunk-VVOEPDQD.js → chunk-GJJZ5QH6.js} +1 -1
- package/dist/ui/chunk-I7EELB7M.js +1 -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-SCSYN7U2.js +1 -0
- package/dist/ui/index.html +3 -3
- package/dist/ui/main-WQA6J5V5.js +2 -0
- package/dist/ui/{styles-M2FETVAG.css → styles-ALBMEXCF.css} +1 -1
- package/package.json +4 -4
- package/dist/ui/chunk-25AWRVIC.js +0 -965
- package/dist/ui/chunk-GETTEQ3S.js +0 -123
- package/dist/ui/chunk-HC6PNQMW.js +0 -251
- package/dist/ui/chunk-HJHWJTFH.js +0 -1
- package/dist/ui/chunk-MF2M6GYF.js +0 -1
- package/dist/ui/chunk-V3SZQETX.js +0 -61
- package/dist/ui/main-Q2WC254P.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>,
|
|
@@ -768,7 +768,7 @@ interface IExtensionBase {
|
|
|
768
768
|
* typed DTOs from `@skill-map/spec` is deferred to a future iteration when a
|
|
769
769
|
* third consumer (real providers / extractors / rules) forces a single
|
|
770
770
|
* source of truth. Until then, both `ui/src/models/` and `src/kernel/types/`
|
|
771
|
-
* hand-curate their own local mirror
|
|
771
|
+
* hand-curate their own local mirror, the risk of drift is accepted at
|
|
772
772
|
* this scale (17 schemas) and flagged in the roadmap.
|
|
773
773
|
*/
|
|
774
774
|
|
|
@@ -778,7 +778,7 @@ interface IExtensionBase {
|
|
|
778
778
|
* tables with explicit migrations (mode `dedicated`). Absent = the plugin
|
|
779
779
|
* does not persist state at all.
|
|
780
780
|
*
|
|
781
|
-
* Optional output-schema declarations (spec § A.12
|
|
781
|
+
* Optional output-schema declarations (spec § A.12, opt-in correctness
|
|
782
782
|
* for plugin custom storage):
|
|
783
783
|
* - Mode `kv` → `schema` (single relative path). Validates the value
|
|
784
784
|
* written by `ctx.store.set(key, value)`.
|
|
@@ -802,13 +802,13 @@ type TPluginStorage = {
|
|
|
802
802
|
/**
|
|
803
803
|
* Toggle granularity for a plugin / built-in bundle.
|
|
804
804
|
*
|
|
805
|
-
* - `'bundle'`
|
|
805
|
+
* - `'bundle'` , the plugin id is the only enable/disable key. The whole
|
|
806
806
|
* bundle of extensions follows the toggle; the user cannot
|
|
807
807
|
* enable some extensions of the bundle and disable others.
|
|
808
808
|
* Default for plugins (and for the built-in `claude`
|
|
809
809
|
* bundle, where the provider and its kind-aware extractors
|
|
810
810
|
* form a coherent provider).
|
|
811
|
-
* - `'extension'
|
|
811
|
+
* - `'extension'`, each extension is independently toggle-able under its
|
|
812
812
|
* qualified id `<plugin-id>/<extension-id>`. Used for
|
|
813
813
|
* the built-in `core` bundle (every kernel built-in
|
|
814
814
|
* rule / formatter is removable per spec
|
|
@@ -845,7 +845,7 @@ interface IPluginManifest {
|
|
|
845
845
|
* Plugin user-configurable settings. Each entry picks an `input-type`
|
|
846
846
|
* from the closed catalog at
|
|
847
847
|
* `spec/schemas/input-types.schema.json#/$defs/InputTypeName`.
|
|
848
|
-
* The plugin author NEVER writes JSON Schema
|
|
848
|
+
* The plugin author NEVER writes JSON Schema, they pick `type` by
|
|
849
849
|
* name and supply per-type parameters. The kernel exposes resolved
|
|
850
850
|
* settings to extractors via `ctx.settings.<settingId>`; settings
|
|
851
851
|
* are read once at extractor invocation; changing a setting requires
|
|
@@ -866,7 +866,7 @@ interface IPluginManifest {
|
|
|
866
866
|
* against the installed `@skill-map/spec` version.
|
|
867
867
|
* - `invalid-manifest`: `plugin.json` missing, unparseable, failing AJV on
|
|
868
868
|
* the base manifest schema, OR the exported extension shape failed its
|
|
869
|
-
* kind-specific schema (per spec/architecture.md §Plugin discovery
|
|
869
|
+
* kind-specific schema (per spec/architecture.md §Plugin discovery,
|
|
870
870
|
* "AJV rejects unknown `slot` names with `invalid-manifest`").
|
|
871
871
|
* - `load-error`: manifest parsed but an extension module failed to import.
|
|
872
872
|
*/
|
|
@@ -874,21 +874,21 @@ interface IPluginManifest {
|
|
|
874
874
|
* Possible outcomes after the loader sees a plugin.json. Mirrors the
|
|
875
875
|
* `status` enum in `spec/schemas/plugins-registry.schema.json`.
|
|
876
876
|
*
|
|
877
|
-
* - `enabled`
|
|
877
|
+
* - `enabled` , manifest valid, specCompat satisfied, every
|
|
878
878
|
* extension imported and validated.
|
|
879
|
-
* - `disabled`
|
|
879
|
+
* - `disabled` , user-toggled off via `sm plugins disable` or
|
|
880
880
|
* `settings.json#/plugins/<id>/enabled`. Manifest
|
|
881
881
|
* is parsed and surfaced (so `sm plugins list`
|
|
882
882
|
* shows it), but extensions are not imported.
|
|
883
|
-
* - `incompatible-spec`
|
|
884
|
-
* - `invalid-manifest`
|
|
883
|
+
* - `incompatible-spec` , manifest parsed but `semver.satisfies` failed.
|
|
884
|
+
* - `invalid-manifest` , `plugin.json` missing, unparseable, AJV-fails,
|
|
885
885
|
* OR the directory name does not equal the
|
|
886
886
|
* manifest id (a cheap structural rule that
|
|
887
887
|
* rules out same-root collisions by construction:
|
|
888
888
|
* a filesystem cannot contain two siblings with
|
|
889
889
|
* the same name).
|
|
890
|
-
* - `load-error`
|
|
891
|
-
* - `id-collision`
|
|
890
|
+
* - `load-error` , manifest passed, an extension module failed.
|
|
891
|
+
* - `id-collision` , two plugins reachable from different roots
|
|
892
892
|
* (project + global, or any `--plugin-dir`
|
|
893
893
|
* combination) declared the same `id`. Both
|
|
894
894
|
* collided plugins receive this status; no
|
|
@@ -900,7 +900,7 @@ interface ILoadedExtension {
|
|
|
900
900
|
kind: ExtensionKind;
|
|
901
901
|
id: string;
|
|
902
902
|
/**
|
|
903
|
-
* Owning plugin namespace
|
|
903
|
+
* Owning plugin namespace, `manifest.id` of the `plugin.json` that
|
|
904
904
|
* declared this extension. Composed with `id` to form the qualified
|
|
905
905
|
* registry key `<pluginId>/<id>`. Per spec § A.6 the loader injects
|
|
906
906
|
* this from the manifest; an extension that hand-declares a
|
|
@@ -912,7 +912,7 @@ interface ILoadedExtension {
|
|
|
912
912
|
/** Raw module namespace as returned by the dynamic `import()`. */
|
|
913
913
|
module: unknown;
|
|
914
914
|
/**
|
|
915
|
-
* Runtime extension instance ready for the registry / orchestrator
|
|
915
|
+
* Runtime extension instance ready for the registry / orchestrator,
|
|
916
916
|
* the `default` export of `module` (or the module itself when no
|
|
917
917
|
* default), shallow-cloned with `pluginId` injected per spec § A.6.
|
|
918
918
|
*
|
|
@@ -926,7 +926,7 @@ interface ILoadedExtension {
|
|
|
926
926
|
interface IDiscoveredPlugin {
|
|
927
927
|
/** Absolute path to the plugin directory. */
|
|
928
928
|
path: string;
|
|
929
|
-
/** Plugin id
|
|
929
|
+
/** Plugin id, populated from the manifest if it parsed, else a path hint. */
|
|
930
930
|
id: string;
|
|
931
931
|
status: TPluginLoadStatus;
|
|
932
932
|
/** Only present when status === 'enabled' or 'incompatible-spec'. */
|
|
@@ -940,20 +940,20 @@ interface IDiscoveredPlugin {
|
|
|
940
940
|
*/
|
|
941
941
|
granularity?: TGranularity;
|
|
942
942
|
/**
|
|
943
|
-
* Runtime-only
|
|
943
|
+
* Runtime-only, never persisted, never spec-modeled.
|
|
944
944
|
*
|
|
945
|
-
* Spec § A.12
|
|
945
|
+
* Spec § A.12, opt-in JSON Schema validation for plugin custom storage.
|
|
946
946
|
* Populated by the loader when `manifest.storage.schemas` (Mode B) or
|
|
947
947
|
* `manifest.storage.schema` (Mode A) declares schema paths the loader
|
|
948
948
|
* successfully read and AJV-compiled. Consumed by the runtime store
|
|
949
949
|
* wrapper to validate `ctx.store.write(table, row)` (Mode B) and
|
|
950
950
|
* `ctx.store.set(key, value)` (Mode A) before persisting.
|
|
951
951
|
*
|
|
952
|
-
* Mode B layout
|
|
952
|
+
* Mode B layout, keyed by logical table name (without the
|
|
953
953
|
* `plugin_<normalizedId>_` prefix), matching the manifest's `schemas`
|
|
954
954
|
* map. Tables not present in the map accept any shape (permissive).
|
|
955
955
|
*
|
|
956
|
-
* Mode A layout
|
|
956
|
+
* Mode A layout, uses the sentinel key `__kv__` for the single
|
|
957
957
|
* value-shape schema. The sentinel survives the runtime contract change
|
|
958
958
|
* if Mode A ever grows multiple namespaces.
|
|
959
959
|
*
|
|
@@ -966,7 +966,7 @@ interface IDiscoveredPlugin {
|
|
|
966
966
|
reason?: string;
|
|
967
967
|
}
|
|
968
968
|
/**
|
|
969
|
-
* Runtime-only
|
|
969
|
+
* Runtime-only, a single AJV-compiled storage schema attached to a
|
|
970
970
|
* loaded plugin. The schema path (relative to the plugin directory) is
|
|
971
971
|
* preserved so error messages can name the offending file. `validate`
|
|
972
972
|
* is the AJV `ValidateFunction` itself: it returns `true` on shape
|
|
@@ -988,20 +988,20 @@ interface IPluginStorageSchema {
|
|
|
988
988
|
}
|
|
989
989
|
|
|
990
990
|
/**
|
|
991
|
-
* Plugin store wrappers
|
|
991
|
+
* Plugin store wrappers, runtime injection for `ctx.store` per spec
|
|
992
992
|
* § A.12 (opt-in `outputSchema` for plugin custom storage).
|
|
993
993
|
*
|
|
994
994
|
* Two shapes, mirroring the manifest's storage modes documented in
|
|
995
995
|
* `spec/plugin-kv-api.md`:
|
|
996
996
|
*
|
|
997
|
-
* - Mode A
|
|
997
|
+
* - Mode A, `KvStore.set(key, value)`. AJV-validates `value` against
|
|
998
998
|
* the schema declared by `manifest.storage.schema` (single
|
|
999
999
|
* value-shape) when present. Absent = permissive.
|
|
1000
|
-
* - Mode B
|
|
1000
|
+
* - Mode B, `DedicatedStore.write(table, row)`. AJV-validates `row`
|
|
1001
1001
|
* against the per-table schema declared in `manifest.storage.schemas`
|
|
1002
1002
|
* when present. Tables absent from the map accept any shape.
|
|
1003
1003
|
*
|
|
1004
|
-
* Both wrappers are storage-engine agnostic
|
|
1004
|
+
* Both wrappers are storage-engine agnostic, they accept a `persist`
|
|
1005
1005
|
* callback the caller supplies. The persistence side (SQLite, in-memory,
|
|
1006
1006
|
* mock) is the caller's concern; this wrapper's only job is the
|
|
1007
1007
|
* AJV gate. That separation lets the test suite exercise the validator
|
|
@@ -1010,7 +1010,7 @@ interface IPluginStorageSchema {
|
|
|
1010
1010
|
* unchanged.
|
|
1011
1011
|
*
|
|
1012
1012
|
* Universal validation (`emitLink` against `link.schema.json`,
|
|
1013
|
-
* `enrichNode` against `node.schema.json`) is unaffected
|
|
1013
|
+
* `enrichNode` against `node.schema.json`) is unaffected, it lives on
|
|
1014
1014
|
* the orchestrator side and runs regardless of the plugin's
|
|
1015
1015
|
* `outputSchema` opt-in.
|
|
1016
1016
|
*/
|
|
@@ -1036,7 +1036,7 @@ interface IDedicatedStorePersist {
|
|
|
1036
1036
|
* schema path and AJV errors; persistence is skipped on failure.
|
|
1037
1037
|
*
|
|
1038
1038
|
* `pluginId` is captured for diagnostics (the throw message names the
|
|
1039
|
-
* plugin). The wrapper does NOT itself scope by plugin id
|
|
1039
|
+
* plugin). The wrapper does NOT itself scope by plugin id, that is
|
|
1040
1040
|
* the persistence layer's job (the spec's `state_plugin_kvs` PK includes
|
|
1041
1041
|
* `pluginId` and the kernel-side adapter prepends it before write).
|
|
1042
1042
|
*/
|
|
@@ -1044,7 +1044,7 @@ interface IKvStoreWrapper {
|
|
|
1044
1044
|
set(key: string, value: unknown): Promise<void>;
|
|
1045
1045
|
}
|
|
1046
1046
|
/**
|
|
1047
|
-
* 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
|
|
1048
1048
|
* (`kv`) returns a `set(key, value)` surface; Mode B (`dedicated`) returns
|
|
1049
1049
|
* `write(table, row)`. Plugin authors narrow at the call site based on
|
|
1050
1050
|
* the storage mode declared in their `plugin.json`.
|
|
@@ -1058,7 +1058,7 @@ declare function makeKvStoreWrapper(opts: {
|
|
|
1058
1058
|
/**
|
|
1059
1059
|
* Mode B wrapper. `write(table, row)` AJV-validates `row` against
|
|
1060
1060
|
* `storageSchemas[table]` when declared, then forwards to `persist`.
|
|
1061
|
-
* Tables absent from the map are permissive
|
|
1061
|
+
* Tables absent from the map are permissive, the wrapper forwards
|
|
1062
1062
|
* straight to `persist` without validation.
|
|
1063
1063
|
*
|
|
1064
1064
|
* The wrapper accepts the full `storageSchemas` map (rather than a
|
|
@@ -1088,7 +1088,7 @@ declare function makePluginStore(opts: {
|
|
|
1088
1088
|
}): IPluginStore | undefined;
|
|
1089
1089
|
|
|
1090
1090
|
/**
|
|
1091
|
-
* `scan_contributions` adapter
|
|
1091
|
+
* `scan_contributions` adapter, replace-all writer used by
|
|
1092
1092
|
* `persistScanResult`, plus read helpers consumed by the BFF
|
|
1093
1093
|
* (`/api/contributions/...`) and rules (`core/contribution-orphan`).
|
|
1094
1094
|
*
|
|
@@ -1101,7 +1101,7 @@ declare function makePluginStore(opts: {
|
|
|
1101
1101
|
* scan is a fresh snapshot, so prior rows are deleted before insert.
|
|
1102
1102
|
* Wrapped in the same transaction `persistScanResult` opens.
|
|
1103
1103
|
*
|
|
1104
|
-
* The rename heuristic does NOT need to migrate `node_path` here
|
|
1104
|
+
* The rename heuristic does NOT need to migrate `node_path` here,
|
|
1105
1105
|
* because of replace-all, every contribution is re-emitted on the new
|
|
1106
1106
|
* path automatically. Keeping the rename path lighter than `state_*`
|
|
1107
1107
|
* (which IS rename-migrated because state survives across scans).
|
|
@@ -1121,7 +1121,7 @@ interface IContributionRecord {
|
|
|
1121
1121
|
contributionId: string;
|
|
1122
1122
|
/**
|
|
1123
1123
|
* Closed enum value mirroring `view-slots.schema.json#/$defs/SlotName`.
|
|
1124
|
-
* Persisted as TEXT (no SQL CHECK by design
|
|
1124
|
+
* Persisted as TEXT (no SQL CHECK by design, see migration comment).
|
|
1125
1125
|
*/
|
|
1126
1126
|
slot: string;
|
|
1127
1127
|
/** Already-validated payload. Serialised via `JSON.stringify` at write. */
|
|
@@ -1145,7 +1145,7 @@ interface IPersistedContribution {
|
|
|
1145
1145
|
}
|
|
1146
1146
|
|
|
1147
1147
|
/**
|
|
1148
|
-
* `loadScanResult
|
|
1148
|
+
* `loadScanResult`, driving inverse of `persistScanResult`. Reads the
|
|
1149
1149
|
* `scan_*` tables and reconstructs a `ScanResult` shape so the
|
|
1150
1150
|
* orchestrator can run an incremental scan (`sm scan --changed`) on
|
|
1151
1151
|
* top of a prior snapshot.
|
|
@@ -1158,7 +1158,7 @@ interface IPersistedContribution {
|
|
|
1158
1158
|
*
|
|
1159
1159
|
* **Documented omission**: external pseudo-links (those whose target is
|
|
1160
1160
|
* an `http://` / `https://` URL emitted by the external-url-counter
|
|
1161
|
-
* extractor) are NEVER persisted to `scan_links
|
|
1161
|
+
* extractor) are NEVER persisted to `scan_links`, only their per-node
|
|
1162
1162
|
* count survives in `scan_nodes.external_refs_count`. Therefore the
|
|
1163
1163
|
* `result.links` returned by `loadScanResult` contains only internal
|
|
1164
1164
|
* graph links, and `node.externalRefsCount` is the authoritative count
|
|
@@ -1185,11 +1185,11 @@ interface IPersistedContribution {
|
|
|
1185
1185
|
* `durationMs`; the three count fields derive from row counts.
|
|
1186
1186
|
*
|
|
1187
1187
|
* Both branches keep `nodesCount` / `linksCount` / `issuesCount` derived
|
|
1188
|
-
* from `COUNT(*)` of the loaded rows
|
|
1188
|
+
* from `COUNT(*)` of the loaded rows, never persisted, always recomputed.
|
|
1189
1189
|
*/
|
|
1190
1190
|
|
|
1191
1191
|
/**
|
|
1192
|
-
* Spec § A.9
|
|
1192
|
+
* Spec § A.9, load the fine-grained Extractor cache as a per-node map
|
|
1193
1193
|
* from qualified extractor id (`<pluginId>/<id>`) to the run-time
|
|
1194
1194
|
* hashes the extractor recorded on its last run. Empty map is the
|
|
1195
1195
|
* default when the table is empty (fresh DB, never-scanned scope, or
|
|
@@ -1212,14 +1212,14 @@ interface IPriorExtractorRun {
|
|
|
1212
1212
|
*
|
|
1213
1213
|
* Why a wrapper instead of exposing `ignore` directly:
|
|
1214
1214
|
*
|
|
1215
|
-
* 1. Single-source defaults
|
|
1215
|
+
* 1. Single-source defaults, `src/config/defaults/skillmapignore` is
|
|
1216
1216
|
* the canonical default list, loaded once at module init (or at
|
|
1217
1217
|
* explicit build time, depending on bundling). The runtime never
|
|
1218
1218
|
* re-reads it per scan.
|
|
1219
|
-
* 2. Stable interface
|
|
1219
|
+
* 2. Stable interface, Providers and the orchestrator depend on a
|
|
1220
1220
|
* minimal `IIgnoreFilter` shape, so the underlying library can be
|
|
1221
1221
|
* swapped without touching every consumer.
|
|
1222
|
-
* 3. Path normalization
|
|
1222
|
+
* 3. Path normalization, every consumer passes the path RELATIVE to
|
|
1223
1223
|
* the scan root (POSIX separators); the wrapper guarantees that
|
|
1224
1224
|
* contract before delegating to `ignore`.
|
|
1225
1225
|
*/
|
|
@@ -1233,6 +1233,33 @@ interface IIgnoreFilter {
|
|
|
1233
1233
|
ignores(relativePath: string): boolean;
|
|
1234
1234
|
}
|
|
1235
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
|
+
|
|
1236
1263
|
/**
|
|
1237
1264
|
* Provider runtime contract. Walks filesystem roots and emits raw node
|
|
1238
1265
|
* records; classification maps path conventions to a node kind.
|
|
@@ -1267,6 +1294,14 @@ interface IRawNode {
|
|
|
1267
1294
|
frontmatterRaw: string;
|
|
1268
1295
|
/** Parsed frontmatter, or `{}` when absent / unparseable. */
|
|
1269
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[];
|
|
1270
1305
|
}
|
|
1271
1306
|
/**
|
|
1272
1307
|
* One entry in a Provider's `kinds` map. Declares both the per-kind
|
|
@@ -1322,7 +1357,7 @@ interface IProviderKind {
|
|
|
1322
1357
|
* intent (label + base color, optional dark variant + emoji + icon);
|
|
1323
1358
|
* the UI derives `bg`/`fg` tints per theme via a deterministic helper
|
|
1324
1359
|
* and reads the registry from the `kindRegistry` field embedded in REST
|
|
1325
|
-
* envelopes. Single source of truth for what a kind looks like
|
|
1360
|
+
* envelopes. Single source of truth for what a kind looks like, the
|
|
1326
1361
|
* UI never hardcodes presentation for a built-in kind.
|
|
1327
1362
|
*/
|
|
1328
1363
|
interface IProviderKindUi {
|
|
@@ -1398,12 +1433,12 @@ interface IProvider extends IExtensionBase {
|
|
|
1398
1433
|
* per-kind schemas compile, so cross-file `$ref` resolution succeeds.
|
|
1399
1434
|
*
|
|
1400
1435
|
* Use case: when several kinds share a common base (e.g. Anthropic's
|
|
1401
|
-
* merged skill / command frontmatter
|
|
1436
|
+
* merged skill / command frontmatter, both extend a shared
|
|
1402
1437
|
* `skill-base.schema.json`), the Provider declares the base here so
|
|
1403
1438
|
* `skill.schema.json` and `command.schema.json` can `$ref` it without
|
|
1404
1439
|
* duplicating fields.
|
|
1405
1440
|
*
|
|
1406
|
-
* Runtime-only
|
|
1441
|
+
* Runtime-only, does NOT appear in the spec's `provider.schema.json`
|
|
1407
1442
|
* manifest. Manifest-validated schemas remain the per-kind ones in
|
|
1408
1443
|
* `kinds[<kind>].schema`; auxiliary schemas are an implementation
|
|
1409
1444
|
* concern of how the runtime composes those.
|
|
@@ -1421,7 +1456,7 @@ interface IProvider extends IExtensionBase {
|
|
|
1421
1456
|
* so the most common Provider shape needs zero configuration.
|
|
1422
1457
|
*
|
|
1423
1458
|
* Precedence: when both `walk()` (runtime field) and `read` are
|
|
1424
|
-
* declared, `walk()` wins
|
|
1459
|
+
* declared, `walk()` wins, `read` is ignored. The escape-hatch
|
|
1425
1460
|
* relationship is intentional: most Providers should use `read`;
|
|
1426
1461
|
* Providers with non-standard discovery requirements (custom file
|
|
1427
1462
|
* naming, multi-pass walks, dynamic ignore logic) implement `walk()`
|
|
@@ -1438,7 +1473,7 @@ interface IProvider extends IExtensionBase {
|
|
|
1438
1473
|
* Non-matching files are silently skipped. Unreadable files produce
|
|
1439
1474
|
* a diagnostic via the emitter but do not abort the walk.
|
|
1440
1475
|
*
|
|
1441
|
-
* `options.ignoreFilter
|
|
1476
|
+
* `options.ignoreFilter`, when supplied, the Provider MUST
|
|
1442
1477
|
* skip every directory and file whose path-relative-to-root the
|
|
1443
1478
|
* filter reports as ignored. Providers MAY also keep their own
|
|
1444
1479
|
* hard-coded skip list (e.g. `.git`) as a defensive measure, but the
|
|
@@ -1446,14 +1481,14 @@ interface IProvider extends IExtensionBase {
|
|
|
1446
1481
|
*
|
|
1447
1482
|
* Optional. When omitted, the Provider MUST declare `read` (or rely
|
|
1448
1483
|
* on the default config). The orchestrator never calls `walk()`
|
|
1449
|
-
* directly
|
|
1484
|
+
* directly, it goes through `resolveProviderWalk(provider)` which
|
|
1450
1485
|
* picks `walk` over `read`.
|
|
1451
1486
|
*/
|
|
1452
1487
|
walk?(roots: string[], options?: {
|
|
1453
1488
|
ignoreFilter?: IIgnoreFilter;
|
|
1454
1489
|
}): AsyncIterable<IRawNode>;
|
|
1455
1490
|
/**
|
|
1456
|
-
* Given a path and its parsed frontmatter, decide the node kind
|
|
1491
|
+
* Given a path and its parsed frontmatter, decide the node kind, or
|
|
1457
1492
|
* `null` to disclaim the file. The classifier is called after walk()
|
|
1458
1493
|
* yields; with multiple Providers active, every Provider walks every
|
|
1459
1494
|
* file matching its `read.extensions`, so each Provider MUST disclaim
|
|
@@ -1500,18 +1535,18 @@ interface IProviderReadConfig {
|
|
|
1500
1535
|
* Extractors are deterministic-only. They run synchronously inside the
|
|
1501
1536
|
* scan loop; LLM-driven enrichment of a node is an Action concern, not
|
|
1502
1537
|
* an Extractor concern. The Extractor context therefore exposes no
|
|
1503
|
-
* `RunnerPort
|
|
1538
|
+
* `RunnerPort`, see spec `architecture.md` §Execution modes.
|
|
1504
1539
|
*
|
|
1505
1540
|
* Output channels (all on the context):
|
|
1506
1541
|
*
|
|
1507
|
-
* - `ctx.emitLink(link)
|
|
1542
|
+
* - `ctx.emitLink(link)`, persist a link in the kernel's `links` table.
|
|
1508
1543
|
* Validated against `emitsLinkKinds` before insertion; an off-contract
|
|
1509
1544
|
* kind drops the link and surfaces an `extension.error` event.
|
|
1510
|
-
* - `ctx.enrichNode(partial)
|
|
1545
|
+
* - `ctx.enrichNode(partial)`, merge canonical, kernel-curated properties
|
|
1511
1546
|
* onto the node. Strictly separate from the author-supplied frontmatter
|
|
1512
1547
|
* (the latter remains immutable and survives verbatim). Persistence
|
|
1513
1548
|
* is spec'd in § A.8.
|
|
1514
|
-
* - `ctx.store
|
|
1549
|
+
* - `ctx.store`, plugin-scoped persistence. Present only when the
|
|
1515
1550
|
* plugin declares `storage.mode` in `plugin.json`; shape depends on the
|
|
1516
1551
|
* mode (`KvStore` for mode A, scoped `Database` for mode B). See
|
|
1517
1552
|
* `plugin-kv-api.md` for the contract.
|
|
@@ -1550,7 +1585,7 @@ interface IExtractorCallbacks {
|
|
|
1550
1585
|
* extension-local Record key declared under
|
|
1551
1586
|
* `extension.viewContributions[<contributionId>]`; the second is a
|
|
1552
1587
|
* payload that conforms to the slot's payload schema in
|
|
1553
|
-
* `spec/schemas/view-slots.schema.json#/$defs/payloads/<slot
|
|
1588
|
+
* `spec/schemas/view-slots.schema.json#/$defs/payloads/<slot>`,
|
|
1554
1589
|
* where `<slot>` is the slot the manifest declared for this
|
|
1555
1590
|
* contribution. The orchestrator validates the payload against the
|
|
1556
1591
|
* slot's schema before persisting to `scan_contributions`; off-shape
|
|
@@ -1573,7 +1608,7 @@ interface IExtractorContext extends IExtractorCallbacks {
|
|
|
1573
1608
|
* (`write(table, row)`). See `spec/plugin-kv-api.md`.
|
|
1574
1609
|
*
|
|
1575
1610
|
* Typed as `unknown` so this contract module stays free of any
|
|
1576
|
-
* adapter-side imports
|
|
1611
|
+
* adapter-side imports, the concrete `IPluginStore` lives in
|
|
1577
1612
|
* `kernel/adapters/plugin-store.js`. Plugin authors narrow at the
|
|
1578
1613
|
* call site based on the storage mode declared in their manifest.
|
|
1579
1614
|
* The orchestrator looks up the wrapper per-extractor in
|
|
@@ -1590,11 +1625,11 @@ interface IExtractor extends IExtensionBase {
|
|
|
1590
1625
|
/**
|
|
1591
1626
|
* Optional opt-in filter on `node.kind`. When declared, the orchestrator
|
|
1592
1627
|
* skips invocation of `extract()` for any node whose `kind` is NOT in
|
|
1593
|
-
* this list
|
|
1628
|
+
* this list, fail-fast, before context construction, so the extractor
|
|
1594
1629
|
* wastes zero CPU on inapplicable nodes.
|
|
1595
1630
|
*
|
|
1596
1631
|
* Absent (`undefined`) is the default: the extractor applies to every
|
|
1597
|
-
* kind. There are no wildcards
|
|
1632
|
+
* kind. There are no wildcards, the absence of the field already
|
|
1598
1633
|
* encodes "every kind". An empty array (`[]`) is rejected at load
|
|
1599
1634
|
* time by AJV (`minItems: 1` in the schema).
|
|
1600
1635
|
*
|
|
@@ -1619,13 +1654,13 @@ interface IExtractor extends IExtensionBase {
|
|
|
1619
1654
|
* findings into the UI via view contributions. Deterministic analyzers
|
|
1620
1655
|
* are pure (same graph in → same issues out) and run synchronously
|
|
1621
1656
|
* inside `sm scan` / `sm check`. Probabilistic analyzers invoke an LLM
|
|
1622
|
-
* through the kernel's `RunnerPort` and dispatch only as queued jobs
|
|
1657
|
+
* through the kernel's `RunnerPort` and dispatch only as queued jobs,
|
|
1623
1658
|
* they never participate in scan-time pipelines. Mode is declared in
|
|
1624
1659
|
* the manifest (default `deterministic`).
|
|
1625
1660
|
*/
|
|
1626
1661
|
|
|
1627
1662
|
/**
|
|
1628
|
-
* Step 9.6.2
|
|
1663
|
+
* Step 9.6.2, orphan sidecar entry surfaced to analyzers. A `.sm` file
|
|
1629
1664
|
* whose sibling `.md` does not exist on disk; the `annotation-orphan`
|
|
1630
1665
|
* built-in analyzer emits one warning per entry. Other analyzers that
|
|
1631
1666
|
* care about orphan sidecars MAY consume the list too.
|
|
@@ -1640,13 +1675,13 @@ interface IAnalyzerContext {
|
|
|
1640
1675
|
nodes: Node[];
|
|
1641
1676
|
links: Link[];
|
|
1642
1677
|
/**
|
|
1643
|
-
* Step 9.6.2
|
|
1678
|
+
* Step 9.6.2, orphaned sidecars discovered during the scan walk.
|
|
1644
1679
|
* Empty when sidecar discovery did not run (legacy callers) or
|
|
1645
1680
|
* when no orphans exist.
|
|
1646
1681
|
*/
|
|
1647
1682
|
orphanSidecars?: IAnalyzerOrphanSidecar[];
|
|
1648
1683
|
/**
|
|
1649
|
-
* Step 9.6.6
|
|
1684
|
+
* Step 9.6.6, raw parsed sidecar root keyed by `node.path`. Populated
|
|
1650
1685
|
* by the orchestrator alongside the public `Node.sidecar` overlay so
|
|
1651
1686
|
* analyzers that inspect plugin namespaces (e.g. the built-in
|
|
1652
1687
|
* `core/unknown-field` Analyzer) can walk the full tree without
|
|
@@ -1656,7 +1691,7 @@ interface IAnalyzerContext {
|
|
|
1656
1691
|
*/
|
|
1657
1692
|
sidecarRoots?: ReadonlyMap<string, Record<string, unknown>>;
|
|
1658
1693
|
/**
|
|
1659
|
-
* Step 9.6.6
|
|
1694
|
+
* Step 9.6.6, runtime catalog of plugin-contributed annotation keys,
|
|
1660
1695
|
* as exposed by `kernel.getRegisteredAnnotationKeys()`. Threaded
|
|
1661
1696
|
* through so analyzers can reason about the registered-vs-unknown
|
|
1662
1697
|
* split without reaching back into the kernel. Empty array when no
|
|
@@ -1665,20 +1700,20 @@ interface IAnalyzerContext {
|
|
|
1665
1700
|
*/
|
|
1666
1701
|
annotationContributions?: readonly IRegisteredAnnotationKey[];
|
|
1667
1702
|
/**
|
|
1668
|
-
* Step 11.x
|
|
1703
|
+
* Step 11.x, runtime catalog of plugin-contributed view contributions,
|
|
1669
1704
|
* as exposed by `kernel.getRegisteredViewContributions()`. Threaded
|
|
1670
1705
|
* through so analyzers can reason about emissions without reaching
|
|
1671
|
-
* back into the kernel
|
|
1672
|
-
*
|
|
1673
|
-
*
|
|
1674
|
-
*
|
|
1675
|
-
*
|
|
1676
|
-
* 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).
|
|
1677
1712
|
*/
|
|
1678
1713
|
viewContributions?: readonly IRegisteredViewContribution[];
|
|
1679
1714
|
/**
|
|
1680
1715
|
* Absolute paths of `*.md` files under the project's
|
|
1681
|
-
* `.skill-map/jobs/` that no `state_jobs.filePath` references
|
|
1716
|
+
* `.skill-map/jobs/` that no `state_jobs.filePath` references, the
|
|
1682
1717
|
* built-in `core/job-orphan-file` analyzer projects each as a `warn`
|
|
1683
1718
|
* issue. Pre-computed by the driving adapter (CLI / BFF) inside its
|
|
1684
1719
|
* already-open storage transaction (mirrors the `orphanSidecars`
|
|
@@ -1693,7 +1728,7 @@ interface IAnalyzerContext {
|
|
|
1693
1728
|
* link-validation purposes via `scan.referencePaths`. The driving
|
|
1694
1729
|
* adapter walks each configured path before the scan and collects
|
|
1695
1730
|
* every existing file's absolute path here. Files in this set are
|
|
1696
|
-
* NOT indexed as graph nodes
|
|
1731
|
+
* NOT indexed as graph nodes, the only consumer is
|
|
1697
1732
|
* `core/broken-ref`, which suppresses its `warn` issue when a
|
|
1698
1733
|
* path-style link target falls into the set. Absent / empty when
|
|
1699
1734
|
* the operator left `scan.referencePaths` empty or when the
|
|
@@ -1738,6 +1773,20 @@ interface IAnalyzer extends IExtensionBase {
|
|
|
1738
1773
|
* `deterministic` per `spec/schemas/extensions/analyzer.schema.json`.
|
|
1739
1774
|
*/
|
|
1740
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[];
|
|
1741
1790
|
evaluate(ctx: IAnalyzerContext): Issue[] | Promise<Issue[]>;
|
|
1742
1791
|
}
|
|
1743
1792
|
|
|
@@ -1747,9 +1796,9 @@ interface IAnalyzer extends IExtensionBase {
|
|
|
1747
1796
|
*
|
|
1748
1797
|
* Actions operate on one or more nodes in one of two execution modes:
|
|
1749
1798
|
*
|
|
1750
|
-
* - `deterministic
|
|
1799
|
+
* - `deterministic`, code runs in-process; the action computes the
|
|
1751
1800
|
* report synchronously and returns it. No job file, no runner.
|
|
1752
|
-
* - `probabilistic
|
|
1801
|
+
* - `probabilistic`, the kernel renders a prompt + preamble into a
|
|
1753
1802
|
* job file; a runner executes it via `RunnerPort` against an LLM;
|
|
1754
1803
|
* `sm record` closes the job and validates the report against
|
|
1755
1804
|
* `reportSchemaRef`.
|
|
@@ -1759,7 +1808,7 @@ interface IAnalyzer extends IExtensionBase {
|
|
|
1759
1808
|
* probabilistic) lands with the job subsystem (Decision #114 in
|
|
1760
1809
|
* `ROADMAP.md`). Today the loader still validates `kind: 'action'`
|
|
1761
1810
|
* manifests against `extension-action.schema.json` and the registry
|
|
1762
|
-
* holds them
|
|
1811
|
+
* holds them, `sm actions show` and the precondition gating UI consume
|
|
1763
1812
|
* the manifest data. The runtime entry point is intentionally absent
|
|
1764
1813
|
* from `IAction` so plugin authors don't ship a method the kernel will
|
|
1765
1814
|
* not call until the job subsystem is in place; when it ships, the
|
|
@@ -1767,21 +1816,21 @@ interface IAnalyzer extends IExtensionBase {
|
|
|
1767
1816
|
*
|
|
1768
1817
|
* Mirrors `extensions/action.schema.json`:
|
|
1769
1818
|
*
|
|
1770
|
-
* - `mode` (required)
|
|
1771
|
-
* - `reportSchemaRef` (required)
|
|
1819
|
+
* - `mode` (required), discriminator between the two modes.
|
|
1820
|
+
* - `reportSchemaRef` (required), JSON Schema reference the report
|
|
1772
1821
|
* MUST validate against. MUST extend `report-base.schema.json`.
|
|
1773
|
-
* - `promptTemplateRef
|
|
1822
|
+
* - `promptTemplateRef`, REQUIRED when `mode: 'probabilistic'`,
|
|
1774
1823
|
* FORBIDDEN when `mode: 'deterministic'`. The schema's conditional
|
|
1775
1824
|
* `allOf` enforces both directions; the runtime contract simply
|
|
1776
1825
|
* surfaces the field as optional and lets the loader catch shape
|
|
1777
1826
|
* violations at AJV time.
|
|
1778
|
-
* - `expectedDurationSeconds
|
|
1827
|
+
* - `expectedDurationSeconds`, REQUIRED for probabilistic (drives
|
|
1779
1828
|
* TTL); advisory for deterministic.
|
|
1780
|
-
* - `precondition
|
|
1829
|
+
* - `precondition`, declarative filter consumed by `--all` fan-out,
|
|
1781
1830
|
* UI button gating, `sm actions show`.
|
|
1782
|
-
* - `expectedTools
|
|
1831
|
+
* - `expectedTools`, hint to Skill / CLI runners about expected
|
|
1783
1832
|
* tools (no normative enforcement in v0).
|
|
1784
|
-
* - `fanOutPolicy
|
|
1833
|
+
* - `fanOutPolicy`, `'per-node'` (default) vs `'batch'`.
|
|
1785
1834
|
*/
|
|
1786
1835
|
|
|
1787
1836
|
/**
|
|
@@ -1789,10 +1838,10 @@ interface IAnalyzer extends IExtensionBase {
|
|
|
1789
1838
|
* future write kinds (storage rows, plugin KV, etc.) can land additively
|
|
1790
1839
|
* without breaking consumers that only handle `kind: 'sidecar'`.
|
|
1791
1840
|
*
|
|
1792
|
-
* - `path
|
|
1841
|
+
* - `path`, absolute path to the `.sm` file the kernel must materialise
|
|
1793
1842
|
* the change into. Resolved by the Action from the node's absolute
|
|
1794
1843
|
* path via `sidecarPathFor()`.
|
|
1795
|
-
* - `changes
|
|
1844
|
+
* - `changes`, partial sidecar root used as a deep-merge patch (NOT a
|
|
1796
1845
|
* full replacement). Arrays REPLACE; objects RECURSE. Reason:
|
|
1797
1846
|
* sidecars are shared-write between skill-map core and plugins;
|
|
1798
1847
|
* a full replace would clobber `<plugin-id>:` namespaced blocks.
|
|
@@ -1805,7 +1854,7 @@ type TActionWrite = {
|
|
|
1805
1854
|
/**
|
|
1806
1855
|
* Result envelope returned by deterministic Actions. The `report` field
|
|
1807
1856
|
* carries the typed report payload (each Action declares its shape via
|
|
1808
|
-
* `reportSchemaRef`); `writes` is opt-in
|
|
1857
|
+
* `reportSchemaRef`); `writes` is opt-in, Actions that do not mutate
|
|
1809
1858
|
* persistent state simply omit it.
|
|
1810
1859
|
*/
|
|
1811
1860
|
interface IActionResult<TReport = unknown> {
|
|
@@ -1814,20 +1863,20 @@ interface IActionResult<TReport = unknown> {
|
|
|
1814
1863
|
}
|
|
1815
1864
|
/**
|
|
1816
1865
|
* Runtime context passed to a deterministic Action's `invoke()` method.
|
|
1817
|
-
* Minimal surface
|
|
1866
|
+
* Minimal surface, Actions stay pure (no IO inside `invoke`); the kernel
|
|
1818
1867
|
* materialises any returned `writes` after the call.
|
|
1819
1868
|
*
|
|
1820
|
-
* - `node
|
|
1869
|
+
* - `node`, the target `Node` the Action operates on. Open-by-design;
|
|
1821
1870
|
* batch / fan-out flows pick the matching nodes upstream.
|
|
1822
|
-
* - `nodeAbsolutePath
|
|
1871
|
+
* - `nodeAbsolutePath`, absolute path to the node's `.md` file on
|
|
1823
1872
|
* disk. The Action uses this to compute the sidecar path it returns
|
|
1824
1873
|
* in a `TActionWrite`. Surfaced separately from `node.path` (which is
|
|
1825
1874
|
* the relative scope-root form) so Actions never compose absolute
|
|
1826
1875
|
* paths from `node.path` themselves.
|
|
1827
|
-
* - `invoker
|
|
1876
|
+
* - `invoker`, identity of the caller; written into the sidecar's
|
|
1828
1877
|
* `audit.lastBumpedBy` when the Action chooses to. CLI invocations
|
|
1829
1878
|
* pass `'cli'`; plugin-driven invocations pass `'plugin:<plugin-id>'`.
|
|
1830
|
-
* - `now
|
|
1879
|
+
* - `now`, clock function; tests inject a deterministic source.
|
|
1831
1880
|
* Defaults to `() => new Date()` at the composition root.
|
|
1832
1881
|
*/
|
|
1833
1882
|
interface IActionContext {
|
|
@@ -1838,7 +1887,7 @@ interface IActionContext {
|
|
|
1838
1887
|
}
|
|
1839
1888
|
/**
|
|
1840
1889
|
* Declarative filter applied by `--all` fan-out, UI button gating, and
|
|
1841
|
-
* `sm actions show`. All fields optional
|
|
1890
|
+
* `sm actions show`. All fields optional, an empty precondition matches
|
|
1842
1891
|
* every node.
|
|
1843
1892
|
*/
|
|
1844
1893
|
interface IActionPrecondition {
|
|
@@ -1908,14 +1957,14 @@ interface IAction extends IExtensionBase {
|
|
|
1908
1957
|
/**
|
|
1909
1958
|
* Deterministic invocation entry point. OPTIONAL on the runtime
|
|
1910
1959
|
* contract for backward compatibility with the manifest-only era
|
|
1911
|
-
* (Decision #114)
|
|
1960
|
+
* (Decision #114), actions that ship for the future probabilistic
|
|
1912
1961
|
* runner / record path leave it absent and the kernel never calls it.
|
|
1913
1962
|
* Step 9.6.3 (Decision #125) introduces the first concrete consumer:
|
|
1914
1963
|
* the built-in `bump` Action implements `invoke()` and returns a
|
|
1915
1964
|
* `writes: [{ kind: 'sidecar', ... }]` payload that the kernel
|
|
1916
1965
|
* materialises through `ISidecarStore`.
|
|
1917
1966
|
*
|
|
1918
|
-
* Implementations MUST stay pure
|
|
1967
|
+
* Implementations MUST stay pure, no IO inside `invoke()`. The Action
|
|
1919
1968
|
* computes the patch and returns it; the kernel reads the on-disk
|
|
1920
1969
|
* sidecar, deep-merges, validates, and writes back inside its critical
|
|
1921
1970
|
* section.
|
|
@@ -1933,10 +1982,10 @@ interface IAction extends IExtensionBase {
|
|
|
1933
1982
|
*
|
|
1934
1983
|
* Two adjacent names live on the same instance:
|
|
1935
1984
|
*
|
|
1936
|
-
* - `formatId: string
|
|
1985
|
+
* - `formatId: string`, the manifest field consumed by the
|
|
1937
1986
|
* `--format <name>` CLI flag. The kernel's lookup is
|
|
1938
1987
|
* `formatters.find((f) => f.formatId === flag)`.
|
|
1939
|
-
* - `format(ctx) → string
|
|
1988
|
+
* - `format(ctx) → string`, the runtime method. Receives the full
|
|
1940
1989
|
* graph and returns the serialized output. Output MUST be
|
|
1941
1990
|
* byte-deterministic for the same input (the snapshot-test suite
|
|
1942
1991
|
* relies on this).
|
|
@@ -1950,6 +1999,16 @@ interface IFormatterContext {
|
|
|
1950
1999
|
nodes: Node[];
|
|
1951
2000
|
links: Link[];
|
|
1952
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;
|
|
1953
2012
|
}
|
|
1954
2013
|
interface IFormatter extends IExtensionBase {
|
|
1955
2014
|
kind: 'formatter';
|
|
@@ -1968,7 +2027,7 @@ interface IFormatter extends IExtensionBase {
|
|
|
1968
2027
|
* are notification (Slack on `job.completed`), integration glue (CI
|
|
1969
2028
|
* webhook on `job.failed`), and bookkeeping (per-extractor metrics).
|
|
1970
2029
|
*
|
|
1971
|
-
* The hookable trigger set is INTENTIONALLY SMALL
|
|
2030
|
+
* The hookable trigger set is INTENTIONALLY SMALL, ten events. Eight
|
|
1972
2031
|
* are pipeline-driven (emitted from inside `runScan`); two
|
|
1973
2032
|
* (`boot`, `shutdown`) are CLI-process-driven (emitted by the driving
|
|
1974
2033
|
* binary before / after the verb runs, fire-and-forget so
|
|
@@ -1992,16 +2051,16 @@ interface IFormatter extends IExtensionBase {
|
|
|
1992
2051
|
*
|
|
1993
2052
|
* Curated trigger set (per spec § A.11):
|
|
1994
2053
|
*
|
|
1995
|
-
* 0. `boot`
|
|
1996
|
-
* 1. `scan.started`
|
|
1997
|
-
* 2. `scan.completed`
|
|
1998
|
-
* 3. `extractor.completed`
|
|
1999
|
-
* 4. `analyzer.completed`
|
|
2000
|
-
* 5. `action.completed`
|
|
2001
|
-
* 6. `job.spawning`
|
|
2002
|
-
* 7. `job.completed`
|
|
2003
|
-
* 8. `job.failed`
|
|
2004
|
-
* 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
|
|
2005
2064
|
* exit code resolves and before
|
|
2006
2065
|
* `process.exit`.
|
|
2007
2066
|
*/
|
|
@@ -2089,7 +2148,7 @@ interface IHookContext {
|
|
|
2089
2148
|
* at load time: when none of the declared triggers carries a given
|
|
2090
2149
|
* filter field, the loader surfaces `invalid-manifest`. The current
|
|
2091
2150
|
* impl performs the basic enum check but defers full payload-shape
|
|
2092
|
-
* cross-validation to a follow-up
|
|
2151
|
+
* cross-validation to a follow-up, the dispatcher is permissive at
|
|
2093
2152
|
* runtime (an unknown field never matches → the hook simply never
|
|
2094
2153
|
* fires for that event, which is a correct interpretation of "filter
|
|
2095
2154
|
* by a field that doesn't exist").
|
|
@@ -2120,13 +2179,13 @@ interface IHook extends IExtensionBase {
|
|
|
2120
2179
|
* Hook entry point. Returns nothing; reactions are side effects.
|
|
2121
2180
|
* Errors are caught by the dispatcher (logged as `extension.error`,
|
|
2122
2181
|
* surfaced via `hook.failed` meta-event) and NEVER block the main
|
|
2123
|
-
* pipeline
|
|
2182
|
+
* pipeline, a buggy hook degrades gracefully.
|
|
2124
2183
|
*/
|
|
2125
2184
|
on(ctx: IHookContext): void | Promise<void>;
|
|
2126
2185
|
}
|
|
2127
2186
|
|
|
2128
2187
|
/**
|
|
2129
|
-
* `ProgressEmitterPort
|
|
2188
|
+
* `ProgressEmitterPort`, emits progress events during long operations.
|
|
2130
2189
|
*
|
|
2131
2190
|
* Shape-only today. The full event catalog (`run.started`,
|
|
2132
2191
|
* `job.claimed`, `model.delta`, etc.) is normative in
|
|
@@ -2160,13 +2219,13 @@ interface ProgressEmitterPort {
|
|
|
2160
2219
|
*/
|
|
2161
2220
|
|
|
2162
2221
|
/**
|
|
2163
|
-
* Spec § A.9
|
|
2222
|
+
* Spec § A.9, runs to persist into `scan_extractor_runs`. One entry
|
|
2164
2223
|
* per `(nodePath, qualifiedExtractorId)` pair the orchestrator decided
|
|
2165
2224
|
* "this extractor is current for this body". Includes both freshly-run
|
|
2166
2225
|
* pairs (extractor invoked this scan) and reused pairs (cached node, the
|
|
2167
2226
|
* extractor's prior run still applies to the same body hash). Excludes
|
|
2168
|
-
* obsolete pairs
|
|
2169
|
-
* registered
|
|
2227
|
+
* obsolete pairs, extractors that ran in the prior but are no longer
|
|
2228
|
+
* registered, so a replace-all persist drops them automatically.
|
|
2170
2229
|
*/
|
|
2171
2230
|
interface IExtractorRunRecord {
|
|
2172
2231
|
nodePath: string;
|
|
@@ -2183,7 +2242,7 @@ interface IExtractorRunRecord {
|
|
|
2183
2242
|
sidecarAnnotationsHashAtRun: string;
|
|
2184
2243
|
}
|
|
2185
2244
|
/**
|
|
2186
|
-
* Spec § A.8
|
|
2245
|
+
* Spec § A.8, universal enrichment layer.
|
|
2187
2246
|
*
|
|
2188
2247
|
* One entry per `(nodePath, qualifiedExtractorId)` pair an Extractor
|
|
2189
2248
|
* produced via `ctx.enrichNode(...)` during the walk. Attribution is
|
|
@@ -2196,7 +2255,7 @@ interface IExtractorRunRecord {
|
|
|
2196
2255
|
* for last-write-wins per field at read time.
|
|
2197
2256
|
*
|
|
2198
2257
|
* `value` is the cumulative merge across every `enrichNode` call that
|
|
2199
|
-
* Extractor made for this node within this scan
|
|
2258
|
+
* Extractor made for this node within this scan, multiple
|
|
2200
2259
|
* `ctx.enrichNode({...})` calls inside one `extract(ctx)` invocation
|
|
2201
2260
|
* fold into a single row, but two different Extractors hitting the
|
|
2202
2261
|
* same node yield two distinct rows.
|
|
@@ -2205,7 +2264,7 @@ interface IExtractorRunRecord {
|
|
|
2205
2264
|
* every record produced by the orchestrator sets it to `false`. The
|
|
2206
2265
|
* field is kept on the record (and the row in `node_enrichments`) so a
|
|
2207
2266
|
* future Action-issued enrichment can populate it without reshaping
|
|
2208
|
-
* the persistence contract
|
|
2267
|
+
* the persistence contract, see spec `architecture.md`
|
|
2209
2268
|
* §Extractor · enrichment layer.
|
|
2210
2269
|
*/
|
|
2211
2270
|
interface IEnrichmentRecord {
|
|
@@ -2224,13 +2283,13 @@ interface IEnrichmentRecord {
|
|
|
2224
2283
|
* a focused refresh result, etc.).
|
|
2225
2284
|
*
|
|
2226
2285
|
* Exported so `cli/commands/refresh.ts` can reuse the same wiring it
|
|
2227
|
-
* needs for re-running a single extractor against a single node
|
|
2286
|
+
* needs for re-running a single extractor against a single node, the
|
|
2228
2287
|
* pre-extraction code in `refresh.ts` was hand-duplicating this loop
|
|
2229
2288
|
* (audit item V4).
|
|
2230
2289
|
*
|
|
2231
2290
|
* Within this call, multiple `enrichNode(partial)` calls from the same
|
|
2232
2291
|
* extractor against the same node fold into one record (last-write-wins
|
|
2233
|
-
* per field)
|
|
2292
|
+
* per field), same contract as the in-scan path.
|
|
2234
2293
|
*/
|
|
2235
2294
|
declare function runExtractorsForNode(opts: {
|
|
2236
2295
|
extractors: IExtractor[];
|
|
@@ -2240,7 +2299,7 @@ declare function runExtractorsForNode(opts: {
|
|
|
2240
2299
|
bodyHash: string;
|
|
2241
2300
|
emitter: ProgressEmitterPort;
|
|
2242
2301
|
/**
|
|
2243
|
-
* Spec § A.12
|
|
2302
|
+
* Spec § A.12, per-plugin `ctx.store` wrappers keyed by `pluginId`.
|
|
2244
2303
|
* The map's lookup is per-extractor inside the loop, so callers that
|
|
2245
2304
|
* don't track plugin storage can omit it; the resulting `ctx.store`
|
|
2246
2305
|
* stays `undefined` (the existing contract).
|
|
@@ -2274,7 +2333,7 @@ interface RenameOp {
|
|
|
2274
2333
|
}
|
|
2275
2334
|
/**
|
|
2276
2335
|
* Pure rename / orphan classification per `spec/db-schema.md` §Rename
|
|
2277
|
-
* detection. Mutates `issues` in place
|
|
2336
|
+
* detection. Mutates `issues` in place, caller passes the in-progress
|
|
2278
2337
|
* issue list; returns the `RenameOp[]` for the persistence layer to
|
|
2279
2338
|
* apply inside its tx.
|
|
2280
2339
|
*
|
|
@@ -2295,30 +2354,30 @@ interface RenameOp {
|
|
|
2295
2354
|
* `orphan` issue (severity info) with `data: { path: <deletedPath> }`.
|
|
2296
2355
|
*
|
|
2297
2356
|
* Determinism: `deletedPaths` and `newPaths` are iterated in lex-asc
|
|
2298
|
-
* order so the same input always produces the same matches
|
|
2357
|
+
* order so the same input always produces the same matches,
|
|
2299
2358
|
* required for reproducible tests and conformance fixtures (the spec
|
|
2300
2359
|
* does not prescribe an order, but stability is the obvious contract).
|
|
2301
2360
|
*/
|
|
2302
2361
|
declare function detectRenamesAndOrphans(prior: ScanResult, current: Node[], issues: Issue[]): RenameOp[];
|
|
2303
2362
|
|
|
2304
2363
|
/**
|
|
2305
|
-
* Scan orchestrator
|
|
2364
|
+
* Scan orchestrator, runs the Provider → extractor → analyzer pipeline across
|
|
2306
2365
|
* every registered extension and emits `ProgressEmitterPort` events in
|
|
2307
2366
|
* canonical order. The callable extension set is injected via
|
|
2308
|
-
* `RunScanOptions.extensions
|
|
2367
|
+
* `RunScanOptions.extensions`, the Registry holds manifest metadata, the
|
|
2309
2368
|
* callable set holds the runtime instances the orchestrator actually
|
|
2310
2369
|
* invokes. Separating the two lets `sm plugins` and `sm help` introspect
|
|
2311
2370
|
* the graph without loading code.
|
|
2312
2371
|
*
|
|
2313
2372
|
* With zero registered extensions (or a callable set that carries none)
|
|
2314
|
-
* the pipeline still produces a valid zero-filled `ScanResult
|
|
2373
|
+
* the pipeline still produces a valid zero-filled `ScanResult`, the
|
|
2315
2374
|
* kernel-empty-boot invariant.
|
|
2316
2375
|
*
|
|
2317
2376
|
* Roots are validated up front: each entry of `RunScanOptions.roots`
|
|
2318
2377
|
* must exist on disk as a directory. The first failure throws a clear
|
|
2319
2378
|
* `Error` naming the offending path. This guards every caller (CLI,
|
|
2320
2379
|
* server, skill-agent) against silently producing a zero-filled
|
|
2321
|
-
* `ScanResult` when a Provider walks a non-existent path
|
|
2380
|
+
* `ScanResult` when a Provider walks a non-existent path, the bug
|
|
2322
2381
|
* that wiped a populated DB via `sm scan -- --dry-run` (clipanion's
|
|
2323
2382
|
* `--` made `--dry-run` a positional root that did not exist).
|
|
2324
2383
|
*
|
|
@@ -2328,7 +2387,7 @@ declare function detectRenamesAndOrphans(prior: ScanResult, current: Node[], iss
|
|
|
2328
2387
|
* `bodyHash` and `frontmatterHash` match. New / modified files run
|
|
2329
2388
|
* through the full extractor pipeline (including the external-url-counter
|
|
2330
2389
|
* which produces ephemeral pseudo-links). Rules ALWAYS run over the
|
|
2331
|
-
* fully merged graph
|
|
2390
|
+
* fully merged graph, issue state can change even for an unchanged node
|
|
2332
2391
|
* (e.g. a previously broken `references` link now resolves because a new
|
|
2333
2392
|
* node was added). For unchanged nodes the prior `externalRefsCount` is
|
|
2334
2393
|
* preserved as-is (the external pseudo-links were never persisted, so
|
|
@@ -2342,7 +2401,7 @@ declare function detectRenamesAndOrphans(prior: ScanResult, current: Node[], iss
|
|
|
2342
2401
|
* entry per `(node, extractor)` so attribution survives into the DB.
|
|
2343
2402
|
* Persisted into `node_enrichments` (A.8). The author-supplied
|
|
2344
2403
|
* frontmatter on `node.frontmatter` stays immutable from any Extractor
|
|
2345
|
-
*
|
|
2404
|
+
* , the enrichment layer is the only writable surface, and rules /
|
|
2346
2405
|
* formatters consume it via `mergeNodeWithEnrichments`.
|
|
2347
2406
|
* - `ctx.store` → plugin's own KV / dedicated tables (spec § A.12).
|
|
2348
2407
|
* Wired by the driving adapter via `RunScanOptions.pluginStores`,
|
|
@@ -2377,13 +2436,13 @@ interface RunScanOptions {
|
|
|
2377
2436
|
/** Runtime extension instances. Absent → empty pipeline. */
|
|
2378
2437
|
extensions?: IScanExtensions;
|
|
2379
2438
|
/**
|
|
2380
|
-
* Step 9.6.6
|
|
2439
|
+
* Step 9.6.6, runtime catalog of plugin-contributed annotation keys
|
|
2381
2440
|
* (the same shape `kernel.getRegisteredAnnotationKeys()` returns).
|
|
2382
2441
|
* Threaded into the rule pass so `core/unknown-field` can
|
|
2383
2442
|
* legitimise registered plugin namespaces / root keys without
|
|
2384
2443
|
* re-walking the manifests. Absent → empty catalog (every plugin
|
|
2385
2444
|
* key is treated as unknown). Built-in catalog from
|
|
2386
|
-
* `annotations.schema.json` is NOT included
|
|
2445
|
+
* `annotations.schema.json` is NOT included, that is hard-coded
|
|
2387
2446
|
* inside the rule.
|
|
2388
2447
|
*/
|
|
2389
2448
|
annotationContributions?: readonly IRegisteredAnnotationKey[];
|
|
@@ -2391,8 +2450,12 @@ interface RunScanOptions {
|
|
|
2391
2450
|
* Runtime catalog of plugin-contributed view contributions (the same
|
|
2392
2451
|
* shape `kernel.getRegisteredViewContributions()` returns). Threaded
|
|
2393
2452
|
* into the rule pass so:
|
|
2394
|
-
* - `core/
|
|
2395
|
-
*
|
|
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).
|
|
2396
2459
|
* - The orchestrator's per-rule emit closure can look up each
|
|
2397
2460
|
* declared `(contributionId → slot)` pairing for AJV
|
|
2398
2461
|
* payload validation.
|
|
@@ -2463,7 +2526,7 @@ interface RunScanOptions {
|
|
|
2463
2526
|
*/
|
|
2464
2527
|
strict?: boolean;
|
|
2465
2528
|
/**
|
|
2466
|
-
* Spec § A.9
|
|
2529
|
+
* Spec § A.9, fine-grained Extractor cache breadcrumbs from the
|
|
2467
2530
|
* prior scan. Shape: `Map<nodePath, Map<qualifiedExtractorId, IPriorExtractorRun>>`.
|
|
2468
2531
|
* Loaded from the `scan_extractor_runs` table by the CLI before
|
|
2469
2532
|
* invoking `runScan`; absent / empty for a fresh DB or an out-of-band
|
|
@@ -2483,11 +2546,11 @@ interface RunScanOptions {
|
|
|
2483
2546
|
*/
|
|
2484
2547
|
priorExtractorRuns?: Map<string, Map<string, IPriorExtractorRun>>;
|
|
2485
2548
|
/**
|
|
2486
|
-
* Spec § A.12
|
|
2549
|
+
* Spec § A.12, per-plugin storage wrappers exposed to extractors via
|
|
2487
2550
|
* `ctx.store`. Keyed by `pluginId`; absent / missing entry leaves
|
|
2488
2551
|
* `ctx.store` undefined for that extractor (the existing contract).
|
|
2489
2552
|
*
|
|
2490
|
-
* The kernel does not construct these
|
|
2553
|
+
* The kernel does not construct these, the driving adapter (CLI,
|
|
2491
2554
|
* future server) builds them with `makePluginStore` from
|
|
2492
2555
|
* `kernel/adapters/plugin-store.js` and threads them through. This
|
|
2493
2556
|
* keeps the orchestrator persistence-agnostic (the wrapper supplies
|
|
@@ -2504,7 +2567,7 @@ interface RunScanOptions {
|
|
|
2504
2567
|
* its own FS walk. The driving adapter (CLI, BFF) computes this
|
|
2505
2568
|
* inside its already-open storage transaction via
|
|
2506
2569
|
* `findOrphanJobFiles(jobsDir, await port.jobs.listReferencedFilePaths())`
|
|
2507
|
-
*
|
|
2570
|
+
* mirrors the `orphanSidecars` model where detection lives
|
|
2508
2571
|
* outside the rule and the rule only projects. Absent / empty when
|
|
2509
2572
|
* the caller has no jobs context (out-of-band tests, fresh DB,
|
|
2510
2573
|
* `--no-built-ins`).
|
|
@@ -2516,7 +2579,7 @@ interface RunScanOptions {
|
|
|
2516
2579
|
* through to `IAnalyzerContext.referenceablePaths` so the built-in
|
|
2517
2580
|
* `core/broken-ref` rule can suppress its `warn` for path-style
|
|
2518
2581
|
* links whose target lands in the set. Files are NOT walked by
|
|
2519
|
-
* the kernel
|
|
2582
|
+
* the kernel, the driving adapter populates the set before
|
|
2520
2583
|
* calling `runScan`. Absent / empty when the operator left
|
|
2521
2584
|
* `scan.referencePaths` unconfigured.
|
|
2522
2585
|
*/
|
|
@@ -2532,13 +2595,13 @@ interface RunScanOptions {
|
|
|
2532
2595
|
}
|
|
2533
2596
|
/**
|
|
2534
2597
|
* Same as `runScan` but also returns the rename heuristic's `RenameOp[]`
|
|
2535
|
-
*
|
|
2598
|
+
* the high- and medium-confidence renames the persistence layer must
|
|
2536
2599
|
* apply to `state_*` rows inside the same tx as the scan zone replace-
|
|
2537
2600
|
* all (per `spec/db-schema.md` §Rename detection). Most callers want
|
|
2538
2601
|
* `runScan` (which returns just `ScanResult`); the CLI's `sm scan`
|
|
2539
2602
|
* uses this variant so it can hand the ops off to `persistScanResult`.
|
|
2540
2603
|
*
|
|
2541
|
-
* Also returns `extractorRuns
|
|
2604
|
+
* Also returns `extractorRuns`, the Spec § A.9 fine-grained cache
|
|
2542
2605
|
* breadcrumbs the CLI persists into `scan_extractor_runs` so the next
|
|
2543
2606
|
* incremental scan can decide per-(node, extractor) whether re-running
|
|
2544
2607
|
* is required.
|
|
@@ -2558,15 +2621,15 @@ declare function runScan(_kernel: Kernel, options: RunScanOptions): Promise<Scan
|
|
|
2558
2621
|
* sidecar annotations, resolve the sidecar overlay for a given relative
|
|
2559
2622
|
* path, and produce a fresh `Node` (validating its frontmatter on the
|
|
2560
2623
|
* way out). Also hosts `mergeNodeWithEnrichments` + `IPersistedEnrichment`
|
|
2561
|
-
*
|
|
2624
|
+
* the read-time merge of author frontmatter with the A.8 enrichment
|
|
2562
2625
|
* layer.
|
|
2563
2626
|
*/
|
|
2564
2627
|
|
|
2565
2628
|
/**
|
|
2566
|
-
* Spec § A.8
|
|
2629
|
+
* Spec § A.8, produce the merged read-time view of a Node.
|
|
2567
2630
|
*
|
|
2568
2631
|
* Rules / `sm check` / `sm export` consume `node.frontmatter` directly
|
|
2569
|
-
* (deterministic CI-safe baseline
|
|
2632
|
+
* (deterministic CI-safe baseline, author intent, byte-stable). UI / future
|
|
2570
2633
|
* rules that opt into enrichment context call this helper to merge the
|
|
2571
2634
|
* author frontmatter with the live enrichment layer.
|
|
2572
2635
|
*
|
|
@@ -2580,18 +2643,18 @@ declare function runScan(_kernel: Kernel, options: RunScanOptions): Promise<Scan
|
|
|
2580
2643
|
* belongs to the UI layer next to the value.
|
|
2581
2644
|
* 2. Sort the survivors by `enrichedAt` ASC so iteration order is
|
|
2582
2645
|
* "oldest first". This makes the spread merge below
|
|
2583
|
-
* last-write-wins per field
|
|
2646
|
+
* last-write-wins per field, the freshest Extractor's value
|
|
2584
2647
|
* pisar the older one for any conflicting key.
|
|
2585
2648
|
* 3. Spread-merge each row's `value` over `node.frontmatter`. The
|
|
2586
2649
|
* author's keys are the base; enrichment keys overlay them.
|
|
2587
2650
|
*
|
|
2588
|
-
* The returned object is a fresh shallow copy
|
|
2651
|
+
* The returned object is a fresh shallow copy, mutating it does not
|
|
2589
2652
|
* touch the caller's node. The original `node.frontmatter` reference
|
|
2590
2653
|
* remains accessible via `node.frontmatter` for callers that want the
|
|
2591
2654
|
* pristine author baseline.
|
|
2592
2655
|
*
|
|
2593
2656
|
* @param node Node to merge against; `node.frontmatter` is the base.
|
|
2594
|
-
* @param enrichments Per-(node, extractor) enrichment records
|
|
2657
|
+
* @param enrichments Per-(node, extractor) enrichment records, typically
|
|
2595
2658
|
* loaded via `loadNodeEnrichments(db, node.path)` or
|
|
2596
2659
|
* pre-filtered to this node by the caller.
|
|
2597
2660
|
* @param opts.includeStale When true, include rows flagged stale. Defaults
|
|
@@ -2621,7 +2684,7 @@ interface IPersistedEnrichment {
|
|
|
2621
2684
|
}
|
|
2622
2685
|
|
|
2623
2686
|
/**
|
|
2624
|
-
* In-memory `ProgressEmitterPort` adapter. No network, no DB
|
|
2687
|
+
* In-memory `ProgressEmitterPort` adapter. No network, no DB, just a
|
|
2625
2688
|
* synchronous fan-out to registered listeners. Used by the default scan
|
|
2626
2689
|
* orchestrator; the WebSocket-backed emitter that streams to
|
|
2627
2690
|
* the Web UI lands.
|
|
@@ -2638,7 +2701,7 @@ declare class InMemoryProgressEmitter implements ProgressEmitterPort {
|
|
|
2638
2701
|
*
|
|
2639
2702
|
* Wraps `chokidar` behind a small `IFsWatcher` interface so:
|
|
2640
2703
|
*
|
|
2641
|
-
* 1. The CLI command is impl-agnostic
|
|
2704
|
+
* 1. The CLI command is impl-agnostic, swapping chokidar for a
|
|
2642
2705
|
* different watcher later (Java? Rust port? a future `WatchPort`?)
|
|
2643
2706
|
* doesn't ripple into the command.
|
|
2644
2707
|
* 2. Debouncing, batching, and ignore-filter integration live in one
|
|
@@ -2687,28 +2750,37 @@ interface ICreateFsWatcherOptions {
|
|
|
2687
2750
|
/** Debounce window in milliseconds. `0` triggers `onBatch` synchronously per event. */
|
|
2688
2751
|
debounceMs: number;
|
|
2689
2752
|
/**
|
|
2690
|
-
* Optional ignore filter
|
|
2753
|
+
* Optional ignore filter, same instance the scan walker uses.
|
|
2691
2754
|
*
|
|
2692
2755
|
* Two shapes are accepted:
|
|
2693
2756
|
*
|
|
2694
|
-
* - **`IIgnoreFilter`** (the static one)
|
|
2757
|
+
* - **`IIgnoreFilter`** (the static one), captured by reference at
|
|
2695
2758
|
* construction. Use this when the filter never changes for the
|
|
2696
2759
|
* lifetime of the watcher (the typical CLI `sm watch` flow).
|
|
2697
2760
|
*
|
|
2698
|
-
* - **`() => IIgnoreFilter | undefined`** (a getter)
|
|
2761
|
+
* - **`() => IIgnoreFilter | undefined`** (a getter), re-evaluated
|
|
2699
2762
|
* on EVERY chokidar `ignored` predicate call. Use this when the
|
|
2700
|
-
* filter can change at runtime
|
|
2763
|
+
* filter can change at runtime, e.g. the BFF rebuilds it after
|
|
2701
2764
|
* a `.skillmapignore` or `.skill-map/settings.json` edit and
|
|
2702
2765
|
* wants chokidar to immediately respect the new patterns without
|
|
2703
2766
|
* tearing down and rebuilding the watcher. A getter that returns
|
|
2704
2767
|
* `undefined` disables ignore filtering for that call.
|
|
2705
2768
|
*/
|
|
2706
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;
|
|
2707
2779
|
/** Called once per debounced batch. Awaited; concurrent batches are serialised. */
|
|
2708
2780
|
onBatch: (batch: IWatchBatch) => void | Promise<void>;
|
|
2709
2781
|
/**
|
|
2710
2782
|
* Called when the underlying watcher surfaces an error. The watcher
|
|
2711
|
-
* stays open
|
|
2783
|
+
* stays open, callers decide whether to log, keep going, or close.
|
|
2712
2784
|
*/
|
|
2713
2785
|
onError?: (err: Error) => void;
|
|
2714
2786
|
}
|
|
@@ -2717,7 +2789,7 @@ interface ICreateFsWatcherOptions {
|
|
|
2717
2789
|
* returned `ready` promise resolves once chokidar's initial directory
|
|
2718
2790
|
* walk completes, at which point only NEW events fire `onBatch`.
|
|
2719
2791
|
*
|
|
2720
|
-
* The initial directory walk is deliberately silent
|
|
2792
|
+
* The initial directory walk is deliberately silent, we set
|
|
2721
2793
|
* `ignoreInitial: true`. The CLI runs a one-shot scan before flipping
|
|
2722
2794
|
* the watcher on, so re-emitting an `add` for every existing file
|
|
2723
2795
|
* would be redundant churn.
|
|
@@ -2725,20 +2797,20 @@ interface ICreateFsWatcherOptions {
|
|
|
2725
2797
|
declare function createChokidarWatcher(opts: ICreateFsWatcherOptions): IFsWatcher;
|
|
2726
2798
|
|
|
2727
2799
|
/**
|
|
2728
|
-
* Scan delta
|
|
2800
|
+
* Scan delta, pure comparison of two `ScanResult` snapshots. Drives
|
|
2729
2801
|
* `sm scan --compare-with <path>` and is the single place the kernel
|
|
2730
2802
|
* knows how to identify "the same" entity across two scans.
|
|
2731
2803
|
*
|
|
2732
2804
|
* **Identity contract** (mirrors decisions made at earlier sub-steps):
|
|
2733
2805
|
*
|
|
2734
2806
|
* - **Node**: `node.path`. The path is the only field stable across
|
|
2735
|
-
* edits
|
|
2807
|
+
* edits, every other Node field is content-derived (hashes, counts,
|
|
2736
2808
|
* denormalised frontmatter). Two nodes with the same path are the
|
|
2737
2809
|
* "same" node; differences are reported as a `changed` entry with
|
|
2738
2810
|
* a reason narrowing what diverged.
|
|
2739
2811
|
*
|
|
2740
2812
|
* - **Link**: `(source, target, kind, normalizedTrigger ?? '')`. This
|
|
2741
|
-
* mirrors the link-conflict rule and `sm show` aggregation
|
|
2813
|
+
* mirrors the link-conflict rule and `sm show` aggregation,
|
|
2742
2814
|
* two links with identical endpoints, kind, and (optional) trigger
|
|
2743
2815
|
* are the same link, even if emitted by different extractors. The
|
|
2744
2816
|
* `sources[]` union and confidence are NOT part of identity; they
|
|
@@ -2746,13 +2818,13 @@ declare function createChokidarWatcher(opts: ICreateFsWatcherOptions): IFsWatche
|
|
|
2746
2818
|
* "different" for delta purposes.
|
|
2747
2819
|
*
|
|
2748
2820
|
* - **Issue**: `(analyzerId, sorted nodeIds, message)`. Mirrors
|
|
2749
|
-
* `spec/job-events.md` §issue
|
|
2821
|
+
* `spec/job-events.md` §issue.*, same key → same issue, even when
|
|
2750
2822
|
* `data` / `severity` / `linkIndices` shift. A meaningful change in
|
|
2751
2823
|
* `message` (or a different set of node ids) is a different issue.
|
|
2752
2824
|
* This is the same key future job events will use; keep it aligned
|
|
2753
2825
|
* so consumers can reuse logic.
|
|
2754
2826
|
*
|
|
2755
|
-
* No "changed" bucket for links / issues
|
|
2827
|
+
* No "changed" bucket for links / issues, identity already captures
|
|
2756
2828
|
* everything that matters there. Nodes get a "changed" bucket because
|
|
2757
2829
|
* the path stays stable while the body / frontmatter rewrite, and that
|
|
2758
2830
|
* change is meaningful (formatters, summarisers, downstream consumers
|
|
@@ -2798,7 +2870,7 @@ declare function computeScanDelta(prior: ScanResult, current: ScanResult, compar
|
|
|
2798
2870
|
declare function isEmptyDelta(delta: IScanDelta): boolean;
|
|
2799
2871
|
|
|
2800
2872
|
/**
|
|
2801
|
-
* Export query
|
|
2873
|
+
* Export query, minimal filter language for `sm export <query>` (Step 8.3).
|
|
2802
2874
|
*
|
|
2803
2875
|
* Spec contract: `spec/cli-contract.md` line 190 says "Query syntax is
|
|
2804
2876
|
* implementation-defined pre-1.0". This module defines the v0.5.0 syntax.
|
|
@@ -2816,12 +2888,12 @@ declare function isEmptyDelta(delta: IScanDelta): boolean;
|
|
|
2816
2888
|
*
|
|
2817
2889
|
* **Filters**:
|
|
2818
2890
|
*
|
|
2819
|
-
* - `kind=skill` / `kind=skill,agent
|
|
2820
|
-
* - `has=issues
|
|
2891
|
+
* - `kind=skill` / `kind=skill,agent`, node kind whitelist.
|
|
2892
|
+
* - `has=issues`, node must appear in some issue's `nodeIds`. (Future
|
|
2821
2893
|
* expansion: `has=findings` / `has=summary` once Step 10 / 11 land.
|
|
2822
2894
|
* Unknown values are a parse error today; we'll ratchet up the
|
|
2823
2895
|
* accepted set additively.)
|
|
2824
|
-
* - `path=foo/*` / `path=.claude/agents
|
|
2896
|
+
* - `path=foo/*` / `path=.claude/agents/**`, POSIX glob over `node.path`.
|
|
2825
2897
|
* Supports `*` (any chars except `/`) and `**` (any chars including `/`).
|
|
2826
2898
|
*
|
|
2827
2899
|
* **Subset semantics** (`applyExportQuery`):
|
|
@@ -2830,7 +2902,7 @@ declare function isEmptyDelta(delta: IScanDelta): boolean;
|
|
|
2830
2902
|
* OR within values).
|
|
2831
2903
|
* - Links survive only when BOTH endpoints (`source` + `target`) belong
|
|
2832
2904
|
* to the filtered node set. A subset that includes "edges out to
|
|
2833
|
-
* unfiltered nodes" would be confusing
|
|
2905
|
+
* unfiltered nodes" would be confusing, the user asked for a focused
|
|
2834
2906
|
* subgraph, not its boundary. External-URL pseudo-links are already
|
|
2835
2907
|
* stripped by the orchestrator and never reach this layer.
|
|
2836
2908
|
* - Issues survive when ANY of the issue's `nodeIds` is in the filtered
|
|
@@ -2845,7 +2917,7 @@ interface IExportQuery {
|
|
|
2845
2917
|
/** Original query string echoed back so consumers can render the header. */
|
|
2846
2918
|
raw: string;
|
|
2847
2919
|
/**
|
|
2848
|
-
* Whitelist of node kinds (`node.kind` is open string
|
|
2920
|
+
* Whitelist of node kinds (`node.kind` is open string, built-in
|
|
2849
2921
|
* Claude catalog `skill` / `agent` / `command` / `hook` / `note`,
|
|
2850
2922
|
* plus whatever external Providers declare). The query parser does
|
|
2851
2923
|
* not validate values against a closed enum; an unknown kind simply
|
|
@@ -2872,16 +2944,16 @@ declare function applyExportQuery(scan: {
|
|
|
2872
2944
|
}, query: IExportQuery): IExportSubset;
|
|
2873
2945
|
|
|
2874
2946
|
/**
|
|
2875
|
-
* `scan_node_tags` adapter
|
|
2947
|
+
* `scan_node_tags` adapter, tags · dual-source persistence layer.
|
|
2876
2948
|
*
|
|
2877
2949
|
* One row per `(node_path, tag, source)` triple. Projected at persist
|
|
2878
2950
|
* time from BOTH `frontmatter.tags` (with `source='author'`) and
|
|
2879
2951
|
* `sidecar.annotations.tags` (with `source='user'`). The same tag
|
|
2880
|
-
* string MAY appear under both sources for the same node
|
|
2952
|
+
* string MAY appear under both sources for the same node, the PK
|
|
2881
2953
|
* accepts the pair; search returns the node once via DISTINCT, the
|
|
2882
2954
|
* UI renders both chips with their attribution.
|
|
2883
2955
|
*
|
|
2884
|
-
* Belongs to the `scan_*` family
|
|
2956
|
+
* Belongs to the `scan_*` family, replaced wholesale per scan.
|
|
2885
2957
|
* Cached nodes' tag rows are projected from the cached
|
|
2886
2958
|
* `node.frontmatter.tags` / `node.sidecar.annotations.tags` (both
|
|
2887
2959
|
* already in memory at persist time), so the rebuild is cheap
|
|
@@ -2906,17 +2978,17 @@ interface ITagRecord {
|
|
|
2906
2978
|
* Pure helpers for the "update available" notification feature.
|
|
2907
2979
|
*
|
|
2908
2980
|
* Three responsibilities:
|
|
2909
|
-
* - `fetchLatestVersion`
|
|
2981
|
+
* - `fetchLatestVersion` , query `https://registry.npmjs.org/<pkg>/latest`
|
|
2910
2982
|
* with `AbortController` + timeout. Throws on
|
|
2911
2983
|
* non-200 / parse failure / abort.
|
|
2912
|
-
* - `compareVersions`
|
|
2984
|
+
* - `compareVersions` , semver compare (-1 / 0 / 1). Pre-1.0 aware:
|
|
2913
2985
|
* treats prereleases via the standard rules
|
|
2914
2986
|
* (release > prerelease at the same triple).
|
|
2915
|
-
* - `isOutdated`
|
|
2987
|
+
* - `isOutdated` , sugar over `compareVersions` for the common
|
|
2916
2988
|
* "is `latest` strictly greater than `current`"
|
|
2917
2989
|
* check the banner runs against.
|
|
2918
2990
|
*
|
|
2919
|
-
* Pure kernel module
|
|
2991
|
+
* Pure kernel module, NO `process.env` reads, NO Node globals beyond the
|
|
2920
2992
|
* built-in `fetch` / `AbortController` (Node 22+). Every env / settings
|
|
2921
2993
|
* lookup happens in `src/cli/util/update-check-banner.ts`, the CLI-side
|
|
2922
2994
|
* adapter that owns side effects.
|
|
@@ -2924,21 +2996,21 @@ interface ITagRecord {
|
|
|
2924
2996
|
* The shared cache type (`IUpdateCheckCache`) is used by the storage
|
|
2925
2997
|
* helpers under `kernel/storage/update-check.ts` and by the BFF's
|
|
2926
2998
|
* `GET /api/update-status` projection. A second type
|
|
2927
|
-
* (`IUpdateStatus`) shapes the BFF response
|
|
2999
|
+
* (`IUpdateStatus`) shapes the BFF response, it merges `current`
|
|
2928
3000
|
* (from `VERSION`) into the cache so the UI can render without a
|
|
2929
|
-
* second lookup. Both stay flat
|
|
3001
|
+
* second lookup. Both stay flat, no nested objects, so JSON
|
|
2930
3002
|
* serialization is trivial.
|
|
2931
3003
|
*/
|
|
2932
3004
|
interface IUpdateCheckCache {
|
|
2933
3005
|
latestVersion: string;
|
|
2934
|
-
/** Epoch ms
|
|
3006
|
+
/** Epoch ms, when the registry was last successfully probed. */
|
|
2935
3007
|
checkedAt: number;
|
|
2936
|
-
/** Epoch ms
|
|
3008
|
+
/** Epoch ms, when the banner was last printed; null = never shown yet. */
|
|
2937
3009
|
shownAt: number | null;
|
|
2938
3010
|
}
|
|
2939
3011
|
|
|
2940
3012
|
/**
|
|
2941
|
-
* `PluginLoaderPort
|
|
3013
|
+
* `PluginLoaderPort`, discovers plugin directories and loads their
|
|
2942
3014
|
* extensions. The shape mirrors what the concrete loader actually
|
|
2943
3015
|
* exposes (see `kernel/adapters/plugin-loader.ts`); the port exists so
|
|
2944
3016
|
* the CLI consumes the abstract contract via `createPluginLoader(...)`
|
|
@@ -2961,12 +3033,12 @@ interface PluginLoaderPort {
|
|
|
2961
3033
|
discoverPaths(): string[];
|
|
2962
3034
|
/**
|
|
2963
3035
|
* Discover every plugin, attempt to load each, then apply the
|
|
2964
|
-
* cross-root id-collision pass. Never throws
|
|
3036
|
+
* cross-root id-collision pass. Never throws, failures are reported
|
|
2965
3037
|
* via `IDiscoveredPlugin.status`.
|
|
2966
3038
|
*/
|
|
2967
3039
|
discoverAndLoadAll(): Promise<IDiscoveredPlugin[]>;
|
|
2968
3040
|
/**
|
|
2969
|
-
* Load a single plugin from its directory. Never throws
|
|
3041
|
+
* Load a single plugin from its directory. Never throws, failure is
|
|
2970
3042
|
* reported via the returned `status`.
|
|
2971
3043
|
*/
|
|
2972
3044
|
loadOne(pluginPath: string): Promise<IDiscoveredPlugin>;
|
|
@@ -2974,7 +3046,7 @@ interface PluginLoaderPort {
|
|
|
2974
3046
|
|
|
2975
3047
|
/**
|
|
2976
3048
|
* Row-level filter for `port.scans.findNodes(...)` (driven by
|
|
2977
|
-
* `sm list`'s flags). All fields are optional
|
|
3049
|
+
* `sm list`'s flags). All fields are optional, an empty filter
|
|
2978
3050
|
* returns every node sorted by `path` asc.
|
|
2979
3051
|
*/
|
|
2980
3052
|
interface INodeFilter {
|
|
@@ -2998,7 +3070,7 @@ interface INodeFilter {
|
|
|
2998
3070
|
limit?: number;
|
|
2999
3071
|
}
|
|
3000
3072
|
/**
|
|
3001
|
-
* Bundled fetch for `port.scans.findNode(path)
|
|
3073
|
+
* Bundled fetch for `port.scans.findNode(path)`, one node and
|
|
3002
3074
|
* everything `sm show <path>` displays alongside it. Every field is
|
|
3003
3075
|
* computed from `scan_*` zone reads only; per-domain data (history,
|
|
3004
3076
|
* jobs, plugin enrichments) ships through other namespaces.
|
|
@@ -3032,7 +3104,7 @@ interface IPersistOptions {
|
|
|
3032
3104
|
enrichments?: IEnrichmentRecord[];
|
|
3033
3105
|
contributions?: IContributionRecord[];
|
|
3034
3106
|
/**
|
|
3035
|
-
* Phase 3 / View contribution system
|
|
3107
|
+
* Phase 3 / View contribution system, active runtime catalog of
|
|
3036
3108
|
* registered view contributions, keyed by qualified id
|
|
3037
3109
|
* `<pluginId>/<extensionId>/<contributionId>`. Passed to the
|
|
3038
3110
|
* `scan_contributions` upsert so the catalog sweep can drop rows
|
|
@@ -3044,10 +3116,10 @@ interface IPersistOptions {
|
|
|
3044
3116
|
*/
|
|
3045
3117
|
registeredContributionKeys?: ReadonlySet<string>;
|
|
3046
3118
|
/**
|
|
3047
|
-
* Phase 3 / View contribution system
|
|
3119
|
+
* Phase 3 / View contribution system, set of `(plugin, extension,
|
|
3048
3120
|
* node)` tuples where the extension actually RAN against that node
|
|
3049
3121
|
* in this scan. Format: `<pluginId>/<extensionId>/<nodePath>` (no
|
|
3050
|
-
* contribution-id segment
|
|
3122
|
+
* contribution-id segment, the sweep operates at the (plugin,
|
|
3051
3123
|
* extension, node) level and inspects the buffer to decide which
|
|
3052
3124
|
* contribution-ids survive).
|
|
3053
3125
|
*
|
|
@@ -3070,7 +3142,7 @@ interface IPersistOptions {
|
|
|
3070
3142
|
freshlyRunTuples?: ReadonlySet<string>;
|
|
3071
3143
|
}
|
|
3072
3144
|
/**
|
|
3073
|
-
* Issue row as the storage layer sees it
|
|
3145
|
+
* Issue row as the storage layer sees it, paired with its DB-assigned
|
|
3074
3146
|
* id so `port.issues.deleteById(id)` can target it inside a
|
|
3075
3147
|
* transaction. The runtime `Issue` shape (per `issue.schema.json`) does
|
|
3076
3148
|
* not carry `id` because the spec models issues as ephemeral findings
|
|
@@ -3081,6 +3153,54 @@ interface IIssueRow {
|
|
|
3081
3153
|
id: number;
|
|
3082
3154
|
issue: Issue;
|
|
3083
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
|
+
}
|
|
3084
3204
|
/** Output of `port.jobs.pruneTerminal` / `listTerminalCandidates`. */
|
|
3085
3205
|
interface IPruneResult {
|
|
3086
3206
|
/** How many `state_jobs` rows were deleted (or would be, in dry-run). */
|
|
@@ -3127,7 +3247,7 @@ interface IMigrateNodeFksReport {
|
|
|
3127
3247
|
/**
|
|
3128
3248
|
* Collisions encountered when migrating any of the keyed-by-node
|
|
3129
3249
|
* `state_*` tables because a row already existed at the destination
|
|
3130
|
-
* PK. The pre-existing rows are preserved
|
|
3250
|
+
* PK. The pre-existing rows are preserved, the migrating rows are
|
|
3131
3251
|
* dropped (deleted from `fromPath` without a corresponding INSERT).
|
|
3132
3252
|
* One entry per dropped row, with the affected PK fields included
|
|
3133
3253
|
* for diagnostic output. `state_node_favorites` has no composite key
|
|
@@ -3210,7 +3330,7 @@ interface IPluginApplyResult {
|
|
|
3210
3330
|
|
|
3211
3331
|
/**
|
|
3212
3332
|
* Subset of `StoragePort` exposed inside a `transaction(fn)` callback.
|
|
3213
|
-
* Lifecycle methods are intentionally omitted
|
|
3333
|
+
* Lifecycle methods are intentionally omitted, a transaction that
|
|
3214
3334
|
* tries to `init()` the adapter mid-flight is a category error.
|
|
3215
3335
|
*
|
|
3216
3336
|
* Every callable in the subset MUST run on the same underlying
|
|
@@ -3231,7 +3351,7 @@ interface ITransactionalStorage {
|
|
|
3231
3351
|
* Upsert a batch of fresh enrichment records produced by an
|
|
3232
3352
|
* extractor pass. Composite PK is `(nodePath, extractorId)`;
|
|
3233
3353
|
* conflict → replace. Every row lands with `stale = 0` (the
|
|
3234
|
-
* caller just refreshed it; ROADMAP §B.10
|
|
3354
|
+
* caller just refreshed it; ROADMAP §B.10, staleness is
|
|
3235
3355
|
* computed downstream when the body hash changes again).
|
|
3236
3356
|
*/
|
|
3237
3357
|
upsertMany(records: IEnrichmentRecord[]): Promise<void>;
|
|
@@ -3255,23 +3375,23 @@ interface StoragePort {
|
|
|
3255
3375
|
* Persist a fresh `ScanResult` (replace-all on the scan zone).
|
|
3256
3376
|
* Called by `sm scan` after the orchestrator returns. The renames /
|
|
3257
3377
|
* extractor-runs / enrichments side bags ride along inside the
|
|
3258
|
-
* same transaction
|
|
3378
|
+
* same transaction, the call is atomic from the caller's view.
|
|
3259
3379
|
*/
|
|
3260
3380
|
persist(result: ScanResult, opts?: IPersistOptions): Promise<void>;
|
|
3261
3381
|
/**
|
|
3262
3382
|
* Hydrate the persisted `ScanResult`. Returns the snapshot the
|
|
3263
|
-
* scan zone holds today (including external-Provider kinds
|
|
3383
|
+
* scan zone holds today (including external-Provider kinds,
|
|
3264
3384
|
* `node.kind` is open string per `node.schema.json`).
|
|
3265
3385
|
*/
|
|
3266
3386
|
load(): Promise<ScanResult>;
|
|
3267
3387
|
/**
|
|
3268
|
-
* Spec § A.9
|
|
3388
|
+
* Spec § A.9, fine-grained extractor-runs cache breadcrumbs.
|
|
3269
3389
|
* Returns `Map<nodePath, Map<qualifiedExtractorId, IPriorExtractorRun>>`.
|
|
3270
3390
|
* Inner value carries `bodyHash` AND `sidecarAnnotationsHash`; both
|
|
3271
3391
|
* participate in the cache hit condition for every Extractor.
|
|
3272
3392
|
*/
|
|
3273
3393
|
loadExtractorRuns(): Promise<Map<string, Map<string, IPriorExtractorRun>>>;
|
|
3274
|
-
/** Universal enrichment layer
|
|
3394
|
+
/** Universal enrichment layer, every persisted `(node, extractor)` pair. */
|
|
3275
3395
|
loadNodeEnrichments(): Promise<IPersistedEnrichment[]>;
|
|
3276
3396
|
/**
|
|
3277
3397
|
* Row counts for `scan_nodes` / `scan_links` / `scan_issues`.
|
|
@@ -3287,7 +3407,7 @@ interface StoragePort {
|
|
|
3287
3407
|
findNode(path: string): Promise<INodeBundle | null>;
|
|
3288
3408
|
};
|
|
3289
3409
|
/**
|
|
3290
|
-
* Phase 3 / View contribution system
|
|
3410
|
+
* Phase 3 / View contribution system, read access to
|
|
3291
3411
|
* `scan_contributions`, plus the targeted purge used by
|
|
3292
3412
|
* `sm plugins disable` to clear stale rows immediately at toggle time.
|
|
3293
3413
|
* Bulk writes still happen exclusively via
|
|
@@ -3341,6 +3461,22 @@ interface StoragePort {
|
|
|
3341
3461
|
issues: {
|
|
3342
3462
|
/** Every issue from the latest scan, in insertion order. */
|
|
3343
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>;
|
|
3344
3480
|
/**
|
|
3345
3481
|
* Issue rows whose runtime `Issue` shape passes `predicate`.
|
|
3346
3482
|
* `port.issues.findActive((i) => i.analyzerId === 'orphan')` is the
|
|
@@ -3390,19 +3526,19 @@ interface StoragePort {
|
|
|
3390
3526
|
* `path.resolve()`. The CLI's `sm job prune --orphan-files` flow
|
|
3391
3527
|
* pairs this set with `kernel/jobs/orphan-files.ts:findOrphanJobFiles`
|
|
3392
3528
|
* (which walks the directory) to compute the MD files on disk that
|
|
3393
|
-
* no row references
|
|
3529
|
+
* no row references, keeps the storage layer FS-free.
|
|
3394
3530
|
*/
|
|
3395
3531
|
listReferencedFilePaths(): Promise<Set<string>>;
|
|
3396
3532
|
};
|
|
3397
3533
|
/**
|
|
3398
3534
|
* Generic key/value preferences keyed by a stable string. Backs the
|
|
3399
|
-
* `config_preferences` table
|
|
3535
|
+
* `config_preferences` table, one row per `key`, `value_json` is a
|
|
3400
3536
|
* single JSON blob the caller serialises. Keys with the `_kernel.`
|
|
3401
3537
|
* prefix are reserved for kernel-managed entries (today: the
|
|
3402
3538
|
* update-check cache); user-set preferences land under unprefixed
|
|
3403
3539
|
* keys when those ship.
|
|
3404
3540
|
*
|
|
3405
|
-
* Read-only by design at the port level
|
|
3541
|
+
* Read-only by design at the port level, the only writer is the
|
|
3406
3542
|
* CLI's post-run hook (`cli/util/update-check-banner.ts`), which
|
|
3407
3543
|
* reaches the persistence helpers directly. The port surfaces the
|
|
3408
3544
|
* read so the BFF's `GET /api/update-status` projection can stay
|
|
@@ -3412,31 +3548,31 @@ interface StoragePort {
|
|
|
3412
3548
|
/**
|
|
3413
3549
|
* Load the update-check cache row. Returns `null` when the row
|
|
3414
3550
|
* is absent, malformed JSON, or fails the shape guard. Never
|
|
3415
|
-
* throws
|
|
3551
|
+
* throws, read failures degrade silently because the banner is
|
|
3416
3552
|
* a non-essential surface.
|
|
3417
3553
|
*/
|
|
3418
3554
|
loadUpdateCheckCache(): Promise<IUpdateCheckCache | null>;
|
|
3419
3555
|
/**
|
|
3420
3556
|
* Upsert the update-check cache row. Always overwrites the
|
|
3421
3557
|
* existing JSON blob in place. `updated_at` tracks wall-clock
|
|
3422
|
-
* now
|
|
3558
|
+
* now, separate from the embedded `checkedAt` field, which
|
|
3423
3559
|
* the caller controls.
|
|
3424
3560
|
*/
|
|
3425
3561
|
saveUpdateCheckCache(cache: IUpdateCheckCache): Promise<void>;
|
|
3426
3562
|
};
|
|
3427
3563
|
favorites: {
|
|
3428
3564
|
/**
|
|
3429
|
-
* Mark `path` as favorited. Idempotent
|
|
3565
|
+
* Mark `path` as favorited. Idempotent, a second call refreshes
|
|
3430
3566
|
* `favoritedAt` but does not error. The path is FK-semantic to
|
|
3431
3567
|
* `scan_nodes.path`; the route layer is responsible for confirming
|
|
3432
3568
|
* the path exists in the live scan before calling.
|
|
3433
3569
|
*/
|
|
3434
3570
|
set(path: string): Promise<void>;
|
|
3435
|
-
/** Drop the favorite row for `path`. Idempotent
|
|
3571
|
+
/** Drop the favorite row for `path`. Idempotent, no-op when absent. */
|
|
3436
3572
|
unset(path: string): Promise<void>;
|
|
3437
3573
|
/**
|
|
3438
3574
|
* Load every favorited path as a `Set<string>` ready for `O(1)`
|
|
3439
|
-
* membership checks. Used by the BFF's `/api/nodes` decorator
|
|
3575
|
+
* membership checks. Used by the BFF's `/api/nodes` decorator,
|
|
3440
3576
|
* one query per request, no SQL JOIN against `scan_nodes`.
|
|
3441
3577
|
*/
|
|
3442
3578
|
listPaths(): Promise<Set<string>>;
|
|
@@ -3510,7 +3646,7 @@ interface StoragePort {
|
|
|
3510
3646
|
}
|
|
3511
3647
|
|
|
3512
3648
|
/**
|
|
3513
|
-
* `FilesystemPort
|
|
3649
|
+
* `FilesystemPort`, walks roots, reads nodes, writes job files.
|
|
3514
3650
|
*
|
|
3515
3651
|
* Shape-only. The real adapter ships with the scan end-to-end pipeline.
|
|
3516
3652
|
*/
|
|
@@ -3531,7 +3667,7 @@ interface FilesystemPort {
|
|
|
3531
3667
|
}
|
|
3532
3668
|
|
|
3533
3669
|
/**
|
|
3534
|
-
* `RunnerPort
|
|
3670
|
+
* `RunnerPort`, executes an action against a rendered job file.
|
|
3535
3671
|
*
|
|
3536
3672
|
* Shape-only. `ClaudeCliRunner` + `MockRunner` land with the job subsystem
|
|
3537
3673
|
* (job subsystem + first summarizer).
|
|
@@ -3552,7 +3688,7 @@ interface RunnerPort {
|
|
|
3552
3688
|
}
|
|
3553
3689
|
|
|
3554
3690
|
/**
|
|
3555
|
-
* `LoggerPort
|
|
3691
|
+
* `LoggerPort`, structured logging port for the kernel.
|
|
3556
3692
|
*
|
|
3557
3693
|
* The kernel must NOT write to stdout/stderr directly. Anything that
|
|
3558
3694
|
* would historically have been a `console.log` / `console.error` goes
|
|
@@ -3563,7 +3699,7 @@ interface RunnerPort {
|
|
|
3563
3699
|
*
|
|
3564
3700
|
* trace < debug < info < warn < error < silent
|
|
3565
3701
|
*
|
|
3566
|
-
* `silent` is a sentinel for filtering only
|
|
3702
|
+
* `silent` is a sentinel for filtering only, it never appears as a
|
|
3567
3703
|
* `LogRecord.level`. Setting an adapter to `silent` disables every
|
|
3568
3704
|
* method.
|
|
3569
3705
|
*/
|
|
@@ -3600,7 +3736,7 @@ interface LoggerPort {
|
|
|
3600
3736
|
* `InMemoryProgressEmitter`: callers that don't care get a working
|
|
3601
3737
|
* implementation that does nothing.
|
|
3602
3738
|
*
|
|
3603
|
-
* Every method is intentionally empty
|
|
3739
|
+
* Every method is intentionally empty, that IS the contract of this
|
|
3604
3740
|
* class. We disable `no-empty-function` for the whole file because
|
|
3605
3741
|
* adding `// eslint-disable-next-line` to each method would be noise.
|
|
3606
3742
|
*/
|
|
@@ -3625,7 +3761,7 @@ declare class SilentLogger implements LoggerPort {
|
|
|
3625
3761
|
* side-channel concern.
|
|
3626
3762
|
* - The active impl is a pointer; the exported `log` is a stable
|
|
3627
3763
|
* proxy. Imports made before `configureLogger` runs still see the
|
|
3628
|
-
* new impl on every call
|
|
3764
|
+
* new impl on every call, no "captured stale logger" bugs.
|
|
3629
3765
|
*
|
|
3630
3766
|
* Tradeoffs accepted:
|
|
3631
3767
|
* - Tests must call `resetLogger()` (or replace the active impl) in
|
|
@@ -3640,7 +3776,7 @@ declare const log: LoggerPort;
|
|
|
3640
3776
|
declare function configureLogger(impl: LoggerPort): void;
|
|
3641
3777
|
/** Restore the default `SilentLogger`. Call from test teardown. */
|
|
3642
3778
|
declare function resetLogger(): void;
|
|
3643
|
-
/** Inspect the active logger. Test-only
|
|
3779
|
+
/** Inspect the active logger. Test-only, production code uses `log`. */
|
|
3644
3780
|
declare function getActiveLogger(): LoggerPort;
|
|
3645
3781
|
|
|
3646
3782
|
/**
|
|
@@ -3659,7 +3795,7 @@ declare function getActiveLogger(): LoggerPort;
|
|
|
3659
3795
|
* Error policy: a hook that throws is caught here, logged through a
|
|
3660
3796
|
* synthetic `extension.error` event with kind `hook-error`, and the
|
|
3661
3797
|
* caller continues. A buggy hook MUST NOT block the main pipeline (or
|
|
3662
|
-
* the CLI exit path)
|
|
3798
|
+
* the CLI exit path), that would invert the design intent (hooks
|
|
3663
3799
|
* REACT to events, they never steer them).
|
|
3664
3800
|
*
|
|
3665
3801
|
* The module lives under `kernel/extensions/` (alongside the `IHook`
|
|
@@ -3668,7 +3804,7 @@ declare function getActiveLogger(): LoggerPort;
|
|
|
3668
3804
|
* `analyzer.completed`, `action.completed`, `job.*`) and the CLI entry
|
|
3669
3805
|
* for the two CLI-process-driven triggers (`boot`, `shutdown`).
|
|
3670
3806
|
* Pulling the dispatcher out of the orchestrator keeps both consumers
|
|
3671
|
-
* symmetric
|
|
3807
|
+
* symmetric, same indexing, same filter semantics, same error
|
|
3672
3808
|
* policy.
|
|
3673
3809
|
*/
|
|
3674
3810
|
|
|
@@ -3697,20 +3833,20 @@ declare function makeEvent(type: string, data: unknown): ProgressEvent;
|
|
|
3697
3833
|
interface Kernel {
|
|
3698
3834
|
registry: Registry;
|
|
3699
3835
|
/**
|
|
3700
|
-
* Step 9.6.6
|
|
3836
|
+
* Step 9.6.6, read-only catalog of plugin-contributed annotation
|
|
3701
3837
|
* keys, keyed by `(pluginId, key)`. Populated at plugin-load time;
|
|
3702
3838
|
* pure read with no side effects. Built-in catalog (from
|
|
3703
3839
|
* `annotations.schema.json`) is NOT included here.
|
|
3704
3840
|
*/
|
|
3705
3841
|
getRegisteredAnnotationKeys: () => readonly IRegisteredAnnotationKey[];
|
|
3706
3842
|
/**
|
|
3707
|
-
* Internal
|
|
3843
|
+
* Internal, replace the frozen catalog. Called once by the
|
|
3708
3844
|
* plugin runtime composer after every plugin has loaded; consumers
|
|
3709
3845
|
* MUST treat the resulting array as immutable.
|
|
3710
3846
|
*/
|
|
3711
3847
|
setRegisteredAnnotationKeys: (entries: readonly IRegisteredAnnotationKey[]) => void;
|
|
3712
3848
|
/**
|
|
3713
|
-
* Step 11.x
|
|
3849
|
+
* Step 11.x, read-only catalog of plugin-contributed view
|
|
3714
3850
|
* contributions, keyed by `(pluginId, extensionId, contributionId)`.
|
|
3715
3851
|
* Populated at plugin-load time; pure read with no side effects.
|
|
3716
3852
|
* Mirror of `getRegisteredAnnotationKeys` for the view contribution
|
|
@@ -3719,7 +3855,7 @@ interface Kernel {
|
|
|
3719
3855
|
*/
|
|
3720
3856
|
getRegisteredViewContributions: () => readonly IRegisteredViewContribution[];
|
|
3721
3857
|
/**
|
|
3722
|
-
* Internal
|
|
3858
|
+
* Internal, replace the frozen view-contribution catalog. Called
|
|
3723
3859
|
* once by the plugin runtime composer after every plugin has loaded;
|
|
3724
3860
|
* consumers MUST treat the resulting array as immutable.
|
|
3725
3861
|
*/
|