@happyvertical/smrt-scanner 0.43.10 → 0.44.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/AGENTS.md +146 -1
- package/dist/chunks/{scanner-CW9g-vyS.js → scanner-C3VqXyzB.js} +815 -19
- package/dist/chunks/scanner-C3VqXyzB.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +261 -0
- package/dist/index.js +3 -3
- package/dist/types.d.ts +126 -0
- package/package.json +1 -1
- package/dist/chunks/scanner-CW9g-vyS.js.map +0 -1
package/AGENTS.md
CHANGED
|
@@ -36,10 +36,141 @@ executes the source.
|
|
|
36
36
|
Driven by `scripts/verify-manifest-completeness.mjs` from `prepack`.
|
|
37
37
|
- `discoverSourceFiles(options)` — shared bounded source-discovery policy used
|
|
38
38
|
by `OxcScanner` and the core manifest preflight.
|
|
39
|
+
- `extractAgentSurface` / `scanSvelteAgentSurface` / `mergeAgentSurfaces` —
|
|
40
|
+
the agent-surface matcher (#2591). See below.
|
|
39
41
|
- Types (re-exported from `./types`): `RawClassDefinition`,
|
|
40
42
|
`RawFieldDefinition`, `RawMethodDefinition`, `ResolvedClassDefinition`,
|
|
41
43
|
`ScanResults`, `FileScanResult`, `OxcScannerOptions`, `InferredFieldType`,
|
|
42
|
-
`FieldTypeInference
|
|
44
|
+
`FieldTypeInference`, `AgentSurface`, `AgentSurfaceIntent`,
|
|
45
|
+
`AgentSurfacePlaybook`, `AgentSurfaceDiagnostic`.
|
|
46
|
+
|
|
47
|
+
## The agent-surface matcher (#2591)
|
|
48
|
+
|
|
49
|
+
`@smrt()` classes are found by matching DECORATORS. A view intent (#2588) and a
|
|
50
|
+
playbook (#2589) are not classes, so `agent-surface.ts` adds the second shape
|
|
51
|
+
the framework emits from: a **module-scope call with one object-literal
|
|
52
|
+
argument**. `ScanResults.agentSurface` carries the merged result.
|
|
53
|
+
|
|
54
|
+
### What it accepts
|
|
55
|
+
|
|
56
|
+
| Requirement | Why |
|
|
57
|
+
|---|---|
|
|
58
|
+
| A `.ts` / `.tsx` / `.js` / `.jsx` module | the scanner never reads `.svelte` |
|
|
59
|
+
| Callee bound by an import from exactly one specifier — `defineIntent` from `@happyvertical/smrt-web/intents`, `definePlaybook` from `@happyvertical/smrt-playbooks` | a local function of the same name is not a declaration. `defineIntent` ships solely from the `/intents` subpath so a sidecar drags in no client-data engine; matching the package root would invent a specifier |
|
|
60
|
+
| The call directly at module scope — `const x = f({…})`, `export const x = f({…})`, `f({…});`, `export default f({…})`, or an element of a module-scope array initializer | anything deeper is conditional on control flow; an array literal is not |
|
|
61
|
+
| Exactly one argument, an `ObjectExpression` whose values are literals, literal objects, or literal arrays | there is nothing to read otherwise |
|
|
62
|
+
|
|
63
|
+
Named imports (aliased or not) and namespace imports (`intents.defineIntent`)
|
|
64
|
+
both resolve; a default import does not, since neither package has one.
|
|
65
|
+
|
|
66
|
+
**Declaration discovery is NOT bound to the class-scan `include` glob.** A model
|
|
67
|
+
scan is routinely narrowed to where models live — the shipped SvelteKit template
|
|
68
|
+
uses `src/lib/objects/**/*.ts` — but an intent sidecar lives beside the component
|
|
69
|
+
that uses it. So `agentSurfaceInclude` (default `**/*.{ts,tsx,js,jsx}`, the
|
|
70
|
+
extensions the Vite plugin's own default accepts) is globbed separately, files
|
|
71
|
+
the class pass already parsed are skipped so nothing is counted twice, and the
|
|
72
|
+
token pre-filter keeps a non-declaring file at one read. Binding the two globs
|
|
73
|
+
together made sidecars vanish from every artifact with no diagnostic at all —
|
|
74
|
+
the exact silent omission this matcher exists to prevent.
|
|
75
|
+
|
|
76
|
+
**`isAgentSurfaceSourcePath` is the single authority on what counts as a
|
|
77
|
+
declaration source**, and it is a path predicate, not a glob. Both passes here
|
|
78
|
+
and `dev:knowledge-check`'s freshness re-scan call it. That matters because the
|
|
79
|
+
two sides disagreeing is not cosmetic: a file the emitter reads but the checker
|
|
80
|
+
skips reports as "no longer present in source" forever, and the reverse reports
|
|
81
|
+
as "missing from smrt-knowledge.json" forever — neither clearable by a rebuild.
|
|
82
|
+
It rejects `.d.ts`, `*.test.*`, `*.spec.*`, any hidden (dot) segment, and
|
|
83
|
+
anything under `node_modules`, `dist`, `build`, `coverage`, `__tests__`, or
|
|
84
|
+
`__typechecks__` — measured
|
|
85
|
+
**relative to the scan root**, via the companion `isPrunedAgentSurfacePath`
|
|
86
|
+
(which the `.svelte` passes call directly, since a `.svelte` path is rejected on
|
|
87
|
+
extension by the source predicate). Relative matching is load-bearing: against
|
|
88
|
+
an absolute path, a checkout that merely LIVES under `build/` — a container with
|
|
89
|
+
`WORKDIR /build`, a clone in `~/build/…` — would drop every declaration with no
|
|
90
|
+
diagnostic, and the freshness check would apply the same rule and agree, so the
|
|
91
|
+
artifact would ship an empty surface and `smrt doctor` would call it healthy.
|
|
92
|
+
This is the trap `discovery.ts` rewrites globs relative to `cwd` to avoid.
|
|
93
|
+
|
|
94
|
+
`dist` is not paranoia. A caller's `exclude` REPLACES `DEFAULT_EXCLUDE`, and
|
|
95
|
+
every real caller passes a narrower one (the Vite plugin sends only test globs
|
|
96
|
+
plus `node_modules`), so this whole-project pass would otherwise walk build
|
|
97
|
+
output. A transpiling build — `tsc`, `svelte-package`, vite lib mode with
|
|
98
|
+
`@happyvertical/*` externalized — keeps both the import specifier and the
|
|
99
|
+
module-scope call in its output, so `dist/foo.intents.js` matches this matcher
|
|
100
|
+
exactly; and since `dist` sorts before `src`, it would WIN the duplicate tie and
|
|
101
|
+
become the recorded source of a declaration nobody wrote there.
|
|
102
|
+
|
|
103
|
+
### What it refuses, always with a diagnostic
|
|
104
|
+
|
|
105
|
+
Never a silent omission — every message names `useWebMcpTool`, the escape hatch
|
|
106
|
+
for a tool set genuinely derived from computed or fetched data:
|
|
107
|
+
|
|
108
|
+
| Code | Shape |
|
|
109
|
+
|---|---|
|
|
110
|
+
| `non-literal-argument` | an identifier reference, a spread (in an object or an array), a call, a conditional, a template literal (interpolated **or not**), a computed or shorthand key, a computed unary, or an argument that is not an object literal |
|
|
111
|
+
| `not-module-scope` | declared inside a function, class, conditional, or loop |
|
|
112
|
+
| `argument-count` | not exactly one argument |
|
|
113
|
+
| `incomplete-declaration` | the literal parsed but lacks `id`/`description`/`target` (intent) or `key`/`title`/`description`/`steps` (playbook) |
|
|
114
|
+
| `invalid-identity` | a declaration the runtime helper itself would reject: an intent `id` that is not lowercase and dot-namespaced, over 128 chars, or resolving into the reserved `smrt_ui_` namespace; an unknown declaration, target, or capability key; a malformed `capability` (bad `effect`, non-boolean flag); a `target` outside the closed `control`/`dataSurface` unions or missing a required `controlId`; a playbook with no steps, an empty/non-array/unknown-plane `planes`, an `onStepFailure` outside `abort`/`continue`, a non-boolean `enabled`, or a step whose `model` is not a qualified pair |
|
|
115
|
+
| `svelte-declaration` | written inline in a `.svelte` file |
|
|
116
|
+
| `duplicate-identity` | two modules declare the same `id`/`key`, or two intent ids derive the same WebMCP tool name |
|
|
117
|
+
|
|
118
|
+
These rules are mirrored from `defineIntent` and `definePlaybook` (this package
|
|
119
|
+
cannot depend on `smrt-web` or `smrt-playbooks`), and keeping them in step is
|
|
120
|
+
load-bearing: the declaration types `id` as `string`, so `id: 'Orders.Bad'`
|
|
121
|
+
type-checks and fails only at page load. An entry the runtime would reject is
|
|
122
|
+
worse than no entry, because the artifact and `smrt doctor` would then advertise
|
|
123
|
+
an operation that can never register. The same applies to the tool name, which
|
|
124
|
+
is `id` with `.`/`-` replaced by `_` and is therefore **not injective** —
|
|
125
|
+
`orders.foo_bar` and `orders.foo.bar` collide, and `defineIntent` rejects the
|
|
126
|
+
second registration.
|
|
127
|
+
|
|
128
|
+
Invalid values are **rejected, never repaired**. That matters most for a
|
|
129
|
+
playbook's `planes`: `definePlaybook` throws on an empty or unknown-plane list,
|
|
130
|
+
so defaulting it here would emit an entry asserting `server` validity the author
|
|
131
|
+
never declared — the exact fail-open the plane rule exists to prevent. The same
|
|
132
|
+
holds for `capability`: the fail-closed default is for an OMITTED capability,
|
|
133
|
+
not a typo'd one, so `{ effect: 'reed' }` is a diagnostic rather than a silent
|
|
134
|
+
`destructive`. Playbook *keys* get no pattern check, because `definePlaybook`
|
|
135
|
+
imposes none; only uniqueness applies. **If either runtime tightens its rules,
|
|
136
|
+
tighten these too.**
|
|
137
|
+
|
|
138
|
+
This is deliberately narrower than the decorator-config extractor, which
|
|
139
|
+
RESOLVES spreads against module-scope constants. That one must, because a
|
|
140
|
+
dropped `@smrt({ ...CFG })` key silently reopens an exposure surface. Here the
|
|
141
|
+
requirement runs the other way: an emitted entry has to be exactly what an
|
|
142
|
+
author can see in one object literal, so a partial resolution would be worse
|
|
143
|
+
than a refusal.
|
|
144
|
+
|
|
145
|
+
The `.svelte` pass is textual, not a Svelte parse: it requires the import
|
|
146
|
+
specifier plus a call, and it exists only to say "move this to a `.ts` sidecar"
|
|
147
|
+
— which is the answer regardless of what the declaration contains. It resolves
|
|
148
|
+
the local names the file's own import binds, so `defineIntent as declare`
|
|
149
|
+
followed by `declare({…})` is caught, and it tolerates whitespace before the
|
|
150
|
+
parenthesis; requiring the literal token `defineIntent(` would let exactly the
|
|
151
|
+
case this pass exists for slip through unremarked. Any `*.svelte` exclude a
|
|
152
|
+
caller passes for the class scan is dropped here, since callers routinely
|
|
153
|
+
exclude Svelte because OXC cannot parse it, and honouring that would silence the
|
|
154
|
+
one thing the pass is for.
|
|
155
|
+
|
|
156
|
+
### Deterministic identity
|
|
157
|
+
|
|
158
|
+
`mergeAgentSurfaces` makes emission independent of file order, so a
|
|
159
|
+
cross-profile parity snapshot does not churn on directory order:
|
|
160
|
+
|
|
161
|
+
- an intent is identified by `id`, a playbook by `key`;
|
|
162
|
+
- entries sort by identity, then by source path;
|
|
163
|
+
- on a duplicate identity the **lexicographically smaller path wins** and the
|
|
164
|
+
other becomes a `duplicate-identity` diagnostic. "First one scanned wins"
|
|
165
|
+
would give different answers for different input orders;
|
|
166
|
+
- diagnostics sort by path, line, column, code, message;
|
|
167
|
+
- paths are recorded `cwd`-relative and POSIX-separated, so a checked-in
|
|
168
|
+
artifact is neither machine- nor platform-specific.
|
|
169
|
+
|
|
170
|
+
The scanner mirrors the #2587 capability vocabulary structurally instead of
|
|
171
|
+
importing `@happyvertical/smrt-types`: core depends on this package, so the
|
|
172
|
+
reverse edge would close a cycle. Core reconciles the two shapes in exactly one
|
|
173
|
+
place, `toKnowledgeAgentSurface` in `vite-plugin/index.ts`.
|
|
43
174
|
|
|
44
175
|
## Discovery boundaries
|
|
45
176
|
|
|
@@ -87,6 +218,11 @@ exhausts the heap when the scanner is pointed at an application root (#2275):
|
|
|
87
218
|
- `src/manifest-adapter.ts` — `ManifestAdapter`: raw → manifest conversion and
|
|
88
219
|
field-type inference.
|
|
89
220
|
- `src/verify-completeness.ts` — `verifyManifestCompleteness` publish guard.
|
|
221
|
+
- `src/agent-surface.ts` — the `defineIntent` / `definePlaybook` matcher, its
|
|
222
|
+
diagnostics, and `mergeAgentSurfaces` (#2591).
|
|
223
|
+
- `src/source-location.ts` — `getLineColumn`, split out so `agent-surface.ts`
|
|
224
|
+
can resolve a diagnostic's position without importing `oxc-parser.ts`, which
|
|
225
|
+
imports it back.
|
|
90
226
|
- `src/types.ts` — shared raw/resolved/result type definitions.
|
|
91
227
|
- `src/cli.ts` / `bin/smrt-scan.js` — the `smrt-scan` CLI.
|
|
92
228
|
|
|
@@ -128,6 +264,15 @@ exhausts the heap when the scanner is pointed at an application root (#2275):
|
|
|
128
264
|
but its taint replays into the diagnostics of every decorator that spreads
|
|
129
265
|
it, transitively through constant chains. Without that the silent drop simply
|
|
130
266
|
moves one level up. An unused tainted constant reports nothing.
|
|
267
|
+
- **A non-static `defineIntent`/`definePlaybook` warns, it does not fail the
|
|
268
|
+
build** — unlike an unresolved `@smrt()` spread, which errors. The asymmetry
|
|
269
|
+
is deliberate: a dropped decorator spread reopens an exposure surface, while a
|
|
270
|
+
computed tool set is a legitimate choice with a supported path
|
|
271
|
+
(`useWebMcpTool`). What is never acceptable is the declaration disappearing
|
|
272
|
+
unremarked, so it is recorded in `ScanResults.agentSurface.diagnostics`,
|
|
273
|
+
printed by the Vite plugin, carried into `smrt-knowledge.json`, reported by
|
|
274
|
+
`smrt doctor`, and surfaced by `dev:knowledge-check` as
|
|
275
|
+
`agent-surface-not-static`.
|
|
131
276
|
- **Relationship targets are resolved, not copied**: `@foreignKey`,
|
|
132
277
|
`@oneToMany` and `@manyToMany` arguments arrive as raw source text.
|
|
133
278
|
`'Target'`/`Target` pass through and a forward-reference thunk
|