@archpilotlabs/archpilot 0.0.18 → 0.0.19

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.
package/README.md CHANGED
@@ -8,19 +8,19 @@
8
8
  Architecture guardrails for AI-native software development
9
9
  </p>
10
10
 
11
- ArchPilot validates your repository against architecture rules and helps engineering teams move faster with AI-assisted development-without sacrificing architectural integrity.
11
+ ArchPilot validates your repository against architecture rules, helping engineering teams move faster with AI-assisted development without sacrificing architectural integrity.
12
12
 
13
13
  **Your AI writes code. ArchPilot protects architecture.**
14
14
 
15
15
  <p align="center">
16
- Validate Enforce Measure architecture continuously
16
+ Validate | Enforce | Measure architecture continuously
17
17
  </p>
18
18
 
19
19
  ArchPilot makes architecture **visible, enforceable, and measurable** as your system evolves.
20
20
 
21
21
  ---
22
22
 
23
- ## 🧭 Why this exists
23
+ ## Why this exists
24
24
 
25
25
  Most teams rely on documentation or tribal knowledge to maintain architecture.
26
26
 
@@ -28,83 +28,85 @@ Over time, boundaries blur, dependencies leak, and architectural decisions drift
28
28
 
29
29
  ArchPilot turns architecture into something you can **actually validate, enforce, and track continuously**.
30
30
 
31
- AI accelerates code delivery-but architectural drift accelerates too.
31
+ AI accelerates code delivery but architectural drift can accelerate too.
32
32
 
33
33
  ArchPilot gives teams deterministic architecture guardrails in local development, CI, and governance workflows.
34
34
 
35
35
  ---
36
36
 
37
- ## 🚀 What it does
37
+ ## What it does
38
38
 
39
39
  ArchPilot turns architecture into something you can actually run and verify:
40
40
 
41
- * Validate module boundaries and dependencies
42
- * Enforce architecture rules and contracts
43
- * Detect architectural drift over time
44
- * Generate architecture scores and reports
45
- * Surface architecture feedback in PRs and CI
41
+ - Validate module boundaries and dependencies
42
+ - Enforce architecture rules and contracts
43
+ - Detect architectural drift over time
44
+ - Generate architecture scores and reports
45
+ - Surface architecture feedback in PRs and CI
46
46
 
47
47
  No runtime agents. No external services. Fully deterministic and local-first.
48
48
 
49
+ ArchPilot works alongside GitHub Copilot, Claude Code, Codex, Cursor, and other AI coding tools by continuously validating architectural boundaries before architectural drift becomes technical debt.
50
+
49
51
  ---
50
52
 
51
- ## 🧩 Supported Stacks
53
+ ## Supported Stacks
52
54
 
53
55
  ArchPilot supports **practical deterministic architecture detection** across modern application stacks.
54
56
 
55
57
  ### Backend
56
58
 
57
- * Node.js / TypeScript
58
- * NestJS
59
- * Express.js
60
- * Java
61
- * Spring / Spring Boot
62
- * Kotlin
63
- * FastAPI
64
- * Django
65
- * Flask
66
- * PHP
67
- * Laravel
68
- * Go
69
- * .NET
70
- * Ruby on Rails
59
+ - Node.js / TypeScript
60
+ - NestJS
61
+ - Express.js
62
+ - Java
63
+ - Spring / Spring Boot
64
+ - Kotlin
65
+ - FastAPI
66
+ - Django
67
+ - Flask
68
+ - PHP
69
+ - Laravel
70
+ - Go
71
+ - .NET
72
+ - Ruby on Rails
71
73
 
72
74
  ### Frontend
73
75
 
74
- * React
75
- * Next.js
76
- * Angular
77
- * Vue
76
+ - React
77
+ - Next.js
78
+ - Angular
79
+ - Vue
78
80
 
79
81
  ### Infrastructure
80
82
 
81
- * Terraform
82
- * Ansible
83
+ - Terraform
84
+ - Ansible
83
85
 
84
- ArchPilot detects stack shape from practical repository evidence - framework markers, manifests, project layout, and source structure - then applies conservative architecture discovery defaults locally.
86
+ ArchPilot detects application stacks from practical repository evidence - framework markers, manifests, project layout, and source structure - then applies conservative architecture discovery defaults locally.
85
87
 
86
88
  **No cloud scanning. No remote indexing. Deterministic local validation.**
87
89
 
88
90
  ---
89
91
 
90
- ## ☁️ Local-first. Cloud-powered.
92
+ ## Local-first. Cloud-powered.
91
93
 
92
94
  Start locally in minutes:
93
95
 
94
- * Offline validation
95
- * Local reports and artifacts
96
- * PR review markdown
97
- * CI validation
98
- * Deterministic architecture checks
96
+ - Offline validation
97
+ - Local reports and artifacts
98
+ - PR review markdown
99
+ - CI validation
100
+ - Deterministic architecture checks
99
101
 
100
102
  Then connect to **ArchPilot Cloud** to unlock:
101
103
 
102
- * Repository history and trends
103
- * Findings dashboards
104
- * Team ownership and governance workflows
105
- * Shared policies and exceptions
106
- * Organization-wide architecture visibility
107
- * Continuous governance across repositories
104
+ - Repository history and trends
105
+ - Findings dashboards
106
+ - Team ownership and governance workflows
107
+ - Shared policies and exceptions
108
+ - Organization-wide architecture visibility
109
+ - Continuous governance across repositories
108
110
 
109
111
  ```bash
110
112
  archpilot cloud status
@@ -114,14 +116,15 @@ archpilot cloud disconnect
114
116
 
115
117
  Cloud setup only asks for your API token.
116
118
 
117
- ArchPilot uses the canonical production API automatically-no server URL setup required for normal use.
119
+ ArchPilot uses the canonical production API automatically; no server URL setup is required for normal use.
120
+
118
121
  Learn more:
119
122
 
120
123
  https://archpilot.org/docs
121
124
 
122
125
  ---
123
126
 
124
- ## 📦 Installation
127
+ ## Installation
125
128
 
126
129
  Install globally:
127
130
 
@@ -137,7 +140,7 @@ npx @archpilotlabs/archpilot --help
137
140
 
138
141
  ---
139
142
 
140
- ## Quick Start
143
+ ## Quick Start
141
144
 
142
145
  ### 1. Initialize architecture
143
146
 
@@ -183,24 +186,24 @@ Applies safe, deterministic fixes for missing architecture artifacts.
183
186
  archpilot validate --ci
184
187
  ```
185
188
 
186
- * Fails only on real architecture violations
187
- * Keeps setup issues separate from architecture quality
189
+ - Fails only on real architecture violations
190
+ - Keeps setup issues separate from architecture quality
188
191
 
189
192
  ---
190
193
 
191
- ## 🧑‍💻 CLI vs VS Code Extension
194
+ ## CLI vs VS Code Extension
192
195
 
193
196
  ArchPilot works in two ways:
194
197
 
195
- * **CLI (this package)** validation, CI, automation
196
- * **VS Code Extension** visual inspector, guided workflows, and architecture exploration
198
+ - **CLI (this package)** - validation, CI, automation
199
+ - **VS Code Extension** - visual inspector, guided workflows, and architecture exploration
197
200
 
198
201
  Both use the same validation engine and produce identical results.
199
202
  The extension enhances developer experience, but is not required.
200
203
 
201
204
  ---
202
205
 
203
- ## 🧠 Key Concepts
206
+ ## Key Concepts
204
207
 
205
208
  ### Architecture Health Score
206
209
 
@@ -212,13 +215,13 @@ Tracks completeness of configuration and onboarding.
212
215
 
213
216
  ### Findings
214
217
 
215
- * **violation** real architecture issue
216
- * **setup-gap** missing config / skipped checks
217
- * **guidance** non-blocking recommendation
218
+ - **violation** - real architecture issue
219
+ - **setup-gap** - missing config / skipped checks
220
+ - **guidance** - non-blocking recommendation
218
221
 
219
222
  ---
220
223
 
221
- ## 🔍 Core Commands
224
+ ## Core Commands
222
225
 
223
226
  ```bash
224
227
  archpilot init
@@ -247,37 +250,38 @@ archpilot governance upload
247
250
 
248
251
  ---
249
252
 
250
- ## ⚙️ Core Capabilities
253
+ ## Core Capabilities
251
254
 
252
- * Architecture-as-code (`.archpilot/`)
253
- * Dependency validation and module contracts
254
- * ADR (Architecture Decision Record) support
255
- * Drift detection and history tracking
256
- * PR review and CI integration
257
- * Architecture scoring and reporting
255
+ - Architecture-as-code (`.archpilot/`)
256
+ - Smart Init architecture discovery
257
+ - Dependency validation and module contracts
258
+ - ADR (Architecture Decision Record) support
259
+ - Drift detection and history tracking
260
+ - PR review and CI integration
261
+ - Architecture scoring and reporting
258
262
 
259
263
  ---
260
264
 
261
- ## 🎯 Philosophy
265
+ ## Philosophy
262
266
 
263
267
  ArchPilot is built on four principles:
264
268
 
265
- * Architecture should be explicit
266
- * Rules should be executable
267
- * Validation should be deterministic
268
- * Feedback should be continuous
269
+ - Architecture should be explicit
270
+ - Rules should be executable
271
+ - Validation should be deterministic
272
+ - Feedback should be continuous
269
273
 
270
274
  ---
271
275
 
272
- ## 📄 License
276
+ ## License
273
277
 
274
278
  Proprietary. See LICENSE file for details.
275
279
 
276
280
  ---
277
281
 
278
- ## 🌐 Learn More
282
+ ## Learn More
279
283
 
280
- * Website: https://archpilot.org
281
- * Documentation: https://archpilot.org/docs
284
+ - Website: https://archpilot.org
285
+ - Documentation: https://archpilot.org/docs
282
286
 
283
287
  ---
@@ -8635,7 +8635,7 @@ ${lanes.join("\n")}
8635
8635
  watchDirectory,
8636
8636
  preferNonRecursiveWatch: !fsSupportsRecursiveFsWatch,
8637
8637
  resolvePath: (path71) => _path.resolve(path71),
8638
- fileExists: fileExists5,
8638
+ fileExists: fileExists6,
8639
8639
  directoryExists: directoryExists2,
8640
8640
  getAccessibleFileSystemEntries,
8641
8641
  createDirectory(directoryName) {
@@ -8797,7 +8797,7 @@ ${lanes.join("\n")}
8797
8797
  if (platform === "win32" || platform === "win64") {
8798
8798
  return false;
8799
8799
  }
8800
- return !fileExists5(swapCase(__filename));
8800
+ return !fileExists6(swapCase(__filename));
8801
8801
  }
8802
8802
  function swapCase(s) {
8803
8803
  return s.replace(/\w/g, (ch) => {
@@ -8930,7 +8930,7 @@ ${lanes.join("\n")}
8930
8930
  return false;
8931
8931
  }
8932
8932
  }
8933
- function fileExists5(path71) {
8933
+ function fileExists6(path71) {
8934
8934
  return fileSystemEntryExists(
8935
8935
  path71,
8936
8936
  0
@@ -134756,7 +134756,7 @@ ${lanes.join("\n")}
134756
134756
  const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames2);
134757
134757
  return {
134758
134758
  useCaseSensitiveFileNames: useCaseSensitiveFileNames2,
134759
- fileExists: fileExists5,
134759
+ fileExists: fileExists6,
134760
134760
  readFile: (path71, encoding) => host.readFile(path71, encoding),
134761
134761
  directoryExists: host.directoryExists && directoryExists2,
134762
134762
  getDirectories,
@@ -134842,7 +134842,7 @@ ${lanes.join("\n")}
134842
134842
  }
134843
134843
  return host.writeFile(fileName, data, writeByteOrderMark);
134844
134844
  }
134845
- function fileExists5(fileName) {
134845
+ function fileExists6(fileName) {
134846
134846
  const path71 = toPath3(fileName);
134847
134847
  const result = getCachedFileSystemEntriesForBaseDir(path71);
134848
134848
  return result && hasEntry(result.sortedAndCanonicalizedFiles, getCanonicalFileName(getBaseNameOfFileName(fileName))) || host.fileExists(fileName);
@@ -135261,10 +135261,10 @@ ${lanes.join("\n")}
135261
135261
  function closeFileWatcherOf(objWithWatcher) {
135262
135262
  objWithWatcher.watcher.close();
135263
135263
  }
135264
- function findConfigFile(searchPath, fileExists5, configName = "tsconfig.json") {
135264
+ function findConfigFile(searchPath, fileExists6, configName = "tsconfig.json") {
135265
135265
  return forEachAncestorDirectory(searchPath, (ancestor) => {
135266
135266
  const fileName = combinePaths(ancestor, configName);
135267
- return fileExists5(fileName) ? fileName : void 0;
135267
+ return fileExists6(fileName) ? fileName : void 0;
135268
135268
  });
135269
135269
  }
135270
135270
  function resolveTripleslashReference(moduleName, containingFile) {
@@ -135871,14 +135871,14 @@ ${lanes.join("\n")}
135871
135871
  }
135872
135872
  return { file, pos, end, packageId };
135873
135873
  }
135874
- function isProgramUptoDate(program, rootFileNames, newOptions, getSourceVersion, fileExists5, hasInvalidatedResolutions, hasInvalidatedLibResolutions, hasChangedAutomaticTypeDirectiveNames, getParsedCommandLine, projectReferences) {
135874
+ function isProgramUptoDate(program, rootFileNames, newOptions, getSourceVersion, fileExists6, hasInvalidatedResolutions, hasInvalidatedLibResolutions, hasChangedAutomaticTypeDirectiveNames, getParsedCommandLine, projectReferences) {
135875
135875
  if (!program || (hasChangedAutomaticTypeDirectiveNames == null ? void 0 : hasChangedAutomaticTypeDirectiveNames())) return false;
135876
135876
  if (!arrayIsEqualTo(program.getRootFileNames(), rootFileNames)) return false;
135877
135877
  let seenResolvedRefs;
135878
135878
  if (!arrayIsEqualTo(program.getProjectReferences(), projectReferences, projectReferenceUptoDate)) return false;
135879
135879
  if (program.getSourceFiles().some(sourceFileNotUptoDate)) return false;
135880
135880
  const missingPaths = program.getMissingFilePaths();
135881
- if (missingPaths && forEachEntry(missingPaths, fileExists5)) return false;
135881
+ if (missingPaths && forEachEntry(missingPaths, fileExists6)) return false;
135882
135882
  const currentOptions = program.getCompilerOptions();
135883
135883
  if (!compareDataObjects(currentOptions, newOptions)) return false;
135884
135884
  if (program.resolvedLibReferences && forEachEntry(program.resolvedLibReferences, (_value, libFileName) => hasInvalidatedLibResolutions(libFileName))) return false;
@@ -136206,7 +136206,7 @@ ${lanes.join("\n")}
136206
136206
  let mapSourceFileToResolvedRef;
136207
136207
  let mapOutputFileToResolvedRef;
136208
136208
  const useSourceOfProjectReferenceRedirect = !!((_d = host.useSourceOfProjectReferenceRedirect) == null ? void 0 : _d.call(host)) && !options.disableSourceOfProjectReferenceRedirect;
136209
- const { onProgramCreateComplete, fileExists: fileExists5, directoryExists: directoryExists2 } = updateHostForUseSourceOfProjectReferenceRedirect({
136209
+ const { onProgramCreateComplete, fileExists: fileExists6, directoryExists: directoryExists2 } = updateHostForUseSourceOfProjectReferenceRedirect({
136210
136210
  compilerHost: host,
136211
136211
  getSymlinkCache,
136212
136212
  useSourceOfProjectReferenceRedirect,
@@ -136440,7 +136440,7 @@ ${lanes.join("\n")}
136440
136440
  getImpliedNodeFormatForEmit: getImpliedNodeFormatForEmit2,
136441
136441
  shouldTransformImportCall,
136442
136442
  emitBuildInfo,
136443
- fileExists: fileExists5,
136443
+ fileExists: fileExists6,
136444
136444
  readFile,
136445
136445
  directoryExists: directoryExists2,
136446
136446
  getSymlinkCache,
@@ -138939,8 +138939,8 @@ ${lanes.join("\n")}
138939
138939
  const originalDirectoryExists = host.compilerHost.directoryExists;
138940
138940
  const originalGetDirectories = host.compilerHost.getDirectories;
138941
138941
  const originalRealpath = host.compilerHost.realpath;
138942
- if (!host.useSourceOfProjectReferenceRedirect) return { onProgramCreateComplete: noop, fileExists: fileExists5 };
138943
- host.compilerHost.fileExists = fileExists5;
138942
+ if (!host.useSourceOfProjectReferenceRedirect) return { onProgramCreateComplete: noop, fileExists: fileExists6 };
138943
+ host.compilerHost.fileExists = fileExists6;
138944
138944
  let directoryExists2;
138945
138945
  if (originalDirectoryExists) {
138946
138946
  directoryExists2 = host.compilerHost.directoryExists = (path71) => {
@@ -138979,13 +138979,13 @@ ${lanes.join("\n")}
138979
138979
  return ((_a = host.getSymlinkCache().getSymlinkedFiles()) == null ? void 0 : _a.get(host.toPath(s))) || originalRealpath.call(host.compilerHost, s);
138980
138980
  };
138981
138981
  }
138982
- return { onProgramCreateComplete, fileExists: fileExists5, directoryExists: directoryExists2 };
138982
+ return { onProgramCreateComplete, fileExists: fileExists6, directoryExists: directoryExists2 };
138983
138983
  function onProgramCreateComplete() {
138984
138984
  host.compilerHost.fileExists = originalFileExists;
138985
138985
  host.compilerHost.directoryExists = originalDirectoryExists;
138986
138986
  host.compilerHost.getDirectories = originalGetDirectories;
138987
138987
  }
138988
- function fileExists5(file) {
138988
+ function fileExists6(file) {
138989
138989
  if (originalFileExists.call(host.compilerHost, file)) return true;
138990
138990
  if (!host.getResolvedProjectReferences()) return false;
138991
138991
  if (!isDeclarationFileName(file)) return false;
@@ -143337,7 +143337,7 @@ ${lanes.join("\n")}
143337
143337
  compilerHost.getSourceFile = (fileName, ...args) => getVersionedSourceFileByPath(fileName, toPath3(fileName), ...args);
143338
143338
  compilerHost.getSourceFileByPath = getVersionedSourceFileByPath;
143339
143339
  compilerHost.getNewLine = () => newLine;
143340
- compilerHost.fileExists = fileExists5;
143340
+ compilerHost.fileExists = fileExists6;
143341
143341
  compilerHost.onReleaseOldSourceFile = onReleaseOldSourceFile;
143342
143342
  compilerHost.onReleaseParsedCommandLine = onReleaseParsedCommandLine;
143343
143343
  compilerHost.toPath = toPath3;
@@ -143541,7 +143541,7 @@ ${lanes.join("\n")}
143541
143541
  function isFilePresenceUnknownOnHost(hostSourceFile) {
143542
143542
  return typeof hostSourceFile.version === "boolean";
143543
143543
  }
143544
- function fileExists5(fileName) {
143544
+ function fileExists6(fileName) {
143545
143545
  const path71 = toPath3(fileName);
143546
143546
  if (isFileMissingOnHost(sourceFilesCache.get(path71))) {
143547
143547
  return false;
@@ -150510,15 +150510,15 @@ ${lanes.join("\n")}
150510
150510
  }
150511
150511
  return true;
150512
150512
  }
150513
- function getMappedLocation(location, sourceMapper, fileExists5) {
150513
+ function getMappedLocation(location, sourceMapper, fileExists6) {
150514
150514
  const mapsTo = sourceMapper.tryGetSourcePosition(location);
150515
- return mapsTo && (!fileExists5 || fileExists5(normalizePath26(mapsTo.fileName)) ? mapsTo : void 0);
150515
+ return mapsTo && (!fileExists6 || fileExists6(normalizePath26(mapsTo.fileName)) ? mapsTo : void 0);
150516
150516
  }
150517
- function getMappedDocumentSpan(documentSpan, sourceMapper, fileExists5) {
150517
+ function getMappedDocumentSpan(documentSpan, sourceMapper, fileExists6) {
150518
150518
  const { fileName, textSpan } = documentSpan;
150519
- const newPosition = getMappedLocation({ fileName, pos: textSpan.start }, sourceMapper, fileExists5);
150519
+ const newPosition = getMappedLocation({ fileName, pos: textSpan.start }, sourceMapper, fileExists6);
150520
150520
  if (!newPosition) return void 0;
150521
- const newEndPosition = getMappedLocation({ fileName, pos: textSpan.start + textSpan.length }, sourceMapper, fileExists5);
150521
+ const newEndPosition = getMappedLocation({ fileName, pos: textSpan.start + textSpan.length }, sourceMapper, fileExists6);
150522
150522
  const newLength = newEndPosition ? newEndPosition.pos - newPosition.pos : textSpan.length;
150523
150523
  return {
150524
150524
  fileName: newPosition.fileName,
@@ -150528,20 +150528,20 @@ ${lanes.join("\n")}
150528
150528
  },
150529
150529
  originalFileName: documentSpan.fileName,
150530
150530
  originalTextSpan: documentSpan.textSpan,
150531
- contextSpan: getMappedContextSpan(documentSpan, sourceMapper, fileExists5),
150531
+ contextSpan: getMappedContextSpan(documentSpan, sourceMapper, fileExists6),
150532
150532
  originalContextSpan: documentSpan.contextSpan
150533
150533
  };
150534
150534
  }
150535
- function getMappedContextSpan(documentSpan, sourceMapper, fileExists5) {
150535
+ function getMappedContextSpan(documentSpan, sourceMapper, fileExists6) {
150536
150536
  const contextSpanStart = documentSpan.contextSpan && getMappedLocation(
150537
150537
  { fileName: documentSpan.fileName, pos: documentSpan.contextSpan.start },
150538
150538
  sourceMapper,
150539
- fileExists5
150539
+ fileExists6
150540
150540
  );
150541
150541
  const contextSpanEnd = documentSpan.contextSpan && getMappedLocation(
150542
150542
  { fileName: documentSpan.fileName, pos: documentSpan.contextSpan.start + documentSpan.contextSpan.length },
150543
150543
  sourceMapper,
150544
- fileExists5
150544
+ fileExists6
150545
150545
  );
150546
150546
  return contextSpanStart && contextSpanEnd ? { start: contextSpanStart.pos, length: contextSpanEnd.pos - contextSpanStart.pos } : void 0;
150547
150547
  }
@@ -199025,9 +199025,9 @@ ${options.prefix}` : "\n" : options.prefix
199025
199025
  this.markContainingProjectsAsDirty();
199026
199026
  }
199027
199027
  }
199028
- close(fileExists5 = true) {
199028
+ close(fileExists6 = true) {
199029
199029
  this.textStorage.isOpen = false;
199030
- if (fileExists5 && this.textStorage.scheduleReloadIfNeeded()) {
199030
+ if (fileExists6 && this.textStorage.scheduleReloadIfNeeded()) {
199031
199031
  this.markContainingProjectsAsDirty();
199032
199032
  }
199033
199033
  }
@@ -200131,11 +200131,11 @@ ${options.prefix}` : "\n" : options.prefix
200131
200131
  this.rootFilesMap.set(path71, { fileName });
200132
200132
  this.markAsDirty();
200133
200133
  }
200134
- removeFile(info, fileExists5, detachFromProject) {
200134
+ removeFile(info, fileExists6, detachFromProject) {
200135
200135
  if (this.isRoot(info)) {
200136
200136
  this.removeRoot(info);
200137
200137
  }
200138
- if (fileExists5) {
200138
+ if (fileExists6) {
200139
200139
  this.resolutionCache.removeResolutionsOfFile(info.path);
200140
200140
  } else {
200141
200141
  this.resolutionCache.invalidateResolutionOfFile(info.path);
@@ -203005,8 +203005,8 @@ ${options.prefix}` : "\n" : options.prefix
203005
203005
  */
203006
203006
  closeOpenFile(info, skipAssignOrphanScriptInfosToInferredProject) {
203007
203007
  var _a;
203008
- const fileExists5 = info.isDynamic ? false : this.host.fileExists(info.fileName);
203009
- info.close(fileExists5);
203008
+ const fileExists6 = info.isDynamic ? false : this.host.fileExists(info.fileName);
203009
+ info.close(fileExists6);
203010
203010
  this.stopWatchingConfigFilesForScriptInfo(info);
203011
203011
  const canonicalFileName = this.toCanonicalFileName(info.fileName);
203012
203012
  if (this.openFilesWithNonRootedDiskPath.get(canonicalFileName) === info) {
@@ -203032,7 +203032,7 @@ ${options.prefix}` : "\n" : options.prefix
203032
203032
  }
203033
203033
  p.removeFile(
203034
203034
  info,
203035
- fileExists5,
203035
+ fileExists6,
203036
203036
  /*detachFromProject*/
203037
203037
  true
203038
203038
  );
@@ -203048,7 +203048,7 @@ ${options.prefix}` : "\n" : options.prefix
203048
203048
  if (!skipAssignOrphanScriptInfosToInferredProject && ensureProjectsForOpenFiles) {
203049
203049
  this.assignOrphanScriptInfosToInferredProject();
203050
203050
  }
203051
- if (fileExists5) {
203051
+ if (fileExists6) {
203052
203052
  this.watchClosedScriptInfo(info);
203053
203053
  } else {
203054
203054
  this.handleDeletedFile(
@@ -232861,18 +232861,18 @@ async function previewArch003(context) {
232861
232861
  const directoryPath = path36.join(context.workspaceRoot, "docs", "architecture");
232862
232862
  const filePath = path36.join(directoryPath, "overview.md");
232863
232863
  const directoryExists2 = await pathExists13(directoryPath);
232864
- const fileExists5 = await pathExists13(filePath);
232864
+ const fileExists6 = await pathExists13(filePath);
232865
232865
  const filesCreated = [];
232866
232866
  if (!directoryExists2) {
232867
232867
  filesCreated.push(relative10(directoryPath, context.workspaceRoot));
232868
232868
  }
232869
- if (!fileExists5) {
232869
+ if (!fileExists6) {
232870
232870
  filesCreated.push(relative10(filePath, context.workspaceRoot));
232871
232871
  }
232872
232872
  return {
232873
232873
  filesCreated,
232874
232874
  filesModified: [],
232875
- diffPreview: fileExists5 ? "(no changes)" : `${directoryExists2 ? "" : `+ create directory ${relative10(directoryPath, context.workspaceRoot)}
232875
+ diffPreview: fileExists6 ? "(no changes)" : `${directoryExists2 ? "" : `+ create directory ${relative10(directoryPath, context.workspaceRoot)}
232876
232876
  `}+ ${architectureOverviewStarterTemplate.trimEnd().replace(/\n/g, "\n+ ")}`,
232877
232877
  description: "Create a starter architecture overview document if missing."
232878
232878
  };
@@ -239759,10 +239759,10 @@ function detectProjectKinds(workspaceRoot, options) {
239759
239759
  addSignal("backend", 3, "Found requirements.txt", true);
239760
239760
  }
239761
239761
  if (hasFile("pyproject.toml")) {
239762
- addSignal("backend", 3, "Found pyproject.toml", true);
239762
+ addSignal("library", 2, "Found pyproject.toml");
239763
239763
  }
239764
239764
  if (hasFile("setup.py")) {
239765
- addSignal("backend", 3, "Found setup.py", true);
239765
+ addSignal("library", 3, "Found setup.py", true);
239766
239766
  }
239767
239767
  if (hasFile("composer.json")) {
239768
239768
  addSignal("backend", 3, "Found composer.json", true);
@@ -239771,7 +239771,10 @@ function detectProjectKinds(workspaceRoot, options) {
239771
239771
  addSignal("backend", 5, "Found Laravel app-root backend evidence", true);
239772
239772
  }
239773
239773
  if (hasFile("go.mod")) {
239774
- addSignal("backend", 3, "Found go.mod", true);
239774
+ addSignal("library", 2, "Found go.mod");
239775
+ }
239776
+ if (hasDirectoryPath("cmd") || hasDirectoryNamed("cmd")) {
239777
+ addSignal("backend", 2, "Found Go cmd/ entrypoint directory");
239775
239778
  }
239776
239779
  if (hasFile("openapi.yaml") || hasFile("openapi.yml")) {
239777
239780
  addSignal("backend", 2, "Found openapi.yaml/openapi.yml", true);
@@ -239782,6 +239785,9 @@ function detectProjectKinds(workspaceRoot, options) {
239782
239785
  if (hasDirectoryPath("src/main/java")) {
239783
239786
  addSignal("backend", 3, "Found src/main/java", true);
239784
239787
  }
239788
+ if (scan.filePaths.some((entry) => /(^|\/)(repositories?|repository)\.py$/u.test(entry))) {
239789
+ addSignal("backend", 2, "Found Python repository-layer files");
239790
+ }
239785
239791
  for (const dependency of ["express", "fastify", "@nestjs/core", "koa", "hapi"]) {
239786
239792
  if (packageHasDependency(dependency)) {
239787
239793
  addSignal("backend", 2, `Found package.json with ${dependency} dependency`);
@@ -239825,6 +239831,12 @@ function detectProjectKinds(workspaceRoot, options) {
239825
239831
  if (hasFile("src/index.ts") || hasFile("src/index.js")) {
239826
239832
  addSignal("library", 2, "Found src/index.ts or src/index.js");
239827
239833
  }
239834
+ if (hasDirectoryPath("src/modules") && !hasFile("requirements.txt")) {
239835
+ addSignal("library", 1, "Found reusable module folders under src/modules");
239836
+ }
239837
+ if (scan.filePaths.some((entry) => entry.startsWith("src/") && entry.endsWith("/__init__.py"))) {
239838
+ addSignal("library", 2, "Found Python package __init__.py files");
239839
+ }
239828
239840
  if (packageSummaries.some((summary) => summary.hasExports)) {
239829
239841
  addSignal("library", 3, "Found package.json exports field", true);
239830
239842
  }
@@ -240306,6 +240318,13 @@ var textDependencyDatabaseHints = [
240306
240318
  { database: "snowflake", pattern: /\b(snowflake-connector|snowflake-sqlalchemy|snowflake-jdbc|Snowflake\.Data|snowflake-sdk)\b/iu, label: "Snowflake dependency" },
240307
240319
  { database: "bigquery", pattern: /\b(bigquery|google-cloud-bigquery|Google\.Cloud\.BigQuery)\b/iu, label: "BigQuery dependency" }
240308
240320
  ];
240321
+ var ormToolingPatterns = [
240322
+ /\b(typeorm|sequelize|drizzle-orm|@mikro-orm\/core|prisma|@prisma\/client)\b/iu,
240323
+ /\b(hibernate-core|org\.hibernate|spring-boot-starter-data-jpa|JpaRepository|CrudRepository|jakarta\.persistence|javax\.persistence)\b/u,
240324
+ /\b(Microsoft\.EntityFrameworkCore|DbContext|DbSet<)\b/u,
240325
+ /\b(SQLAlchemy|sqlalchemy|alembic|Alembic)\b/u,
240326
+ /\b(doctrine\/orm|illuminate\/database)\b/iu
240327
+ ];
240309
240328
  function normalizeConnectionName(value) {
240310
240329
  const normalized = value.trim().toLowerCase().replace(/['"]/gu, "");
240311
240330
  for (const entry of databaseAliases) {
@@ -240529,6 +240548,35 @@ function detectDatabaseHints(workspaceRoot, files, directories, packageJsonSumma
240529
240548
  }
240530
240549
  }
240531
240550
  }
240551
+ const hasRelationalOrmEvidence = ["typeorm", "sequelize", "@sequelize/core", "knex", "drizzle-orm", "@mikro-orm/core", "mikro-orm", "prisma", "@prisma/client"].some((dependency) => dependencyExists(packageJsonSummaries, dependency)) || ["doctrine/orm", "illuminate/database"].some((dependency) => composerDependencyExists(composerJsonSummaries, dependency)) || configCandidateFiles.some((relativePath) => {
240552
+ const content = safeReadText(path59.join(workspaceRoot, ...relativePath.split("/")), 5e4);
240553
+ return content ? ormToolingPatterns.some((pattern) => pattern.test(content)) : false;
240554
+ });
240555
+ for (const relativePath of configCandidateFiles.slice(0, 120)) {
240556
+ const content = safeReadText(path59.join(workspaceRoot, ...relativePath.split("/")), 5e4);
240557
+ if (!content) {
240558
+ continue;
240559
+ }
240560
+ if (/\bNpgsql\.EntityFrameworkCore\.PostgreSQL\b/u.test(content)) {
240561
+ addDb("postgresql", 4, `Found Entity Framework PostgreSQL provider in ${relativePath}`);
240562
+ }
240563
+ if (/\bMicrosoft\.EntityFrameworkCore\.SqlServer\b/u.test(content)) {
240564
+ addDb("sqlserver", 4, `Found Entity Framework SQL Server provider in ${relativePath}`);
240565
+ }
240566
+ if (/\b(Pomelo\.EntityFrameworkCore\.MySql|MySql\.EntityFrameworkCore)\b/u.test(content)) {
240567
+ addDb("mysql", 4, `Found Entity Framework MySQL provider in ${relativePath}`);
240568
+ }
240569
+ if (/\bSqlite\b/u.test(content) && /\bMicrosoft\.EntityFrameworkCore\b/u.test(content)) {
240570
+ addDb("sqlite", 3, `Found Entity Framework SQLite provider in ${relativePath}`);
240571
+ }
240572
+ }
240573
+ if (hasRelationalOrmEvidence) {
240574
+ const primaryScores = Object.entries(scores).filter(([database2, score]) => primaryDatabaseKinds.has(database2) && score > 0);
240575
+ const onlyPrimaryScore = primaryScores.length === 1 ? primaryScores[0] : void 0;
240576
+ if (onlyPrimaryScore) {
240577
+ addDb(onlyPrimaryScore[0], 1, `Found ORM/data-mapper evidence supporting ${onlyPrimaryScore[0]} database selection`);
240578
+ }
240579
+ }
240532
240580
  if (directoryExists(directories, "db") || directoryExists(directories, "migrations")) {
240533
240581
  addEvidence3(evidence, "Found db/ or migrations/ directory");
240534
240582
  }
@@ -240771,16 +240819,67 @@ function parsePackageJsonSummaries3(workspaceRoot, files) {
240771
240819
  summaries.push({
240772
240820
  path: packageJsonPath,
240773
240821
  dependencies,
240774
- scripts
240822
+ scripts,
240823
+ ...typeof parsed.packageManager === "string" ? { packageManager: parsed.packageManager } : {}
240775
240824
  });
240776
240825
  } catch {
240777
240826
  }
240778
240827
  }
240779
240828
  return summaries;
240780
240829
  }
240830
+ function parseComposerJsonSummaries2(workspaceRoot, files) {
240831
+ const composerJsonPaths = files.filter((entry) => entry === "composer.json" || entry.endsWith("/composer.json"));
240832
+ const summaries = [];
240833
+ for (const composerJsonPath of composerJsonPaths) {
240834
+ const absolutePath = path60.join(workspaceRoot, ...composerJsonPath.split("/"));
240835
+ const contents = safeReadText2(absolutePath);
240836
+ if (!contents) {
240837
+ continue;
240838
+ }
240839
+ try {
240840
+ const parsed = JSON.parse(contents);
240841
+ const dependencies = /* @__PURE__ */ new Set();
240842
+ for (const fieldName of ["require", "require-dev"]) {
240843
+ const value = parsed[fieldName];
240844
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
240845
+ continue;
240846
+ }
240847
+ for (const name of Object.keys(value)) {
240848
+ dependencies.add(name.toLowerCase());
240849
+ }
240850
+ }
240851
+ summaries.push({ path: composerJsonPath, dependencies });
240852
+ } catch {
240853
+ }
240854
+ }
240855
+ return summaries;
240856
+ }
240857
+ function dependencyExists2(packageJsonSummaries, name) {
240858
+ return packageJsonSummaries.some((summary) => summary.dependencies.has(name));
240859
+ }
240860
+ function composerDependencyExists2(composerJsonSummaries, name) {
240861
+ const normalized = name.toLowerCase();
240862
+ return composerJsonSummaries.some((summary) => summary.dependencies.has(normalized));
240863
+ }
240864
+ function fileExists4(files, name) {
240865
+ return files.some((entry) => entry === name || entry.endsWith(`/${name}`));
240866
+ }
240867
+ function anyFileExists(files, names) {
240868
+ return names.some((name) => fileExists4(files, name));
240869
+ }
240870
+ function anyPackageManager(packageJsonSummaries, pattern) {
240871
+ return packageJsonSummaries.some((summary) => summary.packageManager ? pattern.test(summary.packageManager) : false);
240872
+ }
240873
+ function configTextMatches(workspaceRoot, files, filePattern, textPattern) {
240874
+ return files.filter((entry) => filePattern.test(entry)).slice(0, 80).some((entry) => {
240875
+ const content = safeReadText2(path60.join(workspaceRoot, ...entry.split("/")), 6e4);
240876
+ return content ? textPattern.test(content) : false;
240877
+ });
240878
+ }
240781
240879
  function detectToolingHints(workspaceRoot, options) {
240782
240880
  const scan = scanWorkspace4(workspaceRoot, 7, options?.ignoreMatcher);
240783
240881
  const packageJsonSummaries = parsePackageJsonSummaries3(workspaceRoot, scan.files);
240882
+ const composerJsonSummaries = parseComposerJsonSummaries2(workspaceRoot, scan.files);
240784
240883
  const toolingHints = [];
240785
240884
  const databaseHints = [];
240786
240885
  const evidence = [];
@@ -240801,6 +240900,15 @@ function detectToolingHints(workspaceRoot, options) {
240801
240900
  if (scan.files.includes("pnpm-workspace.yaml")) {
240802
240901
  addToolingHint("pnpm-workspaces", "Found pnpm-workspace.yaml", 2);
240803
240902
  }
240903
+ if (anyFileExists(scan.files, ["bun.lock", "bun.lockb"]) || anyPackageManager(packageJsonSummaries, /^bun@/iu)) {
240904
+ addToolingHint("bun", "Found Bun lockfile or packageManager hint", 3);
240905
+ }
240906
+ if (anyFileExists(scan.files, ["deno.json", "deno.jsonc", "deno.lock"])) {
240907
+ addToolingHint("deno", "Found Deno config or lockfile", 3);
240908
+ }
240909
+ if (scan.files.some((entry) => /(^|\/)vite\.config\.(?:js|mjs|cjs|ts|mts|cts)$/iu.test(entry)) || dependencyExists2(packageJsonSummaries, "vite")) {
240910
+ addToolingHint("vite", "Found Vite config or dependency", 2);
240911
+ }
240804
240912
  const prismaSchemaPath = scan.files.find((entry) => entry === "prisma/schema.prisma" || entry.endsWith("/prisma/schema.prisma"));
240805
240913
  const hasPrismaDependency = packageJsonSummaries.some((summary) => summary.dependencies.has("prisma"));
240806
240914
  const hasPrismaClientDependency = packageJsonSummaries.some((summary) => summary.dependencies.has("@prisma/client"));
@@ -240828,6 +240936,63 @@ function detectToolingHints(workspaceRoot, options) {
240828
240936
  if (hasPrismaScriptUsage) {
240829
240937
  addToolingHint("prisma", "Found package.json scripts using prisma migrate/generate", 1);
240830
240938
  }
240939
+ const ormPackageHints = [
240940
+ { hint: "drizzle-orm", dependencies: ["drizzle-orm"], note: "Found package.json with drizzle-orm dependency" },
240941
+ { hint: "typeorm", dependencies: ["typeorm"], note: "Found package.json with typeorm dependency" },
240942
+ { hint: "sequelize", dependencies: ["sequelize", "@sequelize/core"], note: "Found package.json with Sequelize dependency" },
240943
+ { hint: "mikro-orm", dependencies: ["@mikro-orm/core", "mikro-orm"], note: "Found package.json with MikroORM dependency" }
240944
+ ];
240945
+ for (const ormHint of ormPackageHints) {
240946
+ if (ormHint.dependencies.some((dependency) => dependencyExists2(packageJsonSummaries, dependency))) {
240947
+ addToolingHint(ormHint.hint, ormHint.note, 2);
240948
+ }
240949
+ }
240950
+ if (configTextMatches(
240951
+ workspaceRoot,
240952
+ scan.files,
240953
+ /(^|\/)(pom\.xml|build\.gradle(\.kts)?|[^/]+\.java|[^/]+\.kt)$/iu,
240954
+ /\b(org\.hibernate|hibernate-core|jakarta\.persistence|javax\.persistence|@Entity\b|@PersistenceContext\b)/u
240955
+ )) {
240956
+ addToolingHint("hibernate", "Found Hibernate/JPA dependency or entity evidence", 2);
240957
+ }
240958
+ if (configTextMatches(
240959
+ workspaceRoot,
240960
+ scan.files,
240961
+ /(^|\/)(pom\.xml|build\.gradle(\.kts)?|[^/]+\.java|[^/]+\.kt)$/iu,
240962
+ /\b(spring-boot-starter-data-jpa|spring-data-jpa|JpaRepository|CrudRepository|PagingAndSortingRepository)\b/u
240963
+ )) {
240964
+ addToolingHint("spring-data-jpa", "Found Spring Data JPA dependency or repository evidence", 2);
240965
+ }
240966
+ if (configTextMatches(
240967
+ workspaceRoot,
240968
+ scan.files,
240969
+ /(^|\/)([^/]+\.csproj|packages\.config|[^/]+\.cs)$/iu,
240970
+ /\b(Microsoft\.EntityFrameworkCore|DbContext|DbSet<)/u
240971
+ )) {
240972
+ addToolingHint("entity-framework-core", "Found Entity Framework Core dependency or DbContext evidence", 2);
240973
+ }
240974
+ if (configTextMatches(
240975
+ workspaceRoot,
240976
+ scan.files,
240977
+ /(^|\/)(requirements.*\.txt|pyproject\.toml|setup\.py|Pipfile|poetry\.lock|[^/]+\.py)$/iu,
240978
+ /\b(SQLAlchemy|sqlalchemy)\b/u
240979
+ )) {
240980
+ addToolingHint("sqlalchemy", "Found SQLAlchemy dependency or import evidence", 2);
240981
+ }
240982
+ if (configTextMatches(
240983
+ workspaceRoot,
240984
+ scan.files,
240985
+ /(^|\/)(requirements.*\.txt|pyproject\.toml|setup\.py|Pipfile|poetry\.lock|alembic\.ini|[^/]+\.py)$/iu,
240986
+ /\b(alembic|Alembic)\b/u
240987
+ ) || fileExists4(scan.files, "alembic.ini")) {
240988
+ addToolingHint("alembic", "Found Alembic dependency or migration config", 2);
240989
+ }
240990
+ if (composerDependencyExists2(composerJsonSummaries, "doctrine/orm")) {
240991
+ addToolingHint("doctrine-orm", "Found composer.json with doctrine/orm dependency", 2);
240992
+ }
240993
+ if (composerDependencyExists2(composerJsonSummaries, "illuminate/database")) {
240994
+ addToolingHint("eloquent", "Found composer.json with illuminate/database dependency", 2);
240995
+ }
240831
240996
  const envFilePath = scan.files.find((entry) => entry === ".env" || entry.endsWith("/.env"));
240832
240997
  if (hasPrismaEvidence && envFilePath) {
240833
240998
  const envText = safeReadText2(path60.join(workspaceRoot, ...envFilePath.split("/")), 2e4);
@@ -240973,7 +241138,7 @@ function scanRoot(rootAbsolutePath, options) {
240973
241138
  directories: unique(directories).sort((left, right) => left.localeCompare(right))
240974
241139
  };
240975
241140
  }
240976
- function fileExists4(files, fileName) {
241141
+ function fileExists5(files, fileName) {
240977
241142
  return files.some((entry) => entry === fileName || entry.endsWith(`/${fileName}`));
240978
241143
  }
240979
241144
  function hasDirectory(directories, directoryName) {
@@ -241076,11 +241241,11 @@ function inspectRoot(workspaceRoot, root, ignoreMatcher) {
241076
241241
  const scan = scanRoot(rootAbsolutePath, { rootRelativePath: root, ignoreMatcher });
241077
241242
  const packageJson = readPackageJsonSummary(rootAbsolutePath);
241078
241243
  const evidence = [];
241079
- const hasApiBoundary = hasDirectory(scan.directories, "controllers") || hasDirectory(scan.directories, "routes") || hasDirectory(scan.directories, "router") || hasDirectory(scan.directories, "src/app/api") || scan.files.some((entry) => /(^|\/)(.*controller.*|.*route.*|.*router.*)\.(ts|tsx|js|jsx|py|java|kt)$/iu.test(entry)) || fileExists4(scan.files, "openapi.yaml") || fileExists4(scan.files, "openapi.yml") || fileExists4(scan.files, "swagger.yaml") || fileExists4(scan.files, "swagger.yml") || hasAnyDependency2(packageJson, ["express", "fastify", "@nestjs/core", "koa", "hapi", "fastapi", "@apollo/server"]);
241244
+ const hasApiBoundary = hasDirectory(scan.directories, "controllers") || hasDirectory(scan.directories, "routes") || hasDirectory(scan.directories, "router") || hasDirectory(scan.directories, "src/app/api") || scan.files.some((entry) => /(^|\/)(.*controller.*|.*route.*|.*router.*)\.(ts|tsx|js|jsx|py|java|kt)$/iu.test(entry)) || fileExists5(scan.files, "openapi.yaml") || fileExists5(scan.files, "openapi.yml") || fileExists5(scan.files, "swagger.yaml") || fileExists5(scan.files, "swagger.yml") || hasAnyDependency2(packageJson, ["express", "fastify", "@nestjs/core", "koa", "hapi", "fastapi", "@apollo/server"]);
241080
241245
  if (hasApiBoundary) {
241081
241246
  addEvidence5(evidence, "Detected controllers/routes or backend API framework signals");
241082
241247
  }
241083
- const hasDatabaseAccess = hasDirectory(scan.directories, "db") || hasDirectory(scan.directories, "migrations") || fileExists4(scan.files, "prisma/schema.prisma") || fileExists4(scan.files, "ormconfig.ts") || fileExists4(scan.files, "ormconfig.js") || fileExists4(scan.files, "ormconfig.json") || fileExists4(scan.files, "knexfile.ts") || fileExists4(scan.files, "knexfile.js") || fileExists4(scan.files, "application.yml") || fileExists4(scan.files, "application.yaml") || fileExists4(scan.files, "application.properties") || hasAnyDependency2(packageJson, [
241248
+ const hasDatabaseAccess = hasDirectory(scan.directories, "db") || hasDirectory(scan.directories, "migrations") || fileExists5(scan.files, "prisma/schema.prisma") || fileExists5(scan.files, "ormconfig.ts") || fileExists5(scan.files, "ormconfig.js") || fileExists5(scan.files, "ormconfig.json") || fileExists5(scan.files, "knexfile.ts") || fileExists5(scan.files, "knexfile.js") || fileExists5(scan.files, "application.yml") || fileExists5(scan.files, "application.yaml") || fileExists5(scan.files, "application.properties") || hasAnyDependency2(packageJson, [
241084
241249
  "@prisma/client",
241085
241250
  "prisma",
241086
241251
  "typeorm",
@@ -241103,7 +241268,7 @@ function inspectRoot(workspaceRoot, root, ignoreMatcher) {
241103
241268
  if (hasServiceEnvConfig) {
241104
241269
  addEvidence5(evidence, "Detected service-specific environment configuration");
241105
241270
  }
241106
- const hasOpenApiSpec = fileExists4(scan.files, "openapi.yaml") || fileExists4(scan.files, "openapi.yml") || fileExists4(scan.files, "openapi.json") || fileExists4(scan.files, "contracts/openapi.yaml") || fileExists4(scan.files, "contracts/openapi.yml") || fileExists4(scan.files, "swagger.yaml") || fileExists4(scan.files, "swagger.yml") || fileExists4(scan.files, "swagger.json");
241271
+ const hasOpenApiSpec = fileExists5(scan.files, "openapi.yaml") || fileExists5(scan.files, "openapi.yml") || fileExists5(scan.files, "openapi.json") || fileExists5(scan.files, "contracts/openapi.yaml") || fileExists5(scan.files, "contracts/openapi.yml") || fileExists5(scan.files, "swagger.yaml") || fileExists5(scan.files, "swagger.yml") || fileExists5(scan.files, "swagger.json");
241107
241272
  if (hasOpenApiSpec) {
241108
241273
  addEvidence5(evidence, "Detected service-specific OpenAPI contract");
241109
241274
  }
@@ -241111,8 +241276,8 @@ function inspectRoot(workspaceRoot, root, ignoreMatcher) {
241111
241276
  if (hasModulesRoot) {
241112
241277
  addEvidence5(evidence, "Detected modules root");
241113
241278
  }
241114
- const hasFrontendSignals = fileExists4(scan.files, "next.config.js") || fileExists4(scan.files, "next.config.ts") || fileExists4(scan.files, "angular.json") || fileExists4(scan.files, "vite.config.ts") || fileExists4(scan.files, "vite.config.js") || hasDirectory(scan.directories, "pages") || hasDirectory(scan.directories, "public") || hasAnyDependency2(packageJson, ["next", "react", "vue", "@angular/core"]);
241115
- const hasBackendSignals = hasApiBoundary || hasDatabaseAccess || fileExists4(scan.files, "pom.xml") || fileExists4(scan.files, "requirements.txt") || fileExists4(scan.files, "go.mod") || hasDirectory(scan.directories, "src/main/java");
241279
+ const hasFrontendSignals = fileExists5(scan.files, "next.config.js") || fileExists5(scan.files, "next.config.ts") || fileExists5(scan.files, "angular.json") || fileExists5(scan.files, "vite.config.ts") || fileExists5(scan.files, "vite.config.js") || hasDirectory(scan.directories, "pages") || hasDirectory(scan.directories, "public") || hasAnyDependency2(packageJson, ["next", "react", "vue", "@angular/core"]);
241280
+ const hasBackendSignals = hasApiBoundary || hasDatabaseAccess || fileExists5(scan.files, "pom.xml") || fileExists5(scan.files, "requirements.txt") || fileExists5(scan.files, "go.mod") || hasDirectory(scan.directories, "src/main/java");
241116
241281
  let classification = "unknown";
241117
241282
  if ((root.startsWith("packages/") || root.startsWith("libs/")) && !hasApiBoundary) {
241118
241283
  classification = "shared_package";
@@ -241562,6 +241727,9 @@ function addEvidence7(evidence, value) {
241562
241727
  function uniqueSorted5(values) {
241563
241728
  return [...new Set(values)].sort((left, right) => left.localeCompare(right));
241564
241729
  }
241730
+ function uniqueLimited(values, limit = 5) {
241731
+ return uniqueSorted5(values.filter((value) => value.trim().length > 0)).slice(0, limit);
241732
+ }
241565
241733
  function uniqueInOrder2(values) {
241566
241734
  const seen = /* @__PURE__ */ new Set();
241567
241735
  const result = [];
@@ -241865,6 +242033,51 @@ function hasAttributableBackendRoot(detection) {
241865
242033
  function isGenericLibraryStack(stack) {
241866
242034
  return stack === "node_typescript" || stack === "node_javascript" || stack === "python" || stack === "go" || stack === "kotlin" || stack === "terraform" || stack === "ansible";
241867
242035
  }
242036
+ function hasProjectKindEvidence(detection, patterns) {
242037
+ const evidence = detection.projectKinds?.evidence ?? [];
242038
+ return evidence.some((entry) => patterns.some((pattern) => pattern.test(entry)));
242039
+ }
242040
+ function resolveSingleProjectGenericKinds(detection, selectedAdapterId, selectedHints, evidence) {
242041
+ if (detection.topology.topology !== "single_project" || !selectedAdapterId || getStackAdapterById(selectedAdapterId).category !== "generic-language" || !(selectedHints.includes("backend") && selectedHints.includes("library"))) {
242042
+ return void 0;
242043
+ }
242044
+ const hasFrameworkBackendCandidate = (detection.stacks.candidates ?? []).some((candidate) => {
242045
+ const adapter = getStackAdapterById(candidate.adapterId);
242046
+ return adapter.category === "framework" && adapter.id !== selectedAdapterId;
242047
+ });
242048
+ if (hasFrameworkBackendCandidate) {
242049
+ return void 0;
242050
+ }
242051
+ const hasServiceBackendEvidence = hasProjectKindEvidence(detection, [
242052
+ /requirements\.txt/u,
242053
+ /composer\.json/u,
242054
+ /pom\.xml/u,
242055
+ /build\.gradle/u,
242056
+ /openapi/u,
242057
+ /db\/ or migrations/u,
242058
+ /repository-layer/u
242059
+ ]);
242060
+ const hasCliEntrypointEvidence = hasProjectKindEvidence(detection, [
242061
+ /cmd\/ entrypoint/u,
242062
+ /CLI-style entrypoint/u
242063
+ ]);
242064
+ const hasLibraryEvidence = hasProjectKindEvidence(detection, [
242065
+ /exports field/u,
242066
+ /main\/module\/types fields/u,
242067
+ /src\/index/u,
242068
+ /pyproject\.toml/u,
242069
+ /setup\.py/u,
242070
+ /go\.mod/u,
242071
+ /Python package __init__\.py/u,
242072
+ /reusable module folders/u
242073
+ ]);
242074
+ const selected = hasLibraryEvidence && !hasServiceBackendEvidence ? "library" : "backend";
242075
+ addEvidence7(evidence, `Reduced single-project generic ${selectedAdapterId} project kind to ${selected}`);
242076
+ if (selected === "library" && hasCliEntrypointEvidence) {
242077
+ addEvidence7(evidence, "Treated CLI entrypoint as package tooling rather than backend service evidence");
242078
+ }
242079
+ return [selected];
242080
+ }
241868
242081
  function normalizeDetectedBackendStack(value) {
241869
242082
  if (value === "springboot") {
241870
242083
  return "spring";
@@ -241941,7 +242154,15 @@ function resolveNormalizedProjectKinds(detection, evidence) {
241941
242154
  evidence: []
241942
242155
  }) ?? [] : []);
241943
242156
  let normalizedKinds = [];
241944
- if (detection.topology.topology === "single_project" && selectedHints.length > 0 && selectedAdapter.category === "framework") {
242157
+ const genericSingleProjectKinds = resolveSingleProjectGenericKinds(
242158
+ detection,
242159
+ selectedAdapterId,
242160
+ selectedHints,
242161
+ evidence
242162
+ );
242163
+ if (genericSingleProjectKinds) {
242164
+ normalizedKinds = genericSingleProjectKinds;
242165
+ } else if (detection.topology.topology === "single_project" && selectedHints.length > 0 && selectedAdapter.category === "framework") {
241945
242166
  normalizedKinds = uniqueSortedKinds(selectedHints);
241946
242167
  addEvidence7(evidence, `Normalized project kinds from selected adapter '${selectedAdapter.id}' hints`);
241947
242168
  } else if (detection.topology.topology === "monorepo" && selectedHints.length > 0) {
@@ -242237,6 +242458,40 @@ function resolveConfidence2(proposal, detection) {
242237
242458
  }
242238
242459
  return "low";
242239
242460
  }
242461
+ function resolveProposalReasons(input2) {
242462
+ const reasons = {};
242463
+ const selectedStackIds = uniqueSorted5(
242464
+ Object.values(input2.stackSelections).filter((stack) => Boolean(stack))
242465
+ );
242466
+ const selectedStackReasons = selectedStackIds.flatMap((stackId) => input2.detection.stacks.candidates?.find((candidate) => candidate.adapterId === stackId)?.evidence ?? []);
242467
+ if (selectedStackReasons.length > 0) {
242468
+ reasons.framework = uniqueLimited(selectedStackReasons);
242469
+ }
242470
+ if (input2.apiStyle && input2.apiStyle !== "unknown") {
242471
+ reasons.api = input2.apiStyle === "none" ? uniqueLimited(input2.detection.apiDbHints?.api.evidence ?? ["No API artifacts detected for current project profile"], 4) : uniqueLimited(input2.detection.apiDbHints?.api.evidence ?? [], 4);
242472
+ }
242473
+ if (input2.database && input2.database !== "unknown") {
242474
+ const databaseEvidence = [
242475
+ ...input2.detection.apiDbHints?.database.evidence ?? [],
242476
+ ...(input2.detection.toolingHints?.evidence ?? []).filter((entry) => /prisma|datasource|database|orm|jpa|entity framework|sqlalchemy|alembic/iu.test(entry))
242477
+ ];
242478
+ reasons.database = input2.database === "none" ? uniqueLimited(databaseEvidence.length > 0 ? databaseEvidence : ["No database artifacts detected for current project profile"], 4) : uniqueLimited(databaseEvidence, 4);
242479
+ }
242480
+ if ((input2.detection.toolingHints?.toolingHints.length ?? 0) > 0) {
242481
+ reasons.tooling = uniqueLimited(input2.detection.toolingHints?.evidence ?? [], 5);
242482
+ }
242483
+ if (input2.architectureStyleEvidence.length > 0) {
242484
+ reasons.architectureStyle = uniqueLimited(input2.architectureStyleEvidence, 5);
242485
+ }
242486
+ if (input2.modulesRoot || (input2.moduleRoots?.length ?? 0) > 0) {
242487
+ reasons.modulesRoot = uniqueLimited([
242488
+ ...input2.modulesRoot ? [`Detected modules root at ${input2.modulesRoot}`] : [],
242489
+ ...(input2.moduleRoots ?? []).length > 0 ? [`Detected module roots: ${(input2.moduleRoots ?? []).join(", ")}`] : [],
242490
+ ...input2.detection.modules?.evidence ?? []
242491
+ ], 4);
242492
+ }
242493
+ return Object.keys(reasons).length > 0 ? reasons : void 0;
242494
+ }
242240
242495
  function buildArchitectureProposal(detection) {
242241
242496
  const evidence = [];
242242
242497
  addEvidence7(evidence, `Detected ${detection.topology.topology} topology`);
@@ -242287,6 +242542,15 @@ function buildArchitectureProposal(detection) {
242287
242542
  if ((detection.toolingHints?.toolingHints.length ?? 0) > 0) {
242288
242543
  addEvidence7(evidence, `Detected tooling hints: ${(detection.toolingHints?.toolingHints ?? []).join(", ")}`);
242289
242544
  }
242545
+ const proposalReasons = resolveProposalReasons({
242546
+ detection,
242547
+ stackSelections,
242548
+ architectureStyleEvidence: architectureStyleDecision.architectureStyleEvidence,
242549
+ modulesRoot,
242550
+ moduleRoots,
242551
+ apiStyle,
242552
+ database
242553
+ });
242290
242554
  const proposalCore = {
242291
242555
  ...projectKinds.length > 0 ? { projectKinds } : {},
242292
242556
  ...architectureStyle ? { architectureStyle } : {},
@@ -242306,7 +242570,8 @@ function buildArchitectureProposal(detection) {
242306
242570
  ...modules.length > 0 ? { modules: uniqueSorted5(modules) } : {},
242307
242571
  ...moduleCandidates.length > 0 ? { moduleCandidates } : {},
242308
242572
  ...moduleCandidateGroups ? { moduleCandidateGroups } : {},
242309
- ...(detection.detectedComponents?.length ?? 0) > 0 ? { detectedComponents: detection.detectedComponents } : {}
242573
+ ...(detection.detectedComponents?.length ?? 0) > 0 ? { detectedComponents: detection.detectedComponents } : {},
242574
+ ...proposalReasons ? { proposalReasons } : {}
242310
242575
  };
242311
242576
  const confidence = resolveConfidence2(proposalCore, detection);
242312
242577
  if (architectureStyle) {
@@ -245377,8 +245642,6 @@ function normalizeCanonicalStackId(value) {
245377
245642
  "dotnet",
245378
245643
  "ruby_rails",
245379
245644
  "kotlin",
245380
- "kotlin_spring",
245381
- "rust",
245382
245645
  "react",
245383
245646
  "angular",
245384
245647
  "vue",
@@ -245510,54 +245773,72 @@ function formatModuleRootsSummary(proposal, inputs) {
245510
245773
  function renderSmartInitDetectionSummary(detection, inputs) {
245511
245774
  const proposal = detection.proposal ?? buildArchitectureProposal(detection);
245512
245775
  const formatStack = (stack) => canonicalStackLabels[stack] ?? stack.replace(/_/gu, " ");
245776
+ const formatToken = (value) => value.replace(/_/gu, " ");
245777
+ const formatList = (values, formatter = formatToken) => {
245778
+ const unique2 = [...new Set((values ?? []).filter((value) => typeof value === "string" && value.length > 0))];
245779
+ return unique2.length > 0 ? unique2.map(formatter).join(", ") : "(none)";
245780
+ };
245781
+ const selectedStacks = [
245782
+ inputs.stacks.backendStack,
245783
+ inputs.stacks.frontendStack,
245784
+ inputs.stacks.libraryStack
245785
+ ].filter((stack) => typeof stack === "string" && stack.length > 0);
245786
+ const selectedStackIds = new Set(selectedStacks);
245513
245787
  const backendStacks = [...new Set(detection.stacks.backendStacks ?? [])];
245788
+ const frontendStacks = [...new Set(detection.stacks.frontendStacks ?? [])];
245514
245789
  const libraryStacks = [
245515
245790
  ...new Set(
245516
245791
  (proposal.detectedComponents ?? detection.detectedComponents ?? []).filter((component) => component.kind === "library" || component.kind === "data").map((component) => component.stack).filter((stack) => typeof stack === "string" && stack.length > 0)
245517
245792
  )
245518
245793
  ];
245794
+ const candidateStacks = [
245795
+ ...backendStacks,
245796
+ ...frontendStacks,
245797
+ ...libraryStacks,
245798
+ ...detection.stacks.stacks ?? []
245799
+ ].filter(
245800
+ (stack, index, values) => typeof stack === "string" && stack.length > 0 && !selectedStackIds.has(stack) && values.indexOf(stack) === index
245801
+ );
245802
+ const toolingHints = proposal.toolingHints ?? detection.toolingHints?.toolingHints ?? [];
245803
+ const databaseHints = proposal.databaseHints ?? detection.apiDbHints?.database?.databaseHints ?? [];
245804
+ const apiHint = proposal.apiStyle ?? inputs.apiStyle;
245805
+ const selectedModules = inputs.modules ?? proposal.modules ?? [];
245806
+ const architectureCandidates = (proposal.moduleCandidates ?? []).filter((candidate) => candidate.classification === "architecture-module").map((candidate) => candidate.name);
245807
+ const ignoredCandidates = (proposal.moduleCandidates ?? []).filter((candidate) => candidate.classification === "technical-layer" || candidate.classification === "shared-infrastructure").map((candidate) => `${candidate.name} (${formatToken(candidate.classification)})`);
245519
245808
  const lines = [];
245520
- lines.push("ArchPilot Init - Smart Init Detection Summary");
245809
+ lines.push("Detected Architecture Signals");
245521
245810
  lines.push("");
245522
- lines.push(`Repository topology: ${detection.topology.topology}`);
245523
- lines.push(`Project kinds: ${(proposal.projectKinds ?? detection.projectKinds?.kinds ?? []).join(", ") || "(none)"}`);
245524
- lines.push(`Detected stacks: ${detection.stacks.selectedAdapterId ?? "(none)"}`);
245525
- lines.push(
245526
- inputs.stacks.backendStack ? `Selected backend stack: ${formatStack(inputs.stacks.backendStack)}${backendStacks.length > 1 ? ` (detected: ${backendStacks.map(formatStack).join(", ")})` : ""}` : backendStacks.length > 1 ? `Backend stacks: ${backendStacks.map(formatStack).join(", ")}` : `Backend stack: ${proposal.stackSelections?.backend ? formatStack(proposal.stackSelections.backend) : "(pending)"}`
245527
- );
245528
- if ((proposal.projectKinds ?? []).includes("library")) {
245529
- lines.push(
245530
- libraryStacks.length > 1 ? `Library stacks: ${libraryStacks.map(formatStack).join(", ")}` : `Library stack: ${proposal.stackSelections?.library ? formatStack(proposal.stackSelections.library) : "(pending)"}`
245531
- );
245532
- }
245533
- lines.push(`Module roots: ${formatModuleRootsSummary(proposal, inputs)}`);
245534
- if (Array.isArray(proposal.moduleCandidates) && proposal.moduleCandidates.length > 0) {
245535
- const groups = proposal.moduleCandidateGroups ?? {
245536
- "architecture-module": proposal.moduleCandidates.filter((candidate) => candidate.classification === "architecture-module"),
245537
- "technical-layer": proposal.moduleCandidates.filter((candidate) => candidate.classification === "technical-layer"),
245538
- "shared-infrastructure": proposal.moduleCandidates.filter((candidate) => candidate.classification === "shared-infrastructure"),
245539
- uncertain: proposal.moduleCandidates.filter((candidate) => candidate.classification === "uncertain")
245540
- };
245541
- const labels = {
245542
- "architecture-module": "Architecture Modules",
245543
- "technical-layer": "Technical Layers",
245544
- "shared-infrastructure": "Shared Infrastructure",
245545
- uncertain: "Uncertain Candidates"
245546
- };
245547
- for (const key of ["architecture-module", "technical-layer", "shared-infrastructure", "uncertain"]) {
245548
- const candidates = groups[key] ?? [];
245549
- if (candidates.length > 0) {
245550
- lines.push(`${labels[key]}: ${candidates.map((candidate) => candidate.name).join(", ")}`);
245551
- }
245552
- }
245553
- lines.push(`Selected architecture modules: ${inputs.modules?.join(", ") || "(none)"}`);
245811
+ lines.push(`Primary stack: ${formatList(selectedStacks, formatStack)}`);
245812
+ lines.push(`Additional stack signals: ${formatList(candidateStacks, formatStack)}`);
245813
+ lines.push(`Project kind: ${formatList(proposal.projectKinds ?? detection.projectKinds?.kinds, formatToken)}`);
245814
+ lines.push(`Repository topology: ${formatToken(detection.topology.topology)}`);
245815
+ lines.push(`Modules root: ${formatModuleRootsSummary(proposal, inputs)}`);
245816
+ if (selectedModules.length > 0) {
245817
+ lines.push(`Selected architecture modules: ${formatList(selectedModules)}`);
245818
+ } else if (architectureCandidates.length > 0) {
245819
+ lines.push(`Architecture module candidates: ${formatList(architectureCandidates)}`);
245820
+ } else {
245821
+ lines.push("Architecture modules: none confidently detected");
245822
+ }
245823
+ if (ignoredCandidates.length > 0) {
245824
+ lines.push(`Ignored technical/shared folders: ${formatList(ignoredCandidates)}`);
245825
+ }
245826
+ lines.push(`API hints: ${apiHint && apiHint !== "unknown" ? formatToken(apiHint) : "(none)"}`);
245827
+ lines.push(`Database hints: ${formatList(databaseHints, formatToken)}`);
245828
+ lines.push(`Tooling/data hints: ${formatList(toolingHints, formatToken)}`);
245829
+ lines.push(`Detection confidence: ${proposal.confidence}`);
245830
+ const proposalReasons = proposal.proposalReasons ?? {};
245831
+ const reasonEntries = [
245832
+ ...proposalReasons.framework?.length ? [`Framework: ${formatList(proposalReasons.framework.slice(0, 2))}`] : [],
245833
+ ...proposalReasons.api?.length ? [`API: ${formatList(proposalReasons.api.slice(0, 2))}`] : [],
245834
+ ...proposalReasons.database?.length ? [`Database: ${formatList(proposalReasons.database.slice(0, 2))}`] : []
245835
+ ];
245836
+ if (reasonEntries.length > 0) {
245837
+ lines.push(`Reasons: ${reasonEntries.join(" | ")}`);
245554
245838
  }
245555
- lines.push(`API style: ${proposal.apiStyle ?? inputs.apiStyle}`);
245556
- lines.push(`Database: ${proposal.database ?? inputs.database}`);
245557
- lines.push(`Components: ${Array.isArray(proposal.components) && proposal.components.length > 0 ? proposal.components.map((component) => `${component.id ?? "(unnamed)"}:${component.path ?? "(root)"}`).join(", ") : "(none)"}`);
245558
- lines.push(`Resources: ${Array.isArray(proposal.resources) && proposal.resources.length > 0 ? proposal.resources.map((resource) => `${resource.id ?? "(unnamed)"}:${resource.type ?? "resource"}:${resource.provider ?? "unknown"}`).join(", ") : "(none)"}`);
245839
+ lines.push(`Evidence: ${formatList((proposal.evidence ?? []).slice(0, 5))}`);
245559
245840
  lines.push("");
245560
- lines.push("Detected values are preselected. You can edit them before anything is written.");
245841
+ lines.push("Detected values are preselected. Tooling/data hints are supporting signals, not primary stack adapters. You can edit everything before anything is written.");
245561
245842
  return lines.join("\n");
245562
245843
  }
245563
245844
  function renderArchitectureInputsSummary(inputs) {
@@ -245693,7 +245974,7 @@ async function editModulesAndApi(rl, inputs) {
245693
245974
  return authStyle;
245694
245975
  }
245695
245976
  inputs.authStyle = authStyle;
245696
- const database = await askInitValue(rl, "Legacy database field (none, postgresql, mysql)", inputs.database, isValidDatabaseChoice);
245977
+ const database = await askInitValue(rl, "Primary database (none, postgresql, mysql)", inputs.database, isValidDatabaseChoice);
245697
245978
  if (database === "back" || database === "cancel") {
245698
245979
  return database;
245699
245980
  }
@@ -249444,7 +249725,7 @@ async function runArchpilotCli() {
249444
249725
  if (workspaceRoot !== invocationDirectory) {
249445
249726
  process.chdir(workspaceRoot);
249446
249727
  }
249447
- } else {
249728
+ } else if (command !== "init") {
249448
249729
  const discoveredWorkspaceRoot = findArchpilotWorkspaceRoot(invocationDirectory);
249449
249730
  if (discoveredWorkspaceRoot && discoveredWorkspaceRoot !== invocationDirectory) {
249450
249731
  process.chdir(discoveredWorkspaceRoot);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@archpilotlabs/archpilot",
3
- "version": "0.0.18",
4
- "description": "Executable architecture governance CLI",
3
+ "version": "0.0.19",
4
+ "description": "Executable architecture governance CLI for Smart Init, validation, drift detection, and CI.",
5
5
  "homepage": "https://archpilot.org",
6
6
  "bugs": {
7
7
  "url": "https://archpilot.org/contact"
@@ -22,7 +22,8 @@
22
22
  "dependency-validation",
23
23
  "adr",
24
24
  "ci",
25
- "developer-tools"
25
+ "developer-tools",
26
+ "smart-init"
26
27
  ],
27
28
  "scripts": {
28
29
  "build": "npm run compile",