@happyvertical/smrt-scanner 0.51.32 → 0.51.33

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/AGENTS.md CHANGED
@@ -306,3 +306,12 @@ place, `toKnowledgeAgentSurface` in `vite-plugin/index.ts`.
306
306
  invoking it. Any other expression (a call, a computed reference) yields
307
307
  `related: undefined` — writing the raw source through produced a garbage FK
308
308
  table name and, once FK columns are indexed, a garbage index (#2379).
309
+
310
+
311
+ ## Filesystem knowledge
312
+
313
+ The public `@happyvertical/smrt-scanner/knowledge` entry owns filesystem-only
314
+ package/doc discovery, linked module-doc parsing, and knowledge-graph freshness.
315
+ Core's existing knowledge entrypoints reexport these implementations for
316
+ compatibility. Keep this entry free of ORM, SQL, AI, and configuration runtime
317
+ imports so standalone development tooling remains lightweight.
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Agent-surface matcher (#2591).
3
+ *
4
+ * The class scanner discovers `@smrt()`-decorated classes by matching
5
+ * DECORATORS. Neither a view intent (#2588) nor a playbook (#2589) is a class,
6
+ * so this module adds the second recognizable shape the framework emits from:
7
+ * a **module-scope call with a single object-literal argument**.
8
+ *
9
+ * ```ts
10
+ * import { defineIntent } from '@happyvertical/smrt-web/intents';
11
+ * export const nextPage = defineIntent({ id: 'orders.next_page', ... });
12
+ * ```
13
+ *
14
+ * It keeps exactly the discipline the decorator matcher keeps:
15
+ *
16
+ * - **structural only** — nothing is evaluated, no import is followed, no
17
+ * module is loaded. A value that is not spelled literally in the argument is
18
+ * not a value this matcher can read;
19
+ * - **never a silent drop** — every declaration this matcher recognizes but
20
+ * cannot read produces a diagnostic naming `useWebMcpTool`, the documented
21
+ * escape hatch for a tool set that genuinely cannot be static. A declaration
22
+ * that vanishes from the emitted surface without a word is the failure mode
23
+ * this module exists to prevent;
24
+ * - **binding-aware** — a call is matched only when its callee resolves to an
25
+ * import binding from the ONE accepted specifier per helper. A local helper
26
+ * that happens to be named `defineIntent` is not a view intent.
27
+ *
28
+ * Accepted import specifiers, exactly:
29
+ *
30
+ * | Helper | Specifier |
31
+ * | --- | --- |
32
+ * | `defineIntent` | `@happyvertical/smrt-web/intents` |
33
+ * | `definePlaybook` | `@happyvertical/smrt-playbooks` |
34
+ *
35
+ * `defineIntent` deliberately ships ONLY from the `/intents` subpath entry, so
36
+ * an `OrderTable.intents.ts` sidecar never drags the client-data engine into a
37
+ * page (see `packages/smrt-web/AGENTS.md`). Recognizing the subpath and not the
38
+ * package root is therefore part of the contract, not an omission.
39
+ */
40
+ import type { AgentSurface, AgentSurfaceDiagnostic } from './types.js';
41
+ /**
42
+ * Whether the agent-surface emitter reads this file. **This is the one
43
+ * authority on that question.**
44
+ *
45
+ * Both the scanner's declaration pass and `dev:knowledge-check`'s freshness
46
+ * re-scan call it, because the two answering differently is not a cosmetic
47
+ * inconsistency: a file the emitter reads but the checker skips is reported as
48
+ * "no longer present in source" on every run, and a file the checker reads but
49
+ * the emitter skips is reported as "missing from smrt-knowledge.json" — both
50
+ * unclearable by any rebuild. It is a path predicate rather than a glob so the
51
+ * two sides cannot drift through differing glob semantics either.
52
+ *
53
+ * Callers still pass globs to prune the WALK for speed; this decides what
54
+ * counts.
55
+ */
56
+ export declare function isAgentSurfaceSourcePath(filePath: string, rootDir?: string): boolean;
57
+ /**
58
+ * Whether a path lies in a directory declarations are never read from,
59
+ * measured **relative to the project root**.
60
+ *
61
+ * `rootDir` is not optional in spirit. Matching these segments against an
62
+ * absolute path would disable the entire feature for a checkout that merely
63
+ * LIVES under one — a container with `WORKDIR /build`, or a clone in
64
+ * `~/build/…` — and it would do so with no diagnostic at all, because the
65
+ * freshness check applies the same predicate and would agree that nothing is
66
+ * declared. That is the silent drop this module exists to prevent, so the same
67
+ * care `discovery.ts` takes to rewrite globs relative to `cwd` applies here.
68
+ *
69
+ * Exported so the `.svelte` pass and `dev:knowledge-check`'s walk prune
70
+ * identically; a `.svelte` file cannot go through
71
+ * {@link isAgentSurfaceSourcePath}, which rejects it on extension.
72
+ */
73
+ export declare function isPrunedAgentSurfacePath(filePath: string, rootDir?: string): boolean;
74
+ export interface ExtractAgentSurfaceOptions {
75
+ /** Program body from an OXC parse. */
76
+ body: readonly unknown[];
77
+ /** Full source text, used for diagnostic line/column. */
78
+ sourceText: string;
79
+ /** Path recorded on every emitted entry and diagnostic. */
80
+ filePath: string;
81
+ }
82
+ /**
83
+ * Fast pre-check: a file that names neither helper cannot declare either, so
84
+ * the AST walk is skipped entirely. Callers use it to keep the matcher off the
85
+ * hot path of a large scan.
86
+ */
87
+ export declare function sourceMayDeclareAgentSurface(sourceText: string): boolean;
88
+ /**
89
+ * Match `defineIntent(...)` / `definePlaybook(...)` declarations in one parsed
90
+ * module.
91
+ *
92
+ * @returns Emittable entries plus a diagnostic for every recognized call that
93
+ * is not emittable. A recognized call always produces exactly one of the two.
94
+ */
95
+ export declare function extractAgentSurface(options: ExtractAgentSurfaceOptions): AgentSurface;
96
+ export declare function scanSvelteAgentSurface(filePath: string, sourceText?: string): AgentSurfaceDiagnostic[];
97
+ /**
98
+ * Merge per-file results into ONE deterministic surface.
99
+ *
100
+ * Emission must not depend on the order the file system happened to hand files
101
+ * to the scanner — a cross-profile parity snapshot that churns on directory
102
+ * order proves nothing. So identity is total and content-derived:
103
+ *
104
+ * - an intent is identified by its `id`, a playbook by its `key`;
105
+ * - entries sort by that identity, then by the recorded source path;
106
+ * - when two files declare the same identity, the entry from the
107
+ * lexicographically smaller path wins and the other is reported as a
108
+ * `duplicate-identity` diagnostic — a rule that gives the same answer for
109
+ * every input order, which "first one scanned wins" does not;
110
+ * - diagnostics sort by path, then line, column, code, and message.
111
+ *
112
+ * `relativize` maps an absolute scan path to the stable path recorded in the
113
+ * artifact; callers pass the package root's relativizer so a checked-in
114
+ * artifact never carries a machine-specific absolute path.
115
+ */
116
+ export declare function mergeAgentSurfaces(surfaces: readonly AgentSurface[], relativize?: (filePath: string) => string): AgentSurface;
117
+ /**
118
+ * One WebMCP tool name the build will register for a generated model action,
119
+ * supplied by the caller that knows the exposure policy.
120
+ */
121
+ export interface GeneratedModelToolName {
122
+ /**
123
+ * The tool name EXACTLY as it will be registered.
124
+ *
125
+ * This pass never derives a generated name and never applies a namespace of
126
+ * its own: it compares the strings it is handed. That is deliberate. The
127
+ * WebMCP `namespace` is a runtime `<Provider webmcp={{ namespace }}>` value
128
+ * that no build artifact records, so inventing a build-time declaration of
129
+ * it here would create a second place to say what the provider already says,
130
+ * free to disagree with it silently. Taking names instead means a caller
131
+ * that ever does know the namespace qualifies them itself and nothing in
132
+ * this module changes.
133
+ */
134
+ name: string;
135
+ /** What owns that name, for the message — e.g. `Product.list`. */
136
+ declaredBy?: string;
137
+ }
138
+ export interface AgentSurfaceToolNameOptions {
139
+ /**
140
+ * Generated model tool names for the SAME manifest, already filtered by the
141
+ * exposure policy.
142
+ *
143
+ * Filtered, not enumerated: comparing against every verb a class COULD
144
+ * expose would report collisions with tools that are never registered.
145
+ * `buildWebMcpToolDefinitions` in `@happyvertical/smrt-core/vite-plugin` is
146
+ * the function that emits the runtime definitions, so its output is the only
147
+ * honest input here.
148
+ */
149
+ generatedToolNames?: readonly GeneratedModelToolName[];
150
+ /**
151
+ * Prefixes the fixed UI tools are mounted under, defaulting to the
152
+ * registrar's own `smrt_ui_`.
153
+ *
154
+ * Supplied for the same reason `generatedToolNames` is: a name is compared
155
+ * against tools that will really register. Quantifying over every prefix an
156
+ * app COULD have configured — deriving the would-be prefix from the name —
157
+ * looks like more coverage and is the opposite. Under the default it has no
158
+ * true positive at all, because an id flattening to `smrt_ui_*` is rejected
159
+ * by {@link intentIdentityProblem} before it can become an entry, so every
160
+ * diagnostic such a rule emits for a default-configured app is false. A
161
+ * warning that is always wrong where it fires is worse than silence: it is
162
+ * persisted into the knowledge artifact, counted by `smrt doctor`, and
163
+ * clearable only by renaming an intent that was correct.
164
+ *
165
+ * There is no build-time source for this today — `ui.prefix` is a runtime
166
+ * `<Provider webmcp={{ ui: { prefix } }}>` prop — so the default leaves this
167
+ * half dormant. That is the honest state of it, and the seam is ready for
168
+ * the caller that can fill it.
169
+ */
170
+ uiToolPrefixes?: readonly string[];
171
+ }
172
+ /**
173
+ * Report an emitted intent whose derived WebMCP tool name is already spoken
174
+ * for by something OUTSIDE the declared surface (#2725).
175
+ *
176
+ * `mergeAgentSurfaces` resolves the collisions it can see on its own —
177
+ * intent vs intent, where `intentToolName` is not injective. It cannot see the
178
+ * two sources that live outside the declaration set:
179
+ *
180
+ * - **generated model tools**, `${className.toLowerCase()}_${action}`, so
181
+ * `defineIntent({ id: 'product.list' })` lands exactly on `Product.list`;
182
+ * - **the six fixed UI tools** under a CUSTOM `webmcp.ui.prefix`. The default
183
+ * `smrt_ui_` is already a hard rejection in {@link intentIdentityProblem},
184
+ * mirroring `defineIntent`; a custom prefix is not, because `defineIntent`
185
+ * accepts such an id and the intent really does register.
186
+ *
187
+ * ### These are warnings, not drops — unlike intent vs intent
188
+ *
189
+ * The asymmetry is real and it is the whole reason this is a separate pass.
190
+ * Two colliding intents are a closed question: `defineIntent` REJECTS the
191
+ * second declaration, so only one can exist and emitting both would overstate
192
+ * the surface by an entry that cannot be. Neither collision here is closed:
193
+ *
194
+ * - `defineIntent` accepts the intent, so the declaration is real and the
195
+ * entry belongs in the artifact. Dropping it would make the emitted surface
196
+ * disagree with the source, which is the failure this module exists to
197
+ * prevent;
198
+ * - which registration survives is decided at mount by the document-global
199
+ * tool-name lock (#2613), which rejects the second with a
200
+ * `WebMcpToolNameCollisionError` naming the owner. That is a runtime answer
201
+ * to a question the build can already see coming, and it costs whoever loses
202
+ * its tool; the build-time notice is the earlier, cheaper one;
203
+ * - and whether it happens at all depends on runtime values no artifact
204
+ * records: a WebMCP `namespace` moves every generated tool out of the way,
205
+ * an `effects` policy can exclude the action, and a page need not mount
206
+ * both. A build-time drop would be a guess about all three.
207
+ *
208
+ * So the entry stays and the report is advisory. The message names both sides,
209
+ * the consequence, and — because those runtime values are unknowable here and
210
+ * a `namespace` genuinely dissolves the collision — the precondition under
211
+ * which it holds. Without that last part a namespaced app gets a notice that
212
+ * is always wrong where it fires, recommending the very remedy it already
213
+ * applied, with renaming a correct intent as the only clearing action. That is
214
+ * the standard `uiToolPrefixes` exists to meet; the generated half has to meet
215
+ * it too, and it does so in the message because the caller CAN supply
216
+ * namespaced names when it knows them.
217
+ *
218
+ * @param surface - A MERGED surface; intent-vs-intent losers are already gone.
219
+ * @returns Diagnostics only. The caller appends them; nothing is removed.
220
+ */
221
+ export declare function checkAgentSurfaceToolNames(surface: AgentSurface, options?: AgentSurfaceToolNameOptions): AgentSurfaceDiagnostic[];
222
+ /** An empty surface, for callers that skipped the scan. */
223
+ export declare function emptyAgentSurface(): AgentSurface;
224
+ //# sourceMappingURL=agent-surface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-surface.d.ts","sourceRoot":"","sources":["../src/agent-surface.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAKH,OAAO,KAAK,EACV,YAAY,EAEZ,sBAAsB,EAKvB,MAAM,YAAY,CAAC;AAqKpB;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAKT;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAmBT;AAutBD,MAAM,WAAW,0BAA0B;IACzC,sCAAsC;IACtC,IAAI,EAAE,SAAS,OAAO,EAAE,CAAC;IACzB,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAExE;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,0BAA0B,GAClC,YAAY,CA6Kd;AAqED,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,MAAM,GAClB,sBAAsB,EAAE,CA4B1B;AAUD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,SAAS,YAAY,EAAE,EACjC,UAAU,GAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAA+B,GAChE,YAAY,CAoHd;AAMD;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;;;;;;;;;OAWG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,2BAA2B;IAC1C;;;;;;;;;OASG;IACH,kBAAkB,CAAC,EAAE,SAAS,sBAAsB,EAAE,CAAC;IACvD;;;;;;;;;;;;;;;;;;;OAmBG;IACH,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,YAAY,EACrB,OAAO,GAAE,2BAAgC,GACxC,sBAAsB,EAAE,CAyE1B;AAED,2DAA2D;AAC3D,wBAAgB,iBAAiB,IAAI,YAAY,CAEhD"}
package/dist/cli.d.ts CHANGED
@@ -1 +1,21 @@
1
- export {}
1
+ #!/usr/bin/env node
2
+ /**
3
+ * smrt-scan CLI
4
+ *
5
+ * High-performance TypeScript scanner for SMRT manifest generation.
6
+ *
7
+ * Usage:
8
+ * smrt-scan [options] [directory]
9
+ *
10
+ * Options:
11
+ * -o, --output <file> Output manifest file (default: stdout)
12
+ * -i, --include <glob> Include patterns (comma-separated)
13
+ * -e, --exclude <glob> Exclude patterns (comma-separated)
14
+ * --json Output as JSON (default)
15
+ * --stats Output scan statistics
16
+ * --benchmark Run performance benchmark
17
+ * -h, --help Show help
18
+ * -v, --version Show version
19
+ */
20
+ export {};
21
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;GAiBG"}
@@ -0,0 +1,15 @@
1
+ export interface SourceDiscoveryOptions {
2
+ cwd: string;
3
+ include: string[];
4
+ exclude: string[];
5
+ followSymbolicLinks?: boolean;
6
+ }
7
+ /**
8
+ * Discover authored source files with the bounded policy shared by every
9
+ * scanner entry point, including ManifestBuilder's preflight (#2275).
10
+ */
11
+ export declare function discoverSourceFiles(options: SourceDiscoveryOptions): Promise<string[]>;
12
+ /** Preserve relative glob escapes; normalize separators only after rewriting. */
13
+ export declare function relativeGlobToCwd(pattern: string, cwd: string): string;
14
+ export declare function normalizeGlobSeparators(pattern: string, pathSeparator?: "\\" | "/"): string;
15
+ //# sourceMappingURL=discovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,MAAM,EAAE,CAAC,CAgBnB;AAED,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAkCtE;AAED,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,aAAa,aAAM,GAClB,MAAM,CAIR"}