@open-mercato/cli 0.6.8-develop.6930.1.1e5976efc3 → 0.6.8-develop.6940.1.177ea30c6e
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/.turbo/turbo-build.log +65 -1
- package/build.mjs +57 -7
- package/dist/agentic/guides/backend-ui.md +7 -1
- package/dist/agentic/guides/module-facts.json +565 -224
- package/dist/agentic/guides/module-facts.v2.json +122933 -0
- package/dist/agentic/guides/modules/ai_assistant.md +2 -2
- package/dist/agentic/guides/modules/api_docs.md +2 -2
- package/dist/agentic/guides/modules/api_keys.md +2 -2
- package/dist/agentic/guides/modules/attachments.md +2 -2
- package/dist/agentic/guides/modules/audit_logs.md +2 -2
- package/dist/agentic/guides/modules/auth.md +2 -2
- package/dist/agentic/guides/modules/business_rules.md +2 -2
- package/dist/agentic/guides/modules/catalog.md +11 -4
- package/dist/agentic/guides/modules/channel_gmail.md +2 -2
- package/dist/agentic/guides/modules/channel_imap.md +2 -2
- package/dist/agentic/guides/modules/checkout.md +8 -2
- package/dist/agentic/guides/modules/communication_channels.md +6 -6
- package/dist/agentic/guides/modules/configs.md +2 -2
- package/dist/agentic/guides/modules/content.md +2 -2
- package/dist/agentic/guides/modules/currencies.md +2 -2
- package/dist/agentic/guides/modules/customer_accounts.md +2 -2
- package/dist/agentic/guides/modules/customers.md +8 -8
- package/dist/agentic/guides/modules/dashboards.md +12 -12
- package/dist/agentic/guides/modules/data_sync.md +4 -4
- package/dist/agentic/guides/modules/design_system.md +2 -2
- package/dist/agentic/guides/modules/dictionaries.md +2 -2
- package/dist/agentic/guides/modules/directory.md +2 -2
- package/dist/agentic/guides/modules/entities.md +3 -3
- package/dist/agentic/guides/modules/eudr.md +3 -3
- package/dist/agentic/guides/modules/events.md +2 -2
- package/dist/agentic/guides/modules/feature_toggles.md +2 -2
- package/dist/agentic/guides/modules/gateway_stripe.md +2 -2
- package/dist/agentic/guides/modules/generators.md +2 -2
- package/dist/agentic/guides/modules/inbox_ops.md +2 -2
- package/dist/agentic/guides/modules/integrations.md +5 -2
- package/dist/agentic/guides/modules/messages.md +2 -2
- package/dist/agentic/guides/modules/notifications.md +2 -2
- package/dist/agentic/guides/modules/onboarding.md +2 -2
- package/dist/agentic/guides/modules/payment_gateways.md +7 -3
- package/dist/agentic/guides/modules/perspectives.md +2 -2
- package/dist/agentic/guides/modules/planner.md +2 -2
- package/dist/agentic/guides/modules/portal.md +2 -2
- package/dist/agentic/guides/modules/progress.md +2 -2
- package/dist/agentic/guides/modules/query_index.md +2 -2
- package/dist/agentic/guides/modules/record_locks.md +3 -3
- package/dist/agentic/guides/modules/resources.md +2 -2
- package/dist/agentic/guides/modules/sales.md +13 -4
- package/dist/agentic/guides/modules/scheduler.md +2 -2
- package/dist/agentic/guides/modules/search.md +4 -4
- package/dist/agentic/guides/modules/security.md +3 -3
- package/dist/agentic/guides/modules/shipping_carriers.md +2 -2
- package/dist/agentic/guides/modules/sso.md +2 -2
- package/dist/agentic/guides/modules/staff.md +7 -4
- package/dist/agentic/guides/modules/storage_s3.md +2 -2
- package/dist/agentic/guides/modules/sync_akeneo.md +4 -4
- package/dist/agentic/guides/modules/sync_excel.md +2 -2
- package/dist/agentic/guides/modules/system_status_overlays.md +2 -2
- package/dist/agentic/guides/modules/translations.md +2 -2
- package/dist/agentic/guides/modules/webhooks.md +2 -2
- package/dist/agentic/guides/modules/wms.md +31 -4
- package/dist/agentic/guides/modules/workflows.md +2 -2
- package/dist/agentic/guides/reference-module-facts.json +8335 -0
- package/dist/agentic/guides/reference-modules/example.md +489 -0
- package/dist/agentic/guides/spec-delivery.md +34 -0
- package/dist/agentic/guides/testing-debugging.md +2 -1
- package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +3 -2
- package/dist/agentic/guides/upstream/manifest.json +2 -2
- package/dist/agentic/shared/AGENTS.md.template +10 -14
- package/dist/agentic/shared/ai/harness/README.md +4 -4
- package/dist/agentic/shared/ai/harness/RELEASE.md +4 -4
- package/dist/agentic/shared/ai/harness/cases.json +11350 -5088
- package/dist/agentic/shared/ai/harness/cases.schema.json +70 -7
- package/dist/agentic/shared/ai/harness/design-system-inventory.json +6697 -0
- package/dist/agentic/shared/ai/harness/fixtures/index.json +3 -1
- package/dist/agentic/shared/ai/harness/fixtures/seeds.json +4 -0
- package/dist/agentic/shared/ai/harness/knowledge-change.schema.json +228 -0
- package/dist/agentic/shared/ai/harness/release-matrix.json +9 -4
- package/dist/agentic/shared/ai/harness/result.schema.json +60 -0
- package/dist/agentic/shared/ai/harness/routing-response.schema.json +10 -0
- package/dist/agentic/shared/ai/harness/source-link-inventory.json +3231 -0
- package/dist/agentic/shared/ai/harness/validators.json +8 -4
- package/dist/agentic/shared/ai/harness/writable-ast-oracles.mjs +5 -2
- package/dist/agentic/shared/ai/harness/writable-behavior-oracles.mjs +3 -0
- package/dist/agentic/shared/ai/harness/writable-spec-oracles.mjs +879 -0
- package/dist/agentic/shared/ai/skills/om-auto-upgrade-0.6.7-to-0.7.0/SKILL.md +102 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/SKILL.md +1 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/crud-surfaces.md +15 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/frontend-and-design-system.md +6 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/page-and-navigation.md +4 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/SKILL.md +1 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/integrity-and-concurrency.md +2 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/migration-workflow.md +2 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/schema-design.md +3 -1
- package/dist/agentic/shared/ai/skills/om-eject-and-customize/SKILL.md +1 -0
- package/dist/agentic/shared/ai/skills/om-eject-and-customize/references/decision-and-procedure.md +2 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/SKILL.md +2 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-template.md +3 -3
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-workflow.md +1 -1
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/knowledge-change.md +133 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/SKILL.md +1 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/provider-families.md +11 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/judge-workflow.md +1 -1
- package/dist/agentic/shared/ai/skills/om-module-scaffold/SKILL.md +2 -2
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/api-and-domain.md +14 -1
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/data-and-migrations.md +2 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/module-surfaces.md +19 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/scripts/prepare-share-bundle.mjs +1 -4
- package/dist/agentic/shared/ai/skills/om-system-extension/SKILL.md +1 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-branches.md +24 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/unified-overrides.md +3 -1
- package/dist/agentic/shared/ai/skills/om-troubleshooter/SKILL.md +1 -0
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnosis-map.md +13 -0
- package/dist/agentic/shared/ai/skills/tiers.json +2 -1
- package/dist/agentic/shared/ai/specs/2026-08-06-reference-module-activation.md +248 -0
- package/dist/agentic/shared/scripts/agent-harness-tool-server.mjs +39 -2
- package/dist/agentic/shared/scripts/design-source-contract.mjs +108 -0
- package/dist/agentic/shared/scripts/evaluate-agent-harness.mjs +935 -28
- package/dist/agentic/shared/scripts/framework-context.mjs +4 -1
- package/dist/agentic/shared/scripts/run-agent-harness-release.mjs +1 -1
- package/dist/agentic/shared/scripts/validate-knowledge-change.mjs +1511 -0
- package/dist/lib/__integration__/TC-INT-008.spec.js +1 -0
- package/dist/lib/__integration__/TC-INT-008.spec.js.map +2 -2
- package/dist/lib/agentic-setup.js +9 -1
- package/dist/lib/agentic-setup.js.map +2 -2
- package/dist/lib/generators/module-extension-facts.js +93 -26
- package/dist/lib/generators/module-extension-facts.js.map +3 -3
- package/dist/lib/generators/module-facts-discovery.js +23 -0
- package/dist/lib/generators/module-facts-discovery.js.map +2 -2
- package/dist/lib/generators/module-facts.js +969 -41
- package/dist/lib/generators/module-facts.js.map +2 -2
- package/dist/lib/generators/module-override-targets.js +35 -11
- package/dist/lib/generators/module-override-targets.js.map +2 -2
- package/dist/lib/generators/module-registry.js +105 -2
- package/dist/lib/generators/module-registry.js.map +2 -2
- package/dist/lib/resolver.js +5 -0
- package/dist/lib/resolver.js.map +2 -2
- package/dist/lib/testing/integration-discovery.js +34 -0
- package/dist/lib/testing/integration-discovery.js.map +2 -2
- package/dist/lib/testing/integration.js +10 -2
- package/dist/lib/testing/integration.js.map +2 -2
- package/dist/mercato.js +4 -4
- package/dist/mercato.js.map +2 -2
- package/package.json +6 -6
- package/src/lib/__integration__/TC-INT-008.spec.ts +4 -2
- package/src/lib/__tests__/resolve-environment.test.ts +18 -0
- package/src/lib/agentic-setup.ts +10 -2
- package/src/lib/generators/__tests__/helpers/override-target-diagnostic-fixtures.ts +150 -0
- package/src/lib/generators/__tests__/module-extension-facts.test.ts +290 -0
- package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +72 -7
- package/src/lib/generators/__tests__/module-facts.computed-di-token.test.ts +88 -0
- package/src/lib/generators/__tests__/module-facts.example-fact-coverage.test.ts +502 -0
- package/src/lib/generators/__tests__/module-facts.local-reference.test.ts +414 -0
- package/src/lib/generators/__tests__/module-facts.search-entity-id.test.ts +151 -0
- package/src/lib/generators/__tests__/module-facts.source-links.test.ts +42 -1
- package/src/lib/generators/__tests__/module-override-targets.diagnostic-wiring.test.ts +92 -0
- package/src/lib/generators/__tests__/module-override-targets.test.ts +24 -0
- package/src/lib/generators/__tests__/module-override-targets.unknown-mode.test.ts +117 -0
- package/src/lib/generators/__tests__/module-subset.test.ts +60 -0
- package/src/lib/generators/module-extension-facts.ts +181 -25
- package/src/lib/generators/module-facts-discovery.ts +46 -0
- package/src/lib/generators/module-facts.ts +1330 -86
- package/src/lib/generators/module-override-targets.ts +70 -26
- package/src/lib/generators/module-registry.ts +135 -2
- package/src/lib/resolver.ts +6 -1
- package/src/lib/testing/__tests__/integration-discovery.test.ts +35 -0
- package/src/lib/testing/integration-discovery.ts +50 -0
- package/src/lib/testing/integration.ts +10 -2
- package/src/mercato.ts +3 -3
|
@@ -60,6 +60,8 @@ export interface ExtractModuleExtensionFactsOptions {
|
|
|
60
60
|
notifications?: readonly string[]
|
|
61
61
|
aiTools?: ReadonlyArray<{ name: string; sourcePath: string }>
|
|
62
62
|
aiAgents?: ReadonlyArray<{ id: string; sourcePath: string }>
|
|
63
|
+
/** Generated-facts compatibility projection. Omitted means the corrected v2 contract. */
|
|
64
|
+
factsContractVersion?: 1 | 2
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
export interface CorrelateExtensionFactsOptions {
|
|
@@ -69,12 +71,15 @@ export interface CorrelateExtensionFactsOptions {
|
|
|
69
71
|
apiRoutes: ReadonlySet<string>
|
|
70
72
|
commandIds?: ReadonlySet<string>
|
|
71
73
|
contributingModuleId?: string
|
|
74
|
+
/** Generated-facts compatibility projection. Omitted means the corrected v2 contract. */
|
|
75
|
+
factsContractVersion?: 1 | 2
|
|
72
76
|
}
|
|
73
77
|
|
|
74
78
|
type StaticContext = {
|
|
75
79
|
initializers: Map<string, ts.Expression | ts.FunctionDeclaration>
|
|
76
80
|
sourceFile: ts.SourceFile
|
|
77
81
|
resolving: Set<string>
|
|
82
|
+
factsContractVersion: 1 | 2
|
|
78
83
|
}
|
|
79
84
|
|
|
80
85
|
type ApiExtensionHostIds = {
|
|
@@ -229,7 +234,7 @@ function propertyName(node: ts.PropertyName): string | null {
|
|
|
229
234
|
return null
|
|
230
235
|
}
|
|
231
236
|
|
|
232
|
-
function buildStaticContext(file: ts.SourceFile): StaticContext {
|
|
237
|
+
function buildStaticContext(file: ts.SourceFile, factsContractVersion: 1 | 2 = 2): StaticContext {
|
|
233
238
|
const initializers = new Map<string, ts.Expression | ts.FunctionDeclaration>()
|
|
234
239
|
const visit = (node: ts.Node): void => {
|
|
235
240
|
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {
|
|
@@ -241,7 +246,7 @@ function buildStaticContext(file: ts.SourceFile): StaticContext {
|
|
|
241
246
|
node.forEachChild(visit)
|
|
242
247
|
}
|
|
243
248
|
file.forEachChild(visit)
|
|
244
|
-
return { initializers, sourceFile: file, resolving: new Set() }
|
|
249
|
+
return { initializers, sourceFile: file, resolving: new Set(), factsContractVersion }
|
|
245
250
|
}
|
|
246
251
|
|
|
247
252
|
function entityRegistryValue(expression: ts.PropertyAccessExpression): string | null {
|
|
@@ -266,6 +271,26 @@ function staticTemplate(expression: ts.TemplateExpression, context: StaticContex
|
|
|
266
271
|
return value
|
|
267
272
|
}
|
|
268
273
|
|
|
274
|
+
/**
|
|
275
|
+
* A function-valued property carries no statically readable value, but its
|
|
276
|
+
* PRESENCE is the discriminant several conventions are keyed on (a
|
|
277
|
+
* `ComponentOverride` is a wrapper/props-transform/replacement depending on
|
|
278
|
+
* which callable it declares; an enricher declares `enrichOne`/`enrichMany`).
|
|
279
|
+
* Method shorthand (`enrichOne() {}`) is already recorded as `true`, so an
|
|
280
|
+
* arrow/function-expression property — or an identifier bound to one — records
|
|
281
|
+
* the same marker instead of vanishing from the object.
|
|
282
|
+
*/
|
|
283
|
+
function isFunctionLikeInitializer(expression: ts.Expression, context: StaticContext): boolean {
|
|
284
|
+
const current = unwrap(expression)
|
|
285
|
+
if (ts.isArrowFunction(current) || ts.isFunctionExpression(current)) return true
|
|
286
|
+
if (!ts.isIdentifier(current)) return false
|
|
287
|
+
const initializer = context.initializers.get(current.text)
|
|
288
|
+
if (!initializer) return false
|
|
289
|
+
if (ts.isFunctionDeclaration(initializer)) return true
|
|
290
|
+
const resolved = unwrap(initializer)
|
|
291
|
+
return ts.isArrowFunction(resolved) || ts.isFunctionExpression(resolved)
|
|
292
|
+
}
|
|
293
|
+
|
|
269
294
|
function staticObject(expression: ts.ObjectLiteralExpression, context: StaticContext): StaticObject {
|
|
270
295
|
const result: StaticObject = {}
|
|
271
296
|
for (const property of expression.properties) {
|
|
@@ -274,6 +299,7 @@ function staticObject(expression: ts.ObjectLiteralExpression, context: StaticCon
|
|
|
274
299
|
if (!name) continue
|
|
275
300
|
const value = staticValue(property.initializer, context)
|
|
276
301
|
if (value !== undefined) result[name] = value
|
|
302
|
+
else if (context.factsContractVersion === 2 && isFunctionLikeInitializer(property.initializer, context)) result[name] = true
|
|
277
303
|
continue
|
|
278
304
|
}
|
|
279
305
|
if (ts.isShorthandPropertyAssignment(property)) {
|
|
@@ -326,6 +352,12 @@ function staticValue(expression: ts.Expression, context: StaticContext): StaticV
|
|
|
326
352
|
return value
|
|
327
353
|
}
|
|
328
354
|
if (ts.isCallExpression(current)) {
|
|
355
|
+
const handle = componentReplacementHandle(current, context)
|
|
356
|
+
if (handle) {
|
|
357
|
+
if (context.factsContractVersion === 2) return handle.resolved
|
|
358
|
+
const firstArgument = current.arguments[0]
|
|
359
|
+
return firstArgument ? staticValue(firstArgument, context) : undefined
|
|
360
|
+
}
|
|
329
361
|
if (ts.isIdentifier(current.expression)) {
|
|
330
362
|
const callable = context.initializers.get(current.expression.text)
|
|
331
363
|
if (callable && (ts.isArrowFunction(callable) || ts.isFunctionExpression(callable) || ts.isFunctionDeclaration(callable))) {
|
|
@@ -339,14 +371,19 @@ function staticValue(expression: ts.Expression, context: StaticContext): StaticV
|
|
|
339
371
|
initializers,
|
|
340
372
|
sourceFile: context.sourceFile,
|
|
341
373
|
resolving: new Set(context.resolving),
|
|
374
|
+
factsContractVersion: context.factsContractVersion,
|
|
342
375
|
}
|
|
343
376
|
if (callable.body && ts.isExpression(callable.body)) return staticValue(callable.body, childContext)
|
|
344
377
|
const returnStatement = callable.body?.statements.find(ts.isReturnStatement)
|
|
345
378
|
if (returnStatement?.expression) return staticValue(returnStatement.expression, childContext)
|
|
346
379
|
}
|
|
347
380
|
}
|
|
381
|
+
// Factory-style `defineThing({ … })` calls forward their configuration object,
|
|
382
|
+
// but a member call (`Handles.section('a', 'b')`) computes a value from its
|
|
383
|
+
// arguments — forwarding the first one there invents a wrong id, so it stays
|
|
384
|
+
// unresolved unless a formula above knows the builder.
|
|
348
385
|
const firstArgument = current.arguments[0]
|
|
349
|
-
if (firstArgument) {
|
|
386
|
+
if (firstArgument && (context.factsContractVersion === 1 || ts.isIdentifier(current.expression))) {
|
|
350
387
|
const value = staticValue(firstArgument, context)
|
|
351
388
|
if (!isStaticObject(value) || !ts.isIdentifier(current.expression)) return value
|
|
352
389
|
if (current.expression.text === 'dataTableExtensionHost') return { family: 'data-table', ...value }
|
|
@@ -363,6 +400,33 @@ function staticValue(expression: ts.Expression, context: StaticContext): StaticV
|
|
|
363
400
|
return undefined
|
|
364
401
|
}
|
|
365
402
|
|
|
403
|
+
/**
|
|
404
|
+
* `ComponentReplacementHandles` (packages/shared/src/modules/widgets/component-registry.ts)
|
|
405
|
+
* is the framework-owned builder every `widgets/components.ts` uses to name a
|
|
406
|
+
* replacement handle. Without these formulas the generic call fallback would read
|
|
407
|
+
* `ComponentReplacementHandles.section('ui.detail', 'NotesSection')` as its first
|
|
408
|
+
* argument and publish `ui.detail` as the handle — a target id that exists nowhere.
|
|
409
|
+
*/
|
|
410
|
+
const COMPONENT_REPLACEMENT_HANDLE_BUILDERS: Record<string, (args: Array<string | undefined>) => string | undefined> = {
|
|
411
|
+
page: ([routePath]) => routePath ? `page:${routePath}` : undefined,
|
|
412
|
+
dataTable: ([tableId]) => tableId ? `data-table:${tableId}` : undefined,
|
|
413
|
+
crudForm: ([entityId]) => entityId ? `crud-form:${entityId}` : undefined,
|
|
414
|
+
section: ([scope, sectionId]) => scope && sectionId ? `section:${scope}.${sectionId}` : undefined,
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function componentReplacementHandle(
|
|
418
|
+
expression: ts.CallExpression,
|
|
419
|
+
context: StaticContext,
|
|
420
|
+
): { resolved: string | undefined } | null {
|
|
421
|
+
if (!ts.isPropertyAccessExpression(expression.expression)) return null
|
|
422
|
+
const callee = expression.expression
|
|
423
|
+
if (!ts.isIdentifier(callee.expression) || callee.expression.text !== 'ComponentReplacementHandles') return null
|
|
424
|
+
const builder = COMPONENT_REPLACEMENT_HANDLE_BUILDERS[callee.name.text]
|
|
425
|
+
if (!builder) return { resolved: undefined }
|
|
426
|
+
const args = expression.arguments.map((argument) => stringValue(staticValue(argument, context)))
|
|
427
|
+
return { resolved: builder(args) }
|
|
428
|
+
}
|
|
429
|
+
|
|
366
430
|
function isStaticObject(value: StaticValue | undefined): value is StaticObject {
|
|
367
431
|
return !!value && typeof value === 'object' && !Array.isArray(value)
|
|
368
432
|
}
|
|
@@ -399,20 +463,32 @@ function conventionPath(moduleRoot: string, relativePath: string): string | null
|
|
|
399
463
|
return null
|
|
400
464
|
}
|
|
401
465
|
|
|
402
|
-
|
|
466
|
+
function readConventionObjectArrayForContract(
|
|
467
|
+
filePath: string,
|
|
468
|
+
exportName: string,
|
|
469
|
+
factsContractVersion: 1 | 2,
|
|
470
|
+
): StaticObject[] {
|
|
403
471
|
const file = sourceFile(filePath)
|
|
404
472
|
if (!file) return []
|
|
405
|
-
const context = buildStaticContext(file)
|
|
473
|
+
const context = buildStaticContext(file, factsContractVersion)
|
|
406
474
|
const initializer = context.initializers.get(exportName)
|
|
407
475
|
if (!initializer || ts.isFunctionDeclaration(initializer)) return []
|
|
408
476
|
const value = staticValue(initializer, context)
|
|
409
477
|
return Array.isArray(value) ? value.filter(isStaticObject) : []
|
|
410
478
|
}
|
|
411
479
|
|
|
412
|
-
function
|
|
480
|
+
export function readConventionObjectArray(filePath: string, exportName: string): StaticObject[] {
|
|
481
|
+
return readConventionObjectArrayForContract(filePath, exportName, 2)
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function readRootObject(
|
|
485
|
+
filePath: string,
|
|
486
|
+
variableName: string,
|
|
487
|
+
factsContractVersion: 1 | 2 = 2,
|
|
488
|
+
): StaticObject | null {
|
|
413
489
|
const file = sourceFile(filePath)
|
|
414
490
|
if (!file) return null
|
|
415
|
-
const context = buildStaticContext(file)
|
|
491
|
+
const context = buildStaticContext(file, factsContractVersion)
|
|
416
492
|
const initializer = context.initializers.get(variableName)
|
|
417
493
|
if (!initializer || ts.isFunctionDeclaration(initializer)) return null
|
|
418
494
|
const value = staticValue(initializer, context)
|
|
@@ -1019,20 +1095,36 @@ function contributionBase(
|
|
|
1019
1095
|
}
|
|
1020
1096
|
}
|
|
1021
1097
|
|
|
1098
|
+
/**
|
|
1099
|
+
* `ModuleInjectionTable` maps a spot to `ModuleInjectionSlot | ModuleInjectionSlot[]`,
|
|
1100
|
+
* and a slot is either a bare widget-id string or a placement object. The runtime
|
|
1101
|
+
* loader normalizes all three shapes (`injection-loader.ts` → `loadInjectionTable`);
|
|
1102
|
+
* reading only the array form here silently dropped every string and single-object
|
|
1103
|
+
* slot from the generated contribution facts.
|
|
1104
|
+
*/
|
|
1105
|
+
function injectionTableSlots(value: StaticValue | undefined): StaticValue[] {
|
|
1106
|
+
if (value === undefined) return []
|
|
1107
|
+
return Array.isArray(value) ? value : [value]
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1022
1110
|
function extractInjectionTable(options: ExtractModuleExtensionFactsOptions): ModuleExtensionContributionFact[] {
|
|
1023
1111
|
const filePath = conventionPath(options.moduleRoot, 'widgets/injection-table.ts')
|
|
1024
1112
|
if (!filePath) return []
|
|
1025
|
-
const table = readRootObject(filePath, 'injectionTable')
|
|
1113
|
+
const table = readRootObject(filePath, 'injectionTable', options.factsContractVersion ?? 2)
|
|
1026
1114
|
if (!table) return []
|
|
1027
1115
|
const sourcePath = portablePath(options.moduleRoot, options.sourceRoot, filePath)
|
|
1028
1116
|
const facts: ModuleExtensionContributionFact[] = []
|
|
1029
1117
|
for (const targetId of Object.keys(table).sort((left, right) => left.localeCompare(right))) {
|
|
1030
|
-
const entries =
|
|
1031
|
-
|
|
1118
|
+
const entries = options.factsContractVersion === 1
|
|
1119
|
+
? (Array.isArray(table[targetId]) ? table[targetId] as StaticValue[] : [])
|
|
1120
|
+
: injectionTableSlots(table[targetId])
|
|
1032
1121
|
for (const entry of entries) {
|
|
1033
|
-
if (!isStaticObject(entry)) continue
|
|
1034
|
-
const
|
|
1122
|
+
if (options.factsContractVersion === 1 && !isStaticObject(entry)) continue
|
|
1123
|
+
const slot = isStaticObject(entry) ? entry : null
|
|
1124
|
+
const widgetId = typeof entry === 'string' ? entry : slot ? stringValue(slot.widgetId) : undefined
|
|
1035
1125
|
if (!widgetId) continue
|
|
1126
|
+
const features = slot ? strings(slot.features) : []
|
|
1127
|
+
const priority = slot ? numberValue(slot.priority) : undefined
|
|
1036
1128
|
const payload = targetId.endsWith(':columns') ? 'column'
|
|
1037
1129
|
: targetId.endsWith(':row-actions') ? 'row-action'
|
|
1038
1130
|
: targetId.endsWith(':bulk-actions') ? 'bulk-action'
|
|
@@ -1043,8 +1135,8 @@ function extractInjectionTable(options: ExtractModuleExtensionFactsOptions): Mod
|
|
|
1043
1135
|
const shared = {
|
|
1044
1136
|
...base,
|
|
1045
1137
|
targets: [target(targetId)],
|
|
1046
|
-
features
|
|
1047
|
-
placement:
|
|
1138
|
+
features,
|
|
1139
|
+
placement: priority !== undefined ? { priority } : undefined,
|
|
1048
1140
|
}
|
|
1049
1141
|
if (targetId.startsWith('data-table:')) {
|
|
1050
1142
|
facts.push({
|
|
@@ -1053,7 +1145,7 @@ function extractInjectionTable(options: ExtractModuleExtensionFactsOptions): Mod
|
|
|
1053
1145
|
details: {
|
|
1054
1146
|
payload: payload === 'field' ? 'render' : payload,
|
|
1055
1147
|
tableId: targetId.replace(/^data-table:/, '').replace(/:(?:columns|row-actions|bulk-actions|filters|toolbar|header|footer|search-trailing)$/, ''),
|
|
1056
|
-
executionGuard:
|
|
1148
|
+
executionGuard: features.length > 0 ? 'both' : 'host',
|
|
1057
1149
|
},
|
|
1058
1150
|
})
|
|
1059
1151
|
} else if (targetId.startsWith('crud-form:')) {
|
|
@@ -1073,7 +1165,7 @@ function extractInjectionTable(options: ExtractModuleExtensionFactsOptions): Mod
|
|
|
1073
1165
|
details: {
|
|
1074
1166
|
payload: 'render',
|
|
1075
1167
|
registryKey: widgetId,
|
|
1076
|
-
executionGuard:
|
|
1168
|
+
executionGuard: features.length > 0 ? 'both' : 'host',
|
|
1077
1169
|
},
|
|
1078
1170
|
})
|
|
1079
1171
|
}
|
|
@@ -1093,7 +1185,9 @@ function extractObjectConvention(options: {
|
|
|
1093
1185
|
if (!filePath) return []
|
|
1094
1186
|
const exportNames = [options.exportName, ...(options.exportAliases ?? [])]
|
|
1095
1187
|
const entries = exportNames.reduce<StaticObject[]>((found, exportName) =>
|
|
1096
|
-
found.length > 0
|
|
1188
|
+
found.length > 0
|
|
1189
|
+
? found
|
|
1190
|
+
: readConventionObjectArrayForContract(filePath, exportName, options.module.factsContractVersion ?? 2), [])
|
|
1097
1191
|
const sourcePath = portablePath(options.module.moduleRoot, options.module.sourceRoot, filePath)
|
|
1098
1192
|
return entries.flatMap((entry, index) => options.build(entry, sourcePath, index) ?? [])
|
|
1099
1193
|
}
|
|
@@ -1353,7 +1447,11 @@ function extractComponentOverrides(options: ExtractModuleExtensionFactsOptions):
|
|
|
1353
1447
|
const targetDefinition = isStaticObject(entry.target) ? entry.target : null
|
|
1354
1448
|
const handle = targetDefinition ? stringValue(targetDefinition.componentId) : undefined
|
|
1355
1449
|
if (!handle) return null
|
|
1356
|
-
|
|
1450
|
+
// `ComponentOverride` discriminates on `wrapper` / `propsTransform` /
|
|
1451
|
+
// `replacement` — never on a `props` property, which the union has no member for.
|
|
1452
|
+
const mode = options.factsContractVersion === 1
|
|
1453
|
+
? (entry.wrapper !== undefined ? 'wrapper' : entry.props !== undefined ? 'props' : 'replace')
|
|
1454
|
+
: (entry.wrapper !== undefined ? 'wrapper' : entry.propsTransform !== undefined ? 'props' : 'replace')
|
|
1357
1455
|
const id = `${options.moduleId}.component-override.${index}:${handle}`
|
|
1358
1456
|
const contribution = contributionBase(id, sourcePath, 'componentOverrides')
|
|
1359
1457
|
return {
|
|
@@ -1780,6 +1878,22 @@ function targetModuleId(targetId: string): string | null {
|
|
|
1780
1878
|
return match?.[1] ?? null
|
|
1781
1879
|
}
|
|
1782
1880
|
|
|
1881
|
+
function normalizedEntityId(value: string): string {
|
|
1882
|
+
return value.replace(/^([a-z][a-z0-9_]*):/, '$1.')
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
function entityFactKeyForTarget(targetId: string, entityIds: ReadonlySet<string>): string | null {
|
|
1886
|
+
const normalizedTarget = normalizedEntityId(targetId)
|
|
1887
|
+
for (const entityId of entityIds) {
|
|
1888
|
+
const normalizedEntity = normalizedEntityId(entityId)
|
|
1889
|
+
if (normalizedTarget === normalizedEntity) return entityId
|
|
1890
|
+
if (normalizedTarget.startsWith(`${normalizedEntity}.`) && /\.(?:creating|created|updating|updated|deleting|deleted)$/.test(normalizedTarget)) {
|
|
1891
|
+
return entityId
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
return null
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1783
1897
|
export function correlateExtensionTarget(
|
|
1784
1898
|
targetFact: ModuleExtensionTargetFact,
|
|
1785
1899
|
options: CorrelateExtensionFactsOptions,
|
|
@@ -1813,8 +1927,11 @@ export function correlateExtensionTarget(
|
|
|
1813
1927
|
const framework = allFrameworkHosts().find((host) => patternMatches(host.id, targetFact.id))
|
|
1814
1928
|
?? (FRAMEWORK_PREFIXES.some((prefix) => targetFact.id.startsWith(prefix)) ? FRAMEWORK_HOSTS[0] : undefined)
|
|
1815
1929
|
if (framework) return { id: targetFact.id, resolution: 'framework' }
|
|
1816
|
-
|
|
1817
|
-
|
|
1930
|
+
const entityFactKey = options.factsContractVersion === 1
|
|
1931
|
+
? (options.entityIds.has(targetFact.id) ? targetFact.id : null)
|
|
1932
|
+
: entityFactKeyForTarget(targetFact.id, options.entityIds)
|
|
1933
|
+
if (entityFactKey) {
|
|
1934
|
+
return { id: targetFact.id, resolution: 'fact-ref', factRef: { factSection: 'entities', factKey: entityFactKey } }
|
|
1818
1935
|
}
|
|
1819
1936
|
if (options.eventIds.has(targetFact.id)) {
|
|
1820
1937
|
return { id: targetFact.id, resolution: 'fact-ref', factRef: { factSection: 'events', factKey: targetFact.id } }
|
|
@@ -2331,17 +2448,56 @@ export function assertNoUnresolvedExtensionTargets(
|
|
|
2331
2448
|
* — spec 2026-08-02-module-facts-exact-override-targets). Keyed by the dotted
|
|
2332
2449
|
* host id (e.g. `routes.api`, `ai.extensions`, `nav.groupOrder`) so the module
|
|
2333
2450
|
* override-target adapters share one mode source of truth with the catalog.
|
|
2451
|
+
*
|
|
2452
|
+
* @deprecated Use {@link getFrameworkOverrideHostOperations}. This helper drops any host whose
|
|
2453
|
+
* declared operation is not one of the three recognized modes, which collapses "the catalog does
|
|
2454
|
+
* not describe this host" into "the catalog describes it with a mode this generator has fallen
|
|
2455
|
+
* behind on" — the distinction the `unknown-framework-domain` / `unknown-framework-mode`
|
|
2456
|
+
* diagnostics exist to keep apart. Every in-tree consumer has moved; the export is retained
|
|
2457
|
+
* because `BACKWARD_COMPATIBILITY.md` classifies exported generator helpers as removable only
|
|
2458
|
+
* through the deprecation protocol, and it stays behaviour-identical for as long as it exists.
|
|
2334
2459
|
*/
|
|
2335
|
-
export function getFrameworkOverrideModes(
|
|
2460
|
+
export function getFrameworkOverrideModes(
|
|
2461
|
+
hosts: readonly ModuleExtensionHostFact[] = FRAMEWORK_OVERRIDE_HOSTS,
|
|
2462
|
+
): Record<string, 'disable-replace' | 'replace' | 'additive'> {
|
|
2336
2463
|
const modes: Record<string, 'disable-replace' | 'replace' | 'additive'> = {}
|
|
2337
|
-
for (const
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2464
|
+
for (const [dotted, operation] of Object.entries(getFrameworkOverrideHostOperations(hosts))) {
|
|
2465
|
+
if (operation === 'disable-replace' || operation === 'replace' || operation === 'additive') {
|
|
2466
|
+
modes[dotted] = operation
|
|
2467
|
+
}
|
|
2341
2468
|
}
|
|
2342
2469
|
return modes
|
|
2343
2470
|
}
|
|
2344
2471
|
|
|
2472
|
+
/**
|
|
2473
|
+
* The catalog's declared first operation for every dotted unified-override host,
|
|
2474
|
+
* **without** validating it against the known mode set.
|
|
2475
|
+
*
|
|
2476
|
+
* {@link getFrameworkOverrideModes} silently drops a host whose operation is not a
|
|
2477
|
+
* recognized mode, which makes "the catalog does not describe this host at all"
|
|
2478
|
+
* indistinguishable from "the catalog describes it with a mode this generator does
|
|
2479
|
+
* not understand". Consumers that must tell those apart (the override-target
|
|
2480
|
+
* adapters, which emit different diagnostics for each) read the raw operations
|
|
2481
|
+
* here instead — spec `2026-07-31-standalone-canonical-example-module.md`,
|
|
2482
|
+
* § PR #4883 Module-Fact and Extension-Topology Contract.
|
|
2483
|
+
*
|
|
2484
|
+
* `hosts` defaults to the framework catalog. Every catalog host declares a valid
|
|
2485
|
+
* mode today, so the pass-through is unobservable against the real catalog; passing
|
|
2486
|
+
* an explicit host list is how `module-override-targets.unknown-mode.test.ts` proves
|
|
2487
|
+
* that an operation this generator does not recognize really does survive here.
|
|
2488
|
+
*/
|
|
2489
|
+
export function getFrameworkOverrideHostOperations(
|
|
2490
|
+
hosts: readonly ModuleExtensionHostFact[] = FRAMEWORK_OVERRIDE_HOSTS,
|
|
2491
|
+
): Record<string, string> {
|
|
2492
|
+
const operations: Record<string, string> = {}
|
|
2493
|
+
for (const host of hosts) {
|
|
2494
|
+
const dotted = host.key.replace(/^framework\.module-override\./, '')
|
|
2495
|
+
const operation = host.operations?.[0]
|
|
2496
|
+
if (typeof operation === 'string') operations[dotted] = operation
|
|
2497
|
+
}
|
|
2498
|
+
return operations
|
|
2499
|
+
}
|
|
2500
|
+
|
|
2345
2501
|
export function getFrameworkExtensionHosts(): ModuleExtensionHostFact[] {
|
|
2346
2502
|
return sortHosts(allFrameworkHosts().map((host) => ({
|
|
2347
2503
|
...host,
|
|
@@ -107,3 +107,49 @@ export function discoverPackageModuleSources(resolver: PackageResolver): ModuleF
|
|
|
107
107
|
}
|
|
108
108
|
return resolveDuplicateModuleIds(sources, resolver)
|
|
109
109
|
}
|
|
110
|
+
|
|
111
|
+
/** App-local modules always live under this app-root-relative directory. */
|
|
112
|
+
export const LOCAL_MODULES_DIRECTORY = path.posix.join('src', 'modules')
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Locates an app-local module that is projected into the reference bundle rather than the
|
|
116
|
+
* normal package outputs. `appRoot` is the directory containing `src/modules` — the
|
|
117
|
+
* create-app template root at build time, the generated app root at scaffold and
|
|
118
|
+
* `agentic:init` time — so the same module yields the same portable `src/modules/<id>`
|
|
119
|
+
* provenance from every root. Returns `null` when the module is absent or is not readable
|
|
120
|
+
* TypeScript source, because a reference bundle built from an unreadable root would claim
|
|
121
|
+
* an empty surface rather than fail.
|
|
122
|
+
*/
|
|
123
|
+
export function discoverLocalReferenceModuleSource(options: {
|
|
124
|
+
appRoot: string
|
|
125
|
+
moduleId: string
|
|
126
|
+
}): ModuleFactSource | null {
|
|
127
|
+
const moduleRoot = path.join(options.appRoot, 'src', 'modules', options.moduleId)
|
|
128
|
+
if (!hasReadableModuleSource(moduleRoot)) return null
|
|
129
|
+
return {
|
|
130
|
+
moduleId: options.moduleId,
|
|
131
|
+
moduleRoot,
|
|
132
|
+
portableSourceRoot: path.posix.join(LOCAL_MODULES_DIRECTORY, options.moduleId),
|
|
133
|
+
sourceKind: 'local-reference',
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Appends the local reference source to the package batch, rejecting a duplicate module
|
|
139
|
+
* id *before* assignment. A package that already provides the same id would otherwise be
|
|
140
|
+
* silently replaced by (or silently replace) the app-local projection, and the emitted
|
|
141
|
+
* bundle would attribute one module's surfaces to the other's source tree.
|
|
142
|
+
*/
|
|
143
|
+
export function appendLocalReferenceModuleSource(
|
|
144
|
+
packageSources: readonly ModuleFactSource[],
|
|
145
|
+
reference: ModuleFactSource,
|
|
146
|
+
): ModuleFactSource[] {
|
|
147
|
+
const conflict = packageSources.find((source) => source.moduleId === reference.moduleId)
|
|
148
|
+
if (conflict) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`[module-facts] local reference module "${reference.moduleId}" collides with the module provided by `
|
|
151
|
+
+ `${conflict.from ?? '<unknown package>'}. Rename the app-local module or remove the package providing it.`,
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
return [...packageSources, reference]
|
|
155
|
+
}
|