@happyvertical/smrt-scanner 0.46.0 → 0.47.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 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, and `mergeAgentSurfaces` (#2591).
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.
@@ -214,6 +283,21 @@ place, `toKnowledgeAgentSurface` in `vite-plugin/index.ts`.
214
283
  printed by the Vite plugin, carried into `smrt-knowledge.json`, reported by
215
284
  `smrt doctor`, and surfaced by `dev:knowledge-check` as
216
285
  `agent-surface-not-static`.
286
+ - **A parameter carries type PROVENANCE, a field does not.**
287
+ `RawParameterDefinition.typeUnresolved` marks an annotation the scanner could
288
+ not express (intersection, tuple, conditional, mapped, `typeof`, indexed
289
+ access) — `type` still reads `'any'` for compatibility, so that flag is the
290
+ only thing separating it from an authored `any`. `memberTypes` carries the
291
+ members of an INLINE object literal, which `extractTypeName` otherwise
292
+ flattens to `'object'`; NAMED bags stay unexpanded on purpose. Core's API
293
+ wire-ability gate fails closed on both (#2686). The bare `object` keyword is
294
+ resolved for parameters only — `ManifestAdapter.inferFromAnnotation` gives an
295
+ `object`-typed FIELD a `{}` column default, so naming it in `extractTypeName`
296
+ would silently change DDL.
297
+ - **`@method()` config is read with `requireLiteralValues`**, like `@smrt()`: an
298
+ unresolvable value is a scan error, because a dropped `expose: false` restores
299
+ the routing the author was withholding. `decoratorConfig` is `undefined` for
300
+ an undecorated method and `{}` for a bare `@method()`; the two differ.
217
301
  - **Relationship targets are resolved, not copied**: `@foreignKey`,
218
302
  `@oneToMany` and `@manyToMany` arguments arrive as raw source text.
219
303
  `'Target'`/`Target` pass through and a forward-reference thunk
@@ -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: [],
@@ -1080,7 +1121,8 @@ function parseFile(filePath) {
1080
1121
  smrtImports = extractSmrtImports(program.body);
1081
1122
  const ctx = {
1082
1123
  constants: extractModuleObjectConstants(program.body, sourceText),
1083
- unresolved: []
1124
+ unresolved: [],
1125
+ importAliases
1084
1126
  };
1085
1127
  for (const node of program.body) {
1086
1128
  const extracted = extractClassFromNode(node, filePath, sourceText, importAliases, ctx);
@@ -1164,7 +1206,8 @@ function parseSource(sourceText, filename = "test.ts") {
1164
1206
  smrtImports = extractSmrtImports(program.body);
1165
1207
  const ctx = {
1166
1208
  constants: extractModuleObjectConstants(program.body, sourceText),
1167
- unresolved: []
1209
+ unresolved: [],
1210
+ importAliases
1168
1211
  };
1169
1212
  for (const node of program.body) {
1170
1213
  const extracted = extractClassFromNode(node, filename, sourceText, importAliases, ctx);
@@ -1491,7 +1534,7 @@ function reportUnresolvedSpreads(unresolved, filePath, sourceText, errors) {
1491
1534
  for (const spread of unresolved) {
1492
1535
  const loc = spread.start === void 0 ? void 0 : getLineColumn(sourceText, spread.start);
1493
1536
  errors.push({
1494
- message: `Cannot statically resolve \`${spread.expression}\` while expanding a @smrt() config. Only literal keys/values and unescaped module-scope \`const\` object literals in the same file are supported. Inline the keys or remove the mutation/alias \u2014 an unresolved expression would drop api/mcp/cli from the manifest, and an absent surface defaults to open.`,
1537
+ message: `Cannot statically resolve \`${spread.expression}\` while expanding a ${spread.decorator ?? "@smrt()"} config. Only literal keys/values and unescaped module-scope \`const\` object literals in the same file are supported. Inline the keys or remove the mutation/alias \u2014 an unresolved expression would drop keys from the manifest, and every absent exposure key defaults to open.`,
1495
1538
  filePath,
1496
1539
  line: loc?.line,
1497
1540
  column: loc?.column,
@@ -1508,9 +1551,9 @@ function extractClassFromNode(node, filePath, sourceText, importAliases, ctx) {
1508
1551
  function extractClassDeclaration(node, filePath, sourceText, importAliases, ctx) {
1509
1552
  const className = node.id?.name || "AnonymousClass";
1510
1553
  const decorators = node.decorators || [];
1511
- const smrtDecorator = decorators.find((d) => isSmrtDecorator(d));
1512
- const reportDecorator = decorators.find((d) => isNamedDecorator(d, "report"));
1513
- const tenantScopedDecorator = decorators.find((d) => isNamedDecorator(d, "TenantScoped"));
1554
+ const smrtDecorator = decorators.find((d) => isSmrtDecorator(d, importAliases));
1555
+ const reportDecorator = decorators.find((d) => isNamedDecorator(d, "report", importAliases));
1556
+ const tenantScopedDecorator = decorators.find((d) => isNamedDecorator(d, "TenantScoped", importAliases));
1514
1557
  const hasSmartDecorator = !!smrtDecorator;
1515
1558
  const smrtConfig = smrtDecorator ? extractDecoratorConfig(smrtDecorator, sourceText, ctx ? {
1516
1559
  ...ctx,
@@ -1528,7 +1571,7 @@ function extractClassDeclaration(node, filePath, sourceText, importAliases, ctx)
1528
1571
  const field = extractPropertyDefinition(member, sourceText);
1529
1572
  if (field) fields.push(field);
1530
1573
  } else if (member.type === "MethodDefinition") {
1531
- const method = extractMethodDefinition(member, sourceText);
1574
+ const method = extractMethodDefinition(member, sourceText, ctx);
1532
1575
  if (method) methods.push(method);
1533
1576
  }
1534
1577
  return {
@@ -1544,16 +1587,17 @@ function extractClassDeclaration(node, filePath, sourceText, importAliases, ctx)
1544
1587
  endLine: node.loc?.end.line || 1
1545
1588
  };
1546
1589
  }
1547
- function isSmrtDecorator(decorator) {
1548
- return isNamedDecorator(decorator, "smrt");
1590
+ function isSmrtDecorator(decorator, importAliases) {
1591
+ return isNamedDecorator(decorator, "smrt", importAliases);
1549
1592
  }
1550
- function isNamedDecorator(decorator, name) {
1593
+ function isNamedDecorator(decorator, name, importAliases) {
1551
1594
  const expr = decorator.expression;
1595
+ const resolve = (local) => importAliases?.get(local) ?? local;
1552
1596
  if (expr.type === "CallExpression") {
1553
1597
  const callee = expr.callee;
1554
- if (callee.type === "Identifier" && callee.name === name) return true;
1598
+ if (callee.type === "Identifier" && resolve(callee.name) === name) return true;
1555
1599
  }
1556
- if (expr.type === "Identifier" && expr.name === name) return true;
1600
+ if (expr.type === "Identifier" && resolve(expr.name) === name) return true;
1557
1601
  return false;
1558
1602
  }
1559
1603
  function extractDecoratorConfig(decorator, sourceText, ctx) {
@@ -1757,7 +1801,8 @@ function extractTypeName(type) {
1757
1801
  else if (type.typeName.type === "TSQualifiedName") baseName = getQualifiedName(type.typeName);
1758
1802
  const typeParams = type.typeArguments?.params || type.typeParameters?.params;
1759
1803
  if (baseName && typeParams?.length) {
1760
- const typeArgs = typeParams.map((p) => extractTypeName(p)).filter(Boolean);
1804
+ const typeArgs = typeParams.map((p) => extractTypeName(p));
1805
+ if (typeArgs.some((arg) => arg === null)) return null;
1761
1806
  if (typeArgs.length > 0) return `${baseName}<${typeArgs.join(", ")}>`;
1762
1807
  }
1763
1808
  return baseName;
@@ -1766,7 +1811,12 @@ function extractTypeName(type) {
1766
1811
  case "TSNumberKeyword": return "number";
1767
1812
  case "TSBooleanKeyword": return "boolean";
1768
1813
  case "TSAnyKeyword": return "any";
1814
+ case "TSUnknownKeyword": return "unknown";
1815
+ case "TSNeverKeyword": return "never";
1816
+ case "TSBigIntKeyword": return "bigint";
1817
+ case "TSSymbolKeyword": return "symbol";
1769
1818
  case "TSVoidKeyword": return "void";
1819
+ case "TSThisType": return "this";
1770
1820
  case "TSNullKeyword": return "null";
1771
1821
  case "TSUndefinedKeyword": return "undefined";
1772
1822
  case "TSLiteralType": {
@@ -1781,7 +1831,11 @@ function extractTypeName(type) {
1781
1831
  const elementType = extractTypeName(type.elementType);
1782
1832
  return elementType ? `${elementType}[]` : null;
1783
1833
  }
1784
- case "TSUnionType": return type.types.map((t) => extractTypeName(t)).filter(Boolean).join(" | ");
1834
+ case "TSUnionType": {
1835
+ const types = type.types.map((t) => extractTypeName(t));
1836
+ if (types.some((branch) => branch === null)) return null;
1837
+ return types.join(" | ");
1838
+ }
1785
1839
  case "TSTypeLiteral": return "object";
1786
1840
  case "TSFunctionType": return "Function";
1787
1841
  default: return null;
@@ -1861,7 +1915,7 @@ function extractFieldDecorator(decorator, sourceText) {
1861
1915
  arguments: args
1862
1916
  };
1863
1917
  }
1864
- function extractMethodDefinition(node, sourceText) {
1918
+ function extractMethodDefinition(node, sourceText, ctx) {
1865
1919
  if (node.kind !== "method") return null;
1866
1920
  const name = getPropertyKey(node.key);
1867
1921
  if (!name) return null;
@@ -1872,6 +1926,7 @@ function extractMethodDefinition(node, sourceText) {
1872
1926
  if (extracted) parameters.push(extracted);
1873
1927
  }
1874
1928
  const returnType = func.returnType ? extractTypeName(func.returnType.typeAnnotation) : null;
1929
+ const decoratorConfig = extractMethodDecoratorConfig(node, sourceText, ctx);
1875
1930
  return {
1876
1931
  name,
1877
1932
  async: func.async,
@@ -1880,21 +1935,113 @@ function extractMethodDefinition(node, sourceText) {
1880
1935
  parameters,
1881
1936
  returnType,
1882
1937
  description: null,
1938
+ ...decoratorConfig ? { decoratorConfig } : {},
1883
1939
  line: node.loc?.start.line || 0
1884
1940
  };
1885
1941
  }
1942
+ function extractMethodDecoratorConfig(node, sourceText, ctx) {
1943
+ const decorator = node.decorators?.find((d) => isNamedDecorator(d, "method", ctx?.importAliases));
1944
+ if (!decorator) return void 0;
1945
+ const unresolvedBefore = ctx?.unresolved.length ?? 0;
1946
+ const config = extractDecoratorConfig(decorator, sourceText, ctx ? {
1947
+ ...ctx,
1948
+ requireLiteralValues: true
1949
+ } : ctx);
1950
+ if (ctx) for (const entry of ctx.unresolved.slice(unresolvedBefore)) entry.decorator = "@method()";
1951
+ return config ?? {};
1952
+ }
1953
+ var INLINE_MEMBER_TYPE_MAX_DEPTH = 4;
1954
+ function describeParameterType(annotation) {
1955
+ if (!annotation) return {
1956
+ type: null,
1957
+ typeUnresolved: false
1958
+ };
1959
+ const node = annotation.typeAnnotation;
1960
+ if (node.type === "TSObjectKeyword") return {
1961
+ type: "object",
1962
+ typeUnresolved: false
1963
+ };
1964
+ const type = extractTypeName(node);
1965
+ if (type === null) return {
1966
+ type: null,
1967
+ typeUnresolved: true
1968
+ };
1969
+ const members = [];
1970
+ const memberUnresolved = collectInlineMemberTypes(node, members, 0);
1971
+ let unionBranches;
1972
+ if (node.type === "TSUnionType") {
1973
+ const branches = [];
1974
+ for (const branch of node.types) {
1975
+ const branchType = branch.type === "TSObjectKeyword" ? "object" : extractTypeName(branch);
1976
+ if (branchType === null) return {
1977
+ type,
1978
+ typeUnresolved: true
1979
+ };
1980
+ const branchMembers = [];
1981
+ collectInlineMemberTypes(branch, branchMembers, 0);
1982
+ branches.push({
1983
+ type: branchType,
1984
+ ...branchMembers.length > 0 ? { memberTypes: [...new Set(branchMembers)] } : {}
1985
+ });
1986
+ }
1987
+ if (branches.length > 0) unionBranches = branches;
1988
+ }
1989
+ return {
1990
+ type,
1991
+ typeUnresolved: memberUnresolved,
1992
+ ...members.length > 0 ? { memberTypes: [...new Set(members)] } : {},
1993
+ ...unionBranches ? { unionBranches } : {}
1994
+ };
1995
+ }
1996
+ function collectInlineMemberTypes(node, out, depth) {
1997
+ if (depth > INLINE_MEMBER_TYPE_MAX_DEPTH) return false;
1998
+ switch (node.type) {
1999
+ case "TSTypeLiteral": {
2000
+ let unresolved = false;
2001
+ for (const member of node.members) {
2002
+ if (member.type === "TSMethodSignature") {
2003
+ out.push("Function");
2004
+ continue;
2005
+ }
2006
+ const memberAnnotation = member.typeAnnotation;
2007
+ if (!memberAnnotation) continue;
2008
+ const memberType = extractTypeName(memberAnnotation.typeAnnotation);
2009
+ if (memberType === null) {
2010
+ unresolved = true;
2011
+ continue;
2012
+ }
2013
+ out.push(memberType);
2014
+ if (collectInlineMemberTypes(memberAnnotation.typeAnnotation, out, depth + 1)) unresolved = true;
2015
+ }
2016
+ return unresolved;
2017
+ }
2018
+ case "TSArrayType": return collectInlineMemberTypes(node.elementType, out, depth + 1);
2019
+ case "TSUnionType": {
2020
+ let unresolved = false;
2021
+ for (const branch of node.types) if (collectInlineMemberTypes(branch, out, depth + 1)) unresolved = true;
2022
+ return unresolved;
2023
+ }
2024
+ case "TSTypeReference": {
2025
+ let unresolved = false;
2026
+ const typeParams = node.typeArguments?.params || node.typeParameters?.params;
2027
+ for (const param of typeParams ?? []) if (collectInlineMemberTypes(param, out, depth + 1)) unresolved = true;
2028
+ return unresolved;
2029
+ }
2030
+ default: return false;
2031
+ }
2032
+ }
1886
2033
  function extractParameter(param, sourceText) {
1887
2034
  if (param.type === "AssignmentPattern") {
1888
2035
  const left = param.left;
1889
2036
  if (left.type === "Identifier") return {
1890
2037
  name: left.name,
1891
- type: left.typeAnnotation ? extractTypeName(left.typeAnnotation.typeAnnotation) : null,
2038
+ ...describeParameterTypeFields(left.typeAnnotation),
1892
2039
  optional: true,
1893
2040
  defaultValue: sliceSource(param.right, sourceText)
1894
2041
  };
1895
2042
  if (left.type === "ObjectPattern" || left.type === "ArrayPattern") return {
1896
2043
  name: "options",
1897
- type: left.typeAnnotation ? extractTypeName(left.typeAnnotation.typeAnnotation) : "any",
2044
+ ...describeParameterTypeFields(left.typeAnnotation, "any"),
1898
2045
  optional: true,
1899
2046
  defaultValue: sliceSource(param.right, sourceText)
1900
2047
  };
@@ -1904,7 +2051,7 @@ function extractParameter(param, sourceText) {
1904
2051
  const arg = param.argument;
1905
2052
  if (arg.type === "Identifier") return {
1906
2053
  name: `...${arg.name}`,
1907
- type: param.typeAnnotation ? extractTypeName(param.typeAnnotation.typeAnnotation) : null,
2054
+ ...describeParameterTypeFields(param.typeAnnotation),
1908
2055
  optional: true,
1909
2056
  defaultValue: null
1910
2057
  };
@@ -1912,18 +2059,28 @@ function extractParameter(param, sourceText) {
1912
2059
  }
1913
2060
  if (param.type === "Identifier") return {
1914
2061
  name: param.name,
1915
- type: param.typeAnnotation ? extractTypeName(param.typeAnnotation.typeAnnotation) : null,
2062
+ ...describeParameterTypeFields(param.typeAnnotation),
1916
2063
  optional: param.optional || false,
1917
2064
  defaultValue: null
1918
2065
  };
1919
2066
  if (param.type === "ObjectPattern") return {
1920
2067
  name: "options",
1921
- type: param.typeAnnotation ? extractTypeName(param.typeAnnotation.typeAnnotation) : "any",
2068
+ ...describeParameterTypeFields(param.typeAnnotation, "any"),
1922
2069
  optional: false,
1923
2070
  defaultValue: null
1924
2071
  };
1925
2072
  return null;
1926
2073
  }
2074
+ function describeParameterTypeFields(annotation, missingAnnotationType = null) {
2075
+ if (!annotation) return { type: missingAnnotationType };
2076
+ const described = describeParameterType(annotation);
2077
+ return {
2078
+ type: described.type,
2079
+ ...described.typeUnresolved ? { typeUnresolved: true } : {},
2080
+ ...described.memberTypes ? { memberTypes: described.memberTypes } : {},
2081
+ ...described.unionBranches ? { unionBranches: described.unionBranches } : {}
2082
+ };
2083
+ }
1927
2084
  //#endregion
1928
2085
  //#region src/manifest-adapter.ts
1929
2086
  var MANIFEST_TIMESTAMP = 0;
@@ -2617,12 +2774,16 @@ var ManifestAdapter = class ManifestAdapter {
2617
2774
  name: p.name,
2618
2775
  type: p.type || "any",
2619
2776
  optional: p.optional,
2620
- default: p.defaultValue ? this.parseDefaultValue(p.defaultValue, "string") : void 0
2777
+ default: p.defaultValue ? this.parseDefaultValue(p.defaultValue, "string") : void 0,
2778
+ ...p.typeUnresolved ? { typeUnresolved: true } : {},
2779
+ ...p.memberTypes ? { memberTypes: p.memberTypes } : {},
2780
+ ...p.unionBranches ? { unionBranches: p.unionBranches } : {}
2621
2781
  })),
2622
2782
  returnType: method.returnType || "any",
2623
2783
  description: method.description || void 0,
2624
2784
  isStatic: method.isStatic,
2625
- isPublic: true
2785
+ isPublic: true,
2786
+ ...method.decoratorConfig ? { decoratorConfig: method.decoratorConfig } : {}
2626
2787
  };
2627
2788
  }
2628
2789
  /**
@@ -2975,6 +3136,6 @@ var OxcScanner = class {
2975
3136
  }
2976
3137
  };
2977
3138
  //#endregion
2978
- export { sourceMayDeclareAgentSurface as _, parseFile as a, discoverSourceFiles as c, emptyAgentSurface as d, extractAgentSurface as f, scanSvelteAgentSurface as g, mergeAgentSurfaces as h, parseAgentSurfaceFile as i, normalizeGlobSeparators as l, isPrunedAgentSurfacePath as m, ManifestAdapter as n, parseSource as o, isAgentSurfaceSourcePath as p, extractSmrtImports as r, InheritanceResolver as s, OxcScanner as t, relativeGlobToCwd as u };
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 };
2979
3140
 
2980
- //# sourceMappingURL=scanner-C3VqXyzB.js.map
3141
+ //# sourceMappingURL=scanner-CxUeI0FI.js.map