@living-architecture/riviere-cli 0.8.10 → 0.8.11

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 +327 -203
  2. package/dist/index.js +326 -202
  3. package/package.json +6 -6
package/dist/bin.js CHANGED
@@ -2980,7 +2980,7 @@ var require_compile = __commonJS({
2980
2980
  const schOrFunc = root.refs[ref];
2981
2981
  if (schOrFunc)
2982
2982
  return schOrFunc;
2983
- let _sch = resolve6.call(this, root, ref);
2983
+ let _sch = resolve8.call(this, root, ref);
2984
2984
  if (_sch === void 0) {
2985
2985
  const schema = (_a2 = root.localRefs) === null || _a2 === void 0 ? void 0 : _a2[ref];
2986
2986
  const { schemaId } = this.opts;
@@ -3007,7 +3007,7 @@ var require_compile = __commonJS({
3007
3007
  function sameSchemaEnv(s1, s2) {
3008
3008
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
3009
3009
  }
3010
- function resolve6(root, ref) {
3010
+ function resolve8(root, ref) {
3011
3011
  let sch;
3012
3012
  while (typeof (sch = this.refs[ref]) == "string")
3013
3013
  ref = sch;
@@ -3582,7 +3582,7 @@ var require_fast_uri = __commonJS({
3582
3582
  }
3583
3583
  return uri;
3584
3584
  }
3585
- function resolve6(baseURI, relativeURI, options) {
3585
+ function resolve8(baseURI, relativeURI, options) {
3586
3586
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3587
3587
  const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
3588
3588
  schemelessOptions.skipEscape = true;
@@ -3809,7 +3809,7 @@ var require_fast_uri = __commonJS({
3809
3809
  var fastUri = {
3810
3810
  SCHEMES,
3811
3811
  normalize,
3812
- resolve: resolve6,
3812
+ resolve: resolve8,
3813
3813
  resolveComponent,
3814
3814
  equal,
3815
3815
  serialize,
@@ -25180,7 +25180,8 @@ import { Command as Command21 } from "commander";
25180
25180
  // src/platform/infra/extraction-config/config-loader.ts
25181
25181
  import {
25182
25182
  dirname as dirname2,
25183
- resolve as resolve2
25183
+ resolve as resolve2,
25184
+ posix as posix3
25184
25185
  } from "node:path";
25185
25186
  import {
25186
25187
  existsSync as existsSync2,
@@ -25241,7 +25242,7 @@ var extraction_config_schema_default = {
25241
25242
  module: {
25242
25243
  type: "object",
25243
25244
  description: "A module defines extraction rules for a path pattern",
25244
- required: ["name", "path"],
25245
+ required: ["name", "path", "glob"],
25245
25246
  additionalProperties: false,
25246
25247
  properties: {
25247
25248
  name: {
@@ -25251,7 +25252,12 @@ var extraction_config_schema_default = {
25251
25252
  },
25252
25253
  path: {
25253
25254
  type: "string",
25254
- description: "Glob pattern for files in this module",
25255
+ description: "Module root directory relative to config file",
25256
+ minLength: 1
25257
+ },
25258
+ glob: {
25259
+ type: "string",
25260
+ description: "Glob pattern for source files within the module directory",
25255
25261
  minLength: 1
25256
25262
  },
25257
25263
  extends: {
@@ -26318,11 +26324,11 @@ function createFunctionComponent(func, filePath, domain2, componentType) {
26318
26324
  function findMatchingModule(filePath, modules, globMatcher, configDir) {
26319
26325
  const normalized = filePath.replaceAll(/\\+/g, "/");
26320
26326
  if (configDir === void 0) {
26321
- return modules.find((m) => globMatcher(normalized, m.path));
26327
+ return modules.find((m) => globMatcher(normalized, posix.join(m.path, m.glob)));
26322
26328
  }
26323
26329
  const normalizedConfigDir = configDir.replaceAll(/\\+/g, "/");
26324
26330
  const pathToMatch = posix.relative(normalizedConfigDir, normalized);
26325
- return modules.find((m) => globMatcher(pathToMatch, m.path));
26331
+ return modules.find((m) => globMatcher(pathToMatch, posix.join(m.path, m.glob)));
26326
26332
  }
26327
26333
 
26328
26334
  // ../riviere-extract-ts/dist/features/extraction/domain/config-resolution/config-resolution-errors.js
@@ -26360,6 +26366,7 @@ function resolveModule(moduleConfig, loader) {
26360
26366
  return {
26361
26367
  name: moduleConfig.name,
26362
26368
  path: moduleConfig.path,
26369
+ glob: moduleConfig.glob,
26363
26370
  api: requireRule(moduleConfig.api, "api", moduleConfig.name),
26364
26371
  useCase: requireRule(moduleConfig.useCase, "useCase", moduleConfig.name),
26365
26372
  domainOp: requireRule(moduleConfig.domainOp, "domainOp", moduleConfig.name),
@@ -26379,6 +26386,7 @@ function resolveModuleWithExtends(moduleConfig, extendsSource, loader) {
26379
26386
  return {
26380
26387
  name: moduleConfig.name,
26381
26388
  path: moduleConfig.path,
26389
+ glob: moduleConfig.glob,
26382
26390
  api: moduleConfig.api ?? baseModule.api,
26383
26391
  useCase: moduleConfig.useCase ?? baseModule.useCase,
26384
26392
  domainOp: moduleConfig.domainOp ?? baseModule.domainOp,
@@ -29298,7 +29306,7 @@ import { posix as posix2 } from "node:path";
29298
29306
  function findMatchingModule2(filePath, modules, globMatcher, configDir) {
29299
29307
  const normalized = filePath.replaceAll(/\\+/g, "/");
29300
29308
  const pathToMatch = posix2.relative(configDir.replaceAll(/\\+/g, "/"), normalized);
29301
- return modules.find((m) => globMatcher(pathToMatch, m.path));
29309
+ return modules.find((m) => globMatcher(pathToMatch, posix2.join(m.path, m.glob)));
29302
29310
  }
29303
29311
  function getBuiltInRule(module, componentType) {
29304
29312
  const ruleMap = {
@@ -29520,7 +29528,8 @@ var NOT_USED = { notUsed: true };
29520
29528
  function topLevelRulesToModule(parsed) {
29521
29529
  return {
29522
29530
  name: "extended",
29523
- path: "**",
29531
+ path: ".",
29532
+ glob: "**",
29524
29533
  api: parsed.api ?? NOT_USED,
29525
29534
  useCase: parsed.useCase ?? NOT_USED,
29526
29535
  domainOp: parsed.domainOp ?? NOT_USED,
@@ -29640,9 +29649,9 @@ function tryExpandModuleRefs(data, configDir) {
29640
29649
  }
29641
29650
  }
29642
29651
  function resolveSourceFiles(resolvedConfig, configDir) {
29643
- const sourceFilePaths = resolvedConfig.modules.flatMap((module) => globSync(module.path, { cwd: configDir })).map((filePath) => resolve2(configDir, filePath));
29652
+ const sourceFilePaths = resolvedConfig.modules.flatMap((module) => globSync(posix3.join(module.path, module.glob), { cwd: configDir })).map((filePath) => resolve2(configDir, filePath));
29644
29653
  if (sourceFilePaths.length === 0) {
29645
- const patterns = resolvedConfig.modules.map((m) => m.path).join(", ");
29654
+ const patterns = resolvedConfig.modules.map((m) => posix3.join(m.path, m.glob)).join(", ");
29646
29655
  throw new ConfigValidationError(
29647
29656
  "VALIDATION_ERROR" /* ValidationError */,
29648
29657
  `No files matched extraction patterns: ${patterns}
@@ -29897,111 +29906,6 @@ function validateFlagCombinations(options) {
29897
29906
  validateFormatOption(options);
29898
29907
  }
29899
29908
 
29900
- // src/platform/infra/cli-presentation/format-pr-markdown.ts
29901
- function formatComponentLine(component) {
29902
- return `- **${component.type}** \`${component.name}\` in \`${component.domain}\` domain`;
29903
- }
29904
- function formatSection(title, components) {
29905
- const header = `### ${title} (${components.length})`;
29906
- if (components.length === 0) {
29907
- return `${header}
29908
- None`;
29909
- }
29910
- return `${header}
29911
- ${components.map(formatComponentLine).join("\n")}`;
29912
- }
29913
- function formatPrMarkdown(categorized) {
29914
- const sections = [
29915
- formatSection("Added Components", categorized.added),
29916
- formatSection("Modified Components", categorized.modified),
29917
- formatSection("Removed Components", categorized.removed)
29918
- ];
29919
- return `## Architecture Changes
29920
-
29921
- ${sections.join("\n\n")}`;
29922
- }
29923
-
29924
- // src/platform/infra/cli-presentation/extract-output-formatter.ts
29925
- function compareByCodePoint2(a, b) {
29926
- if (a < b) return -1;
29927
- if (a > b) return 1;
29928
- return 0;
29929
- }
29930
- function formatDryRunOutput(components) {
29931
- const countsByDomain = /* @__PURE__ */ new Map();
29932
- for (const component of components) {
29933
- const existingTypeCounts = countsByDomain.get(component.domain);
29934
- const typeCounts = existingTypeCounts ?? /* @__PURE__ */ new Map();
29935
- if (existingTypeCounts === void 0) {
29936
- countsByDomain.set(component.domain, typeCounts);
29937
- }
29938
- const currentCount = typeCounts.get(component.type) ?? 0;
29939
- typeCounts.set(component.type, currentCount + 1);
29940
- }
29941
- const sortedDomains = [...countsByDomain.entries()].sort(([a], [b]) => compareByCodePoint2(a, b));
29942
- const lines = [];
29943
- for (const [domain2, typeCounts] of sortedDomains) {
29944
- const typeStrings = [...typeCounts.entries()].sort(([a], [b]) => compareByCodePoint2(a, b)).map(([type, count]) => `${type}(${count})`);
29945
- lines.push(`${domain2}: ${typeStrings.join(", ")}`);
29946
- }
29947
- return lines;
29948
- }
29949
-
29950
- // src/platform/infra/cli-presentation/output-writer.ts
29951
- import { writeFileSync } from "node:fs";
29952
- function outputResult(data, options) {
29953
- if (options.output !== void 0) {
29954
- try {
29955
- writeFileSync(options.output, JSON.stringify(data));
29956
- } catch {
29957
- console.log(
29958
- JSON.stringify(
29959
- formatError2(
29960
- "VALIDATION_ERROR" /* ValidationError */,
29961
- "Failed to write output file: " + options.output
29962
- )
29963
- )
29964
- );
29965
- process.exit(3 /* RuntimeError */);
29966
- }
29967
- return;
29968
- }
29969
- console.log(JSON.stringify(data));
29970
- }
29971
-
29972
- // src/platform/infra/cli-presentation/format-extraction-stats.ts
29973
- function countLinksByType(componentCount, links) {
29974
- const syncLinkCount = links.filter((l) => l.type === "sync").length;
29975
- const asyncLinkCount = links.filter((l) => l.type === "async").length;
29976
- const uncertainLinkCount = links.filter((l) => l._uncertain !== void 0).length;
29977
- return {
29978
- componentCount,
29979
- linkCount: links.length,
29980
- syncLinkCount,
29981
- asyncLinkCount,
29982
- uncertainLinkCount
29983
- };
29984
- }
29985
- function formatSeconds(ms) {
29986
- return (ms / 1e3).toFixed(2) + "s";
29987
- }
29988
- function formatExtractionStats(stats) {
29989
- const lines = [`Components: ${stats.componentCount}`];
29990
- if (stats.linkCount !== void 0) {
29991
- lines.push(
29992
- `Links: ${stats.linkCount} (sync: ${stats.syncLinkCount}, async: ${stats.asyncLinkCount})`
29993
- );
29994
- lines.push(`Uncertain: ${stats.uncertainLinkCount}`);
29995
- }
29996
- return lines;
29997
- }
29998
- function formatTimingLine(timings) {
29999
- return `Extraction completed in ${formatSeconds(timings.totalMs)} (call graph: ${formatSeconds(timings.callGraphMs)}, detection: ${formatSeconds(timings.asyncDetectionMs)}, setup: ${formatSeconds(timings.setupMs)})`;
30000
- }
30001
-
30002
- // src/features/extract/infra/safe-extraction-operations.ts
30003
- import "ts-morph";
30004
-
30005
29909
  // src/platform/infra/extraction-config/draft-component-loader.ts
30006
29910
  import {
30007
29911
  existsSync as existsSync4,
@@ -30040,43 +29944,6 @@ function loadDraftComponentsFromFile(filePath) {
30040
29944
  return parsed;
30041
29945
  }
30042
29946
 
30043
- // src/features/extract/infra/safe-extraction-operations.ts
30044
- function loadOrExtractComponents(project, sourceFilePaths, resolvedConfig, configDir, enrichPath) {
30045
- if (enrichPath === void 0) {
30046
- return extractComponents(project, sourceFilePaths, resolvedConfig, matchesGlob, configDir);
30047
- }
30048
- return loadDraftComponentsFromFile(enrichPath);
30049
- }
30050
- function enrichComponentsSafe(draftComponents, resolvedConfig, project, configDir, allowIncomplete) {
30051
- const result = enrichComponents(draftComponents, resolvedConfig, project, matchesGlob, configDir);
30052
- if (result.failures.length > 0) {
30053
- const failedFields = result.failures.map((f) => f.field);
30054
- if (!allowIncomplete) {
30055
- throw new ExtractionFieldFailureError(failedFields);
30056
- }
30057
- console.error(
30058
- `Warning: Enrichment failed for ${failedFields.length} field(s): ${failedFields.join(", ")}`
30059
- );
30060
- }
30061
- return result;
30062
- }
30063
- function detectConnectionsSafe(project, components, moduleGlobs, repository, allowIncomplete, showStats) {
30064
- const result = detectConnections(
30065
- project,
30066
- components,
30067
- {
30068
- allowIncomplete,
30069
- moduleGlobs,
30070
- repository
30071
- },
30072
- matchesGlob
30073
- );
30074
- if (showStats) {
30075
- console.error(formatTimingLine(result.timings));
30076
- }
30077
- return result;
30078
- }
30079
-
30080
29947
  // src/platform/infra/git/git-repository-info.ts
30081
29948
  import { execFileSync as execFileSync2 } from "node:child_process";
30082
29949
  var RepositoryUrlParseError = class extends Error {
@@ -30158,31 +30025,298 @@ function getRepositoryInfo(gitBinary = "git", cwd = process.cwd(), executor = de
30158
30025
  }
30159
30026
  }
30160
30027
 
30161
- // src/features/extract/infra/ts-morph/create-configured-project.ts
30028
+ // src/features/extract/infra/external-clients/create-module-contexts.ts
30029
+ import {
30030
+ posix as posix4,
30031
+ resolve as resolve7
30032
+ } from "node:path";
30033
+ import { globSync as globSync2 } from "glob";
30034
+
30035
+ // src/features/extract/infra/external-clients/find-module-tsconfig-dir.ts
30162
30036
  import { existsSync as existsSync5 } from "node:fs";
30163
30037
  import { resolve as resolve5 } from "node:path";
30164
- import { Project as Project3 } from "ts-morph";
30038
+ function findModuleTsConfigDir(configDir, modulePath) {
30039
+ const moduleTsConfigPath = resolve5(configDir, modulePath, "tsconfig.json");
30040
+ if (existsSync5(moduleTsConfigPath)) {
30041
+ return resolve5(configDir, modulePath);
30042
+ }
30043
+ return configDir;
30044
+ }
30045
+
30046
+ // src/features/extract/infra/external-clients/create-configured-project.ts
30047
+ import { existsSync as existsSync6 } from "node:fs";
30048
+ import { resolve as resolve6 } from "node:path";
30049
+ import { Project as Project2 } from "ts-morph";
30165
30050
  function createConfiguredProject(configDir, skipTsConfig) {
30166
30051
  if (skipTsConfig) {
30167
- return new Project3();
30052
+ return new Project2();
30168
30053
  }
30169
- const tsConfigPath = resolve5(configDir, "tsconfig.json");
30170
- if (!existsSync5(tsConfigPath)) {
30171
- return new Project3();
30054
+ const tsConfigPath = resolve6(configDir, "tsconfig.json");
30055
+ if (!existsSync6(tsConfigPath)) {
30056
+ return new Project2();
30172
30057
  }
30173
- return new Project3({
30058
+ return new Project2({
30174
30059
  tsConfigFilePath: tsConfigPath,
30175
30060
  skipAddingFilesFromTsConfig: true
30176
30061
  });
30177
30062
  }
30178
30063
 
30179
- // src/features/extract/infra/load-extraction-project.ts
30064
+ // src/features/extract/infra/external-clients/load-extraction-project.ts
30180
30065
  function loadExtractionProject(configDir, sourceFilePaths, skipTsConfig) {
30181
30066
  const project = createConfiguredProject(configDir, skipTsConfig);
30182
30067
  project.addSourceFilesAtPaths(sourceFilePaths);
30183
30068
  return project;
30184
30069
  }
30185
30070
 
30071
+ // src/features/extract/infra/external-clients/create-module-contexts.ts
30072
+ function createModuleContexts(resolvedConfig, configDir, sourceFilePaths, skipTsConfig) {
30073
+ const sourceFileSet = new Set(sourceFilePaths);
30074
+ return resolvedConfig.modules.map((module) => {
30075
+ const allModuleFiles = globSync2(posix4.join(module.path, module.glob), { cwd: configDir }).map(
30076
+ (f) => resolve7(configDir, f)
30077
+ );
30078
+ const moduleFiles = allModuleFiles.filter((f) => sourceFileSet.has(f));
30079
+ const tsConfigDir = findModuleTsConfigDir(configDir, module.path);
30080
+ const project = loadExtractionProject(tsConfigDir, moduleFiles, skipTsConfig);
30081
+ return {
30082
+ module,
30083
+ files: moduleFiles,
30084
+ project
30085
+ };
30086
+ });
30087
+ }
30088
+
30089
+ // src/features/extract/domain/extract-draft-components.ts
30090
+ function extractDraftComponents(moduleContexts, resolvedConfig, configDir) {
30091
+ return moduleContexts.flatMap(
30092
+ (ctx) => extractComponents(ctx.project, ctx.files, resolvedConfig, matchesGlob, configDir)
30093
+ );
30094
+ }
30095
+
30096
+ // src/features/extract/domain/enrich-per-module.ts
30097
+ var OrphanedDraftComponentError = class extends Error {
30098
+ constructor(orphanedModules, knownModules) {
30099
+ super(
30100
+ `Draft components reference unknown modules: [${orphanedModules.join(", ")}]. Known modules: [${knownModules.join(", ")}]`
30101
+ );
30102
+ this.name = "OrphanedDraftComponentError";
30103
+ }
30104
+ };
30105
+ function enrichPerModule(moduleContexts, draftComponents, resolvedConfig, configDir) {
30106
+ const moduleNames = new Set(moduleContexts.map((ctx) => ctx.module.name));
30107
+ const draftsByModule = groupDraftsByModule(draftComponents);
30108
+ assertAllDraftsMatchModules(draftsByModule, moduleNames);
30109
+ const components = [];
30110
+ const failedFieldSet = /* @__PURE__ */ new Set();
30111
+ for (const ctx of moduleContexts) {
30112
+ const moduleDrafts = draftsByModule.get(ctx.module.name) ?? [];
30113
+ if (moduleDrafts.length === 0) {
30114
+ continue;
30115
+ }
30116
+ const result = enrichComponents(
30117
+ moduleDrafts,
30118
+ resolvedConfig,
30119
+ ctx.project,
30120
+ matchesGlob,
30121
+ configDir
30122
+ );
30123
+ components.push(...result.components);
30124
+ for (const f of result.failures) {
30125
+ failedFieldSet.add(f.field);
30126
+ }
30127
+ }
30128
+ return {
30129
+ components,
30130
+ failedFields: [...failedFieldSet]
30131
+ };
30132
+ }
30133
+ function assertAllDraftsMatchModules(draftsByModule, moduleNames) {
30134
+ const orphanedModules = [...draftsByModule.keys()].filter((name) => !moduleNames.has(name));
30135
+ if (orphanedModules.length > 0) {
30136
+ throw new OrphanedDraftComponentError(orphanedModules, [...moduleNames]);
30137
+ }
30138
+ }
30139
+ function groupDraftsByModule(drafts) {
30140
+ const grouped = /* @__PURE__ */ new Map();
30141
+ for (const draft of drafts) {
30142
+ const existing = grouped.get(draft.domain);
30143
+ if (existing) {
30144
+ existing.push(draft);
30145
+ } else {
30146
+ grouped.set(draft.domain, [draft]);
30147
+ }
30148
+ }
30149
+ return grouped;
30150
+ }
30151
+
30152
+ // src/features/extract/domain/detect-connections-per-module.ts
30153
+ import { posix as posix5 } from "node:path";
30154
+ function detectConnectionsPerModule(moduleContexts, enrichedComponents, repositoryName, allowIncomplete) {
30155
+ const links = [];
30156
+ const timings = [];
30157
+ for (const ctx of moduleContexts) {
30158
+ const moduleComponents = enrichedComponents.filter((c) => c.domain === ctx.module.name);
30159
+ if (moduleComponents.length === 0) {
30160
+ continue;
30161
+ }
30162
+ const result = detectConnections(
30163
+ ctx.project,
30164
+ moduleComponents,
30165
+ {
30166
+ allowIncomplete,
30167
+ moduleGlobs: [posix5.join(ctx.module.path, ctx.module.glob)],
30168
+ repository: repositoryName
30169
+ },
30170
+ matchesGlob
30171
+ );
30172
+ links.push(...result.links);
30173
+ timings.push(result.timings);
30174
+ }
30175
+ return {
30176
+ links,
30177
+ timings
30178
+ };
30179
+ }
30180
+
30181
+ // src/features/extract/commands/run-extraction.ts
30182
+ function runExtraction(options, resolvedConfig, configDir, sourceFilePaths) {
30183
+ const skipTsConfig = options.tsConfig === false;
30184
+ const moduleContexts = createModuleContexts(
30185
+ resolvedConfig,
30186
+ configDir,
30187
+ sourceFilePaths,
30188
+ skipTsConfig
30189
+ );
30190
+ const draftComponents = options.enrich === void 0 ? extractDraftComponents(moduleContexts, resolvedConfig, configDir) : loadDraftComponentsFromFile(options.enrich);
30191
+ if (options.dryRun || options.format === "markdown" || options.componentsOnly) {
30192
+ return {
30193
+ kind: "draftOnly",
30194
+ components: draftComponents
30195
+ };
30196
+ }
30197
+ const allowIncomplete = options.allowIncomplete === true;
30198
+ const enrichment = enrichPerModule(moduleContexts, draftComponents, resolvedConfig, configDir);
30199
+ if (enrichment.failedFields.length > 0 && !allowIncomplete) {
30200
+ throw new ExtractionFieldFailureError(enrichment.failedFields);
30201
+ }
30202
+ const repositoryInfo = getRepositoryInfo();
30203
+ const connectionResult = detectConnectionsPerModule(
30204
+ moduleContexts,
30205
+ enrichment.components,
30206
+ repositoryInfo.name,
30207
+ allowIncomplete
30208
+ );
30209
+ return {
30210
+ kind: "full",
30211
+ components: enrichment.components,
30212
+ links: connectionResult.links,
30213
+ timings: connectionResult.timings,
30214
+ failedFields: enrichment.failedFields
30215
+ };
30216
+ }
30217
+
30218
+ // src/platform/infra/cli-presentation/format-pr-markdown.ts
30219
+ function formatComponentLine(component) {
30220
+ return `- **${component.type}** \`${component.name}\` in \`${component.domain}\` domain`;
30221
+ }
30222
+ function formatSection(title, components) {
30223
+ const header = `### ${title} (${components.length})`;
30224
+ if (components.length === 0) {
30225
+ return `${header}
30226
+ None`;
30227
+ }
30228
+ return `${header}
30229
+ ${components.map(formatComponentLine).join("\n")}`;
30230
+ }
30231
+ function formatPrMarkdown(categorized) {
30232
+ const sections = [
30233
+ formatSection("Added Components", categorized.added),
30234
+ formatSection("Modified Components", categorized.modified),
30235
+ formatSection("Removed Components", categorized.removed)
30236
+ ];
30237
+ return `## Architecture Changes
30238
+
30239
+ ${sections.join("\n\n")}`;
30240
+ }
30241
+
30242
+ // src/platform/infra/cli-presentation/extract-output-formatter.ts
30243
+ function compareByCodePoint2(a, b) {
30244
+ if (a < b) return -1;
30245
+ if (a > b) return 1;
30246
+ return 0;
30247
+ }
30248
+ function formatDryRunOutput(components) {
30249
+ const countsByDomain = /* @__PURE__ */ new Map();
30250
+ for (const component of components) {
30251
+ const existingTypeCounts = countsByDomain.get(component.domain);
30252
+ const typeCounts = existingTypeCounts ?? /* @__PURE__ */ new Map();
30253
+ if (existingTypeCounts === void 0) {
30254
+ countsByDomain.set(component.domain, typeCounts);
30255
+ }
30256
+ const currentCount = typeCounts.get(component.type) ?? 0;
30257
+ typeCounts.set(component.type, currentCount + 1);
30258
+ }
30259
+ const sortedDomains = [...countsByDomain.entries()].sort(([a], [b]) => compareByCodePoint2(a, b));
30260
+ const lines = [];
30261
+ for (const [domain2, typeCounts] of sortedDomains) {
30262
+ const typeStrings = [...typeCounts.entries()].sort(([a], [b]) => compareByCodePoint2(a, b)).map(([type, count]) => `${type}(${count})`);
30263
+ lines.push(`${domain2}: ${typeStrings.join(", ")}`);
30264
+ }
30265
+ return lines;
30266
+ }
30267
+
30268
+ // src/platform/infra/cli-presentation/output-writer.ts
30269
+ import { writeFileSync } from "node:fs";
30270
+ function outputResult(data, options) {
30271
+ if (options.output !== void 0) {
30272
+ try {
30273
+ writeFileSync(options.output, JSON.stringify(data));
30274
+ } catch {
30275
+ console.log(
30276
+ JSON.stringify(
30277
+ formatError2(
30278
+ "VALIDATION_ERROR" /* ValidationError */,
30279
+ "Failed to write output file: " + options.output
30280
+ )
30281
+ )
30282
+ );
30283
+ process.exit(3 /* RuntimeError */);
30284
+ }
30285
+ return;
30286
+ }
30287
+ console.log(JSON.stringify(data));
30288
+ }
30289
+
30290
+ // src/platform/infra/cli-presentation/format-extraction-stats.ts
30291
+ function countLinksByType(componentCount, links) {
30292
+ const syncLinkCount = links.filter((l) => l.type === "sync").length;
30293
+ const asyncLinkCount = links.filter((l) => l.type === "async").length;
30294
+ const uncertainLinkCount = links.filter((l) => l._uncertain !== void 0).length;
30295
+ return {
30296
+ componentCount,
30297
+ linkCount: links.length,
30298
+ syncLinkCount,
30299
+ asyncLinkCount,
30300
+ uncertainLinkCount
30301
+ };
30302
+ }
30303
+ function formatSeconds(ms) {
30304
+ return (ms / 1e3).toFixed(2) + "s";
30305
+ }
30306
+ function formatExtractionStats(stats) {
30307
+ const lines = [`Components: ${stats.componentCount}`];
30308
+ if (stats.linkCount !== void 0) {
30309
+ lines.push(
30310
+ `Links: ${stats.linkCount} (sync: ${stats.syncLinkCount}, async: ${stats.asyncLinkCount})`
30311
+ );
30312
+ lines.push(`Uncertain: ${stats.uncertainLinkCount}`);
30313
+ }
30314
+ return lines;
30315
+ }
30316
+ function formatTimingLine(timings) {
30317
+ return `Extraction completed in ${formatSeconds(timings.totalMs)} (call graph: ${formatSeconds(timings.callGraphMs)}, detection: ${formatSeconds(timings.asyncDetectionMs)}, setup: ${formatSeconds(timings.setupMs)})`;
30318
+ }
30319
+
30186
30320
  // src/platform/infra/cli-presentation/categorize-components.ts
30187
30321
  function componentKey(component) {
30188
30322
  return `${component.domain}:${component.type}:${component.name}`;
@@ -30213,61 +30347,50 @@ function categorizeComponents(current, baseline) {
30213
30347
  };
30214
30348
  }
30215
30349
 
30216
- // src/features/extract/commands/run-extraction.ts
30217
- function runExtraction(options, resolvedConfig, configDir, sourceFilePaths) {
30218
- const project = loadExtractionProject(configDir, sourceFilePaths, options.tsConfig === false);
30219
- const draftComponents = loadOrExtractComponents(
30220
- project,
30221
- sourceFilePaths,
30222
- resolvedConfig,
30223
- configDir,
30224
- options.enrich
30225
- );
30350
+ // src/features/extract/infra/mappers/present-extraction-result.ts
30351
+ function presentExtractionResult(result, options) {
30352
+ if (result.kind === "draftOnly") {
30353
+ presentDraftResult(result.components, options);
30354
+ return;
30355
+ }
30356
+ presentFullResult(result, options);
30357
+ }
30358
+ function presentDraftResult(components, options) {
30226
30359
  if (options.dryRun) {
30227
- for (const line of formatDryRunOutput(draftComponents)) {
30360
+ for (const line of formatDryRunOutput(components)) {
30228
30361
  console.log(line);
30229
30362
  }
30230
30363
  return;
30231
30364
  }
30232
30365
  if (options.format === "markdown") {
30233
- const categorized = categorizeComponents(draftComponents, void 0);
30366
+ const categorized = categorizeComponents(components, void 0);
30234
30367
  const markdown = formatPrMarkdown(categorized);
30235
30368
  console.log(markdown);
30236
30369
  return;
30237
30370
  }
30238
- if (options.componentsOnly) {
30239
- outputResult(formatSuccess(draftComponents), options);
30240
- return;
30371
+ outputResult(formatSuccess(components), { output: options.output });
30372
+ }
30373
+ function presentFullResult(result, options) {
30374
+ if (result.failedFields.length > 0) {
30375
+ console.error(
30376
+ `Warning: Enrichment failed for ${result.failedFields.length} field(s): ${result.failedFields.join(", ")}`
30377
+ );
30241
30378
  }
30242
- const enrichmentResult = enrichComponentsSafe(
30243
- draftComponents,
30244
- resolvedConfig,
30245
- project,
30246
- configDir,
30247
- options.allowIncomplete === true
30248
- );
30249
- const repositoryInfo = getRepositoryInfo();
30250
- const { links } = detectConnectionsSafe(
30251
- project,
30252
- enrichmentResult.components,
30253
- resolvedConfig.modules.map((m) => m.path),
30254
- repositoryInfo.name,
30255
- options.allowIncomplete === true,
30256
- options.stats === true
30257
- );
30258
30379
  if (options.stats === true) {
30259
- const stats = countLinksByType(enrichmentResult.components.length, links);
30380
+ for (const timing of result.timings) {
30381
+ console.error(formatTimingLine(timing));
30382
+ }
30383
+ const stats = countLinksByType(result.components.length, result.links);
30260
30384
  for (const line of formatExtractionStats(stats)) {
30261
30385
  console.error(line);
30262
30386
  }
30263
30387
  }
30264
- const outputOptions = options.output === void 0 ? {} : { output: options.output };
30265
30388
  outputResult(
30266
30389
  formatSuccess({
30267
- components: enrichmentResult.components,
30268
- links
30390
+ components: result.components,
30391
+ links: result.links
30269
30392
  }),
30270
- outputOptions
30393
+ { output: options.output }
30271
30394
  );
30272
30395
  }
30273
30396
 
@@ -30281,7 +30404,8 @@ function createExtractCommand() {
30281
30404
  } = loadAndValidateConfig(options.config);
30282
30405
  const allSourceFilePaths = resolveSourceFiles(resolvedConfig, configDir);
30283
30406
  const sourceFilePaths = resolveFilteredSourceFiles(allSourceFilePaths, options);
30284
- runExtraction(options, resolvedConfig, configDir, sourceFilePaths);
30407
+ const result = runExtraction(options, resolvedConfig, configDir, sourceFilePaths);
30408
+ presentExtractionResult(result, options);
30285
30409
  });
30286
30410
  }
30287
30411
 
@@ -30297,7 +30421,7 @@ function parsePackageJson(pkg) {
30297
30421
  }
30298
30422
  function loadPackageJson() {
30299
30423
  if (true) {
30300
- return { version: "0.8.9" };
30424
+ return { version: "0.8.10" };
30301
30425
  }
30302
30426
  const require2 = createRequire2(import.meta.url);
30303
30427
  return parsePackageJson(require2("../../package.json"));
@@ -30395,8 +30519,8 @@ program.parseAsync().catch(handleGlobalError);
30395
30519
  /* v8 ignore next -- @preserve: error is always Error from yaml parser; defensive guard */
30396
30520
  /* v8 ignore start -- @preserve: default executor delegates to execFileSync; tested via CLI integration */
30397
30521
  /* v8 ignore start -- @preserve: detectChangedTypeScriptFiles only throws GitError; non-GitError path is unreachable */
30398
- /* v8 ignore start -- @preserve: trivial comparator, Map keys guarantee a !== b */
30399
- /* v8 ignore start -- @preserve: dry-run output formatting; tested via CLI integration */
30400
30522
  /* v8 ignore start -- @preserve: git execution; mocked in all integration tests */
30401
30523
  /* v8 ignore start -- @preserve: defensive check; regex ([^/]+) requires non-empty groups */
30524
+ /* v8 ignore start -- @preserve: trivial comparator, Map keys guarantee a !== b */
30525
+ /* v8 ignore start -- @preserve: dry-run output formatting; tested via CLI integration */
30402
30526
  /* v8 ignore start -- @preserve: dry-run tested via CLI integration */