@living-architecture/riviere-cli 0.8.12 → 0.9.0

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 +610 -435
  2. package/dist/index.js +614 -436
  3. package/package.json +6 -6
package/dist/bin.js CHANGED
@@ -23541,13 +23541,6 @@ async function fileExists(path2) {
23541
23541
  }
23542
23542
 
23543
23543
  // src/platform/infra/cli-presentation/error-codes.ts
23544
- var ExtractionFieldFailureError = class extends Error {
23545
- constructor(failedFields) {
23546
- const uniqueFields = [...new Set(failedFields)];
23547
- super(`Extraction failed for fields: ${uniqueFields.join(", ")}`);
23548
- this.name = "ExtractionFieldFailureError";
23549
- }
23550
- };
23551
23544
  var ConfigValidationError = class extends Error {
23552
23545
  errorCode;
23553
23546
  constructor(code, message) {
@@ -25177,6 +25170,69 @@ Examples:
25177
25170
  // src/features/extract/entrypoint/extract.ts
25178
25171
  import { Command as Command21 } from "commander";
25179
25172
 
25173
+ // src/platform/infra/cli-presentation/exit-with-cli-error.ts
25174
+ function exitWithCliError(code, message, exitCode) {
25175
+ console.log(JSON.stringify(formatError2(code, message)));
25176
+ process.exit(exitCode);
25177
+ }
25178
+
25179
+ // src/platform/infra/cli-presentation/extract-validator.ts
25180
+ function rejectMutuallyExclusive(flagA, flagB, aPresent, bPresent) {
25181
+ if (aPresent && bPresent) {
25182
+ throw new ConfigValidationError(
25183
+ "VALIDATION_ERROR" /* ValidationError */,
25184
+ `${flagA} and ${flagB} cannot be used together`
25185
+ );
25186
+ }
25187
+ }
25188
+ function validateMutualExclusions(options) {
25189
+ rejectMutuallyExclusive(
25190
+ "--components-only",
25191
+ "--enrich",
25192
+ options.componentsOnly === true,
25193
+ options.enrich !== void 0
25194
+ );
25195
+ rejectMutuallyExclusive("--pr", "--files", options.pr === true, options.files !== void 0);
25196
+ rejectMutuallyExclusive("--pr", "--enrich", options.pr === true, options.enrich !== void 0);
25197
+ rejectMutuallyExclusive(
25198
+ "--files",
25199
+ "--enrich",
25200
+ options.files !== void 0,
25201
+ options.enrich !== void 0
25202
+ );
25203
+ }
25204
+ function validateFormatOption(options) {
25205
+ if (options.format !== void 0 && options.format !== "json" && options.format !== "markdown") {
25206
+ throw new ConfigValidationError(
25207
+ "VALIDATION_ERROR" /* ValidationError */,
25208
+ `Invalid format '${options.format}'. Must be 'json' or 'markdown'.`
25209
+ );
25210
+ }
25211
+ if (options.format === "markdown" && !options.pr && options.files === void 0) {
25212
+ throw new ConfigValidationError(
25213
+ "VALIDATION_ERROR" /* ValidationError */,
25214
+ "--format markdown requires --pr or --files"
25215
+ );
25216
+ }
25217
+ }
25218
+ function validateFlagCombinations(options) {
25219
+ validateMutualExclusions(options);
25220
+ if (options.base !== void 0 && !options.pr) {
25221
+ throw new ConfigValidationError(
25222
+ "VALIDATION_ERROR" /* ValidationError */,
25223
+ "--base can only be used with --pr"
25224
+ );
25225
+ }
25226
+ validateFormatOption(options);
25227
+ }
25228
+
25229
+ // src/features/extract/infra/persistence/extraction-project/extraction-project-repository.ts
25230
+ import {
25231
+ posix as posix5,
25232
+ resolve as resolve7
25233
+ } from "node:path";
25234
+ import { globSync as globSync2 } from "glob";
25235
+
25180
25236
  // src/platform/infra/extraction-config/config-loader.ts
25181
25237
  import {
25182
25238
  dirname as dirname2,
@@ -29701,13 +29757,46 @@ ${formatValidationErrors2(validationResult.errors)}`
29701
29757
  };
29702
29758
  }
29703
29759
 
29704
- // src/platform/infra/source-filtering/filter-source-files.ts
29705
- import { existsSync as existsSync3 } from "node:fs";
29706
- import { resolve as resolve4 } from "node:path";
29760
+ // src/platform/infra/extraction-config/draft-component-loader.ts
29761
+ import {
29762
+ existsSync as existsSync3,
29763
+ readFileSync as readFileSync3
29764
+ } from "node:fs";
29765
+ var DraftComponentLoadError = class extends Error {
29766
+ constructor(message) {
29767
+ super(message);
29768
+ this.name = "DraftComponentLoadError";
29769
+ Error.captureStackTrace?.(this, this.constructor);
29770
+ }
29771
+ };
29772
+ function isDraftComponentArray(value) {
29773
+ if (!Array.isArray(value)) return false;
29774
+ return value.every(
29775
+ (item) => typeof item === "object" && item !== null && "type" in item && "name" in item && "domain" in item && "location" in item
29776
+ );
29777
+ }
29778
+ function parseJsonFile(filePath) {
29779
+ try {
29780
+ return JSON.parse(readFileSync3(filePath, "utf-8"));
29781
+ } catch {
29782
+ throw new DraftComponentLoadError(`Enrich file contains invalid JSON: ${filePath}`);
29783
+ }
29784
+ }
29785
+ function loadDraftComponentsFromFile(filePath) {
29786
+ if (!existsSync3(filePath)) {
29787
+ throw new DraftComponentLoadError(`Enrich file not found: ${filePath}`);
29788
+ }
29789
+ const parsed = parseJsonFile(filePath);
29790
+ if (!isDraftComponentArray(parsed)) {
29791
+ throw new DraftComponentLoadError(
29792
+ `Enrich file does not contain valid draft components: ${filePath}`
29793
+ );
29794
+ }
29795
+ return parsed;
29796
+ }
29707
29797
 
29708
- // src/platform/infra/git/git-changed-files.ts
29798
+ // src/platform/infra/git/git-repository-info.ts
29709
29799
  import { execFileSync } from "node:child_process";
29710
- import { resolve as resolve3 } from "node:path";
29711
29800
 
29712
29801
  // src/platform/infra/git/git-errors.ts
29713
29802
  var GitError = class extends Error {
@@ -29719,8 +29808,22 @@ var GitError = class extends Error {
29719
29808
  Error.captureStackTrace?.(this, this.constructor);
29720
29809
  }
29721
29810
  };
29722
- function isGitError(error48) {
29723
- return error48 instanceof GitError;
29811
+
29812
+ // src/platform/infra/git/git-repository-info.ts
29813
+ var RepositoryUrlParseError = class extends Error {
29814
+ /* v8 ignore start -- @preserve: Error constructor; tested via integration */
29815
+ constructor(url2) {
29816
+ super(`Expected owner and repo in git URL, got ${url2}`);
29817
+ this.name = "RepositoryUrlParseError";
29818
+ }
29819
+ /* v8 ignore stop */
29820
+ };
29821
+ function defaultGitExecutor(binary, args, cwd) {
29822
+ return execFileSync(binary, args, {
29823
+ cwd,
29824
+ encoding: "utf-8",
29825
+ stdio: ["pipe", "pipe", "pipe"]
29826
+ }).trim();
29724
29827
  }
29725
29828
  function extractStderr(error48) {
29726
29829
  if (!Object.hasOwn(error48, "stderr")) {
@@ -29732,16 +29835,98 @@ function extractStderr(error48) {
29732
29835
  }
29733
29836
  return String(stderrValue);
29734
29837
  }
29838
+ function runGit(executor, gitBinary, cwd, args) {
29839
+ try {
29840
+ return executor(gitBinary, args, cwd);
29841
+ } catch (error48) {
29842
+ if (error48 instanceof Error && "code" in error48 && error48.code === "ENOENT") {
29843
+ throw new GitError("GIT_NOT_FOUND", "Install git to detect repository information.");
29844
+ }
29845
+ if (error48 instanceof Error) {
29846
+ const stderr = extractStderr(error48);
29847
+ if (stderr.includes("not a git repository")) {
29848
+ throw new GitError("NOT_A_REPOSITORY", "Run from within a git repository.");
29849
+ }
29850
+ }
29851
+ throw error48;
29852
+ }
29853
+ }
29854
+ function parseRepositoryUrl(url2) {
29855
+ const sshRegex = /^git@[^:]+:([^/]+)\/(.+?)(?:\.git)?$/;
29856
+ const sshMatch = sshRegex.exec(url2);
29857
+ if (sshMatch) {
29858
+ const [, owner, repo] = sshMatch;
29859
+ if (!owner || !repo) {
29860
+ throw new RepositoryUrlParseError(url2);
29861
+ }
29862
+ return {
29863
+ name: `${owner}/${repo}`,
29864
+ owner,
29865
+ url: url2
29866
+ };
29867
+ }
29868
+ const httpsRegex = /^https?:\/\/[^/]+\/([^/]+)\/(.+?)(?:\.git)?$/;
29869
+ const httpsMatch = httpsRegex.exec(url2);
29870
+ if (httpsMatch) {
29871
+ const [, owner, repo] = httpsMatch;
29872
+ if (!owner || !repo) {
29873
+ throw new RepositoryUrlParseError(url2);
29874
+ }
29875
+ return {
29876
+ name: `${owner}/${repo}`,
29877
+ owner,
29878
+ url: url2
29879
+ };
29880
+ }
29881
+ return {
29882
+ name: url2,
29883
+ url: url2
29884
+ };
29885
+ }
29886
+ function getRepositoryInfo(gitBinary = "git", cwd = process.cwd(), executor = defaultGitExecutor) {
29887
+ try {
29888
+ const url2 = runGit(executor, gitBinary, cwd, ["remote", "get-url", "origin"]);
29889
+ return parseRepositoryUrl(url2);
29890
+ } catch (error48) {
29891
+ if (error48 instanceof GitError) throw error48;
29892
+ if (error48 instanceof Error) {
29893
+ const stderr = extractStderr(error48);
29894
+ if (stderr.includes("No such remote")) {
29895
+ throw new GitError("NO_REMOTE", 'No git remote named "origin" found.');
29896
+ }
29897
+ }
29898
+ throw error48;
29899
+ }
29900
+ }
29901
+
29902
+ // src/platform/infra/source-filtering/filter-source-files.ts
29903
+ import { existsSync as existsSync4 } from "node:fs";
29904
+ import { resolve as resolve4 } from "node:path";
29735
29905
 
29736
29906
  // src/platform/infra/git/git-changed-files.ts
29737
- function defaultGitExecutor(binary, args, cwd) {
29738
- return execFileSync(binary, args, {
29907
+ import { execFileSync as execFileSync2 } from "node:child_process";
29908
+ import { resolve as resolve3 } from "node:path";
29909
+ function extractStderr2(error48) {
29910
+ if (!Object.hasOwn(error48, "stderr")) {
29911
+ throw error48;
29912
+ }
29913
+ const stderrValue = Object.getOwnPropertyDescriptor(error48, "stderr")?.value;
29914
+ if (!stderrValue) {
29915
+ throw error48;
29916
+ }
29917
+ return String(stderrValue);
29918
+ }
29919
+ function isGitError(error48) {
29920
+ return error48 instanceof GitError;
29921
+ }
29922
+ function defaultGitExecutor2(binary, args, cwd) {
29923
+ return execFileSync2(binary, args, {
29739
29924
  cwd,
29740
29925
  encoding: "utf-8",
29741
29926
  stdio: ["pipe", "pipe", "pipe"]
29742
29927
  }).trim();
29743
29928
  }
29744
- function runGit(executor, gitBinary, cwd, args) {
29929
+ function runGit2(executor, gitBinary, cwd, args) {
29745
29930
  try {
29746
29931
  return executor(gitBinary, args, cwd);
29747
29932
  } catch (error48) {
@@ -29749,7 +29934,7 @@ function runGit(executor, gitBinary, cwd, args) {
29749
29934
  throw new GitError("GIT_NOT_FOUND", "Install git to use --pr flag.");
29750
29935
  }
29751
29936
  if (error48 instanceof Error) {
29752
- const stderr = extractStderr(error48);
29937
+ const stderr = extractStderr2(error48);
29753
29938
  if (stderr.includes("not a git repository")) {
29754
29939
  throw new GitError("NOT_A_REPOSITORY", "Run from within a git repository.");
29755
29940
  }
@@ -29762,7 +29947,7 @@ function isTypeScriptFile(filePath) {
29762
29947
  }
29763
29948
  function isDetachedHead(executor, gitBinary, cwd) {
29764
29949
  try {
29765
- runGit(executor, gitBinary, cwd, ["symbolic-ref", "HEAD"]);
29950
+ runGit2(executor, gitBinary, cwd, ["symbolic-ref", "HEAD"]);
29766
29951
  return false;
29767
29952
  } catch {
29768
29953
  return true;
@@ -29773,25 +29958,25 @@ function detectBaseBranch(executor, gitBinary, cwd, explicitBase) {
29773
29958
  return explicitBase;
29774
29959
  }
29775
29960
  try {
29776
- const ref = runGit(executor, gitBinary, cwd, ["symbolic-ref", "refs/remotes/origin/HEAD"]);
29961
+ const ref = runGit2(executor, gitBinary, cwd, ["symbolic-ref", "refs/remotes/origin/HEAD"]);
29777
29962
  return ref.replace("refs/remotes/origin/", "");
29778
29963
  } catch {
29779
29964
  return "main";
29780
29965
  }
29781
29966
  }
29782
29967
  function getUntrackedTypeScriptFiles(executor, gitBinary, cwd) {
29783
- const output = runGit(executor, gitBinary, cwd, ["ls-files", "--others", "--exclude-standard"]);
29968
+ const output = runGit2(executor, gitBinary, cwd, ["ls-files", "--others", "--exclude-standard"]);
29784
29969
  if (output === "") return [];
29785
29970
  return output.split("\n").filter(isTypeScriptFile);
29786
29971
  }
29787
29972
  function getStagedFiles(executor, gitBinary, cwd, base) {
29788
- const output = runGit(executor, gitBinary, cwd, ["diff", "--name-only", "--cached", base]);
29973
+ const output = runGit2(executor, gitBinary, cwd, ["diff", "--name-only", "--cached", base]);
29789
29974
  if (output === "") return [];
29790
29975
  return output.split("\n").filter(isTypeScriptFile);
29791
29976
  }
29792
29977
  function getCommittedChangedFiles(executor, gitBinary, cwd, base) {
29793
29978
  try {
29794
- const output = runGit(executor, gitBinary, cwd, ["diff", "--name-only", `${base}...HEAD`]);
29979
+ const output = runGit2(executor, gitBinary, cwd, ["diff", "--name-only", `${base}...HEAD`]);
29795
29980
  if (output === "") return [];
29796
29981
  return output.split("\n").filter(isTypeScriptFile);
29797
29982
  } catch (error48) {
@@ -29799,9 +29984,9 @@ function getCommittedChangedFiles(executor, gitBinary, cwd, base) {
29799
29984
  throw new GitError("BASE_BRANCH_NOT_FOUND", `Base branch '${base}' not found.`);
29800
29985
  }
29801
29986
  }
29802
- function detectChangedTypeScriptFiles(cwd, options, executor = defaultGitExecutor) {
29987
+ function detectChangedTypeScriptFiles(cwd, options, executor = defaultGitExecutor2) {
29803
29988
  const gitBinary = "git";
29804
- runGit(executor, gitBinary, cwd, ["rev-parse", "--git-dir"]);
29989
+ runGit2(executor, gitBinary, cwd, ["rev-parse", "--git-dir"]);
29805
29990
  const warnings = [];
29806
29991
  const detached = isDetachedHead(executor, gitBinary, cwd);
29807
29992
  const base = detached ? "HEAD~1" : detectBaseBranch(executor, gitBinary, cwd, options.base);
@@ -29847,7 +30032,7 @@ function filterSourceFiles(allSourceFiles, options) {
29847
30032
  }
29848
30033
  }
29849
30034
  if (options.files !== void 0) {
29850
- const missingFiles = options.files.filter((f) => !existsSync3(resolve4(f)));
30035
+ const missingFiles = options.files.filter((f) => !existsSync4(resolve4(f)));
29851
30036
  if (missingFiles.length > 0) {
29852
30037
  throw new SourceFilterError("FILES_NOT_FOUND", `Files not found: ${missingFiles.join(", ")}`);
29853
30038
  }
@@ -29860,203 +30045,191 @@ function resolveFilteredSourceFiles(allSourceFiles, options) {
29860
30045
  return filterSourceFiles(allSourceFiles, options).files;
29861
30046
  }
29862
30047
 
29863
- // src/platform/infra/cli-presentation/extract-validator.ts
29864
- function rejectMutuallyExclusive(flagA, flagB, aPresent, bPresent) {
29865
- if (aPresent && bPresent) {
29866
- throw new ConfigValidationError(
29867
- "VALIDATION_ERROR" /* ValidationError */,
29868
- `${flagA} and ${flagB} cannot be used together`
30048
+ // src/features/extract/domain/extraction-project.ts
30049
+ import { posix as posix4 } from "node:path";
30050
+ var OrphanedDraftComponentError = class extends Error {
30051
+ constructor(orphanedModules, knownModules) {
30052
+ super(
30053
+ `Draft components reference unknown modules: [${orphanedModules.join(", ")}]. Known modules: [${knownModules.join(", ")}]`
29869
30054
  );
30055
+ this.name = "OrphanedDraftComponentError";
29870
30056
  }
29871
- }
29872
- function validateMutualExclusions(options) {
29873
- rejectMutuallyExclusive(
29874
- "--components-only",
29875
- "--enrich",
29876
- options.componentsOnly === true,
29877
- options.enrich !== void 0
29878
- );
29879
- rejectMutuallyExclusive("--pr", "--files", options.pr === true, options.files !== void 0);
29880
- rejectMutuallyExclusive("--pr", "--enrich", options.pr === true, options.enrich !== void 0);
29881
- rejectMutuallyExclusive(
29882
- "--files",
29883
- "--enrich",
29884
- options.files !== void 0,
29885
- options.enrich !== void 0
29886
- );
29887
- }
29888
- function validateFormatOption(options) {
29889
- if (options.format !== void 0 && options.format !== "json" && options.format !== "markdown") {
29890
- throw new ConfigValidationError(
29891
- "VALIDATION_ERROR" /* ValidationError */,
29892
- `Invalid format '${options.format}'. Must be 'json' or 'markdown'.`
30057
+ };
30058
+ var ExtractionProject = class {
30059
+ constructor(configDir, moduleContexts, resolvedConfig, repositoryName, draftComponents = []) {
30060
+ this.configDir = configDir;
30061
+ this.moduleContexts = moduleContexts;
30062
+ this.resolvedConfig = resolvedConfig;
30063
+ this.repositoryName = repositoryName;
30064
+ this.draftComponents = draftComponents;
30065
+ }
30066
+ extractDraftComponents(options) {
30067
+ const draftComponents = this.moduleContexts.flatMap(
30068
+ (moduleContext) => extractComponents(
30069
+ moduleContext.project,
30070
+ moduleContext.files,
30071
+ this.resolvedConfig,
30072
+ matchesGlob,
30073
+ this.configDir
30074
+ )
29893
30075
  );
30076
+ if (!options.includeConnections) {
30077
+ return {
30078
+ kind: "draftOnly",
30079
+ components: draftComponents
30080
+ };
30081
+ }
30082
+ const enrichment = this.enrichDraftComponentValues(draftComponents, options.allowIncomplete);
30083
+ if (enrichment.kind === "fieldFailure") {
30084
+ return enrichment;
30085
+ }
30086
+ const connectionResult = this.detectConnections(enrichment.components, options.allowIncomplete);
30087
+ return {
30088
+ kind: "full",
30089
+ components: enrichment.components,
30090
+ failedFields: enrichment.failedFields,
30091
+ links: connectionResult.links,
30092
+ timings: connectionResult.timings
30093
+ };
29894
30094
  }
29895
- if (options.format === "markdown" && !options.pr && options.files === void 0) {
29896
- throw new ConfigValidationError(
29897
- "VALIDATION_ERROR" /* ValidationError */,
29898
- "--format markdown requires --pr or --files"
30095
+ enrichDraftComponents(options) {
30096
+ if (!options.includeConnections) {
30097
+ return {
30098
+ kind: "draftOnly",
30099
+ components: this.draftComponents
30100
+ };
30101
+ }
30102
+ const enrichment = this.enrichDraftComponentValues(
30103
+ this.draftComponents,
30104
+ options.allowIncomplete
29899
30105
  );
30106
+ if (enrichment.kind === "fieldFailure") {
30107
+ return enrichment;
30108
+ }
30109
+ const connectionResult = this.detectConnections(enrichment.components, options.allowIncomplete);
30110
+ return {
30111
+ kind: "full",
30112
+ components: enrichment.components,
30113
+ failedFields: enrichment.failedFields,
30114
+ links: connectionResult.links,
30115
+ timings: connectionResult.timings
30116
+ };
29900
30117
  }
29901
- }
29902
- function validateFlagCombinations(options) {
29903
- validateMutualExclusions(options);
29904
- if (options.base !== void 0 && !options.pr) {
29905
- throw new ConfigValidationError(
29906
- "VALIDATION_ERROR" /* ValidationError */,
29907
- "--base can only be used with --pr"
29908
- );
30118
+ get moduleContextProjectNames() {
30119
+ return this.moduleContexts.map((moduleContext) => moduleContext.module.name);
29909
30120
  }
29910
- validateFormatOption(options);
29911
- }
29912
-
29913
- // src/platform/infra/extraction-config/draft-component-loader.ts
29914
- import {
29915
- existsSync as existsSync4,
29916
- readFileSync as readFileSync3
29917
- } from "node:fs";
29918
- var DraftComponentLoadError = class extends Error {
29919
- constructor(message) {
29920
- super(message);
29921
- this.name = "DraftComponentLoadError";
29922
- Error.captureStackTrace?.(this, this.constructor);
29923
- }
29924
- };
29925
- function isDraftComponentArray(value) {
29926
- if (!Array.isArray(value)) return false;
29927
- return value.every(
29928
- (item) => typeof item === "object" && item !== null && "type" in item && "name" in item && "domain" in item && "location" in item
29929
- );
29930
- }
29931
- function parseJsonFile(filePath) {
29932
- try {
29933
- return JSON.parse(readFileSync3(filePath, "utf-8"));
29934
- } catch {
29935
- throw new DraftComponentLoadError(`Enrich file contains invalid JSON: ${filePath}`);
29936
- }
29937
- }
29938
- function loadDraftComponentsFromFile(filePath) {
29939
- if (!existsSync4(filePath)) {
29940
- throw new DraftComponentLoadError(`Enrich file not found: ${filePath}`);
29941
- }
29942
- const parsed = parseJsonFile(filePath);
29943
- if (!isDraftComponentArray(parsed)) {
29944
- throw new DraftComponentLoadError(
29945
- `Enrich file does not contain valid draft components: ${filePath}`
29946
- );
29947
- }
29948
- return parsed;
29949
- }
29950
-
29951
- // src/platform/infra/git/git-repository-info.ts
29952
- import { execFileSync as execFileSync2 } from "node:child_process";
29953
- var RepositoryUrlParseError = class extends Error {
29954
- /* v8 ignore start -- @preserve: Error constructor; tested via integration */
29955
- constructor(url2) {
29956
- super(`Expected owner and repo in git URL, got ${url2}`);
29957
- this.name = "RepositoryUrlParseError";
29958
- }
29959
- /* v8 ignore stop */
29960
- };
29961
- function defaultGitExecutor2(binary, args, cwd) {
29962
- return execFileSync2(binary, args, {
29963
- cwd,
29964
- encoding: "utf-8",
29965
- stdio: ["pipe", "pipe", "pipe"]
29966
- }).trim();
29967
- }
29968
- function runGit2(executor, gitBinary, cwd, args) {
29969
- try {
29970
- return executor(gitBinary, args, cwd);
29971
- } catch (error48) {
29972
- if (error48 instanceof Error && "code" in error48 && error48.code === "ENOENT") {
29973
- throw new GitError("GIT_NOT_FOUND", "Install git to detect repository information.");
29974
- }
29975
- if (error48 instanceof Error) {
29976
- const stderr = extractStderr(error48);
29977
- if (stderr.includes("not a git repository")) {
29978
- throw new GitError("NOT_A_REPOSITORY", "Run from within a git repository.");
30121
+ detectConnections(enrichedComponents, allowIncomplete) {
30122
+ const links = [];
30123
+ const timings = [];
30124
+ for (const moduleContext of this.moduleContexts) {
30125
+ const moduleComponents = enrichedComponents.filter(
30126
+ (component) => component.domain === moduleContext.module.name
30127
+ );
30128
+ if (moduleComponents.length === 0) {
30129
+ continue;
29979
30130
  }
30131
+ const result = detectPerModuleConnections(
30132
+ moduleContext.project,
30133
+ moduleComponents,
30134
+ {
30135
+ allowIncomplete,
30136
+ moduleGlobs: [posix4.join(moduleContext.module.path, moduleContext.module.glob)],
30137
+ repository: this.repositoryName
30138
+ },
30139
+ matchesGlob
30140
+ );
30141
+ links.push(...result.links);
30142
+ timings.push({
30143
+ callGraphMs: result.timings.callGraphMs,
30144
+ asyncDetectionMs: 0,
30145
+ configurableMs: result.timings.configurableMs,
30146
+ setupMs: result.timings.setupMs,
30147
+ totalMs: result.timings.callGraphMs + result.timings.configurableMs + result.timings.setupMs
30148
+ });
29980
30149
  }
29981
- throw error48;
29982
- }
29983
- }
29984
- function parseRepositoryUrl(url2) {
29985
- const sshRegex = /^git@[^:]+:([^/]+)\/(.+?)(?:\.git)?$/;
29986
- const sshMatch = sshRegex.exec(url2);
29987
- if (sshMatch) {
29988
- const [, owner, repo] = sshMatch;
29989
- if (!owner || !repo) {
29990
- throw new RepositoryUrlParseError(url2);
29991
- }
30150
+ const crossResult = detectCrossModuleConnections(enrichedComponents, {
30151
+ allowIncomplete,
30152
+ repository: this.repositoryName
30153
+ });
30154
+ links.push(...crossResult.links);
30155
+ timings.push({
30156
+ callGraphMs: 0,
30157
+ asyncDetectionMs: crossResult.timings.asyncDetectionMs,
30158
+ configurableMs: 0,
30159
+ setupMs: 0,
30160
+ totalMs: crossResult.timings.asyncDetectionMs
30161
+ });
29992
30162
  return {
29993
- name: `${owner}/${repo}`,
29994
- owner,
29995
- url: url2
30163
+ links: deduplicateCrossStrategy(links),
30164
+ timings
29996
30165
  };
29997
30166
  }
29998
- const httpsRegex = /^https?:\/\/[^/]+\/([^/]+)\/(.+?)(?:\.git)?$/;
29999
- const httpsMatch = httpsRegex.exec(url2);
30000
- if (httpsMatch) {
30001
- const [, owner, repo] = httpsMatch;
30002
- if (!owner || !repo) {
30003
- throw new RepositoryUrlParseError(url2);
30167
+ enrichDraftComponentValues(draftComponents, allowIncomplete) {
30168
+ const moduleNames = new Set(this.moduleContextProjectNames);
30169
+ const draftsByModule = groupDraftsByModule(draftComponents);
30170
+ assertAllDraftsMatchModules(draftsByModule, moduleNames);
30171
+ const components = [];
30172
+ const failedFieldSet = /* @__PURE__ */ new Set();
30173
+ for (const moduleContext of this.moduleContexts) {
30174
+ const moduleDrafts = draftsByModule.get(moduleContext.module.name) ?? [];
30175
+ if (moduleDrafts.length === 0) {
30176
+ continue;
30177
+ }
30178
+ const result = enrichComponents(
30179
+ moduleDrafts,
30180
+ this.resolvedConfig,
30181
+ moduleContext.project,
30182
+ matchesGlob,
30183
+ this.configDir
30184
+ );
30185
+ components.push(...result.components);
30186
+ for (const failure of result.failures) {
30187
+ failedFieldSet.add(failure.field);
30188
+ }
30189
+ }
30190
+ const failedFields = [...failedFieldSet];
30191
+ if (failedFields.length > 0 && !allowIncomplete) {
30192
+ return {
30193
+ kind: "fieldFailure",
30194
+ failedFields
30195
+ };
30004
30196
  }
30005
30197
  return {
30006
- name: `${owner}/${repo}`,
30007
- owner,
30008
- url: url2
30198
+ kind: "enriched",
30199
+ components,
30200
+ failedFields
30009
30201
  };
30010
30202
  }
30011
- return {
30012
- name: url2,
30013
- url: url2
30014
- };
30203
+ };
30204
+ function assertAllDraftsMatchModules(draftsByModule, moduleNames) {
30205
+ const orphanedModules = [...draftsByModule.keys()].filter((name) => !moduleNames.has(name));
30206
+ if (orphanedModules.length > 0) {
30207
+ throw new OrphanedDraftComponentError(orphanedModules, [...moduleNames]);
30208
+ }
30015
30209
  }
30016
- function getRepositoryInfo(gitBinary = "git", cwd = process.cwd(), executor = defaultGitExecutor2) {
30017
- try {
30018
- const url2 = runGit2(executor, gitBinary, cwd, ["remote", "get-url", "origin"]);
30019
- return parseRepositoryUrl(url2);
30020
- } catch (error48) {
30021
- if (error48 instanceof GitError) throw error48;
30022
- if (error48 instanceof Error) {
30023
- const stderr = extractStderr(error48);
30024
- if (stderr.includes("No such remote")) {
30025
- throw new GitError("NO_REMOTE", 'No git remote named "origin" found.');
30026
- }
30210
+ function groupDraftsByModule(drafts) {
30211
+ const grouped = /* @__PURE__ */ new Map();
30212
+ for (const draft of drafts) {
30213
+ const existing = grouped.get(draft.domain);
30214
+ if (existing !== void 0) {
30215
+ existing.push(draft);
30216
+ continue;
30027
30217
  }
30028
- throw error48;
30218
+ grouped.set(draft.domain, [draft]);
30029
30219
  }
30220
+ return grouped;
30030
30221
  }
30031
30222
 
30032
- // src/features/extract/infra/external-clients/create-module-contexts.ts
30033
- import {
30034
- posix as posix4,
30035
- resolve as resolve7
30036
- } from "node:path";
30037
- import { globSync as globSync2 } from "glob";
30038
-
30039
- // src/features/extract/infra/external-clients/find-module-tsconfig-dir.ts
30223
+ // src/features/extract/infra/external-clients/create-configured-project.ts
30040
30224
  import { existsSync as existsSync5 } from "node:fs";
30041
30225
  import { resolve as resolve5 } from "node:path";
30042
- function findModuleTsConfigDir(configDir, modulePath) {
30043
- const moduleTsConfigPath = resolve5(configDir, modulePath, "tsconfig.json");
30044
- if (existsSync5(moduleTsConfigPath)) {
30045
- return resolve5(configDir, modulePath);
30046
- }
30047
- return configDir;
30048
- }
30049
-
30050
- // src/features/extract/infra/external-clients/create-configured-project.ts
30051
- import { existsSync as existsSync6 } from "node:fs";
30052
- import { resolve as resolve6 } from "node:path";
30053
30226
  import { Project as Project2 } from "ts-morph";
30054
30227
  function createConfiguredProject(configDir, skipTsConfig) {
30055
30228
  if (skipTsConfig) {
30056
30229
  return new Project2();
30057
30230
  }
30058
- const tsConfigPath = resolve6(configDir, "tsconfig.json");
30059
- if (!existsSync6(tsConfigPath)) {
30231
+ const tsConfigPath = resolve5(configDir, "tsconfig.json");
30232
+ if (!existsSync5(tsConfigPath)) {
30060
30233
  return new Project2();
30061
30234
  }
30062
30235
  return new Project2({
@@ -30065,200 +30238,238 @@ function createConfiguredProject(configDir, skipTsConfig) {
30065
30238
  });
30066
30239
  }
30067
30240
 
30068
- // src/features/extract/infra/external-clients/load-extraction-project.ts
30069
- function loadExtractionProject(configDir, sourceFilePaths, skipTsConfig) {
30070
- const project = createConfiguredProject(configDir, skipTsConfig);
30071
- project.addSourceFilesAtPaths(sourceFilePaths);
30072
- return project;
30241
+ // src/features/extract/infra/external-clients/find-module-tsconfig-dir.ts
30242
+ import { existsSync as existsSync6 } from "node:fs";
30243
+ import { resolve as resolve6 } from "node:path";
30244
+ function findModuleTsConfigDir(configDir, modulePath) {
30245
+ const moduleTsConfigPath = resolve6(configDir, modulePath, "tsconfig.json");
30246
+ if (existsSync6(moduleTsConfigPath)) {
30247
+ return resolve6(configDir, modulePath);
30248
+ }
30249
+ return configDir;
30073
30250
  }
30074
30251
 
30075
- // src/features/extract/infra/external-clients/create-module-contexts.ts
30076
- function createModuleContexts(resolvedConfig, configDir, sourceFilePaths, skipTsConfig) {
30077
- const sourceFileSet = new Set(sourceFilePaths);
30078
- return resolvedConfig.modules.map((module) => {
30079
- const allModuleFiles = globSync2(posix4.join(module.path, module.glob), { cwd: configDir }).map(
30080
- (f) => resolve7(configDir, f)
30252
+ // src/features/extract/infra/persistence/extraction-project/extraction-project-repository.ts
30253
+ var ExtractionProjectRepository = class {
30254
+ loadFromChangedProject(loadChangedProjectParams) {
30255
+ const parsedConfigState = this.loadParsedConfigState(loadChangedProjectParams.configPath);
30256
+ const sourceFilePaths = resolveFilteredSourceFiles(
30257
+ this.resolveSourceFilePaths(parsedConfigState),
30258
+ loadChangedProjectParams.baseBranch === void 0 ? { pr: true } : {
30259
+ base: loadChangedProjectParams.baseBranch,
30260
+ pr: true
30261
+ }
30081
30262
  );
30082
- const moduleFiles = allModuleFiles.filter((f) => sourceFileSet.has(f));
30083
- const tsConfigDir = findModuleTsConfigDir(configDir, module.path);
30084
- const project = loadExtractionProject(tsConfigDir, moduleFiles, skipTsConfig);
30085
- return {
30086
- module,
30087
- files: moduleFiles,
30088
- project
30089
- };
30263
+ return this.createExtractionProject(
30264
+ parsedConfigState,
30265
+ sourceFilePaths,
30266
+ loadChangedProjectParams.useTsConfig
30267
+ );
30268
+ }
30269
+ loadFromDraftEnrichment(draftEnrichmentParams) {
30270
+ const parsedConfigState = this.loadParsedConfigState(draftEnrichmentParams.configPath);
30271
+ return this.createExtractionProject(
30272
+ parsedConfigState,
30273
+ this.resolveSourceFilePaths(parsedConfigState),
30274
+ draftEnrichmentParams.useTsConfig,
30275
+ loadDraftComponentsFromFile(draftEnrichmentParams.draftComponentsPath)
30276
+ );
30277
+ }
30278
+ loadFromFullProject(loadFullProjectParams) {
30279
+ const parsedConfigState = this.loadParsedConfigState(loadFullProjectParams.configPath);
30280
+ return this.createExtractionProject(
30281
+ parsedConfigState,
30282
+ this.resolveSourceFilePaths(parsedConfigState),
30283
+ loadFullProjectParams.useTsConfig
30284
+ );
30285
+ }
30286
+ loadFromSelectedFiles(selectedFilesProjectParams) {
30287
+ const parsedConfigState = this.loadParsedConfigState(selectedFilesProjectParams.configPath);
30288
+ const sourceFilePaths = resolveFilteredSourceFiles(
30289
+ this.resolveSourceFilePaths(parsedConfigState),
30290
+ { files: selectedFilesProjectParams.filePaths }
30291
+ );
30292
+ return this.createExtractionProject(
30293
+ parsedConfigState,
30294
+ sourceFilePaths,
30295
+ selectedFilesProjectParams.useTsConfig
30296
+ );
30297
+ }
30298
+ createExtractionProject(parsedConfigState, sourceFilePaths, useTsConfig, draftComponents = []) {
30299
+ return new ExtractionProject(
30300
+ parsedConfigState.configDir,
30301
+ this.createModuleContexts(parsedConfigState, sourceFilePaths, useTsConfig),
30302
+ parsedConfigState.resolvedConfig,
30303
+ getRepositoryInfo().name,
30304
+ draftComponents
30305
+ );
30306
+ }
30307
+ createModuleContexts(parsedConfigState, sourceFilePaths, useTsConfig) {
30308
+ const sourceFileSet = new Set(sourceFilePaths);
30309
+ return parsedConfigState.resolvedConfig.modules.map((module) => {
30310
+ const allModuleFiles = globSync2(posix5.join(module.path, module.glob), { cwd: parsedConfigState.configDir }).map((filePath) => resolve7(parsedConfigState.configDir, filePath));
30311
+ const moduleFiles = allModuleFiles.filter((filePath) => sourceFileSet.has(filePath));
30312
+ const moduleConfigDir = findModuleTsConfigDir(parsedConfigState.configDir, module.path);
30313
+ const project = createConfiguredProject(moduleConfigDir, !useTsConfig);
30314
+ project.addSourceFilesAtPaths(moduleFiles);
30315
+ return {
30316
+ files: moduleFiles,
30317
+ module,
30318
+ project
30319
+ };
30320
+ });
30321
+ }
30322
+ loadParsedConfigState(configPath) {
30323
+ return loadAndValidateConfig(configPath);
30324
+ }
30325
+ resolveSourceFilePaths(parsedConfigState) {
30326
+ return resolveSourceFiles(parsedConfigState.resolvedConfig, parsedConfigState.configDir);
30327
+ }
30328
+ };
30329
+
30330
+ // src/features/extract/commands/enrich-draft-components.ts
30331
+ function enrichDraftComponents(enrichDraftComponentsInput) {
30332
+ const extractionProjectRepository = new ExtractionProjectRepository();
30333
+ const extractionProject = extractionProjectRepository.loadFromDraftEnrichment({
30334
+ configPath: enrichDraftComponentsInput.configPath,
30335
+ draftComponentsPath: enrichDraftComponentsInput.draftComponentsPath,
30336
+ useTsConfig: enrichDraftComponentsInput.useTsConfig
30337
+ });
30338
+ return extractionProject.enrichDraftComponents({
30339
+ allowIncomplete: enrichDraftComponentsInput.allowIncomplete,
30340
+ includeConnections: enrichDraftComponentsInput.includeConnections
30090
30341
  });
30091
30342
  }
30092
30343
 
30093
- // src/features/extract/domain/extract-draft-components.ts
30094
- function extractDraftComponents(moduleContexts, resolvedConfig, configDir) {
30095
- return moduleContexts.flatMap(
30096
- (ctx) => extractComponents(ctx.project, ctx.files, resolvedConfig, matchesGlob, configDir)
30344
+ // src/features/extract/commands/extract-draft-components.ts
30345
+ function extractDraftComponents(extractDraftComponentsInput) {
30346
+ const extractionProjectRepository = new ExtractionProjectRepository();
30347
+ const extractionProject = loadProjectFromInput(
30348
+ extractionProjectRepository,
30349
+ extractDraftComponentsInput
30097
30350
  );
30351
+ return extractionProject.extractDraftComponents({
30352
+ allowIncomplete: extractDraftComponentsInput.allowIncomplete,
30353
+ includeConnections: extractDraftComponentsInput.includeConnections
30354
+ });
30098
30355
  }
30099
-
30100
- // src/features/extract/domain/enrich-per-module.ts
30101
- var OrphanedDraftComponentError = class extends Error {
30102
- constructor(orphanedModules, knownModules) {
30103
- super(
30104
- `Draft components reference unknown modules: [${orphanedModules.join(", ")}]. Known modules: [${knownModules.join(", ")}]`
30105
- );
30106
- this.name = "OrphanedDraftComponentError";
30356
+ function loadProjectFromInput(extractionProjectRepository, extractDraftComponentsInput) {
30357
+ if (extractDraftComponentsInput.sourceMode === "pull-request") {
30358
+ return extractionProjectRepository.loadFromChangedProject({
30359
+ configPath: extractDraftComponentsInput.configPath,
30360
+ ...extractDraftComponentsInput.baseBranch === void 0 ? {} : { baseBranch: extractDraftComponentsInput.baseBranch },
30361
+ useTsConfig: extractDraftComponentsInput.useTsConfig
30362
+ });
30107
30363
  }
30108
- };
30109
- function enrichPerModule(moduleContexts, draftComponents, resolvedConfig, configDir) {
30110
- const moduleNames = new Set(moduleContexts.map((ctx) => ctx.module.name));
30111
- const draftsByModule = groupDraftsByModule(draftComponents);
30112
- assertAllDraftsMatchModules(draftsByModule, moduleNames);
30113
- const components = [];
30114
- const failedFieldSet = /* @__PURE__ */ new Set();
30115
- for (const ctx of moduleContexts) {
30116
- const moduleDrafts = draftsByModule.get(ctx.module.name) ?? [];
30117
- if (moduleDrafts.length === 0) {
30118
- continue;
30119
- }
30120
- const result = enrichComponents(
30121
- moduleDrafts,
30122
- resolvedConfig,
30123
- ctx.project,
30124
- matchesGlob,
30125
- configDir
30126
- );
30127
- components.push(...result.components);
30128
- for (const f of result.failures) {
30129
- failedFieldSet.add(f.field);
30130
- }
30364
+ if (extractDraftComponentsInput.sourceMode === "files") {
30365
+ return extractionProjectRepository.loadFromSelectedFiles({
30366
+ configPath: extractDraftComponentsInput.configPath,
30367
+ filePaths: extractDraftComponentsInput.files ?? [],
30368
+ useTsConfig: extractDraftComponentsInput.useTsConfig
30369
+ });
30131
30370
  }
30371
+ return extractionProjectRepository.loadFromFullProject({
30372
+ configPath: extractDraftComponentsInput.configPath,
30373
+ useTsConfig: extractDraftComponentsInput.useTsConfig
30374
+ });
30375
+ }
30376
+
30377
+ // src/features/extract/commands/create-extract-draft-components-input.ts
30378
+ function createExtractDraftComponentsInput(options) {
30132
30379
  return {
30133
- components,
30134
- failedFields: [...failedFieldSet]
30380
+ allowIncomplete: options.allowIncomplete === true,
30381
+ ...options.base === void 0 ? {} : { baseBranch: options.base },
30382
+ configPath: options.config,
30383
+ ...options.files === void 0 ? {} : { files: options.files },
30384
+ includeConnections: !shouldStopAtDraftComponents(options),
30385
+ ...options.output === void 0 ? {} : { output: options.output },
30386
+ sourceMode: readSourceMode(options),
30387
+ useTsConfig: options.tsConfig !== false
30135
30388
  };
30136
30389
  }
30137
- function assertAllDraftsMatchModules(draftsByModule, moduleNames) {
30138
- const orphanedModules = [...draftsByModule.keys()].filter((name) => !moduleNames.has(name));
30139
- if (orphanedModules.length > 0) {
30140
- throw new OrphanedDraftComponentError(orphanedModules, [...moduleNames]);
30141
- }
30390
+ function shouldStopAtDraftComponents(options) {
30391
+ return options.dryRun === true || options.format === "markdown" || options.componentsOnly === true;
30142
30392
  }
30143
- function groupDraftsByModule(drafts) {
30144
- const grouped = /* @__PURE__ */ new Map();
30145
- for (const draft of drafts) {
30146
- const existing = grouped.get(draft.domain);
30147
- if (existing) {
30148
- existing.push(draft);
30149
- } else {
30150
- grouped.set(draft.domain, [draft]);
30151
- }
30393
+ function readSourceMode(options) {
30394
+ if (options.pr === true) {
30395
+ return "pull-request";
30152
30396
  }
30153
- return grouped;
30397
+ return options.files === void 0 ? "all" : "files";
30154
30398
  }
30155
30399
 
30156
- // src/features/extract/domain/detect-connections-per-module.ts
30157
- import { posix as posix5 } from "node:path";
30158
- function detectConnectionsPerModule(moduleContexts, enrichedComponents, repositoryName, allowIncomplete) {
30159
- const links = [];
30160
- const timings = [];
30161
- for (const ctx of moduleContexts) {
30162
- const moduleComponents = enrichedComponents.filter((c) => c.domain === ctx.module.name);
30163
- if (moduleComponents.length === 0) {
30164
- continue;
30165
- }
30166
- const result = detectPerModuleConnections(
30167
- ctx.project,
30168
- moduleComponents,
30169
- {
30170
- allowIncomplete,
30171
- moduleGlobs: [posix5.join(ctx.module.path, ctx.module.glob)],
30172
- repository: repositoryName
30173
- },
30174
- matchesGlob
30175
- );
30176
- links.push(...result.links);
30177
- timings.push({
30178
- callGraphMs: result.timings.callGraphMs,
30179
- asyncDetectionMs: 0,
30180
- configurableMs: result.timings.configurableMs,
30181
- setupMs: result.timings.setupMs,
30182
- totalMs: result.timings.callGraphMs + result.timings.configurableMs + result.timings.setupMs
30183
- });
30184
- }
30185
- const crossResult = detectCrossModuleConnections(enrichedComponents, {
30186
- allowIncomplete,
30187
- repository: repositoryName
30188
- });
30189
- links.push(...crossResult.links);
30190
- timings.push({
30191
- callGraphMs: 0,
30192
- asyncDetectionMs: crossResult.timings.asyncDetectionMs,
30193
- configurableMs: 0,
30194
- setupMs: 0,
30195
- totalMs: crossResult.timings.asyncDetectionMs
30196
- });
30400
+ // src/features/extract/commands/create-enrich-draft-components-input.ts
30401
+ function createEnrichDraftComponentsInput(options, enrichPath) {
30197
30402
  return {
30198
- links: deduplicateCrossStrategy(links),
30199
- timings
30403
+ allowIncomplete: options.allowIncomplete === true,
30404
+ configPath: options.config,
30405
+ draftComponentsPath: enrichPath,
30406
+ includeConnections: !shouldStopAtDraftComponents2(options),
30407
+ ...options.output === void 0 ? {} : { output: options.output },
30408
+ useTsConfig: options.tsConfig !== false
30200
30409
  };
30201
30410
  }
30411
+ function shouldStopAtDraftComponents2(options) {
30412
+ return options.dryRun === true || options.format === "markdown" || options.componentsOnly === true;
30413
+ }
30202
30414
 
30203
- // src/features/extract/commands/run-extraction.ts
30204
- function runExtraction(options, resolvedConfig, configDir, sourceFilePaths) {
30205
- const skipTsConfig = options.tsConfig === false;
30206
- const moduleContexts = createModuleContexts(
30207
- resolvedConfig,
30208
- configDir,
30209
- sourceFilePaths,
30210
- skipTsConfig
30211
- );
30212
- const draftComponents = options.enrich === void 0 ? extractDraftComponents(moduleContexts, resolvedConfig, configDir) : loadDraftComponentsFromFile(options.enrich);
30213
- if (options.dryRun || options.format === "markdown" || options.componentsOnly) {
30415
+ // src/platform/infra/cli-presentation/categorize-components.ts
30416
+ function componentKey(component) {
30417
+ return `${component.domain}:${component.type}:${component.name}`;
30418
+ }
30419
+ function toComponentSummary(component) {
30420
+ return {
30421
+ type: component.type,
30422
+ name: component.name,
30423
+ domain: component.domain
30424
+ };
30425
+ }
30426
+ function categorizeComponents(current, baseline) {
30427
+ if (baseline === void 0) {
30214
30428
  return {
30215
- kind: "draftOnly",
30216
- components: draftComponents
30429
+ added: current.map(toComponentSummary),
30430
+ modified: [],
30431
+ removed: []
30217
30432
  };
30218
30433
  }
30219
- const allowIncomplete = options.allowIncomplete === true;
30220
- const enrichment = enrichPerModule(moduleContexts, draftComponents, resolvedConfig, configDir);
30221
- if (enrichment.failedFields.length > 0 && !allowIncomplete) {
30222
- throw new ExtractionFieldFailureError(enrichment.failedFields);
30223
- }
30224
- const repositoryInfo = getRepositoryInfo();
30225
- const connectionResult = detectConnectionsPerModule(
30226
- moduleContexts,
30227
- enrichment.components,
30228
- repositoryInfo.name,
30229
- allowIncomplete
30230
- );
30434
+ const baselineKeys = new Set(baseline.map((c) => componentKey(c)));
30435
+ const currentKeys = new Set(current.map((c) => componentKey(c)));
30436
+ const added = current.filter((c) => !baselineKeys.has(componentKey(c))).map(toComponentSummary);
30437
+ const removed = baseline.filter((c) => !currentKeys.has(componentKey(c))).map(toComponentSummary);
30231
30438
  return {
30232
- kind: "full",
30233
- components: enrichment.components,
30234
- links: connectionResult.links,
30235
- timings: connectionResult.timings,
30236
- failedFields: enrichment.failedFields
30439
+ added,
30440
+ modified: [],
30441
+ removed
30237
30442
  };
30238
30443
  }
30239
30444
 
30240
- // src/platform/infra/cli-presentation/format-pr-markdown.ts
30241
- function formatComponentLine(component) {
30242
- return `- **${component.type}** \`${component.name}\` in \`${component.domain}\` domain`;
30445
+ // src/platform/infra/cli-presentation/format-extraction-stats.ts
30446
+ function countLinksByType(componentCount, links) {
30447
+ const syncLinkCount = links.filter((l) => l.type === "sync").length;
30448
+ const asyncLinkCount = links.filter((l) => l.type === "async").length;
30449
+ const uncertainLinkCount = links.filter((l) => l._uncertain !== void 0).length;
30450
+ return {
30451
+ componentCount,
30452
+ linkCount: links.length,
30453
+ syncLinkCount,
30454
+ asyncLinkCount,
30455
+ uncertainLinkCount
30456
+ };
30243
30457
  }
30244
- function formatSection(title, components) {
30245
- const header = `### ${title} (${components.length})`;
30246
- if (components.length === 0) {
30247
- return `${header}
30248
- None`;
30458
+ function formatSeconds(ms) {
30459
+ return (ms / 1e3).toFixed(2) + "s";
30460
+ }
30461
+ function formatExtractionStats(stats) {
30462
+ const lines = [`Components: ${stats.componentCount}`];
30463
+ if (stats.linkCount !== void 0) {
30464
+ lines.push(
30465
+ `Links: ${stats.linkCount} (sync: ${stats.syncLinkCount}, async: ${stats.asyncLinkCount})`
30466
+ );
30467
+ lines.push(`Uncertain: ${stats.uncertainLinkCount}`);
30249
30468
  }
30250
- return `${header}
30251
- ${components.map(formatComponentLine).join("\n")}`;
30469
+ return lines;
30252
30470
  }
30253
- function formatPrMarkdown(categorized) {
30254
- const sections = [
30255
- formatSection("Added Components", categorized.added),
30256
- formatSection("Modified Components", categorized.modified),
30257
- formatSection("Removed Components", categorized.removed)
30258
- ];
30259
- return `## Architecture Changes
30260
-
30261
- ${sections.join("\n\n")}`;
30471
+ function formatTimingLine(timings) {
30472
+ return `Extraction completed in ${formatSeconds(timings.totalMs)} (call graph: ${formatSeconds(timings.callGraphMs)}, detection: ${formatSeconds(timings.asyncDetectionMs)}, setup: ${formatSeconds(timings.setupMs)})`;
30262
30473
  }
30263
30474
 
30264
30475
  // src/platform/infra/cli-presentation/extract-output-formatter.ts
@@ -30287,6 +30498,30 @@ function formatDryRunOutput(components) {
30287
30498
  return lines;
30288
30499
  }
30289
30500
 
30501
+ // src/platform/infra/cli-presentation/format-pr-markdown.ts
30502
+ function formatComponentLine(component) {
30503
+ return `- **${component.type}** \`${component.name}\` in \`${component.domain}\` domain`;
30504
+ }
30505
+ function formatSection(title, components) {
30506
+ const header = `### ${title} (${components.length})`;
30507
+ if (components.length === 0) {
30508
+ return `${header}
30509
+ None`;
30510
+ }
30511
+ return `${header}
30512
+ ${components.map(formatComponentLine).join("\n")}`;
30513
+ }
30514
+ function formatPrMarkdown(categorized) {
30515
+ const sections = [
30516
+ formatSection("Added Components", categorized.added),
30517
+ formatSection("Modified Components", categorized.modified),
30518
+ formatSection("Removed Components", categorized.removed)
30519
+ ];
30520
+ return `## Architecture Changes
30521
+
30522
+ ${sections.join("\n\n")}`;
30523
+ }
30524
+
30290
30525
  // src/platform/infra/cli-presentation/output-writer.ts
30291
30526
  import { writeFileSync } from "node:fs";
30292
30527
  function outputResult(data, options) {
@@ -30309,67 +30544,7 @@ function outputResult(data, options) {
30309
30544
  console.log(JSON.stringify(data));
30310
30545
  }
30311
30546
 
30312
- // src/platform/infra/cli-presentation/format-extraction-stats.ts
30313
- function countLinksByType(componentCount, links) {
30314
- const syncLinkCount = links.filter((l) => l.type === "sync").length;
30315
- const asyncLinkCount = links.filter((l) => l.type === "async").length;
30316
- const uncertainLinkCount = links.filter((l) => l._uncertain !== void 0).length;
30317
- return {
30318
- componentCount,
30319
- linkCount: links.length,
30320
- syncLinkCount,
30321
- asyncLinkCount,
30322
- uncertainLinkCount
30323
- };
30324
- }
30325
- function formatSeconds(ms) {
30326
- return (ms / 1e3).toFixed(2) + "s";
30327
- }
30328
- function formatExtractionStats(stats) {
30329
- const lines = [`Components: ${stats.componentCount}`];
30330
- if (stats.linkCount !== void 0) {
30331
- lines.push(
30332
- `Links: ${stats.linkCount} (sync: ${stats.syncLinkCount}, async: ${stats.asyncLinkCount})`
30333
- );
30334
- lines.push(`Uncertain: ${stats.uncertainLinkCount}`);
30335
- }
30336
- return lines;
30337
- }
30338
- function formatTimingLine(timings) {
30339
- return `Extraction completed in ${formatSeconds(timings.totalMs)} (call graph: ${formatSeconds(timings.callGraphMs)}, detection: ${formatSeconds(timings.asyncDetectionMs)}, setup: ${formatSeconds(timings.setupMs)})`;
30340
- }
30341
-
30342
- // src/platform/infra/cli-presentation/categorize-components.ts
30343
- function componentKey(component) {
30344
- return `${component.domain}:${component.type}:${component.name}`;
30345
- }
30346
- function toComponentSummary(component) {
30347
- return {
30348
- type: component.type,
30349
- name: component.name,
30350
- domain: component.domain
30351
- };
30352
- }
30353
- function categorizeComponents(current, baseline) {
30354
- if (baseline === void 0) {
30355
- return {
30356
- added: current.map(toComponentSummary),
30357
- modified: [],
30358
- removed: []
30359
- };
30360
- }
30361
- const baselineKeys = new Set(baseline.map((c) => componentKey(c)));
30362
- const currentKeys = new Set(current.map((c) => componentKey(c)));
30363
- const added = current.filter((c) => !baselineKeys.has(componentKey(c))).map(toComponentSummary);
30364
- const removed = baseline.filter((c) => !currentKeys.has(componentKey(c))).map(toComponentSummary);
30365
- return {
30366
- added,
30367
- modified: [],
30368
- removed
30369
- };
30370
- }
30371
-
30372
- // src/features/extract/infra/mappers/present-extraction-result.ts
30547
+ // src/features/extract/infra/cli/output/present-extraction-result.ts
30373
30548
  function presentExtractionResult(result, options) {
30374
30549
  if (result.kind === "draftOnly") {
30375
30550
  presentDraftResult(result.components, options);
@@ -30390,7 +30565,7 @@ function presentDraftResult(components, options) {
30390
30565
  console.log(markdown);
30391
30566
  return;
30392
30567
  }
30393
- outputResult(formatSuccess(components), { output: options.output });
30568
+ outputResult(formatSuccess(components), createOutputOptions(options.output));
30394
30569
  }
30395
30570
  function presentFullResult(result, options) {
30396
30571
  if (result.failedFields.length > 0) {
@@ -30412,21 +30587,25 @@ function presentFullResult(result, options) {
30412
30587
  components: result.components,
30413
30588
  links: result.links
30414
30589
  }),
30415
- { output: options.output }
30590
+ createOutputOptions(options.output)
30416
30591
  );
30417
30592
  }
30593
+ function createOutputOptions(outputPath) {
30594
+ return outputPath === void 0 ? {} : { output: outputPath };
30595
+ }
30418
30596
 
30419
30597
  // src/features/extract/entrypoint/extract.ts
30420
30598
  function createExtractCommand() {
30421
30599
  return new Command21("extract").description("Extract architectural components from source code").requiredOption("--config <path>", "Path to extraction config file").option("--dry-run", "Show component counts per domain without full output").option("-o, --output <file>", "Write output to file instead of stdout").option("--components-only", "Output only component identity (no metadata enrichment)").option("--enrich <file>", "Read draft components from file and enrich with extraction rules").option("--allow-incomplete", "Output components even when some extraction fields fail").option("--pr", "Extract from files changed in current branch vs base branch").option("--base <branch>", "Override base branch for --pr (default: auto-detect)").option("--files <paths...>", "Extract from specific files").option("--format <type>", "Output format: json (default) or markdown").option("--stats", "Show extraction statistics on stderr").option("--patterns", "Enable pattern-based connection detection").option("--no-ts-config", "Skip tsconfig.json auto-discovery (disables full type resolution)").action((options) => {
30422
30600
  validateFlagCombinations(options);
30423
- const {
30424
- resolvedConfig,
30425
- configDir
30426
- } = loadAndValidateConfig(options.config);
30427
- const allSourceFilePaths = resolveSourceFiles(resolvedConfig, configDir);
30428
- const sourceFilePaths = resolveFilteredSourceFiles(allSourceFilePaths, options);
30429
- const result = runExtraction(options, resolvedConfig, configDir, sourceFilePaths);
30601
+ const result = options.enrich === void 0 ? extractDraftComponents(createExtractDraftComponentsInput(options)) : enrichDraftComponents(createEnrichDraftComponentsInput(options, options.enrich));
30602
+ if (result.kind === "fieldFailure") {
30603
+ exitWithCliError(
30604
+ "VALIDATION_ERROR" /* ValidationError */,
30605
+ `Extraction failed for fields: ${result.failedFields.join(", ")}`,
30606
+ 1 /* ExtractionFailure */
30607
+ );
30608
+ }
30430
30609
  presentExtractionResult(result, options);
30431
30610
  });
30432
30611
  }
@@ -30443,7 +30622,7 @@ function parsePackageJson(pkg) {
30443
30622
  }
30444
30623
  function loadPackageJson() {
30445
30624
  if (true) {
30446
- return { version: "0.8.11" };
30625
+ return { version: "0.8.13" };
30447
30626
  }
30448
30627
  const require2 = createRequire2(import.meta.url);
30449
30628
  return parsePackageJson(require2("../../package.json"));
@@ -30484,10 +30663,6 @@ function handleGlobalError(error48) {
30484
30663
  console.log(JSON.stringify(formatError2(error48.errorCode, error48.message)));
30485
30664
  process.exit(2 /* ConfigValidation */);
30486
30665
  }
30487
- if (error48 instanceof ExtractionFieldFailureError) {
30488
- console.log(JSON.stringify(formatError2("VALIDATION_ERROR" /* ValidationError */, error48.message)));
30489
- process.exit(1 /* ExtractionFailure */);
30490
- }
30491
30666
  if (error48 instanceof GitError) {
30492
30667
  console.log(JSON.stringify(formatError2("VALIDATION_ERROR" /* ValidationError */, error48.message)));
30493
30668
  process.exit(3 /* RuntimeError */);
@@ -30540,9 +30715,9 @@ program.parseAsync().catch(handleGlobalError);
30540
30715
  /* v8 ignore next -- @preserve: yaml library always throws Error instances; defensive guard */
30541
30716
  /* v8 ignore next -- @preserve: error is always Error from yaml parser; defensive guard */
30542
30717
  /* v8 ignore start -- @preserve: default executor delegates to execFileSync; tested via CLI integration */
30543
- /* v8 ignore start -- @preserve: detectChangedTypeScriptFiles only throws GitError; non-GitError path is unreachable */
30544
- /* v8 ignore start -- @preserve: git execution; mocked in all integration tests */
30718
+ /* v8 ignore next -- @preserve: defensive optional chain; property existence guaranteed by hasOwn check above */
30545
30719
  /* v8 ignore start -- @preserve: defensive check; regex ([^/]+) requires non-empty groups */
30720
+ /* v8 ignore start -- @preserve: detectChangedTypeScriptFiles only throws GitError; non-GitError path is unreachable */
30546
30721
  /* v8 ignore start -- @preserve: trivial comparator, Map keys guarantee a !== b */
30547
30722
  /* v8 ignore start -- @preserve: dry-run output formatting; tested via CLI integration */
30548
30723
  /* v8 ignore start -- @preserve: dry-run tested via CLI integration */