@open-mercato/cli 0.6.7-develop.6795.1.8a3f27921c → 0.6.7-develop.6824.1.21fff90682
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 +1 -1
- package/build.mjs +1 -1
- package/dist/agentic/guides/module-facts.json +90266 -20749
- package/dist/agentic/guides/modules/ai_assistant.md +170 -60
- package/dist/agentic/guides/modules/api_docs.md +26 -1
- package/dist/agentic/guides/modules/api_keys.md +57 -17
- package/dist/agentic/guides/modules/attachments.md +72 -19
- package/dist/agentic/guides/modules/audit_logs.md +95 -29
- package/dist/agentic/guides/modules/auth.md +218 -83
- package/dist/agentic/guides/modules/business_rules.md +108 -34
- package/dist/agentic/guides/modules/catalog.md +386 -157
- package/dist/agentic/guides/modules/channel_gmail.md +55 -6
- package/dist/agentic/guides/modules/channel_imap.md +55 -6
- package/dist/agentic/guides/modules/checkout.md +254 -129
- package/dist/agentic/guides/modules/communication_channels.md +280 -109
- package/dist/agentic/guides/modules/configs.md +68 -11
- package/dist/agentic/guides/modules/content.md +26 -1
- package/dist/agentic/guides/modules/currencies.md +109 -44
- package/dist/agentic/guides/modules/customer_accounts.md +350 -156
- package/dist/agentic/guides/modules/customers.md +642 -294
- package/dist/agentic/guides/modules/dashboards.md +122 -28
- package/dist/agentic/guides/modules/data_sync.md +117 -37
- package/dist/agentic/guides/modules/design_system.md +36 -2
- package/dist/agentic/guides/modules/dictionaries.md +78 -20
- package/dist/agentic/guides/modules/directory.md +110 -45
- package/dist/agentic/guides/modules/entities.md +95 -38
- package/dist/agentic/guides/modules/events.md +31 -1
- package/dist/agentic/guides/modules/feature_toggles.md +79 -12
- package/dist/agentic/guides/modules/gateway_stripe.md +61 -6
- package/dist/agentic/guides/modules/generators.md +17 -1
- package/dist/agentic/guides/modules/inbox_ops.md +171 -60
- package/dist/agentic/guides/modules/integrations.md +116 -30
- package/dist/agentic/guides/modules/messages.md +187 -66
- package/dist/agentic/guides/modules/notifications.md +64 -9
- package/dist/agentic/guides/modules/onboarding.md +47 -8
- package/dist/agentic/guides/modules/payment_gateways.md +133 -48
- package/dist/agentic/guides/modules/perspectives.md +50 -12
- package/dist/agentic/guides/modules/planner.md +102 -32
- package/dist/agentic/guides/modules/portal.md +58 -15
- package/dist/agentic/guides/modules/progress.md +78 -30
- package/dist/agentic/guides/modules/query_index.md +115 -53
- package/dist/agentic/guides/modules/record_locks.md +178 -59
- package/dist/agentic/guides/modules/resources.md +155 -78
- package/dist/agentic/guides/modules/sales.md +620 -257
- package/dist/agentic/guides/modules/scheduler.md +95 -19
- package/dist/agentic/guides/modules/search.md +124 -20
- package/dist/agentic/guides/modules/security.md +251 -105
- package/dist/agentic/guides/modules/shipping_carriers.md +106 -37
- package/dist/agentic/guides/modules/sso.md +121 -49
- package/dist/agentic/guides/modules/staff.md +314 -126
- package/dist/agentic/guides/modules/storage_s3.md +55 -6
- package/dist/agentic/guides/modules/sync_akeneo.md +54 -4
- package/dist/agentic/guides/modules/sync_excel.md +49 -11
- package/dist/agentic/guides/modules/system_status_overlays.md +33 -5
- package/dist/agentic/guides/modules/translations.md +76 -21
- package/dist/agentic/guides/modules/webhooks.md +166 -82
- package/dist/agentic/guides/modules/wms.md +383 -223
- package/dist/agentic/guides/modules/workflows.md +248 -112
- package/dist/agentic/guides/upstream/AGENTS.md +1 -1
- package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +2 -0
- package/dist/agentic/guides/upstream/manifest.json +3 -3
- package/dist/agentic/shared/AGENTS.md.template +6 -5
- package/dist/agentic/shared/ai/harness/README.md +2 -2
- package/dist/agentic/shared/ai/harness/RELEASE.md +4 -4
- package/dist/agentic/shared/ai/harness/cases.json +94 -1
- package/dist/agentic/shared/ai/harness/cases.schema.json +3 -3
- package/dist/agentic/shared/ai/harness/validators.json +1 -1
- package/dist/agentic/shared/ai/lessons/_template.md +16 -0
- package/dist/agentic/shared/ai/lessons.md +26 -3
- package/dist/agentic/shared/ai/skills/om-evolve-harness/SKILL.md +7 -6
- package/dist/agentic/shared/ai/skills/om-system-extension/SKILL.md +1 -1
- package/dist/agentic/shared/ai/skills/om-system-extension/references/unified-overrides.md +4 -2
- package/dist/agentic/shared/scripts/check-lessons.mjs +178 -0
- package/dist/agentic/shared/scripts/evaluate-agent-harness.mjs +37 -12
- package/dist/lib/agentic-setup.js +2 -2
- package/dist/lib/agentic-setup.js.map +2 -2
- package/dist/lib/generators/module-extension-facts.js +652 -24
- package/dist/lib/generators/module-extension-facts.js.map +3 -3
- package/dist/lib/generators/module-fact-sources.js +70 -0
- package/dist/lib/generators/module-fact-sources.js.map +7 -0
- package/dist/lib/generators/module-facts.js +1001 -26
- package/dist/lib/generators/module-facts.js.map +3 -3
- package/dist/lib/generators/module-override-targets.js +688 -0
- package/dist/lib/generators/module-override-targets.js.map +7 -0
- package/dist/lib/testing/integration.js +5 -0
- package/dist/lib/testing/integration.js.map +2 -2
- package/package.json +5 -5
- package/src/lib/__tests__/agentic-setup.ownership.test.ts +6 -0
- package/src/lib/agentic-setup.ts +6 -2
- package/src/lib/generators/__tests__/activation-adapter-coverage.test.ts +92 -0
- package/src/lib/generators/__tests__/module-extension-activations.test.ts +762 -0
- package/src/lib/generators/__tests__/module-extension-facts.test.ts +4 -2
- package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +37 -3
- package/src/lib/generators/__tests__/module-facts.owned-contracts.test.ts +561 -0
- package/src/lib/generators/__tests__/module-override-targets.roundtrip.test.ts +149 -0
- package/src/lib/generators/__tests__/module-override-targets.test.ts +304 -0
- package/src/lib/generators/module-extension-facts.ts +923 -27
- package/src/lib/generators/module-fact-sources.ts +108 -0
- package/src/lib/generators/module-facts.ts +1360 -25
- package/src/lib/generators/module-override-targets.ts +863 -0
- package/src/lib/testing/__tests__/integration.test.ts +4 -0
- package/src/lib/testing/integration.ts +5 -0
|
@@ -2,15 +2,23 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import ts from "typescript-js";
|
|
4
4
|
import { toSnake } from "../utils.js";
|
|
5
|
+
import { extractCommandIdsFromSource } from "./module-registry.js";
|
|
6
|
+
import { MODULE_CODE_EXTENSIONS } from "./scanner.js";
|
|
5
7
|
import {
|
|
6
8
|
assertNoUnresolvedExtensionTargets,
|
|
9
|
+
correlateIncomingExtensions,
|
|
7
10
|
correlateModuleExtensionFacts,
|
|
11
|
+
extractActivationTargetOwners,
|
|
8
12
|
extractKnownApiRouteIds,
|
|
9
13
|
extractKnownCommandIds,
|
|
10
14
|
extractModuleExtensionFacts,
|
|
11
15
|
renderFrameworkExtensionPointsMarkdown,
|
|
12
16
|
withModuleExtensionFactExtractionCache
|
|
13
17
|
} from "./module-extension-facts.js";
|
|
18
|
+
import {
|
|
19
|
+
collectModuleOverrideTargets
|
|
20
|
+
} from "./module-override-targets.js";
|
|
21
|
+
import { buildFactSourceLookup } from "./module-fact-sources.js";
|
|
14
22
|
function readSourceFile(filePath) {
|
|
15
23
|
if (!fs.existsSync(filePath)) return null;
|
|
16
24
|
const source = fs.readFileSync(filePath, "utf8");
|
|
@@ -582,16 +590,21 @@ function extractModulePages(moduleId, moduleRoot, sourceRoot, surface) {
|
|
|
582
590
|
const fileName = segments.pop();
|
|
583
591
|
const fileStem = fileName.replace(/\.tsx$/, "");
|
|
584
592
|
const isModernPage = fileStem === "page";
|
|
585
|
-
if (!isModernPage && (fileStem.endsWith(".meta") || /^[A-Z]/.test(fileStem))) continue;
|
|
593
|
+
if (!isModernPage && (fileStem === "meta" || fileStem.endsWith(".meta") || /^[A-Z]/.test(fileStem))) continue;
|
|
586
594
|
const sourceFile = readSourceFile(filePath);
|
|
587
595
|
if (!sourceFile || !sourceHasDefaultExport(sourceFile)) continue;
|
|
588
596
|
const routeSegments = isModernPage ? segments : [...segments, fileStem];
|
|
589
597
|
const routePath = surface === "frontend" ? `/${routeSegments.filter(Boolean).join("/")}` : isModernPage ? `/backend/${routeSegments.join("/") || moduleId}` : `/backend/${routeSegments[0] === moduleId ? routeSegments.filter(Boolean).join("/") : [moduleId, ...routeSegments].filter(Boolean).join("/")}`;
|
|
590
598
|
if (seen.has(routePath)) continue;
|
|
591
599
|
seen.add(routePath);
|
|
600
|
+
const metadataFilePath = resolvePageMetadataFile(path.dirname(filePath));
|
|
601
|
+
const metadataSourceFile = metadataFilePath ? readSourceFile(metadataFilePath) : sourceFile;
|
|
602
|
+
const metadata = metadataSourceFile ? readPageMetadata(metadataSourceFile) : void 0;
|
|
592
603
|
pages.push({
|
|
593
604
|
path: routePath,
|
|
594
|
-
sourcePath: toPortableSourcePath(moduleRoot, sourceRoot, filePath)
|
|
605
|
+
sourcePath: toPortableSourcePath(moduleRoot, sourceRoot, filePath),
|
|
606
|
+
...metadataFilePath ? { metadataSourcePath: toPortableSourcePath(moduleRoot, sourceRoot, metadataFilePath) } : {},
|
|
607
|
+
...metadata ? { metadata } : {}
|
|
595
608
|
});
|
|
596
609
|
}
|
|
597
610
|
return pages.sort((left, right) => left.path.localeCompare(right.path));
|
|
@@ -725,6 +738,580 @@ function extractModuleMeta(indexFilePath) {
|
|
|
725
738
|
description: readStringPropertyInitializer(metadata, "description") ?? null
|
|
726
739
|
};
|
|
727
740
|
}
|
|
741
|
+
function nodeLine(sourceFile, node) {
|
|
742
|
+
return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
|
743
|
+
}
|
|
744
|
+
function readPropertyNameText(name) {
|
|
745
|
+
if (ts.isIdentifier(name) || ts.isStringLiteralLike(name)) return name.text;
|
|
746
|
+
return void 0;
|
|
747
|
+
}
|
|
748
|
+
function readMethodBearingPropertyNames(objectLiteral) {
|
|
749
|
+
const names = [];
|
|
750
|
+
for (const property of objectLiteral.properties) {
|
|
751
|
+
if (ts.isMethodDeclaration(property)) {
|
|
752
|
+
const name = readPropertyNameText(property.name);
|
|
753
|
+
if (name) names.push(name);
|
|
754
|
+
continue;
|
|
755
|
+
}
|
|
756
|
+
if (ts.isPropertyAssignment(property)) {
|
|
757
|
+
const name = readPropertyNameText(property.name);
|
|
758
|
+
if (!name) continue;
|
|
759
|
+
const initializer = unwrapExpression(property.initializer);
|
|
760
|
+
if (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)) names.push(name);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return names.sort((left, right) => left.localeCompare(right));
|
|
764
|
+
}
|
|
765
|
+
function readObjectKeyNames(objectLiteral) {
|
|
766
|
+
const names = [];
|
|
767
|
+
for (const property of objectLiteral.properties) {
|
|
768
|
+
const name = getPropertyName(property);
|
|
769
|
+
if (name) names.push(name);
|
|
770
|
+
}
|
|
771
|
+
return names.sort((left, right) => left.localeCompare(right));
|
|
772
|
+
}
|
|
773
|
+
function mapDiRegistrationKind(base) {
|
|
774
|
+
switch (base) {
|
|
775
|
+
case "asFunction":
|
|
776
|
+
return "function";
|
|
777
|
+
case "asClass":
|
|
778
|
+
return "class";
|
|
779
|
+
case "asValue":
|
|
780
|
+
return "value";
|
|
781
|
+
case "aliasTo":
|
|
782
|
+
return "alias";
|
|
783
|
+
default:
|
|
784
|
+
return null;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
function readDiChainMetadata(initializer) {
|
|
788
|
+
const result = {};
|
|
789
|
+
let current = unwrapExpression(initializer);
|
|
790
|
+
while (ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
|
|
791
|
+
const method = current.expression.name.text;
|
|
792
|
+
if ((method === "singleton" || method === "scoped" || method === "transient") && !result.lifetime) {
|
|
793
|
+
result.lifetime = method;
|
|
794
|
+
} else if (method === "proxy" && !result.injectionMode) {
|
|
795
|
+
result.injectionMode = "proxy";
|
|
796
|
+
} else if (method === "classic" && !result.injectionMode) {
|
|
797
|
+
result.injectionMode = "classic";
|
|
798
|
+
}
|
|
799
|
+
current = unwrapExpression(current.expression.expression);
|
|
800
|
+
}
|
|
801
|
+
return result;
|
|
802
|
+
}
|
|
803
|
+
function readDiProviderSymbol(initializer, kind) {
|
|
804
|
+
let current = unwrapExpression(initializer);
|
|
805
|
+
while (ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
|
|
806
|
+
current = unwrapExpression(current.expression.expression);
|
|
807
|
+
}
|
|
808
|
+
if (!ts.isCallExpression(current)) return void 0;
|
|
809
|
+
const argument = current.arguments[0];
|
|
810
|
+
if (!argument) return void 0;
|
|
811
|
+
if (kind === "value") return void 0;
|
|
812
|
+
if (kind === "alias" && ts.isStringLiteralLike(argument)) return argument.text;
|
|
813
|
+
if ((kind === "function" || kind === "class") && ts.isIdentifier(argument)) return argument.text;
|
|
814
|
+
return void 0;
|
|
815
|
+
}
|
|
816
|
+
function extractDiRegistrationFacts(diFilePath, sourcePath) {
|
|
817
|
+
if (!diFilePath) return { facts: [], unresolvedTokens: [] };
|
|
818
|
+
const sourceFile = readSourceFile(diFilePath);
|
|
819
|
+
if (!sourceFile) return { facts: [], unresolvedTokens: [] };
|
|
820
|
+
const facts = [];
|
|
821
|
+
const unresolvedTokens = [];
|
|
822
|
+
const visit = (node) => {
|
|
823
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "register") {
|
|
824
|
+
const argument = node.arguments[0];
|
|
825
|
+
if (argument && ts.isObjectLiteralExpression(argument)) {
|
|
826
|
+
for (const property of argument.properties) {
|
|
827
|
+
if (!ts.isPropertyAssignment(property)) continue;
|
|
828
|
+
const token = getPropertyName(property);
|
|
829
|
+
if (!token) continue;
|
|
830
|
+
const kind = mapDiRegistrationKind(detectAwilixRegistrationKind(property.initializer));
|
|
831
|
+
if (!kind) {
|
|
832
|
+
if (!unresolvedTokens.includes(token)) unresolvedTokens.push(token);
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
const chain = readDiChainMetadata(property.initializer);
|
|
836
|
+
const providerSymbol = readDiProviderSymbol(property.initializer, kind);
|
|
837
|
+
facts.push({
|
|
838
|
+
token,
|
|
839
|
+
registrationKind: kind,
|
|
840
|
+
...providerSymbol ? { providerSymbol } : {},
|
|
841
|
+
...chain.lifetime ? { lifetime: chain.lifetime } : {},
|
|
842
|
+
...chain.injectionMode ? { injectionMode: chain.injectionMode } : {},
|
|
843
|
+
source: { sourcePath, exportName: token, line: nodeLine(sourceFile, property) }
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
node.forEachChild(visit);
|
|
849
|
+
};
|
|
850
|
+
sourceFile.forEachChild(visit);
|
|
851
|
+
return { facts, unresolvedTokens };
|
|
852
|
+
}
|
|
853
|
+
function deriveLegacyDiTokens(facts) {
|
|
854
|
+
const tokens = [];
|
|
855
|
+
const seen = /* @__PURE__ */ new Set();
|
|
856
|
+
for (const fact of facts) {
|
|
857
|
+
if (fact.registrationKind !== "function" && fact.registrationKind !== "class") continue;
|
|
858
|
+
if (seen.has(fact.token)) continue;
|
|
859
|
+
seen.add(fact.token);
|
|
860
|
+
tokens.push(fact.token);
|
|
861
|
+
}
|
|
862
|
+
return tokens;
|
|
863
|
+
}
|
|
864
|
+
function dedupeOwnedContractFacts(facts) {
|
|
865
|
+
const byId = /* @__PURE__ */ new Map();
|
|
866
|
+
for (const fact of facts) {
|
|
867
|
+
const existing = byId.get(fact.id);
|
|
868
|
+
if (!existing) {
|
|
869
|
+
byId.set(fact.id, fact);
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
if (fact.source.sourcePath.localeCompare(existing.source.sourcePath) < 0) byId.set(fact.id, fact);
|
|
873
|
+
}
|
|
874
|
+
return [...byId.values()].sort((left, right) => left.id.localeCompare(right.id));
|
|
875
|
+
}
|
|
876
|
+
const PAGE_CONTEXT_VALUES = /* @__PURE__ */ new Set(["main", "admin", "settings", "profile"]);
|
|
877
|
+
function readAliasedStringProperty(metadata, preferred, fallback) {
|
|
878
|
+
return readStringPropertyInitializer(metadata, preferred) ?? readStringPropertyInitializer(metadata, fallback);
|
|
879
|
+
}
|
|
880
|
+
function readNumericProperty(metadata, propertyName) {
|
|
881
|
+
const initializer = getObjectPropertyInitializer(metadata, propertyName);
|
|
882
|
+
if (!initializer || !ts.isNumericLiteral(initializer)) return void 0;
|
|
883
|
+
return Number(initializer.text);
|
|
884
|
+
}
|
|
885
|
+
function readPageMetadata(sourceFile) {
|
|
886
|
+
const metadata = findObjectLiteralDeclaration(sourceFile, "metadata");
|
|
887
|
+
if (!metadata) return void 0;
|
|
888
|
+
const result = {};
|
|
889
|
+
const requireAuth = readBooleanPropertyInitializer(metadata, "requireAuth");
|
|
890
|
+
if (requireAuth !== void 0) result.requireAuth = requireAuth;
|
|
891
|
+
const requireCustomerAuth = readBooleanPropertyInitializer(metadata, "requireCustomerAuth");
|
|
892
|
+
if (requireCustomerAuth !== void 0) result.requireCustomerAuth = requireCustomerAuth;
|
|
893
|
+
const requireFeatures = readStringArrayPropertyInitializer(metadata, "requireFeatures");
|
|
894
|
+
if (requireFeatures && requireFeatures.length > 0) result.requireFeatures = requireFeatures;
|
|
895
|
+
const requireCustomerFeatures = readStringArrayPropertyInitializer(metadata, "requireCustomerFeatures");
|
|
896
|
+
if (requireCustomerFeatures && requireCustomerFeatures.length > 0) {
|
|
897
|
+
result.requireCustomerFeatures = requireCustomerFeatures;
|
|
898
|
+
}
|
|
899
|
+
const navHidden = readBooleanPropertyInitializer(metadata, "navHidden");
|
|
900
|
+
if (navHidden !== void 0) result.navHidden = navHidden;
|
|
901
|
+
const navGroup = readAliasedStringProperty(metadata, "pageGroup", "group");
|
|
902
|
+
if (navGroup) result.navGroup = navGroup;
|
|
903
|
+
const navGroupKey = readAliasedStringProperty(metadata, "pageGroupKey", "groupKey");
|
|
904
|
+
if (navGroupKey) result.navGroupKey = navGroupKey;
|
|
905
|
+
const navOrder = readNumericProperty(metadata, "pageOrder") ?? readNumericProperty(metadata, "order");
|
|
906
|
+
if (navOrder !== void 0) result.navOrder = navOrder;
|
|
907
|
+
const pageContext = readStringPropertyInitializer(metadata, "pageContext");
|
|
908
|
+
if (pageContext && PAGE_CONTEXT_VALUES.has(pageContext)) {
|
|
909
|
+
result.pageContext = pageContext;
|
|
910
|
+
}
|
|
911
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
912
|
+
}
|
|
913
|
+
function resolvePageMetadataFile(pageDirectory) {
|
|
914
|
+
for (const baseName of ["page.meta", "meta"]) {
|
|
915
|
+
for (const extension of MODULE_CODE_EXTENSIONS) {
|
|
916
|
+
const candidate = path.join(pageDirectory, `${baseName}${extension}`);
|
|
917
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
return null;
|
|
921
|
+
}
|
|
922
|
+
function extractModuleMetadataContract(indexFilePath, moduleId, sourcePath) {
|
|
923
|
+
if (!indexFilePath || !sourcePath) return null;
|
|
924
|
+
const sourceFile = readSourceFile(indexFilePath);
|
|
925
|
+
if (!sourceFile) return null;
|
|
926
|
+
const metadata = findObjectLiteralDeclaration(sourceFile, "metadata");
|
|
927
|
+
if (!metadata) return null;
|
|
928
|
+
const facts = {};
|
|
929
|
+
const name = readStringPropertyInitializer(metadata, "name");
|
|
930
|
+
if (name) facts.name = name;
|
|
931
|
+
const version = readStringPropertyInitializer(metadata, "version");
|
|
932
|
+
if (version) facts.version = version;
|
|
933
|
+
const requires = readStringArrayPropertyInitializer(metadata, "requires");
|
|
934
|
+
if (requires && requires.length > 0) facts.requires = requires;
|
|
935
|
+
const ejectable = readBooleanPropertyInitializer(metadata, "ejectable");
|
|
936
|
+
if (ejectable !== void 0) facts.ejectable = ejectable;
|
|
937
|
+
return {
|
|
938
|
+
kind: "module-metadata",
|
|
939
|
+
id: moduleId,
|
|
940
|
+
source: { sourcePath, exportName: "metadata", line: nodeLine(sourceFile, metadata) },
|
|
941
|
+
...Object.keys(facts).length > 0 ? { metadata: facts } : {}
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
function extractCommandContractCandidates(moduleId, moduleRoot, sourceRoot) {
|
|
945
|
+
const commandsRoot = path.join(moduleRoot, "commands");
|
|
946
|
+
if (!fs.existsSync(commandsRoot)) return [];
|
|
947
|
+
const candidates = [];
|
|
948
|
+
for (const filePath of listSourceFilesRecursive(commandsRoot)) {
|
|
949
|
+
if (path.basename(filePath).replace(/\.tsx?$/, "") === "interceptors") continue;
|
|
950
|
+
const sourcePath = toPortableSourcePath(moduleRoot, sourceRoot, filePath);
|
|
951
|
+
for (const id of extractCommandIdsFromSource(filePath)) candidates.push({ id, sourcePath });
|
|
952
|
+
}
|
|
953
|
+
return candidates;
|
|
954
|
+
}
|
|
955
|
+
function deriveWorkerId(moduleId, workersRoot, filePath) {
|
|
956
|
+
const relativePath = path.relative(workersRoot, filePath).split(path.sep).join("/");
|
|
957
|
+
const segments = relativePath.split("/");
|
|
958
|
+
const fileName = segments.pop();
|
|
959
|
+
const fileStem = fileName.replace(/\.tsx?$/, "");
|
|
960
|
+
return [moduleId, "workers", ...segments, fileStem].filter(Boolean).join(":");
|
|
961
|
+
}
|
|
962
|
+
function readResolvedStringProperty(objectLiteral, propertyName, initializers) {
|
|
963
|
+
const initializer = getObjectPropertyInitializer(objectLiteral, propertyName);
|
|
964
|
+
if (!initializer) return void 0;
|
|
965
|
+
const resolved = unwrapExpression(initializer);
|
|
966
|
+
if (ts.isStringLiteralLike(resolved)) return resolved.text;
|
|
967
|
+
if (ts.isIdentifier(resolved)) {
|
|
968
|
+
const declaration = initializers.get(resolved.text);
|
|
969
|
+
if (declaration) {
|
|
970
|
+
const declarationValue = unwrapExpression(declaration);
|
|
971
|
+
if (ts.isStringLiteralLike(declarationValue)) return declarationValue.text;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
return void 0;
|
|
975
|
+
}
|
|
976
|
+
function extractWorkerContracts(moduleId, moduleRoot, sourceRoot) {
|
|
977
|
+
const workersRoot = path.join(moduleRoot, "workers");
|
|
978
|
+
if (!fs.existsSync(workersRoot)) return { facts: [], unresolved: [] };
|
|
979
|
+
const facts = [];
|
|
980
|
+
const unresolved = [];
|
|
981
|
+
for (const filePath of listSourceFilesRecursive(workersRoot)) {
|
|
982
|
+
const sourceFile = readSourceFile(filePath);
|
|
983
|
+
if (!sourceFile) continue;
|
|
984
|
+
const metadata = findObjectLiteralDeclaration(sourceFile, "metadata");
|
|
985
|
+
if (!metadata) continue;
|
|
986
|
+
const sourcePath = toPortableSourcePath(moduleRoot, sourceRoot, filePath);
|
|
987
|
+
const source = { sourcePath, exportName: "metadata", line: nodeLine(sourceFile, metadata) };
|
|
988
|
+
const initializers = buildVariableInitializerMap(sourceFile);
|
|
989
|
+
const workerId = readResolvedStringProperty(metadata, "id", initializers) ?? deriveWorkerId(moduleId, workersRoot, filePath);
|
|
990
|
+
const hasQueueProperty = getObjectPropertyInitializer(metadata, "queue") !== void 0;
|
|
991
|
+
const queue = readResolvedStringProperty(metadata, "queue", initializers);
|
|
992
|
+
if (!hasQueueProperty) {
|
|
993
|
+
unresolved.push({ code: "unresolved-static-contract", kind: "worker", id: workerId, source });
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
const workerMetadata = {};
|
|
997
|
+
if (queue) workerMetadata.queue = queue;
|
|
998
|
+
else unresolved.push({ code: "unresolved-static-contract", kind: "worker", id: workerId, source });
|
|
999
|
+
const name = readResolvedStringProperty(metadata, "name", initializers);
|
|
1000
|
+
if (name) workerMetadata.name = name;
|
|
1001
|
+
const concurrencyInitializer = getObjectPropertyInitializer(metadata, "concurrency");
|
|
1002
|
+
if (concurrencyInitializer && ts.isNumericLiteral(concurrencyInitializer)) {
|
|
1003
|
+
workerMetadata.concurrency = Number(concurrencyInitializer.text);
|
|
1004
|
+
}
|
|
1005
|
+
facts.push({
|
|
1006
|
+
kind: "worker",
|
|
1007
|
+
id: workerId,
|
|
1008
|
+
source,
|
|
1009
|
+
...Object.keys(workerMetadata).length > 0 ? { metadata: workerMetadata } : {}
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
return { facts: dedupeOwnedContractFacts(facts), unresolved };
|
|
1013
|
+
}
|
|
1014
|
+
function extractPageMiddlewareContracts(moduleRoot, sourceRoot) {
|
|
1015
|
+
const facts = [];
|
|
1016
|
+
const unresolved = [];
|
|
1017
|
+
for (const surface of ["backend", "frontend"]) {
|
|
1018
|
+
const filePath = resolveConventionFile(path.join(moduleRoot, surface), "middleware");
|
|
1019
|
+
if (!filePath) continue;
|
|
1020
|
+
const sourceFile = readSourceFile(filePath);
|
|
1021
|
+
if (!sourceFile) continue;
|
|
1022
|
+
const sourcePath = toPortableSourcePath(moduleRoot, sourceRoot, filePath);
|
|
1023
|
+
const middlewareArray = findArrayLiteralDeclaration(sourceFile, "middleware") ?? (() => {
|
|
1024
|
+
const defaultExport = findDefaultExportExpression(sourceFile);
|
|
1025
|
+
return defaultExport ? unwrapArrayLiteral(defaultExport) : null;
|
|
1026
|
+
})();
|
|
1027
|
+
if (!middlewareArray) continue;
|
|
1028
|
+
let index = 0;
|
|
1029
|
+
for (const element of middlewareArray.elements) {
|
|
1030
|
+
if (!ts.isObjectLiteralExpression(element)) continue;
|
|
1031
|
+
const middlewareId = readStringPropertyInitializer(element, "id");
|
|
1032
|
+
if (!middlewareId) {
|
|
1033
|
+
unresolved.push({
|
|
1034
|
+
code: "unresolved-static-contract",
|
|
1035
|
+
kind: "page-middleware",
|
|
1036
|
+
id: `${sourcePath}#${index}`,
|
|
1037
|
+
source: { sourcePath, line: nodeLine(sourceFile, element) }
|
|
1038
|
+
});
|
|
1039
|
+
index += 1;
|
|
1040
|
+
continue;
|
|
1041
|
+
}
|
|
1042
|
+
const metadata = { surface };
|
|
1043
|
+
const mode = readStringPropertyInitializer(element, "mode");
|
|
1044
|
+
if (mode) metadata.mode = mode;
|
|
1045
|
+
const priorityInitializer = getObjectPropertyInitializer(element, "priority");
|
|
1046
|
+
if (priorityInitializer && ts.isNumericLiteral(priorityInitializer)) {
|
|
1047
|
+
metadata.priority = Number(priorityInitializer.text);
|
|
1048
|
+
}
|
|
1049
|
+
facts.push({
|
|
1050
|
+
kind: "page-middleware",
|
|
1051
|
+
id: middlewareId,
|
|
1052
|
+
source: { sourcePath, exportName: "middleware", line: nodeLine(sourceFile, element) },
|
|
1053
|
+
metadata
|
|
1054
|
+
});
|
|
1055
|
+
index += 1;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
return { facts: dedupeOwnedContractFacts(facts), unresolved };
|
|
1059
|
+
}
|
|
1060
|
+
function extractSetupContract(moduleId, setupFilePath, sourcePath) {
|
|
1061
|
+
if (!setupFilePath || !sourcePath) return null;
|
|
1062
|
+
const sourceFile = readSourceFile(setupFilePath);
|
|
1063
|
+
if (!sourceFile) return null;
|
|
1064
|
+
const setupObject = findObjectLiteralDeclaration(sourceFile, "setup") ?? (() => {
|
|
1065
|
+
const defaultExport = findDefaultExportExpression(sourceFile);
|
|
1066
|
+
if (!defaultExport) return null;
|
|
1067
|
+
const unwrapped = unwrapExpression(defaultExport);
|
|
1068
|
+
return ts.isObjectLiteralExpression(unwrapped) ? unwrapped : null;
|
|
1069
|
+
})();
|
|
1070
|
+
if (!setupObject) return null;
|
|
1071
|
+
const hooks = readMethodBearingPropertyNames(setupObject);
|
|
1072
|
+
const metadata = {};
|
|
1073
|
+
if (hooks.length > 0) metadata.hooks = hooks;
|
|
1074
|
+
const roleFeatures = getObjectPropertyInitializer(setupObject, "defaultRoleFeatures");
|
|
1075
|
+
if (roleFeatures && ts.isObjectLiteralExpression(roleFeatures)) {
|
|
1076
|
+
const roles = readObjectKeyNames(roleFeatures);
|
|
1077
|
+
if (roles.length > 0) metadata.roles = roles;
|
|
1078
|
+
}
|
|
1079
|
+
const customerRoleFeatures = getObjectPropertyInitializer(setupObject, "defaultCustomerRoleFeatures");
|
|
1080
|
+
if (customerRoleFeatures && ts.isObjectLiteralExpression(customerRoleFeatures)) {
|
|
1081
|
+
const customerRoles = readObjectKeyNames(customerRoleFeatures);
|
|
1082
|
+
if (customerRoles.length > 0) metadata.customerRoles = customerRoles;
|
|
1083
|
+
}
|
|
1084
|
+
return {
|
|
1085
|
+
kind: "setup",
|
|
1086
|
+
id: `${moduleId}:setup`,
|
|
1087
|
+
source: { sourcePath, exportName: "setup", line: nodeLine(sourceFile, setupObject) },
|
|
1088
|
+
...Object.keys(metadata).length > 0 ? { metadata } : {}
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
function extractEncryptionContracts(encryptionFilePath, sourcePath) {
|
|
1092
|
+
if (!encryptionFilePath || !sourcePath) return [];
|
|
1093
|
+
const sourceFile = readSourceFile(encryptionFilePath);
|
|
1094
|
+
if (!sourceFile) return [];
|
|
1095
|
+
const mapsArray = findArrayLiteralDeclaration(sourceFile, "defaultEncryptionMaps") ?? findArrayLiteralDeclaration(sourceFile, "encryptionMaps");
|
|
1096
|
+
if (!mapsArray) return [];
|
|
1097
|
+
const facts = [];
|
|
1098
|
+
for (const element of mapsArray.elements) {
|
|
1099
|
+
if (!ts.isObjectLiteralExpression(element)) continue;
|
|
1100
|
+
const entityId = readStringPropertyInitializer(element, "entityId");
|
|
1101
|
+
if (!entityId) continue;
|
|
1102
|
+
const fieldsInitializer = getObjectPropertyInitializer(element, "fields");
|
|
1103
|
+
const fields = [];
|
|
1104
|
+
if (fieldsInitializer) {
|
|
1105
|
+
const fieldsArray = unwrapArrayLiteral(fieldsInitializer);
|
|
1106
|
+
if (fieldsArray) {
|
|
1107
|
+
for (const fieldElement of fieldsArray.elements) {
|
|
1108
|
+
if (!ts.isObjectLiteralExpression(fieldElement)) continue;
|
|
1109
|
+
const field = readStringPropertyInitializer(fieldElement, "field");
|
|
1110
|
+
if (field) fields.push(field);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
facts.push({
|
|
1115
|
+
kind: "encryption",
|
|
1116
|
+
id: entityId,
|
|
1117
|
+
source: { sourcePath, exportName: "defaultEncryptionMaps", line: nodeLine(sourceFile, element) },
|
|
1118
|
+
...fields.length > 0 ? { metadata: { fields: fields.sort((left, right) => left.localeCompare(right)) } } : {}
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
return dedupeOwnedContractFacts(facts);
|
|
1122
|
+
}
|
|
1123
|
+
function extractCustomEntityContracts(ceFilePath, sourcePath) {
|
|
1124
|
+
if (!ceFilePath || !sourcePath) return [];
|
|
1125
|
+
const entityIds = [...collectCustomFieldEntityIds(ceFilePath)].sort((left, right) => left.localeCompare(right));
|
|
1126
|
+
return entityIds.map((id) => ({
|
|
1127
|
+
kind: "custom-entity",
|
|
1128
|
+
id,
|
|
1129
|
+
source: { sourcePath, exportName: "default" }
|
|
1130
|
+
}));
|
|
1131
|
+
}
|
|
1132
|
+
const AI_OVERRIDE_EXPORTS = [
|
|
1133
|
+
{ exportName: "aiAgentOverrides", target: "agent" },
|
|
1134
|
+
{ exportName: "aiToolOverrides", target: "tool" }
|
|
1135
|
+
];
|
|
1136
|
+
function extractAiExtensionContracts(moduleRoot, sourceRoot) {
|
|
1137
|
+
const facts = [];
|
|
1138
|
+
const sources = [
|
|
1139
|
+
{ basename: "ai-agents", relativeDir: "." },
|
|
1140
|
+
{ basename: "ai-tools", relativeDir: "." },
|
|
1141
|
+
{ basename: "ai-agents.extensions", relativeDir: "." },
|
|
1142
|
+
{ basename: "ai-tools.extensions", relativeDir: "." }
|
|
1143
|
+
];
|
|
1144
|
+
for (const entry of sources) {
|
|
1145
|
+
const filePath = resolveConventionFile(path.join(moduleRoot, entry.relativeDir), entry.basename);
|
|
1146
|
+
if (!filePath) continue;
|
|
1147
|
+
const sourceFile = readSourceFile(filePath);
|
|
1148
|
+
if (!sourceFile) continue;
|
|
1149
|
+
const sourcePath = toPortableSourcePath(moduleRoot, sourceRoot, filePath);
|
|
1150
|
+
const extensionsArray = findArrayLiteralDeclaration(sourceFile, "aiAgentExtensions");
|
|
1151
|
+
if (extensionsArray) {
|
|
1152
|
+
for (const element of extensionsArray.elements) {
|
|
1153
|
+
const objectLiteral = ts.isObjectLiteralExpression(element) ? element : ts.isCallExpression(element) && element.arguments[0] && ts.isObjectLiteralExpression(element.arguments[0]) ? element.arguments[0] : null;
|
|
1154
|
+
if (!objectLiteral) continue;
|
|
1155
|
+
const targetAgentId = readStringPropertyInitializer(objectLiteral, "targetAgentId");
|
|
1156
|
+
if (!targetAgentId) continue;
|
|
1157
|
+
facts.push({
|
|
1158
|
+
kind: "ai-extension",
|
|
1159
|
+
id: `agent-extension:${targetAgentId}`,
|
|
1160
|
+
source: { sourcePath, exportName: "aiAgentExtensions", line: nodeLine(sourceFile, objectLiteral) },
|
|
1161
|
+
metadata: { target: "agent", mode: "extension", targetAgentId }
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
for (const { exportName, target } of AI_OVERRIDE_EXPORTS) {
|
|
1166
|
+
const overridesObject = findObjectLiteralDeclaration(sourceFile, exportName);
|
|
1167
|
+
if (!overridesObject) continue;
|
|
1168
|
+
for (const property of overridesObject.properties) {
|
|
1169
|
+
const overrideId = getPropertyName(property);
|
|
1170
|
+
if (!overrideId) continue;
|
|
1171
|
+
facts.push({
|
|
1172
|
+
kind: "ai-extension",
|
|
1173
|
+
id: `${target}-override:${overrideId}`,
|
|
1174
|
+
source: { sourcePath, exportName, line: nodeLine(sourceFile, property) },
|
|
1175
|
+
metadata: { target, mode: "override", overrideKey: overrideId }
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
return dedupeOwnedContractFacts(facts);
|
|
1181
|
+
}
|
|
1182
|
+
function extractGeneratorPluginContracts(moduleRoot, sourceRoot) {
|
|
1183
|
+
const filePath = resolveConventionFile(moduleRoot, "generators");
|
|
1184
|
+
if (!filePath) return { facts: [], unresolved: [] };
|
|
1185
|
+
const sourceFile = readSourceFile(filePath);
|
|
1186
|
+
if (!sourceFile) return { facts: [], unresolved: [] };
|
|
1187
|
+
const pluginsArray = findArrayLiteralDeclaration(sourceFile, "generatorPlugins");
|
|
1188
|
+
if (!pluginsArray) return { facts: [], unresolved: [] };
|
|
1189
|
+
const sourcePath = toPortableSourcePath(moduleRoot, sourceRoot, filePath);
|
|
1190
|
+
const initializers = buildVariableInitializerMap(sourceFile);
|
|
1191
|
+
const facts = [];
|
|
1192
|
+
const unresolved = [];
|
|
1193
|
+
let index = 0;
|
|
1194
|
+
for (const element of pluginsArray.elements) {
|
|
1195
|
+
const objectLiteral = resolveToObjectLiteral(element, initializers);
|
|
1196
|
+
if (!objectLiteral) {
|
|
1197
|
+
index += 1;
|
|
1198
|
+
continue;
|
|
1199
|
+
}
|
|
1200
|
+
const pluginId = readStringPropertyInitializer(objectLiteral, "id");
|
|
1201
|
+
if (!pluginId) {
|
|
1202
|
+
unresolved.push({
|
|
1203
|
+
code: "unresolved-static-contract",
|
|
1204
|
+
kind: "generator-plugin",
|
|
1205
|
+
id: `${sourcePath}#${index}`,
|
|
1206
|
+
source: { sourcePath, exportName: "generatorPlugins", line: nodeLine(sourceFile, objectLiteral) }
|
|
1207
|
+
});
|
|
1208
|
+
index += 1;
|
|
1209
|
+
continue;
|
|
1210
|
+
}
|
|
1211
|
+
const metadata = {};
|
|
1212
|
+
const conventionFile = readStringPropertyInitializer(objectLiteral, "conventionFile");
|
|
1213
|
+
if (conventionFile) metadata.conventionFile = conventionFile;
|
|
1214
|
+
const outputFileName = readStringPropertyInitializer(objectLiteral, "outputFileName");
|
|
1215
|
+
if (outputFileName) metadata.outputFileName = outputFileName;
|
|
1216
|
+
facts.push({
|
|
1217
|
+
kind: "generator-plugin",
|
|
1218
|
+
id: pluginId,
|
|
1219
|
+
source: { sourcePath, exportName: "generatorPlugins", line: nodeLine(sourceFile, objectLiteral) },
|
|
1220
|
+
...Object.keys(metadata).length > 0 ? { metadata } : {}
|
|
1221
|
+
});
|
|
1222
|
+
index += 1;
|
|
1223
|
+
}
|
|
1224
|
+
return { facts: dedupeOwnedContractFacts(facts), unresolved };
|
|
1225
|
+
}
|
|
1226
|
+
function compareSourceRefs(left, right) {
|
|
1227
|
+
return left.sourcePath.localeCompare(right.sourcePath) || (left.line ?? 0) - (right.line ?? 0) || (left.exportName ?? "").localeCompare(right.exportName ?? "");
|
|
1228
|
+
}
|
|
1229
|
+
function sameSourceRef(left, right) {
|
|
1230
|
+
return left.sourcePath === right.sourcePath && (left.line ?? null) === (right.line ?? null) && (left.exportName ?? null) === (right.exportName ?? null);
|
|
1231
|
+
}
|
|
1232
|
+
function provenanceCandidateOrderKey(candidate) {
|
|
1233
|
+
if (candidate.source) {
|
|
1234
|
+
return JSON.stringify([
|
|
1235
|
+
0,
|
|
1236
|
+
candidate.source.sourcePath,
|
|
1237
|
+
candidate.source.line ?? 0,
|
|
1238
|
+
candidate.source.exportName ?? ""
|
|
1239
|
+
]);
|
|
1240
|
+
}
|
|
1241
|
+
return JSON.stringify([1, candidate.factRef?.factSection ?? "", candidate.factRef?.factKey ?? ""]);
|
|
1242
|
+
}
|
|
1243
|
+
function buildProvenanceIndex(candidates) {
|
|
1244
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1245
|
+
for (const candidate of candidates) {
|
|
1246
|
+
const key = JSON.stringify([candidate.kind, candidate.id]);
|
|
1247
|
+
const group = grouped.get(key) ?? [];
|
|
1248
|
+
group.push(candidate);
|
|
1249
|
+
grouped.set(key, group);
|
|
1250
|
+
}
|
|
1251
|
+
const factSources = [];
|
|
1252
|
+
const diagnostics = [];
|
|
1253
|
+
for (const group of grouped.values()) {
|
|
1254
|
+
const sorted = [...group].sort(
|
|
1255
|
+
(left, right) => provenanceCandidateOrderKey(left).localeCompare(provenanceCandidateOrderKey(right))
|
|
1256
|
+
);
|
|
1257
|
+
const canonical = sorted[0];
|
|
1258
|
+
factSources.push(
|
|
1259
|
+
canonical.factRef ? {
|
|
1260
|
+
kind: canonical.kind,
|
|
1261
|
+
id: canonical.id,
|
|
1262
|
+
factRef: {
|
|
1263
|
+
factSection: canonical.factRef.factSection,
|
|
1264
|
+
...canonical.factRef.factKey === canonical.id ? {} : { factKey: canonical.factRef.factKey }
|
|
1265
|
+
}
|
|
1266
|
+
} : {
|
|
1267
|
+
kind: canonical.kind,
|
|
1268
|
+
id: canonical.id,
|
|
1269
|
+
source: { sourcePath: canonical.source.sourcePath }
|
|
1270
|
+
}
|
|
1271
|
+
);
|
|
1272
|
+
const canonicalKey = provenanceCandidateOrderKey(canonical);
|
|
1273
|
+
for (const rejected of sorted.slice(1)) {
|
|
1274
|
+
if (provenanceCandidateOrderKey(rejected) === canonicalKey) continue;
|
|
1275
|
+
diagnostics.push({
|
|
1276
|
+
code: "duplicate-source",
|
|
1277
|
+
kind: rejected.kind,
|
|
1278
|
+
id: rejected.id,
|
|
1279
|
+
...rejected.source ? { source: rejected.source } : {}
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
factSources.sort(
|
|
1284
|
+
(left, right) => left.kind.localeCompare(right.kind) || left.id.localeCompare(right.id)
|
|
1285
|
+
);
|
|
1286
|
+
diagnostics.sort(
|
|
1287
|
+
(left, right) => left.code.localeCompare(right.code) || left.kind.localeCompare(right.kind) || left.id.localeCompare(right.id) || compareSourceRefs(left.source ?? { sourcePath: "" }, right.source ?? { sourcePath: "" })
|
|
1288
|
+
);
|
|
1289
|
+
return { factSources, diagnostics };
|
|
1290
|
+
}
|
|
1291
|
+
function buildOwnedContracts(families) {
|
|
1292
|
+
const result = {};
|
|
1293
|
+
for (const [kind, facts] of families) {
|
|
1294
|
+
if (facts.length === 0) continue;
|
|
1295
|
+
result[kind] = facts.map((fact) => ({
|
|
1296
|
+
...fact,
|
|
1297
|
+
source: {
|
|
1298
|
+
sourcePath: fact.source.sourcePath,
|
|
1299
|
+
...fact.source.line ? { line: fact.source.line } : {}
|
|
1300
|
+
}
|
|
1301
|
+
}));
|
|
1302
|
+
}
|
|
1303
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
1304
|
+
}
|
|
1305
|
+
const CONTRIBUTION_KIND_TO_SOURCE_KIND = {
|
|
1306
|
+
subscriber: "subscriber"
|
|
1307
|
+
};
|
|
1308
|
+
const REGISTRY_TO_SOURCE_KIND = {
|
|
1309
|
+
vector: "vector",
|
|
1310
|
+
integration: "integration",
|
|
1311
|
+
payment: "integration",
|
|
1312
|
+
shipping: "integration",
|
|
1313
|
+
currency: "integration"
|
|
1314
|
+
};
|
|
728
1315
|
function extractModuleFacts(options) {
|
|
729
1316
|
const { moduleId, coreVersion = null, sourcePackage = null, sourceVersion = null } = options;
|
|
730
1317
|
const moduleRoot = options.moduleRoot ?? (options.coreSrcRoot ? path.join(options.coreSrcRoot, moduleId) : null);
|
|
@@ -741,6 +1328,9 @@ function extractModuleFacts(options) {
|
|
|
741
1328
|
const notificationsFilePath = resolveConventionFile(moduleRoot, "notifications");
|
|
742
1329
|
const cliFilePath = resolveConventionFile(moduleRoot, "cli");
|
|
743
1330
|
const indexFilePath = resolveConventionFile(moduleRoot, "index");
|
|
1331
|
+
const setupFilePath = resolveConventionFile(moduleRoot, "setup");
|
|
1332
|
+
const encryptionFilePath = resolveConventionFile(moduleRoot, "encryption");
|
|
1333
|
+
const portableOf = (filePath) => filePath ? toPortableSourcePath(moduleRoot, sourceRoot, filePath) : null;
|
|
744
1334
|
const warnings = [];
|
|
745
1335
|
const { title, description } = extractModuleMeta(indexFilePath);
|
|
746
1336
|
const customFieldEntityIds = collectCustomFieldEntityIds(ceFilePath);
|
|
@@ -750,7 +1340,11 @@ function extractModuleFacts(options) {
|
|
|
750
1340
|
const { source: registrySource, description: registryDescription } = resolveRegistrySource(options);
|
|
751
1341
|
const apiRouteSourcePaths = extractApiRouteSourcePaths(moduleId, moduleRoot, sourceRoot);
|
|
752
1342
|
const apiRoutes = extractApiRoutes(moduleId, registrySource, registryDescription, apiRouteSourcePaths, warnings);
|
|
753
|
-
const
|
|
1343
|
+
const diRegistrations = extractDiRegistrationFacts(diFilePath, portableOf(diFilePath) ?? "");
|
|
1344
|
+
const diTokens = deriveLegacyDiTokens(diRegistrations.facts);
|
|
1345
|
+
for (const legacyToken of extractDiTokens(diFilePath)) {
|
|
1346
|
+
if (!diTokens.includes(legacyToken)) diTokens.push(legacyToken);
|
|
1347
|
+
}
|
|
754
1348
|
const searchEntities = extractSearchEntities(searchFilePath, warnings);
|
|
755
1349
|
const notifications = extractNotifications(notificationsFilePath, warnings);
|
|
756
1350
|
const cli = extractCli(cliFilePath, warnings);
|
|
@@ -778,7 +1372,183 @@ function extractModuleFacts(options) {
|
|
|
778
1372
|
aiTools,
|
|
779
1373
|
aiAgents
|
|
780
1374
|
});
|
|
781
|
-
|
|
1375
|
+
const indexSourcePath = portableOf(indexFilePath);
|
|
1376
|
+
const diSourcePath = portableOf(diFilePath);
|
|
1377
|
+
const moduleMetadataFact = extractModuleMetadataContract(indexFilePath, moduleId, indexSourcePath);
|
|
1378
|
+
const commandCandidates = extractCommandContractCandidates(moduleId, moduleRoot, sourceRoot);
|
|
1379
|
+
const commandFacts = dedupeOwnedContractFacts(
|
|
1380
|
+
commandCandidates.map((candidate) => ({
|
|
1381
|
+
kind: "command",
|
|
1382
|
+
id: candidate.id,
|
|
1383
|
+
source: { sourcePath: candidate.sourcePath }
|
|
1384
|
+
}))
|
|
1385
|
+
);
|
|
1386
|
+
const workers = extractWorkerContracts(moduleId, moduleRoot, sourceRoot);
|
|
1387
|
+
const pageMiddleware = extractPageMiddlewareContracts(moduleRoot, sourceRoot);
|
|
1388
|
+
const setupFact = extractSetupContract(moduleId, setupFilePath, portableOf(setupFilePath));
|
|
1389
|
+
const encryptionFacts = extractEncryptionContracts(encryptionFilePath, portableOf(encryptionFilePath));
|
|
1390
|
+
const customEntityFacts = extractCustomEntityContracts(ceFilePath, portableOf(ceFilePath));
|
|
1391
|
+
const aiExtensionFacts = extractAiExtensionContracts(moduleRoot, sourceRoot);
|
|
1392
|
+
const generatorPlugins = extractGeneratorPluginContracts(moduleRoot, sourceRoot);
|
|
1393
|
+
const diContractFacts = dedupeOwnedContractFacts(
|
|
1394
|
+
diRegistrations.facts.map((fact) => ({
|
|
1395
|
+
kind: "di-registration",
|
|
1396
|
+
id: fact.token,
|
|
1397
|
+
source: fact.source,
|
|
1398
|
+
metadata: {
|
|
1399
|
+
registrationKind: fact.registrationKind,
|
|
1400
|
+
...fact.providerSymbol ? { providerSymbol: fact.providerSymbol } : {},
|
|
1401
|
+
...fact.lifetime ? { lifetime: fact.lifetime } : {},
|
|
1402
|
+
...fact.injectionMode ? { injectionMode: fact.injectionMode } : {}
|
|
1403
|
+
}
|
|
1404
|
+
}))
|
|
1405
|
+
);
|
|
1406
|
+
const ownedContracts = buildOwnedContracts([
|
|
1407
|
+
["module-metadata", moduleMetadataFact ? [moduleMetadataFact] : []],
|
|
1408
|
+
["command", commandFacts],
|
|
1409
|
+
["worker", workers.facts],
|
|
1410
|
+
["page-middleware", pageMiddleware.facts],
|
|
1411
|
+
["setup", setupFact ? [setupFact] : []],
|
|
1412
|
+
["encryption", encryptionFacts],
|
|
1413
|
+
["di-registration", diContractFacts],
|
|
1414
|
+
["custom-entity", customEntityFacts],
|
|
1415
|
+
["ai-extension", aiExtensionFacts],
|
|
1416
|
+
["generator-plugin", generatorPlugins.facts]
|
|
1417
|
+
]);
|
|
1418
|
+
const provenanceCandidates = [];
|
|
1419
|
+
const pushSource = (kind, id, source) => {
|
|
1420
|
+
provenanceCandidates.push({ kind, id, source });
|
|
1421
|
+
};
|
|
1422
|
+
const pushRef = (kind, id, source, factSection, factKey) => {
|
|
1423
|
+
provenanceCandidates.push({ kind, id, source, factRef: { factSection, factKey } });
|
|
1424
|
+
};
|
|
1425
|
+
const pushOwnedRefs = (kind, facts2) => {
|
|
1426
|
+
for (const fact of facts2) pushRef(kind, fact.id, fact.source, `ownedContracts.${kind}`, fact.id);
|
|
1427
|
+
};
|
|
1428
|
+
if (moduleMetadataFact) {
|
|
1429
|
+
pushRef(
|
|
1430
|
+
"module-metadata",
|
|
1431
|
+
moduleId,
|
|
1432
|
+
moduleMetadataFact.source,
|
|
1433
|
+
"ownedContracts.module-metadata",
|
|
1434
|
+
moduleMetadataFact.id
|
|
1435
|
+
);
|
|
1436
|
+
}
|
|
1437
|
+
if (entitiesFilePath) {
|
|
1438
|
+
const entitiesSourcePath = portableOf(entitiesFilePath);
|
|
1439
|
+
for (const entity of entities) {
|
|
1440
|
+
pushSource("entity", entity.id, { sourcePath: entitiesSourcePath, exportName: entity.class });
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
if (eventsFilePath) {
|
|
1444
|
+
const eventsSourcePath = portableOf(eventsFilePath);
|
|
1445
|
+
for (const event of events) pushSource("event", event.id, { sourcePath: eventsSourcePath, exportName: "events" });
|
|
1446
|
+
}
|
|
1447
|
+
if (aclFilePath) {
|
|
1448
|
+
const aclSourcePath = portableOf(aclFilePath);
|
|
1449
|
+
for (const feature of aclFeatures) pushSource("acl-feature", feature, { sourcePath: aclSourcePath, exportName: "features" });
|
|
1450
|
+
}
|
|
1451
|
+
for (const route of apiRoutes) {
|
|
1452
|
+
if (route.sourcePath) {
|
|
1453
|
+
pushRef("api-route", route.path, { sourcePath: route.sourcePath }, "apiRoutes", route.path);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
if (searchFilePath) {
|
|
1457
|
+
const searchSourcePath = portableOf(searchFilePath);
|
|
1458
|
+
for (const entityId of searchEntities) pushSource("search", entityId, { sourcePath: searchSourcePath, exportName: "searchConfig" });
|
|
1459
|
+
}
|
|
1460
|
+
if (notificationsFilePath) {
|
|
1461
|
+
const notificationsSourcePath = portableOf(notificationsFilePath);
|
|
1462
|
+
for (const id of notifications) pushSource("notification", id, { sourcePath: notificationsSourcePath });
|
|
1463
|
+
}
|
|
1464
|
+
for (const command of cliCommands) {
|
|
1465
|
+
pushRef("cli-command", command.command, { sourcePath: command.sourcePath }, "cliCommands", command.command);
|
|
1466
|
+
}
|
|
1467
|
+
for (const page of backendPages) {
|
|
1468
|
+
pushRef("backend-page", page.path, { sourcePath: page.sourcePath }, "backendPages", page.path);
|
|
1469
|
+
}
|
|
1470
|
+
for (const page of frontendPages) {
|
|
1471
|
+
pushRef("frontend-page", page.path, { sourcePath: page.sourcePath }, "frontendPages", page.path);
|
|
1472
|
+
}
|
|
1473
|
+
for (const tool of aiTools) pushRef("ai-tool", tool.name, { sourcePath: tool.sourcePath }, "aiTools", tool.name);
|
|
1474
|
+
for (const agent of aiAgents) pushRef("ai-agent", agent.id, { sourcePath: agent.sourcePath }, "aiAgents", agent.id);
|
|
1475
|
+
for (const candidate of commandCandidates) {
|
|
1476
|
+
pushRef("command", candidate.id, { sourcePath: candidate.sourcePath }, "ownedContracts.command", candidate.id);
|
|
1477
|
+
}
|
|
1478
|
+
pushOwnedRefs("worker", workers.facts);
|
|
1479
|
+
pushOwnedRefs("page-middleware", pageMiddleware.facts);
|
|
1480
|
+
if (setupFact) pushOwnedRefs("setup", [setupFact]);
|
|
1481
|
+
pushOwnedRefs("encryption", encryptionFacts);
|
|
1482
|
+
for (const fact of diRegistrations.facts) {
|
|
1483
|
+
pushRef("di-registration", fact.token, fact.source, "ownedContracts.di-registration", fact.token);
|
|
1484
|
+
}
|
|
1485
|
+
pushOwnedRefs("custom-entity", customEntityFacts);
|
|
1486
|
+
pushOwnedRefs("ai-extension", aiExtensionFacts);
|
|
1487
|
+
pushOwnedRefs("generator-plugin", generatorPlugins.facts);
|
|
1488
|
+
const contributionRef = (contributionId) => ({
|
|
1489
|
+
factSection: "extensionSurfaces.contributions",
|
|
1490
|
+
factKey: contributionId
|
|
1491
|
+
});
|
|
1492
|
+
for (const contribution of extensionSurfaces.contributions) {
|
|
1493
|
+
const contributionSource = {
|
|
1494
|
+
sourcePath: contribution.source.path,
|
|
1495
|
+
...contribution.source.symbol ? { exportName: contribution.source.symbol } : {}
|
|
1496
|
+
};
|
|
1497
|
+
const ref = contributionRef(contribution.id);
|
|
1498
|
+
if (contribution.kind === "subscriber") {
|
|
1499
|
+
pushRef("subscriber", contribution.id, contributionSource, ref.factSection, ref.factKey);
|
|
1500
|
+
continue;
|
|
1501
|
+
}
|
|
1502
|
+
if (contribution.kind === "specialized-registry") {
|
|
1503
|
+
const details = contribution.details;
|
|
1504
|
+
const sourceKind = details.registry ? REGISTRY_TO_SOURCE_KIND[details.registry] : void 0;
|
|
1505
|
+
if (sourceKind) {
|
|
1506
|
+
pushRef(
|
|
1507
|
+
sourceKind,
|
|
1508
|
+
details.registryId ?? contribution.id,
|
|
1509
|
+
contributionSource,
|
|
1510
|
+
ref.factSection,
|
|
1511
|
+
ref.factKey
|
|
1512
|
+
);
|
|
1513
|
+
}
|
|
1514
|
+
continue;
|
|
1515
|
+
}
|
|
1516
|
+
const mapped = CONTRIBUTION_KIND_TO_SOURCE_KIND[contribution.kind];
|
|
1517
|
+
if (mapped) pushRef(mapped, contribution.id, contributionSource, ref.factSection, ref.factKey);
|
|
1518
|
+
}
|
|
1519
|
+
for (const host of extensionSurfaces.hosts) {
|
|
1520
|
+
provenanceCandidates.push({
|
|
1521
|
+
kind: "extension-host",
|
|
1522
|
+
id: host.key,
|
|
1523
|
+
...host.source.kind === "fact-ref" ? {} : { source: { sourcePath: host.source.path } },
|
|
1524
|
+
factRef: { factSection: "extensionSurfaces.hosts", factKey: host.key }
|
|
1525
|
+
});
|
|
1526
|
+
}
|
|
1527
|
+
for (const contribution of extensionSurfaces.contributions) {
|
|
1528
|
+
pushRef(
|
|
1529
|
+
"extension-contribution",
|
|
1530
|
+
contribution.id,
|
|
1531
|
+
{ sourcePath: contribution.source.path },
|
|
1532
|
+
"extensionSurfaces.contributions",
|
|
1533
|
+
contribution.id
|
|
1534
|
+
);
|
|
1535
|
+
}
|
|
1536
|
+
const provenance = buildProvenanceIndex(provenanceCandidates);
|
|
1537
|
+
const factDiagnostics = [
|
|
1538
|
+
...provenance.diagnostics,
|
|
1539
|
+
...workers.unresolved,
|
|
1540
|
+
...pageMiddleware.unresolved,
|
|
1541
|
+
...generatorPlugins.unresolved,
|
|
1542
|
+
...diRegistrations.unresolvedTokens.map((token) => ({
|
|
1543
|
+
code: "unresolved-static-contract",
|
|
1544
|
+
kind: "di-registration",
|
|
1545
|
+
id: token,
|
|
1546
|
+
...diSourcePath ? { source: { sourcePath: diSourcePath, exportName: token } } : {}
|
|
1547
|
+
}))
|
|
1548
|
+
].sort(
|
|
1549
|
+
(left, right) => left.code.localeCompare(right.code) || left.kind.localeCompare(right.kind) || left.id.localeCompare(right.id)
|
|
1550
|
+
);
|
|
1551
|
+
const facts = {
|
|
782
1552
|
module: moduleId,
|
|
783
1553
|
title,
|
|
784
1554
|
description,
|
|
@@ -801,8 +1571,17 @@ function extractModuleFacts(options) {
|
|
|
801
1571
|
aiTools,
|
|
802
1572
|
aiAgents,
|
|
803
1573
|
extensionSurfaces,
|
|
1574
|
+
...provenance.factSources.length > 0 ? { factSources: provenance.factSources } : {},
|
|
1575
|
+
...ownedContracts ? { ownedContracts } : {},
|
|
1576
|
+
...factDiagnostics.length > 0 ? { factDiagnostics } : {},
|
|
804
1577
|
warnings
|
|
805
1578
|
};
|
|
1579
|
+
const overrides = collectModuleOverrideTargets(facts, buildFactSourceLookup(facts));
|
|
1580
|
+
if (overrides.overrideTargets.length > 0) facts.overrideTargets = overrides.overrideTargets;
|
|
1581
|
+
if (overrides.overrideTargetDiagnostics.length > 0) {
|
|
1582
|
+
facts.overrideTargetDiagnostics = overrides.overrideTargetDiagnostics;
|
|
1583
|
+
}
|
|
1584
|
+
return facts;
|
|
806
1585
|
}
|
|
807
1586
|
const EMPTY_SECTION_MARKER = "_none_";
|
|
808
1587
|
function renderVersionStamp(coreVersion, sourcePackage, sourceVersion) {
|
|
@@ -812,27 +1591,27 @@ function renderVersionStamp(coreVersion, sourcePackage, sourceVersion) {
|
|
|
812
1591
|
const version = coreVersion && coreVersion.length > 0 ? coreVersion : "<unknown>";
|
|
813
1592
|
return `<!-- generated from @open-mercato/core ${version} \u2014 R1 staleness stamp -->`;
|
|
814
1593
|
}
|
|
815
|
-
function renderEntitiesSection(entities) {
|
|
1594
|
+
function renderEntitiesSection(entities, lookup) {
|
|
816
1595
|
if (entities.length === 0) return `## Entities
|
|
817
1596
|
|
|
818
1597
|
${EMPTY_SECTION_MARKER}`;
|
|
819
|
-
const header = "| Entity ID | Class | Table | Editable | CustomFields |";
|
|
820
|
-
const divider = "
|
|
1598
|
+
const header = "| Entity ID | Class | Table | Editable | CustomFields | Source |";
|
|
1599
|
+
const divider = "|---|---|---|---|---|---|";
|
|
821
1600
|
const rows = entities.map(
|
|
822
|
-
(entity) => `| ${entity.id} | ${entity.class} | ${entity.table} | ${entity.editable ? "yes" : "no"} | ${entity.customFields ? "yes" : "no"} |`
|
|
1601
|
+
(entity) => `| ${entity.id} | ${entity.class} | ${entity.table} | ${entity.editable ? "yes" : "no"} | ${entity.customFields ? "yes" : "no"} | ${renderLookedUpSource(lookup, "entity", entity.id)} |`
|
|
823
1602
|
);
|
|
824
1603
|
return ["## Entities", "", header, divider, ...rows].join("\n");
|
|
825
1604
|
}
|
|
826
|
-
function renderEventsSection(events) {
|
|
1605
|
+
function renderEventsSection(events, lookup) {
|
|
827
1606
|
const heading = `## Events (${events.length})`;
|
|
828
1607
|
if (events.length === 0) return `${heading}
|
|
829
1608
|
|
|
830
1609
|
${EMPTY_SECTION_MARKER}`;
|
|
831
|
-
const header = "| ID | Category | Entity | Browser transport |";
|
|
832
|
-
const divider = "
|
|
1610
|
+
const header = "| ID | Category | Entity | Browser transport | Source |";
|
|
1611
|
+
const divider = "|---|---|---|---|---|";
|
|
833
1612
|
const rows = events.map((event) => {
|
|
834
1613
|
const transports = [event.clientBroadcast ? "client" : null, event.portalBroadcast ? "portal" : null].filter((value) => value !== null);
|
|
835
|
-
return `| ${event.id} | ${event.category ?? "\u2014"} | ${event.entity ?? "\u2014"} | ${transports.join(", ") || "\u2014"} |`;
|
|
1614
|
+
return `| ${event.id} | ${event.category ?? "\u2014"} | ${event.entity ?? "\u2014"} | ${transports.join(", ") || "\u2014"} | ${renderLookedUpSource(lookup, "event", event.id)} |`;
|
|
836
1615
|
});
|
|
837
1616
|
return [heading, "", header, divider, ...rows].join("\n");
|
|
838
1617
|
}
|
|
@@ -844,9 +1623,57 @@ ${EMPTY_SECTION_MARKER}`;
|
|
|
844
1623
|
|
|
845
1624
|
${values.join(" \xB7 ")}`;
|
|
846
1625
|
}
|
|
1626
|
+
function renderSourceLinkedListSection(heading, values, lookup, kind, idColumnLabel) {
|
|
1627
|
+
if (values.length === 0) return `${heading}
|
|
1628
|
+
|
|
1629
|
+
${EMPTY_SECTION_MARKER}`;
|
|
1630
|
+
const rows = values.map((value) => `| ${value} | ${renderLookedUpSource(lookup, kind, value)} |`);
|
|
1631
|
+
return [heading, "", `| ${idColumnLabel} | Source |`, "|---|---|", ...rows].join("\n");
|
|
1632
|
+
}
|
|
847
1633
|
function renderSourceLink(sourcePath) {
|
|
848
1634
|
return `[${sourcePath}](../../../${sourcePath})`;
|
|
849
1635
|
}
|
|
1636
|
+
function renderSourceRefLink(source) {
|
|
1637
|
+
const anchor = source.line ? `#L${source.line}` : "";
|
|
1638
|
+
const label = source.line ? `${source.sourcePath}:${source.line}` : source.sourcePath;
|
|
1639
|
+
return `[${label}](../../../${source.sourcePath}${anchor})`;
|
|
1640
|
+
}
|
|
1641
|
+
function renderLookedUpSource(lookup, kind, id) {
|
|
1642
|
+
const source = lookup(kind, id);
|
|
1643
|
+
return source ? renderSourceRefLink(source) : "\u2014";
|
|
1644
|
+
}
|
|
1645
|
+
function renderSafeScalar(value) {
|
|
1646
|
+
if (value === void 0 || value === null) return "\u2014";
|
|
1647
|
+
if (Array.isArray(value)) return value.length > 0 ? value.map((entry) => String(entry)).join(", ") : "\u2014";
|
|
1648
|
+
return String(value);
|
|
1649
|
+
}
|
|
1650
|
+
function renderOwnedContractMetadata(metadata) {
|
|
1651
|
+
if (!metadata) return "\u2014";
|
|
1652
|
+
const keys = Object.keys(metadata).sort((left, right) => left.localeCompare(right));
|
|
1653
|
+
if (keys.length === 0) return "\u2014";
|
|
1654
|
+
return keys.map((key) => `${key}=${renderSafeScalar(metadata[key])}`).join("; ");
|
|
1655
|
+
}
|
|
1656
|
+
function renderOwnedContractSection(heading, facts) {
|
|
1657
|
+
if (!facts || facts.length === 0) return "";
|
|
1658
|
+
const rows = facts.map(
|
|
1659
|
+
(fact) => `| ${fact.id} | ${renderOwnedContractMetadata(fact.metadata)} | ${renderSourceRefLink(fact.source)} |`
|
|
1660
|
+
);
|
|
1661
|
+
return [heading, "", "| ID | Metadata | Source |", "|---|---|---|", ...rows].join("\n");
|
|
1662
|
+
}
|
|
1663
|
+
function renderDiRegistrationsSection(facts) {
|
|
1664
|
+
if (!facts || facts.length === 0) return "";
|
|
1665
|
+
const rows = facts.map((fact) => {
|
|
1666
|
+
const metadata = fact.metadata ?? {};
|
|
1667
|
+
return `| ${fact.id} | ${renderSafeScalar(metadata.registrationKind)} | ${renderSafeScalar(metadata.lifetime)} | ${renderSafeScalar(metadata.injectionMode)} | ${renderSafeScalar(metadata.providerSymbol)} | ${renderSourceRefLink(fact.source)} |`;
|
|
1668
|
+
});
|
|
1669
|
+
return [
|
|
1670
|
+
"## DI registrations (rich)",
|
|
1671
|
+
"",
|
|
1672
|
+
"| Token | Kind | Lifetime | Injection | Provider | Source |",
|
|
1673
|
+
"|---|---|---|---|---|---|",
|
|
1674
|
+
...rows
|
|
1675
|
+
].join("\n");
|
|
1676
|
+
}
|
|
850
1677
|
function describeAuthRule(rule) {
|
|
851
1678
|
if (!rule) return "public";
|
|
852
1679
|
if (rule.requireFeatures && rule.requireFeatures.length > 0) return rule.requireFeatures.join(", ");
|
|
@@ -891,19 +1718,23 @@ function renderHostTokensSection(hostTokens) {
|
|
|
891
1718
|
function renderExtensionHostContext(host) {
|
|
892
1719
|
return host.contextContract ?? host.runtimeContract ?? host.scopeContract ?? "\u2014";
|
|
893
1720
|
}
|
|
1721
|
+
function renderExtensionHostSource(host) {
|
|
1722
|
+
if (host.source.kind === "fact-ref") return `${host.source.factSection}:${host.source.factKey}`;
|
|
1723
|
+
return renderSourceRefLink({ sourcePath: host.source.path });
|
|
1724
|
+
}
|
|
894
1725
|
function renderExtensionHostsSection(extensionSurfaces) {
|
|
895
1726
|
const boundHosts = extensionSurfaces.hosts.filter((host) => host.bound);
|
|
896
1727
|
if (boundHosts.length === 0) return `## UMES hosts
|
|
897
1728
|
|
|
898
1729
|
${EMPTY_SECTION_MARKER}`;
|
|
899
1730
|
const rows = boundHosts.map(
|
|
900
|
-
(host) => `| ${host.id} | ${host.family} | ${host.capabilities.join(", ") || "\u2014"} | ${renderExtensionHostContext(host)} | ${host.stability.toUpperCase()} |`
|
|
1731
|
+
(host) => `| ${host.id} | ${host.family} | ${host.capabilities.join(", ") || "\u2014"} | ${renderExtensionHostContext(host)} | ${host.stability.toUpperCase()} | ${renderExtensionHostSource(host)} |`
|
|
901
1732
|
);
|
|
902
1733
|
return [
|
|
903
1734
|
"## UMES hosts",
|
|
904
1735
|
"",
|
|
905
|
-
"| ID / pattern | Family | Supports | Context | Stability |",
|
|
906
|
-
"
|
|
1736
|
+
"| ID / pattern | Family | Supports | Context | Stability | Source |",
|
|
1737
|
+
"|---|---|---|---|---|---|",
|
|
907
1738
|
...rows
|
|
908
1739
|
].join("\n");
|
|
909
1740
|
}
|
|
@@ -931,13 +1762,14 @@ ${EMPTY_SECTION_MARKER}`;
|
|
|
931
1762
|
const targets = contribution.targets.map((entry) => entry.id).join(", ");
|
|
932
1763
|
const resolution = contribution.targets.map((entry) => entry.resolution).join(", ");
|
|
933
1764
|
const phases = [...contribution.phases ?? [], ...contribution.operations ?? []].join(", ") || "\u2014";
|
|
934
|
-
|
|
1765
|
+
const source = renderSourceRefLink({ sourcePath: contribution.source.path });
|
|
1766
|
+
return `| ${contribution.id} | ${contribution.kind} | ${targets || "\u2014"} | ${phases} | ${compactContributionDetails(contribution)} | ${resolution || "\u2014"} | ${source} |`;
|
|
935
1767
|
});
|
|
936
1768
|
return [
|
|
937
1769
|
"## UMES contributions",
|
|
938
1770
|
"",
|
|
939
|
-
"| ID | Kind | Target | Phase / operations | Contract | Resolution |",
|
|
940
|
-
"
|
|
1771
|
+
"| ID | Kind | Target | Phase / operations | Contract | Resolution | Source |",
|
|
1772
|
+
"|---|---|---|---|---|---|---|",
|
|
941
1773
|
...rows
|
|
942
1774
|
].join("\n");
|
|
943
1775
|
}
|
|
@@ -950,18 +1782,108 @@ function renderExtensionDiagnosticsSection(extensionSurfaces) {
|
|
|
950
1782
|
];
|
|
951
1783
|
return ["## UMES diagnostics", "", ...diagnostics].join("\n");
|
|
952
1784
|
}
|
|
1785
|
+
function renderExtensionTargetRef(target) {
|
|
1786
|
+
const method = target.method ? ` ${target.method}` : "";
|
|
1787
|
+
const owner = target.moduleId ? ` @${target.moduleId}` : "";
|
|
1788
|
+
return `${target.kind}:${target.id}${method}${owner}`;
|
|
1789
|
+
}
|
|
1790
|
+
function renderActivationBindingsSection(extensionSurfaces) {
|
|
1791
|
+
const activations = extensionSurfaces.activations ?? [];
|
|
1792
|
+
if (activations.length === 0) return `## Active extension bindings
|
|
1793
|
+
|
|
1794
|
+
${EMPTY_SECTION_MARKER}`;
|
|
1795
|
+
const rows = activations.map((activation) => {
|
|
1796
|
+
const phases = activation.phases && activation.phases.length > 0 ? activation.phases.join(", ") : "\u2014";
|
|
1797
|
+
const source = activation.source ? renderSourceRefLink(activation.source) : "\u2014";
|
|
1798
|
+
const bridge = activation.bridge ? `${activation.bridge.factSection}:${activation.bridge.factKey}` : "\u2014";
|
|
1799
|
+
return `| ${activation.id} | ${activation.kind} | ${renderExtensionTargetRef(activation.host)} | ${activation.contributionKinds.join(", ") || "\u2014"} | ${phases} | ${bridge} | ${source} |`;
|
|
1800
|
+
});
|
|
1801
|
+
return [
|
|
1802
|
+
"## Active extension bindings",
|
|
1803
|
+
"",
|
|
1804
|
+
"| Activation | Kind | Host | Contribution kinds | Phases | Bridge | Source |",
|
|
1805
|
+
"|---|---|---|---|---|---|---|",
|
|
1806
|
+
...rows
|
|
1807
|
+
].join("\n");
|
|
1808
|
+
}
|
|
1809
|
+
function renderIncomingContributionsSection(extensionSurfaces) {
|
|
1810
|
+
const incoming = extensionSurfaces.incoming ?? [];
|
|
1811
|
+
if (incoming.length === 0) return `## Incoming installed contributions
|
|
1812
|
+
|
|
1813
|
+
${EMPTY_SECTION_MARKER}`;
|
|
1814
|
+
const rows = incoming.map((entry) => {
|
|
1815
|
+
const activation = entry.activationId ?? "\u2014";
|
|
1816
|
+
const source = entry.source ? renderSourceRefLink(entry.source) : "\u2014";
|
|
1817
|
+
return `| ${entry.contributorModuleId} | ${entry.contributionKind} | ${renderExtensionTargetRef(entry.target)} | ${entry.resolution} | ${activation} | ${entry.contributionId} \xB7 ${source} |`;
|
|
1818
|
+
});
|
|
1819
|
+
return [
|
|
1820
|
+
"## Incoming installed contributions",
|
|
1821
|
+
"",
|
|
1822
|
+
"| Contributor | Kind | Target | Resolution | Activation | Contribution \xB7 Source |",
|
|
1823
|
+
"|---|---|---|---|---|---|",
|
|
1824
|
+
...rows
|
|
1825
|
+
].join("\n");
|
|
1826
|
+
}
|
|
1827
|
+
function renderContributionResolutionsSection(extensionSurfaces, lookup) {
|
|
1828
|
+
const resolutions = extensionSurfaces.contributionResolutions ?? [];
|
|
1829
|
+
if (resolutions.length === 0) return `## Contribution resolutions
|
|
1830
|
+
|
|
1831
|
+
${EMPTY_SECTION_MARKER}`;
|
|
1832
|
+
const rows = resolutions.map((resolution) => {
|
|
1833
|
+
const activations = resolution.activationIds.length > 0 ? resolution.activationIds.join(", ") : "\u2014";
|
|
1834
|
+
const source = renderLookedUpSource(lookup, "extension-contribution", resolution.contributionId);
|
|
1835
|
+
return `| ${resolution.contributionId} | ${renderExtensionTargetRef(resolution.target)} | ${resolution.resolution} | ${activations} | ${source} |`;
|
|
1836
|
+
});
|
|
1837
|
+
return [
|
|
1838
|
+
"## Contribution resolutions",
|
|
1839
|
+
"",
|
|
1840
|
+
"| Contribution | Target | Resolution | Activations | Source |",
|
|
1841
|
+
"|---|---|---|---|---|",
|
|
1842
|
+
...rows
|
|
1843
|
+
].join("\n");
|
|
1844
|
+
}
|
|
1845
|
+
function renderOverrideTargetPath(target) {
|
|
1846
|
+
let display = "overrides";
|
|
1847
|
+
for (let index = 0; index < target.path.length; index += 1) {
|
|
1848
|
+
const segment = target.path[index];
|
|
1849
|
+
const isKeyedTerminal = target.key !== void 0 && index === target.path.length - 1;
|
|
1850
|
+
display += isKeyedTerminal ? `[${JSON.stringify(segment)}]` : `.${segment}`;
|
|
1851
|
+
}
|
|
1852
|
+
return display;
|
|
1853
|
+
}
|
|
1854
|
+
function renderOverrideTargetsSection(targets) {
|
|
1855
|
+
if (!targets || targets.length === 0) return `## Exact override targets
|
|
1856
|
+
|
|
1857
|
+
${EMPTY_SECTION_MARKER}`;
|
|
1858
|
+
const header = "| Domain | Path / key | Supported modes | Referenced fact | Source |";
|
|
1859
|
+
const divider = "|---|---|---|---|---|";
|
|
1860
|
+
const rows = targets.map((target) => {
|
|
1861
|
+
const path2 = `\`${renderOverrideTargetPath(target)}\``;
|
|
1862
|
+
const modes = target.modes.join(", ") || "\u2014";
|
|
1863
|
+
const fact = `${target.factRef.factSection}:${target.factRef.factKey}`;
|
|
1864
|
+
return `| ${target.domain} | ${path2} | ${modes} | ${fact} | ${renderSourceRefLink(target.source)} |`;
|
|
1865
|
+
});
|
|
1866
|
+
return ["## Exact override targets", "", header, divider, ...rows].join("\n");
|
|
1867
|
+
}
|
|
953
1868
|
function renderModuleFactsMarkdown(facts) {
|
|
954
1869
|
const extensionSurfaces = facts.extensionSurfaces ?? { hosts: [], contributions: [], unresolved: [] };
|
|
1870
|
+
const lookup = buildFactSourceLookup(facts);
|
|
955
1871
|
const sections = [
|
|
956
1872
|
`# ${facts.module} \u2014 module facts (generated, do not edit)`,
|
|
957
1873
|
renderVersionStamp(facts.coreVersion, facts.sourcePackage, facts.sourceVersion),
|
|
958
1874
|
`Source root: ${renderSourceLink(facts.sourceRoot)}`,
|
|
959
1875
|
"",
|
|
960
|
-
renderEntitiesSection(facts.entities),
|
|
1876
|
+
renderEntitiesSection(facts.entities, lookup),
|
|
961
1877
|
"",
|
|
962
|
-
renderEventsSection(facts.events),
|
|
1878
|
+
renderEventsSection(facts.events, lookup),
|
|
963
1879
|
"",
|
|
964
|
-
|
|
1880
|
+
renderSourceLinkedListSection(
|
|
1881
|
+
`## ACL features (${facts.aclFeatures.length})`,
|
|
1882
|
+
facts.aclFeatures,
|
|
1883
|
+
lookup,
|
|
1884
|
+
"acl-feature",
|
|
1885
|
+
"Feature"
|
|
1886
|
+
),
|
|
965
1887
|
"",
|
|
966
1888
|
renderApiRoutesSection(facts.apiRoutes),
|
|
967
1889
|
"",
|
|
@@ -969,9 +1891,9 @@ function renderModuleFactsMarkdown(facts) {
|
|
|
969
1891
|
"",
|
|
970
1892
|
renderLinkedFactsSection("## Frontend pages", facts.frontendPages.map((page) => ({ label: page.path, sourcePath: page.sourcePath }))),
|
|
971
1893
|
"",
|
|
972
|
-
|
|
1894
|
+
renderSourceLinkedListSection("## DI service tokens", facts.diTokens, lookup, "di-registration", "Token"),
|
|
973
1895
|
"",
|
|
974
|
-
|
|
1896
|
+
renderSourceLinkedListSection("## Search entities", facts.searchEntities, lookup, "search", "Entity ID"),
|
|
975
1897
|
"",
|
|
976
1898
|
renderHostTokensSection(facts.hostTokens),
|
|
977
1899
|
"",
|
|
@@ -979,17 +1901,41 @@ function renderModuleFactsMarkdown(facts) {
|
|
|
979
1901
|
"",
|
|
980
1902
|
renderExtensionContributionsSection(extensionSurfaces),
|
|
981
1903
|
"",
|
|
1904
|
+
renderActivationBindingsSection(extensionSurfaces),
|
|
1905
|
+
"",
|
|
1906
|
+
renderIncomingContributionsSection(extensionSurfaces),
|
|
1907
|
+
"",
|
|
1908
|
+
renderContributionResolutionsSection(extensionSurfaces, lookup),
|
|
1909
|
+
"",
|
|
982
1910
|
renderExtensionDiagnosticsSection(extensionSurfaces),
|
|
983
1911
|
"",
|
|
984
|
-
|
|
1912
|
+
renderSourceLinkedListSection("## Notifications", facts.notifications, lookup, "notification", "Notification ID"),
|
|
985
1913
|
"",
|
|
986
1914
|
renderLinkedFactsSection("## CLI commands", facts.cliCommands.map((command) => ({ label: command.command, sourcePath: command.sourcePath }))),
|
|
987
1915
|
"",
|
|
988
1916
|
renderLinkedFactsSection("## AI tools / MCP capabilities", facts.aiTools.map((tool) => ({ label: tool.name, sourcePath: tool.sourcePath }))),
|
|
989
1917
|
"",
|
|
990
1918
|
renderLinkedFactsSection("## AI agents", facts.aiAgents.map((agent) => ({ label: agent.id, sourcePath: agent.sourcePath }))),
|
|
1919
|
+
"",
|
|
1920
|
+
renderOverrideTargetsSection(facts.overrideTargets),
|
|
991
1921
|
""
|
|
992
1922
|
];
|
|
1923
|
+
const owned = facts.ownedContracts ?? {};
|
|
1924
|
+
const ownedSections = [
|
|
1925
|
+
renderOwnedContractSection("## Owned contract \u2014 module metadata", owned["module-metadata"]),
|
|
1926
|
+
renderOwnedContractSection("## Domain commands", owned.command),
|
|
1927
|
+
renderOwnedContractSection("## Workers", owned.worker),
|
|
1928
|
+
renderOwnedContractSection("## Page middleware", owned["page-middleware"]),
|
|
1929
|
+
renderOwnedContractSection("## Setup", owned.setup),
|
|
1930
|
+
renderOwnedContractSection("## Encryption", owned.encryption),
|
|
1931
|
+
renderDiRegistrationsSection(owned["di-registration"]),
|
|
1932
|
+
renderOwnedContractSection("## Custom entities", owned["custom-entity"]),
|
|
1933
|
+
renderOwnedContractSection("## AI extensions", owned["ai-extension"]),
|
|
1934
|
+
renderOwnedContractSection("## Generator plugins", owned["generator-plugin"])
|
|
1935
|
+
].filter((section) => section.length > 0);
|
|
1936
|
+
for (const section of ownedSections) {
|
|
1937
|
+
sections.push(section, "");
|
|
1938
|
+
}
|
|
993
1939
|
return sections.join("\n");
|
|
994
1940
|
}
|
|
995
1941
|
function toModuleFactsJsonEntry(facts) {
|
|
@@ -1020,7 +1966,12 @@ function toModuleFactsJsonEntry(facts) {
|
|
|
1020
1966
|
cliCommands: facts.cliCommands,
|
|
1021
1967
|
aiTools: facts.aiTools,
|
|
1022
1968
|
aiAgents: facts.aiAgents,
|
|
1023
|
-
...facts.extensionSurfaces ? { extensionSurfaces: facts.extensionSurfaces } : {}
|
|
1969
|
+
...facts.extensionSurfaces ? { extensionSurfaces: facts.extensionSurfaces } : {},
|
|
1970
|
+
...facts.factSources && facts.factSources.length > 0 ? { factSources: facts.factSources } : {},
|
|
1971
|
+
...facts.ownedContracts && Object.keys(facts.ownedContracts).length > 0 ? { ownedContracts: facts.ownedContracts } : {},
|
|
1972
|
+
...facts.factDiagnostics && facts.factDiagnostics.length > 0 ? { factDiagnostics: facts.factDiagnostics } : {},
|
|
1973
|
+
...facts.overrideTargets && facts.overrideTargets.length > 0 ? { overrideTargets: facts.overrideTargets } : {},
|
|
1974
|
+
...facts.overrideTargetDiagnostics && facts.overrideTargetDiagnostics.length > 0 ? { overrideTargetDiagnostics: facts.overrideTargetDiagnostics } : {}
|
|
1024
1975
|
};
|
|
1025
1976
|
}
|
|
1026
1977
|
function buildModuleFactsJsonObject(factsByModule) {
|
|
@@ -1075,8 +2026,32 @@ function extractAllModuleFactsWithCache(options) {
|
|
|
1075
2026
|
commandIds: new Set(sources.flatMap((source) => extractKnownCommandIds(source.moduleId, source.moduleRoot)))
|
|
1076
2027
|
});
|
|
1077
2028
|
assertNoUnresolvedExtensionTargets(correlated);
|
|
2029
|
+
const apiRouteOwners = /* @__PURE__ */ new Map();
|
|
2030
|
+
const commandOwners = /* @__PURE__ */ new Map();
|
|
2031
|
+
for (const source of [...sources].sort((left, right) => left.moduleId.localeCompare(right.moduleId))) {
|
|
2032
|
+
const facts = factsByModule[source.moduleId];
|
|
2033
|
+
const sourceRoot = facts ? facts.sourceRoot : source.moduleId;
|
|
2034
|
+
const owners = extractActivationTargetOwners({
|
|
2035
|
+
moduleId: source.moduleId,
|
|
2036
|
+
moduleRoot: source.moduleRoot,
|
|
2037
|
+
sourceRoot
|
|
2038
|
+
});
|
|
2039
|
+
for (const route of owners.apiRoutes) {
|
|
2040
|
+
if (!apiRouteOwners.has(route.id)) {
|
|
2041
|
+
apiRouteOwners.set(route.id, { moduleId: source.moduleId, source: route.source, bridges: route.bridges });
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
for (const command of owners.commands) {
|
|
2045
|
+
if (!commandOwners.has(command.id)) commandOwners.set(command.id, { moduleId: source.moduleId, source: command.source });
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
const withIncoming = correlateIncomingExtensions({
|
|
2049
|
+
surfacesByModule: correlated,
|
|
2050
|
+
apiRouteOwners,
|
|
2051
|
+
commandOwners
|
|
2052
|
+
});
|
|
1078
2053
|
for (const moduleId of Object.keys(factsByModule).sort((left, right) => left.localeCompare(right))) {
|
|
1079
|
-
factsByModule[moduleId].extensionSurfaces =
|
|
2054
|
+
factsByModule[moduleId].extensionSurfaces = withIncoming[moduleId];
|
|
1080
2055
|
markdownByModule[moduleId] = renderModuleFactsMarkdown(factsByModule[moduleId]);
|
|
1081
2056
|
warnings.push(...correlated[moduleId].unresolved.map(
|
|
1082
2057
|
(entry) => `[module-facts] ${moduleId} ${entry.reason}: ${entry.key} (${entry.source.path})`
|