@archpilotlabs/archpilot 0.0.7 → 0.0.8

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
@@ -65,12 +73,9 @@ archpilot cloud connect
65
73
  archpilot cloud disconnect
66
74
  ```
67
75
 
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.
76
+ Cloud setup only asks for your API token.
73
77
 
78
+ ArchPilot uses the canonical production API automatically-no server URL setup required for normal use.
74
79
  Learn more:
75
80
 
76
81
  https://archpilot.org/docs
@@ -210936,6 +210936,8 @@ var validCanonicalStackIds = /* @__PURE__ */ new Set([
210936
210936
  "java",
210937
210937
  "spring",
210938
210938
  "fastapi",
210939
+ "django",
210940
+ "flask",
210939
210941
  "python",
210940
210942
  "php",
210941
210943
  "go",
@@ -210958,6 +210960,8 @@ var canonicalStackLabels = {
210958
210960
  java: "Java",
210959
210961
  spring: "Spring",
210960
210962
  fastapi: "FastAPI",
210963
+ django: "Django",
210964
+ flask: "Flask",
210961
210965
  python: "Python",
210962
210966
  php: "PHP",
210963
210967
  go: "Go",
@@ -211805,6 +211809,326 @@ var stackAdaptersById = {
211805
211809
  resolveProjectKinds: () => ["backend"],
211806
211810
  getImportExtractionMode: () => "python-imports"
211807
211811
  }),
211812
+ django: buildAdapter({
211813
+ id: "django",
211814
+ displayName: canonicalStackLabels.django,
211815
+ ecosystem: "python",
211816
+ category: "framework",
211817
+ priority: 68,
211818
+ manifestDetectionHints: [
211819
+ "requirements.txt",
211820
+ "pyproject.toml",
211821
+ "Pipfile",
211822
+ "Pipfile.lock",
211823
+ "poetry.lock",
211824
+ "manage.py",
211825
+ "settings.py",
211826
+ "urls.py",
211827
+ "wsgi.py",
211828
+ "asgi.py"
211829
+ ],
211830
+ buildToolHints: ["pip", "poetry", "django-admin"],
211831
+ defaults: {
211832
+ sourceRoots: [".", "src", "app"],
211833
+ moduleRootCandidates: [".", "src", "app"],
211834
+ apiRoots: [".", "src", "app"],
211835
+ databaseRoots: ["migrations", "db", "database"],
211836
+ configFiles: ["requirements.txt", "pyproject.toml", "Pipfile", "Pipfile.lock", "poetry.lock", "manage.py"],
211837
+ fileExtensions: [".py", ".toml", ".txt", ".sql", ".yaml", ".yml"],
211838
+ dependencyFileExtensions: [".py"],
211839
+ ignoreDirectories: [
211840
+ "__pycache__",
211841
+ ".pytest_cache",
211842
+ ".venv",
211843
+ "venv",
211844
+ "dist",
211845
+ "build",
211846
+ ".idea",
211847
+ ...commonIgnoreDirectories
211848
+ ],
211849
+ entrypointHints: ["manage.py", "wsgi.py", "asgi.py", "settings.py", "urls.py"],
211850
+ publicEntrypointDirectories: [],
211851
+ moduleIndexFileName: "__init__.py"
211852
+ },
211853
+ detect: (context) => {
211854
+ const evidence = [];
211855
+ let strongSignals = 0;
211856
+ let mediumSignals = 0;
211857
+ let weakSignals = 0;
211858
+ let score = 0;
211859
+ const hasRequirementsDjango = context.hasFileMatchingPattern(
211860
+ "requirements.txt",
211861
+ /(^|\s)django([=<>~!]|$)/imu
211862
+ );
211863
+ const hasPyprojectDjango = context.hasFileMatchingPattern("pyproject.toml", /\bdjango\b/iu);
211864
+ const hasPoetryLockDjango = context.hasFileMatchingPattern(
211865
+ "poetry.lock",
211866
+ /\bname\s*=\s*["']django["']/iu
211867
+ );
211868
+ const hasPipfileDjango = context.hasFileMatchingPattern("Pipfile", /\bdjango\b/iu);
211869
+ const hasPipfileLockDjango = context.hasFileMatchingPattern("Pipfile.lock", /"django"\s*:/iu);
211870
+ const hasDjangoDependencyEvidence = hasRequirementsDjango || hasPyprojectDjango || hasPoetryLockDjango || hasPipfileDjango || hasPipfileLockDjango;
211871
+ const hasManagePy = context.hasFile("manage.py");
211872
+ const hasDjangoSettingsModule = context.hasFileMatchingPattern(
211873
+ "manage.py",
211874
+ /\bDJANGO_SETTINGS_MODULE\b/u
211875
+ );
211876
+ const hasSettingsPy = context.hasFile("settings.py");
211877
+ const hasUrlsPy = context.hasFile("urls.py");
211878
+ const hasWsgiOrAsgi = context.hasFile("wsgi.py") || context.hasFile("asgi.py");
211879
+ const hasInstalledApps = context.hasFileMatchingPattern("settings.py", /\bINSTALLED_APPS\s*=/u);
211880
+ const hasDjangoAdmin = context.hasAnyFileMatchingPattern(/(^|\/)(manage|settings|urls|wsgi|asgi)\.py$/iu, /\bdjango-admin\b/iu);
211881
+ const hasDjangoImport = context.hasAnyFileMatchingPattern(/\.py$/iu, /\b(from|import)\s+django\b/u);
211882
+ const hasDjangoUrlsImport = context.hasAnyFileMatchingPattern(
211883
+ /\.py$/iu,
211884
+ /\bfrom\s+django\.urls\s+import\s+(?:path|include|re_path)\b/u
211885
+ );
211886
+ if (hasRequirementsDjango) {
211887
+ strongSignals += 1;
211888
+ score += 52;
211889
+ evidence.push("Found requirements.txt with django dependency");
211890
+ }
211891
+ if (hasPyprojectDjango) {
211892
+ strongSignals += 1;
211893
+ score += 52;
211894
+ evidence.push("Found pyproject.toml with django dependency");
211895
+ }
211896
+ if (hasPoetryLockDjango || hasPipfileDjango || hasPipfileLockDjango) {
211897
+ strongSignals += 1;
211898
+ score += 50;
211899
+ evidence.push("Found Pipfile/lock metadata with django dependency");
211900
+ }
211901
+ if (hasManagePy) {
211902
+ strongSignals += 1;
211903
+ score += 34;
211904
+ evidence.push("Found manage.py");
211905
+ }
211906
+ if (hasDjangoSettingsModule) {
211907
+ strongSignals += 1;
211908
+ score += 24;
211909
+ evidence.push("Found DJANGO_SETTINGS_MODULE in manage.py");
211910
+ }
211911
+ if (hasInstalledApps) {
211912
+ strongSignals += 1;
211913
+ score += 24;
211914
+ evidence.push("Found INSTALLED_APPS in settings.py");
211915
+ }
211916
+ if (hasDjangoImport) {
211917
+ strongSignals += 1;
211918
+ score += 22;
211919
+ evidence.push("Found django import in source");
211920
+ }
211921
+ if (hasSettingsPy) {
211922
+ mediumSignals += 1;
211923
+ score += 12;
211924
+ evidence.push("Found settings.py");
211925
+ }
211926
+ if (hasUrlsPy) {
211927
+ mediumSignals += 1;
211928
+ score += 10;
211929
+ evidence.push("Found urls.py");
211930
+ }
211931
+ if (hasWsgiOrAsgi) {
211932
+ mediumSignals += 1;
211933
+ score += 10;
211934
+ evidence.push("Found wsgi.py or asgi.py");
211935
+ }
211936
+ if (hasDjangoUrlsImport) {
211937
+ mediumSignals += 1;
211938
+ score += 10;
211939
+ evidence.push("Found Django URL routing import");
211940
+ }
211941
+ if (hasDjangoAdmin) {
211942
+ weakSignals += 1;
211943
+ score += 3;
211944
+ evidence.push("Found django-admin reference");
211945
+ }
211946
+ if (context.hasDirectory("migrations")) {
211947
+ weakSignals += 1;
211948
+ score += 2;
211949
+ evidence.push("Found migrations directory");
211950
+ }
211951
+ const hasSafeDjangoCoreEvidence = hasDjangoDependencyEvidence || hasManagePy && (hasDjangoSettingsModule || hasSettingsPy || hasUrlsPy || hasWsgiOrAsgi) || hasInstalledApps && (hasSettingsPy || hasDjangoImport) || hasDjangoImport && (hasDjangoUrlsImport || hasUrlsPy);
211952
+ if (!hasSafeDjangoCoreEvidence || strongSignals === 0) {
211953
+ return null;
211954
+ }
211955
+ const hasRestEvidence = hasUrlsPy || hasDjangoUrlsImport;
211956
+ const confidence = strongSignals >= 2 ? "high" : strongSignals >= 1 || mediumSignals >= 3 || mediumSignals >= 2 && weakSignals >= 1 ? "medium" : "low";
211957
+ return toResult({
211958
+ adapterId: "django",
211959
+ confidence,
211960
+ score,
211961
+ evidence,
211962
+ projectKindHints: ["backend"],
211963
+ apiStyleHint: hasRestEvidence ? "rest" : "unknown"
211964
+ });
211965
+ },
211966
+ resolveProjectKinds: () => ["backend"],
211967
+ getImportExtractionMode: () => "python-imports"
211968
+ }),
211969
+ flask: buildAdapter({
211970
+ id: "flask",
211971
+ displayName: canonicalStackLabels.flask,
211972
+ ecosystem: "python",
211973
+ category: "framework",
211974
+ priority: 66,
211975
+ manifestDetectionHints: [
211976
+ "requirements.txt",
211977
+ "pyproject.toml",
211978
+ "Pipfile",
211979
+ "Pipfile.lock",
211980
+ "poetry.lock",
211981
+ "app.py",
211982
+ "wsgi.py",
211983
+ "main.py"
211984
+ ],
211985
+ buildToolHints: ["pip", "poetry", "flask"],
211986
+ defaults: {
211987
+ sourceRoots: ["app", "src", "."],
211988
+ moduleRootCandidates: ["app", "src/app", "src", "blueprints", "api"],
211989
+ apiRoots: ["app", "src", "blueprints", "api"],
211990
+ databaseRoots: ["migrations", "app/db", "app/database", "src/db", "src/database"],
211991
+ configFiles: ["requirements.txt", "pyproject.toml", "Pipfile", "Pipfile.lock", "poetry.lock"],
211992
+ fileExtensions: [".py", ".toml", ".txt", ".sql", ".yaml", ".yml"],
211993
+ dependencyFileExtensions: [".py"],
211994
+ ignoreDirectories: [
211995
+ "__pycache__",
211996
+ ".pytest_cache",
211997
+ ".venv",
211998
+ "venv",
211999
+ "dist",
212000
+ "build",
212001
+ ".idea",
212002
+ ...commonIgnoreDirectories
212003
+ ],
212004
+ entrypointHints: ["app.py", "main.py", "wsgi.py", "app/__init__.py"],
212005
+ publicEntrypointDirectories: [],
212006
+ moduleIndexFileName: "__init__.py"
212007
+ },
212008
+ detect: (context) => {
212009
+ const evidence = [];
212010
+ let strongSignals = 0;
212011
+ let mediumSignals = 0;
212012
+ let weakSignals = 0;
212013
+ let score = 0;
212014
+ const hasRequirementsFlask = context.hasFileMatchingPattern(
212015
+ "requirements.txt",
212016
+ /(^|\s)flask([=<>~!]|$)/imu
212017
+ );
212018
+ const hasPyprojectFlask = context.hasFileMatchingPattern("pyproject.toml", /\bflask\b/iu);
212019
+ const hasPoetryLockFlask = context.hasFileMatchingPattern(
212020
+ "poetry.lock",
212021
+ /\bname\s*=\s*["']flask["']/iu
212022
+ );
212023
+ const hasPipfileFlask = context.hasFileMatchingPattern("Pipfile", /\bflask\b/iu);
212024
+ const hasPipfileLockFlask = context.hasFileMatchingPattern("Pipfile.lock", /"flask"\s*:/iu);
212025
+ const hasFlaskDependencyEvidence = hasRequirementsFlask || hasPyprojectFlask || hasPoetryLockFlask || hasPipfileFlask || hasPipfileLockFlask;
212026
+ const hasFlaskImport = context.hasAnyFileMatchingPattern(
212027
+ /\.py$/iu,
212028
+ /\bfrom\s+flask\s+import\s+.*\bFlask\b/u
212029
+ );
212030
+ const hasFlaskConstruction = context.hasAnyFileMatchingPattern(
212031
+ /\.py$/iu,
212032
+ /\b\w+\s*=\s*Flask\s*\(\s*__name__/u
212033
+ );
212034
+ const hasRouteDecorator = context.hasAnyFileMatchingPattern(
212035
+ /\.py$/iu,
212036
+ /@\w+\.route\s*\(/u
212037
+ );
212038
+ const hasMethodRouteDecorator = context.hasAnyFileMatchingPattern(
212039
+ /\.py$/iu,
212040
+ /@\w+\.(?:get|post|put|delete)\s*\(/u
212041
+ );
212042
+ const hasBlueprintImport = context.hasAnyFileMatchingPattern(
212043
+ /\.py$/iu,
212044
+ /\bfrom\s+flask\s+import\s+.*\bBlueprint\b/u
212045
+ );
212046
+ const hasBlueprintConstruction = context.hasAnyFileMatchingPattern(
212047
+ /\.py$/iu,
212048
+ /\b\w+\s*=\s*Blueprint\s*\(/u
212049
+ );
212050
+ const hasFlaskEntrypointConstruction = context.hasAnyFileMatchingPattern(
212051
+ /(^|\/)(app|main|wsgi)\.py$/iu,
212052
+ /\b\w+\s*=\s*Flask\s*\(/u
212053
+ );
212054
+ if (hasRequirementsFlask) {
212055
+ strongSignals += 1;
212056
+ score += 52;
212057
+ evidence.push("Found requirements.txt with flask dependency");
212058
+ }
212059
+ if (hasPyprojectFlask) {
212060
+ strongSignals += 1;
212061
+ score += 52;
212062
+ evidence.push("Found pyproject.toml with flask dependency");
212063
+ }
212064
+ if (hasPoetryLockFlask || hasPipfileFlask || hasPipfileLockFlask) {
212065
+ strongSignals += 1;
212066
+ score += 50;
212067
+ evidence.push("Found Pipfile/lock metadata with flask dependency");
212068
+ }
212069
+ if (hasFlaskImport) {
212070
+ strongSignals += 1;
212071
+ score += 34;
212072
+ evidence.push("Found Flask import in source");
212073
+ }
212074
+ if (hasFlaskConstruction) {
212075
+ strongSignals += 1;
212076
+ score += 34;
212077
+ evidence.push("Found Flask application construction in source");
212078
+ }
212079
+ if (hasRouteDecorator) {
212080
+ strongSignals += 1;
212081
+ score += 22;
212082
+ evidence.push("Found Flask route decorator in source");
212083
+ }
212084
+ if (hasMethodRouteDecorator) {
212085
+ mediumSignals += 1;
212086
+ score += 10;
212087
+ evidence.push("Found Flask method route decorator in source");
212088
+ }
212089
+ if (hasBlueprintImport) {
212090
+ mediumSignals += 1;
212091
+ score += 10;
212092
+ evidence.push("Found Blueprint import in source");
212093
+ }
212094
+ if (hasBlueprintConstruction) {
212095
+ mediumSignals += 1;
212096
+ score += 8;
212097
+ evidence.push("Found Blueprint construction in source");
212098
+ }
212099
+ if (hasFlaskEntrypointConstruction) {
212100
+ mediumSignals += 1;
212101
+ score += 8;
212102
+ evidence.push("Found Flask construction in app.py/main.py/wsgi.py");
212103
+ }
212104
+ if (context.hasDirectory("blueprints")) {
212105
+ weakSignals += 1;
212106
+ score += 2;
212107
+ evidence.push("Found blueprints directory");
212108
+ }
212109
+ if (context.hasDirectory("app") || context.hasDirectory("src")) {
212110
+ weakSignals += 1;
212111
+ score += 2;
212112
+ evidence.push("Found typical Python backend layout directory");
212113
+ }
212114
+ const hasSafeFlaskCoreEvidence = hasFlaskDependencyEvidence || hasFlaskImport && hasFlaskConstruction || hasFlaskImport && (hasRouteDecorator || hasMethodRouteDecorator || hasBlueprintImport) || hasBlueprintImport && hasBlueprintConstruction;
212115
+ if (!hasSafeFlaskCoreEvidence || strongSignals === 0) {
212116
+ return null;
212117
+ }
212118
+ const hasRestEvidence = hasRouteDecorator || hasMethodRouteDecorator || hasBlueprintImport;
212119
+ const confidence = strongSignals >= 2 ? "high" : strongSignals >= 1 || mediumSignals >= 3 || mediumSignals >= 2 && weakSignals >= 1 ? "medium" : "low";
212120
+ return toResult({
212121
+ adapterId: "flask",
212122
+ confidence,
212123
+ score,
212124
+ evidence,
212125
+ projectKindHints: ["backend"],
212126
+ apiStyleHint: hasRestEvidence ? "rest" : "unknown"
212127
+ });
212128
+ },
212129
+ resolveProjectKinds: () => ["backend"],
212130
+ getImportExtractionMode: () => "python-imports"
212131
+ }),
211808
212132
  python: buildAdapter({
211809
212133
  id: "python",
211810
212134
  displayName: canonicalStackLabels.python,
@@ -231312,6 +231636,8 @@ var ignoredDirectoryNames2 = /* @__PURE__ */ new Set([
231312
231636
  var backendStackIds = /* @__PURE__ */ new Set([
231313
231637
  "spring",
231314
231638
  "fastapi",
231639
+ "django",
231640
+ "flask",
231315
231641
  "dotnet",
231316
231642
  "nestjs",
231317
231643
  "java",
@@ -231539,6 +231865,8 @@ function toDetectedStack(adapterId) {
231539
231865
  case "java":
231540
231866
  case "spring":
231541
231867
  case "fastapi":
231868
+ case "django":
231869
+ case "flask":
231542
231870
  case "dotnet":
231543
231871
  case "python":
231544
231872
  case "php":
@@ -231572,6 +231900,12 @@ function resolveLanguages(stacks) {
231572
231900
  if (stacks.includes("fastapi")) {
231573
231901
  languages.add("python");
231574
231902
  }
231903
+ if (stacks.includes("django")) {
231904
+ languages.add("python");
231905
+ }
231906
+ if (stacks.includes("flask")) {
231907
+ languages.add("python");
231908
+ }
231575
231909
  if (stacks.includes("php")) {
231576
231910
  languages.add("php");
231577
231911
  }
@@ -233402,6 +233736,10 @@ function resolveImplementationProfile(input2) {
233402
233736
  return input2.apiStyleHint === "rest" ? "backend-spring-rest" : "backend-spring";
233403
233737
  case "fastapi":
233404
233738
  return input2.apiStyleHint === "rest" ? "backend-fastapi-rest" : "backend-fastapi";
233739
+ case "django":
233740
+ return input2.apiStyleHint === "rest" ? "backend-django-rest" : "backend-django";
233741
+ case "flask":
233742
+ return input2.apiStyleHint === "rest" ? "backend-flask-rest" : "backend-flask";
233405
233743
  case "dotnet":
233406
233744
  return input2.apiStyleHint === "rest" ? "backend-dotnet-webapi" : "backend-dotnet";
233407
233745
  case "java":
@@ -233457,6 +233795,9 @@ function resolveNormalizedProjectKinds(detection, evidence) {
233457
233795
  addEvidence6(evidence, `Normalized project kinds from selected adapter '${selectedAdapter.id}' hints`);
233458
233796
  } else if (detection.topology.topology === "monorepo" && selectedHints.length > 0) {
233459
233797
  normalizedKinds = uniqueSortedKinds([...heuristicKinds, ...selectedHints]);
233798
+ if (normalizedKinds.includes("library") && !selectedHints.includes("library") && (detection.architectureStyleSignals?.sharedPackageCount ?? 0) === 0) {
233799
+ normalizedKinds = normalizedKinds.filter((kind) => kind !== "library");
233800
+ }
233460
233801
  addEvidence6(evidence, "Normalized monorepo project kinds from adapter hints plus heuristic evidence");
233461
233802
  } else if (heuristicKinds.length > 0) {
233462
233803
  normalizedKinds = uniqueSortedKinds(heuristicKinds);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archpilotlabs/archpilot",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "Executable architecture governance CLI",
5
5
  "homepage": "https://archpilot.org",
6
6
  "bugs": {