@happyvertical/smrt-core 0.57.17 → 0.57.19
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/README.md +14 -0
- package/agents/build-knowledge.md +3 -0
- package/agents/cookbook-engine.md +2 -1
- package/dist/consumer-plugin/index.d.ts.map +1 -1
- package/dist/consumer-plugin/index.js +237 -10
- package/dist/consumer-plugin/index.js.map +1 -1
- package/dist/cookbook/engine/commands.d.ts.map +1 -1
- package/dist/cookbook/engine/commands.js +7 -2
- package/dist/cookbook/engine/commands.js.map +1 -1
- package/dist/cookbook/engine/document.d.ts.map +1 -1
- package/dist/cookbook/engine/document.js +2 -0
- package/dist/cookbook/engine/document.js.map +1 -1
- package/dist/cookbook/engine/engine.d.ts.map +1 -1
- package/dist/cookbook/engine/engine.js +1 -0
- package/dist/cookbook/engine/engine.js.map +1 -1
- package/dist/cookbook/engine/schemas.d.ts.map +1 -1
- package/dist/cookbook/engine/schemas.js +7 -1
- package/dist/cookbook/engine/schemas.js.map +1 -1
- package/dist/cookbook/engine/types.d.ts +3 -1
- package/dist/cookbook/engine/types.d.ts.map +1 -1
- package/dist/cookbook/engine/types.js.map +1 -1
- package/dist/cookbook/v1.schema.json +1 -0
- package/dist/cookbook/validate.d.ts.map +1 -1
- package/dist/cookbook/validate.js +6 -1
- package/dist/cookbook/validate.js.map +1 -1
- package/dist/manifest/static-manifest.js +1 -1
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/scripts/generate-manifest.js +1 -0
- package/dist/scripts/prepack.js +4 -0
- package/dist/scripts/verify-production-manifest.js +25 -0
- package/dist/smrt-knowledge.json +8 -7
- package/dist/utils/stack-frames.d.ts.map +1 -1
- package/dist/utils/stack-frames.js +10 -0
- package/dist/utils/stack-frames.js.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.js +15 -6
- package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
- package/package.json +11 -10
package/dist/manifest.json
CHANGED
|
@@ -40,6 +40,7 @@ async function generateManifest() {
|
|
|
40
40
|
'src/**/*.test.ts',
|
|
41
41
|
'src/**/*.spec.ts',
|
|
42
42
|
'src/**/__tests__/**/*.ts',
|
|
43
|
+
'src/**/__test-*/**', // Temporary test projects are never package models (#4190)
|
|
43
44
|
'src/**/*.d.ts',
|
|
44
45
|
'src/scanner/**/*.ts',
|
|
45
46
|
'src/vite-plugin/**/*.ts',
|
package/dist/scripts/prepack.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { execFileSync } from 'node:child_process';
|
|
15
15
|
import { existsSync } from 'node:fs';
|
|
16
16
|
import { resolve } from 'node:path';
|
|
17
|
+
import { verifyProductionManifest } from './verify-production-manifest.js';
|
|
17
18
|
|
|
18
19
|
function run(script) {
|
|
19
20
|
execFileSync('npm', ['run', script], {
|
|
@@ -47,3 +48,6 @@ if (runningInCi && hasReusableBuildArtifacts()) {
|
|
|
47
48
|
}
|
|
48
49
|
run('build:fresh');
|
|
49
50
|
}
|
|
51
|
+
|
|
52
|
+
// Validate both newly generated and reused CI artifacts before packaging.
|
|
53
|
+
verifyProductionManifest(resolve('dist/manifest.json'));
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
|
|
3
|
+
/** Reject test authority even when prepack reuses an earlier CI build. */
|
|
4
|
+
export function verifyProductionManifest(manifestPath) {
|
|
5
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
6
|
+
if (
|
|
7
|
+
manifest.artifactPurpose === 'test' ||
|
|
8
|
+
!manifest.objects ||
|
|
9
|
+
typeof manifest.objects !== 'object' ||
|
|
10
|
+
Array.isArray(manifest.objects)
|
|
11
|
+
) {
|
|
12
|
+
throw new Error(`Invalid production manifest: ${manifestPath}`);
|
|
13
|
+
}
|
|
14
|
+
for (const [identity, object] of Object.entries(manifest.objects)) {
|
|
15
|
+
const path = String(object.filePath ?? '').replaceAll('\\', '/');
|
|
16
|
+
if (
|
|
17
|
+
/(?:^|\/)(?:__tests__|__test-[^/]+)(?:\/|$)/.test(path) ||
|
|
18
|
+
/\.(?:test|spec)\.[cm]?[jt]sx?$/.test(path)
|
|
19
|
+
) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`Test fixture in production manifest: ${identity} (${path})`,
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
package/dist/smrt-knowledge.json
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
"sensitiveFieldsExcluded": true,
|
|
4
4
|
"generatedAt": "1970-01-01T00:00:00.000Z",
|
|
5
5
|
"packageName": "@happyvertical/smrt-core",
|
|
6
|
-
"packageVersion": "0.57.
|
|
6
|
+
"packageVersion": "0.57.19",
|
|
7
7
|
"sourceManifestPath": "dist/manifest.json",
|
|
8
8
|
"agentDocPath": "AGENTS.md",
|
|
9
9
|
"sourceHashes": {
|
|
10
|
-
"manifest": "
|
|
11
|
-
"packageJson": "
|
|
10
|
+
"manifest": "a7d53aaef5aee844c20588413367a2f37d0abdb16f2b3bbbd70c01578cfea66a",
|
|
11
|
+
"packageJson": "f6e734164ef91d613c00382a882a655cb039f1331b1750c2ca12e4842ac9c4c8",
|
|
12
12
|
"agents": "3a78a0babd2884ac22e4b376a3c9618037ab818ad4bdd762751f82fa53c21d04",
|
|
13
13
|
"moduleDoc:agents/object-runtime.md": "fd2748649e820c2fe5960fa4a44c5f3e2a5afa57da89925e8b4cb3b53d67a07b",
|
|
14
14
|
"moduleDoc:agents/revision-guard.md": "f6ea32a6860964c98aba14b93587f40b33c8f7d0deccb2c54d21bff45db3da42",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"moduleDoc:agents/change-feed.md": "c3d6206758dd1d2e0475177e1938fb7d7b6cfe8a6c4d3c3b56ef62bc7bc7739d",
|
|
20
20
|
"moduleDoc:agents/change-signals.md": "a2dbe7077483b5a19bd5ea9589b5a08fe847377c8e05d01d57f813c6d9cbc28b",
|
|
21
21
|
"moduleDoc:agents/generators.md": "6eecf5c639bac7c88d7b72456f16d84dd9b69bebac76464e9329b4775c54906f",
|
|
22
|
-
"moduleDoc:agents/build-knowledge.md": "
|
|
22
|
+
"moduleDoc:agents/build-knowledge.md": "b8bed0288c2fd4b525f3bc92baae1c629af02124a13fca0063d3c98ae56045b1",
|
|
23
23
|
"moduleDoc:agents/memory.md": "a86bdf370403d08ecad3be86e341eb322dfe6e6b7a4e8806a6f8be9bf3421da5",
|
|
24
24
|
"moduleDoc:agents/system-diagnostics.md": "b7ccae18ee312bf788d5470f95fa78d3535f1789247d5372e40ca86538290772",
|
|
25
25
|
"moduleDoc:agents/registry-snapshot.md": "6234aa540427396d1dda1825a4641b1072e247187d5ddf413ae46c691344ee30",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"moduleDoc:agents/run-once.md": "43c589a3cab2ff61147128014cc0084509faf623eb76860140d589539420a1f3",
|
|
28
28
|
"moduleDoc:agents/recipes.md": "b8a0661d03d3f8df07874456164ef5648c62c775267d8e8340dac0a5d383a54e",
|
|
29
29
|
"moduleDoc:agents/cookbook.md": "20c256f4f6219f9b2add6365ef930e0bb3eda37e7866ddbc4bdd466830f76e4f",
|
|
30
|
-
"moduleDoc:agents/cookbook-engine.md": "
|
|
30
|
+
"moduleDoc:agents/cookbook-engine.md": "f8e6869021b6cc214fe34da20b73a83509abc10bab71299ea9668b47a1c4307d",
|
|
31
31
|
"moduleDoc:agents/browser-entry.md": "8d928babfe7308512c901a12c23fbfb7ff395f6b1cddce5cd069e4ece38743da",
|
|
32
32
|
"moduleDoc:agents/test-types.md": "37c009ae2bd21001d83bba6cfab2047465fe32d5443de12661e24ba7d4e2d613"
|
|
33
33
|
},
|
|
@@ -97,6 +97,7 @@
|
|
|
97
97
|
"@types/pluralize": "^0.0.33",
|
|
98
98
|
"@xenova/transformers": "^2.17.2",
|
|
99
99
|
"ajv": "8.20.0",
|
|
100
|
+
"reflect-metadata": "0.2.2",
|
|
100
101
|
"svelte": "^5.57.1",
|
|
101
102
|
"svelte-check": "^4.7.6",
|
|
102
103
|
"typescript": "6.0.3",
|
|
@@ -1197,7 +1198,7 @@
|
|
|
1197
1198
|
{
|
|
1198
1199
|
"path": "agents/build-knowledge.md",
|
|
1199
1200
|
"module": "build-knowledge",
|
|
1200
|
-
"content": "# Decorators, build integration, and knowledge\n\nKey options: `tableName`, `tableStrategy` ('cti'|'sti'), `conflictColumns`, `indexes` (declared multi-column indexes; see [schema-paths.md](schema-paths.md)), `api`/`mcp`/`cli` (generation config), `ai` (callable methods), `hooks` (beforeSave/afterSave/beforeDelete/afterDelete), `embeddings` (auto-generate), `tenantScoped`, `agent`, `ui` (`{ icon, label, description }` — nav/help hints round-tripped through the manifest as plain data; `description` is the object-level seed for form-level help, #2046).\n\nRegistration sets `SMRT_TABLE_NAME` static property (survives minification).\n\nAn explicit `@smrt({ collection: 'domain.records' })` names the route/permission\nnamespace independently of `tableName`, including standalone non-STI models.\nScanner output preserves it in both `collection` and `decoratorConfig.collection`.\nGenerated registration reads the merged declaration, so older manifests with a\nderived top-level collection still agree with native decorators. Explicit runtime\nregistration options win; absent declarations retain manifest/STI/default naming.\n\n## @field() UI hints (#2046)\n\n`@field({ ui: { basic, group, order, locked } })` — a static, presentation-only\nseed for the field-policy rail (epic #2045). Carried in the manifest under the\nfield's `_meta.ui` (never a top-level `FieldDefinition` key), readable at\nruntime via `getAllFields()` at `field._meta.ui`, and emitted (sanitized) with\n`description` into generated web-collection definitions and browser MCP tool\nschemas. No schema/persistence/security effect — `sensitive`/`readPermission`\nstay the security rail, and `sensitive`/`transient` fields never emit to the\nclient at all.\n\n`ui.widget` (#3599) adds a presentation widget hint:\n`'textarea' | 'currency' | 'email' | 'url' | 'phone'`. It rides the same\n`_meta.ui` channel and is also emitted (sanitized) into web-collection\ndefinitions. The manifest generator validates it against the field type and\nfails the build otherwise: every widget needs a `text` field (`currency` is a\ncurrency-CODE picker for an ISO 4217 string such as `currency = 'USD'`, not a\nmoney amount; amounts are integer minor units and take no widget); an unknown\nvalue or a non-text field is an error. `ui.widget` is a hint only — it never changes\nthe column type or validation.\n\n`ui.format` (#3718) says how a value is presented: `money` (an `integer` of\nminor units — the amount hint record pages format and total), `date` (a\nbusiness date on a `datetime`, shown without a time), `datetime`, `percent`\n(a rate on a `decimal`/`integer`). Same channel, same emission, same build-time\ncheck against the field type. `@foreignKey(Target, { role: 'items' })` (or on\n`@crossPackageRef`) marks the declaring model as the target's line items; the\nscanner keeps it at `_meta.role`, the build rejects it on a non-reference or\nan unknown value, and the generated web relationship edge carries\n`role: 'items'` (edges without a role stay byte-stable). Both are hints: no\nschema, persistence or security effect.\n\n## Presentation metadata (#3599)\n\nPure helpers live in `src/ui-metadata.ts`, published browser-safe as\n`@happyvertical/smrt-core/ui-metadata` (also re-exported from the package\nroot). `ManifestGenerator.applyUiMetadata()` runs in `applyGenerationPasses`\nafter inheritance, so every producer (Vite plugin, `ManifestBuilder`,\n`generateManifest`) validates the same way.\n\n- **Display label.** `@smrt({ display: { label: '<field>' } })` names the own\n field generic pickers and assistants use to label a record. Own fields only —\n no paths into other packages; a model whose label lives elsewhere ships a\n selector. Undeclared, it defaults to the first of `name`, `title`, `label`,\n `code` that is a usable own field. Build fails when the declared field is\n missing, `sensitive`, `transient`, or a relationship/json field. The resolved\n field is emitted as `displayLabelField` on the manifest object and in the\n knowledge artifact, and generated MCP/WebMCP `list`/`get`/`update`/`delete`\n descriptions end with ``Records are identified by their `<field>` field.``\n (`describeAction` in `generators/tool-schema.ts`; nothing is added when the\n model has no label field).\n- **Selector slots.** `ModuleUISlot.selects: '@scope/pkg:Model'`\n (`@happyvertical/smrt-types`) marks a package component as THE selector for a\n model. The scanner reads such slots statically from `src/**/ui.ts`\n (`parseUiSelectorsFile`, independent of the class glob) and the manifest\n carries them as `uiSelectors` keyed by slot id. `selects` must be a string\n literal; a duplicate slot id, two selectors for one model, or an own-package\n target that does not exist fails the build. Hosts call\n `findSelectorFor(manifests, qualifiedName)`; generic forms use the selector\n for any `@foreignKey`/`@crossPackageRef` field targeting that model and fall\n back to a generic picker labelled by `displayLabelField`. `display` is read\n per class: an STI subclass that declares none uses the default lookup rather\n than its base's declaration, and a CTI child cannot name a parent's field.\n\n## Worker registration artifact (#3117)\n\nWith `svelteKit.enabled`, a production `vite build` compiles the generated\n`smrt-register.ts` into `.smrt/runtime/register.js` after the server build\n(`src/vite-plugin/worker-registration.ts`, called once from `closeBundle`).\nadapter-node re-bundles the server from its own entries, so the web bundle is\nnever importable by a separate Node process; this artifact is how a deployed\ntask/schedule worker registers the app's local objects (and, through the\n`.smrt/register.js` it imports, consumed packages' objects) with the same\nqualified identities and isolated manifests as the web server. It is a nested\nSSR build with no consumer plugins: only aliases pointing into the project\nsources (e.g. `$lib`) apply, and bare imports stay external so the worker\nshares the installed `@happyvertical/smrt-core` registry. The sole exception is\n`@oxc-project/runtime` and its helper subpaths: Vite injects them when lowering\ndecorators, but applications do not declare that compiler support dependency,\nso the artifact bundles them. SvelteKit\nvirtual modules (`$env/*`, `$app/*`) are not available to a worker; an object\nimporting one fails at worker startup. A failure fails the build.\n\n## Lightweight discovery\n\nFilesystem-only discovery, module-doc parsing, and graph freshness are owned by\n`@happyvertical/smrt-scanner/knowledge`. Core's knowledge exports remain compatible\nreexports. Runtime manifest projection and custom-action semantics stay in core.\n\n`src/knowledge-discovery.ts`, exported through `smrt-core/knowledge`, enumerates\ninstalled scope directories and reads canonical AGENTS/legacy CLAUDE docs without\nloading package code, artifacts, or scanning objects. CLI snapshots and MCP share\nthese primitives. Callers retain selection policy: snapshots resolve links and\nfall back to `packages/*` only when no installed SMRT package loads; MCP preserves\nnode_modules paths, deduplicates realpaths, excludes authored workspace links,\nand then enriches the selected packages. Workspace-root/glob discovery remains\nowned by each consumer.\n\n## Domain Knowledge Artifacts\n\n`smrtPlugin()` writes runtime manifests and agent/developer knowledge artifacts:\n\n- local dev/build: `.smrt/manifest.json` and `.smrt/smrt-knowledge.json`\n- package build: `dist/manifest.json` and `dist/smrt-knowledge.json`\n\nKeep `manifest.json` runtime-focused. `smrt-knowledge.json` is the deterministic\nagent contract for downstream review and architecture tools.\n\nThe CLI's schema-command gate reads that same merged file: a pure-consumer\nproject (0 local objects) qualifies on the consumed packages its manifest\nrecords, so a clobbered `objects` map is what made `smrt db:migrate` report\n`missing_local_manifest` (#2925).\n\n`.smrt/manifest.json` has two writers and neither owns all of it: `smrtPlugin()`\ncontributes the project's scanned objects and `smrtConsumer()` the consumed\npackages' entries. Both writes are merge-preserving, keyed on each entry's\nrecorded `packageName` — a plain overwrite in either direction silently drops\nthe other's objects, and the last pass of a multi-environment build is not\nreliably the aggregating one (#1760, #2925). `dist/manifest.json` stays\nlocal-only: a published package must not carry its dependencies' objects.\n\nThe schema-version-1 object projection is additive and high-signal: it retains\nnormalized tenant mode/field, explicit `cti`/`sti` strategy, conflict columns,\nmethod signatures, and field defaults/constraints/readonly/transient flags.\nSensitive fields are removed before both `fields` and `relationships` are\nderived, including legacy flags stored under `_meta`; matching field and\nsnake-case column names are also removed from projected conflict columns, and a\nsensitive custom tenant field is omitted while retaining scope and mode.\nGenerated artifacts assert this boundary with `sensitiveFieldsExcluded: true`;\nthe optional marker keeps schema version 1 additive while letting readers\nidentify older artifacts that require raw-manifest corroboration.\n\nConfig precedence for knowledge is defaults → top-level `knowledge` in\n`smrt.config.ts` → `packages[packageName].knowledge` → plugin option →\nobject-level `@smrt({ knowledge })`.\n\nTest generation uses that same effective configuration and publishes knowledge\nmatching the manifest it generated. Disabling knowledge removes a previous local\nknowledge artifact, so consumers cannot mistake it for current enabled output.\n`new ManifestBuilder(projectRoot)` keeps discovery, package identity, and output\nat that explicit root, including installed providers and external base classes;\nomitting the argument retains the current-working-directory\nbehavior. Both manifest writers retain declared package dependencies when merging\ntheir local and consumed-object projections.\n\nTest manifests carry optional `artifactPurpose: 'test'`; dependency discovery\nskips those artifacts, including `.smrt` fallbacks. Unmarked runtime manifests,\nempty published providers, and source-only development providers remain eligible.\n`ManifestBuilder` accepts explicit `outputMode: 'dev' | 'build'`; omitted mode\nretains legacy filename inference. Vitest selects dev mode explicitly, so tests\nnever replace a production `dist/manifest.json`. Relative output directories are\nresolved against the builder's project root.\n\nWith `svelteKit.enabled`, `smrtPlugin()`'s transform prepends\n`import './smrt-register.js';` to the configured config module\n(`<configPath>/<configFileName>`, `src/vite-plugin/sveltekit-register-injection.ts`)\nonce the register module exists, on the same first line so line numbers hold.\nApps therefore never hand-write the guarded registration import (#3416); a\nconfig module that still has it imports the same module twice, harmlessly.\n\nTracked `src/lib/server/smrt-register.ts` belongs to SvelteKit config/type\ngeneration. Generic library builds consume it but do not cache or restore it\nas an output; otherwise a concurrent config producer's bytes can be replayed\nover a newer registration after dependency eligibility changes.\n\nObject-level `knowledge: false` excludes an object from authored context only;\nit must not change runtime manifest registration. Use\n`knowledge: { tags, summary, risks }` for review-sensitive domain objects.\n\nHTTP knowledge routes are disabled by default. If `knowledge.api.enabled` is\ntrue, generated SvelteKit routes must stay GET-only and guarded by dev mode or\nadmin auth.\n\n\n## Vite Plugin\n\n`src/vite-preset/` (`@happyvertical/smrt-core/vite`, `smrt()`) composes the Oxc\ndecorator block, `smrtConsumer`, then `smrtPlugin` with the template path\nconventions. Its package list comes only from the `smrt({ packages })` option, or\n`smrt.config` `consumer.packages` (smrt-config schema); it never sniffs\n`package.json`, and a missing or malformed list throws. Keep its output\nbyte-identical to the two-plugin form (`src/vite-preset/index.test.ts`). It\nalso dedupes `@sveltejs/kit` (`smrt:shared-runtime`): SvelteKit matches\n`redirect()`/`fail()` by class, and a linked/workspace SMRT SvelteKit entry\nwould otherwise import its own copy and turn redirects into 500s. The preset\nalso keeps the thin `@happyvertical/smrt-app-runtime/sveltekit` entry inside\nthe SSR bundle: adapter-node bundles Kit itself, and an external runtime\nwould load a separate class through Node even at the same installed version.\nVite matches `ssr.noExternal` by package name, so a pre-resolver keeps the\nexact runtime root external and `ssr.external` keeps `./sveltekit-runtime`\nexternal. That implementation subpath is an internal composition boundary;\napps keep importing `./sveltekit`. This avoids bundling native SQL dependencies.\nThis split applies only to builds; development keeps normal SSR externalization\nand Kit dedupe because Vite's dev module runner ignores resolver external flags.\nOther runtime dependencies retain their normal externalization. Do not explicitly\nexternalize app-runtime in an app config. The production regression in\n`packages/app-runtime/src/sveltekit-production.test.ts` exercises the final\nadapter-node HTTP handler using an installed copy of the published dist entry.\n\n**Package models over REST (#3749).** `consumer.packages` registers a package's\nmodels and gives `@smrt/web` (the consumer's `collectionDefinitions`, one per\nlist-capable model, endpoint `/<collection>`) their definitions, but a package\nmodel gets generated REST routes only when it is on the consumer's hosting\nallowlist (`smrtConsumer({ svelteKit: { objects } })`, #2850: an HTTP exposure\nboundary, each model's own `api` config still applies, so `api: false` and\nempty-include models stay closed, tenancy comes with the generated route). The\npreset fills that allowlist: with `smrt.cookbook.json` present it is the models\nof the cookbook's recipes plus `features`, minus models the cookbook `exposure`\nturns off for `api` or a recipe's `options[model].exposure.api === false`\n(`vite-preset/cookbook-exposure.ts`; recipe `{ exclude }` narrowing is not\napplied by the build yet). `smrt({ expose })` replaces the derived list. No\ncookbook and no `expose` keeps `svelteKit: true` (nothing hosted). The producer's\n`@happyvertical/smrt-virt-web` still covers only the app's own models; read\npackage models from `@smrt/web`. `smrt({ objectsDir })` is passed to the hosting\ncontribution too: the producer and consumer share one `routesDir`, and the\ngenerator refuses it when their settings differ.\n\n**STI siblings (#3749).** Package STI subclasses share their base's table, and\nthe manifest gives them the base's `collection` unless they declare one\n(`consumer-plugin/hosted-sti-collections.ts`). Two hosted siblings would both\nclaim `/<base collection>`, so each hosted subclass that inherits its\ncollection gets its class-derived one (`Order` -> `orders`,\n`defaultCollectionName` in the scanner), in every manifest view the consumer\nderives: routes, `@smrt/web`, the generated client and registration. This is the\nconvention the framework already has for an independent route collection\n(`@smrt({ collection })`), applied by default for hosted subclasses; an explicit\ncollection wins. The route therefore reads through the subclass's own\ncollection (rows scoped to its `_meta_type`) and the definition for that model\nadvertises that route (`objectRef` = the model, `endpoint` = `/orders`). A hosted\nbase keeps the shared name and the base-collection semantics (every row of the\ntable, any subtype). A subclass left off the allowlist keeps the shared\ncollection and folds into the base's definition, as before. A collection class\nthat inherits another's item type (`MaterialCollection extends\nProductCollection`) resolves to the same item and collection, so only one of\nthem is hosted (the class named for the item, else the first). Subclasses that\ndeclare no `api` config get the omitted-config default (full CRUD), not the\nbase's `include`; narrow them on the subclass if the base is meant to restrict\nthem.\n\n```typescript\n// vite.config.ts — required for @smrt() decorators (Vite 8+, oxc transform)\nexport default defineConfig({\n oxc: {\n decorator: {\n legacy: true,\n emitDecoratorMetadata: true,\n },\n },\n});\n```\n\nUnder Vite 8 the oxc transform does not honor the pre-Vite-8 `esbuild.tsconfigRaw`\nrecipe (or tsconfig `experimentalDecorators` reached through SvelteKit's\n`extends \"./.svelte-kit/tsconfig.json\"` chain), so that recipe throws\n`SyntaxError: Invalid or unexpected token` on the first SSR request. Configure\ndecorators through `oxc.decorator` instead. Consumers still pinned on vite<8 need\nthe legacy `esbuild.tsconfigRaw` form with `experimentalDecorators: true,\nemitDecoratorMetadata: true`.\n\nFor independent CI invocations, both `smrtPlugin()` and `smrtConsumer()` accept\nthe same `generationSnapshot: { path, sha256, provenance, sourceRoot }`. The\nschema-v1 snapshot produced by `serializeSmrtGenerationSnapshot()` contains the\nmerged project/dependency manifest, portable source paths, and source-file\ndigests; each plugin selects its own view. Reuse mode fails closed on\nbyte/provenance/path/content drift, skips scans and manifest writes, and still\ngenerates routes, types, registration, and virtual modules. Omit it for normal\nlocal development and watch mode.\n\n## Clean consumer type checks (TypeScript 6)\n\nThe consumer config hook writes `.smrt/register.js` alongside generated types\nafter route preflight succeeds. Generated SvelteKit registration imports that\nmodule, so it must exist during `svelte-kit sync`, before Vite's `buildStart`.\nBoth hosted routes and the legacy `svelteKit: true` mode follow this order.\nThe clean-consumer regression fixture ships real provider declarations and\nchecks the generated imports with `svelte-check`.\nCore's `generate:test` task depends on its own `build`: both publish the local\n`.smrt` manifest/knowledge pair, so Turbo must serialize production before test\ngeneration instead of caching an interleaved pair.\n\n## Model descriptions\n\nEach manifest object and knowledge `objects[]` entry may carry a user-facing\n`description` (for the planner Features tab and similar). The scanner\n(`extractClassJsDocDescription` in `packages/scanner/src/oxc-parser.ts`) reads it\nfrom the parsed source, in priority order: explicit `@smrt({ description })`,\nelse the first paragraph of the class JSDoc (the JSDoc may sit above `export`\nand decorators), minus a leading `ClassName - ` / `ClassName: `, tags\n(`{@link}`, backticks, HTML) and later paragraphs, capped at 200 chars on a\nsentence boundary. `@internal` classes and classes without docs emit no key. It\nis not inherited from a parent class. Generated MCP tool descriptions are\nunchanged (they stay generic).\n"
|
|
1201
|
+
"content": "# Decorators, build integration, and knowledge\n\nKey options: `tableName`, `tableStrategy` ('cti'|'sti'), `conflictColumns`, `indexes` (declared multi-column indexes; see [schema-paths.md](schema-paths.md)), `api`/`mcp`/`cli` (generation config), `ai` (callable methods), `hooks` (beforeSave/afterSave/beforeDelete/afterDelete), `embeddings` (auto-generate), `tenantScoped`, `agent`, `ui` (`{ icon, label, description }` — nav/help hints round-tripped through the manifest as plain data; `description` is the object-level seed for form-level help, #2046).\n\nRegistration sets `SMRT_TABLE_NAME` static property (survives minification).\n\nAn explicit `@smrt({ collection: 'domain.records' })` names the route/permission\nnamespace independently of `tableName`, including standalone non-STI models.\nScanner output preserves it in both `collection` and `decoratorConfig.collection`.\nGenerated registration reads the merged declaration, so older manifests with a\nderived top-level collection still agree with native decorators. Explicit runtime\nregistration options win; absent declarations retain manifest/STI/default naming.\n\n## @field() UI hints (#2046)\n\n`@field({ ui: { basic, group, order, locked } })` — a static, presentation-only\nseed for the field-policy rail (epic #2045). Carried in the manifest under the\nfield's `_meta.ui` (never a top-level `FieldDefinition` key), readable at\nruntime via `getAllFields()` at `field._meta.ui`, and emitted (sanitized) with\n`description` into generated web-collection definitions and browser MCP tool\nschemas. No schema/persistence/security effect — `sensitive`/`readPermission`\nstay the security rail, and `sensitive`/`transient` fields never emit to the\nclient at all.\n\n`ui.widget` (#3599) adds a presentation widget hint:\n`'textarea' | 'currency' | 'email' | 'url' | 'phone'`. It rides the same\n`_meta.ui` channel and is also emitted (sanitized) into web-collection\ndefinitions. The manifest generator validates it against the field type and\nfails the build otherwise: every widget needs a `text` field (`currency` is a\ncurrency-CODE picker for an ISO 4217 string such as `currency = 'USD'`, not a\nmoney amount; amounts are integer minor units and take no widget); an unknown\nvalue or a non-text field is an error. `ui.widget` is a hint only — it never changes\nthe column type or validation.\n\n`ui.format` (#3718) says how a value is presented: `money` (an `integer` of\nminor units — the amount hint record pages format and total), `date` (a\nbusiness date on a `datetime`, shown without a time), `datetime`, `percent`\n(a rate on a `decimal`/`integer`). Same channel, same emission, same build-time\ncheck against the field type. `@foreignKey(Target, { role: 'items' })` (or on\n`@crossPackageRef`) marks the declaring model as the target's line items; the\nscanner keeps it at `_meta.role`, the build rejects it on a non-reference or\nan unknown value, and the generated web relationship edge carries\n`role: 'items'` (edges without a role stay byte-stable). Both are hints: no\nschema, persistence or security effect.\n\n## Presentation metadata (#3599)\n\nPure helpers live in `src/ui-metadata.ts`, published browser-safe as\n`@happyvertical/smrt-core/ui-metadata` (also re-exported from the package\nroot). `ManifestGenerator.applyUiMetadata()` runs in `applyGenerationPasses`\nafter inheritance, so every producer (Vite plugin, `ManifestBuilder`,\n`generateManifest`) validates the same way.\n\n- **Display label.** `@smrt({ display: { label: '<field>' } })` names the own\n field generic pickers and assistants use to label a record. Own fields only —\n no paths into other packages; a model whose label lives elsewhere ships a\n selector. Undeclared, it defaults to the first of `name`, `title`, `label`,\n `code` that is a usable own field. Build fails when the declared field is\n missing, `sensitive`, `transient`, or a relationship/json field. The resolved\n field is emitted as `displayLabelField` on the manifest object and in the\n knowledge artifact, and generated MCP/WebMCP `list`/`get`/`update`/`delete`\n descriptions end with ``Records are identified by their `<field>` field.``\n (`describeAction` in `generators/tool-schema.ts`; nothing is added when the\n model has no label field).\n- **Selector slots.** `ModuleUISlot.selects: '@scope/pkg:Model'`\n (`@happyvertical/smrt-types`) marks a package component as THE selector for a\n model. The scanner reads such slots statically from `src/**/ui.ts`\n (`parseUiSelectorsFile`, independent of the class glob) and the manifest\n carries them as `uiSelectors` keyed by slot id. `selects` must be a string\n literal; a duplicate slot id, two selectors for one model, or an own-package\n target that does not exist fails the build. Hosts call\n `findSelectorFor(manifests, qualifiedName)`; generic forms use the selector\n for any `@foreignKey`/`@crossPackageRef` field targeting that model and fall\n back to a generic picker labelled by `displayLabelField`. `display` is read\n per class: an STI subclass that declares none uses the default lookup rather\n than its base's declaration, and a CTI child cannot name a parent's field.\n\n## Worker registration artifact (#3117)\n\nWith `svelteKit.enabled`, a production `vite build` compiles the generated\n`smrt-register.ts` into `.smrt/runtime/register.js` after the server build\n(`src/vite-plugin/worker-registration.ts`, called once from `closeBundle`).\nadapter-node re-bundles the server from its own entries, so the web bundle is\nnever importable by a separate Node process; this artifact is how a deployed\ntask/schedule worker registers the app's local objects (and, through the\n`.smrt/register.js` it imports, consumed packages' objects) with the same\nqualified identities and isolated manifests as the web server. It is a nested\nSSR build with no consumer plugins: only aliases pointing into the project\nsources (e.g. `$lib`) apply, and bare imports stay external so the worker\nshares the installed `@happyvertical/smrt-core` registry. The sole exception is\n`@oxc-project/runtime` and its helper subpaths: Vite injects them when lowering\ndecorators, but applications do not declare that compiler support dependency,\nso the artifact bundles them. SvelteKit\nvirtual modules (`$env/*`, `$app/*`) are not available to a worker; an object\nimporting one fails at worker startup. A failure fails the build.\n\n## Lightweight discovery\n\nFilesystem-only discovery, module-doc parsing, and graph freshness are owned by\n`@happyvertical/smrt-scanner/knowledge`. Core's knowledge exports remain compatible\nreexports. Runtime manifest projection and custom-action semantics stay in core.\n\n`src/knowledge-discovery.ts`, exported through `smrt-core/knowledge`, enumerates\ninstalled scope directories and reads canonical AGENTS/legacy CLAUDE docs without\nloading package code, artifacts, or scanning objects. CLI snapshots and MCP share\nthese primitives. Callers retain selection policy: snapshots resolve links and\nfall back to `packages/*` only when no installed SMRT package loads; MCP preserves\nnode_modules paths, deduplicates realpaths, excludes authored workspace links,\nand then enriches the selected packages. Workspace-root/glob discovery remains\nowned by each consumer.\n\n## Domain Knowledge Artifacts\n\n`smrtPlugin()` writes runtime manifests and agent/developer knowledge artifacts:\n\n- local dev/build: `.smrt/manifest.json` and `.smrt/smrt-knowledge.json`\n- package build: `dist/manifest.json` and `dist/smrt-knowledge.json`\n\nKeep `manifest.json` runtime-focused. `smrt-knowledge.json` is the deterministic\nagent contract for downstream review and architecture tools.\n\nThe CLI's schema-command gate reads that same merged file: a pure-consumer\nproject (0 local objects) qualifies on the consumed packages its manifest\nrecords, so a clobbered `objects` map is what made `smrt db:migrate` report\n`missing_local_manifest` (#2925).\n\n`.smrt/manifest.json` has two writers and neither owns all of it: `smrtPlugin()`\ncontributes the project's scanned objects and `smrtConsumer()` the consumed\npackages' entries. Both writes are merge-preserving, keyed on each entry's\nrecorded `packageName` — a plain overwrite in either direction silently drops\nthe other's objects, and the last pass of a multi-environment build is not\nreliably the aggregating one (#1760, #2925). `dist/manifest.json` stays\nlocal-only: a published package must not carry its dependencies' objects.\nCore's canonical generator excludes temporary `__test-*` source roots as well\nas test files and `__tests__`; prepack verifies fresh and reused manifests\ncontain no test-artifact marker or fixture source provenance (#4190).\n\nThe schema-version-1 object projection is additive and high-signal: it retains\nnormalized tenant mode/field, explicit `cti`/`sti` strategy, conflict columns,\nmethod signatures, and field defaults/constraints/readonly/transient flags.\nSensitive fields are removed before both `fields` and `relationships` are\nderived, including legacy flags stored under `_meta`; matching field and\nsnake-case column names are also removed from projected conflict columns, and a\nsensitive custom tenant field is omitted while retaining scope and mode.\nGenerated artifacts assert this boundary with `sensitiveFieldsExcluded: true`;\nthe optional marker keeps schema version 1 additive while letting readers\nidentify older artifacts that require raw-manifest corroboration.\n\nConfig precedence for knowledge is defaults → top-level `knowledge` in\n`smrt.config.ts` → `packages[packageName].knowledge` → plugin option →\nobject-level `@smrt({ knowledge })`.\n\nTest generation uses that same effective configuration and publishes knowledge\nmatching the manifest it generated. Disabling knowledge removes a previous local\nknowledge artifact, so consumers cannot mistake it for current enabled output.\n`new ManifestBuilder(projectRoot)` keeps discovery, package identity, and output\nat that explicit root, including installed providers and external base classes;\nomitting the argument retains the current-working-directory\nbehavior. Both manifest writers retain declared package dependencies when merging\ntheir local and consumed-object projections.\n\nTest manifests carry optional `artifactPurpose: 'test'`; dependency discovery\nskips those artifacts, including `.smrt` fallbacks. Unmarked runtime manifests,\nempty published providers, and source-only development providers remain eligible.\n`ManifestBuilder` accepts explicit `outputMode: 'dev' | 'build'`; omitted mode\nretains legacy filename inference. Vitest selects dev mode explicitly, so tests\nnever replace a production `dist/manifest.json`. Relative output directories are\nresolved against the builder's project root.\n\nWith `svelteKit.enabled`, `smrtPlugin()`'s transform prepends\n`import './smrt-register.js';` to the configured config module\n(`<configPath>/<configFileName>`, `src/vite-plugin/sveltekit-register-injection.ts`)\nonce the register module exists, on the same first line so line numbers hold.\nApps therefore never hand-write the guarded registration import (#3416); a\nconfig module that still has it imports the same module twice, harmlessly.\n\nTracked `src/lib/server/smrt-register.ts` belongs to SvelteKit config/type\ngeneration. Generic library builds consume it but do not cache or restore it\nas an output; otherwise a concurrent config producer's bytes can be replayed\nover a newer registration after dependency eligibility changes.\n\nObject-level `knowledge: false` excludes an object from authored context only;\nit must not change runtime manifest registration. Use\n`knowledge: { tags, summary, risks }` for review-sensitive domain objects.\n\nHTTP knowledge routes are disabled by default. If `knowledge.api.enabled` is\ntrue, generated SvelteKit routes must stay GET-only and guarded by dev mode or\nadmin auth.\n\n\n## Vite Plugin\n\n`src/vite-preset/` (`@happyvertical/smrt-core/vite`, `smrt()`) composes the Oxc\ndecorator block, `smrtConsumer`, then `smrtPlugin` with the template path\nconventions. Its package list comes only from the `smrt({ packages })` option, or\n`smrt.config` `consumer.packages` (smrt-config schema); it never sniffs\n`package.json`, and a missing or malformed list throws. Keep its output\nbyte-identical to the two-plugin form (`src/vite-preset/index.test.ts`). It\nalso dedupes `@sveltejs/kit` (`smrt:shared-runtime`): SvelteKit matches\n`redirect()`/`fail()` by class, and a linked/workspace SMRT SvelteKit entry\nwould otherwise import its own copy and turn redirects into 500s. The preset\nalso keeps the thin `@happyvertical/smrt-app-runtime/sveltekit` entry inside\nthe SSR bundle: adapter-node bundles Kit itself, and an external runtime\nwould load a separate class through Node even at the same installed version.\nVite matches `ssr.noExternal` by package name, so a pre-resolver keeps the\nexact runtime root external and `ssr.external` keeps `./sveltekit-runtime`\nexternal. That implementation subpath is an internal composition boundary;\napps keep importing `./sveltekit`. This avoids bundling native SQL dependencies.\nThis split applies only to builds; development keeps normal SSR externalization\nand Kit dedupe because Vite's dev module runner ignores resolver external flags.\nOther runtime dependencies retain their normal externalization. Do not explicitly\nexternalize app-runtime in an app config. The production regression in\n`packages/app-runtime/src/sveltekit-production.test.ts` exercises the final\nadapter-node HTTP handler using an installed copy of the published dist entry.\n\n**Package models over REST (#3749).** `consumer.packages` registers a package's\nmodels and gives `@smrt/web` (the consumer's `collectionDefinitions`, one per\nlist-capable model, endpoint `/<collection>`) their definitions, but a package\nmodel gets generated REST routes only when it is on the consumer's hosting\nallowlist (`smrtConsumer({ svelteKit: { objects } })`, #2850: an HTTP exposure\nboundary, each model's own `api` config still applies, so `api: false` and\nempty-include models stay closed, tenancy comes with the generated route). The\npreset fills that allowlist: with `smrt.cookbook.json` present it is the models\nof the cookbook's recipes plus `features`, minus models the cookbook `exposure`\nturns off for `api` or a recipe's `options[model].exposure.api === false`\n(`vite-preset/cookbook-exposure.ts`; recipe `{ exclude }` narrowing is not\napplied by the build yet). `smrt({ expose })` replaces the derived list. No\ncookbook and no `expose` keeps `svelteKit: true` (nothing hosted). The producer's\n`@happyvertical/smrt-virt-web` still covers only the app's own models; read\npackage models from `@smrt/web`. `smrt({ objectsDir })` is passed to the hosting\ncontribution too: the producer and consumer share one `routesDir`, and the\ngenerator refuses it when their settings differ.\n\n**STI siblings (#3749).** Package STI subclasses share their base's table, and\nthe manifest gives them the base's `collection` unless they declare one\n(`consumer-plugin/hosted-sti-collections.ts`). Two hosted siblings would both\nclaim `/<base collection>`, so each hosted subclass that inherits its\ncollection gets its class-derived one (`Order` -> `orders`,\n`defaultCollectionName` in the scanner), in every manifest view the consumer\nderives: routes, `@smrt/web`, the generated client and registration. This is the\nconvention the framework already has for an independent route collection\n(`@smrt({ collection })`), applied by default for hosted subclasses; an explicit\ncollection wins. The route therefore reads through the subclass's own\ncollection (rows scoped to its `_meta_type`) and the definition for that model\nadvertises that route (`objectRef` = the model, `endpoint` = `/orders`). A hosted\nbase keeps the shared name and the base-collection semantics (every row of the\ntable, any subtype). A subclass left off the allowlist keeps the shared\ncollection and folds into the base's definition, as before. A collection class\nthat inherits another's item type (`MaterialCollection extends\nProductCollection`) resolves to the same item and collection, so only one of\nthem is hosted (the class named for the item, else the first). Subclasses that\ndeclare no `api` config get the omitted-config default (full CRUD), not the\nbase's `include`; narrow them on the subclass if the base is meant to restrict\nthem.\n\n```typescript\n// vite.config.ts — required for @smrt() decorators (Vite 8+, oxc transform)\nexport default defineConfig({\n oxc: {\n decorator: {\n legacy: true,\n emitDecoratorMetadata: true,\n },\n },\n});\n```\n\nUnder Vite 8 the oxc transform does not honor the pre-Vite-8 `esbuild.tsconfigRaw`\nrecipe (or tsconfig `experimentalDecorators` reached through SvelteKit's\n`extends \"./.svelte-kit/tsconfig.json\"` chain), so that recipe throws\n`SyntaxError: Invalid or unexpected token` on the first SSR request. Configure\ndecorators through `oxc.decorator` instead. Consumers still pinned on vite<8 need\nthe legacy `esbuild.tsconfigRaw` form with `experimentalDecorators: true,\nemitDecoratorMetadata: true`.\n\nFor independent CI invocations, both `smrtPlugin()` and `smrtConsumer()` accept\nthe same `generationSnapshot: { path, sha256, provenance, sourceRoot }`. The\nschema-v1 snapshot produced by `serializeSmrtGenerationSnapshot()` contains the\nmerged project/dependency manifest, portable source paths, and source-file\ndigests; each plugin selects its own view. Reuse mode fails closed on\nbyte/provenance/path/content drift, skips scans and manifest writes, and still\ngenerates routes, types, registration, and virtual modules. Omit it for normal\nlocal development and watch mode.\n\n## Clean consumer type checks (TypeScript 6)\n\nThe consumer config hook writes `.smrt/register.js` alongside generated types\nafter route preflight succeeds. Generated SvelteKit registration imports that\nmodule, so it must exist during `svelte-kit sync`, before Vite's `buildStart`.\nBoth hosted routes and the legacy `svelteKit: true` mode follow this order.\nThe clean-consumer regression fixture ships real provider declarations and\nchecks the generated imports with `svelte-check`.\nCore's `generate:test` task depends on its own `build`: both publish the local\n`.smrt` manifest/knowledge pair, so Turbo must serialize production before test\ngeneration instead of caching an interleaved pair.\n\n## Model descriptions\n\nEach manifest object and knowledge `objects[]` entry may carry a user-facing\n`description` (for the planner Features tab and similar). The scanner\n(`extractClassJsDocDescription` in `packages/scanner/src/oxc-parser.ts`) reads it\nfrom the parsed source, in priority order: explicit `@smrt({ description })`,\nelse the first paragraph of the class JSDoc (the JSDoc may sit above `export`\nand decorators), minus a leading `ClassName - ` / `ClassName: `, tags\n(`{@link}`, backticks, HTML) and later paragraphs, capped at 200 chars on a\nsentence boundary. `@internal` classes and classes without docs emit no key. It\nis not inherited from a parent class. Generated MCP tool descriptions are\nunchanged (they stay generic).\n"
|
|
1201
1202
|
},
|
|
1202
1203
|
{
|
|
1203
1204
|
"path": "agents/memory.md",
|
|
@@ -1237,7 +1238,7 @@
|
|
|
1237
1238
|
{
|
|
1238
1239
|
"path": "agents/cookbook-engine.md",
|
|
1239
1240
|
"module": "cookbook-engine",
|
|
1240
|
-
"content": "# Cookbook command engine (#3753)\n\n`@happyvertical/smrt-core/cookbook/engine` (`src/cookbook/engine/`) is the one\nplace the rules for changing a [cookbook](cookbook.md) live. A planner UI, a\nlocal Kitchen MCP server, the CLI and a website all call it; each is an adapter.\nIt imports only relative modules and `@happyvertical/smrt-types` types, so it\nruns in Node and in a browser. No SvelteKit, storage, singleton or inference.\n\nIt lives in `smrt-core` because the contract and validator (`./cookbook`) do and\nthe package DAG forbids a back edge: types -> core. A separate package would add\na node for pure functions with no other dependency.\n\n## API\n\n```ts\nimport { createCookbookEngine } from '@happyvertical/smrt-core/cookbook/engine';\nconst engine = createCookbookEngine({ catalog, cookbook?, state? });\nengine.run({ id?, name, input, expectedRevision? }) // CommandResult, never throws\nengine.batch({ id?, commands, expectedRevision? }) // BatchResult, atomic\nengine.snapshot() / .cookbook() / .state() / .subscribe(fn)\nengine.tools / engine.batchTool // JSON Schema tool definitions\n```\n\n- **Catalog is data** (`EngineCatalog`): `recipes` (manifest `RecipeDefinition`\n fits), `models` (`id, name, packageId, exposed?, fields?`), optional\n `cookbooks` (a library), `themePresets`, `currencies`, `settingTargets`.\n Nothing is bundled. Build it from published manifests or `smrt-knowledge.json`.\n- **Commands** (20, `COOKBOOK_COMMANDS_VERSION = 1`): `apply_cookbook` (select),\n `import_cookbook`, `add_cookbook`, `remove_cookbook`, `add_recipes`,\n `remove_recipes`, `add_features`, `remove_features`, `set_settings`,\n `set_policy`, `set_theme`, `reset_theme`, `set_name`, `rename_section`,\n `rename_item`, `hide`, `show`, `validate`, `export_cookbook`, `undo`. Navigation\n and focus are not commands: they are an adapter concern.\n- **`set_theme`** takes `preset`, `primary` (a brand colour that replaces the\n preset, see cookbook.md), `colorScheme`, `fontFamily` (`null` restores the\n preset font), and `borderRadius` (`none|sm|md|lg|xl|full`), `density`\n (`comfortable|touch`), `contrast` (`auto|standard|more`). Each is optional and\n only the fields given change; repeating the same input is an unchanged\n receipt. Values outside the enums are `invalid_input`. `snapshot().theme`\n reports the three new fields (`null` when unset); `reset_theme` clears them.\n- **Errors** are typed (`CommandError`: `code`, `message`, `path?`, `details?`):\n `unknown_command`, `invalid_input`, `not_found`, `confirmation_required`,\n `conflict`, `id_reuse`, `batch_failed`, `failed`.\n- **Result** carries the `receipt` (`name, id?, summary, changed, revisionBefore,\n revisionAfter, undoId?, changes?, warnings?`), the compact `snapshot` and\n optional `data` (`validate`, `export_cookbook`).\n\n## Execution contract\n\n- **Revisions** count applied changes (a no-op does not move it), so equal\n histories give equal revisions. `expectedRevision` that is not current fails\n `conflict` with `details.{expectedRevision,currentRevision}`: an older model\n action cannot overwrite a newer manual edit. Send it on every model action.\n- **Command ids**: a repeated `id` with the same command returns the first\n result with `replayed: true` and applies nothing (failures replay too: take a\n new id after re-reading). The same id with a different command is `id_reuse`.\n The journal keeps `maxJournal` (500) ids and is part of `state()`.\n- **Atomic batches** run on a working copy; one failure leaves the state\n byte-identical and returns `batch_failed` naming `details.index`. A batch that\n changes n times advances the revision by n, as n runs would, and has one undo.\n `undo` and per-command ids/revisions are not allowed inside a batch.\n- **Undo** restores the document from before the change; it is a `conflict`\n while the plan differs from what that change left, unless `force`.\n- **Recovery**: `state()` is plain JSON (revision, applied library id, document,\n journal); `createCookbookEngine({ catalog, state })` resumes it, retries\n included. Undo entries are not persisted.\n- **Canonical document**: ids sorted, policies sorted, empty sections absent,\n key order fixed, so two engines export identical text. Recipe/feature ids the\n catalog lacks stay in the document and show as `snapshot.unavailable`.\n\n## Deliberately not here\n\nPackage or starter version pinning (waits on #3604/#3754). Deep overview-widget\nand nav-id checks (smrt-svelte owns them; `import_cookbook` carries `overviews`\nand `layout` through unjudged, `validate` only flags layout ids the menu lacks).\nTyped diagnostics from `validateCookbook` itself (#3754): `validate` wraps its\nstrings as `invalid_document` until it returns them.\n\n## Becoming an adapter: what smrt-planner changes (after the smrt release)\n\nParity to hold: the planner's `tests/headless-planner.test.ts` scenario is\nported in `__tests__/engine.test.ts` (`SCRIPT`) over a data catalog.\n\n1. Depend on the released `@happyvertical/smrt-core`; build `EngineCatalog` from\n its catalog data (`recipes.json`/`catalog.json` -> `recipes`, `models`,\n `cookbooks`: the library wrapper keeps id/icon/keywords planner-side).\n2. Delete `commands/execute.ts`, `commands/runner.ts`, `commands/snapshot.ts`,\n `commands/schemas.ts` (keep `checkSchema` only if still used), `recipes/\n plan-data.ts`, `recipes/resolve.ts`, `settings/app-settings.ts` rules,\n `assistant/change.ts` apply* helpers, `cookbook/assemble.ts` and the\n `createPlainHost`. `createHeadlessPlanner` becomes `createCookbookEngine`\n behind the same `run/snapshot/subscribe/cookbook` names.\n3. The browser controller keeps: `focus` and the planner tab/section cells\n (snapshot `focus` is added by the adapter), reactive wrapping of\n `engine.subscribe`, `onReplaced` (regenerate sample data after\n `apply_cookbook`), and the `cookbookStore` <-> `engine.state()` sync\n (`engine.cookbook()` to persist; rebuild with `cookbook`/`state` on load).\n4. Command differences to absorb: `apply_cookbook`/`import_cookbook` report\n `changed` by content; `rename_*`/`hide`/`show` need the catalog `models`\n `packageId` for item ids; undo is whole-document (the old per-slice undo goes);\n `unsupported` is gone (the engine always has every slice); new `set_name`,\n `validate`; receipts gain `revisionBefore/After`; `expectedRevision` moves\n into the command (the option form still works).\n5. Overview customisation stays a planner/smrt-svelte concern; hold `overviews`\n in the document via `import_cookbook` and write them with a planner-side\n command until an engine command is agreed.\n6. MCP/Kitchen (#3752, done): `smrt kitchen mcp` holds one engine per plan id and\n serves `engine.batchTool`'s schema as `run_commands`; the plan reaches disk\n as `smrt.cookbook.json` through `cookbook apply` (`cli/agents/kitchen.md`).\n"
|
|
1241
|
+
"content": "# Cookbook command engine (#3753)\n\n`@happyvertical/smrt-core/cookbook/engine` (`src/cookbook/engine/`) is the one\nplace the rules for changing a [cookbook](cookbook.md) live. A planner UI, a\nlocal Kitchen MCP server, the CLI and a website all call it; each is an adapter.\nIt imports only relative modules and `@happyvertical/smrt-types` types, so it\nruns in Node and in a browser. No SvelteKit, storage, singleton or inference.\n\nIt lives in `smrt-core` because the contract and validator (`./cookbook`) do and\nthe package DAG forbids a back edge: types -> core. A separate package would add\na node for pure functions with no other dependency.\n\n## API\n\n```ts\nimport { createCookbookEngine } from '@happyvertical/smrt-core/cookbook/engine';\nconst engine = createCookbookEngine({ catalog, cookbook?, state? });\nengine.run({ id?, name, input, expectedRevision? }) // CommandResult, never throws\nengine.batch({ id?, commands, expectedRevision? }) // BatchResult, atomic\nengine.snapshot() / .cookbook() / .state() / .subscribe(fn)\nengine.tools / engine.batchTool // JSON Schema tool definitions\n```\n\n- **Catalog is data** (`EngineCatalog`): `recipes` (manifest `RecipeDefinition`\n fits), `models` (`id, name, packageId, exposed?, fields?`), optional\n `cookbooks` (a library), `themePresets`, `currencies`, `settingTargets`.\n Nothing is bundled. Build it from published manifests or `smrt-knowledge.json`.\n- **Commands** (20, `COOKBOOK_COMMANDS_VERSION = 1`): `apply_cookbook` (select),\n `import_cookbook`, `add_cookbook`, `remove_cookbook`, `add_recipes`,\n `remove_recipes`, `add_features`, `remove_features`, `set_settings`,\n `set_policy`, `set_theme`, `reset_theme`, `set_name` (name, description and `specs`, free-text requirements beyond\n the recipes; internal newlines are kept), `rename_section`,\n `rename_item`, `hide`, `show`, `validate`, `export_cookbook`, `undo`. Navigation\n and focus are not commands: they are an adapter concern.\n- **`set_theme`** takes `preset`, `primary` (a brand colour that replaces the\n preset, see cookbook.md), `colorScheme`, `fontFamily` (`null` restores the\n preset font), and `borderRadius` (`none|sm|md|lg|xl|full`), `density`\n (`comfortable|touch`), `contrast` (`auto|standard|more`). Each is optional and\n only the fields given change; repeating the same input is an unchanged\n receipt. Values outside the enums are `invalid_input`. `snapshot().theme`\n reports the three new fields (`null` when unset); `reset_theme` clears them.\n- **Errors** are typed (`CommandError`: `code`, `message`, `path?`, `details?`):\n `unknown_command`, `invalid_input`, `not_found`, `confirmation_required`,\n `conflict`, `id_reuse`, `batch_failed`, `failed`.\n- **Result** carries the `receipt` (`name, id?, summary, changed, revisionBefore,\n revisionAfter, undoId?, changes?, warnings?`), the compact `snapshot` and\n optional `data` (`validate`, `export_cookbook`).\n\n## Execution contract\n\n- **Revisions** count applied changes (a no-op does not move it), so equal\n histories give equal revisions. `expectedRevision` that is not current fails\n `conflict` with `details.{expectedRevision,currentRevision}`: an older model\n action cannot overwrite a newer manual edit. Send it on every model action.\n- **Command ids**: a repeated `id` with the same command returns the first\n result with `replayed: true` and applies nothing (failures replay too: take a\n new id after re-reading). The same id with a different command is `id_reuse`.\n The journal keeps `maxJournal` (500) ids and is part of `state()`.\n- **Atomic batches** run on a working copy; one failure leaves the state\n byte-identical and returns `batch_failed` naming `details.index`. A batch that\n changes n times advances the revision by n, as n runs would, and has one undo.\n `undo` and per-command ids/revisions are not allowed inside a batch.\n- **Undo** restores the document from before the change; it is a `conflict`\n while the plan differs from what that change left, unless `force`.\n- **Recovery**: `state()` is plain JSON (revision, applied library id, document,\n journal); `createCookbookEngine({ catalog, state })` resumes it, retries\n included. Undo entries are not persisted.\n- **Canonical document**: ids sorted, policies sorted, empty sections absent,\n key order fixed, so two engines export identical text. Recipe/feature ids the\n catalog lacks stay in the document and show as `snapshot.unavailable`.\n\n## Deliberately not here\n\nPackage or starter version pinning (waits on #3604/#3754). Deep overview-widget\nand nav-id checks (smrt-svelte owns them; `import_cookbook` carries `overviews`\nand `layout` through unjudged, `validate` only flags layout ids the menu lacks).\nTyped diagnostics from `validateCookbook` itself (#3754): `validate` wraps its\nstrings as `invalid_document` until it returns them.\n\n## Becoming an adapter: what smrt-planner changes (after the smrt release)\n\nParity to hold: the planner's `tests/headless-planner.test.ts` scenario is\nported in `__tests__/engine.test.ts` (`SCRIPT`) over a data catalog.\n\n1. Depend on the released `@happyvertical/smrt-core`; build `EngineCatalog` from\n its catalog data (`recipes.json`/`catalog.json` -> `recipes`, `models`,\n `cookbooks`: the library wrapper keeps id/icon/keywords planner-side).\n2. Delete `commands/execute.ts`, `commands/runner.ts`, `commands/snapshot.ts`,\n `commands/schemas.ts` (keep `checkSchema` only if still used), `recipes/\n plan-data.ts`, `recipes/resolve.ts`, `settings/app-settings.ts` rules,\n `assistant/change.ts` apply* helpers, `cookbook/assemble.ts` and the\n `createPlainHost`. `createHeadlessPlanner` becomes `createCookbookEngine`\n behind the same `run/snapshot/subscribe/cookbook` names.\n3. The browser controller keeps: `focus` and the planner tab/section cells\n (snapshot `focus` is added by the adapter), reactive wrapping of\n `engine.subscribe`, `onReplaced` (regenerate sample data after\n `apply_cookbook`), and the `cookbookStore` <-> `engine.state()` sync\n (`engine.cookbook()` to persist; rebuild with `cookbook`/`state` on load).\n4. Command differences to absorb: `apply_cookbook`/`import_cookbook` report\n `changed` by content; `rename_*`/`hide`/`show` need the catalog `models`\n `packageId` for item ids; undo is whole-document (the old per-slice undo goes);\n `unsupported` is gone (the engine always has every slice); new `set_name`,\n `validate`; receipts gain `revisionBefore/After`; `expectedRevision` moves\n into the command (the option form still works).\n5. Overview customisation stays a planner/smrt-svelte concern; hold `overviews`\n in the document via `import_cookbook` and write them with a planner-side\n command until an engine command is agreed.\n6. MCP/Kitchen (#3752, done): `smrt kitchen mcp` holds one engine per plan id and\n serves `engine.batchTool`'s schema as `run_commands`; the plan reaches disk\n as `smrt.cookbook.json` through `cookbook apply` (`cli/agents/kitchen.md`).\n"
|
|
1241
1242
|
},
|
|
1242
1243
|
{
|
|
1243
1244
|
"path": "agents/browser-entry.md",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stack-frames.d.ts","sourceRoot":"","sources":["../../src/utils/stack-frames.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,EAAE,SAAS,MAAM,EAKvD,CAAC;
|
|
1
|
+
{"version":3,"file":"stack-frames.d.ts","sourceRoot":"","sources":["../../src/utils/stack-frames.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,EAAE,SAAS,MAAM,EAKvD,CAAC;AAYF;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAmBjE;AAYD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAG7D;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnE;AAqDD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAUzD"}
|
|
@@ -35,6 +35,15 @@ var DECORATOR_RUNTIME_PACKAGES = [
|
|
|
35
35
|
"tslib"
|
|
36
36
|
];
|
|
37
37
|
/**
|
|
38
|
+
* Plumbing packages matched only as an installed `node_modules/<pkg>/` path.
|
|
39
|
+
* `reflect-metadata` (pulled in by e.g. `@simplewebauthn/server` and tsyringe)
|
|
40
|
+
* defines `Reflect.decorate`, which tsc-style `__decorate` helpers delegate
|
|
41
|
+
* to, so every decorator is then applied FROM its Reflect.js frame. The name
|
|
42
|
+
* is also a plausible directory in an application's own source tree, so the
|
|
43
|
+
* match is anchored to `node_modules` rather than any `/reflect-metadata/`.
|
|
44
|
+
*/
|
|
45
|
+
var INSTALLED_ONLY_RUNTIME_PACKAGES = ["reflect-metadata"];
|
|
46
|
+
/**
|
|
38
47
|
* Whether a file path (normalized to forward slashes) lives inside a
|
|
39
48
|
* decorator-runtime helper package — i.e. a stack frame introduced by decorator
|
|
40
49
|
* lowering rather than the module that declares the decorated class.
|
|
@@ -47,6 +56,7 @@ var DECORATOR_RUNTIME_PACKAGES = [
|
|
|
47
56
|
*/
|
|
48
57
|
function isDecoratorRuntimeFramePath(path) {
|
|
49
58
|
const lower = path.toLowerCase();
|
|
59
|
+
if (INSTALLED_ONLY_RUNTIME_PACKAGES.some((pkg) => lower.includes(`/node_modules/${pkg}/`))) return true;
|
|
50
60
|
return DECORATOR_RUNTIME_PACKAGES.some((pkg) => lower.includes(`/${pkg}/`) || lower.includes(`\0${pkg.replace("/", "+")}@`));
|
|
51
61
|
}
|
|
52
62
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stack-frames.js","names":[],"sources":["../../src/utils/stack-frames.ts"],"sourcesContent":["/**\n * Decorator-runtime stack-frame detection (#1785).\n *\n * ObjectRegistry attributes a `@smrt()` class to its declaring package by\n * walking the registration call stack for the first frame OUTSIDE smrt-core —\n * the module that applied the decorator. Decorator *lowering* breaks that\n * assumption: the compiler inserts a runtime-helper frame between the `@smrt()`\n * decorator and the consumer module that declares the class.\n *\n * Under Vite 8, oxc's legacy-decorator lowering routes the decorator application\n * through `@oxc-project/runtime`'s helper (e.g.\n * `.../@oxc-project/runtime/src/helpers/esm/applyDecoratedDescriptor.js`), so a\n * naive walk stops at that frame and misattributes the class to\n * `@oxc-project/runtime` instead of the consumer package. `tslib` (tsc's\n * `__decorate`), `@swc/helpers`, and `@babel/runtime` insert the identical kind\n * of frame under their respective toolchains.\n *\n * These are pure plumbing packages — a class is applied THROUGH them, never\n * DECLARED in them — so both stack walks (source-file identity in\n * `registry/shared-state.ts` and package attribution in\n * `manifest/manifest-loader.ts`) skip them and continue to the real declaring\n * module. Kept as a leaf module (no smrt imports) so both walkers can share it\n * without risking an import cycle.\n */\n\n/**\n * Runtime-helper packages that decorator lowering routes the decorator\n * application through. Frames from these packages are never the module that\n * declares a decorated class.\n */\nexport const DECORATOR_RUNTIME_PACKAGES: readonly string[] = [\n '@oxc-project/runtime',\n '@swc/helpers',\n '@babel/runtime',\n 'tslib',\n];\n\n/**\n * Whether a file path (normalized to forward slashes) lives inside a\n * decorator-runtime helper package — i.e. a stack frame introduced by decorator\n * lowering rather than the module that declares the decorated class.\n *\n * Matches both the plain installed form (`.../node_modules/tslib/...`) and the\n * pnpm form (`.../node_modules/.pnpm/tslib@2/node_modules/tslib/...`) because\n * the real `/<package>/` segment appears in both. Case-insensitive.\n *\n * @param path - A file path from a stack frame (already normalized to `/`).\n */\nexport function isDecoratorRuntimeFramePath(path: string): boolean {\n const lower = path.toLowerCase();\n return DECORATOR_RUNTIME_PACKAGES.some(\n (pkg) =>\n lower.includes(`/${pkg}/`) ||\n // Vite serves the helper as a virtual module whose id flattens the\n // scope separator and appends the version, e.g.\n // `<root>/\\0@oxc-project+runtime@0.138.0/helpers/esm/decorate.js`.\n // Missing it attributed every class Vitest loads to the project root's\n // package (#3098).\n lower.includes(`\\0${pkg.replace('/', '+')}@`),\n );\n}\n\n/**\n * Module and test runners that evaluate a module and so appear below it on\n * the stack. Like decorator helpers, they never declare a class.\n */\nconst MODULE_RUNNER_PATH_SEGMENTS: readonly string[] = [\n '/node_modules/@vitest/',\n '/node_modules/vitest/',\n '/node_modules/vite/dist/node/module-runner',\n];\n\n/**\n * Whether a stack-frame path belongs to a module or test runner (Vitest, the\n * Vite module runner) rather than to code that could declare a class (#3098).\n *\n * @param path - A file path from a stack frame (already normalized to `/`).\n */\nexport function isModuleRunnerFramePath(path: string): boolean {\n const lower = path.toLowerCase();\n return MODULE_RUNNER_PATH_SEGMENTS.some((segment) => lower.includes(segment));\n}\n\n/**\n * Whether a scoped package name (as captured from a `node_modules/@scope/name`\n * stack match) is a decorator-runtime helper package.\n *\n * @param name - A scoped package name, e.g. `@oxc-project/runtime`.\n */\nexport function isDecoratorRuntimePackageName(name: string): boolean {\n return DECORATOR_RUNTIME_PACKAGES.includes(name);\n}\n\nlet smrtCoreRoot: string | null | undefined;\n\n/**\n * The directory of the smrt-core package this module belongs to (its\n * `package.json` names `@happyvertical/smrt-core`), or `null` when it cannot\n * be determined (a browser, or core inlined into a consumer's bundle).\n * Resolved once from this module's own location, so it follows the monorepo\n * checkout, a linked checkout, or an installed copy alike.\n */\nfunction getSmrtCoreRoot(): string | null {\n if (smrtCoreRoot !== undefined) return smrtCoreRoot;\n smrtCoreRoot = null;\n try {\n const getBuiltinModule = (\n globalThis as {\n process?: { getBuiltinModule?: (id: string) => unknown };\n }\n ).process?.getBuiltinModule;\n const fs = getBuiltinModule?.('node:fs') as\n | typeof import('node:fs')\n | undefined;\n const path = getBuiltinModule?.('node:path') as\n | typeof import('node:path')\n | undefined;\n const url = getBuiltinModule?.('node:url') as\n | typeof import('node:url')\n | undefined;\n if (!fs || !path || !url || !import.meta.url.startsWith('file:')) {\n return smrtCoreRoot;\n }\n let dir = path.dirname(url.fileURLToPath(import.meta.url));\n for (let depth = 0; depth < 16; depth++) {\n const manifest = path.join(dir, 'package.json');\n if (\n fs.existsSync(manifest) &&\n (JSON.parse(fs.readFileSync(manifest, 'utf8')) as { name?: string })\n .name === '@happyvertical/smrt-core'\n ) {\n smrtCoreRoot = dir.replace(/\\\\/g, '/');\n break;\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n } catch {\n smrtCoreRoot = null;\n }\n return smrtCoreRoot;\n}\n\n/**\n * Whether a stack-frame path is smrt-core's own code, which applies decorators\n * but never declares an application class. Both stack walks (source-file\n * identity in `registry/shared-state.ts` and package attribution in\n * `manifest/manifest-loader.ts`) skip these frames.\n *\n * Matched by where core actually lives, never by a generic substring: a\n * consumer's own `packages/core` workspace package (ergot's `@ergot/core`) or\n * a file named like a core module declares classes too. That is this core's\n * package directory (outside its `__tests__` fixtures) and any installed\n * `node_modules/@happyvertical/smrt-core` copy. With source maps enabled —\n * `tsx`, `node --enable-source-maps`, Vite SSR — an installed core's frames\n * report mapped `.../node_modules/@happyvertical/smrt-core/src/...` paths;\n * missing them attributed every class to `@happyvertical/smrt-core` (#3109,\n * #3110).\n *\n * @param path - A file path from a stack frame (already normalized to `/`).\n */\nexport function isSmrtCoreFramePath(path: string): boolean {\n const normalized = path.replace(/^file:\\/\\//, '').replace(/\\\\/g, '/');\n if (\n normalized.toLowerCase().includes('/node_modules/@happyvertical/smrt-core/')\n ) {\n return true;\n }\n const root = getSmrtCoreRoot();\n if (!root || !normalized.startsWith(`${root}/`)) return false;\n return !normalized.slice(root.length + 1).includes('__tests__');\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,IAAa,6BAAgD;CAC3D;CACA;CACA;CACA;AACF;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"stack-frames.js","names":[],"sources":["../../src/utils/stack-frames.ts"],"sourcesContent":["/**\n * Decorator-runtime stack-frame detection (#1785).\n *\n * ObjectRegistry attributes a `@smrt()` class to its declaring package by\n * walking the registration call stack for the first frame OUTSIDE smrt-core —\n * the module that applied the decorator. Decorator *lowering* breaks that\n * assumption: the compiler inserts a runtime-helper frame between the `@smrt()`\n * decorator and the consumer module that declares the class.\n *\n * Under Vite 8, oxc's legacy-decorator lowering routes the decorator application\n * through `@oxc-project/runtime`'s helper (e.g.\n * `.../@oxc-project/runtime/src/helpers/esm/applyDecoratedDescriptor.js`), so a\n * naive walk stops at that frame and misattributes the class to\n * `@oxc-project/runtime` instead of the consumer package. `tslib` (tsc's\n * `__decorate`), `@swc/helpers`, and `@babel/runtime` insert the identical kind\n * of frame under their respective toolchains.\n *\n * These are pure plumbing packages — a class is applied THROUGH them, never\n * DECLARED in them — so both stack walks (source-file identity in\n * `registry/shared-state.ts` and package attribution in\n * `manifest/manifest-loader.ts`) skip them and continue to the real declaring\n * module. Kept as a leaf module (no smrt imports) so both walkers can share it\n * without risking an import cycle.\n */\n\n/**\n * Runtime-helper packages that decorator lowering routes the decorator\n * application through. Frames from these packages are never the module that\n * declares a decorated class.\n */\nexport const DECORATOR_RUNTIME_PACKAGES: readonly string[] = [\n '@oxc-project/runtime',\n '@swc/helpers',\n '@babel/runtime',\n 'tslib',\n];\n\n/**\n * Plumbing packages matched only as an installed `node_modules/<pkg>/` path.\n * `reflect-metadata` (pulled in by e.g. `@simplewebauthn/server` and tsyringe)\n * defines `Reflect.decorate`, which tsc-style `__decorate` helpers delegate\n * to, so every decorator is then applied FROM its Reflect.js frame. The name\n * is also a plausible directory in an application's own source tree, so the\n * match is anchored to `node_modules` rather than any `/reflect-metadata/`.\n */\nconst INSTALLED_ONLY_RUNTIME_PACKAGES: readonly string[] = ['reflect-metadata'];\n\n/**\n * Whether a file path (normalized to forward slashes) lives inside a\n * decorator-runtime helper package — i.e. a stack frame introduced by decorator\n * lowering rather than the module that declares the decorated class.\n *\n * Matches both the plain installed form (`.../node_modules/tslib/...`) and the\n * pnpm form (`.../node_modules/.pnpm/tslib@2/node_modules/tslib/...`) because\n * the real `/<package>/` segment appears in both. Case-insensitive.\n *\n * @param path - A file path from a stack frame (already normalized to `/`).\n */\nexport function isDecoratorRuntimeFramePath(path: string): boolean {\n const lower = path.toLowerCase();\n if (\n INSTALLED_ONLY_RUNTIME_PACKAGES.some((pkg) =>\n lower.includes(`/node_modules/${pkg}/`),\n )\n ) {\n return true;\n }\n return DECORATOR_RUNTIME_PACKAGES.some(\n (pkg) =>\n lower.includes(`/${pkg}/`) ||\n // Vite serves the helper as a virtual module whose id flattens the\n // scope separator and appends the version, e.g.\n // `<root>/\\0@oxc-project+runtime@0.138.0/helpers/esm/decorate.js`.\n // Missing it attributed every class Vitest loads to the project root's\n // package (#3098).\n lower.includes(`\\0${pkg.replace('/', '+')}@`),\n );\n}\n\n/**\n * Module and test runners that evaluate a module and so appear below it on\n * the stack. Like decorator helpers, they never declare a class.\n */\nconst MODULE_RUNNER_PATH_SEGMENTS: readonly string[] = [\n '/node_modules/@vitest/',\n '/node_modules/vitest/',\n '/node_modules/vite/dist/node/module-runner',\n];\n\n/**\n * Whether a stack-frame path belongs to a module or test runner (Vitest, the\n * Vite module runner) rather than to code that could declare a class (#3098).\n *\n * @param path - A file path from a stack frame (already normalized to `/`).\n */\nexport function isModuleRunnerFramePath(path: string): boolean {\n const lower = path.toLowerCase();\n return MODULE_RUNNER_PATH_SEGMENTS.some((segment) => lower.includes(segment));\n}\n\n/**\n * Whether a scoped package name (as captured from a `node_modules/@scope/name`\n * stack match) is a decorator-runtime helper package.\n *\n * @param name - A scoped package name, e.g. `@oxc-project/runtime`.\n */\nexport function isDecoratorRuntimePackageName(name: string): boolean {\n return DECORATOR_RUNTIME_PACKAGES.includes(name);\n}\n\nlet smrtCoreRoot: string | null | undefined;\n\n/**\n * The directory of the smrt-core package this module belongs to (its\n * `package.json` names `@happyvertical/smrt-core`), or `null` when it cannot\n * be determined (a browser, or core inlined into a consumer's bundle).\n * Resolved once from this module's own location, so it follows the monorepo\n * checkout, a linked checkout, or an installed copy alike.\n */\nfunction getSmrtCoreRoot(): string | null {\n if (smrtCoreRoot !== undefined) return smrtCoreRoot;\n smrtCoreRoot = null;\n try {\n const getBuiltinModule = (\n globalThis as {\n process?: { getBuiltinModule?: (id: string) => unknown };\n }\n ).process?.getBuiltinModule;\n const fs = getBuiltinModule?.('node:fs') as\n | typeof import('node:fs')\n | undefined;\n const path = getBuiltinModule?.('node:path') as\n | typeof import('node:path')\n | undefined;\n const url = getBuiltinModule?.('node:url') as\n | typeof import('node:url')\n | undefined;\n if (!fs || !path || !url || !import.meta.url.startsWith('file:')) {\n return smrtCoreRoot;\n }\n let dir = path.dirname(url.fileURLToPath(import.meta.url));\n for (let depth = 0; depth < 16; depth++) {\n const manifest = path.join(dir, 'package.json');\n if (\n fs.existsSync(manifest) &&\n (JSON.parse(fs.readFileSync(manifest, 'utf8')) as { name?: string })\n .name === '@happyvertical/smrt-core'\n ) {\n smrtCoreRoot = dir.replace(/\\\\/g, '/');\n break;\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n } catch {\n smrtCoreRoot = null;\n }\n return smrtCoreRoot;\n}\n\n/**\n * Whether a stack-frame path is smrt-core's own code, which applies decorators\n * but never declares an application class. Both stack walks (source-file\n * identity in `registry/shared-state.ts` and package attribution in\n * `manifest/manifest-loader.ts`) skip these frames.\n *\n * Matched by where core actually lives, never by a generic substring: a\n * consumer's own `packages/core` workspace package (ergot's `@ergot/core`) or\n * a file named like a core module declares classes too. That is this core's\n * package directory (outside its `__tests__` fixtures) and any installed\n * `node_modules/@happyvertical/smrt-core` copy. With source maps enabled —\n * `tsx`, `node --enable-source-maps`, Vite SSR — an installed core's frames\n * report mapped `.../node_modules/@happyvertical/smrt-core/src/...` paths;\n * missing them attributed every class to `@happyvertical/smrt-core` (#3109,\n * #3110).\n *\n * @param path - A file path from a stack frame (already normalized to `/`).\n */\nexport function isSmrtCoreFramePath(path: string): boolean {\n const normalized = path.replace(/^file:\\/\\//, '').replace(/\\\\/g, '/');\n if (\n normalized.toLowerCase().includes('/node_modules/@happyvertical/smrt-core/')\n ) {\n return true;\n }\n const root = getSmrtCoreRoot();\n if (!root || !normalized.startsWith(`${root}/`)) return false;\n return !normalized.slice(root.length + 1).includes('__tests__');\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,IAAa,6BAAgD;CAC3D;CACA;CACA;CACA;AACF;;;;;;;;;AAUA,IAAM,kCAAqD,CAAC,kBAAkB;;;;;;;;;;;;AAa9E,SAAgB,4BAA4B,MAAuB;CACjE,MAAM,QAAQ,KAAK,YAAY;CAC/B,IACE,gCAAgC,MAAM,QACpC,MAAM,SAAS,iBAAiB,IAAI,EAAE,CACxC,GAEA,OAAO;CAET,OAAO,2BAA2B,MAC/B,QACC,MAAM,SAAS,IAAI,IAAI,EAAE,KAMzB,MAAM,SAAS,KAAK,IAAI,QAAQ,KAAK,GAAG,EAAE,EAAE,CAChD;AACF;;;;;AAMA,IAAM,8BAAiD;CACrD;CACA;CACA;AACF;;;;;;;AAQA,SAAgB,wBAAwB,MAAuB;CAC7D,MAAM,QAAQ,KAAK,YAAY;CAC/B,OAAO,4BAA4B,MAAM,YAAY,MAAM,SAAS,OAAO,CAAC;AAC9E;;;;;;;AAQA,SAAgB,8BAA8B,MAAuB;CACnE,OAAO,2BAA2B,SAAS,IAAI;AACjD;AAEA,IAAI;;;;;;;;AASJ,SAAS,kBAAiC;CACxC,IAAI,iBAAiB,KAAA,GAAW,OAAO;CACvC,eAAe;CACf,IAAI;EACF,MAAM,mBACJ,WAGA,SAAS;EACX,MAAM,KAAK,mBAAmB,SAAS;EAGvC,MAAM,OAAO,mBAAmB,WAAW;EAG3C,MAAM,MAAM,mBAAmB,UAAU;EAGzC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,KAAK,IAAI,WAAW,OAAO,GAC7D,OAAO;EAET,IAAI,MAAM,KAAK,QAAQ,IAAI,cAAc,OAAO,KAAK,GAAG,CAAC;EACzD,KAAK,IAAI,QAAQ,GAAG,QAAQ,IAAI,SAAS;GACvC,MAAM,WAAW,KAAK,KAAK,KAAK,cAAc;GAC9C,IACE,GAAG,WAAW,QAAQ,KACrB,KAAK,MAAM,GAAG,aAAa,UAAU,MAAM,CAAC,CAAC,CAC3C,SAAS,4BACZ;IACA,eAAe,IAAI,QAAQ,OAAO,GAAG;IACrC;GACF;GACA,MAAM,SAAS,KAAK,QAAQ,GAAG;GAC/B,IAAI,WAAW,KAAK;GACpB,MAAM;EACR;CACF,QAAQ;EACN,eAAe;CACjB;CACA,OAAO;AACT;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,oBAAoB,MAAuB;CACzD,MAAM,aAAa,KAAK,QAAQ,cAAc,EAAE,CAAC,CAAC,QAAQ,OAAO,GAAG;CACpE,IACE,WAAW,YAAY,CAAC,CAAC,SAAS,yCAAyC,GAE3E,OAAO;CAET,MAAM,OAAO,gBAAgB;CAC7B,IAAI,CAAC,QAAQ,CAAC,WAAW,WAAW,GAAG,KAAK,EAAE,GAAG,OAAO;CACxD,OAAO,CAAC,WAAW,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,WAAW;AAChE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sveltekit-generator.d.ts","sourceRoot":"","sources":["../../src/vite-plugin/sveltekit-generator.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"sveltekit-generator.d.ts","sourceRoot":"","sources":["../../src/vite-plugin/sveltekit-generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -1008,11 +1008,15 @@ function readKnowledgeRouteArtifact(projectRoot) {
|
|
|
1008
1008
|
async function generateRegistrationFile(projectRoot, manifest, options) {
|
|
1009
1009
|
const configDir = join(projectRoot, options.configPath || "src/lib/server");
|
|
1010
1010
|
const registrationFilePath = join(configDir, "smrt-register.ts");
|
|
1011
|
+
const externalRuntimeDependencies = (manifest.smrtDependencies || []).filter((dependency) => dependency !== "@happyvertical/smrt-core");
|
|
1012
|
+
const consumerRegistrationPaths = options.consumerRegistrationPaths ?? (externalRuntimeDependencies.length > 0 ? [join(projectRoot, ".smrt", "register.js")] : []);
|
|
1013
|
+
const consumerOwnsExternalRegistration = consumerRegistrationPaths.length > 0;
|
|
1011
1014
|
const localObjects = [];
|
|
1012
1015
|
const packageObjects = /* @__PURE__ */ new Map();
|
|
1013
1016
|
const registrationBindings = buildRegistrationBindings(manifest);
|
|
1014
1017
|
for (const [className, objectDef] of Object.entries(manifest.objects)) if (isLocalObject(projectRoot, objectDef)) localObjects.push([className, objectDef]);
|
|
1015
1018
|
else if (objectDef.packageName) {
|
|
1019
|
+
if (consumerOwnsExternalRegistration) continue;
|
|
1016
1020
|
const externalImportPath = objectDef.importPath || objectDef.packageName;
|
|
1017
1021
|
const packageEntry = packageObjects.get(externalImportPath) || {
|
|
1018
1022
|
objects: [],
|
|
@@ -1057,11 +1061,12 @@ async function generateRegistrationFile(projectRoot, manifest, options) {
|
|
|
1057
1061
|
}
|
|
1058
1062
|
return imports;
|
|
1059
1063
|
}).join("\n"), localImports].filter(Boolean).join("\n");
|
|
1060
|
-
const externalRuntimeDependencies = (manifest.smrtDependencies || []).filter((dependency) => dependency !== "@happyvertical/smrt-core");
|
|
1061
|
-
const consumerRegistrationPaths = options.consumerRegistrationPaths ?? (externalRuntimeDependencies.length > 0 ? [join(projectRoot, ".smrt", "register.js")] : []);
|
|
1062
1064
|
let consumerRegistrationImport = "";
|
|
1063
1065
|
if (consumerRegistrationPaths.length > 0) consumerRegistrationImport = [...new Set(consumerRegistrationPaths)].sort().map((registerPath) => {
|
|
1064
|
-
const
|
|
1066
|
+
const lexicalRegistrationPath = relative(projectRoot, registerPath);
|
|
1067
|
+
const registerPathFromProjectRoot = lexicalRegistrationPath === ".." || lexicalRegistrationPath.startsWith(`..${sep}`) || isAbsolute(lexicalRegistrationPath) ? relative(canonicalSvelteKitPath(projectRoot), canonicalSvelteKitPath(registerPath)) : lexicalRegistrationPath;
|
|
1068
|
+
const registrationTarget = registerPathFromProjectRoot === ".." || registerPathFromProjectRoot.startsWith(`..${sep}`) || isAbsolute(registerPathFromProjectRoot) ? registerPath : resolve(projectRoot, registerPathFromProjectRoot);
|
|
1069
|
+
const consumerRegistrationPath = relative(configDir, registrationTarget).replace(/\\/g, "/");
|
|
1065
1070
|
return `import '${consumerRegistrationPath.startsWith(".") ? consumerRegistrationPath : `./${consumerRegistrationPath}`}';`;
|
|
1066
1071
|
}).join("\n");
|
|
1067
1072
|
/**
|
|
@@ -1105,7 +1110,9 @@ async function generateRegistrationFile(projectRoot, manifest, options) {
|
|
|
1105
1110
|
}));
|
|
1106
1111
|
const registrationManifests = Object.fromEntries(Object.entries(manifest.objects).flatMap(([className, objectDef]) => {
|
|
1107
1112
|
if (isCollectionManifestClass(manifest, objectDef)) return [];
|
|
1108
|
-
const
|
|
1113
|
+
const localObject = isLocalObject(projectRoot, objectDef);
|
|
1114
|
+
if (!localObject && consumerOwnsExternalRegistration) return [];
|
|
1115
|
+
const packageName = getRegistrationPackageName(manifest, objectDef, localObject);
|
|
1109
1116
|
if (!packageName) return [];
|
|
1110
1117
|
return [[className, {
|
|
1111
1118
|
...manifest,
|
|
@@ -1118,7 +1125,9 @@ async function generateRegistrationFile(projectRoot, manifest, options) {
|
|
|
1118
1125
|
if (isCollectionManifestClass(manifest, objectDef)) return null;
|
|
1119
1126
|
const simpleClassName = extractSimpleClassName(className);
|
|
1120
1127
|
const bindingName = registrationBindings.get(className) || simpleClassName;
|
|
1121
|
-
const
|
|
1128
|
+
const localObject = isLocalObject(projectRoot, objectDef);
|
|
1129
|
+
if (!localObject && consumerOwnsExternalRegistration) return null;
|
|
1130
|
+
const packageName = getRegistrationPackageName(manifest, objectDef, localObject);
|
|
1122
1131
|
if (!packageName) return null;
|
|
1123
1132
|
const packageNameLiteral = toSingleQuotedStringLiteral(packageName);
|
|
1124
1133
|
const manifestKeyLiteral = toSingleQuotedStringLiteral(className);
|
|
@@ -1133,7 +1142,7 @@ async function generateRegistrationFile(projectRoot, manifest, options) {
|
|
|
1133
1142
|
`});`
|
|
1134
1143
|
].join("\n");
|
|
1135
1144
|
}).filter((registration) => registration !== null).join("\n");
|
|
1136
|
-
const collectionRegistrations = Object.entries(manifest.objects).filter(([, objectDef]) => isCollectionActionResource(manifest, objectDef)).map(([className, objectDef]) => {
|
|
1145
|
+
const collectionRegistrations = Object.entries(manifest.objects).filter(([, objectDef]) => isCollectionActionResource(manifest, objectDef) && (!consumerOwnsExternalRegistration || isLocalObject(projectRoot, objectDef))).map(([className, objectDef]) => {
|
|
1137
1146
|
const packageName = getRegistrationPackageName(manifest, objectDef, isLocalObject(projectRoot, objectDef));
|
|
1138
1147
|
const qualifiedName = objectDef.qualifiedName || (packageName ? `${packageName}:${extractSimpleClassName(className)}` : className);
|
|
1139
1148
|
const bindingName = registrationBindings.get(className) || extractSimpleClassName(className);
|