@archpilotlabs/archpilot 0.0.7 → 0.0.9

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
@@ -5,14 +5,18 @@
5
5
  <h1 align="center">ArchPilot</h1>
6
6
 
7
7
  <p align="center">
8
- Continuous architecture governance for engineering teams
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.
12
+
13
+ **Your AI writes code. ArchPilot protects architecture.**
14
+
11
15
  <p align="center">
12
16
  Validate • Enforce • Measure architecture continuously
13
17
  </p>
14
18
 
15
- ArchPilot is a CLI that validates your repository against architecture rules and helps you keep your system **visible, enforceable, and measurable** as it evolves.
19
+ ArchPilot makes architecture **visible, enforceable, and measurable** as your system evolves.
16
20
 
17
21
  ---
18
22
 
@@ -24,6 +28,10 @@ Over time, boundaries blur, dependencies leak, and architectural decisions drift
24
28
 
25
29
  ArchPilot turns architecture into something you can **actually validate, enforce, and track continuously**.
26
30
 
31
+ AI accelerates code delivery-but architectural drift accelerates too.
32
+
33
+ ArchPilot gives teams deterministic architecture guardrails in local development, CI, and governance workflows.
34
+
27
35
  ---
28
36
 
29
37
  ## 🚀 What it does
@@ -40,7 +48,46 @@ No runtime agents. No external services. Fully deterministic and local-first.
40
48
 
41
49
  ---
42
50
 
43
- ## Local-first. Cloud-powered.
51
+ ## 🧩 Supported Stacks
52
+
53
+ ArchPilot supports **practical deterministic architecture detection** across modern application stacks.
54
+
55
+ ### Backend
56
+
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
71
+
72
+ ### Frontend
73
+
74
+ * React
75
+ * Next.js
76
+ * Angular
77
+ * Vue
78
+
79
+ ### Infrastructure
80
+
81
+ * Terraform
82
+ * Ansible
83
+
84
+ ArchPilot detects stack shape from practical repository evidence - framework markers, manifests, project layout, and source structure - then applies conservative architecture discovery defaults locally.
85
+
86
+ **No cloud scanning. No remote indexing. Deterministic local validation.**
87
+
88
+ ---
89
+
90
+ ## ☁️ Local-first. Cloud-powered.
44
91
 
45
92
  Start locally in minutes:
46
93
 
@@ -65,12 +112,9 @@ archpilot cloud connect
65
112
  archpilot cloud disconnect
66
113
  ```
67
114
 
68
- Cloud setup only asks for your API token. ArchPilot uses the canonical production API automatically:
69
-
70
- `https://api.archpilot.org`
71
-
72
- For local development, set `ARCHPILOT_CLOUD_URL=http://localhost:4000` before running Cloud commands. CLI `--server` remains available for one-off debugging and automated tests.
115
+ Cloud setup only asks for your API token.
73
116
 
117
+ ArchPilot uses the canonical production API automatically-no server URL setup required for normal use.
74
118
  Learn more:
75
119
 
76
120
  https://archpilot.org/docs
@@ -203,7 +247,7 @@ archpilot governance upload
203
247
 
204
248
  ---
205
249
 
206
- ## 🧩 Core Capabilities
250
+ ## ⚙️ Core Capabilities
207
251
 
208
252
  * Architecture-as-code (`.archpilot/`)
209
253
  * Dependency validation and module contracts
@@ -210932,15 +210932,20 @@ var import_node_fs = require("node:fs");
210932
210932
  var validProjectKinds = /* @__PURE__ */ new Set(["backend", "frontend", "library"]);
210933
210933
  var validCanonicalStackIds = /* @__PURE__ */ new Set([
210934
210934
  "node_typescript",
210935
+ "express",
210935
210936
  "nestjs",
210936
210937
  "java",
210937
210938
  "spring",
210938
210939
  "fastapi",
210940
+ "django",
210941
+ "flask",
210939
210942
  "python",
210940
210943
  "php",
210944
+ "laravel",
210941
210945
  "go",
210942
210946
  "dotnet",
210943
210947
  "ruby_rails",
210948
+ "kotlin",
210944
210949
  "kotlin_spring",
210945
210950
  "rust",
210946
210951
  "react",
@@ -210954,15 +210959,20 @@ var validCanonicalStackIds = /* @__PURE__ */ new Set([
210954
210959
  ]);
210955
210960
  var canonicalStackLabels = {
210956
210961
  node_typescript: "TypeScript / Node.js",
210962
+ express: "Express.js",
210957
210963
  nestjs: "NestJS",
210958
210964
  java: "Java",
210959
210965
  spring: "Spring",
210960
210966
  fastapi: "FastAPI",
210967
+ django: "Django",
210968
+ flask: "Flask",
210961
210969
  python: "Python",
210962
210970
  php: "PHP",
210971
+ laravel: "Laravel",
210963
210972
  go: "Go",
210964
210973
  dotnet: ".NET",
210965
210974
  ruby_rails: "Ruby / Rails",
210975
+ kotlin: "Kotlin",
210966
210976
  kotlin_spring: "Kotlin / Spring",
210967
210977
  rust: "Rust",
210968
210978
  react: "React",
@@ -211173,6 +211183,103 @@ var nodeTypescriptAdapter = buildAdapter({
211173
211183
  var stackAdaptersById = {
211174
211184
  other: genericDefaultAdapter,
211175
211185
  node_typescript: nodeTypescriptAdapter,
211186
+ express: buildAdapter({
211187
+ id: "express",
211188
+ displayName: canonicalStackLabels.express,
211189
+ ecosystem: "node",
211190
+ category: "framework",
211191
+ priority: 70,
211192
+ manifestDetectionHints: ["package.json", "tsconfig.json", "app.js", "app.ts", "server.js", "server.ts"],
211193
+ buildToolHints: ["npm", "pnpm", "yarn"],
211194
+ defaults: {
211195
+ sourceRoots: ["src", "."],
211196
+ moduleRootCandidates: ["src/modules", "src/features", "routes", "src/routes", "src"],
211197
+ apiRoots: ["routes", "src/routes", "src/api", "api"],
211198
+ databaseRoots: ["prisma", "db", "database", "migrations"],
211199
+ configFiles: ["package.json", "tsconfig.json"],
211200
+ fileExtensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
211201
+ dependencyFileExtensions: [".ts", ".js", ".mjs", ".cjs"],
211202
+ ignoreDirectories: ["node_modules", "dist", "coverage"],
211203
+ entrypointHints: ["app.ts", "server.ts", "src/app.ts", "src/server.ts", "app.js", "server.js", "src/app.js", "src/server.js"],
211204
+ publicEntrypointDirectories: ["public"],
211205
+ moduleIndexFileName: "index.ts"
211206
+ },
211207
+ detect: (context) => {
211208
+ const evidence = [];
211209
+ let strongSignals = 0;
211210
+ let mediumSignals = 0;
211211
+ let score = 0;
211212
+ const hasExpressDependency = context.hasPackageDependency("express");
211213
+ const hasExpressFactory = context.hasAnyFileMatchingPattern(
211214
+ /\.(ts|js|mjs|cjs)$/iu,
211215
+ /\bexpress\s*\(\s*\)/u
211216
+ );
211217
+ const hasExpressImport = context.hasAnyFileMatchingPattern(
211218
+ /\.(ts|js|mjs|cjs)$/iu,
211219
+ /\b(import\s+express\s+from\s+['"]express['"]|require\s*\(\s*['"]express['"]\s*\))/u
211220
+ );
211221
+ const hasAppUse = context.hasAnyFileMatchingPattern(
211222
+ /\.(ts|js|mjs|cjs)$/iu,
211223
+ /\bapp\.use\s*\(/u
211224
+ );
211225
+ const hasRouterUsage = context.hasAnyFileMatchingPattern(
211226
+ /\.(ts|js|mjs|cjs)$/iu,
211227
+ /\b(router|Router)\s*\.\s*(get|post|put|patch|delete|use)\s*\(/u
211228
+ );
211229
+ const hasRoutesDirectory = context.hasDirectory("routes") || context.hasDirectory("src/routes");
211230
+ const hasRouteFiles = context.hasAnyPathMatchingPattern(/(^|\/)(routes|src\/routes)\/.+\.(ts|js|mjs|cjs)$/iu);
211231
+ if (hasExpressDependency) {
211232
+ strongSignals += 1;
211233
+ score += 46;
211234
+ evidence.push("Found package.json with express dependency");
211235
+ }
211236
+ if (hasExpressFactory) {
211237
+ strongSignals += 1;
211238
+ score += 28;
211239
+ evidence.push("Found express() app construction");
211240
+ }
211241
+ if (hasExpressImport) {
211242
+ mediumSignals += 1;
211243
+ score += 12;
211244
+ evidence.push("Found Express import or require");
211245
+ }
211246
+ if (hasAppUse) {
211247
+ mediumSignals += 1;
211248
+ score += 10;
211249
+ evidence.push("Found app.use(...) middleware or router mounting");
211250
+ }
211251
+ if (hasRouterUsage) {
211252
+ mediumSignals += 1;
211253
+ score += 10;
211254
+ evidence.push("Found router HTTP method usage");
211255
+ }
211256
+ if (hasRoutesDirectory) {
211257
+ mediumSignals += 1;
211258
+ score += 6;
211259
+ evidence.push("Found routes/ or src/routes/ directory");
211260
+ }
211261
+ if (hasRouteFiles) {
211262
+ mediumSignals += 1;
211263
+ score += 5;
211264
+ evidence.push("Found route files under routes/");
211265
+ }
211266
+ const hasSafeExpressEvidence = hasExpressDependency && (hasExpressFactory || hasExpressImport || hasAppUse || hasRouterUsage || hasRoutesDirectory);
211267
+ if (!hasSafeExpressEvidence) {
211268
+ return null;
211269
+ }
211270
+ const confidence = strongSignals >= 2 || strongSignals >= 1 && mediumSignals >= 2 ? "high" : strongSignals >= 1 ? "medium" : "low";
211271
+ return toResult({
211272
+ adapterId: "express",
211273
+ confidence,
211274
+ score,
211275
+ evidence,
211276
+ projectKindHints: ["backend"],
211277
+ apiStyleHint: "rest"
211278
+ });
211279
+ },
211280
+ resolveProjectKinds: () => ["backend"],
211281
+ getImportExtractionMode: () => "typescript-imports"
211282
+ }),
211176
211283
  nestjs: buildAdapter({
211177
211284
  id: "nestjs",
211178
211285
  displayName: canonicalStackLabels.nestjs,
@@ -211448,9 +211555,9 @@ var stackAdaptersById = {
211448
211555
  ],
211449
211556
  buildToolHints: ["maven", "gradle"],
211450
211557
  defaults: {
211451
- sourceRoots: ["src/main/java", "src"],
211452
- moduleRootCandidates: ["src/main/java"],
211453
- apiRoots: ["src/main/java"],
211558
+ sourceRoots: ["src/main/java", "src/main/kotlin", "src"],
211559
+ moduleRootCandidates: ["src/main/java", "src/main/kotlin"],
211560
+ apiRoots: ["src/main/java", "src/main/kotlin"],
211454
211561
  databaseRoots: ["src/main/resources", "src/main/resources/db"],
211455
211562
  configFiles: [
211456
211563
  "pom.xml",
@@ -211470,7 +211577,7 @@ var stackAdaptersById = {
211470
211577
  "out",
211471
211578
  ...commonIgnoreDirectories
211472
211579
  ],
211473
- entrypointHints: ["src/main/java"],
211580
+ entrypointHints: ["src/main/java", "src/main/kotlin", "Application.java", "Application.kt"],
211474
211581
  publicEntrypointDirectories: [],
211475
211582
  moduleIndexFileName: "Application.java"
211476
211583
  },
@@ -211496,7 +211603,7 @@ var stackAdaptersById = {
211496
211603
  /\.(java|kt)$/iu,
211497
211604
  /@SpringBootApplication\b/u
211498
211605
  );
211499
- const hasMainJavaWithDependencies = context.hasDirectory("src/main/java") && hasSpringDependencyEvidence;
211606
+ const hasMainSourceWithDependencies = (context.hasDirectory("src/main/java") || context.hasDirectory("src/main/kotlin")) && hasSpringDependencyEvidence;
211500
211607
  const hasApplicationConfigWithDependencies = (context.hasFile("application.yml") || context.hasFile("application.yaml") || context.hasFile("application.properties")) && hasSpringDependencyEvidence;
211501
211608
  const hasRestController = context.hasAnyFileMatchingPattern(
211502
211609
  /\.(java|kt)$/iu,
@@ -211520,9 +211627,9 @@ var stackAdaptersById = {
211520
211627
  );
211521
211628
  const hasResourcesDirectory = context.hasDirectory("src/main/resources");
211522
211629
  const hasWrapperWithSpringEvidence = (context.hasFile("mvnw") || context.hasFile("gradlew")) && hasSpringDependencyEvidence;
211523
- const hasTestJava = context.hasDirectory("src/test/java");
211630
+ const hasTestJvmSource = context.hasDirectory("src/test/java") || context.hasDirectory("src/test/kotlin");
211524
211631
  const hasBootstrapConfig = context.hasFile("bootstrap.yml") || context.hasFile("bootstrap.yaml");
211525
- const hasJavaPackageShape = context.hasDirectory("src/main/java/com") || context.hasDirectory("src/main/java/org");
211632
+ const hasJvmPackageShape = context.hasDirectory("src/main/java/com") || context.hasDirectory("src/main/java/org") || context.hasDirectory("src/main/kotlin/com") || context.hasDirectory("src/main/kotlin/org");
211526
211633
  if (hasPomSpringDependency) {
211527
211634
  strongSignals += 1;
211528
211635
  score += 54;
@@ -211538,10 +211645,10 @@ var stackAdaptersById = {
211538
211645
  score += 44;
211539
211646
  evidence.push("Found @SpringBootApplication in source");
211540
211647
  }
211541
- if (hasMainJavaWithDependencies) {
211648
+ if (hasMainSourceWithDependencies) {
211542
211649
  strongSignals += 1;
211543
211650
  score += 20;
211544
- evidence.push("Found src/main/java with Spring dependency evidence");
211651
+ evidence.push("Found JVM main source root with Spring dependency evidence");
211545
211652
  }
211546
211653
  if (hasApplicationConfigWithDependencies) {
211547
211654
  strongSignals += 1;
@@ -211583,22 +211690,22 @@ var stackAdaptersById = {
211583
211690
  score += 4;
211584
211691
  evidence.push("Found Maven/Gradle wrapper with Spring dependency evidence");
211585
211692
  }
211586
- if (hasTestJava) {
211693
+ if (hasTestJvmSource) {
211587
211694
  weakSignals += 1;
211588
211695
  score += 2;
211589
- evidence.push("Found src/test/java directory");
211696
+ evidence.push("Found JVM test source directory");
211590
211697
  }
211591
211698
  if (hasBootstrapConfig) {
211592
211699
  weakSignals += 1;
211593
211700
  score += 2;
211594
211701
  evidence.push("Found bootstrap.yml or bootstrap.yaml");
211595
211702
  }
211596
- if (hasJavaPackageShape) {
211703
+ if (hasJvmPackageShape) {
211597
211704
  weakSignals += 1;
211598
211705
  score += 1;
211599
- evidence.push("Found conventional Java package directory under src/main/java");
211706
+ evidence.push("Found conventional JVM package directory under main source root");
211600
211707
  }
211601
- const hasSafeSpringCoreEvidence = hasSpringDependencyEvidence || hasSpringBootApplication || (hasRestController || hasController || hasService || hasRepository || hasMappingAnnotation) && (context.hasDirectory("src/main/java") || hasResourcesDirectory);
211708
+ const hasSafeSpringCoreEvidence = hasSpringDependencyEvidence || hasSpringBootApplication || (hasRestController || hasController || hasService || hasRepository || hasMappingAnnotation) && (context.hasDirectory("src/main/java") || context.hasDirectory("src/main/kotlin") || hasResourcesDirectory);
211602
211709
  if (!hasSafeSpringCoreEvidence || strongSignals === 0) {
211603
211710
  return null;
211604
211711
  }
@@ -211805,6 +211912,326 @@ var stackAdaptersById = {
211805
211912
  resolveProjectKinds: () => ["backend"],
211806
211913
  getImportExtractionMode: () => "python-imports"
211807
211914
  }),
211915
+ django: buildAdapter({
211916
+ id: "django",
211917
+ displayName: canonicalStackLabels.django,
211918
+ ecosystem: "python",
211919
+ category: "framework",
211920
+ priority: 68,
211921
+ manifestDetectionHints: [
211922
+ "requirements.txt",
211923
+ "pyproject.toml",
211924
+ "Pipfile",
211925
+ "Pipfile.lock",
211926
+ "poetry.lock",
211927
+ "manage.py",
211928
+ "settings.py",
211929
+ "urls.py",
211930
+ "wsgi.py",
211931
+ "asgi.py"
211932
+ ],
211933
+ buildToolHints: ["pip", "poetry", "django-admin"],
211934
+ defaults: {
211935
+ sourceRoots: [".", "src", "app"],
211936
+ moduleRootCandidates: [".", "src", "app"],
211937
+ apiRoots: [".", "src", "app"],
211938
+ databaseRoots: ["migrations", "db", "database"],
211939
+ configFiles: ["requirements.txt", "pyproject.toml", "Pipfile", "Pipfile.lock", "poetry.lock", "manage.py"],
211940
+ fileExtensions: [".py", ".toml", ".txt", ".sql", ".yaml", ".yml"],
211941
+ dependencyFileExtensions: [".py"],
211942
+ ignoreDirectories: [
211943
+ "__pycache__",
211944
+ ".pytest_cache",
211945
+ ".venv",
211946
+ "venv",
211947
+ "dist",
211948
+ "build",
211949
+ ".idea",
211950
+ ...commonIgnoreDirectories
211951
+ ],
211952
+ entrypointHints: ["manage.py", "wsgi.py", "asgi.py", "settings.py", "urls.py"],
211953
+ publicEntrypointDirectories: [],
211954
+ moduleIndexFileName: "__init__.py"
211955
+ },
211956
+ detect: (context) => {
211957
+ const evidence = [];
211958
+ let strongSignals = 0;
211959
+ let mediumSignals = 0;
211960
+ let weakSignals = 0;
211961
+ let score = 0;
211962
+ const hasRequirementsDjango = context.hasFileMatchingPattern(
211963
+ "requirements.txt",
211964
+ /(^|\s)django([=<>~!]|$)/imu
211965
+ );
211966
+ const hasPyprojectDjango = context.hasFileMatchingPattern("pyproject.toml", /\bdjango\b/iu);
211967
+ const hasPoetryLockDjango = context.hasFileMatchingPattern(
211968
+ "poetry.lock",
211969
+ /\bname\s*=\s*["']django["']/iu
211970
+ );
211971
+ const hasPipfileDjango = context.hasFileMatchingPattern("Pipfile", /\bdjango\b/iu);
211972
+ const hasPipfileLockDjango = context.hasFileMatchingPattern("Pipfile.lock", /"django"\s*:/iu);
211973
+ const hasDjangoDependencyEvidence = hasRequirementsDjango || hasPyprojectDjango || hasPoetryLockDjango || hasPipfileDjango || hasPipfileLockDjango;
211974
+ const hasManagePy = context.hasFile("manage.py");
211975
+ const hasDjangoSettingsModule = context.hasFileMatchingPattern(
211976
+ "manage.py",
211977
+ /\bDJANGO_SETTINGS_MODULE\b/u
211978
+ );
211979
+ const hasSettingsPy = context.hasFile("settings.py");
211980
+ const hasUrlsPy = context.hasFile("urls.py");
211981
+ const hasWsgiOrAsgi = context.hasFile("wsgi.py") || context.hasFile("asgi.py");
211982
+ const hasInstalledApps = context.hasFileMatchingPattern("settings.py", /\bINSTALLED_APPS\s*=/u);
211983
+ const hasDjangoAdmin = context.hasAnyFileMatchingPattern(/(^|\/)(manage|settings|urls|wsgi|asgi)\.py$/iu, /\bdjango-admin\b/iu);
211984
+ const hasDjangoImport = context.hasAnyFileMatchingPattern(/\.py$/iu, /\b(from|import)\s+django\b/u);
211985
+ const hasDjangoUrlsImport = context.hasAnyFileMatchingPattern(
211986
+ /\.py$/iu,
211987
+ /\bfrom\s+django\.urls\s+import\s+(?:path|include|re_path)\b/u
211988
+ );
211989
+ if (hasRequirementsDjango) {
211990
+ strongSignals += 1;
211991
+ score += 52;
211992
+ evidence.push("Found requirements.txt with django dependency");
211993
+ }
211994
+ if (hasPyprojectDjango) {
211995
+ strongSignals += 1;
211996
+ score += 52;
211997
+ evidence.push("Found pyproject.toml with django dependency");
211998
+ }
211999
+ if (hasPoetryLockDjango || hasPipfileDjango || hasPipfileLockDjango) {
212000
+ strongSignals += 1;
212001
+ score += 50;
212002
+ evidence.push("Found Pipfile/lock metadata with django dependency");
212003
+ }
212004
+ if (hasManagePy) {
212005
+ strongSignals += 1;
212006
+ score += 34;
212007
+ evidence.push("Found manage.py");
212008
+ }
212009
+ if (hasDjangoSettingsModule) {
212010
+ strongSignals += 1;
212011
+ score += 24;
212012
+ evidence.push("Found DJANGO_SETTINGS_MODULE in manage.py");
212013
+ }
212014
+ if (hasInstalledApps) {
212015
+ strongSignals += 1;
212016
+ score += 24;
212017
+ evidence.push("Found INSTALLED_APPS in settings.py");
212018
+ }
212019
+ if (hasDjangoImport) {
212020
+ strongSignals += 1;
212021
+ score += 22;
212022
+ evidence.push("Found django import in source");
212023
+ }
212024
+ if (hasSettingsPy) {
212025
+ mediumSignals += 1;
212026
+ score += 12;
212027
+ evidence.push("Found settings.py");
212028
+ }
212029
+ if (hasUrlsPy) {
212030
+ mediumSignals += 1;
212031
+ score += 10;
212032
+ evidence.push("Found urls.py");
212033
+ }
212034
+ if (hasWsgiOrAsgi) {
212035
+ mediumSignals += 1;
212036
+ score += 10;
212037
+ evidence.push("Found wsgi.py or asgi.py");
212038
+ }
212039
+ if (hasDjangoUrlsImport) {
212040
+ mediumSignals += 1;
212041
+ score += 10;
212042
+ evidence.push("Found Django URL routing import");
212043
+ }
212044
+ if (hasDjangoAdmin) {
212045
+ weakSignals += 1;
212046
+ score += 3;
212047
+ evidence.push("Found django-admin reference");
212048
+ }
212049
+ if (context.hasDirectory("migrations")) {
212050
+ weakSignals += 1;
212051
+ score += 2;
212052
+ evidence.push("Found migrations directory");
212053
+ }
212054
+ const hasSafeDjangoCoreEvidence = hasDjangoDependencyEvidence || hasManagePy && (hasDjangoSettingsModule || hasSettingsPy || hasUrlsPy || hasWsgiOrAsgi) || hasInstalledApps && (hasSettingsPy || hasDjangoImport) || hasDjangoImport && (hasDjangoUrlsImport || hasUrlsPy);
212055
+ if (!hasSafeDjangoCoreEvidence || strongSignals === 0) {
212056
+ return null;
212057
+ }
212058
+ const hasRestEvidence = hasUrlsPy || hasDjangoUrlsImport;
212059
+ const confidence = strongSignals >= 2 ? "high" : strongSignals >= 1 || mediumSignals >= 3 || mediumSignals >= 2 && weakSignals >= 1 ? "medium" : "low";
212060
+ return toResult({
212061
+ adapterId: "django",
212062
+ confidence,
212063
+ score,
212064
+ evidence,
212065
+ projectKindHints: ["backend"],
212066
+ apiStyleHint: hasRestEvidence ? "rest" : "unknown"
212067
+ });
212068
+ },
212069
+ resolveProjectKinds: () => ["backend"],
212070
+ getImportExtractionMode: () => "python-imports"
212071
+ }),
212072
+ flask: buildAdapter({
212073
+ id: "flask",
212074
+ displayName: canonicalStackLabels.flask,
212075
+ ecosystem: "python",
212076
+ category: "framework",
212077
+ priority: 66,
212078
+ manifestDetectionHints: [
212079
+ "requirements.txt",
212080
+ "pyproject.toml",
212081
+ "Pipfile",
212082
+ "Pipfile.lock",
212083
+ "poetry.lock",
212084
+ "app.py",
212085
+ "wsgi.py",
212086
+ "main.py"
212087
+ ],
212088
+ buildToolHints: ["pip", "poetry", "flask"],
212089
+ defaults: {
212090
+ sourceRoots: ["app", "src", "."],
212091
+ moduleRootCandidates: ["app", "src/app", "src", "blueprints", "api"],
212092
+ apiRoots: ["app", "src", "blueprints", "api"],
212093
+ databaseRoots: ["migrations", "app/db", "app/database", "src/db", "src/database"],
212094
+ configFiles: ["requirements.txt", "pyproject.toml", "Pipfile", "Pipfile.lock", "poetry.lock"],
212095
+ fileExtensions: [".py", ".toml", ".txt", ".sql", ".yaml", ".yml"],
212096
+ dependencyFileExtensions: [".py"],
212097
+ ignoreDirectories: [
212098
+ "__pycache__",
212099
+ ".pytest_cache",
212100
+ ".venv",
212101
+ "venv",
212102
+ "dist",
212103
+ "build",
212104
+ ".idea",
212105
+ ...commonIgnoreDirectories
212106
+ ],
212107
+ entrypointHints: ["app.py", "main.py", "wsgi.py", "app/__init__.py"],
212108
+ publicEntrypointDirectories: [],
212109
+ moduleIndexFileName: "__init__.py"
212110
+ },
212111
+ detect: (context) => {
212112
+ const evidence = [];
212113
+ let strongSignals = 0;
212114
+ let mediumSignals = 0;
212115
+ let weakSignals = 0;
212116
+ let score = 0;
212117
+ const hasRequirementsFlask = context.hasFileMatchingPattern(
212118
+ "requirements.txt",
212119
+ /(^|\s)flask([=<>~!]|$)/imu
212120
+ );
212121
+ const hasPyprojectFlask = context.hasFileMatchingPattern("pyproject.toml", /\bflask\b/iu);
212122
+ const hasPoetryLockFlask = context.hasFileMatchingPattern(
212123
+ "poetry.lock",
212124
+ /\bname\s*=\s*["']flask["']/iu
212125
+ );
212126
+ const hasPipfileFlask = context.hasFileMatchingPattern("Pipfile", /\bflask\b/iu);
212127
+ const hasPipfileLockFlask = context.hasFileMatchingPattern("Pipfile.lock", /"flask"\s*:/iu);
212128
+ const hasFlaskDependencyEvidence = hasRequirementsFlask || hasPyprojectFlask || hasPoetryLockFlask || hasPipfileFlask || hasPipfileLockFlask;
212129
+ const hasFlaskImport = context.hasAnyFileMatchingPattern(
212130
+ /\.py$/iu,
212131
+ /\bfrom\s+flask\s+import\s+.*\bFlask\b/u
212132
+ );
212133
+ const hasFlaskConstruction = context.hasAnyFileMatchingPattern(
212134
+ /\.py$/iu,
212135
+ /\b\w+\s*=\s*Flask\s*\(\s*__name__/u
212136
+ );
212137
+ const hasRouteDecorator = context.hasAnyFileMatchingPattern(
212138
+ /\.py$/iu,
212139
+ /@\w+\.route\s*\(/u
212140
+ );
212141
+ const hasMethodRouteDecorator = context.hasAnyFileMatchingPattern(
212142
+ /\.py$/iu,
212143
+ /@\w+\.(?:get|post|put|delete)\s*\(/u
212144
+ );
212145
+ const hasBlueprintImport = context.hasAnyFileMatchingPattern(
212146
+ /\.py$/iu,
212147
+ /\bfrom\s+flask\s+import\s+.*\bBlueprint\b/u
212148
+ );
212149
+ const hasBlueprintConstruction = context.hasAnyFileMatchingPattern(
212150
+ /\.py$/iu,
212151
+ /\b\w+\s*=\s*Blueprint\s*\(/u
212152
+ );
212153
+ const hasFlaskEntrypointConstruction = context.hasAnyFileMatchingPattern(
212154
+ /(^|\/)(app|main|wsgi)\.py$/iu,
212155
+ /\b\w+\s*=\s*Flask\s*\(/u
212156
+ );
212157
+ if (hasRequirementsFlask) {
212158
+ strongSignals += 1;
212159
+ score += 52;
212160
+ evidence.push("Found requirements.txt with flask dependency");
212161
+ }
212162
+ if (hasPyprojectFlask) {
212163
+ strongSignals += 1;
212164
+ score += 52;
212165
+ evidence.push("Found pyproject.toml with flask dependency");
212166
+ }
212167
+ if (hasPoetryLockFlask || hasPipfileFlask || hasPipfileLockFlask) {
212168
+ strongSignals += 1;
212169
+ score += 50;
212170
+ evidence.push("Found Pipfile/lock metadata with flask dependency");
212171
+ }
212172
+ if (hasFlaskImport) {
212173
+ strongSignals += 1;
212174
+ score += 34;
212175
+ evidence.push("Found Flask import in source");
212176
+ }
212177
+ if (hasFlaskConstruction) {
212178
+ strongSignals += 1;
212179
+ score += 34;
212180
+ evidence.push("Found Flask application construction in source");
212181
+ }
212182
+ if (hasRouteDecorator) {
212183
+ strongSignals += 1;
212184
+ score += 22;
212185
+ evidence.push("Found Flask route decorator in source");
212186
+ }
212187
+ if (hasMethodRouteDecorator) {
212188
+ mediumSignals += 1;
212189
+ score += 10;
212190
+ evidence.push("Found Flask method route decorator in source");
212191
+ }
212192
+ if (hasBlueprintImport) {
212193
+ mediumSignals += 1;
212194
+ score += 10;
212195
+ evidence.push("Found Blueprint import in source");
212196
+ }
212197
+ if (hasBlueprintConstruction) {
212198
+ mediumSignals += 1;
212199
+ score += 8;
212200
+ evidence.push("Found Blueprint construction in source");
212201
+ }
212202
+ if (hasFlaskEntrypointConstruction) {
212203
+ mediumSignals += 1;
212204
+ score += 8;
212205
+ evidence.push("Found Flask construction in app.py/main.py/wsgi.py");
212206
+ }
212207
+ if (context.hasDirectory("blueprints")) {
212208
+ weakSignals += 1;
212209
+ score += 2;
212210
+ evidence.push("Found blueprints directory");
212211
+ }
212212
+ if (context.hasDirectory("app") || context.hasDirectory("src")) {
212213
+ weakSignals += 1;
212214
+ score += 2;
212215
+ evidence.push("Found typical Python backend layout directory");
212216
+ }
212217
+ const hasSafeFlaskCoreEvidence = hasFlaskDependencyEvidence || hasFlaskImport && hasFlaskConstruction || hasFlaskImport && (hasRouteDecorator || hasMethodRouteDecorator || hasBlueprintImport) || hasBlueprintImport && hasBlueprintConstruction;
212218
+ if (!hasSafeFlaskCoreEvidence || strongSignals === 0) {
212219
+ return null;
212220
+ }
212221
+ const hasRestEvidence = hasRouteDecorator || hasMethodRouteDecorator || hasBlueprintImport;
212222
+ const confidence = strongSignals >= 2 ? "high" : strongSignals >= 1 || mediumSignals >= 3 || mediumSignals >= 2 && weakSignals >= 1 ? "medium" : "low";
212223
+ return toResult({
212224
+ adapterId: "flask",
212225
+ confidence,
212226
+ score,
212227
+ evidence,
212228
+ projectKindHints: ["backend"],
212229
+ apiStyleHint: hasRestEvidence ? "rest" : "unknown"
212230
+ });
212231
+ },
212232
+ resolveProjectKinds: () => ["backend"],
212233
+ getImportExtractionMode: () => "python-imports"
212234
+ }),
211808
212235
  python: buildAdapter({
211809
212236
  id: "python",
211810
212237
  displayName: canonicalStackLabels.python,
@@ -211867,6 +212294,101 @@ var stackAdaptersById = {
211867
212294
  resolveProjectKinds: () => ["backend", "library"],
211868
212295
  getImportExtractionMode: () => "python-imports"
211869
212296
  }),
212297
+ laravel: buildAdapter({
212298
+ id: "laravel",
212299
+ displayName: canonicalStackLabels.laravel,
212300
+ ecosystem: "php",
212301
+ category: "framework",
212302
+ priority: 70,
212303
+ manifestDetectionHints: ["composer.json", "artisan", "web.php", "api.php", "app.php"],
212304
+ buildToolHints: ["composer", "artisan"],
212305
+ defaults: {
212306
+ sourceRoots: ["app", "routes", "config"],
212307
+ moduleRootCandidates: ["app/Modules", "app/Services", "app/Http/Controllers", "app"],
212308
+ apiRoots: ["routes", "app/Http/Controllers"],
212309
+ databaseRoots: ["database", "database/migrations"],
212310
+ configFiles: ["composer.json", "artisan", "config/app.php"],
212311
+ fileExtensions: [".php"],
212312
+ dependencyFileExtensions: [".php"],
212313
+ ignoreDirectories: [
212314
+ "vendor",
212315
+ "storage",
212316
+ "bootstrap/cache",
212317
+ "public",
212318
+ ...commonIgnoreDirectories
212319
+ ],
212320
+ entrypointHints: ["artisan", "public/index.php", "bootstrap/app.php"],
212321
+ publicEntrypointDirectories: [],
212322
+ moduleIndexFileName: "index.php"
212323
+ },
212324
+ detect: (context) => {
212325
+ const evidence = [];
212326
+ let strongSignals = 0;
212327
+ let mediumSignals = 0;
212328
+ let score = 0;
212329
+ const hasLaravelComposer = context.hasFileMatchingPattern(
212330
+ "composer.json",
212331
+ /["']laravel\/framework["']/iu
212332
+ );
212333
+ const hasArtisan = context.hasFile("artisan");
212334
+ const hasRoutesWeb = context.hasAnyPathMatchingPattern(/(^|\/)routes\/web\.php$/iu);
212335
+ const hasRoutesApi = context.hasAnyPathMatchingPattern(/(^|\/)routes\/api\.php$/iu);
212336
+ const hasControllers = context.hasDirectory("app/Http/Controllers");
212337
+ const hasConfigApp = context.hasAnyPathMatchingPattern(/(^|\/)config\/app\.php$/iu);
212338
+ const hasLaravelRouteFacade = context.hasAnyFileMatchingPattern(
212339
+ /(^|\/)routes\/.+\.php$/iu,
212340
+ /\bRoute::(get|post|put|patch|delete|middleware|group)\s*\(/u
212341
+ );
212342
+ if (hasLaravelComposer) {
212343
+ strongSignals += 1;
212344
+ score += 50;
212345
+ evidence.push("Found composer.json with laravel/framework dependency");
212346
+ }
212347
+ if (hasArtisan) {
212348
+ strongSignals += 1;
212349
+ score += 18;
212350
+ evidence.push("Found artisan entrypoint");
212351
+ }
212352
+ if (hasRoutesWeb) {
212353
+ mediumSignals += 1;
212354
+ score += 10;
212355
+ evidence.push("Found routes/web.php");
212356
+ }
212357
+ if (hasRoutesApi) {
212358
+ mediumSignals += 1;
212359
+ score += 10;
212360
+ evidence.push("Found routes/api.php");
212361
+ }
212362
+ if (hasControllers) {
212363
+ mediumSignals += 1;
212364
+ score += 10;
212365
+ evidence.push("Found app/Http/Controllers directory");
212366
+ }
212367
+ if (hasConfigApp) {
212368
+ mediumSignals += 1;
212369
+ score += 6;
212370
+ evidence.push("Found config/app.php");
212371
+ }
212372
+ if (hasLaravelRouteFacade) {
212373
+ mediumSignals += 1;
212374
+ score += 8;
212375
+ evidence.push("Found Laravel Route facade usage");
212376
+ }
212377
+ if (!hasLaravelComposer || !(hasArtisan || hasRoutesWeb || hasRoutesApi || hasControllers)) {
212378
+ return null;
212379
+ }
212380
+ return toResult({
212381
+ adapterId: "laravel",
212382
+ confidence: strongSignals >= 2 || mediumSignals >= 2 ? "high" : "medium",
212383
+ score,
212384
+ evidence,
212385
+ projectKindHints: ["backend"],
212386
+ apiStyleHint: hasRoutesApi || hasLaravelRouteFacade ? "rest" : "unknown"
212387
+ });
212388
+ },
212389
+ resolveProjectKinds: () => ["backend"],
212390
+ getImportExtractionMode: () => "php-imports"
212391
+ }),
211870
212392
  php: buildAdapter({
211871
212393
  id: "php",
211872
212394
  displayName: canonicalStackLabels.php,
@@ -212154,23 +212676,182 @@ var stackAdaptersById = {
212154
212676
  displayName: canonicalStackLabels.ruby_rails,
212155
212677
  ecosystem: "other",
212156
212678
  category: "framework",
212157
- priority: 20,
212158
- manifestDetectionHints: ["Gemfile"],
212679
+ priority: 70,
212680
+ manifestDetectionHints: ["Gemfile", "rails", "routes.rb", "application.rb"],
212159
212681
  buildToolHints: ["bundle"],
212160
212682
  defaults: {
212161
- sourceRoots: ["app"],
212162
- moduleRootCandidates: ["app"],
212163
- apiRoots: ["app/controllers"],
212683
+ sourceRoots: ["app", "config"],
212684
+ moduleRootCandidates: ["app/services", "app/controllers", "app/models", "app"],
212685
+ apiRoots: ["app/controllers", "config/routes.rb"],
212164
212686
  databaseRoots: ["db"],
212165
- configFiles: ["Gemfile"],
212687
+ configFiles: ["Gemfile", "config/routes.rb", "config/application.rb"],
212166
212688
  fileExtensions: [".rb"],
212167
212689
  dependencyFileExtensions: [".rb"],
212168
- ignoreDirectories: [...commonIgnoreDirectories],
212690
+ ignoreDirectories: ["vendor", "tmp", "log", ...commonIgnoreDirectories],
212169
212691
  entrypointHints: ["config/application.rb"],
212170
212692
  publicEntrypointDirectories: [],
212171
212693
  moduleIndexFileName: "application.rb"
212172
212694
  },
212173
- detect: () => null,
212695
+ detect: (context) => {
212696
+ const evidence = [];
212697
+ let strongSignals = 0;
212698
+ let mediumSignals = 0;
212699
+ let score = 0;
212700
+ const hasRailsGem = context.hasFileMatchingPattern("Gemfile", /\bgem\s+['"]rails['"]/u);
212701
+ const hasBinRails = context.hasAnyPathMatchingPattern(/(^|\/)bin\/rails$/u);
212702
+ const hasRoutes = context.hasAnyPathMatchingPattern(/(^|\/)config\/routes\.rb$/u);
212703
+ const hasControllers = context.hasDirectory("app/controllers");
212704
+ const hasModels = context.hasDirectory("app/models");
212705
+ const hasServices = context.hasDirectory("app/services");
212706
+ const hasRailsRoutesDraw = context.hasFileMatchingPattern("routes.rb", /\bRails\.application\.routes\.draw\b/u);
212707
+ const hasApplicationClass = context.hasAnyFileMatchingPattern(
212708
+ /(^|\/)config\/application\.rb$/u,
212709
+ /\bclass\s+Application\s+<\s+Rails::Application\b/u
212710
+ );
212711
+ const hasApplicationController = context.hasAnyFileMatchingPattern(
212712
+ /(^|\/)app\/controllers\/.+\.rb$/u,
212713
+ /\bApplicationController\b/u
212714
+ );
212715
+ if (hasRailsGem) {
212716
+ strongSignals += 1;
212717
+ score += 48;
212718
+ evidence.push("Found Gemfile with rails gem");
212719
+ }
212720
+ if (hasBinRails) {
212721
+ strongSignals += 1;
212722
+ score += 18;
212723
+ evidence.push("Found bin/rails");
212724
+ }
212725
+ if (hasRoutes) {
212726
+ mediumSignals += 1;
212727
+ score += 10;
212728
+ evidence.push("Found config/routes.rb");
212729
+ }
212730
+ if (hasControllers) {
212731
+ mediumSignals += 1;
212732
+ score += 8;
212733
+ evidence.push("Found app/controllers directory");
212734
+ }
212735
+ if (hasModels) {
212736
+ mediumSignals += 1;
212737
+ score += 6;
212738
+ evidence.push("Found app/models directory");
212739
+ }
212740
+ if (hasServices) {
212741
+ mediumSignals += 1;
212742
+ score += 5;
212743
+ evidence.push("Found app/services directory");
212744
+ }
212745
+ if (hasRailsRoutesDraw) {
212746
+ mediumSignals += 1;
212747
+ score += 8;
212748
+ evidence.push("Found Rails routes draw block");
212749
+ }
212750
+ if (hasApplicationClass) {
212751
+ mediumSignals += 1;
212752
+ score += 8;
212753
+ evidence.push("Found Rails application class");
212754
+ }
212755
+ if (hasApplicationController) {
212756
+ mediumSignals += 1;
212757
+ score += 5;
212758
+ evidence.push("Found ApplicationController inheritance");
212759
+ }
212760
+ if (!hasRailsGem || !(hasBinRails || hasRoutes || hasControllers || hasApplicationClass)) {
212761
+ return null;
212762
+ }
212763
+ return toResult({
212764
+ adapterId: "ruby_rails",
212765
+ confidence: strongSignals >= 2 || mediumSignals >= 3 ? "high" : "medium",
212766
+ score,
212767
+ evidence,
212768
+ projectKindHints: ["backend"],
212769
+ apiStyleHint: hasRoutes || hasControllers ? "rest" : "unknown"
212770
+ });
212771
+ },
212772
+ resolveProjectKinds: () => ["backend"],
212773
+ getImportExtractionMode: () => "generic"
212774
+ }),
212775
+ kotlin: buildAdapter({
212776
+ id: "kotlin",
212777
+ displayName: canonicalStackLabels.kotlin,
212778
+ ecosystem: "jvm",
212779
+ category: "generic-language",
212780
+ priority: 40,
212781
+ manifestDetectionHints: ["build.gradle.kts", "pom.xml", "settings.gradle.kts"],
212782
+ buildToolHints: ["gradle", "maven"],
212783
+ defaults: {
212784
+ sourceRoots: ["src/main/kotlin", "src"],
212785
+ moduleRootCandidates: ["src/main/kotlin", "src"],
212786
+ apiRoots: ["src/main/kotlin"],
212787
+ databaseRoots: ["src/main/resources/db", "src/main/resources/migrations"],
212788
+ configFiles: ["build.gradle.kts", "pom.xml", "settings.gradle.kts"],
212789
+ fileExtensions: [".kt", ".kts"],
212790
+ dependencyFileExtensions: [".kt", ".kts"],
212791
+ ignoreDirectories: [
212792
+ "target",
212793
+ "build",
212794
+ ".gradle",
212795
+ ".idea",
212796
+ "out",
212797
+ ...commonIgnoreDirectories
212798
+ ],
212799
+ entrypointHints: ["Application.kt", "Main.kt"],
212800
+ publicEntrypointDirectories: [],
212801
+ moduleIndexFileName: "Application.kt"
212802
+ },
212803
+ detect: (context) => {
212804
+ const evidence = [];
212805
+ let score = 0;
212806
+ const hasBuildGradleKts = context.hasFile("build.gradle.kts");
212807
+ const hasSettingsGradleKts = context.hasFile("settings.gradle.kts");
212808
+ const hasKotlinPluginInGradle = context.hasFileMatchingPattern("build.gradle.kts", /\bkotlin\s*\(\s*["']jvm["']\s*\)/u) || context.hasFileMatchingPattern("build.gradle.kts", /\bid\s*\(\s*["']org\.jetbrains\.kotlin\.jvm["']\s*\)/u);
212809
+ const hasPomKotlinPlugin = context.hasFileMatchingPattern("pom.xml", /\bkotlin-maven-plugin\b/u) || context.hasFileMatchingPattern("pom.xml", /\borg\.jetbrains\.kotlin\b/u);
212810
+ const hasKotlinSourceRoot = context.hasDirectory("src/main/kotlin");
212811
+ const hasKtFiles = context.hasFileExtension(".kt") || context.hasFileExtension(".kts");
212812
+ const hasKotlinPackageShape = context.hasDirectory("src/main/kotlin/com") || context.hasDirectory("src/main/kotlin/org");
212813
+ if (hasBuildGradleKts) {
212814
+ score += 30;
212815
+ evidence.push("Found build.gradle.kts");
212816
+ }
212817
+ if (hasSettingsGradleKts) {
212818
+ score += 8;
212819
+ evidence.push("Found settings.gradle.kts");
212820
+ }
212821
+ if (hasKotlinPluginInGradle) {
212822
+ score += 32;
212823
+ evidence.push("Found Kotlin JVM plugin in build.gradle.kts");
212824
+ }
212825
+ if (hasPomKotlinPlugin) {
212826
+ score += 32;
212827
+ evidence.push("Found Kotlin Maven plugin markers in pom.xml");
212828
+ }
212829
+ if (hasKotlinSourceRoot) {
212830
+ score += 18;
212831
+ evidence.push("Found src/main/kotlin source root");
212832
+ }
212833
+ if (hasKtFiles) {
212834
+ score += 16;
212835
+ evidence.push("Found Kotlin source files");
212836
+ }
212837
+ if (hasKotlinPackageShape) {
212838
+ score += 4;
212839
+ evidence.push("Found conventional Kotlin package directory under src/main/kotlin");
212840
+ }
212841
+ const hasSafeKotlinEvidence = hasKtFiles && (hasBuildGradleKts || hasKotlinPluginInGradle || hasPomKotlinPlugin || hasKotlinSourceRoot);
212842
+ if (!hasSafeKotlinEvidence) {
212843
+ return null;
212844
+ }
212845
+ return toResult({
212846
+ adapterId: "kotlin",
212847
+ confidence: (hasKotlinPluginInGradle || hasPomKotlinPlugin) && hasKotlinSourceRoot ? "high" : hasBuildGradleKts || hasKotlinSourceRoot ? "medium" : "low",
212848
+ score,
212849
+ evidence,
212850
+ projectKindHints: ["backend", "library"],
212851
+ apiStyleHint: "unknown"
212852
+ });
212853
+ },
212854
+ resolveProjectKinds: () => ["backend", "library"],
212174
212855
  getImportExtractionMode: () => "generic"
212175
212856
  }),
212176
212857
  kotlin_spring: buildAdapter({
@@ -212426,7 +213107,7 @@ var stackAdaptersById = {
212426
213107
  let score = 0;
212427
213108
  if (hasVueDependency) {
212428
213109
  strongSignals += 1;
212429
- score += 28;
213110
+ score += 30;
212430
213111
  evidence.push("Found package.json with vue dependency");
212431
213112
  }
212432
213113
  if (hasAppVue) {
@@ -212441,12 +213122,12 @@ var stackAdaptersById = {
212441
213122
  }
212442
213123
  if (hasVitePluginInConfig) {
212443
213124
  strongSignals += 1;
212444
- score += 20;
213125
+ score += 24;
212445
213126
  evidence.push("Found Vue plugin usage in vite.config.ts or vite.config.js");
212446
213127
  }
212447
213128
  if (hasVitePluginDependency) {
212448
213129
  strongSignals += 1;
212449
- score += 18;
213130
+ score += 22;
212450
213131
  evidence.push("Found package.json with @vitejs/plugin-vue dependency");
212451
213132
  }
212452
213133
  if (hasSrcViews) {
@@ -212461,12 +213142,12 @@ var stackAdaptersById = {
212461
213142
  }
212462
213143
  if (hasCreateAppReference) {
212463
213144
  mediumSignals += 1;
212464
- score += 10;
213145
+ score += 12;
212465
213146
  evidence.push("Found createApp usage in src/main.ts or src/main.js");
212466
213147
  }
212467
213148
  if (hasViteConfig && hasVueDependency) {
212468
213149
  mediumSignals += 1;
212469
- score += 6;
213150
+ score += 8;
212470
213151
  evidence.push("Found vite.config.ts or vite.config.js with Vue package evidence");
212471
213152
  }
212472
213153
  if (hasTsConfigApp) {
@@ -231311,13 +231992,20 @@ var ignoredDirectoryNames2 = /* @__PURE__ */ new Set([
231311
231992
  ]);
231312
231993
  var backendStackIds = /* @__PURE__ */ new Set([
231313
231994
  "spring",
231995
+ "express",
231314
231996
  "fastapi",
231997
+ "django",
231998
+ "flask",
231315
231999
  "dotnet",
231316
232000
  "nestjs",
231317
232001
  "java",
231318
232002
  "python",
231319
232003
  "php",
232004
+ "laravel",
231320
232005
  "go",
232006
+ "ruby_rails",
232007
+ "kotlin",
232008
+ "kotlin_spring",
231321
232009
  "node_typescript",
231322
232010
  "node_javascript"
231323
232011
  ]);
@@ -231535,14 +232223,21 @@ function buildDetectionContext(workspaceRoot, filePaths, pathsByFileName, extens
231535
232223
  function toDetectedStack(adapterId) {
231536
232224
  switch (adapterId) {
231537
232225
  case "node_typescript":
232226
+ case "express":
231538
232227
  case "nestjs":
231539
232228
  case "java":
231540
232229
  case "spring":
231541
232230
  case "fastapi":
232231
+ case "django":
232232
+ case "flask":
231542
232233
  case "dotnet":
231543
232234
  case "python":
231544
232235
  case "php":
232236
+ case "laravel":
231545
232237
  case "go":
232238
+ case "ruby_rails":
232239
+ case "kotlin":
232240
+ case "kotlin_spring":
231546
232241
  case "react":
231547
232242
  case "nextjs":
231548
232243
  case "angular":
@@ -231563,6 +232258,9 @@ function resolveLanguages(stacks) {
231563
232258
  if (stacks.includes("spring")) {
231564
232259
  languages.add("java");
231565
232260
  }
232261
+ if (stacks.includes("kotlin") || stacks.includes("kotlin_spring")) {
232262
+ languages.add("kotlin");
232263
+ }
231566
232264
  if (stacks.includes("dotnet")) {
231567
232265
  languages.add("csharp");
231568
232266
  }
@@ -231572,9 +232270,21 @@ function resolveLanguages(stacks) {
231572
232270
  if (stacks.includes("fastapi")) {
231573
232271
  languages.add("python");
231574
232272
  }
232273
+ if (stacks.includes("django")) {
232274
+ languages.add("python");
232275
+ }
232276
+ if (stacks.includes("flask")) {
232277
+ languages.add("python");
232278
+ }
231575
232279
  if (stacks.includes("php")) {
231576
232280
  languages.add("php");
231577
232281
  }
232282
+ if (stacks.includes("laravel")) {
232283
+ languages.add("php");
232284
+ }
232285
+ if (stacks.includes("ruby_rails")) {
232286
+ languages.add("ruby");
232287
+ }
231578
232288
  if (stacks.includes("go")) {
231579
232289
  languages.add("go");
231580
232290
  }
@@ -231588,6 +232298,12 @@ function resolveLanguages(stacks) {
231588
232298
  languages.add("typescript");
231589
232299
  languages.add("javascript");
231590
232300
  }
232301
+ if (stacks.includes("express")) {
232302
+ languages.add("javascript");
232303
+ if (stacks.includes("node_typescript")) {
232304
+ languages.add("typescript");
232305
+ }
232306
+ }
231591
232307
  if (stacks.includes("react") || stacks.includes("nextjs") || stacks.includes("angular") || stacks.includes("vue")) {
231592
232308
  languages.add(stacks.includes("node_typescript") ? "typescript" : "javascript");
231593
232309
  }
@@ -232065,6 +232781,7 @@ var sourceFileExtensions = /* @__PURE__ */ new Set([
232065
232781
  ".tsx",
232066
232782
  ".js",
232067
232783
  ".jsx",
232784
+ ".vue",
232068
232785
  ".java",
232069
232786
  ".py",
232070
232787
  ".php",
@@ -232099,7 +232816,9 @@ var roleFileHints = [
232099
232816
  "layout.tsx",
232100
232817
  "layout.jsx",
232101
232818
  "route.ts",
232102
- "route.js"
232819
+ "route.js",
232820
+ "view.vue",
232821
+ "page.vue"
232103
232822
  ];
232104
232823
  function normalizePath13(value) {
232105
232824
  return value.replaceAll("\\", "/");
@@ -232329,6 +233048,12 @@ function collectExplicitRoots(workspaceRoot) {
232329
233048
  "backend/src/features",
232330
233049
  "frontend/src/modules",
232331
233050
  "frontend/src/features",
233051
+ "frontend/src/views",
233052
+ "src/views",
233053
+ "routes",
233054
+ "src/routes",
233055
+ "app/Modules",
233056
+ "app/services",
232332
233057
  "services",
232333
233058
  "packages",
232334
233059
  "libs"
@@ -232355,6 +233080,47 @@ function collectExplicitRoots(workspaceRoot) {
232355
233080
  }
232356
233081
  return uniqueSorted(roots);
232357
233082
  }
233083
+ function collectJavaPackageRoots(workspaceRoot) {
233084
+ const roots = [];
233085
+ for (const sourceRoot of ["src/main/java", "src/main/kotlin", "src"]) {
233086
+ const absoluteSourceRoot = path53.join(workspaceRoot, ...sourceRoot.split("/"));
233087
+ if (!pathExists15(absoluteSourceRoot)) {
233088
+ continue;
233089
+ }
233090
+ const queue = [
233091
+ { absolutePath: absoluteSourceRoot, relativePath: sourceRoot, depth: 0 }
233092
+ ];
233093
+ while (queue.length > 0) {
233094
+ const current = queue.shift();
233095
+ if (!current) {
233096
+ continue;
233097
+ }
233098
+ if (current.depth > 6) {
233099
+ continue;
233100
+ }
233101
+ for (const entry of safeReadDirEntries(current.absolutePath)) {
233102
+ if (!entry.isDirectory() || ignoredDirectoryNames4.has(entry.name)) {
233103
+ continue;
233104
+ }
233105
+ const childRelativePath = normalizePath13(`${current.relativePath}/${entry.name}`);
233106
+ const lowerName = entry.name.toLowerCase();
233107
+ if (lowerName === "modules" || lowerName === "features") {
233108
+ const childCount = safeReadDirEntries(path53.join(current.absolutePath, entry.name)).filter((child) => child.isDirectory() && !ignoredDirectoryNames4.has(child.name)).length;
233109
+ if (childCount >= 2) {
233110
+ roots.push(childRelativePath);
233111
+ }
233112
+ continue;
233113
+ }
233114
+ queue.push({
233115
+ absolutePath: path53.join(current.absolutePath, entry.name),
233116
+ relativePath: childRelativePath,
233117
+ depth: current.depth + 1
233118
+ });
233119
+ }
233120
+ }
233121
+ }
233122
+ return roots;
233123
+ }
232358
233124
  function collectFallbackSrcModules(workspaceRoot, context) {
232359
233125
  const srcRoot = path53.join(workspaceRoot, "src");
232360
233126
  const srcEntries = safeReadDirEntries(srcRoot).filter((entry) => entry.isDirectory() && !ignoredDirectoryNames4.has(entry.name)).sort((left, right) => left.name.localeCompare(right.name));
@@ -232392,6 +233158,14 @@ function detectModules(workspaceRoot, options) {
232392
233158
  projectKinds: options?.projectKinds,
232393
233159
  stacks: options?.stacks
232394
233160
  };
233161
+ if (options?.stacks?.some((stack) => stack === "java" || stack === "spring" || stack === "kotlin" || stack === "kotlin_spring")) {
233162
+ for (const root of collectJavaPackageRoots(workspaceRoot)) {
233163
+ if (!explicitRoots.includes(root)) {
233164
+ explicitRoots.push(root);
233165
+ }
233166
+ }
233167
+ explicitRoots.sort((left, right) => left.localeCompare(right));
233168
+ }
232395
233169
  for (const root of explicitRoots) {
232396
233170
  if (root.endsWith("/src/modules") || root === "src/modules" || root === "backend/src/modules") {
232397
233171
  evidence.push(`Found ${root} directory`);
@@ -233398,10 +234172,16 @@ function resolveImplementationProfile(input2) {
233398
234172
  return "frontend-vue";
233399
234173
  case "nestjs":
233400
234174
  return input2.apiStyleHint === "rest" ? "backend-nestjs-rest" : "backend-nestjs";
234175
+ case "express":
234176
+ return input2.apiStyleHint === "rest" ? "backend-express-rest" : "backend-express";
233401
234177
  case "spring":
233402
234178
  return input2.apiStyleHint === "rest" ? "backend-spring-rest" : "backend-spring";
233403
234179
  case "fastapi":
233404
234180
  return input2.apiStyleHint === "rest" ? "backend-fastapi-rest" : "backend-fastapi";
234181
+ case "django":
234182
+ return input2.apiStyleHint === "rest" ? "backend-django-rest" : "backend-django";
234183
+ case "flask":
234184
+ return input2.apiStyleHint === "rest" ? "backend-flask-rest" : "backend-flask";
233405
234185
  case "dotnet":
233406
234186
  return input2.apiStyleHint === "rest" ? "backend-dotnet-webapi" : "backend-dotnet";
233407
234187
  case "java":
@@ -233410,6 +234190,14 @@ function resolveImplementationProfile(input2) {
233410
234190
  return "backend-python";
233411
234191
  case "php":
233412
234192
  return "backend-php";
234193
+ case "laravel":
234194
+ return input2.apiStyleHint === "rest" ? "backend-laravel-rest" : "backend-laravel";
234195
+ case "ruby_rails":
234196
+ return input2.apiStyleHint === "rest" ? "backend-rails-rest" : "backend-rails";
234197
+ case "kotlin":
234198
+ return "backend-kotlin";
234199
+ case "kotlin_spring":
234200
+ return input2.apiStyleHint === "rest" ? "backend-kotlin-spring-rest" : "backend-kotlin-spring";
233413
234201
  case "go":
233414
234202
  return "backend-go";
233415
234203
  case "node_typescript":
@@ -233457,6 +234245,9 @@ function resolveNormalizedProjectKinds(detection, evidence) {
233457
234245
  addEvidence6(evidence, `Normalized project kinds from selected adapter '${selectedAdapter.id}' hints`);
233458
234246
  } else if (detection.topology.topology === "monorepo" && selectedHints.length > 0) {
233459
234247
  normalizedKinds = uniqueSortedKinds([...heuristicKinds, ...selectedHints]);
234248
+ if (normalizedKinds.includes("library") && !selectedHints.includes("library") && (detection.architectureStyleSignals?.sharedPackageCount ?? 0) === 0) {
234249
+ normalizedKinds = normalizedKinds.filter((kind) => kind !== "library");
234250
+ }
233460
234251
  addEvidence6(evidence, "Normalized monorepo project kinds from adapter hints plus heuristic evidence");
233461
234252
  } else if (heuristicKinds.length > 0) {
233462
234253
  normalizedKinds = uniqueSortedKinds(heuristicKinds);
@@ -233569,7 +234360,7 @@ function resolveStackSelections(detection, projectKinds) {
233569
234360
  }
233570
234361
  if (projectKinds.includes("library")) {
233571
234362
  const selectedLibrary = detection.stacks.selectedAdapterId;
233572
- if (selectedLibrary === "terraform" || selectedLibrary === "ansible" || selectedLibrary === "node_typescript" || selectedLibrary === "python" || selectedLibrary === "go") {
234363
+ if (selectedLibrary === "terraform" || selectedLibrary === "ansible" || selectedLibrary === "node_typescript" || selectedLibrary === "python" || selectedLibrary === "go" || selectedLibrary === "kotlin") {
233573
234364
  selections.library = selectedLibrary;
233574
234365
  }
233575
234366
  }
package/package.json CHANGED
@@ -1,34 +1,34 @@
1
- {
2
- "name": "@archpilotlabs/archpilot",
3
- "version": "0.0.7",
4
- "description": "Executable architecture governance CLI",
5
- "homepage": "https://archpilot.org",
6
- "bugs": {
7
- "url": "https://archpilot.org/contact"
8
- },
9
- "license": "SEE LICENSE IN LICENSE",
10
- "bin": {
11
- "archpilot": "./dist/archpilot-cli.js"
12
- },
13
- "main": "./dist/archpilot-cli.js",
14
- "files": [
15
- "dist"
16
- ],
17
- "keywords": [
18
- "architecture",
19
- "architecture-validation",
20
- "governance",
21
- "cli",
22
- "dependency-validation",
23
- "adr",
24
- "ci",
25
- "developer-tools"
26
- ],
27
- "scripts": {
28
- "build": "npm run compile",
29
- "compile": "npm run check-types && npm run lint && node esbuild.js",
30
- "watch": "node esbuild.js --watch",
31
- "check-types": "tsc --noEmit -p tsconfig.json",
32
- "lint": "eslint src"
33
- }
34
- }
1
+ {
2
+ "name": "@archpilotlabs/archpilot",
3
+ "version": "0.0.9",
4
+ "description": "Executable architecture governance CLI",
5
+ "homepage": "https://archpilot.org",
6
+ "bugs": {
7
+ "url": "https://archpilot.org/contact"
8
+ },
9
+ "license": "SEE LICENSE IN LICENSE",
10
+ "bin": {
11
+ "archpilot": "./dist/archpilot-cli.js"
12
+ },
13
+ "main": "./dist/archpilot-cli.js",
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "keywords": [
18
+ "architecture",
19
+ "architecture-validation",
20
+ "governance",
21
+ "cli",
22
+ "dependency-validation",
23
+ "adr",
24
+ "ci",
25
+ "developer-tools"
26
+ ],
27
+ "scripts": {
28
+ "build": "npm run compile",
29
+ "compile": "npm run check-types && npm run lint && node esbuild.js",
30
+ "watch": "node esbuild.js --watch",
31
+ "check-types": "tsc --noEmit -p tsconfig.json",
32
+ "lint": "eslint src"
33
+ }
34
+ }