/** The declared agent-addressable surface found by one scan. */ export declare interface AgentSurface { intents: AgentSurfaceIntent[]; playbooks: AgentSurfacePlaybook[]; diagnostics: AgentSurfaceDiagnostic[]; } /** A fully resolved classification (#2587's fail-closed rule already applied). */ export declare interface AgentSurfaceCapability { effect: AgentSurfaceEffect; idempotent: boolean; openWorld: boolean; } /** * Something a reader of the emitted surface has to know about a declaration. * * Two kinds, and the difference is whether the entry survived. Every code but * `tool-name-collision` reports a declaration that is NOT emitted — never a * silent omission, so each of those messages names `useWebMcpTool`, the escape * hatch for a genuinely computed tool set. A `tool-name-collision` is * advisory: the declaration IS emitted, and the diagnostic records that its * derived WebMCP tool name is already spoken for by something outside the * declared surface. */ export declare interface AgentSurfaceDiagnostic { code: AgentSurfaceDiagnosticCode; helper: AgentSurfaceHelper; message: string; filePath: string; line?: number; column?: number; } /** Why a recognized declaration could not be emitted. */ export declare type AgentSurfaceDiagnosticCode = 'non-literal-argument' | 'not-module-scope' | 'argument-count' | 'incomplete-declaration' | 'invalid-identity' | 'svelte-declaration' | 'duplicate-identity' | 'tool-name-collision'; /** * Effect classification, mirroring `CapabilityEffect` in * `@happyvertical/smrt-types`. Mirrored rather than imported because this * package carries no `@happyvertical/*` dependency — core depends on it, and * importing back would close the cycle. */ export declare type AgentSurfaceEffect = 'read' | 'write' | 'destructive'; /** The two module-scope helpers the agent-surface matcher recognizes. */ export declare type AgentSurfaceHelper = 'defineIntent' | 'definePlaybook'; /** One emitted view intent (#2588). */ export declare interface AgentSurfaceIntent { kind: 'intent'; /** Declared, dot-namespaced identity. This is the entry's stable identity. */ id: string; description: string; capability: AgentSurfaceCapability; target: Record; /** Whether the declaration carried an `inputSchema` object. */ hasInputSchema: boolean; /** * Always exactly `['browser']`. An intent moves mounted browser state; a * server-side agent reaches one only through the #2446 command/ack bridge, * which the referencing PLAYBOOK declares. Typed as the literal tuple rather * than the open plane list so the contract cannot be read as wider than it is. */ planes: ['browser']; /** Declaring module, relativized by the emitting caller. */ filePath: string; } /** Plane a declaration is valid on. */ export declare type AgentSurfacePlane = 'browser' | 'server'; /** One emitted playbook (#2589). */ export declare interface AgentSurfacePlaybook { kind: 'playbook'; /** Declared registry key. This is the entry's stable identity. */ key: string; title: string; description: string; steps: AgentSurfacePlaybookStep[]; planes: AgentSurfacePlane[]; /** False when `planes` was derived from the step kinds rather than declared. */ planesDeclared: boolean; onStepFailure: 'abort' | 'continue'; enabled: boolean; /** Declaring module, relativized by the emitting caller. */ filePath: string; } /** One step of an emitted playbook (#2589). Playbooks cannot nest. */ export declare type AgentSurfacePlaybookStep = { kind: 'operation'; model: string; action: string; } | { kind: 'intent'; id: string; }; export declare interface AgentSurfaceToolNameOptions { /** * Generated model tool names for the SAME manifest, already filtered by the * exposure policy. * * Filtered, not enumerated: comparing against every verb a class COULD * expose would report collisions with tools that are never registered. * `buildWebMcpToolDefinitions` in `@happyvertical/smrt-core/vite-plugin` is * the function that emits the runtime definitions, so its output is the only * honest input here. */ generatedToolNames?: readonly GeneratedModelToolName[]; /** * Prefixes the fixed UI tools are mounted under, defaulting to the * registrar's own `smrt_ui_`. * * Supplied for the same reason `generatedToolNames` is: a name is compared * against tools that will really register. Quantifying over every prefix an * app COULD have configured — deriving the would-be prefix from the name — * looks like more coverage and is the opposite. Under the default it has no * true positive at all, because an id flattening to `smrt_ui_*` is rejected * by {@link intentIdentityProblem} before it can become an entry, so every * diagnostic such a rule emits for a default-configured app is false. A * warning that is always wrong where it fires is worse than silence: it is * persisted into the knowledge artifact, counted by `smrt doctor`, and * clearable only by renaming an intent that was correct. * * There is no build-time source for this today — `ui.prefix` is a runtime * `` prop — so the default leaves this * half dormant. That is the honest state of it, and the seam is ready for * the caller that can fill it. */ uiToolPrefixes?: readonly string[]; } declare interface ArrayExpression extends BaseNode { type: 'ArrayExpression'; elements: (Expression | SpreadElement | null)[]; } declare interface ArrayPattern extends BaseNode { type: 'ArrayPattern'; elements: (Pattern | null)[]; typeAnnotation?: TSTypeAnnotation; } declare interface AssignmentPattern extends BaseNode { type: 'AssignmentPattern'; left: Pattern; right: Expression; } declare interface BaseNode { type: string; loc?: SourceLocation; range?: [number, number]; start?: number; end?: number; } declare interface BlockStatement extends BaseNode { type: 'BlockStatement'; body: Statement[]; } declare interface CallExpression extends BaseNode { type: 'CallExpression'; callee: Expression; arguments: Expression[]; typeParameters?: TSTypeParameterInstantiation; } /** * Report an emitted intent whose derived WebMCP tool name is already spoken * for by something OUTSIDE the declared surface (#2725). * * `mergeAgentSurfaces` resolves the collisions it can see on its own — * intent vs intent, where `intentToolName` is not injective. It cannot see the * two sources that live outside the declaration set: * * - **generated model tools**, `${className.toLowerCase()}_${action}`, so * `defineIntent({ id: 'product.list' })` lands exactly on `Product.list`; * - **the six fixed UI tools** under a CUSTOM `webmcp.ui.prefix`. The default * `smrt_ui_` is already a hard rejection in {@link intentIdentityProblem}, * mirroring `defineIntent`; a custom prefix is not, because `defineIntent` * accepts such an id and the intent really does register. * * ### These are warnings, not drops — unlike intent vs intent * * The asymmetry is real and it is the whole reason this is a separate pass. * Two colliding intents are a closed question: `defineIntent` REJECTS the * second declaration, so only one can exist and emitting both would overstate * the surface by an entry that cannot be. Neither collision here is closed: * * - `defineIntent` accepts the intent, so the declaration is real and the * entry belongs in the artifact. Dropping it would make the emitted surface * disagree with the source, which is the failure this module exists to * prevent; * - which registration survives is decided at mount by the document-global * tool-name lock (#2613), which rejects the second with a * `WebMcpToolNameCollisionError` naming the owner. That is a runtime answer * to a question the build can already see coming, and it costs whoever loses * its tool; the build-time notice is the earlier, cheaper one; * - and whether it happens at all depends on runtime values no artifact * records: a WebMCP `namespace` moves every generated tool out of the way, * an `effects` policy can exclude the action, and a page need not mount * both. A build-time drop would be a guess about all three. * * So the entry stays and the report is advisory. The message names both sides, * the consequence, and — because those runtime values are unknowable here and * a `namespace` genuinely dissolves the collision — the precondition under * which it holds. Without that last part a namespaced app gets a notice that * is always wrong where it fires, recommending the very remedy it already * applied, with renaming a correct intent as the only clearing action. That is * the standard `uiToolPrefixes` exists to meet; the generated half has to meet * it too, and it does so in the message because the caller CAN supply * namespaced names when it knows them. * * @param surface - A MERGED surface; intent-vs-intent losers are already gone. * @returns Diagnostics only. The caller appends them; nothing is removed. */ export declare function checkAgentSurfaceToolNames(surface: AgentSurface, options?: AgentSurfaceToolNameOptions): AgentSurfaceDiagnostic[]; declare interface ClassBody extends BaseNode { type: 'ClassBody'; body: ClassElement[]; } declare interface ClassDeclaration extends BaseNode { type: 'ClassDeclaration'; id: Identifier | null; superClass: Expression | null; superTypeParameters?: TSTypeParameterInstantiation; superTypeArguments?: TSTypeParameterInstantiation; body: ClassBody; decorators?: Decorator[]; } declare type ClassElement = PropertyDefinition | MethodDefinition_2; /** * Classify an identifier as money, a rate, or neither. * * The head noun is the last word, or the last word before a trailing qualifier * such as `Paid`. A rate word anywhere in the name wins outright: `taxRate` is * a rate even though `tax` is money, and that precedence is what stops the two * vocabularies from fighting over the same field. */ export declare function classifyNumericFieldName(name: string): NumericPrecisionKind | undefined; declare interface Decorator extends BaseNode { type: 'Decorator'; expression: Expression; } /** * Discover authored source files with the bounded policy shared by every * scanner entry point, including ManifestBuilder's preflight (#2275). */ export declare function discoverSourceFiles(options: SourceDiscoveryOptions): Promise; /** An empty surface, for callers that skipped the scan. */ export declare function emptyAgentSurface(): AgentSurface; declare interface ExportDefaultDeclaration extends BaseNode { type: 'ExportDefaultDeclaration'; declaration: Statement | Expression; } declare interface ExportNamedDeclaration extends BaseNode { type: 'ExportNamedDeclaration'; declaration: Statement | null; specifiers: ExportSpecifier[]; } declare interface ExportSpecifier extends BaseNode { type: 'ExportSpecifier'; local: Identifier; exported: Identifier; } declare type Expression = Identifier | Literal | CallExpression | MemberExpression | ObjectExpression | ArrayExpression | NewExpression | UnaryExpression; /** * Describes a pre-loaded manifest from an installed SMRT package, used by * {@link InheritanceResolver} to resolve base classes that originate outside * the local project source. * * Build tooling (e.g. the SMRT CLI / vitest plugin) loads each installed * `@happyvertical/smrt-*` package's `manifest.json` and converts it into an * `ExternalManifest` before passing it to the scanner. * * @see {@link OxcScannerOptions.externalManifests} * @see {@link InheritanceResolver.addExternalManifest} */ export declare interface ExternalManifest { /** npm package name, e.g. `'@happyvertical/smrt-profiles'`. */ packageName: string; /** SemVer version string of the installed package. */ packageVersion: string; /** All class definitions exported by the package, keyed by class name. */ classes: Map; } /** * Match `defineIntent(...)` / `definePlaybook(...)` declarations in one parsed * module. * * @returns Emittable entries plus a diagnostic for every recognized call that * is not emittable. A recognized call always produces exactly one of the two. */ export declare function extractAgentSurface(options: ExtractAgentSurfaceOptions): AgentSurface; export declare interface ExtractAgentSurfaceOptions { /** Program body from an OXC parse. */ body: readonly unknown[]; /** Full source text, used for diagnostic line/column. */ sourceText: string; /** Path recorded on every emitted entry and diagnostic. */ filePath: string; } /* Excluded from this release type: extractSmrtImports */ declare interface FieldDefinition { type: 'text' | 'decimal' | 'boolean' | 'integer' | 'datetime' | 'json' | 'foreignKey' | 'crossPackageRef' | 'oneToMany' | 'manyToMany' | 'meta'; required?: boolean; default?: unknown; min?: number; max?: number; maxLength?: number; minLength?: number; related?: string; description?: string; _meta?: Record; transient?: boolean; /** Sensitive value — excluded from public serialization + where filtering. */ sensitive?: boolean; /** Read-only over generated write surfaces — stripped from create/update bodies. */ readonly?: boolean; /** Permission slug required before the field is included in public reads. */ readPermission?: string; } /** * Result produced by {@link ManifestAdapter.inferFieldType} for a single field. * * In addition to the inferred `type`, carries the `source` of the inference * so callers can distinguish authoritative decorator-driven results from * heuristic guesses and provide better diagnostics. * * @see {@link InferredFieldType} for valid `type` values. * @see {@link ManifestAdapter.inferFieldType} for inference priority rules. */ export declare interface FieldTypeInference { /** Inferred SMRT type */ type: InferredFieldType; /** Related class for relationship types */ related?: string; /** Default value if extractable */ defaultValue?: unknown; /** Whether field is required */ required: boolean; /** Inference source for debugging */ source: 'helper' | 'decorator' | 'annotation' | 'heuristic' | 'default'; /** Underlying type for meta fields (e.g., 'string' inside Meta) */ underlyingType?: InferredFieldType; /** * Decorator-derived metadata that should be merged into the manifest * field's `_meta` object. Used by `@crossPackageRef`, `@manyToMany`, * `@meta` to carry options (`validate`, `through`, `indexed`, `idType`, * etc.) that don't fit on the top-level FieldDefinition. */ _meta?: Record; } /** * Result from scanning a single file */ export declare interface FileScanResult { /** Source file path */ filePath: string; /** Classes found in file */ classes: RawClassDefinition[]; /** Scan errors */ errors: ScanError[]; /** Parse time in milliseconds */ parseTimeMs: number; /** Type alias declarations found in file (name → resolved type string) */ typeAliases: Record; /** SMRT package imports found in file (package name → Set of imported class names) */ smrtImports?: Map>; /** * Declared view intents and playbooks found in the file, plus a diagnostic * for every recognized declaration that is not statically emittable (#2591). * Omitted when the file declares none. */ agentSurface?: AgentSurface; } declare interface FunctionExpression extends BaseNode { type: 'FunctionExpression'; async: boolean; params: Pattern[]; returnType?: TSTypeAnnotation; body: BlockStatement; } /** * One WebMCP tool name the build will register for a generated model action, * supplied by the caller that knows the exposure policy. */ export declare interface GeneratedModelToolName { /** * The tool name EXACTLY as it will be registered. * * This pass never derives a generated name and never applies a namespace of * its own: it compares the strings it is handed. That is deliberate. The * WebMCP `namespace` is a runtime `` value * that no build artifact records, so inventing a build-time declaration of * it here would create a second place to say what the provider already says, * free to disagree with it silently. Taking names instead means a caller * that ever does know the namespace qualifies them itself and nothing in * this module changes. */ name: string; /** What owns that name, for the message — e.g. `Product.list`. */ declaredBy?: string; } declare interface Identifier extends BaseNode { type: 'Identifier'; name: string; typeAnnotation?: TSTypeAnnotation; optional?: boolean; } declare interface ImportDeclaration extends BaseNode { type: 'ImportDeclaration'; specifiers?: ImportSpecifierLike[]; source: Literal; } declare interface ImportDefaultSpecifier extends BaseNode { type: 'ImportDefaultSpecifier'; local: Identifier; } declare interface ImportNamespaceSpecifier extends BaseNode { type: 'ImportNamespaceSpecifier'; local: Identifier; } declare interface ImportSpecifier extends BaseNode { type: 'ImportSpecifier'; imported: Identifier; local: Identifier; } declare type ImportSpecifierLike = ImportSpecifier | ImportNamespaceSpecifier | ImportDefaultSpecifier; /** * The set of SMRT column types that the scanner can infer for a field. * * | Value | DB column type | Notes | * |---|---|---| * | `text` | `TEXT` / `VARCHAR` | Default for `string` and unknown types | * | `integer` | `INTEGER` | `number` with `= 0` initialiser | * | `decimal` | `DECIMAL` | `number` with `= 0.0` initialiser | * | `boolean` | `BOOLEAN` | `boolean` annotation or literal initialiser | * | `datetime` | `DATETIME` | `Date` annotation | * | `json` | `JSON` / `TEXT` | Arrays, `Record<>`, object types | * | `foreignKey` | `UUID` by default (FK column) | `@foreignKey(Class)` decorator | * | `crossPackageRef` | `UUID` by default (no FK constraint) | `@crossPackageRef('@pkg:Class')` decorator | * | `oneToMany` | — (virtual) | `@oneToMany(Class)` decorator | * | `manyToMany` | — (virtual) | `@manyToMany(Class)` decorator | * | `meta` | Stored in `_meta_data` | STI child field wrapped in `Meta` | * | `unknown` | — | Could not be determined | * * @see {@link FieldTypeInference} for the full inference result shape. */ export declare type InferredFieldType = 'text' | 'integer' | 'decimal' | 'boolean' | 'datetime' | 'json' | 'foreignKey' | 'crossPackageRef' | 'oneToMany' | 'manyToMany' | 'meta' | 'unknown'; /** * Resolves class inheritance chains and STI hierarchies from raw OXC scan output. * * After {@link OxcScanner} parses files, `InheritanceResolver` builds a * class map from the raw definitions and walks each class's `extends` chain * to produce fully-resolved {@link ResolvedClassDefinition} objects. * * Key responsibilities: * - Walking extends chains across local classes and external package manifests. * - Detecting which classes participate in STI (Single Table Inheritance). * - Merging fields from ancestor classes for STI subclasses (base fields first). * - Caching resolved chains to avoid repeated traversals. * * Framework base classes (`SmrtObject`, `SmrtClass`, `SmrtCollection`) are * recognized without needing to appear in source files. * * @example * ```typescript * import { InheritanceResolver } from '@happyvertical/smrt-scanner'; * * const resolver = new InheritanceResolver({ baseClasses: ['MyBaseClass'] }); * resolver.addClasses(rawClasses); * const resolved = resolver.resolveAll(); * ``` * * @see {@link OxcScanner} which owns and drives this resolver internally. */ export declare class InheritanceResolver { /** Map of className -> RawClassDefinition */ private classMap; /** External package manifests for cross-package resolution */ private externalManifests; /** Known base classes (user-provided) */ private knownBaseClasses; /** Cache of resolved inheritance chains */ private chainCache; /** * Create a new `InheritanceResolver`. * * @param options.baseClasses - Additional class names to treat as known * framework base classes (beyond the built-in `SmrtObject`, `SmrtClass`, * and `SmrtCollection`). * @param options.externalManifests - Pre-loaded external package manifests * keyed by package name, used for cross-package parent class resolution. */ constructor(options?: { baseClasses?: string[]; externalManifests?: Map; }); /** * Register raw class definitions from a scan pass. * * Adds each class to the internal class map by `className`. Calling this * clears the inheritance chain cache so subsequent calls to * {@link resolveAll} or {@link resolveInheritanceChain} reflect the new * classes. * * @param classes - Array of {@link RawClassDefinition} objects from * {@link ScanResults.classes}. */ addClasses(classes: RawClassDefinition[]): void; /** * Register an external package manifest for cross-package base class resolution. * * Clears the chain cache after registration so re-resolution picks up the * new definitions. * * @param manifest - External package manifest providing class definitions * that may appear as base classes in the local project. * * @see {@link ExternalManifest} */ addExternalManifest(manifest: ExternalManifest): void; /** * Resolve all registered classes and return fully-resolved definitions. * * A class is included in the output if it either: * 1. Has an `@smrt()` decorator, or * 2. Directly or transitively extends a framework base class * (`SmrtObject`, `SmrtClass`, `SmrtCollection`) — this captures * collection classes such as `class MeetingCollection extends * SmrtCollection` that do not carry `@smrt()` themselves. * * @returns An array of {@link ResolvedClassDefinition} — one entry per * eligible class, with inheritance chain, STI metadata, and merged fields * populated. * * @see {@link resolve} to resolve a single class definition. */ resolveAll(): ResolvedClassDefinition[]; /** * Check if a class extends a framework base class * (SmrtObject, SmrtClass, or SmrtCollection) */ private extendsFrameworkBase; /** * Resolve a single raw class definition into a fully-resolved definition. * * Computes the inheritance chain, determines the effective table strategy, * detects STI membership, and merges ancestor fields for STI classes. * * @param classDef - The raw class definition to resolve. * @returns A {@link ResolvedClassDefinition} with all inherited metadata * applied. The `packageName` field is left as `null` and must be set by * the caller (e.g. {@link ManifestAdapter}). * * @see {@link resolveAll} to resolve every registered class at once. */ resolve(classDef: RawClassDefinition): ResolvedClassDefinition; /** * Resolve the full inheritance chain for a named class, from the root base * class down to the named class itself. * * Results are memoised in an internal cache that is cleared whenever * {@link addClasses} or {@link addExternalManifest} is called. * * @param className - Name of the class to resolve. * @returns An ordered array of class names starting from the furthest * ancestor and ending with `className`. * * @example * ```typescript * // Given: class Article extends Content, class Content extends SmrtObject * resolver.resolveInheritanceChain('Article'); * // => ['SmrtObject', 'Content', 'Article'] * ``` */ resolveInheritanceChain(className: string): string[]; /** * Look up a class definition by name, searching in priority order: * 1. Local classes added via {@link addClasses}. * 2. External package manifests added via {@link addExternalManifest}. * 3. Built-in framework base classes (`SmrtObject`, `SmrtClass`, * `SmrtCollection`) — returns a minimal stub definition so chain walking * can terminate cleanly. * * @param className - Class name to look up. * @returns The {@link RawClassDefinition} if found, or `null` if the class * is unknown to the resolver. */ findClassDefinition(className: string): RawClassDefinition | null; /** * Find the STI root class in a resolved inheritance chain. * * Walks the chain from base to leaf and returns the name of the first class * whose `@smrt()` decorator explicitly declares `tableStrategy: 'sti'`. * * @param chain - Ordered inheritance chain (base → leaf) as returned by * {@link resolveInheritanceChain}. * @returns The class name of the STI root, or `null` if no class in the * chain uses `tableStrategy: 'sti'`. */ findSTIBase(chain: string[]): string | null; /** * Determine the effective table strategy (`'sti'` or `'cti'`) for a class. * * Resolution order: * 1. The class's own `@smrt({ tableStrategy })` declaration, if present. * 2. The nearest ancestor that declares `tableStrategy: 'sti'` — STI is * inherited automatically by all subclasses. * 3. Defaults to `'cti'` if no STI ancestor is found. * * @param classDef - Raw class definition whose strategy is being determined. * @param chain - Pre-resolved inheritance chain for `classDef` (base → leaf). * @returns `'sti'` or `'cti'`. */ determineTableStrategy(classDef: RawClassDefinition, chain: string[]): 'sti' | 'cti'; /** * Merge fields from all classes in an STI inheritance chain. * * Iterates from the root base class to the leaf class so that base class * fields appear first in the returned array. If a field name is declared in * both an ancestor and a descendant, the ancestor's definition takes * precedence (first-seen wins), preserving the base-class column layout. * * @param chain - Ordered inheritance chain (base → leaf) as returned by * {@link resolveInheritanceChain}. * @returns A deduplicated, ordered array of {@link RawFieldDefinition} * covering every field in the STI hierarchy. */ mergeFieldsForSTI(chain: string[]): RawFieldDefinition[]; /** * Return all known descendants of a class. * * Useful for STI schema generation where the base table must accommodate * columns from every subclass. * * @param className - The ancestor class name to search from. * @returns An array of class names (local classes only) whose resolved * inheritance chain includes `className`. Does not include `className` * itself. */ getDescendants(className: string): string[]; /** * Check whether a class participates in an STI hierarchy. * * @param className - Name of the class to check. * @returns `true` if any class in the resolved inheritance chain declares * `tableStrategy: 'sti'`, `false` otherwise. */ isSTIClass(className: string): boolean; /** * Return aggregate statistics about the classes registered with this resolver. * * @returns An object with: * - `totalClasses` — total number of classes in the class map. * - `smrtClasses` — classes that carry `@smrt()`. * - `stiClasses` — `@smrt()` classes in an STI hierarchy. * - `maxInheritanceDepth` — length of the deepest inheritance chain among * `@smrt()` classes. */ getStats(): { totalClasses: number; smrtClasses: number; stiClasses: number; maxInheritanceDepth: number; }; } /** * Whether the agent-surface emitter reads this file. **This is the one * authority on that question.** * * Both the scanner's declaration pass and `dev:knowledge-check`'s freshness * re-scan call it, because the two answering differently is not a cosmetic * inconsistency: a file the emitter reads but the checker skips is reported as * "no longer present in source" on every run, and a file the checker reads but * the emitter skips is reported as "missing from smrt-knowledge.json" — both * unclearable by any rebuild. It is a path predicate rather than a glob so the * two sides cannot drift through differing glob semantics either. * * Callers still pass globs to prune the WALK for speed; this decides what * counts. */ export declare function isAgentSurfaceSourcePath(filePath: string, rootDir?: string): boolean; /** * Whether a path lies in a directory declarations are never read from, * measured **relative to the project root**. * * `rootDir` is not optional in spirit. Matching these segments against an * absolute path would disable the entire feature for a checkout that merely * LIVES under one — a container with `WORKDIR /build`, or a clone in * `~/build/…` — and it would do so with no diagnostic at all, because the * freshness check applies the same predicate and would agree that nothing is * declared. That is the silent drop this module exists to prevent, so the same * care `discovery.ts` takes to rewrite globs relative to `cwd` applies here. * * Exported so the `.svelte` pass and `dev:knowledge-check`'s walk prune * identically; a `.svelte` file cannot go through * {@link isAgentSurfaceSourcePath}, which rejects it on extension. */ export declare function isPrunedAgentSurfacePath(filePath: string, rootDir?: string): boolean; /** * Report every persisted `number` field whose declared precision contradicts * its name — money declared decimal, or a rate declared integer. * * @param classes - Raw class definitions from `parseFile` / `OxcScanner`. * @param sourceText - Optional contents of the scanned file, used only to * recover declaration line numbers the AST does not carry. * @returns One finding per offending field, in declaration order. */ export declare function lintNumericPrecision(classes: RawClassDefinition[], sourceText?: string): NumericPrecisionFinding[]; declare interface Literal extends BaseNode { type: 'Literal'; value: string | number | boolean | null | RegExp | bigint; raw?: string; } /** * Converts OXC scanner output into the smrt-core `SmartObjectManifest` format * consumed by code generators, the Vitest plugin, and the SMRT CLI. * * The adapter handles field type inference (applying the `0` vs `0.0` integer / * decimal heuristic), decorator interpretation (`@foreignKey`, `@oneToMany`, * `@manyToMany`, `@field`), type alias resolution, STI `Meta` unwrapping, * static property capture (`uiSlots`, `adminRoutes`), and qualified name * generation for namespace isolation across packages. * * @example * ```typescript * import { OxcScanner, ManifestAdapter } from '@happyvertical/smrt-scanner'; * * const scanner = new OxcScanner({ cwd: process.cwd() }); * const { results, resolved } = await scanner.scanAndResolve(); * * const adapter = new ManifestAdapter(); * const manifest = adapter.toManifest(resolved, { * packageName: '@my-org/my-package', * packageVersion: '1.0.0', * typeAliases: results.typeAliases, * }); * ``` * * @see {@link OxcScanner} for producing the `ResolvedClassDefinition[]` input. * @see {@link ResolvedClassDefinition} for the shape of each input element. */ export declare class ManifestAdapter { private typeAliases; private _aliasDepth?; /** * Convert an array of resolved class definitions into a `SmartObjectManifest`. * * Each class is converted to a `SmartObjectDefinition` via * {@link toSmartObjectDefinition} and stored under its qualified name key * (e.g. `@my-org/my-package:MyClass`) when `packageName` is provided, or * under its lowercased class name otherwise. * * @param resolved - Resolved class definitions from {@link OxcScanner.resolve} * or {@link OxcScanner.scanAndResolve}. * @param options.packageName - npm package name used to generate qualified * class names for namespace isolation across multi-package projects. * @param options.packageVersion - Package version recorded in the manifest * metadata. * @param options.typeAliases - Map of type alias names to their resolved type * strings (from {@link ScanResults.typeAliases}). Used to resolve custom * types like `type Status = 'active' | 'inactive'` during field inference. * @returns A complete `SmartObjectManifest` ready for serialisation. * * @example * ```typescript * const manifest = adapter.toManifest(resolved, { * packageName: '@my-org/my-package', * packageVersion: '1.0.0', * typeAliases: results.typeAliases, * }); * fs.writeFileSync('manifest.json', JSON.stringify(manifest, null, 2)); * ``` */ toManifest(resolved: ResolvedClassDefinition[], options?: { packageName?: string; packageVersion?: string; typeAliases?: Record; }): SmartObjectManifest; /** * Convert a single resolved class definition to a `SmartObjectDefinition`. * * Handles: * - Static property capture (`uiSlots`, `adminRoutes`) with child-wins * semantics for overridden statics. * - Field conversion (non-static public fields only) via {@link convertField}. * - Method conversion (public instance/static methods) via {@link convertMethod}. * - Collection name pluralisation. * - Qualified name generation when `packageName` is supplied. * * @param classDef - A fully-resolved class definition. * @param options.packageName - Package name used to build the qualified class * name (`@pkg:ClassName`). * @param options.packageVersion - Package version (informational, stored in * the definition). * @returns A `SmartObjectDefinition` ready to be stored in a manifest. * * @see {@link toManifest} for the bulk conversion entry point. */ toSmartObjectDefinition(classDef: ResolvedClassDefinition, options?: { packageName?: string; packageVersion?: string; }): SmartObjectDefinition; /** * Framework internal fields that should NOT be included in manifests * These are SmrtObject internals used by the framework, not user-defined fields */ private static readonly FRAMEWORK_INTERNAL_FIELDS; /** * Convert a single raw field definition to a manifest `FieldDefinition`. * * Returns `null` for fields that should be omitted from the manifest: * - `private` or `protected` fields. * - Framework-internal fields (`_tableName`, `_db`, `_ai`, etc.). * * Delegates type inference to {@link inferFieldType} and applies additional * post-processing: * - Marks fields with `Function` type annotation as `transient`. * - Marks fields with `@field({ transient: true })` decorator as `transient`. * - Populates `_meta.underlyingType` for STI `Meta` fields. * * @param field - Raw field definition from a scanned class. * @returns A `FieldDefinition` for the manifest, or `null` if the field * should be excluded. * * @see {@link inferFieldType} for the type inference logic. */ convertField(field: RawFieldDefinition): FieldDefinition | null; /** * Infer the SMRT field type and required flag from a raw field definition. * * Inference is attempted in the following priority order: * 1. **Field helper call in initializer** — currently always returns `null` * (field helpers removed); reserved for future use. * 2. **Decorator** — `@foreignKey`, `@oneToMany`, `@manyToMany`, `@field({ type })`. * 3. **Type annotation** — `string` → `text`, `number` with `0` vs `0.0` * heuristic → `integer` / `decimal`, `boolean`, `Date` → `datetime`, * arrays → `json`, `Record<>` / `object` → `json`, union types with * `null`, inline string/number literal unions, `Meta` wrapper, * and type alias resolution (up to depth 5). * 4. **Numeric literal without annotation** — `version = 1` → `integer`. * 5. **Boolean literal without annotation** — `isRead = false` → `boolean`. * 6. **Default** — falls back to `text`. * * @param field - The raw field definition to analyse. * @returns A {@link FieldTypeInference} describing the inferred type, * required flag, default value, related class name (for relationships), * and the inference source for debugging. * * @see {@link FieldTypeInference} for the result shape. * @see {@link InferredFieldType} for valid type values. */ inferFieldType(field: RawFieldDefinition): FieldTypeInference; /** * Infer type from field helper call (removed) * * Field helpers have been removed in favor of decorators and TypeScript types: * - Use TypeScript types: name: string = '', price: number = 0.0 * - Use @field() decorator for constraints: @field({ required: true }) * - Use @foreignKey(), @oneToMany(), @manyToMany() decorators for relationships */ private inferFromHelper; /** * Infer type from field decorator */ private inferFromDecorator; private extractFieldDecoratorOptions; private parseReportFieldDecorator; private parseFieldDecoratorOptions; private normalizeFieldType; /** * Infer type from TypeScript type annotation */ private inferFromAnnotation; /** * Parse default value from initializer string */ private parseDefaultValue; /** * Convert a raw method definition to a manifest `MethodDefinition`. * * Returns `null` for `private` or `protected` methods, which are excluded * from the manifest. Parameters are mapped to the manifest parameter shape * and default values are parsed via `parseDefaultValue`. * * @param method - Raw method definition from a scanned class. * @returns A manifest-compatible `MethodDefinition`, or `null` if the method * should be excluded. */ convertMethod(method: RawMethodDefinition): MethodDefinition | null; /** * Simple pluralization for collection names. * * This produces the manifest's `collection` label only; the authoritative DDL * table name is derived independently by core (`classnameToTablename` → * the `pluralize` library), so this needs to stay self-consistent rather than * cover every irregular plural. Note the `y → ies` rule fires only after a * consonant, so vowel+y words pluralise correctly (`Day` → `days`, not * `daies`). */ private pluralize; } declare interface MemberExpression extends BaseNode { type: 'MemberExpression'; object: Expression; property: Expression; computed: boolean; } /** * Merge per-file results into ONE deterministic surface. * * Emission must not depend on the order the file system happened to hand files * to the scanner — a cross-profile parity snapshot that churns on directory * order proves nothing. So identity is total and content-derived: * * - an intent is identified by its `id`, a playbook by its `key`; * - entries sort by that identity, then by the recorded source path; * - when two files declare the same identity, the entry from the * lexicographically smaller path wins and the other is reported as a * `duplicate-identity` diagnostic — a rule that gives the same answer for * every input order, which "first one scanned wins" does not; * - diagnostics sort by path, then line, column, code, and message. * * `relativize` maps an absolute scan path to the stable path recorded in the * artifact; callers pass the package root's relativizer so a checked-in * artifact never carries a machine-specific absolute path. */ export declare function mergeAgentSurfaces(surfaces: readonly AgentSurface[], relativize?: (filePath: string) => string): AgentSurface; declare interface MethodDefinition { name: string; async: boolean; parameters: Array<{ name: string; type: string; optional: boolean; default?: unknown; /** See `RawParameterDefinition.typeUnresolved` (#2686). */ typeUnresolved?: boolean; /** See `RawParameterDefinition.memberTypes` (#2686). */ memberTypes?: string[]; /** See `RawParameterDefinition.unionBranches` (#2686). */ unionBranches?: ParameterTypeBranch[]; }>; returnType: string; description?: string; isStatic: boolean; isPublic: boolean; /** Config of an `@method()` decorator on this method (#2686). */ decoratorConfig?: Record; } declare interface MethodDefinition_2 extends BaseNode { type: 'MethodDefinition'; key: Expression; value: FunctionExpression; kind: 'constructor' | 'method' | 'get' | 'set'; computed: boolean; static: boolean; accessibility?: 'public' | 'private' | 'protected'; decorators?: Decorator[]; } declare interface NewExpression extends BaseNode { type: 'NewExpression'; callee: Expression; arguments: Expression[]; } export declare function normalizeGlobSeparators(pattern: string, pathSeparator?: "\\" | "/"): string; /** One field whose declared precision contradicts its name. */ export declare interface NumericPrecisionFinding { /** `money` wants INTEGER minor units; `rate` wants DECIMAL. */ kind: NumericPrecisionKind; /** Declaring class, e.g. `Invoice`. */ className: string; /** Field name, e.g. `totalAmount`. */ fieldName: string; /** File the class was scanned from. */ filePath: string; /** * 1-based line of the field declaration, or `0` when it could not be * resolved. The OXC AST nodes this scanner consumes do not carry `loc`, so * the line is recovered from the source text when a caller passes it to * {@link lintNumericPrecision}. */ line: number; /** The initializer that triggered the finding. */ initializer: string; /** Human-readable explanation naming the rule. */ message: string; /** The accepted fixes. */ remedy: string; } /** Which rule a field falls under, if any. */ export declare type NumericPrecisionKind = 'money' | 'rate'; declare interface ObjectExpression extends BaseNode { type: 'ObjectExpression'; properties: (Property | SpreadElement)[]; } declare interface ObjectPattern extends BaseNode { type: 'ObjectPattern'; properties: (Property | RestElement)[]; typeAnnotation?: TSTypeAnnotation; } /** * Prunes that always apply, on top of whatever `exclude` a caller passes. * * `exclude` replaces {@link DEFAULT_EXCLUDE} wholesale, so every caller that * narrowed the excludes also silently reopened `node_modules` — and installed * dependencies are never a project's own `@smrt()` sources. Dot directories are * generated or tool state (`.git`, `.svelte-kit`, `.turbo`, `.vercel`, agent * scratch) and are pruned for the same reason: nothing authored lives there. * `**\/.*` keeps hidden FILES out of the result too, so turning `dot` on to * make these prunes work does not quietly widen what gets scanned. * * These are load-bearing for termination, not just for speed. See * {@link OxcScanner.discoverFiles}. */ /** * High-performance TypeScript scanner that discovers `@smrt()`-decorated * classes in a project's source files. * * Orchestrates the two-phase scan pipeline: * 1. **Phase 1 — Parse** (`scan()`): uses OXC (Rust) to parse TypeScript files * in parallel and extract raw class, field, method, and decorator metadata. * 2. **Phase 2 — Resolve** (`resolve()`): walks inheritance chains, detects STI * hierarchies, and merges fields from ancestor classes. * * The common path is {@link scanAndResolve} which runs both phases in sequence. * * @example * ```typescript * import { OxcScanner } from '@happyvertical/smrt-scanner'; * * const scanner = new OxcScanner({ * cwd: process.cwd(), * include: ['src/**\/*.ts'], * exclude: ['**\/*.test.ts'], * }); * * const { results, resolved } = await scanner.scanAndResolve(); * console.log(`Found ${resolved.length} SMRT classes in ${results.fileCount} files`); * ``` * * @see {@link OxcScannerOptions} for all available configuration options. * @see {@link scanDirectory} for a one-liner convenience wrapper. */ export declare class OxcScanner { private options; private resolver; private scanResults; /** * Create a new `OxcScanner` with the given options. * * All options are optional. By default the scanner targets every `.ts` and * `.tsx` file under `process.cwd()`, excluding `node_modules`, `dist`, * `build`, declaration files, and test files. * * @param options - Scanner configuration. See {@link OxcScannerOptions}. */ constructor(options?: OxcScannerOptions); /** * Phase 1 — Discover and parse TypeScript files using OXC. * * Uses `fast-glob` to enumerate matching files and then parses them in * parallel with OXC (Rust). The raw class definitions are registered with * the internal {@link InheritanceResolver} for use in the subsequent * {@link resolve} call. * * @returns A {@link ScanResults} object containing all classes found, any * parse errors, accumulated type aliases, SMRT import metadata, and * aggregate timing information. * * @example * ```typescript * const scanner = new OxcScanner({ cwd: '/project' }); * const results = await scanner.scan(); * console.log(`Parsed ${results.fileCount} files in ${results.totalParseTimeMs.toFixed(1)}ms`); * ``` */ scan(): Promise; /** * Phase 2 — Resolve inheritance chains for all scanned classes. * * Must be called after {@link scan}. Walks each class's extends chain, * detects STI hierarchies, merges ancestor fields for STI subclasses, and * marks framework base classes. * * @returns An array of {@link ResolvedClassDefinition} objects — one for * every class that either carries `@smrt()` or extends a framework base * class (`SmrtObject`, `SmrtClass`, `SmrtCollection`). * * @throws {Error} If called before {@link scan}. * * @see {@link scanAndResolve} to run both phases in one call. */ resolve(): ResolvedClassDefinition[]; /** * Run both scan phases in a single call. * * Equivalent to calling `await scanner.scan()` followed by * `scanner.resolve()`. This is the most common entry point for callers * that want the fully-resolved manifest-ready data in one step. * * @returns An object with: * - `results` — raw {@link ScanResults} from Phase 1. * - `resolved` — array of {@link ResolvedClassDefinition} from Phase 2. * * @example * ```typescript * const scanner = new OxcScanner({ cwd: '/project/src' }); * const { results, resolved } = await scanner.scanAndResolve(); * // resolved is ready to pass to ManifestAdapter.toManifest() * ``` * * @see {@link ManifestAdapter} to convert `resolved` into a manifest JSON. */ scanAndResolve(): Promise<{ results: ScanResults; resolved: ResolvedClassDefinition[]; }>; /** * Register an external package manifest for cross-package base class resolution. * * When a project class extends a class defined in an installed SMRT package, * the resolver needs access to that package's class definitions to walk the * full inheritance chain. Call this method with each external package's * {@link ExternalManifest} before calling {@link scan} or {@link resolve}. * * @param manifest - The external manifest to register, including `packageName`, * `packageVersion`, and a `classes` map keyed by class name. * * @see {@link ExternalManifest} */ addExternalManifest(manifest: ExternalManifest): void; /** * Scan all discovered files for @happyvertical/smrt-* imports. * Returns a map of package name → Set of imported class names. * * Used for tree-shaking: only external objects that are actually imported * in the project's source files will be included in the manifest. * * Must be called after scan() or as part of scanAndResolve(). * * @example * ```typescript * const scanner = new OxcScanner({ cwd: process.cwd() }); * await scanner.scan(); * const imports = scanner.scanSmrtImports(); * // Map { '@happyvertical/smrt-profiles' => Set { 'Person', 'Organization' } } * ``` */ scanSmrtImports(): Map>; /** * Return aggregate statistics about the last scan. * * Can be called after {@link scan} has completed. Returns counts useful for * diagnostics and the `--stats` CLI flag. * * @returns An object with: * - `totalClasses` — total class declarations seen (including non-SMRT). * - `smrtClasses` — classes with `@smrt()` decorator. * - `stiClasses` — SMRT classes participating in an STI hierarchy. * - `maxInheritanceDepth` — length of the deepest inheritance chain. * - `fileCount` — number of files scanned. * - `parseTimeMs` — total wall-clock parse time in milliseconds. */ getStats(): { totalClasses: number; smrtClasses: number; stiClasses: number; maxInheritanceDepth: number; fileCount: number; parseTimeMs: number; }; /** * Discover files to scan using fast-glob. * * Two settings here decide whether discovery terminates at all when the * scanner is pointed at an application root rather than a package `src/`: * * - `dot: true`. Without it a `**` in an ignore pattern cannot cross a * dot segment, so `**\/node_modules/**` prunes `node_modules` at the root * but NOT `.svelte-kit/…/node_modules` or any other `node_modules` under a * dot directory. Those subtrees were then walked in full and every entry * discarded — unbounded work that could never produce a match. * - `followSymbolicLinks: false`. pnpm materializes `node_modules` as a * symlink graph with cycles, so a link-following walk revisits the same * real directories once per path that reaches them. * * Together they were enough to exhaust a 4 GB heap on a consumer app that * installs the published packages (#2275). * * Patterns are rewritten relative to `cwd` first. fast-glob matches `ignore` * in whatever space the patterns use, so an absolute pattern would hand * `**\/.*\/**` the project's own ancestors — a checkout under `~/.worktrees` * or `~/.cache` would then match nothing at all, silently. */ private discoverFiles; /** * Find declarations in files the CLASS scan did not cover. * * The class `include` is routinely narrowed to where models live, but an * intent sidecar lives beside its component. Without this pass those * declarations would be missing from every artifact with no diagnostic — a * silent omission, and in the shipped SvelteKit template's own layout at * that. Files already parsed by the class scan are skipped so a declaration * is never counted twice and cannot collide with itself. * * @param alreadyScanned - Absolute paths the class scan already parsed. */ private scanDeclarationsOutsideClassGlob; /** * Search `.svelte` files for declarations the scanner can never read. * * A `.svelte` file is not a TypeScript program and is never parsed here, so * this pass produces diagnostics only — never an emitted entry. It exists * because the alternative is silence: an intent declared inline in a * component simply would not appear anywhere, with nothing to explain why. */ private scanSvelteDeclarations; /** * Record a declaring module as a `cwd`-relative POSIX path. * * Emitted entries land in checked-in artifacts, so an absolute path would * make them machine-specific and a Windows separator would make them * platform-specific — either one churns a snapshot that is supposed to prove * two builds agree. */ private relativizeSourcePath; /** * Parse a single file with timing */ private parseFileWithTiming; } /** * Configuration options for {@link OxcScanner}. * * All fields are optional; reasonable defaults are applied when omitted. * * @see {@link OxcScanner} */ export declare interface OxcScannerOptions { /** Glob patterns to include */ include?: string[]; /** Glob patterns to exclude */ exclude?: string[]; /** Base directory for scanning */ cwd?: string; /** Path to tsconfig.json for module resolution */ tsconfig?: string; /** Whether to follow imports to find base classes */ followImports?: boolean; /** Known base classes (avoid resolution) */ baseClasses?: string[]; /** Include private methods in output */ includePrivateMethods?: boolean; /** Include static methods in output */ includeStaticMethods?: boolean; /** External package manifests for base class resolution */ externalManifests?: Map; /** * Follow symbolic links while discovering files. Defaults to `false`. * * A package's own sources are real files, while a pnpm `node_modules` is a * symlink graph with cycles: every store entry links back out to its * siblings, so a link-following walk reaches the same real directory once per * path that leads to it and never terminates in practice. Discovery therefore * stays on real directories unless a caller explicitly opts back in. */ followSymbolicLinks?: boolean; /** * Match module-scope `defineIntent()` / `definePlaybook()` declarations and * report `.svelte` declarations the scanner can never read (#2591). Defaults * to `true`. */ agentSurface?: boolean; /** * Glob patterns searched for `.svelte` declarations the scanner cannot read. * Only used when {@link OxcScannerOptions.agentSurface} is enabled. */ svelteInclude?: string[]; /** * Glob patterns searched for `defineIntent` / `definePlaybook` declarations, * independently of {@link OxcScannerOptions.include}. * * A model scan is routinely narrowed to where models live, but an intent * sidecar lives beside the component that uses it; binding declaration * discovery to the class glob would silently drop those. Only used when * {@link OxcScannerOptions.agentSurface} is enabled. */ agentSurfaceInclude?: string[]; } /** One branch of a top-level union parameter type (#2686). */ export declare interface ParameterTypeBranch { /** The branch's own type name, e.g. `string` or `object`. */ type: string; /** Inline object members declared by THIS branch only. */ memberTypes?: string[]; } /** * Read one file for agent-surface declarations ONLY (#2591). * * Exists because the agent surface must not be confined to the class scan's * `include` glob: an application that scans `src/lib/objects/**` for its models * — the shipped template does exactly that — would otherwise never see a * `src/lib/agent/Foo.intents.ts` sidecar, and the declaration would vanish from * every artifact with no diagnostic. Silent omission is the one failure this * matcher exists to prevent, so declarations are discovered on their own terms. * * The token pre-filter runs before the parse, so a file that declares nothing * costs one read and one `String.includes`. * * @returns The file's surface, or `undefined` when it declares nothing or * cannot be read. */ export declare function parseAgentSurfaceFile(filePath: string): AgentSurface | undefined; /** * Parse a single TypeScript file and extract SMRT class definitions. * * Reads the file from disk, runs oxc-parser on it, and returns all class * definitions found, any parse errors, accumulated type aliases, and * `@happyvertical/smrt-*` import metadata. * * @param filePath - Absolute path to the `.ts` or `.tsx` file to parse. * @returns A {@link FileScanResult} containing classes, errors, type aliases, * SMRT imports, and timing information for the file. * * @example * ```typescript * import { parseFile } from '@happyvertical/smrt-scanner'; * * const result = parseFile('/project/src/models/Product.ts'); * console.log(result.classes.map((c) => c.className)); * // ['Product', 'ProductCollection'] * ``` * * @see {@link parseSource} to parse a source string directly (e.g. in tests). */ export declare function parseFile(filePath: string): FileScanResult; /** * Parse TypeScript source text directly and extract SMRT class definitions. * * Identical to {@link parseFile} but accepts a source string instead of a * file path. Primarily used in tests and tooling that constructs source * programmatically. * * @param sourceText - Raw TypeScript source code to parse. * @param filename - Virtual filename used to determine the parser language * mode (`.ts`, `.tsx`, `.js`, `.jsx`) and to populate `filePath` fields in * the result. Defaults to `'test.ts'`. * @returns A {@link FileScanResult} containing classes, errors, type aliases, * and SMRT import metadata extracted from the source text. * * @example * ```typescript * import { parseSource } from '@happyvertical/smrt-scanner'; * * const src = ` * import { smrt } from '@happyvertical/smrt-core'; * @smrt() * export class Widget extends SmrtObject { * label: string = ''; * } * `; * const result = parseSource(src, 'Widget.ts'); * console.log(result.classes[0].className); // 'Widget' * ``` * * @see {@link parseFile} to parse a file from disk. */ export declare function parseSource(sourceText: string, filename?: string): FileScanResult; declare type Pattern = Identifier | AssignmentPattern | RestElement | ObjectPattern | ArrayPattern; declare interface Position { line: number; column: number; } declare interface Property extends BaseNode { type: 'Property'; key: Expression; value: Expression | Pattern; kind: 'init' | 'get' | 'set'; method: boolean; shorthand: boolean; computed: boolean; } declare interface PropertyDefinition extends BaseNode { type: 'PropertyDefinition'; key: Expression; value: Expression | null; computed: boolean; static: boolean; readonly?: boolean; optional?: boolean; accessibility?: 'public' | 'private' | 'protected'; typeAnnotation?: TSTypeAnnotation; decorators?: Decorator[]; } /** * Qualified class name format: "@package/name:ClassName" * Uniquely identifies classes across packages. */ declare type QualifiedClassName = `${string}:${string}`; /** * Type definitions for OXC-based SMRT scanner * * This module defines: * 1. Raw types - Intermediate representation from OXC parsing (Phase 1) * 2. Resolved types - After inheritance resolution (Phase 2) * 3. Re-exports of smrt-core types for compatibility */ /** * Raw class definition extracted from OXC AST * Contains only syntactic information, no semantic resolution */ export declare interface RawClassDefinition { /** Class name as declared in source */ className: string; /** Absolute path to source file */ filePath: string; /** Parent class name from extends clause (null if none) */ extendsClause: string | null; /** Generic type argument from extends (e.g., "Meeting" from SmrtCollection) */ extendsTypeArg: string | null; /** Parsed @smrt() decorator configuration object */ decoratorConfig: RawDecoratorConfig | null; /** Has @smrt() decorator */ hasSmartDecorator: boolean; /** Class properties/fields */ fields: RawFieldDefinition[]; /** Class methods */ methods: RawMethodDefinition[]; /** Start line in source file */ startLine: number; /** End line in source file */ endLine: number; } /** * Raw decorator information */ export declare interface RawDecorator { /** Decorator name (e.g., "field", "foreignKey") */ name: string; /** Raw arguments as strings */ arguments: string[]; } /** * Raw @smrt() decorator configuration */ export declare interface RawDecoratorConfig { /** Table strategy: 'sti' | 'cti' */ tableStrategy?: 'sti' | 'cti'; /** Storage type for the generated id primary key */ idType?: 'uuid' | 'text'; /** Code-owned feature toggle declarations */ features?: Record; }>; /** API configuration */ api?: { include?: string[]; exclude?: string[]; }; /** CLI configuration */ cli?: boolean | { include?: string[]; exclude?: string[]; skipApiCheck?: boolean; http?: boolean; }; /** MCP configuration */ mcp?: { include?: string[]; exclude?: string[]; }; /** Raw config object for unknown properties */ [key: string]: unknown; } /** * Raw field definition from OXC AST */ export declare interface RawFieldDefinition { /** Field name */ name: string; /** TypeScript type annotation as string (e.g., "string", "number", "Date") */ typeAnnotation: string | null; /** Raw initializer expression as string */ initializer: string | null; /** For numeric literals: whether it contains a decimal point */ hasDecimalPoint: boolean; /** For numeric literals: the actual numeric value */ numericValue: number | null; /** Decorators applied to this field */ decorators: RawDecorator[]; /** Whether field is optional (has ?) */ optional: boolean; /** Whether field is static */ isStatic: boolean; /** Whether field is readonly */ readonly: boolean; /** Whether field is private/protected */ accessibility: 'public' | 'private' | 'protected'; /** Start line in source */ line: number; } /** * Raw method definition from OXC AST */ export declare interface RawMethodDefinition { /** Method name */ name: string; /** Whether method is async */ async: boolean; /** Whether method is static */ isStatic: boolean; /** Accessibility modifier */ accessibility: 'public' | 'private' | 'protected'; /** Method parameters */ parameters: RawParameterDefinition[]; /** Return type annotation as string */ returnType: string | null; /** JSDoc description if present */ description: string | null; /** * Config object of an `@method()` decorator on this method, when one is * present. `{}` for a bare `@method()`; `undefined` when the method carries * no `@method()` decorator at all — the two are distinct, because an * explicit bare decorator still marks the method as deliberately reviewed. * * Values are extracted with the same literal-only rules the class-level * `@smrt()` config uses, so an expression the scanner cannot resolve becomes * a scan error rather than a silently dropped `expose: false`. */ decoratorConfig?: Record; /** Start line in source */ line: number; } /** * Raw parameter definition */ export declare interface RawParameterDefinition { /** Parameter name */ name: string; /** Type annotation as string */ type: string | null; /** Whether parameter is optional */ optional: boolean; /** Default value as string */ defaultValue: string | null; /** * True when the parameter carries a type annotation the scanner could not * express as a string — an intersection, tuple, conditional, mapped, * `typeof`, or indexed-access type — or when an inline object literal * contains such a member. * * This is the provenance that separates "the author wrote `any`" from "the * scanner gave up": both previously reached the manifest as the string * `'any'`. Consumers that must fail closed on an uncertain type (the API * wire-ability gate) read this rather than trusting `type`. * * `type: null` with `typeUnresolved` absent means the parameter simply has * no annotation, which is an implicit — and genuinely authored — `any`. */ typeUnresolved?: boolean; /** * Resolved member types of an INLINE object-literal annotation * (`{ onDone: () => void; target: Content }` → `['Function', 'Content']`), * flattened across nested literals, arrays, and unions. * * `extractTypeName` collapses every inline literal to the single string * `'object'`, which erases exactly the members a caller needs to judge * whether the bag can cross a wire. NAMED bags (an interface, type alias, * `Partial<>`/`Pick<>`) are deliberately NOT expanded — resolving them needs * cross-file type resolution this AST layer does not do, and they are * accepted heuristically by the consumers that care. */ memberTypes?: string[]; /** * For a top-level UNION, each branch with the inline members IT declared. * * `memberTypes` above flattens across branches, which lets one branch veto * another: `{ callback: () => void } | string` is wire-able through its * string branch, but the flattened `Function` rejected the whole parameter. * A consumer that understands this field MUST prefer it over `memberTypes` * for unions. Absent on a non-union parameter and on manifests generated * before #2686. */ unionBranches?: ParameterTypeBranch[]; } /** Preserve relative glob escapes; normalize separators only after rewriting. */ export declare function relativeGlobToCwd(pattern: string, cwd: string): string; /** * Resolved class definition with inheritance chain */ export declare interface ResolvedClassDefinition extends RawClassDefinition { /** Full inheritance chain from base to this class */ inheritanceChain: string[]; /** STI base class name (if part of STI hierarchy) */ stiBase: string | null; /** Effective table strategy (inherited or declared) */ effectiveTableStrategy: 'sti' | 'cti'; /** Whether this class uses STI (convenience boolean) */ isSTI: boolean; /** Whether this class is a framework base class */ isFrameworkBase: boolean; /** All fields including inherited (for STI) */ allFields: RawFieldDefinition[]; /** Package this class belongs to (if external) */ packageName: string | null; } declare interface RestElement extends BaseNode { type: 'RestElement'; argument: Pattern; typeAnnotation?: TSTypeAnnotation; } /** * Scan error */ export declare interface ScanError { /** Error message */ message: string; /** Source file */ filePath: string; /** Line number (1-based) */ line?: number; /** Column number (1-based) */ column?: number; /** Error severity */ severity: 'error' | 'warning'; } /** * Result from scanning multiple files */ export declare interface ScanResults { /** All scanned files */ files: FileScanResult[]; /** All classes found (flattened) */ classes: RawClassDefinition[]; /** All errors (flattened) */ errors: ScanError[]; /** Total parse time in milliseconds */ totalParseTimeMs: number; /** Number of files scanned */ fileCount: number; /** Accumulated type aliases across all files */ typeAliases: Record; /** Accumulated SMRT package imports across all files (package name → Set of imported class names) */ smrtImports?: Map>; /** * The project's declared agent-addressable surface, merged across files with * deterministic identity so emission never depends on file order (#2591). */ agentSurface: AgentSurface; } export declare function scanSvelteAgentSurface(filePath: string, sourceText?: string): AgentSurfaceDiagnostic[]; declare interface SmartObjectConfig { tableStrategy?: 'sti' | 'cti'; idType?: 'uuid' | 'text'; features?: Record; }>; api?: { include?: string[]; exclude?: string[]; }; cli?: boolean | { include?: string[]; exclude?: string[]; skipApiCheck?: boolean; http?: boolean; }; mcp?: { include?: string[]; exclude?: string[]; }; [key: string]: unknown; } declare interface SmartObjectDefinition { name: string; className: string; qualifiedName?: QualifiedClassName; collection: string; filePath: string; packageName?: string; packageVersion?: string; importPath?: string; modulePath?: string; exportName?: string; collectionExportName?: string; fields: Record; methods: Record; decoratorConfig: SmartObjectConfig; extends?: string; extendsTypeArg?: string; staticProperties?: Record; } declare interface SmartObjectManifest { version: string; /** Always {@link MANIFEST_TIMESTAMP}: build output must be reproducible. */ timestamp: number; packageName?: string; packageVersion?: string; objects: Record; moduleType?: string; smrtDependencies?: string[]; } export declare interface SourceDiscoveryOptions { cwd: string; include: string[]; exclude: string[]; followSymbolicLinks?: boolean; } declare interface SourceLocation { start: Position; end: Position; } /** * Cheap pre-filter so callers can skip parsing files that cannot produce a * finding. * * Conservative by construction: every condition here is textually implied by a * finding. A reported field lives in a class carrying `@smrt` or * `extends Smrt…`, is initialized to a number, and has a money or rate word at * one of the boundaries the head-noun splitter cuts on. It may still return * `true` for a file the AST pass then clears — that is the intended direction. * * @param source - Full file contents. * @returns `false` only when the file provably cannot produce a finding. */ export declare function sourceMayContainNumericPrecisionIssue(source: string): boolean; /** * Fast pre-check: a file that names neither helper cannot declare either, so * the AST walk is skipped entirely. Callers use it to keep the matcher off the * hot path of a large scan. */ export declare function sourceMayDeclareAgentSurface(sourceText: string): boolean; /** * Split an identifier into lowercase words on camelCase, PascalCase, digits, * and underscores. `totalAmountCents` → `['total', 'amount', 'cents']`. */ export declare function splitIdentifierWords(name: string): string[]; declare interface SpreadElement extends BaseNode { type: 'SpreadElement'; argument: Expression; } declare type Statement = ClassDeclaration | ExportNamedDeclaration | ExportDefaultDeclaration | ImportDeclaration | TSTypeAliasDeclaration | TSEnumDeclaration | VariableDeclaration; declare interface TSAnyKeyword extends BaseNode { type: 'TSAnyKeyword'; } declare interface TSArrayType extends BaseNode { type: 'TSArrayType'; elementType: TSType; } declare interface TSBigIntKeyword extends BaseNode { type: 'TSBigIntKeyword'; } declare interface TSBooleanKeyword extends BaseNode { type: 'TSBooleanKeyword'; } declare interface TSEnumBody extends BaseNode { type: 'TSEnumBody'; members: TSEnumMember[]; } declare interface TSEnumDeclaration extends BaseNode { type: 'TSEnumDeclaration'; id: Identifier; body?: TSEnumBody; members?: TSEnumMember[]; } declare interface TSEnumMember extends BaseNode { type: 'TSEnumMember'; initializer?: Expression; } declare interface TSFunctionType extends BaseNode { type: 'TSFunctionType'; } declare interface TSIndexSignature extends BaseNode { type: 'TSIndexSignature'; parameters: Identifier[]; typeAnnotation?: TSTypeAnnotation; } declare interface TSLiteralType extends BaseNode { type: 'TSLiteralType'; literal?: Literal; } declare interface TSMethodSignature extends BaseNode { type: 'TSMethodSignature'; key: Expression; params: Pattern[]; returnType?: TSTypeAnnotation; } declare interface TSNeverKeyword extends BaseNode { type: 'TSNeverKeyword'; } declare interface TSNullKeyword extends BaseNode { type: 'TSNullKeyword'; } declare interface TSNumberKeyword extends BaseNode { type: 'TSNumberKeyword'; } declare interface TSObjectKeyword extends BaseNode { type: 'TSObjectKeyword'; } declare interface TSPropertySignature extends BaseNode { type: 'TSPropertySignature'; key: Expression; typeAnnotation?: TSTypeAnnotation; optional?: boolean; readonly?: boolean; } declare interface TSQualifiedName extends BaseNode { type: 'TSQualifiedName'; left: Identifier | TSQualifiedName; right: Identifier; } declare interface TSStringKeyword extends BaseNode { type: 'TSStringKeyword'; } declare interface TSSymbolKeyword extends BaseNode { type: 'TSSymbolKeyword'; } declare interface TSThisType extends BaseNode { type: 'TSThisType'; } declare type TSType = TSStringKeyword | TSNumberKeyword | TSBooleanKeyword | TSAnyKeyword | TSUnknownKeyword | TSNeverKeyword | TSObjectKeyword | TSBigIntKeyword | TSSymbolKeyword | TSVoidKeyword | TSNullKeyword | TSUndefinedKeyword | TSThisType | TSTypeReference | TSArrayType | TSUnionType | TSTypeLiteral | TSLiteralType | TSFunctionType; declare interface TSTypeAliasDeclaration extends BaseNode { type: 'TSTypeAliasDeclaration'; id: Identifier; typeAnnotation: TSType; } declare interface TSTypeAnnotation extends BaseNode { type: 'TSTypeAnnotation'; typeAnnotation: TSType; } declare type TSTypeElement = TSPropertySignature | TSMethodSignature | TSIndexSignature; declare interface TSTypeLiteral extends BaseNode { type: 'TSTypeLiteral'; members: TSTypeElement[]; } declare interface TSTypeParameterInstantiation extends BaseNode { type: 'TSTypeParameterInstantiation'; params: TSType[]; } declare interface TSTypeReference extends BaseNode { type: 'TSTypeReference'; typeName: Identifier | TSQualifiedName; typeParameters?: TSTypeParameterInstantiation; typeArguments?: TSTypeParameterInstantiation; } declare interface TSUndefinedKeyword extends BaseNode { type: 'TSUndefinedKeyword'; } declare interface TSUnionType extends BaseNode { type: 'TSUnionType'; types: TSType[]; } declare interface TSUnknownKeyword extends BaseNode { type: 'TSUnknownKeyword'; } declare interface TSVoidKeyword extends BaseNode { type: 'TSVoidKeyword'; } declare interface UnaryExpression extends BaseNode { type: 'UnaryExpression'; operator: string; argument: Expression; } declare interface VariableDeclaration extends BaseNode { type: 'VariableDeclaration'; kind: 'const' | 'let' | 'var'; declarations: VariableDeclarator[]; } declare interface VariableDeclarator extends BaseNode { type: 'VariableDeclarator'; id: Identifier | Pattern; init: Expression | null; } /** * Verify that `dist/manifest.json` contains every `@smrt()` object declared in * the package source. See module docs for the rationale and guarantees. */ export declare function verifyManifestCompleteness(options: VerifyManifestCompletenessOptions): Promise; export declare interface VerifyManifestCompletenessOptions { /** Absolute or relative path to the package directory to verify. */ packageDir: string; /** Source include globs (default mirrors the build). */ include?: string[]; /** Source exclude globs (default mirrors the build). */ exclude?: string[]; /** Package directory basenames to skip (default: framework infrastructure). */ skipPackages?: string[]; } export declare interface VerifyManifestCompletenessResult { status: VerifyManifestStatus; packageName?: string; manifestPath?: string; /** Qualified object keys present in source but missing from the manifest. */ missing: string[]; /** Number of objects the source is expected to contribute. */ expectedCount: number; /** Number of objects present in the published manifest. */ distCount: number; /** Human-readable explanation for `skipped` / `missing-manifest`. */ reason?: string; } /** * Manifest completeness verification. * * Re-scans a package's `src/` with the same OXC scanner the build uses and * asserts that every `@smrt()`-decorated object (and its collection) is present * in the package's published `dist/manifest.json`. This is a publish-time guard: * downstream schema migration is manifest-driven, so a stale manifest that omits * an object means consumers can never create its table via `smrt db:migrate`. * * Root cause it guards against (issue #1483): `@happyvertical/smrt-jobs@0.27.41` * shipped a `dist/manifest.json` generated before `SmrtWorker` existed. The * source, exports, and `__smrt-register__` path were all correct, but the * published manifest had only 4 of 6 objects, so `_smrt_workers` was never * created and `TaskRunner.start()` threw on every consumer that upgraded. * * The check compares object-key SETS only. The downstream manifest-enrichment * passes (schema/validation/agent generation) mutate object entries but never * add or remove object keys, so the scanner + adapter object set is the source * of truth for "which objects the published manifest must contain". The * comparison is `expected ⊆ dist`: enrichment passes that add keys to `dist` * (e.g. STI children) never trigger a false failure. * * Parse errors are handled before the set comparison: a syntactically broken * source file drops its objects from `expected` AND `dist` symmetrically, which * a naive `expected ⊆ dist` would wave through as `ok`. The scan's * `severity:'error'` entries therefore short-circuit to a distinct `scan-error` * status so a broken source can never masquerade as a complete manifest. * * @see https://github.com/happyvertical/smrt/issues/1483 */ export declare type VerifyManifestStatus = 'ok' | 'incomplete' | 'missing-manifest' | 'scan-error' | 'skipped'; export { }