@archpilotlabs/archpilot 0.2.4 → 0.2.5

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/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.5
4
+
5
+ ### Improved
6
+
7
+ - Improved Smart Init classification for mixed backend, frontend, and library monorepos.
8
+ - Improved NestJS detection so runtime applications with package publishing metadata are classified as backend services, while reusable NestJS packages remain libraries.
9
+ - Improved component discovery so test-only packages, build and lint tooling, and scaffold templates are excluded from default governed architecture boundaries.
10
+ - Improved backend resource detection for GraphQL APIs and PostgreSQL databases.
11
+ - Improved data-query-risk analysis for parameterized and interpolated query limits, PostgreSQL catalog lookups, and existence queries.
12
+ - Improved source-scope handling for authorization analysis and controller-role detection.
13
+ - Improved CLI version reporting in bundled and packed executables.
14
+
15
+ ### Fixed
16
+
17
+ - Fixed NestJS backend applications being classified only as Node.js libraries when package entry points or publishing metadata were present.
18
+ - Fixed Playwright test packages, lint-rule packages, build tooling, and nested scaffold templates being proposed as governed components.
19
+ - Fixed authorization findings being emitted from confirmed specification and test source.
20
+ - Fixed bounded SQL queries using parameterized or interpolated `LIMIT` values being reported as unbounded collection reads.
21
+ - Fixed PostgreSQL catalog identity and existence lookups being treated as application collection queries.
22
+ - Fixed generic `handlers` directories being interpreted as controller layers without structural controller evidence.
23
+ - Fixed bundled CLI artifacts reporting a stale version because package metadata could not be resolved at runtime.
24
+
25
+ ### Notes
26
+
27
+ - These changes improve model and finding accuracy without changing rule IDs, severities, score weights, scoring caps, or normalization behavior.
28
+ - Oversized services, direct persistence access, unbounded collection processing, repeated query patterns, and dependency-boundary violations remain reportable when supported by credible evidence.
29
+ - Documentation, examples, integration tests, and tooling remain governed when explicitly included by the generated or reviewed architecture configuration.
30
+
31
+ ---
32
+
3
33
  ## 0.2.4
4
34
 
5
35
  ### Improved
@@ -225656,7 +225656,12 @@ function hasSelectStar(statement) {
225656
225656
  return /\bselect\s+(distinct\s+)?\*/i.test(statement);
225657
225657
  }
225658
225658
  function hasPaginationEvidence(statement) {
225659
- return /\blimit\s+\d+\b|\boffset\s+\d+\b|\bfetch\s+(first|next)\b|\btop\s+\d+\b/i.test(
225659
+ const boundValuePattern = "(?:\\d+|\\$\\d+|\\?|:[A-Za-z_]\\w*|@[A-Za-z_]\\w*|\\$\\{[^}]{1,120}\\}|[A-Za-z_]\\w*)";
225660
+ const valueTerminatorPattern = "(?=\\s|,|;|\\)|$)";
225661
+ return new RegExp(
225662
+ `\\blimit\\s+${boundValuePattern}${valueTerminatorPattern}|\\boffset\\s+${boundValuePattern}${valueTerminatorPattern}|\\bfetch\\s+(first|next)\\b|\\btop\\s+${boundValuePattern}${valueTerminatorPattern}`,
225663
+ "i"
225664
+ ).test(
225660
225665
  statement
225661
225666
  );
225662
225667
  }
@@ -225667,10 +225672,17 @@ function hasEqualityPredicate(statement, columnPattern) {
225667
225672
  ).test(statement);
225668
225673
  }
225669
225674
  function isCatalogIdentifierPointLookup(statement) {
225670
- return /\bfrom\s+information_schema\.schemata\b/iu.test(statement) && hasEqualityPredicate(statement, "schema_name");
225675
+ const metadataSourcePattern = /\bfrom\s+(?:information_schema\.(?:schemata|tables|columns|key_column_usage|table_constraints)|pg_catalog\.(?:pg_class|pg_namespace|pg_attribute|pg_constraint|pg_type|pg_enum|pg_tables|pg_indexes)|pg_(?:class|namespace|attribute|constraint|type|enum|tables|indexes))\b/iu;
225676
+ if (!metadataSourcePattern.test(statement)) {
225677
+ return false;
225678
+ }
225679
+ return hasEqualityPredicate(statement, "schema_name|table_schema|table_name|column_name|constraint_name") || hasEqualityPredicate(statement, "schemaname|tablename|relname|nspname|attname|conname|typname|enumlabel|indexname");
225671
225680
  }
225672
225681
  function isObviousPointLookup(statement) {
225673
225682
  const normalized = statement.toLowerCase();
225683
+ if (/\bselect\s+exists\s*\(/i.test(normalized)) {
225684
+ return true;
225685
+ }
225674
225686
  if (!normalized.includes("where")) {
225675
225687
  return false;
225676
225688
  }
@@ -227467,6 +227479,7 @@ var import_node_fs26 = require("node:fs");
227467
227479
  var ts4 = __toESM(require_typescript());
227468
227480
  init_moduleDiscovery();
227469
227481
  init_archpilotIgnore();
227482
+ init_sourceScopeClassification();
227470
227483
  var RuleIds = {
227471
227484
  AUTH_MISSING_ENFORCEMENT: "AP-AUTH-001",
227472
227485
  AUTH_INCONSISTENT_ENFORCEMENT: "AP-AUTH-002",
@@ -227897,7 +227910,10 @@ function isProtectedLooking(endpoint) {
227897
227910
  }
227898
227911
  function detectAuthorizationFindings(files, contract, astCache) {
227899
227912
  const findings = [];
227900
- for (const file of files) {
227913
+ const productionFiles = files.filter(
227914
+ (file) => isProductionBackendGovernanceScope(classifyValidationSourceScope(file.relativePath))
227915
+ );
227916
+ for (const file of productionFiles) {
227901
227917
  const moduleName = inferModuleForPath3(contract, file.relativePath);
227902
227918
  const endpoints = extractEndpoints(file, moduleName);
227903
227919
  if (endpoints.length === 0 && !isControllerFile(file, astCache)) {
@@ -227932,7 +227948,7 @@ function detectAuthorizationFindings(files, contract, astCache) {
227932
227948
  }
227933
227949
  }
227934
227950
  }
227935
- for (const file of files.filter((candidate) => isRepositoryFile(candidate, astCache))) {
227951
+ for (const file of productionFiles.filter((candidate) => isRepositoryFile(candidate, astCache))) {
227936
227952
  const roleMatch = executableAuthorizationDecisionPattern.exec(file.content);
227937
227953
  if (!roleMatch) {
227938
227954
  continue;
@@ -228415,7 +228431,7 @@ function classifySourceFile2(file, contract, astCache) {
228415
228431
  const scope = classifyValidationSourceScope(file.relativePath);
228416
228432
  const roles = new Set(getDeclaredRoles2(file, astCache));
228417
228433
  const lowerPath = file.relativePath.toLowerCase();
228418
- if (/(^|[./_-])controller\.(ts|tsx|js|jsx|mjs|cjs|java|cs|py|php|go)$/u.test(lowerPath) || /(^|\/)(controllers?|routers?|routes?|handlers?)\//u.test(lowerPath)) {
228434
+ if (/(^|[./_-])controller\.(ts|tsx|js|jsx|mjs|cjs|java|cs|py|php|go)$/u.test(lowerPath) || /(^|\/)(controllers?|routers?|routes?)\//u.test(lowerPath)) {
228419
228435
  roles.add("controller");
228420
228436
  }
228421
228437
  if (/(^|[./_-])service\.(ts|tsx|js|jsx|mjs|cjs|java|cs|py|php|go)$/u.test(lowerPath) || /(^|\/)(services?)\//u.test(lowerPath)) {
@@ -245651,6 +245667,14 @@ function hasObject(value) {
245651
245667
  function hasPackagePublishSurface(packageJson) {
245652
245668
  return hasObject(packageJson?.exports) || typeof packageJson?.main === "string" || typeof packageJson?.module === "string" || typeof packageJson?.types === "string" || hasObject(packageJson?.publishConfig) || Array.isArray(packageJson?.files);
245653
245669
  }
245670
+ function packageDependencyNames(packageJson) {
245671
+ return [
245672
+ ...recordKeys(packageJson?.dependencies),
245673
+ ...recordKeys(packageJson?.devDependencies),
245674
+ ...recordKeys(packageJson?.peerDependencies),
245675
+ ...recordKeys(packageJson?.optionalDependencies)
245676
+ ];
245677
+ }
245654
245678
  function absoluteComponentRoot(workspaceRoot, componentPath) {
245655
245679
  return componentPath === "." ? workspaceRoot : path64.join(workspaceRoot, ...componentPath.split("/"));
245656
245680
  }
@@ -245658,6 +245682,10 @@ function readPackageJson(workspaceRoot, componentPath) {
245658
245682
  const absoluteRoot = absoluteComponentRoot(workspaceRoot, componentPath);
245659
245683
  return safeReadJson(path64.join(absoluteRoot, "package.json"));
245660
245684
  }
245685
+ function readProjectJson(workspaceRoot, componentPath) {
245686
+ const absoluteRoot = absoluteComponentRoot(workspaceRoot, componentPath);
245687
+ return safeReadJson(path64.join(absoluteRoot, "project.json"));
245688
+ }
245661
245689
  function readTextFileSafe(filePath) {
245662
245690
  try {
245663
245691
  return fs58.readFileSync(filePath, { encoding: "utf8" });
@@ -246225,11 +246253,57 @@ function hasRuntimeSourceFiles(workspaceRoot, componentPath) {
246225
246253
  }
246226
246254
  return false;
246227
246255
  }
246256
+ function hasFileMatchingUnderRoot(root, pattern) {
246257
+ if (!pathExists19(root)) {
246258
+ return false;
246259
+ }
246260
+ const entries = fs58.readdirSync(root, { recursive: true, withFileTypes: true });
246261
+ return entries.some((entry) => entry.isFile() && pattern.test(normalizePath25(path64.join(entry.parentPath, entry.name))));
246262
+ }
246263
+ function hasNestRuntimeApplicationEvidence(workspaceRoot, componentPath, packageJson = readPackageJson(workspaceRoot, componentPath)) {
246264
+ const absoluteRoot = absoluteComponentRoot(workspaceRoot, componentPath);
246265
+ const dependencies = new Set(packageDependencyNames(packageJson));
246266
+ const scripts = recordKeys(packageJson?.scripts);
246267
+ const hasNestCore = dependencies.has("@nestjs/core");
246268
+ if (!hasNestCore) {
246269
+ return false;
246270
+ }
246271
+ const hasRuntimePlatform = dependencies.has("@nestjs/platform-express") || dependencies.has("@nestjs/platform-fastify") || dependencies.has("@nestjs/microservices") || dependencies.has("@nestjs/graphql") || dependencies.has("@nestjs/apollo");
246272
+ const hasRunnableScript = scripts.some((script) => /^(?:start|serve|dev|worker)(?::|$)/u.test(script));
246273
+ const hasNestCliConfig = ["nest-cli.json", "nest.config.js", "nest.config.ts"].some(
246274
+ (fileName) => pathExists19(path64.join(absoluteRoot, fileName))
246275
+ );
246276
+ const hasBootstrapEntrypoint = [
246277
+ path64.join(absoluteRoot, "src", "main.ts"),
246278
+ path64.join(absoluteRoot, "src", "main.js"),
246279
+ path64.join(absoluteRoot, "main.ts"),
246280
+ path64.join(absoluteRoot, "main.js")
246281
+ ].some((filePath) => {
246282
+ const contents = readTextFileSafe(filePath);
246283
+ return Boolean(contents && /\bNestFactory\.create(?:ApplicationContext)?\s*\(/u.test(contents));
246284
+ });
246285
+ const srcRoot = path64.join(absoluteRoot, "src");
246286
+ const hasRuntimeSourceShape = hasFileMatchingUnderRoot(srcRoot, /\.(?:controller|resolver|gateway|processor|module|worker|queue)\.(?:ts|js)$/iu) || hasFileMatchingUnderRoot(srcRoot, /[/\\](?:controllers?|resolvers?|queues?|workers?)[/\\][^/\\]+\.(?:ts|js)$/iu);
246287
+ const hasDatabaseRuntimeIntegration = [...dependencies].some(
246288
+ (dependency) => /^(?:typeorm|@nestjs\/typeorm|@prisma\/client|prisma|mongoose|@nestjs\/mongoose|sequelize|@mikro-orm\/core)$/u.test(dependency)
246289
+ );
246290
+ const structuralEvidenceCount = [
246291
+ hasRuntimePlatform,
246292
+ hasRunnableScript,
246293
+ hasNestCliConfig,
246294
+ hasBootstrapEntrypoint,
246295
+ hasRuntimeSourceShape,
246296
+ hasDatabaseRuntimeIntegration
246297
+ ].filter(Boolean).length;
246298
+ return hasBootstrapEntrypoint || hasRuntimePlatform && structuralEvidenceCount >= 2 || hasRunnableScript && hasRuntimeSourceShape && structuralEvidenceCount >= 3;
246299
+ }
246228
246300
  function resolveKind(workspaceRoot, candidate, localProjectKinds) {
246229
246301
  const lowerPath = candidate.path.toLowerCase();
246230
246302
  const packageJson = readPackageJson(workspaceRoot, candidate.path);
246231
246303
  const maven = readMavenMetadata(workspaceRoot, candidate.path);
246304
+ const dependencies = new Set(packageDependencyNames(packageJson));
246232
246305
  const hasRunnableFrontendEvidence = hasRunnableFrontendComponentEvidence(workspaceRoot, candidate.path);
246306
+ const hasNestRuntimeEvidence = hasNestRuntimeApplicationEvidence(workspaceRoot, candidate.path, packageJson);
246233
246307
  const hasPublishSurface = hasPackagePublishSurface(packageJson);
246234
246308
  const hasMavenRunnableEvidence = Boolean(
246235
246309
  maven && (hasMavenMainEntrypoint(workspaceRoot, candidate.path) || hasMavenConfiguredMainClassEntrypoint(workspaceRoot, candidate.path, maven.configuredMainClass) || maven.hasSpringBootPlugin && maven.hasSpringBootRepackageGoal && hasMavenMainEntrypoint(workspaceRoot, candidate.path) || maven.packaging === "war" && hasMavenMainEntrypoint(workspaceRoot, candidate.path))
@@ -246251,6 +246325,12 @@ function resolveKind(workspaceRoot, candidate, localProjectKinds) {
246251
246325
  return hasPublishSurface && !hasRunnableFrontendEvidence ? "library" : "frontend";
246252
246326
  }
246253
246327
  if (classification === "backend_app") {
246328
+ if (hasPublishSurface && dependencies.has("@nestjs/core") && !hasNestRuntimeEvidence) {
246329
+ return "library";
246330
+ }
246331
+ return "backend";
246332
+ }
246333
+ if (hasNestRuntimeEvidence) {
246254
246334
  return "backend";
246255
246335
  }
246256
246336
  if (hasRunnableFrontendEvidence && localProjectKinds.primaryKind !== "backend" && localProjectKinds.primaryKind !== "service" && localProjectKinds.primaryKind !== "cli") {
@@ -246314,6 +246394,7 @@ function hasAnyToken(value, patterns) {
246314
246394
  }
246315
246395
  function classifyGovernanceRole(input2) {
246316
246396
  const packageJson = readPackageJson(input2.workspaceRoot, input2.candidate.path);
246397
+ const projectJson = readProjectJson(input2.workspaceRoot, input2.candidate.path);
246317
246398
  const maven = readMavenMetadata(input2.workspaceRoot, input2.candidate.path);
246318
246399
  const evidence = [];
246319
246400
  let supportScore = 0;
@@ -246327,11 +246408,15 @@ function classifyGovernanceRole(input2) {
246327
246408
  const scripts = recordKeys(packageJson?.scripts);
246328
246409
  const dependencies = recordKeys(packageJson?.dependencies);
246329
246410
  const devDependencies = recordKeys(packageJson?.devDependencies);
246411
+ const projectTags = toStringArray(projectJson?.tags);
246412
+ const projectTargets = recordKeys(projectJson?.targets);
246330
246413
  const scriptText = scripts.join(" ").toLowerCase();
246414
+ const projectMetadataText = `${projectTags.join(" ")} ${projectTargets.join(" ")}`.toLowerCase();
246331
246415
  const dependencyText = [...dependencies, ...devDependencies].join(" ").toLowerCase();
246416
+ const hasNestRuntimeEvidence = hasNestRuntimeApplicationEvidence(input2.workspaceRoot, input2.candidate.path, packageJson);
246332
246417
  const hasStrongSupportPath = hasAnyToken(pathText, [
246333
- /(^|\/)(testing|test-plugins|cypress-tests|fixtures?|examples?|benchmarks?|smoke)(\/|$)/u,
246334
- /(^|\/)(dev-server|scaffold|scaffolding|generators?|devkit|tooling|tools)(\/|$)/u
246418
+ /(^|\/)(testing|e2e-testing|test-plugins|cypress-tests|fixtures?|benchmarks?|smoke)(\/|$)/u,
246419
+ /(^|\/)(dev-server|scaffold|scaffolding|generators?|devkit|tooling|tools|templates?)(\/|$)/u
246335
246420
  ]);
246336
246421
  const isPlainResourcesSurface = isUnderJvmResourceRoot4(pathText) && !hasRuntimeSourceFiles(input2.workspaceRoot, input2.candidate.path) && !packageJson && !maven;
246337
246422
  const hasPublishSurface = hasPackagePublishSurface(packageJson);
@@ -246376,6 +246461,10 @@ function classifyGovernanceRole(input2) {
246376
246461
  runtimeScore += 1;
246377
246462
  addEvidence6(evidence, `Runtime framework stack signal: ${input2.stack}`);
246378
246463
  }
246464
+ if (hasNestRuntimeEvidence) {
246465
+ runtimeScore += 3;
246466
+ addEvidence6(evidence, "NestJS runtime application evidence");
246467
+ }
246379
246468
  if (hasPublishSurface && !isPrivate) {
246380
246469
  runtimeScore += 1;
246381
246470
  addEvidence6(evidence, "Published package surface signal");
@@ -246400,25 +246489,37 @@ function classifyGovernanceRole(input2) {
246400
246489
  if (hasMavenAutoConfigurationEvidence && !hasMavenRunnableApplicationEvidence) {
246401
246490
  addEvidence6(evidence, "Maven Spring Boot auto-configuration/starter library evidence");
246402
246491
  }
246403
- if (hasAnyToken(descriptiveManifestText, [/\b(e2e|end-to-end|testing|test helper|test utilities|fixtures?)\b/u])) {
246492
+ if (hasAnyToken(descriptiveManifestText, [/\b(e2e|end-to-end|testing|test helper|test utilities|fixtures?|test harness)\b/u])) {
246404
246493
  supportScore += 3;
246405
246494
  addEvidence6(evidence, "Package metadata describes test/support usage");
246406
246495
  }
246496
+ if (hasAnyToken(`${packageName.toLowerCase()} ${projectMetadataText}`, [/\b(e2e|end-to-end|testing|test harness|scope:testing|test:ui|test:debug|test:report)\b/u]) && hasAnyToken(dependencyText, [/\b(?:playwright|@playwright\/test|cypress|vitest|jest|mocha)\b/u])) {
246497
+ supportScore += 4;
246498
+ addEvidence6(evidence, "Project metadata and dependencies indicate test-only workspace");
246499
+ }
246407
246500
  if (hasAnyToken(descriptiveManifestText, [/\b(scaffold|scaffolding|generator|codegen|template|starter|create)\b/u])) {
246408
246501
  supportScore += 3;
246409
246502
  addEvidence6(evidence, "Package metadata describes scaffolding/generation usage");
246410
246503
  }
246411
- if (hasAnyToken(descriptiveManifestText, [/\b(dev server|development server|development tooling|tooling|devkit)\b/u])) {
246504
+ if (hasAnyToken(descriptiveManifestText, [/\b(dev server|development server|development tooling|tooling|devkit|lint rules?|linter|build tooling|repository tooling)\b/u])) {
246412
246505
  supportScore += 2;
246413
246506
  addEvidence6(evidence, "Package metadata describes development/tooling usage");
246414
246507
  }
246415
246508
  if (hasAnyToken(pathText, [
246416
- /(^|\/)(test|tests|testing|__tests__|cypress|cypress-tests|fixtures?|examples?|benchmarks?|smoke)(\/|$)/u,
246417
- /(^|\/)(dev-server|test-plugins|scaffold|scaffolding|generators?|devkit|tooling|tools)(\/|$)/u
246509
+ /(^|\/)(test|tests|testing|e2e-testing|__tests__|cypress|cypress-tests|fixtures?|benchmarks?|smoke)(\/|$)/u,
246510
+ /(^|\/)(dev-server|test-plugins|scaffold|scaffolding|generators?|devkit|tooling|tools|templates?)(\/|$)/u
246418
246511
  ])) {
246419
246512
  supportScore += 1;
246420
246513
  addEvidence6(evidence, "Path contains support/tooling role tokens");
246421
246514
  }
246515
+ if (hasAnyToken(pathText, [/(^|\/)(?:src\/constants\/)?templates?(\/|$)/u])) {
246516
+ supportScore += 4;
246517
+ addEvidence6(evidence, "Nested scaffold/template source surface");
246518
+ }
246519
+ if (hasAnyToken(dependencyText, [/\b(?:eslint|oxlint|prettier|typescript-eslint|ts-morph|jscodeshift|schematics|yeoman)\b/u])) {
246520
+ supportScore += 2;
246521
+ addEvidence6(evidence, "Package dependencies indicate lint/build/generation tooling");
246522
+ }
246422
246523
  if (scripts.length > 0 && scripts.every((script) => /^(test|e2e|smoke|bench|benchmark|lint|typecheck|dev|dev:|load-test|generate|scaffold)/u.test(script))) {
246423
246524
  supportScore += 1;
246424
246525
  addEvidence6(evidence, "Package scripts are dominated by development/test workflows");
@@ -250154,6 +250255,9 @@ function readFlagValue(args, flag) {
250154
250255
  return value && value.length > 0 ? value : void 0;
250155
250256
  }
250156
250257
  async function readCliPackageVersion() {
250258
+ if ("0.2.5".trim().length > 0) {
250259
+ return "0.2.5".trim();
250260
+ }
250157
250261
  const candidatePackageJsonPaths = [
250158
250262
  path73.join(__dirname, "..", "package.json"),
250159
250263
  path73.join(__dirname, "..", "..", "..", "..", "cli", "package.json")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archpilotlabs/archpilot",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Executable architecture governance CLI for Smart Init, validation, drift detection, and CI.",
5
5
  "homepage": "https://archpilot.org",
6
6
  "bugs": {