@happyvertical/smrt-scanner 0.47.0 → 0.47.2
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 +71 -2
- package/dist/chunks/{scanner-9p-PUN-X.js → scanner-CxUeI0FI.js} +43 -2
- package/dist/chunks/scanner-CxUeI0FI.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +117 -4
- package/dist/index.js +2 -2
- package/dist/types.d.ts +9 -4
- package/package.json +1 -1
- package/dist/chunks/scanner-9p-PUN-X.js.map +0 -1
package/AGENTS.md
CHANGED
|
@@ -38,6 +38,9 @@ executes the source.
|
|
|
38
38
|
by `OxcScanner` and the core manifest preflight.
|
|
39
39
|
- `extractAgentSurface` / `scanSvelteAgentSurface` / `mergeAgentSurfaces` —
|
|
40
40
|
the agent-surface matcher (#2591). See below.
|
|
41
|
+
- `checkAgentSurfaceToolNames(surface, { generatedToolNames, uiToolPrefixes })`
|
|
42
|
+
— report an emitted intent whose derived WebMCP tool name a generated model
|
|
43
|
+
tool or a fixed UI tool already registers (#2725). Takes names, never config.
|
|
41
44
|
- Types (re-exported from `./types`): `RawClassDefinition`,
|
|
42
45
|
`RawFieldDefinition`, `RawMethodDefinition`, `ResolvedClassDefinition`,
|
|
43
46
|
`ScanResults`, `FileScanResult`, `OxcScannerOptions`, `InferredFieldType`,
|
|
@@ -79,7 +82,9 @@ build output can retain declarations and otherwise win duplicate resolution.
|
|
|
79
82
|
### What it refuses, always with a diagnostic
|
|
80
83
|
|
|
81
84
|
Never a silent omission — every message names `useWebMcpTool`, the escape hatch
|
|
82
|
-
for a tool set genuinely derived from computed or fetched data
|
|
85
|
+
for a tool set genuinely derived from computed or fetched data. Every code here
|
|
86
|
+
means the declaration was NOT emitted; `tool-name-collision` (below) is the one
|
|
87
|
+
exception and is advisory:
|
|
83
88
|
|
|
84
89
|
| Code | Shape |
|
|
85
90
|
|---|---|
|
|
@@ -90,6 +95,7 @@ for a tool set genuinely derived from computed or fetched data:
|
|
|
90
95
|
| `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 |
|
|
91
96
|
| `svelte-declaration` | written inline in a `.svelte` file |
|
|
92
97
|
| `duplicate-identity` | two modules declare the same `id`/`key`, or two intent ids derive the same WebMCP tool name |
|
|
98
|
+
| `tool-name-collision` | an EMITTED intent whose derived tool name is already registered by a generated model tool or a fixed UI tool (see below) |
|
|
93
99
|
|
|
94
100
|
Mirror `defineIntent` and `definePlaybook` validation without importing their
|
|
95
101
|
packages; update the scanner whenever those runtime rules tighten. Reject
|
|
@@ -120,6 +126,68 @@ cross-profile parity snapshot does not churn on directory order:
|
|
|
120
126
|
- paths are recorded `cwd`-relative and POSIX-separated, so a checked-in
|
|
121
127
|
artifact is neither machine- nor platform-specific.
|
|
122
128
|
|
|
129
|
+
### Collisions with names this pass does not own (#2725)
|
|
130
|
+
|
|
131
|
+
`mergeAgentSurfaces` resolves intent-vs-intent by dropping the loser, because
|
|
132
|
+
`defineIntent` REJECTS the second colliding declaration — only one can exist.
|
|
133
|
+
Two other sources register into the same document and are invisible to the
|
|
134
|
+
declaration scan, so `checkAgentSurfaceToolNames` reports them **without
|
|
135
|
+
dropping anything**:
|
|
136
|
+
|
|
137
|
+
- **generated model tools**, `${className.toLowerCase()}_${action}`, so
|
|
138
|
+
`defineIntent({ id: 'product.list' })` lands on `Product.list`;
|
|
139
|
+
- **the six fixed UI tools** under the configured `webmcp.ui.prefix`.
|
|
140
|
+
|
|
141
|
+
Warnings, not drops, and the asymmetry is the point: `defineIntent` accepts
|
|
142
|
+
these ids, so the declarations are real and belong in the artifact; the
|
|
143
|
+
document-global tool-name lock (#2613) decides which registration survives,
|
|
144
|
+
rejecting the second with a `WebMcpToolNameCollisionError` — a runtime answer to
|
|
145
|
+
a question the build can already see coming, which costs whoever loses its tool;
|
|
146
|
+
and whether it happens at all turns on runtime values no artifact records (a
|
|
147
|
+
WebMCP `namespace`, an `effects` policy, whether a page mounts both). A
|
|
148
|
+
build-time drop would guess all three, and the emitted surface would stop
|
|
149
|
+
matching the source.
|
|
150
|
+
|
|
151
|
+
Because those values are unknowable here, the generated-tool message **states
|
|
152
|
+
the precondition it assumes** — no `namespace`, action within the `effects`
|
|
153
|
+
policy — and says to disregard it when either already separates the pair. A
|
|
154
|
+
`namespace` prefixes generated tools and leaves intents alone, so it dissolves
|
|
155
|
+
the collision outright; without that sentence a namespaced app would get a
|
|
156
|
+
notice that is always wrong where it fires, recommending the remedy it had
|
|
157
|
+
already applied. `smrt doctor`'s header is `Tool name also claimed` for the same
|
|
158
|
+
reason: it must not assert past what the message says.
|
|
159
|
+
|
|
160
|
+
**It takes NAMES, never config.** The caller passes the tool names that will
|
|
161
|
+
really register; this module never derives a generated name and never applies a
|
|
162
|
+
namespace. `namespace` and `ui.prefix` are runtime `<Provider webmcp={…}>`
|
|
163
|
+
values that no build artifact records, so a build-time declaration of either
|
|
164
|
+
would be a second place to say what the provider already says, free to diverge
|
|
165
|
+
silently. Core supplies the names from `buildWebMcpToolDefinitions` — the same
|
|
166
|
+
function that emits the runtime `webMcpToolDefinitions`, and therefore already
|
|
167
|
+
filtered by the exposure policy. Comparing against every verb a class *could*
|
|
168
|
+
expose would invent collisions with tools nobody mounts.
|
|
169
|
+
|
|
170
|
+
`uiToolPrefixes` is supplied for the same reason, defaulting to the registrar's
|
|
171
|
+
`smrt_ui_`. Do NOT replace it with a prefix derived from the name: quantifying
|
|
172
|
+
over every prefix an app could have configured has, under the default, no true
|
|
173
|
+
positive at all — an id flattening to `smrt_ui_*` is rejected during extraction
|
|
174
|
+
— so every diagnostic it emits for a default-configured app is false, persisted
|
|
175
|
+
into the artifact and clearable only by renaming a correct intent. There is no
|
|
176
|
+
build-time source for `ui.prefix` (a runtime `<Provider>` prop), so this half is
|
|
177
|
+
dormant by default and the seam waits for a caller that can fill it.
|
|
178
|
+
|
|
179
|
+
`RESERVED_TOOL_NAME_PREFIX` is **not** configurable and must not become so — it
|
|
180
|
+
mirrors `defineIntent`'s own literal, which rejects the id wherever an app moved
|
|
181
|
+
its UI tools, so accepting one here would emit an entry the runtime refuses to
|
|
182
|
+
construct.
|
|
183
|
+
|
|
184
|
+
Consumers: core's Vite plugin runs the check once the manifest exists and
|
|
185
|
+
appends the diagnostics to the emitted surface; `dev:knowledge-check` maps the
|
|
186
|
+
code to the `agent-surface-tool-name-collision` warning and **excludes it from
|
|
187
|
+
the artifact-drift comparison**, which re-derives declarations from source and
|
|
188
|
+
has no manifest to produce one from; `smrt doctor` prints it under
|
|
189
|
+
`⚠️ Tool name also claimed`, separately from `Not statically emittable`.
|
|
190
|
+
|
|
123
191
|
The scanner mirrors the #2587 capability vocabulary structurally instead of
|
|
124
192
|
importing `@happyvertical/smrt-types`: core depends on this package, so the
|
|
125
193
|
reverse edge would close a cycle. Core reconciles the two shapes in exactly one
|
|
@@ -160,7 +228,8 @@ place, `toKnowledgeAgentSurface` in `vite-plugin/index.ts`.
|
|
|
160
228
|
field-type inference.
|
|
161
229
|
- `src/verify-completeness.ts` — `verifyManifestCompleteness` publish guard.
|
|
162
230
|
- `src/agent-surface.ts` — the `defineIntent` / `definePlaybook` matcher, its
|
|
163
|
-
diagnostics,
|
|
231
|
+
diagnostics, `mergeAgentSurfaces` (#2591), and `checkAgentSurfaceToolNames`
|
|
232
|
+
(#2725).
|
|
164
233
|
- `src/source-location.ts` — `getLineColumn`, split out so `agent-surface.ts`
|
|
165
234
|
can resolve a diagnostic's position without importing `oxc-parser.ts`, which
|
|
166
235
|
imports it back.
|
|
@@ -29,6 +29,16 @@ var INTENT_ID_PATTERN = /^[a-z][a-z0-9]*(?:\.[a-z0-9][a-z0-9_]*)+$/;
|
|
|
29
29
|
var INTENT_ID_MAX_LENGTH = 128;
|
|
30
30
|
var DESCRIPTION_MAX_LENGTH = 1024;
|
|
31
31
|
var RESERVED_TOOL_NAME_PREFIX = "smrt_ui_";
|
|
32
|
+
var FIXED_UI_TOOL_SUFFIXES = [
|
|
33
|
+
"execute_data_surface_control",
|
|
34
|
+
"execute_form_control",
|
|
35
|
+
"inspect_data_surface",
|
|
36
|
+
"inspect_form_control",
|
|
37
|
+
"list_data_surfaces",
|
|
38
|
+
"list_form_controls"
|
|
39
|
+
];
|
|
40
|
+
var UI_TOOL_PREFIX_PATTERN = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
|
|
41
|
+
var DEFAULT_UI_TOOL_PREFIX = "smrt_ui_";
|
|
32
42
|
var INTENT_DECLARATION_KEYS = /* @__PURE__ */ new Set([
|
|
33
43
|
"id",
|
|
34
44
|
"description",
|
|
@@ -668,6 +678,37 @@ function mergeAgentSurfaces(surfaces, relativize = (filePath) => filePath) {
|
|
|
668
678
|
diagnostics: diagnostics.sort((a, b) => compareStrings(a.filePath, b.filePath) || (a.line ?? 0) - (b.line ?? 0) || (a.column ?? 0) - (b.column ?? 0) || compareStrings(a.code, b.code) || compareStrings(a.message, b.message))
|
|
669
679
|
};
|
|
670
680
|
}
|
|
681
|
+
function checkAgentSurfaceToolNames(surface, options = {}) {
|
|
682
|
+
const diagnostics = [];
|
|
683
|
+
const generated = /* @__PURE__ */ new Map();
|
|
684
|
+
for (const tool of options.generatedToolNames ?? []) if (!generated.has(tool.name)) generated.set(tool.name, tool);
|
|
685
|
+
const uiTools = /* @__PURE__ */ new Map();
|
|
686
|
+
for (const prefix of options.uiToolPrefixes ?? [DEFAULT_UI_TOOL_PREFIX]) {
|
|
687
|
+
if (!UI_TOOL_PREFIX_PATTERN.test(prefix)) continue;
|
|
688
|
+
for (const suffix of FIXED_UI_TOOL_SUFFIXES) if (!uiTools.has(`${prefix}${suffix}`)) uiTools.set(`${prefix}${suffix}`, prefix);
|
|
689
|
+
}
|
|
690
|
+
for (const intent of surface.intents) {
|
|
691
|
+
const toolName = intentToolName(intent.id);
|
|
692
|
+
const collidingTool = generated.get(toolName);
|
|
693
|
+
if (collidingTool) {
|
|
694
|
+
const owner = collidingTool.declaredBy ? `\`${collidingTool.declaredBy}\`` : "a generated model action";
|
|
695
|
+
diagnostics.push({
|
|
696
|
+
code: "tool-name-collision",
|
|
697
|
+
helper: "defineIntent",
|
|
698
|
+
message: `view intent \`${intent.id}\` derives the WebMCP tool name \`${toolName}\`, which is also the generated model tool for ${owner}. Both are emitted \u2014 \`defineIntent\` accepts the id, so the declaration is real. On a page that mounts both with no WebMCP \`namespace\`, the document-global tool-name lock rejects whichever registers second with a \`WebMcpToolNameCollisionError\`. A build cannot see the provider's \`namespace\` or \`effects\` policy, so if either already separates this pair, disregard this. Otherwise set a \`namespace\`, which prefixes the generated tools and leaves intents alone, or rename the intent.`,
|
|
699
|
+
filePath: intent.filePath
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
const uiPrefix = uiTools.get(toolName);
|
|
703
|
+
if (uiPrefix !== void 0) diagnostics.push({
|
|
704
|
+
code: "tool-name-collision",
|
|
705
|
+
helper: "defineIntent",
|
|
706
|
+
message: `view intent \`${intent.id}\` derives the WebMCP tool name \`${toolName}\`, which is also one of the six fixed UI tools mounted under \`ui.prefix\` \`${uiPrefix}\`. \`defineIntent\` accepts the id because it reserves only the default \`smrt_ui_\` prefix, so the two reach the document-global tool-name lock and whichever registers second is rejected with a \`WebMcpToolNameCollisionError\`. Rename the intent, or give the fixed UI tools a different \`ui.prefix\`.`,
|
|
707
|
+
filePath: intent.filePath
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
return diagnostics.sort((a, b) => compareStrings(a.filePath, b.filePath) || compareStrings(a.code, b.code) || compareStrings(a.message, b.message));
|
|
711
|
+
}
|
|
671
712
|
function emptyAgentSurface() {
|
|
672
713
|
return {
|
|
673
714
|
intents: [],
|
|
@@ -3095,6 +3136,6 @@ var OxcScanner = class {
|
|
|
3095
3136
|
}
|
|
3096
3137
|
};
|
|
3097
3138
|
//#endregion
|
|
3098
|
-
export {
|
|
3139
|
+
export { scanSvelteAgentSurface as _, parseFile as a, discoverSourceFiles as c, checkAgentSurfaceToolNames as d, emptyAgentSurface as f, mergeAgentSurfaces as g, isPrunedAgentSurfacePath as h, parseAgentSurfaceFile as i, normalizeGlobSeparators as l, isAgentSurfaceSourcePath as m, ManifestAdapter as n, parseSource as o, extractAgentSurface as p, extractSmrtImports as r, InheritanceResolver as s, OxcScanner as t, relativeGlobToCwd as u, sourceMayDeclareAgentSurface as v };
|
|
3099
3140
|
|
|
3100
|
-
//# sourceMappingURL=scanner-
|
|
3141
|
+
//# sourceMappingURL=scanner-CxUeI0FI.js.map
|