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