@living-architecture/riviere-cli 0.8.10 → 0.8.12

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 (3) hide show
  1. package/dist/bin.js +412 -266
  2. package/dist/index.js +411 -265
  3. package/package.json +6 -6
package/dist/index.js CHANGED
@@ -2979,7 +2979,7 @@ var require_compile = __commonJS({
2979
2979
  const schOrFunc = root.refs[ref];
2980
2980
  if (schOrFunc)
2981
2981
  return schOrFunc;
2982
- let _sch = resolve6.call(this, root, ref);
2982
+ let _sch = resolve8.call(this, root, ref);
2983
2983
  if (_sch === void 0) {
2984
2984
  const schema = (_a2 = root.localRefs) === null || _a2 === void 0 ? void 0 : _a2[ref];
2985
2985
  const { schemaId } = this.opts;
@@ -3006,7 +3006,7 @@ var require_compile = __commonJS({
3006
3006
  function sameSchemaEnv(s1, s2) {
3007
3007
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
3008
3008
  }
3009
- function resolve6(root, ref) {
3009
+ function resolve8(root, ref) {
3010
3010
  let sch;
3011
3011
  while (typeof (sch = this.refs[ref]) == "string")
3012
3012
  ref = sch;
@@ -3581,7 +3581,7 @@ var require_fast_uri = __commonJS({
3581
3581
  }
3582
3582
  return uri;
3583
3583
  }
3584
- function resolve6(baseURI, relativeURI, options) {
3584
+ function resolve8(baseURI, relativeURI, options) {
3585
3585
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3586
3586
  const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
3587
3587
  schemelessOptions.skipEscape = true;
@@ -3808,7 +3808,7 @@ var require_fast_uri = __commonJS({
3808
3808
  var fastUri = {
3809
3809
  SCHEMES,
3810
3810
  normalize,
3811
- resolve: resolve6,
3811
+ resolve: resolve8,
3812
3812
  resolveComponent,
3813
3813
  equal,
3814
3814
  serialize,
@@ -25204,7 +25204,8 @@ import { Command as Command21 } from "commander";
25204
25204
  // src/platform/infra/extraction-config/config-loader.ts
25205
25205
  import {
25206
25206
  dirname as dirname2,
25207
- resolve as resolve2
25207
+ resolve as resolve2,
25208
+ posix as posix3
25208
25209
  } from "node:path";
25209
25210
  import {
25210
25211
  existsSync as existsSync2,
@@ -25265,7 +25266,7 @@ var extraction_config_schema_default = {
25265
25266
  module: {
25266
25267
  type: "object",
25267
25268
  description: "A module defines extraction rules for a path pattern",
25268
- required: ["name", "path"],
25269
+ required: ["name", "path", "glob"],
25269
25270
  additionalProperties: false,
25270
25271
  properties: {
25271
25272
  name: {
@@ -25275,7 +25276,12 @@ var extraction_config_schema_default = {
25275
25276
  },
25276
25277
  path: {
25277
25278
  type: "string",
25278
- description: "Glob pattern for files in this module",
25279
+ description: "Module root directory relative to config file",
25280
+ minLength: 1
25281
+ },
25282
+ glob: {
25283
+ type: "string",
25284
+ description: "Glob pattern for source files within the module directory",
25279
25285
  minLength: 1
25280
25286
  },
25281
25287
  extends: {
@@ -26342,11 +26348,11 @@ function createFunctionComponent(func, filePath, domain2, componentType) {
26342
26348
  function findMatchingModule(filePath, modules, globMatcher, configDir) {
26343
26349
  const normalized = filePath.replaceAll(/\\+/g, "/");
26344
26350
  if (configDir === void 0) {
26345
- return modules.find((m) => globMatcher(normalized, m.path));
26351
+ return modules.find((m) => globMatcher(normalized, posix.join(m.path, m.glob)));
26346
26352
  }
26347
26353
  const normalizedConfigDir = configDir.replaceAll(/\\+/g, "/");
26348
26354
  const pathToMatch = posix.relative(normalizedConfigDir, normalized);
26349
- return modules.find((m) => globMatcher(pathToMatch, m.path));
26355
+ return modules.find((m) => globMatcher(pathToMatch, posix.join(m.path, m.glob)));
26350
26356
  }
26351
26357
 
26352
26358
  // ../riviere-extract-ts/dist/features/extraction/domain/config-resolution/config-resolution-errors.js
@@ -26384,6 +26390,7 @@ function resolveModule(moduleConfig, loader) {
26384
26390
  return {
26385
26391
  name: moduleConfig.name,
26386
26392
  path: moduleConfig.path,
26393
+ glob: moduleConfig.glob,
26387
26394
  api: requireRule(moduleConfig.api, "api", moduleConfig.name),
26388
26395
  useCase: requireRule(moduleConfig.useCase, "useCase", moduleConfig.name),
26389
26396
  domainOp: requireRule(moduleConfig.domainOp, "domainOp", moduleConfig.name),
@@ -26403,6 +26410,7 @@ function resolveModuleWithExtends(moduleConfig, extendsSource, loader) {
26403
26410
  return {
26404
26411
  name: moduleConfig.name,
26405
26412
  path: moduleConfig.path,
26413
+ glob: moduleConfig.glob,
26406
26414
  api: moduleConfig.api ?? baseModule.api,
26407
26415
  useCase: moduleConfig.useCase ?? baseModule.useCase,
26408
26416
  domainOp: moduleConfig.domainOp ?? baseModule.domainOp,
@@ -28869,50 +28877,48 @@ function traceNonComponent(project, componentIndex, source, typeName, calledMeth
28869
28877
  }
28870
28878
 
28871
28879
  // ../riviere-extract-ts/dist/features/extraction/domain/connection-detection/async-detection/detect-publish-connections.js
28872
- function detectPublishConnections(project, components, options) {
28880
+ function detectPublishConnections(components, options) {
28873
28881
  const publishers = components.filter((c) => c.type === "eventPublisher");
28874
28882
  const events = components.filter((c) => c.type === "event");
28875
28883
  const repository = options.repository;
28876
- return publishers.flatMap((publisher) => extractPublisherLinks(project, publisher, events, options, repository));
28877
- }
28878
- function extractPublisherLinks(project, publisher, events, options, repository) {
28879
- const publishedEventType = publisher.metadata["publishedEventType"];
28880
- if (typeof publishedEventType === "string") {
28884
+ return publishers.flatMap((publisher) => {
28885
+ const publishedEventType = publisher.metadata["publishedEventType"];
28881
28886
  const sourceLocation = {
28882
28887
  repository,
28883
28888
  filePath: publisher.location.file,
28884
28889
  lineNumber: publisher.location.line
28885
28890
  };
28886
- return resolvePublishTarget(publisher, publishedEventType, events, options, sourceLocation);
28887
- }
28888
- const classDecl = findClassInProject(project, publisher);
28889
- if (classDecl === void 0) {
28890
- return [];
28891
- }
28892
- const methods = classDecl.getMethods();
28893
- return methods.flatMap((method) => {
28894
- const firstParam = method.getParameters()[0];
28895
- if (firstParam === void 0) {
28896
- return [];
28891
+ if (typeof publishedEventType !== "string") {
28892
+ return [handleMissingMetadata(publisher, options, sourceLocation)];
28897
28893
  }
28898
- const paramType = firstParam.getType();
28899
- const paramTypeName = stripGenericArgs(paramType.getText(firstParam));
28900
- const sourceLocation = {
28901
- repository,
28902
- filePath: publisher.location.file,
28903
- lineNumber: method.getStartLineNumber()
28904
- };
28905
- return resolvePublishTarget(publisher, paramTypeName, events, options, sourceLocation);
28894
+ return resolvePublishTarget(publisher, publishedEventType, events, options, sourceLocation);
28906
28895
  });
28907
28896
  }
28908
- function resolvePublishTarget(publisher, paramTypeName, events, options, sourceLocation) {
28909
- const matchingEvents = events.filter((e) => e.metadata["eventName"] === paramTypeName);
28897
+ function handleMissingMetadata(publisher, options, sourceLocation) {
28898
+ if (options.strict) {
28899
+ throw new ConnectionDetectionError({
28900
+ file: sourceLocation.filePath,
28901
+ line: sourceLocation.lineNumber,
28902
+ typeName: publisher.name,
28903
+ reason: 'eventPublisher is missing required "publishedEventType" metadata'
28904
+ });
28905
+ }
28906
+ return {
28907
+ source: componentIdentity(publisher),
28908
+ target: "_unresolved",
28909
+ type: "async",
28910
+ sourceLocation,
28911
+ _uncertain: `eventPublisher "${publisher.name}" is missing required "publishedEventType" metadata`
28912
+ };
28913
+ }
28914
+ function resolvePublishTarget(publisher, publishedEventType, events, options, sourceLocation) {
28915
+ const matchingEvents = events.filter((e) => e.metadata["eventName"] === publishedEventType);
28910
28916
  if (matchingEvents.length === 0) {
28911
- return [handleNoMatch(publisher, paramTypeName, options, sourceLocation)];
28917
+ return [handleNoMatch(publisher, publishedEventType, options, sourceLocation)];
28912
28918
  }
28913
28919
  if (matchingEvents.length > 1) {
28914
28920
  return [
28915
- handleAmbiguousMatch(publisher, paramTypeName, matchingEvents.length, options, sourceLocation)
28921
+ handleAmbiguousMatch(publisher, publishedEventType, matchingEvents.length, options, sourceLocation)
28916
28922
  ];
28917
28923
  }
28918
28924
  return matchingEvents.map((event) => ({
@@ -28922,13 +28928,13 @@ function resolvePublishTarget(publisher, paramTypeName, events, options, sourceL
28922
28928
  sourceLocation
28923
28929
  }));
28924
28930
  }
28925
- function handleAmbiguousMatch(publisher, paramTypeName, matchCount, options, sourceLocation) {
28931
+ function handleAmbiguousMatch(publisher, publishedEventType, matchCount, options, sourceLocation) {
28926
28932
  if (options.strict) {
28927
28933
  throw new ConnectionDetectionError({
28928
28934
  file: sourceLocation.filePath,
28929
28935
  line: sourceLocation.lineNumber,
28930
28936
  typeName: publisher.name,
28931
- reason: `parameter type "${paramTypeName}" matches ${matchCount} Event components (ambiguous)`
28937
+ reason: `publishedEventType "${publishedEventType}" matches ${matchCount} Event components (ambiguous)`
28932
28938
  });
28933
28939
  }
28934
28940
  return {
@@ -28936,16 +28942,16 @@ function handleAmbiguousMatch(publisher, paramTypeName, matchCount, options, sou
28936
28942
  target: "_unresolved",
28937
28943
  type: "async",
28938
28944
  sourceLocation,
28939
- _uncertain: `ambiguous: ${matchCount} events match parameter type: ${paramTypeName}`
28945
+ _uncertain: `ambiguous: ${matchCount} events match publishedEventType: ${publishedEventType}`
28940
28946
  };
28941
28947
  }
28942
- function handleNoMatch(publisher, paramTypeName, options, sourceLocation) {
28948
+ function handleNoMatch(publisher, publishedEventType, options, sourceLocation) {
28943
28949
  if (options.strict) {
28944
28950
  throw new ConnectionDetectionError({
28945
28951
  file: sourceLocation.filePath,
28946
28952
  line: sourceLocation.lineNumber,
28947
28953
  typeName: publisher.name,
28948
- reason: `parameter type "${paramTypeName}" does not match any Event component`
28954
+ reason: `publishedEventType "${publishedEventType}" does not match any Event component`
28949
28955
  });
28950
28956
  }
28951
28957
  return {
@@ -28953,7 +28959,7 @@ function handleNoMatch(publisher, paramTypeName, options, sourceLocation) {
28953
28959
  target: "_unresolved",
28954
28960
  type: "async",
28955
28961
  sourceLocation,
28956
- _uncertain: `no event found for parameter type: ${paramTypeName}`
28962
+ _uncertain: `no event found for publishedEventType: ${publishedEventType}`
28957
28963
  };
28958
28964
  }
28959
28965
 
@@ -29262,8 +29268,7 @@ function deduplicateCrossStrategy(links) {
29262
29268
  }
29263
29269
  return [...seen.values()];
29264
29270
  }
29265
- function detectConnections(project, components, options, globMatcher) {
29266
- const totalStart = performance.now();
29271
+ function detectPerModuleConnections(project, components, options, globMatcher) {
29267
29272
  const setupStart = performance.now();
29268
29273
  const componentIndex = new ComponentIndex(components);
29269
29274
  const sourceFilePaths = computeFilteredFilePaths(project, options.moduleGlobs, globMatcher);
@@ -29277,43 +29282,50 @@ function detectConnections(project, components, options, globMatcher) {
29277
29282
  repository
29278
29283
  });
29279
29284
  const callGraphMs = performance.now() - callGraphStart;
29285
+ const patterns = options.patterns ?? [];
29286
+ const { configurableLinks, configurableMs } = runConfigurableDetection(project, patterns, components, componentIndex, strict, repository);
29287
+ return {
29288
+ links: [...syncLinks, ...configurableLinks],
29289
+ timings: {
29290
+ callGraphMs,
29291
+ configurableMs,
29292
+ setupMs
29293
+ }
29294
+ };
29295
+ }
29296
+ function detectCrossModuleConnections(allComponents, options) {
29297
+ const strict = options.allowIncomplete !== true;
29298
+ const repository = options.repository;
29280
29299
  const asyncStart = performance.now();
29281
- const publishLinks = detectPublishConnections(project, components, {
29300
+ const publishLinks = detectPublishConnections(allComponents, {
29282
29301
  strict,
29283
29302
  repository
29284
29303
  });
29285
- const subscribeLinks = detectSubscribeConnections(components, {
29304
+ const subscribeLinks = detectSubscribeConnections(allComponents, {
29286
29305
  strict,
29287
29306
  repository
29288
29307
  });
29289
29308
  const asyncDetectionMs = performance.now() - asyncStart;
29290
- const patterns = options.patterns ?? [];
29291
- const { configurableLinks, configurableMs } = patterns.length > 0 ? (() => {
29292
- const configurableStart = performance.now();
29293
- const links = detectConfigurableConnections(project, patterns, components, componentIndex, {
29294
- strict,
29295
- repository
29296
- });
29309
+ return {
29310
+ links: [...publishLinks, ...subscribeLinks],
29311
+ timings: { asyncDetectionMs }
29312
+ };
29313
+ }
29314
+ function runConfigurableDetection(project, patterns, components, componentIndex, strict, repository) {
29315
+ if (patterns.length === 0) {
29297
29316
  return {
29298
- configurableLinks: links,
29299
- configurableMs: performance.now() - configurableStart
29317
+ configurableLinks: [],
29318
+ configurableMs: 0
29300
29319
  };
29301
- })() : {
29302
- configurableLinks: [],
29303
- configurableMs: 0
29304
- };
29305
- const totalMs = performance.now() - totalStart;
29306
- const allLinks = [...syncLinks, ...publishLinks, ...subscribeLinks, ...configurableLinks];
29307
- const deduplicatedLinks = deduplicateCrossStrategy(allLinks);
29320
+ }
29321
+ const configurableStart = performance.now();
29322
+ const links = detectConfigurableConnections(project, patterns, components, componentIndex, {
29323
+ strict,
29324
+ repository
29325
+ });
29308
29326
  return {
29309
- links: deduplicatedLinks,
29310
- timings: {
29311
- callGraphMs,
29312
- asyncDetectionMs,
29313
- configurableMs,
29314
- setupMs,
29315
- totalMs
29316
- }
29327
+ configurableLinks: links,
29328
+ configurableMs: performance.now() - configurableStart
29317
29329
  };
29318
29330
  }
29319
29331
 
@@ -29322,7 +29334,7 @@ import { posix as posix2 } from "node:path";
29322
29334
  function findMatchingModule2(filePath, modules, globMatcher, configDir) {
29323
29335
  const normalized = filePath.replaceAll(/\\+/g, "/");
29324
29336
  const pathToMatch = posix2.relative(configDir.replaceAll(/\\+/g, "/"), normalized);
29325
- return modules.find((m) => globMatcher(pathToMatch, m.path));
29337
+ return modules.find((m) => globMatcher(pathToMatch, posix2.join(m.path, m.glob)));
29326
29338
  }
29327
29339
  function getBuiltInRule(module, componentType) {
29328
29340
  const ruleMap = {
@@ -29544,7 +29556,8 @@ var NOT_USED = { notUsed: true };
29544
29556
  function topLevelRulesToModule(parsed) {
29545
29557
  return {
29546
29558
  name: "extended",
29547
- path: "**",
29559
+ path: ".",
29560
+ glob: "**",
29548
29561
  api: parsed.api ?? NOT_USED,
29549
29562
  useCase: parsed.useCase ?? NOT_USED,
29550
29563
  domainOp: parsed.domainOp ?? NOT_USED,
@@ -29664,9 +29677,9 @@ function tryExpandModuleRefs(data, configDir) {
29664
29677
  }
29665
29678
  }
29666
29679
  function resolveSourceFiles(resolvedConfig, configDir) {
29667
- const sourceFilePaths = resolvedConfig.modules.flatMap((module) => globSync(module.path, { cwd: configDir })).map((filePath) => resolve2(configDir, filePath));
29680
+ const sourceFilePaths = resolvedConfig.modules.flatMap((module) => globSync(posix3.join(module.path, module.glob), { cwd: configDir })).map((filePath) => resolve2(configDir, filePath));
29668
29681
  if (sourceFilePaths.length === 0) {
29669
- const patterns = resolvedConfig.modules.map((m) => m.path).join(", ");
29682
+ const patterns = resolvedConfig.modules.map((m) => posix3.join(m.path, m.glob)).join(", ");
29670
29683
  throw new ConfigValidationError(
29671
29684
  "VALIDATION_ERROR" /* ValidationError */,
29672
29685
  `No files matched extraction patterns: ${patterns}
@@ -29921,111 +29934,6 @@ function validateFlagCombinations(options) {
29921
29934
  validateFormatOption(options);
29922
29935
  }
29923
29936
 
29924
- // src/platform/infra/cli-presentation/format-pr-markdown.ts
29925
- function formatComponentLine(component) {
29926
- return `- **${component.type}** \`${component.name}\` in \`${component.domain}\` domain`;
29927
- }
29928
- function formatSection(title, components) {
29929
- const header = `### ${title} (${components.length})`;
29930
- if (components.length === 0) {
29931
- return `${header}
29932
- None`;
29933
- }
29934
- return `${header}
29935
- ${components.map(formatComponentLine).join("\n")}`;
29936
- }
29937
- function formatPrMarkdown(categorized) {
29938
- const sections = [
29939
- formatSection("Added Components", categorized.added),
29940
- formatSection("Modified Components", categorized.modified),
29941
- formatSection("Removed Components", categorized.removed)
29942
- ];
29943
- return `## Architecture Changes
29944
-
29945
- ${sections.join("\n\n")}`;
29946
- }
29947
-
29948
- // src/platform/infra/cli-presentation/extract-output-formatter.ts
29949
- function compareByCodePoint2(a, b) {
29950
- if (a < b) return -1;
29951
- if (a > b) return 1;
29952
- return 0;
29953
- }
29954
- function formatDryRunOutput(components) {
29955
- const countsByDomain = /* @__PURE__ */ new Map();
29956
- for (const component of components) {
29957
- const existingTypeCounts = countsByDomain.get(component.domain);
29958
- const typeCounts = existingTypeCounts ?? /* @__PURE__ */ new Map();
29959
- if (existingTypeCounts === void 0) {
29960
- countsByDomain.set(component.domain, typeCounts);
29961
- }
29962
- const currentCount = typeCounts.get(component.type) ?? 0;
29963
- typeCounts.set(component.type, currentCount + 1);
29964
- }
29965
- const sortedDomains = [...countsByDomain.entries()].sort(([a], [b]) => compareByCodePoint2(a, b));
29966
- const lines = [];
29967
- for (const [domain2, typeCounts] of sortedDomains) {
29968
- const typeStrings = [...typeCounts.entries()].sort(([a], [b]) => compareByCodePoint2(a, b)).map(([type, count]) => `${type}(${count})`);
29969
- lines.push(`${domain2}: ${typeStrings.join(", ")}`);
29970
- }
29971
- return lines;
29972
- }
29973
-
29974
- // src/platform/infra/cli-presentation/output-writer.ts
29975
- import { writeFileSync } from "node:fs";
29976
- function outputResult(data, options) {
29977
- if (options.output !== void 0) {
29978
- try {
29979
- writeFileSync(options.output, JSON.stringify(data));
29980
- } catch {
29981
- console.log(
29982
- JSON.stringify(
29983
- formatError2(
29984
- "VALIDATION_ERROR" /* ValidationError */,
29985
- "Failed to write output file: " + options.output
29986
- )
29987
- )
29988
- );
29989
- process.exit(3 /* RuntimeError */);
29990
- }
29991
- return;
29992
- }
29993
- console.log(JSON.stringify(data));
29994
- }
29995
-
29996
- // src/platform/infra/cli-presentation/format-extraction-stats.ts
29997
- function countLinksByType(componentCount, links) {
29998
- const syncLinkCount = links.filter((l) => l.type === "sync").length;
29999
- const asyncLinkCount = links.filter((l) => l.type === "async").length;
30000
- const uncertainLinkCount = links.filter((l) => l._uncertain !== void 0).length;
30001
- return {
30002
- componentCount,
30003
- linkCount: links.length,
30004
- syncLinkCount,
30005
- asyncLinkCount,
30006
- uncertainLinkCount
30007
- };
30008
- }
30009
- function formatSeconds(ms) {
30010
- return (ms / 1e3).toFixed(2) + "s";
30011
- }
30012
- function formatExtractionStats(stats) {
30013
- const lines = [`Components: ${stats.componentCount}`];
30014
- if (stats.linkCount !== void 0) {
30015
- lines.push(
30016
- `Links: ${stats.linkCount} (sync: ${stats.syncLinkCount}, async: ${stats.asyncLinkCount})`
30017
- );
30018
- lines.push(`Uncertain: ${stats.uncertainLinkCount}`);
30019
- }
30020
- return lines;
30021
- }
30022
- function formatTimingLine(timings) {
30023
- return `Extraction completed in ${formatSeconds(timings.totalMs)} (call graph: ${formatSeconds(timings.callGraphMs)}, detection: ${formatSeconds(timings.asyncDetectionMs)}, setup: ${formatSeconds(timings.setupMs)})`;
30024
- }
30025
-
30026
- // src/features/extract/infra/safe-extraction-operations.ts
30027
- import "ts-morph";
30028
-
30029
29937
  // src/platform/infra/extraction-config/draft-component-loader.ts
30030
29938
  import {
30031
29939
  existsSync as existsSync4,
@@ -30064,43 +29972,6 @@ function loadDraftComponentsFromFile(filePath) {
30064
29972
  return parsed;
30065
29973
  }
30066
29974
 
30067
- // src/features/extract/infra/safe-extraction-operations.ts
30068
- function loadOrExtractComponents(project, sourceFilePaths, resolvedConfig, configDir, enrichPath) {
30069
- if (enrichPath === void 0) {
30070
- return extractComponents(project, sourceFilePaths, resolvedConfig, matchesGlob, configDir);
30071
- }
30072
- return loadDraftComponentsFromFile(enrichPath);
30073
- }
30074
- function enrichComponentsSafe(draftComponents, resolvedConfig, project, configDir, allowIncomplete) {
30075
- const result = enrichComponents(draftComponents, resolvedConfig, project, matchesGlob, configDir);
30076
- if (result.failures.length > 0) {
30077
- const failedFields = result.failures.map((f) => f.field);
30078
- if (!allowIncomplete) {
30079
- throw new ExtractionFieldFailureError(failedFields);
30080
- }
30081
- console.error(
30082
- `Warning: Enrichment failed for ${failedFields.length} field(s): ${failedFields.join(", ")}`
30083
- );
30084
- }
30085
- return result;
30086
- }
30087
- function detectConnectionsSafe(project, components, moduleGlobs, repository, allowIncomplete, showStats) {
30088
- const result = detectConnections(
30089
- project,
30090
- components,
30091
- {
30092
- allowIncomplete,
30093
- moduleGlobs,
30094
- repository
30095
- },
30096
- matchesGlob
30097
- );
30098
- if (showStats) {
30099
- console.error(formatTimingLine(result.timings));
30100
- }
30101
- return result;
30102
- }
30103
-
30104
29975
  // src/platform/infra/git/git-repository-info.ts
30105
29976
  import { execFileSync as execFileSync2 } from "node:child_process";
30106
29977
  var RepositoryUrlParseError = class extends Error {
@@ -30182,31 +30053,316 @@ function getRepositoryInfo(gitBinary = "git", cwd = process.cwd(), executor = de
30182
30053
  }
30183
30054
  }
30184
30055
 
30185
- // src/features/extract/infra/ts-morph/create-configured-project.ts
30056
+ // src/features/extract/infra/external-clients/create-module-contexts.ts
30057
+ import {
30058
+ posix as posix4,
30059
+ resolve as resolve7
30060
+ } from "node:path";
30061
+ import { globSync as globSync2 } from "glob";
30062
+
30063
+ // src/features/extract/infra/external-clients/find-module-tsconfig-dir.ts
30186
30064
  import { existsSync as existsSync5 } from "node:fs";
30187
30065
  import { resolve as resolve5 } from "node:path";
30188
- import { Project as Project3 } from "ts-morph";
30066
+ function findModuleTsConfigDir(configDir, modulePath) {
30067
+ const moduleTsConfigPath = resolve5(configDir, modulePath, "tsconfig.json");
30068
+ if (existsSync5(moduleTsConfigPath)) {
30069
+ return resolve5(configDir, modulePath);
30070
+ }
30071
+ return configDir;
30072
+ }
30073
+
30074
+ // src/features/extract/infra/external-clients/create-configured-project.ts
30075
+ import { existsSync as existsSync6 } from "node:fs";
30076
+ import { resolve as resolve6 } from "node:path";
30077
+ import { Project as Project2 } from "ts-morph";
30189
30078
  function createConfiguredProject(configDir, skipTsConfig) {
30190
30079
  if (skipTsConfig) {
30191
- return new Project3();
30080
+ return new Project2();
30192
30081
  }
30193
- const tsConfigPath = resolve5(configDir, "tsconfig.json");
30194
- if (!existsSync5(tsConfigPath)) {
30195
- return new Project3();
30082
+ const tsConfigPath = resolve6(configDir, "tsconfig.json");
30083
+ if (!existsSync6(tsConfigPath)) {
30084
+ return new Project2();
30196
30085
  }
30197
- return new Project3({
30086
+ return new Project2({
30198
30087
  tsConfigFilePath: tsConfigPath,
30199
30088
  skipAddingFilesFromTsConfig: true
30200
30089
  });
30201
30090
  }
30202
30091
 
30203
- // src/features/extract/infra/load-extraction-project.ts
30092
+ // src/features/extract/infra/external-clients/load-extraction-project.ts
30204
30093
  function loadExtractionProject(configDir, sourceFilePaths, skipTsConfig) {
30205
30094
  const project = createConfiguredProject(configDir, skipTsConfig);
30206
30095
  project.addSourceFilesAtPaths(sourceFilePaths);
30207
30096
  return project;
30208
30097
  }
30209
30098
 
30099
+ // src/features/extract/infra/external-clients/create-module-contexts.ts
30100
+ function createModuleContexts(resolvedConfig, configDir, sourceFilePaths, skipTsConfig) {
30101
+ const sourceFileSet = new Set(sourceFilePaths);
30102
+ return resolvedConfig.modules.map((module) => {
30103
+ const allModuleFiles = globSync2(posix4.join(module.path, module.glob), { cwd: configDir }).map(
30104
+ (f) => resolve7(configDir, f)
30105
+ );
30106
+ const moduleFiles = allModuleFiles.filter((f) => sourceFileSet.has(f));
30107
+ const tsConfigDir = findModuleTsConfigDir(configDir, module.path);
30108
+ const project = loadExtractionProject(tsConfigDir, moduleFiles, skipTsConfig);
30109
+ return {
30110
+ module,
30111
+ files: moduleFiles,
30112
+ project
30113
+ };
30114
+ });
30115
+ }
30116
+
30117
+ // src/features/extract/domain/extract-draft-components.ts
30118
+ function extractDraftComponents(moduleContexts, resolvedConfig, configDir) {
30119
+ return moduleContexts.flatMap(
30120
+ (ctx) => extractComponents(ctx.project, ctx.files, resolvedConfig, matchesGlob, configDir)
30121
+ );
30122
+ }
30123
+
30124
+ // src/features/extract/domain/enrich-per-module.ts
30125
+ var OrphanedDraftComponentError = class extends Error {
30126
+ constructor(orphanedModules, knownModules) {
30127
+ super(
30128
+ `Draft components reference unknown modules: [${orphanedModules.join(", ")}]. Known modules: [${knownModules.join(", ")}]`
30129
+ );
30130
+ this.name = "OrphanedDraftComponentError";
30131
+ }
30132
+ };
30133
+ function enrichPerModule(moduleContexts, draftComponents, resolvedConfig, configDir) {
30134
+ const moduleNames = new Set(moduleContexts.map((ctx) => ctx.module.name));
30135
+ const draftsByModule = groupDraftsByModule(draftComponents);
30136
+ assertAllDraftsMatchModules(draftsByModule, moduleNames);
30137
+ const components = [];
30138
+ const failedFieldSet = /* @__PURE__ */ new Set();
30139
+ for (const ctx of moduleContexts) {
30140
+ const moduleDrafts = draftsByModule.get(ctx.module.name) ?? [];
30141
+ if (moduleDrafts.length === 0) {
30142
+ continue;
30143
+ }
30144
+ const result = enrichComponents(
30145
+ moduleDrafts,
30146
+ resolvedConfig,
30147
+ ctx.project,
30148
+ matchesGlob,
30149
+ configDir
30150
+ );
30151
+ components.push(...result.components);
30152
+ for (const f of result.failures) {
30153
+ failedFieldSet.add(f.field);
30154
+ }
30155
+ }
30156
+ return {
30157
+ components,
30158
+ failedFields: [...failedFieldSet]
30159
+ };
30160
+ }
30161
+ function assertAllDraftsMatchModules(draftsByModule, moduleNames) {
30162
+ const orphanedModules = [...draftsByModule.keys()].filter((name) => !moduleNames.has(name));
30163
+ if (orphanedModules.length > 0) {
30164
+ throw new OrphanedDraftComponentError(orphanedModules, [...moduleNames]);
30165
+ }
30166
+ }
30167
+ function groupDraftsByModule(drafts) {
30168
+ const grouped = /* @__PURE__ */ new Map();
30169
+ for (const draft of drafts) {
30170
+ const existing = grouped.get(draft.domain);
30171
+ if (existing) {
30172
+ existing.push(draft);
30173
+ } else {
30174
+ grouped.set(draft.domain, [draft]);
30175
+ }
30176
+ }
30177
+ return grouped;
30178
+ }
30179
+
30180
+ // src/features/extract/domain/detect-connections-per-module.ts
30181
+ import { posix as posix5 } from "node:path";
30182
+ function detectConnectionsPerModule(moduleContexts, enrichedComponents, repositoryName, allowIncomplete) {
30183
+ const links = [];
30184
+ const timings = [];
30185
+ for (const ctx of moduleContexts) {
30186
+ const moduleComponents = enrichedComponents.filter((c) => c.domain === ctx.module.name);
30187
+ if (moduleComponents.length === 0) {
30188
+ continue;
30189
+ }
30190
+ const result = detectPerModuleConnections(
30191
+ ctx.project,
30192
+ moduleComponents,
30193
+ {
30194
+ allowIncomplete,
30195
+ moduleGlobs: [posix5.join(ctx.module.path, ctx.module.glob)],
30196
+ repository: repositoryName
30197
+ },
30198
+ matchesGlob
30199
+ );
30200
+ links.push(...result.links);
30201
+ timings.push({
30202
+ callGraphMs: result.timings.callGraphMs,
30203
+ asyncDetectionMs: 0,
30204
+ configurableMs: result.timings.configurableMs,
30205
+ setupMs: result.timings.setupMs,
30206
+ totalMs: result.timings.callGraphMs + result.timings.configurableMs + result.timings.setupMs
30207
+ });
30208
+ }
30209
+ const crossResult = detectCrossModuleConnections(enrichedComponents, {
30210
+ allowIncomplete,
30211
+ repository: repositoryName
30212
+ });
30213
+ links.push(...crossResult.links);
30214
+ timings.push({
30215
+ callGraphMs: 0,
30216
+ asyncDetectionMs: crossResult.timings.asyncDetectionMs,
30217
+ configurableMs: 0,
30218
+ setupMs: 0,
30219
+ totalMs: crossResult.timings.asyncDetectionMs
30220
+ });
30221
+ return {
30222
+ links: deduplicateCrossStrategy(links),
30223
+ timings
30224
+ };
30225
+ }
30226
+
30227
+ // src/features/extract/commands/run-extraction.ts
30228
+ function runExtraction(options, resolvedConfig, configDir, sourceFilePaths) {
30229
+ const skipTsConfig = options.tsConfig === false;
30230
+ const moduleContexts = createModuleContexts(
30231
+ resolvedConfig,
30232
+ configDir,
30233
+ sourceFilePaths,
30234
+ skipTsConfig
30235
+ );
30236
+ const draftComponents = options.enrich === void 0 ? extractDraftComponents(moduleContexts, resolvedConfig, configDir) : loadDraftComponentsFromFile(options.enrich);
30237
+ if (options.dryRun || options.format === "markdown" || options.componentsOnly) {
30238
+ return {
30239
+ kind: "draftOnly",
30240
+ components: draftComponents
30241
+ };
30242
+ }
30243
+ const allowIncomplete = options.allowIncomplete === true;
30244
+ const enrichment = enrichPerModule(moduleContexts, draftComponents, resolvedConfig, configDir);
30245
+ if (enrichment.failedFields.length > 0 && !allowIncomplete) {
30246
+ throw new ExtractionFieldFailureError(enrichment.failedFields);
30247
+ }
30248
+ const repositoryInfo = getRepositoryInfo();
30249
+ const connectionResult = detectConnectionsPerModule(
30250
+ moduleContexts,
30251
+ enrichment.components,
30252
+ repositoryInfo.name,
30253
+ allowIncomplete
30254
+ );
30255
+ return {
30256
+ kind: "full",
30257
+ components: enrichment.components,
30258
+ links: connectionResult.links,
30259
+ timings: connectionResult.timings,
30260
+ failedFields: enrichment.failedFields
30261
+ };
30262
+ }
30263
+
30264
+ // src/platform/infra/cli-presentation/format-pr-markdown.ts
30265
+ function formatComponentLine(component) {
30266
+ return `- **${component.type}** \`${component.name}\` in \`${component.domain}\` domain`;
30267
+ }
30268
+ function formatSection(title, components) {
30269
+ const header = `### ${title} (${components.length})`;
30270
+ if (components.length === 0) {
30271
+ return `${header}
30272
+ None`;
30273
+ }
30274
+ return `${header}
30275
+ ${components.map(formatComponentLine).join("\n")}`;
30276
+ }
30277
+ function formatPrMarkdown(categorized) {
30278
+ const sections = [
30279
+ formatSection("Added Components", categorized.added),
30280
+ formatSection("Modified Components", categorized.modified),
30281
+ formatSection("Removed Components", categorized.removed)
30282
+ ];
30283
+ return `## Architecture Changes
30284
+
30285
+ ${sections.join("\n\n")}`;
30286
+ }
30287
+
30288
+ // src/platform/infra/cli-presentation/extract-output-formatter.ts
30289
+ function compareByCodePoint2(a, b) {
30290
+ if (a < b) return -1;
30291
+ if (a > b) return 1;
30292
+ return 0;
30293
+ }
30294
+ function formatDryRunOutput(components) {
30295
+ const countsByDomain = /* @__PURE__ */ new Map();
30296
+ for (const component of components) {
30297
+ const existingTypeCounts = countsByDomain.get(component.domain);
30298
+ const typeCounts = existingTypeCounts ?? /* @__PURE__ */ new Map();
30299
+ if (existingTypeCounts === void 0) {
30300
+ countsByDomain.set(component.domain, typeCounts);
30301
+ }
30302
+ const currentCount = typeCounts.get(component.type) ?? 0;
30303
+ typeCounts.set(component.type, currentCount + 1);
30304
+ }
30305
+ const sortedDomains = [...countsByDomain.entries()].sort(([a], [b]) => compareByCodePoint2(a, b));
30306
+ const lines = [];
30307
+ for (const [domain2, typeCounts] of sortedDomains) {
30308
+ const typeStrings = [...typeCounts.entries()].sort(([a], [b]) => compareByCodePoint2(a, b)).map(([type, count]) => `${type}(${count})`);
30309
+ lines.push(`${domain2}: ${typeStrings.join(", ")}`);
30310
+ }
30311
+ return lines;
30312
+ }
30313
+
30314
+ // src/platform/infra/cli-presentation/output-writer.ts
30315
+ import { writeFileSync } from "node:fs";
30316
+ function outputResult(data, options) {
30317
+ if (options.output !== void 0) {
30318
+ try {
30319
+ writeFileSync(options.output, JSON.stringify(data));
30320
+ } catch {
30321
+ console.log(
30322
+ JSON.stringify(
30323
+ formatError2(
30324
+ "VALIDATION_ERROR" /* ValidationError */,
30325
+ "Failed to write output file: " + options.output
30326
+ )
30327
+ )
30328
+ );
30329
+ process.exit(3 /* RuntimeError */);
30330
+ }
30331
+ return;
30332
+ }
30333
+ console.log(JSON.stringify(data));
30334
+ }
30335
+
30336
+ // src/platform/infra/cli-presentation/format-extraction-stats.ts
30337
+ function countLinksByType(componentCount, links) {
30338
+ const syncLinkCount = links.filter((l) => l.type === "sync").length;
30339
+ const asyncLinkCount = links.filter((l) => l.type === "async").length;
30340
+ const uncertainLinkCount = links.filter((l) => l._uncertain !== void 0).length;
30341
+ return {
30342
+ componentCount,
30343
+ linkCount: links.length,
30344
+ syncLinkCount,
30345
+ asyncLinkCount,
30346
+ uncertainLinkCount
30347
+ };
30348
+ }
30349
+ function formatSeconds(ms) {
30350
+ return (ms / 1e3).toFixed(2) + "s";
30351
+ }
30352
+ function formatExtractionStats(stats) {
30353
+ const lines = [`Components: ${stats.componentCount}`];
30354
+ if (stats.linkCount !== void 0) {
30355
+ lines.push(
30356
+ `Links: ${stats.linkCount} (sync: ${stats.syncLinkCount}, async: ${stats.asyncLinkCount})`
30357
+ );
30358
+ lines.push(`Uncertain: ${stats.uncertainLinkCount}`);
30359
+ }
30360
+ return lines;
30361
+ }
30362
+ function formatTimingLine(timings) {
30363
+ return `Extraction completed in ${formatSeconds(timings.totalMs)} (call graph: ${formatSeconds(timings.callGraphMs)}, detection: ${formatSeconds(timings.asyncDetectionMs)}, setup: ${formatSeconds(timings.setupMs)})`;
30364
+ }
30365
+
30210
30366
  // src/platform/infra/cli-presentation/categorize-components.ts
30211
30367
  function componentKey(component) {
30212
30368
  return `${component.domain}:${component.type}:${component.name}`;
@@ -30237,61 +30393,50 @@ function categorizeComponents(current, baseline) {
30237
30393
  };
30238
30394
  }
30239
30395
 
30240
- // src/features/extract/commands/run-extraction.ts
30241
- function runExtraction(options, resolvedConfig, configDir, sourceFilePaths) {
30242
- const project = loadExtractionProject(configDir, sourceFilePaths, options.tsConfig === false);
30243
- const draftComponents = loadOrExtractComponents(
30244
- project,
30245
- sourceFilePaths,
30246
- resolvedConfig,
30247
- configDir,
30248
- options.enrich
30249
- );
30396
+ // src/features/extract/infra/mappers/present-extraction-result.ts
30397
+ function presentExtractionResult(result, options) {
30398
+ if (result.kind === "draftOnly") {
30399
+ presentDraftResult(result.components, options);
30400
+ return;
30401
+ }
30402
+ presentFullResult(result, options);
30403
+ }
30404
+ function presentDraftResult(components, options) {
30250
30405
  if (options.dryRun) {
30251
- for (const line of formatDryRunOutput(draftComponents)) {
30406
+ for (const line of formatDryRunOutput(components)) {
30252
30407
  console.log(line);
30253
30408
  }
30254
30409
  return;
30255
30410
  }
30256
30411
  if (options.format === "markdown") {
30257
- const categorized = categorizeComponents(draftComponents, void 0);
30412
+ const categorized = categorizeComponents(components, void 0);
30258
30413
  const markdown = formatPrMarkdown(categorized);
30259
30414
  console.log(markdown);
30260
30415
  return;
30261
30416
  }
30262
- if (options.componentsOnly) {
30263
- outputResult(formatSuccess(draftComponents), options);
30264
- return;
30417
+ outputResult(formatSuccess(components), { output: options.output });
30418
+ }
30419
+ function presentFullResult(result, options) {
30420
+ if (result.failedFields.length > 0) {
30421
+ console.error(
30422
+ `Warning: Enrichment failed for ${result.failedFields.length} field(s): ${result.failedFields.join(", ")}`
30423
+ );
30265
30424
  }
30266
- const enrichmentResult = enrichComponentsSafe(
30267
- draftComponents,
30268
- resolvedConfig,
30269
- project,
30270
- configDir,
30271
- options.allowIncomplete === true
30272
- );
30273
- const repositoryInfo = getRepositoryInfo();
30274
- const { links } = detectConnectionsSafe(
30275
- project,
30276
- enrichmentResult.components,
30277
- resolvedConfig.modules.map((m) => m.path),
30278
- repositoryInfo.name,
30279
- options.allowIncomplete === true,
30280
- options.stats === true
30281
- );
30282
30425
  if (options.stats === true) {
30283
- const stats = countLinksByType(enrichmentResult.components.length, links);
30426
+ for (const timing of result.timings) {
30427
+ console.error(formatTimingLine(timing));
30428
+ }
30429
+ const stats = countLinksByType(result.components.length, result.links);
30284
30430
  for (const line of formatExtractionStats(stats)) {
30285
30431
  console.error(line);
30286
30432
  }
30287
30433
  }
30288
- const outputOptions = options.output === void 0 ? {} : { output: options.output };
30289
30434
  outputResult(
30290
30435
  formatSuccess({
30291
- components: enrichmentResult.components,
30292
- links
30436
+ components: result.components,
30437
+ links: result.links
30293
30438
  }),
30294
- outputOptions
30439
+ { output: options.output }
30295
30440
  );
30296
30441
  }
30297
30442
 
@@ -30305,7 +30450,8 @@ function createExtractCommand() {
30305
30450
  } = loadAndValidateConfig(options.config);
30306
30451
  const allSourceFilePaths = resolveSourceFiles(resolvedConfig, configDir);
30307
30452
  const sourceFilePaths = resolveFilteredSourceFiles(allSourceFilePaths, options);
30308
- runExtraction(options, resolvedConfig, configDir, sourceFilePaths);
30453
+ const result = runExtraction(options, resolvedConfig, configDir, sourceFilePaths);
30454
+ presentExtractionResult(result, options);
30309
30455
  });
30310
30456
  }
30311
30457
 
@@ -30383,8 +30529,8 @@ export {
30383
30529
  /* v8 ignore next -- @preserve: error is always Error from yaml parser; defensive guard */
30384
30530
  /* v8 ignore start -- @preserve: default executor delegates to execFileSync; tested via CLI integration */
30385
30531
  /* v8 ignore start -- @preserve: detectChangedTypeScriptFiles only throws GitError; non-GitError path is unreachable */
30386
- /* v8 ignore start -- @preserve: trivial comparator, Map keys guarantee a !== b */
30387
- /* v8 ignore start -- @preserve: dry-run output formatting; tested via CLI integration */
30388
30532
  /* v8 ignore start -- @preserve: git execution; mocked in all integration tests */
30389
30533
  /* v8 ignore start -- @preserve: defensive check; regex ([^/]+) requires non-empty groups */
30534
+ /* v8 ignore start -- @preserve: trivial comparator, Map keys guarantee a !== b */
30535
+ /* v8 ignore start -- @preserve: dry-run output formatting; tested via CLI integration */
30390
30536
  /* v8 ignore start -- @preserve: dry-run tested via CLI integration */