@open-mercato/cli 0.6.7-develop.6726.1.983ae8a07e → 0.6.7-develop.6744.1.0483b18565

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.
Files changed (84) hide show
  1. package/dist/agentic/guides/module-facts.json +2247 -172
  2. package/dist/agentic/guides/modules/ai_assistant.md +47 -3
  3. package/dist/agentic/guides/modules/api_docs.md +23 -2
  4. package/dist/agentic/guides/modules/api_keys.md +25 -3
  5. package/dist/agentic/guides/modules/attachments.md +25 -3
  6. package/dist/agentic/guides/modules/audit_logs.md +24 -3
  7. package/dist/agentic/guides/modules/auth.md +47 -3
  8. package/dist/agentic/guides/modules/business_rules.md +31 -3
  9. package/dist/agentic/guides/modules/catalog.md +69 -4
  10. package/dist/agentic/guides/modules/channel_gmail.md +19 -2
  11. package/dist/agentic/guides/modules/channel_imap.md +19 -2
  12. package/dist/agentic/guides/modules/checkout.md +34 -2
  13. package/dist/agentic/guides/modules/communication_channels.md +23 -2
  14. package/dist/agentic/guides/modules/configs.md +28 -3
  15. package/dist/agentic/guides/modules/content.md +22 -2
  16. package/dist/agentic/guides/modules/currencies.md +32 -3
  17. package/dist/agentic/guides/modules/customer_accounts.md +27 -2
  18. package/dist/agentic/guides/modules/customers.md +70 -4
  19. package/dist/agentic/guides/modules/dashboards.md +25 -3
  20. package/dist/agentic/guides/modules/data_sync.md +22 -2
  21. package/dist/agentic/guides/modules/dictionaries.md +21 -2
  22. package/dist/agentic/guides/modules/directory.md +27 -2
  23. package/dist/agentic/guides/modules/entities.md +36 -3
  24. package/dist/agentic/guides/modules/events.md +19 -2
  25. package/dist/agentic/guides/modules/feature_toggles.md +32 -3
  26. package/dist/agentic/guides/modules/gateway_stripe.md +23 -3
  27. package/dist/agentic/guides/modules/generators.md +19 -2
  28. package/dist/agentic/guides/modules/inbox_ops.md +29 -2
  29. package/dist/agentic/guides/modules/integrations.md +23 -2
  30. package/dist/agentic/guides/modules/messages.md +25 -2
  31. package/dist/agentic/guides/modules/notifications.md +24 -3
  32. package/dist/agentic/guides/modules/onboarding.md +22 -2
  33. package/dist/agentic/guides/modules/payment_gateways.md +21 -2
  34. package/dist/agentic/guides/modules/perspectives.md +19 -2
  35. package/dist/agentic/guides/modules/planner.md +27 -3
  36. package/dist/agentic/guides/modules/portal.md +27 -2
  37. package/dist/agentic/guides/modules/progress.md +19 -2
  38. package/dist/agentic/guides/modules/query_index.md +27 -3
  39. package/dist/agentic/guides/modules/record_locks.md +21 -2
  40. package/dist/agentic/guides/modules/resources.md +32 -3
  41. package/dist/agentic/guides/modules/sales.md +44 -3
  42. package/dist/agentic/guides/modules/scheduler.md +30 -3
  43. package/dist/agentic/guides/modules/search.md +38 -3
  44. package/dist/agentic/guides/modules/security.md +32 -2
  45. package/dist/agentic/guides/modules/shipping_carriers.md +21 -2
  46. package/dist/agentic/guides/modules/sso.md +23 -2
  47. package/dist/agentic/guides/modules/staff.md +48 -3
  48. package/dist/agentic/guides/modules/storage_s3.md +23 -3
  49. package/dist/agentic/guides/modules/sync_akeneo.md +23 -3
  50. package/dist/agentic/guides/modules/sync_excel.md +19 -2
  51. package/dist/agentic/guides/modules/system_status_overlays.md +19 -2
  52. package/dist/agentic/guides/modules/translations.md +21 -2
  53. package/dist/agentic/guides/modules/webhooks.md +29 -4
  54. package/dist/agentic/guides/modules/wms.md +35 -3
  55. package/dist/agentic/guides/modules/workflows.md +42 -3
  56. package/dist/agentic/guides/upstream/manifest.json +1 -1
  57. package/dist/agentic/shared/ai/harness/README.md +4 -4
  58. package/dist/agentic/shared/ai/harness/RELEASE.md +2 -2
  59. package/dist/agentic/shared/ai/harness/cases.json +334 -467
  60. package/dist/agentic/shared/ai/harness/cases.schema.json +1 -0
  61. package/dist/agentic/shared/ai/harness/generated-code-review-policy.md +4 -4
  62. package/dist/agentic/shared/ai/harness/generated-code-review-response.schema.json +53 -0
  63. package/dist/agentic/shared/ai/harness/generated-code-review-result.schema.json +62 -0
  64. package/dist/agentic/shared/ai/harness/release-matrix.json +24 -0
  65. package/dist/agentic/shared/ai/harness/writable-ast-oracles.mjs +192 -0
  66. package/dist/agentic/shared/ai/skills/om-evolve-harness/SKILL.md +1 -1
  67. package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-template.md +4 -4
  68. package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-workflow.md +1 -1
  69. package/dist/agentic/shared/ai/skills/om-judge-agent-session/SKILL.md +34 -0
  70. package/dist/agentic/shared/ai/skills/om-judge-agent-session/agents/openai.yaml +4 -0
  71. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/agentic-setup.md +11 -0
  72. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/input-normalization.md +31 -0
  73. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/judge-workflow.md +39 -0
  74. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/report-template.md +43 -0
  75. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/rules.md +11 -0
  76. package/dist/agentic/shared/ai/skills/tiers.json +1 -0
  77. package/dist/agentic/shared/scripts/agent-harness-tool-server.mjs +19 -6
  78. package/dist/agentic/shared/scripts/evaluate-agent-harness.mjs +163 -31
  79. package/dist/agentic/shared/scripts/run-agent-harness-release.mjs +26 -13
  80. package/dist/lib/generators/module-facts.js +197 -9
  81. package/dist/lib/generators/module-facts.js.map +2 -2
  82. package/package.json +5 -5
  83. package/src/lib/generators/__tests__/module-facts.source-links.test.ts +119 -0
  84. package/src/lib/generators/module-facts.ts +271 -7
@@ -63,9 +63,9 @@ tree. Externally supplied targets must use the same
63
63
  node_modules link to that protected tree; nested dependency copies are rejected.
64
64
  The complete release requires trusted Bubblewrap plus working user/network namespaces on Linux;
65
65
  native macOS, Windows, untrusted runtimes, and unavailable isolation fail closed. The command preflights complete matrix,
66
- fixture, review, and target coverage before
66
+ fixture, judge, and target coverage before
67
67
  running deterministic validation, selected primary routing, optional portability routing, writable trusted-oracle
68
- gates, explicit om-code-review, and the release-matrix validation commands. It stores
68
+ gates, explicit om-judge-agent-session with om-code-review, and the release-matrix validation commands. It stores
69
69
  only a sanitized aggregate report under .ai/harness/results/.`
70
70
  }
71
71
 
@@ -541,8 +541,8 @@ export function buildReleasePlan({ cases, registry, releaseMatrix, fixtures, see
541
541
  if (!entry || typeof entry !== 'object' || Array.isArray(entry) || Object.keys(entry).length !== 1 || typeof entry.caseId !== 'string') violations.push(`writable release assignment must be runner-neutral: ${String(entry?.caseId)}`)
542
542
  }
543
543
 
544
- const configuredReviewIds = Array.isArray(releaseMatrix?.generatedCodeReview?.caseIds)
545
- ? releaseMatrix.generatedCodeReview.caseIds
544
+ const configuredReviewIds = Array.isArray(releaseMatrix?.generativeJudge?.caseIds)
545
+ ? releaseMatrix.generativeJudge.caseIds
546
546
  : []
547
547
  const missingReviewMatrixCaseIds = difference(reviewEligibleCaseIds, configuredReviewIds)
548
548
  const unexpectedReviewMatrixCaseIds = difference(configuredReviewIds, reviewEligibleCaseIds)
@@ -553,10 +553,17 @@ export function buildReleasePlan({ cases, registry, releaseMatrix, fixtures, see
553
553
  if (JSON.stringify(configuredReviewIds) !== JSON.stringify(reviewEligibleCaseIds)) {
554
554
  violations.push('generated-code review matrix must list every writable case exactly once in catalog order')
555
555
  }
556
- if (release.requireGeneratedCodeReview !== true) violations.push('release suite must require generated-code review')
557
- if (releaseMatrix?.generatedCodeReview?.skill !== 'om-code-review') violations.push('generated-code review must explicitly use om-code-review')
558
- if (releaseMatrix?.generatedCodeReview?.required !== true) violations.push('generated-code review must be mandatory for every writable case')
559
- const reviewRunners = releaseMatrix?.generatedCodeReview?.runners ?? {}
556
+ if (release.requireGenerativeJudge !== true) violations.push('release suite must require the generative judge')
557
+ if (release.requireGeneratedCodeReview !== true) violations.push('release suite must retain generated-code review compatibility')
558
+ if (releaseMatrix?.generativeJudge?.skill !== 'om-judge-agent-session') violations.push('generative judge must explicitly use om-judge-agent-session')
559
+ if (releaseMatrix?.generativeJudge?.reviewSkill !== 'om-code-review') violations.push('generative judge must explicitly compose om-code-review')
560
+ if (releaseMatrix?.generativeJudge?.required !== true) violations.push('generative judge must be mandatory for every writable case')
561
+ if (releaseMatrix?.generatedCodeReview?.skill !== 'om-code-review'
562
+ || JSON.stringify(releaseMatrix.generatedCodeReview?.caseIds) !== JSON.stringify(configuredReviewIds)
563
+ || JSON.stringify(releaseMatrix.generatedCodeReview?.runners) !== JSON.stringify(releaseMatrix.generativeJudge?.runners)) {
564
+ violations.push('generated-code review compatibility matrix must match the generative judge')
565
+ }
566
+ const reviewRunners = releaseMatrix?.generativeJudge?.runners ?? {}
560
567
  for (const runner of RELEASE_SUPPORTED_RUNNERS) {
561
568
  if (!validModelSelector(reviewRunners?.[runner]?.modelSelector)) violations.push(`generated-code review lacks a valid bounded ${runner} model selector`)
562
569
  }
@@ -564,6 +571,12 @@ export function buildReleasePlan({ cases, registry, releaseMatrix, fixtures, see
564
571
  if (!RUNNERS.has(runner)) violations.push(`generated-code review declares an invalid runner: ${runner}`)
565
572
  }
566
573
  const reviewSkillFiles = [
574
+ '.ai/skills/om-judge-agent-session/SKILL.md',
575
+ '.ai/skills/om-judge-agent-session/references/agentic-setup.md',
576
+ '.ai/skills/om-judge-agent-session/references/input-normalization.md',
577
+ '.ai/skills/om-judge-agent-session/references/judge-workflow.md',
578
+ '.ai/skills/om-judge-agent-session/references/report-template.md',
579
+ '.ai/skills/om-judge-agent-session/references/rules.md',
567
580
  '.agents/skills/om-code-review/SKILL.md',
568
581
  '.agents/skills/om-code-review/references/agentic-setup.md',
569
582
  '.agents/skills/om-code-review/references/output-format.md',
@@ -572,7 +585,7 @@ export function buildReleasePlan({ cases, registry, releaseMatrix, fixtures, see
572
585
  '.agents/skills/.om-external-ownership.json',
573
586
  ]
574
587
  const reviewSkillReady = !root || reviewSkillFiles.every((relative) => fs.existsSync(path.join(root, relative)))
575
- if (!reviewSkillReady) violations.push('generated-code review skill is not installed with ownership evidence')
588
+ if (!reviewSkillReady) violations.push('generative judge or code-review skill is not installed with ownership evidence')
576
589
 
577
590
  const registryCaseCountMatches = registry?.catalog?.expectedCaseCount === cases.length
578
591
  if (!registryCaseCountMatches) violations.push(`validator registry expects ${String(registry?.catalog?.expectedCaseCount)} cases but catalog contains ${cases.length}`)
@@ -720,8 +733,8 @@ export function buildReleasePlan({ cases, registry, releaseMatrix, fixtures, see
720
733
  })
721
734
  }
722
735
  if (configuredReviewIds.includes(caseId)) {
723
- const modelSelector = releaseMatrix.generatedCodeReview?.runners?.[primaryRunner]?.modelSelector
724
- if (!validModelSelector(modelSelector)) violations.push(`generated-code review lacks a valid bounded ${primaryRunner} model selector for ${caseId}`)
736
+ const modelSelector = releaseMatrix.generativeJudge?.runners?.[primaryRunner]?.modelSelector
737
+ if (!validModelSelector(modelSelector)) violations.push(`generative judge lacks a valid bounded ${primaryRunner} model selector for ${caseId}`)
725
738
  steps.push({ id: `review:${caseId}`, kind: 'review', caseId, runner: primaryRunner, modelSelector })
726
739
  }
727
740
  }
@@ -1696,8 +1709,8 @@ export function main(argv = process.argv.slice(2)) {
1696
1709
  const reviewExecution = execute(process.execPath, [
1697
1710
  evaluator, '--root', root, '--runner', reviewStep.runner,
1698
1711
  '--model', reviewStep.modelSelector, '--timeout', String(caseTimeout),
1699
- '--review-writable-result', path.join(root, sourceArtifact.path), '--writable-root', target,
1700
- '--review-validation-result', targetValidationArtifact.absolute,
1712
+ '--judge-writable-result', path.join(root, sourceArtifact.path), '--writable-root', target,
1713
+ '--judge-validation-result', targetValidationArtifact.absolute,
1701
1714
  ], root, caseTimeout + 60_000)
1702
1715
  const reviewArtifacts = readNewResults(root, beforeReview)
1703
1716
  resultArtifacts.push(...reviewArtifacts)
@@ -330,9 +330,9 @@ function parseApiRouteEntry(entryLiteral) {
330
330
  }
331
331
  }
332
332
  }
333
- return { path: routePath, methods, auth };
333
+ return { path: routePath, methods, auth, sourcePath: null };
334
334
  }
335
- function extractApiRoutes(moduleId, registrySource, registryDescription, warnings) {
335
+ function extractApiRoutes(moduleId, registrySource, registryDescription, routeSourcePaths, warnings) {
336
336
  if (registrySource == null) {
337
337
  warnings.push(`[module-facts] module registry unavailable (${registryDescription}); API route auth omitted for ${moduleId}`);
338
338
  return [];
@@ -356,7 +356,7 @@ function extractApiRoutes(moduleId, registrySource, registryDescription, warning
356
356
  const route = parseApiRouteEntry(element);
357
357
  if (route && !seenPaths.has(route.path)) {
358
358
  seenPaths.add(route.path);
359
- routes.push(route);
359
+ routes.push({ ...route, sourcePath: routeSourcePaths.get(route.path) ?? null });
360
360
  }
361
361
  }
362
362
  }
@@ -514,6 +514,153 @@ function listSourceFilesRecursive(directory) {
514
514
  }
515
515
  return files;
516
516
  }
517
+ function toPortableSourceRoot(moduleId, sourcePackage) {
518
+ return path.posix.join("node_modules", sourcePackage ?? "@open-mercato/core", "src", "modules", moduleId);
519
+ }
520
+ function toPortableSourcePath(moduleRoot, sourceRoot, filePath) {
521
+ const relativePath = path.relative(moduleRoot, filePath).split(path.sep).join("/");
522
+ return path.posix.join(sourceRoot, relativePath);
523
+ }
524
+ function sourceHasDefaultExport(sourceFile) {
525
+ return sourceFile.statements.some((statement) => {
526
+ if (ts.isExportAssignment(statement) && !statement.isExportEquals) return true;
527
+ if (!ts.isFunctionDeclaration(statement) && !ts.isClassDeclaration(statement)) return false;
528
+ return statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword) ?? false;
529
+ });
530
+ }
531
+ function readRouteMetadataPath(sourceFile) {
532
+ const metadata = findObjectLiteralDeclaration(sourceFile, "metadata");
533
+ return metadata ? readStringPropertyInitializer(metadata, "path") ?? null : null;
534
+ }
535
+ function extractApiRouteSourcePaths(moduleId, moduleRoot, sourceRoot) {
536
+ const apiRoot = path.join(moduleRoot, "api");
537
+ const sources = /* @__PURE__ */ new Map();
538
+ if (!fs.existsSync(apiRoot)) return sources;
539
+ const methodDirectories = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete"]);
540
+ for (const filePath of listSourceFilesRecursive(apiRoot)) {
541
+ const relativePath = path.relative(apiRoot, filePath).split(path.sep).join("/");
542
+ const segments = relativePath.split("/");
543
+ const fileName = segments.pop();
544
+ const fileStem = fileName.replace(/\.tsx?$/, "");
545
+ let routeSegments;
546
+ if (fileStem === "route") {
547
+ routeSegments = segments;
548
+ } else if (segments[0] && methodDirectories.has(segments[0].toLowerCase())) {
549
+ routeSegments = [...segments.slice(1), fileStem];
550
+ } else {
551
+ routeSegments = [...segments, fileStem];
552
+ }
553
+ const defaultPath = `/${[moduleId, ...routeSegments].filter(Boolean).join("/")}`;
554
+ const sourceFile = readSourceFile(filePath);
555
+ const routePath = sourceFile ? readRouteMetadataPath(sourceFile) ?? defaultPath : defaultPath;
556
+ sources.set(routePath, toPortableSourcePath(moduleRoot, sourceRoot, filePath));
557
+ }
558
+ return sources;
559
+ }
560
+ function extractModulePages(moduleId, moduleRoot, sourceRoot, surface) {
561
+ const pageRoot = path.join(moduleRoot, surface);
562
+ if (!fs.existsSync(pageRoot)) return [];
563
+ const pages = [];
564
+ const seen = /* @__PURE__ */ new Set();
565
+ for (const filePath of listSourceFilesRecursive(pageRoot)) {
566
+ if (!filePath.endsWith(".tsx")) continue;
567
+ const relativePath = path.relative(pageRoot, filePath).split(path.sep).join("/");
568
+ const segments = relativePath.split("/");
569
+ const fileName = segments.pop();
570
+ const fileStem = fileName.replace(/\.tsx$/, "");
571
+ const isModernPage = fileStem === "page";
572
+ if (!isModernPage && (fileStem.endsWith(".meta") || /^[A-Z]/.test(fileStem))) continue;
573
+ const sourceFile = readSourceFile(filePath);
574
+ if (!sourceFile || !sourceHasDefaultExport(sourceFile)) continue;
575
+ const routeSegments = isModernPage ? segments : [...segments, fileStem];
576
+ 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("/")}`;
577
+ if (seen.has(routePath)) continue;
578
+ seen.add(routePath);
579
+ pages.push({
580
+ path: routePath,
581
+ sourcePath: toPortableSourcePath(moduleRoot, sourceRoot, filePath)
582
+ });
583
+ }
584
+ return pages.sort((left, right) => left.path.localeCompare(right.path));
585
+ }
586
+ function findAncestorVariableDeclaration(node) {
587
+ let current = node.parent;
588
+ while (current) {
589
+ if (ts.isVariableDeclaration(current)) return current;
590
+ current = current.parent;
591
+ }
592
+ return null;
593
+ }
594
+ function findAncestorCallExpression(node) {
595
+ let current = node.parent;
596
+ while (current) {
597
+ if (ts.isCallExpression(current)) return current;
598
+ if (ts.isVariableDeclaration(current) || ts.isStatement(current)) return null;
599
+ current = current.parent;
600
+ }
601
+ return null;
602
+ }
603
+ function extractNamedAiFacts(filePaths, moduleRoot, sourceRoot, propertyName) {
604
+ const facts = [];
605
+ const seen = /* @__PURE__ */ new Set();
606
+ for (const filePath of filePaths) {
607
+ const sourceFile = readSourceFile(filePath);
608
+ if (!sourceFile) continue;
609
+ const initializers = buildVariableInitializerMap(sourceFile);
610
+ const readDefinitionValue = (objectLiteral) => {
611
+ const initializer = getObjectPropertyInitializer(objectLiteral, propertyName);
612
+ if (!initializer) return void 0;
613
+ const resolved = unwrapExpression(initializer);
614
+ if (ts.isStringLiteralLike(resolved)) return resolved.text;
615
+ if (ts.isIdentifier(resolved)) {
616
+ const declarationInitializer = initializers.get(resolved.text);
617
+ if (declarationInitializer) {
618
+ const declarationValue = unwrapExpression(declarationInitializer);
619
+ if (ts.isStringLiteralLike(declarationValue)) return declarationValue.text;
620
+ }
621
+ }
622
+ return void 0;
623
+ };
624
+ const visit = (node) => {
625
+ if (ts.isObjectLiteralExpression(node)) {
626
+ const value = readDefinitionValue(node);
627
+ if (value && !seen.has(value)) {
628
+ const declaration = findAncestorVariableDeclaration(node);
629
+ const declarationName = declaration && ts.isIdentifier(declaration.name) ? declaration.name.text : "";
630
+ const declarationType = declaration?.type?.getText(sourceFile) ?? "";
631
+ const call = findAncestorCallExpression(node);
632
+ const callee = call?.expression.getText(sourceFile) ?? "";
633
+ const isDefinition = propertyName === "name" ? /AiTool/.test(declarationType) || /tool$/i.test(declarationName) || /AiTool/.test(callee) : /AiAgentDefinition/.test(declarationType) || /agent$/i.test(declarationName) || /defineAiAgent/.test(callee);
634
+ if (isDefinition) {
635
+ seen.add(value);
636
+ facts.push({
637
+ value,
638
+ sourcePath: toPortableSourcePath(moduleRoot, sourceRoot, filePath)
639
+ });
640
+ }
641
+ }
642
+ }
643
+ node.forEachChild(visit);
644
+ };
645
+ sourceFile.forEachChild(visit);
646
+ }
647
+ return facts.sort((left, right) => left.value.localeCompare(right.value));
648
+ }
649
+ function extractAiTools(moduleRoot, sourceRoot) {
650
+ const files = /* @__PURE__ */ new Set();
651
+ const rootFile = resolveConventionFile(moduleRoot, "ai-tools");
652
+ if (rootFile) files.add(rootFile);
653
+ const toolsDirectory = path.join(moduleRoot, "ai-tools");
654
+ if (fs.existsSync(toolsDirectory)) {
655
+ for (const filePath of listSourceFilesRecursive(toolsDirectory)) files.add(filePath);
656
+ }
657
+ return extractNamedAiFacts([...files], moduleRoot, sourceRoot, "name").map((fact) => ({ name: fact.value, sourcePath: fact.sourcePath }));
658
+ }
659
+ function extractAiAgents(moduleRoot, sourceRoot) {
660
+ const agentsFile = resolveConventionFile(moduleRoot, "ai-agents");
661
+ if (!agentsFile) return [];
662
+ return extractNamedAiFacts([agentsFile], moduleRoot, sourceRoot, "id").map((fact) => ({ id: fact.value, sourcePath: fact.sourcePath }));
663
+ }
517
664
  function extractTableIds(moduleRoot) {
518
665
  if (!fs.existsSync(moduleRoot)) return [];
519
666
  const tableIds = [];
@@ -571,6 +718,7 @@ function extractModuleFacts(options) {
571
718
  if (!moduleRoot) {
572
719
  throw new Error(`[internal] extractModuleFacts requires moduleRoot or coreSrcRoot for module "${moduleId}"`);
573
720
  }
721
+ const sourceRoot = toPortableSourceRoot(moduleId, sourcePackage);
574
722
  const entitiesFilePath = resolveConventionFile(path.join(moduleRoot, "data"), "entities") ?? resolveConventionFile(path.join(moduleRoot, "db"), "entities") ?? resolveConventionFile(path.join(moduleRoot, "data"), "schema");
575
723
  const ceFilePath = resolveConventionFile(moduleRoot, "ce");
576
724
  const eventsFilePath = resolveConventionFile(moduleRoot, "events");
@@ -587,11 +735,20 @@ function extractModuleFacts(options) {
587
735
  const events = extractEvents(eventsFilePath);
588
736
  const aclFeatures = extractAclFeatures(aclFilePath);
589
737
  const { source: registrySource, description: registryDescription } = resolveRegistrySource(options);
590
- const apiRoutes = extractApiRoutes(moduleId, registrySource, registryDescription, warnings);
738
+ const apiRouteSourcePaths = extractApiRouteSourcePaths(moduleId, moduleRoot, sourceRoot);
739
+ const apiRoutes = extractApiRoutes(moduleId, registrySource, registryDescription, apiRouteSourcePaths, warnings);
591
740
  const diTokens = extractDiTokens(diFilePath);
592
741
  const searchEntities = extractSearchEntities(searchFilePath, warnings);
593
742
  const notifications = extractNotifications(notificationsFilePath, warnings);
594
743
  const cli = extractCli(cliFilePath, warnings);
744
+ const cliCommands = cliFilePath ? cli.map((command) => ({
745
+ command,
746
+ sourcePath: toPortableSourcePath(moduleRoot, sourceRoot, cliFilePath)
747
+ })) : [];
748
+ const backendPages = extractModulePages(moduleId, moduleRoot, sourceRoot, "backend");
749
+ const frontendPages = extractModulePages(moduleId, moduleRoot, sourceRoot, "frontend");
750
+ const aiTools = extractAiTools(moduleRoot, sourceRoot);
751
+ const aiAgents = extractAiAgents(moduleRoot, sourceRoot);
595
752
  const hostTokens = {
596
753
  entityIds: extractHostEntityIds(entities),
597
754
  tableIds: extractTableIds(moduleRoot)
@@ -603,6 +760,7 @@ function extractModuleFacts(options) {
603
760
  coreVersion,
604
761
  sourcePackage,
605
762
  sourceVersion,
763
+ sourceRoot,
606
764
  entities,
607
765
  events,
608
766
  aclFeatures,
@@ -612,6 +770,11 @@ function extractModuleFacts(options) {
612
770
  hostTokens,
613
771
  notifications,
614
772
  cli,
773
+ backendPages,
774
+ frontendPages,
775
+ cliCommands,
776
+ aiTools,
777
+ aiAgents,
615
778
  warnings
616
779
  };
617
780
  }
@@ -652,6 +815,9 @@ ${EMPTY_SECTION_MARKER}`;
652
815
 
653
816
  ${values.join(" \xB7 ")}`;
654
817
  }
818
+ function renderSourceLink(sourcePath) {
819
+ return `[${sourcePath}](../../../${sourcePath})`;
820
+ }
655
821
  function describeAuthRule(rule) {
656
822
  if (!rule) return "public";
657
823
  if (rule.requireFeatures && rule.requireFeatures.length > 0) return rule.requireFeatures.join(", ");
@@ -674,13 +840,20 @@ function renderApiRoutesSection(routes) {
674
840
  if (routes.length === 0) return `## API routes
675
841
 
676
842
  ${EMPTY_SECTION_MARKER}`;
677
- const header = "| Path | Methods | Auth (per-method requireFeatures) |";
678
- const divider = "|---|---|---|";
843
+ const header = "| Path | Methods | Auth (per-method requireFeatures) | Source |";
844
+ const divider = "|---|---|---|---|";
679
845
  const rows = routes.map(
680
- (route) => `| ${route.path} | ${route.methods.join(" ")} | ${renderApiRouteAuthCell(route)} |`
846
+ (route) => `| ${route.path} | ${route.methods.join(" ")} | ${renderApiRouteAuthCell(route)} | ${route.sourcePath ? renderSourceLink(route.sourcePath) : "\u2014"} |`
681
847
  );
682
848
  return ["## API routes", "", header, divider, ...rows].join("\n");
683
849
  }
850
+ function renderLinkedFactsSection(heading, facts) {
851
+ if (facts.length === 0) return `${heading}
852
+
853
+ ${EMPTY_SECTION_MARKER}`;
854
+ const rows = facts.map((fact) => `| ${fact.label} | ${renderSourceLink(fact.sourcePath)} |`);
855
+ return [heading, "", "| ID / path | Source |", "|---|---|", ...rows].join("\n");
856
+ }
684
857
  function renderHostTokensSection(hostTokens) {
685
858
  const entityIdsLine = hostTokens.entityIds.length > 0 ? hostTokens.entityIds.join(" \xB7 ") : EMPTY_SECTION_MARKER;
686
859
  const tableIdsLine = hostTokens.tableIds.length > 0 ? hostTokens.tableIds.join(" \xB7 ") : EMPTY_SECTION_MARKER;
@@ -690,6 +863,7 @@ function renderModuleFactsMarkdown(facts) {
690
863
  const sections = [
691
864
  `# ${facts.module} \u2014 module facts (generated, do not edit)`,
692
865
  renderVersionStamp(facts.coreVersion, facts.sourcePackage, facts.sourceVersion),
866
+ `Source root: ${renderSourceLink(facts.sourceRoot)}`,
693
867
  "",
694
868
  renderEntitiesSection(facts.entities),
695
869
  "",
@@ -699,6 +873,10 @@ function renderModuleFactsMarkdown(facts) {
699
873
  "",
700
874
  renderApiRoutesSection(facts.apiRoutes),
701
875
  "",
876
+ renderLinkedFactsSection("## Backend pages", facts.backendPages.map((page) => ({ label: page.path, sourcePath: page.sourcePath }))),
877
+ "",
878
+ renderLinkedFactsSection("## Frontend pages", facts.frontendPages.map((page) => ({ label: page.path, sourcePath: page.sourcePath }))),
879
+ "",
702
880
  renderInlineListSection("## DI service tokens", facts.diTokens),
703
881
  "",
704
882
  renderInlineListSection("## Search entities", facts.searchEntities),
@@ -707,7 +885,11 @@ function renderModuleFactsMarkdown(facts) {
707
885
  "",
708
886
  renderInlineListSection("## Notifications", facts.notifications),
709
887
  "",
710
- renderInlineListSection("## CLI", facts.cli),
888
+ renderLinkedFactsSection("## CLI commands", facts.cliCommands.map((command) => ({ label: command.command, sourcePath: command.sourcePath }))),
889
+ "",
890
+ renderLinkedFactsSection("## AI tools / MCP capabilities", facts.aiTools.map((tool) => ({ label: tool.name, sourcePath: tool.sourcePath }))),
891
+ "",
892
+ renderLinkedFactsSection("## AI agents", facts.aiAgents.map((agent) => ({ label: agent.id, sourcePath: agent.sourcePath }))),
711
893
  ""
712
894
  ];
713
895
  return sections.join("\n");
@@ -719,6 +901,7 @@ function toModuleFactsJsonEntry(facts) {
719
901
  coreVersion: facts.coreVersion,
720
902
  sourcePackage: facts.sourcePackage,
721
903
  sourceVersion: facts.sourceVersion,
904
+ sourceRoot: facts.sourceRoot,
722
905
  entities: facts.entities,
723
906
  events: facts.events.map((event) => ({ id: event.id, category: event.category, entity: event.entity })),
724
907
  aclFeatures: facts.aclFeatures,
@@ -727,7 +910,12 @@ function toModuleFactsJsonEntry(facts) {
727
910
  searchEntities: facts.searchEntities,
728
911
  hostTokens: facts.hostTokens,
729
912
  notifications: facts.notifications,
730
- cli: facts.cli
913
+ cli: facts.cli,
914
+ backendPages: facts.backendPages,
915
+ frontendPages: facts.frontendPages,
916
+ cliCommands: facts.cliCommands,
917
+ aiTools: facts.aiTools,
918
+ aiAgents: facts.aiAgents
731
919
  };
732
920
  }
733
921
  function buildModuleFactsJsonObject(factsByModule) {