@archpilotlabs/archpilot 0.2.5 → 0.2.7

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.
@@ -12907,7 +12907,7 @@ ${lanes.join("\n")}
12907
12907
  return process.memoryUsage().heapUsed;
12908
12908
  },
12909
12909
  getFileSize(path74) {
12910
- const stat = statSync(path74);
12910
+ const stat = statSync2(path74);
12911
12911
  if (stat == null ? void 0 : stat.isFile()) {
12912
12912
  return stat.size;
12913
12913
  }
@@ -12951,7 +12951,7 @@ ${lanes.join("\n")}
12951
12951
  }
12952
12952
  };
12953
12953
  return nodeSystem;
12954
- function statSync(path74) {
12954
+ function statSync2(path74) {
12955
12955
  try {
12956
12956
  return _fs.statSync(path74, statSyncOptions);
12957
12957
  } catch {
@@ -13010,7 +13010,7 @@ ${lanes.join("\n")}
13010
13010
  activeSession.post("Profiler.stop", (err, { profile }) => {
13011
13011
  var _a;
13012
13012
  if (!err) {
13013
- if ((_a = statSync(profilePath)) == null ? void 0 : _a.isDirectory()) {
13013
+ if ((_a = statSync2(profilePath)) == null ? void 0 : _a.isDirectory()) {
13014
13014
  profilePath = _path.join(profilePath, `${(/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-")}+P${process.pid}.cpuprofile`);
13015
13015
  }
13016
13016
  try {
@@ -13130,7 +13130,7 @@ ${lanes.join("\n")}
13130
13130
  let stat;
13131
13131
  if (typeof dirent === "string" || dirent.isSymbolicLink()) {
13132
13132
  const name = combinePaths(path74, entry);
13133
- stat = statSync(name);
13133
+ stat = statSync2(name);
13134
13134
  if (!stat) {
13135
13135
  continue;
13136
13136
  }
@@ -13154,7 +13154,7 @@ ${lanes.join("\n")}
13154
13154
  return matchFiles(path74, extensions, excludes, includes, useCaseSensitiveFileNames2, process.cwd(), depth, getAccessibleFileSystemEntries, realpath);
13155
13155
  }
13156
13156
  function fileSystemEntryExists(path74, entryKind) {
13157
- const stat = statSync(path74);
13157
+ const stat = statSync2(path74);
13158
13158
  if (!stat) {
13159
13159
  return false;
13160
13160
  }
@@ -13196,7 +13196,7 @@ ${lanes.join("\n")}
13196
13196
  }
13197
13197
  function getModifiedTime3(path74) {
13198
13198
  var _a;
13199
- return (_a = statSync(path74)) == null ? void 0 : _a.mtime;
13199
+ return (_a = statSync2(path74)) == null ? void 0 : _a.mtime;
13200
13200
  }
13201
13201
  function setModifiedTime(path74, time) {
13202
13202
  try {
@@ -225435,6 +225435,7 @@ init_sourceScopeClassification();
225435
225435
  var maxFileSizeBytes = 256 * 1024;
225436
225436
  var supplementalRiskScanExtensions = [".json", ".yaml", ".yml", ".sql"];
225437
225437
  var backendCodeExtensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".java", ".cs", ".py", ".php", ".go"];
225438
+ var methodBodyScanExtensions = new Set(backendCodeExtensions);
225438
225439
  var supplementalIgnoredDirectoryNames = [".vscode-test", ".tmp"];
225439
225440
  var batchingEvidencePattern = /\b(?:DataLoader|Promise\.all|findMany\s*\([\s\S]{0,300}\bin\b|where\s*:\s*\{[\s\S]{0,300}\bin\s*:|include\s*:|join\s*\(|leftJoin|innerJoin|JOIN\b)\b/iu;
225440
225441
  var queryReadPattern = /\b((?:this\.)?(?:prisma\.(\w+)|([A-Za-z_$][\w$]*Repository|repository))\.(find\w*|count|aggregate|groupBy)\s*\()/giu;
@@ -225443,6 +225444,7 @@ var boundEvidencePattern = /\b(?:take|limit|page|cursor|skip|offset|PageRequest|
225443
225444
  var repeatedQueryPattern = /\b((?:this\.)?(?:prisma\.(\w+)|(\w*Repository|repository))\.(find\w*|count|aggregate|groupBy)\s*\(\s*(?:\{[\s\S]{0,300}?\}|\w+)\s*\))/giu;
225444
225445
  var tenantPredicatePattern = /\b(?:tenantId|tenant_id|organizationId|organization_id|orgId|org_id|accountId|account_id|workspaceId|workspace_id|companyId|company_id|customerId|customer_id)\b/u;
225445
225446
  var codePaginationEvidencePattern = /\b(?:take|skip|limit|offset|page|perPage|pageSize|cursor|Pageable|PageRequest|paginate|simplePaginate|Limit|Offset|Take|Skip|TOP\s+\d+)\b/iu;
225447
+ var sqlQueryInvocationHintPattern = /\b(?:query|execute|executeQuery|createNativeQuery|createQuery|FromSqlRaw|FromSqlInterpolated|Query|QueryContext|QueryRow|QueryRowContext|Exec|ExecContext|Raw|text|DB::select|DB::statement|DB::raw|DB::table)\s*\(|\$queryRaw\s*\(|\$executeRaw\s*\(|@Query\s*\(/u;
225446
225448
  function getLineStarts(file) {
225447
225449
  if (file.lineStarts) {
225448
225450
  return file.lineStarts;
@@ -225666,10 +225668,15 @@ function hasPaginationEvidence(statement) {
225666
225668
  );
225667
225669
  }
225668
225670
  function hasEqualityPredicate(statement, columnPattern) {
225671
+ const whereMatch = /\bwhere\b/iu.exec(statement);
225672
+ if (!whereMatch) {
225673
+ return false;
225674
+ }
225675
+ const predicateWindow = statement.slice(whereMatch.index, whereMatch.index + 1200);
225669
225676
  return new RegExp(
225670
- `\\bwhere\\b[\\s\\S]*\\b(?:${columnPattern})\\b\\s*=\\s*(?:[\\w$:@?]+|'[^']+'|"[^"]+"|\\d+)`,
225677
+ `\\b(?:${columnPattern})\\b\\s*=\\s*(?:[\\w$:@?]+|'[^']+'|"[^"]+"|\\d+)`,
225671
225678
  "iu"
225672
- ).test(statement);
225679
+ ).test(predicateWindow);
225673
225680
  }
225674
225681
  function isCatalogIdentifierPointLookup(statement) {
225675
225682
  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;
@@ -226270,6 +226277,9 @@ function detectRepeatedQueryPattern(method) {
226270
226277
  }
226271
226278
  function extractSqlQueryLiterals(content) {
226272
226279
  const queries = [];
226280
+ if (!sqlQueryInvocationHintPattern.test(content)) {
226281
+ return queries;
226282
+ }
226273
226283
  const searchableContent = maskSourceComments(content);
226274
226284
  const patterns = [
226275
226285
  /(?:\b(?:[\w$]+\.)?(?:query|execute|executeQuery|createNativeQuery|createQuery|FromSqlRaw|FromSqlInterpolated|Query|QueryContext|QueryRow|QueryRowContext|Exec|ExecContext|Raw|text)|\$queryRaw|\$executeRaw)\s*\([^'"`)]{0,180}?(['"`])([\s\S]{0,900}?)\1/giu,
@@ -226291,7 +226301,7 @@ function extractSqlQueryLiterals(content) {
226291
226301
  return queries;
226292
226302
  }
226293
226303
  function maskSourceComments(content) {
226294
- let masked = "";
226304
+ const masked = [];
226295
226305
  let index = 0;
226296
226306
  let state = "code";
226297
226307
  let escaped = false;
@@ -226300,27 +226310,27 @@ function maskSourceComments(content) {
226300
226310
  const next = content[index + 1] ?? "";
226301
226311
  if (state === "line-comment") {
226302
226312
  if (current === "\r" || current === "\n") {
226303
- masked += current;
226313
+ masked.push(current);
226304
226314
  state = "code";
226305
226315
  } else {
226306
- masked += " ";
226316
+ masked.push(" ");
226307
226317
  }
226308
226318
  index += 1;
226309
226319
  continue;
226310
226320
  }
226311
226321
  if (state === "block-comment") {
226312
226322
  if (current === "*" && next === "/") {
226313
- masked += " ";
226323
+ masked.push(" ");
226314
226324
  index += 2;
226315
226325
  state = "code";
226316
226326
  continue;
226317
226327
  }
226318
- masked += current === "\r" || current === "\n" ? current : " ";
226328
+ masked.push(current === "\r" || current === "\n" ? current : " ");
226319
226329
  index += 1;
226320
226330
  continue;
226321
226331
  }
226322
226332
  if (state === "single" || state === "double" || state === "template") {
226323
- masked += current;
226333
+ masked.push(current);
226324
226334
  if (escaped) {
226325
226335
  escaped = false;
226326
226336
  } else if (current === "\\") {
@@ -226332,13 +226342,13 @@ function maskSourceComments(content) {
226332
226342
  continue;
226333
226343
  }
226334
226344
  if (current === "/" && next === "/") {
226335
- masked += " ";
226345
+ masked.push(" ");
226336
226346
  index += 2;
226337
226347
  state = "line-comment";
226338
226348
  continue;
226339
226349
  }
226340
226350
  if (current === "/" && next === "*") {
226341
- masked += " ";
226351
+ masked.push(" ");
226342
226352
  index += 2;
226343
226353
  state = "block-comment";
226344
226354
  continue;
@@ -226350,10 +226360,10 @@ function maskSourceComments(content) {
226350
226360
  } else if (current === "`") {
226351
226361
  state = "template";
226352
226362
  }
226353
- masked += current;
226363
+ masked.push(current);
226354
226364
  index += 1;
226355
226365
  }
226356
- return masked;
226366
+ return masked.join("");
226357
226367
  }
226358
226368
  function detectCodeSqlQueryShapeFindings(file, config) {
226359
226369
  const findings = [];
@@ -226383,7 +226393,7 @@ function detectCodeSqlQueryShapeFindings(file, config) {
226383
226393
  target: firstTable ?? "sql-query"
226384
226394
  }));
226385
226395
  }
226386
- if (statement.queryType === "select" && !isOperationalOrBatchQueryContext(file) && !hasPaginationEvidence(statement.statement) && !isObviousPointLookup(statement.statement) && !/\bcount\s*\(/i.test(statement.statement)) {
226396
+ if (statement.queryType === "select" && !isOperationalOrBatchQueryContext(file) && !hasPaginationEvidence(statement.statement) && !hasSpringDataRepositoryMethodBoundEvidence(file.content, query.offset) && !isObviousPointLookup(statement.statement) && !/\bcount\s*\(/i.test(statement.statement)) {
226387
226397
  findings.push(createCodeFinding({
226388
226398
  id: "AP-DQR-003",
226389
226399
  message: `Potential unbounded query risk: SELECT query${firstTable ? ` on table '${firstTable}'` : ""} has no LIMIT/OFFSET/FETCH pagination evidence.`,
@@ -226397,6 +226407,18 @@ function detectCodeSqlQueryShapeFindings(file, config) {
226397
226407
  }
226398
226408
  return findings;
226399
226409
  }
226410
+ function hasSpringDataRepositoryMethodBoundEvidence(content, queryOffset) {
226411
+ const annotationOffset = content.lastIndexOf("@Query", queryOffset);
226412
+ if (annotationOffset < 0 || queryOffset - annotationOffset > 2400) {
226413
+ return false;
226414
+ }
226415
+ const semicolonOffset = content.indexOf(";", queryOffset);
226416
+ if (semicolonOffset < queryOffset || semicolonOffset - annotationOffset > 3200) {
226417
+ return false;
226418
+ }
226419
+ const declarationWindow = content.slice(queryOffset, semicolonOffset + 1);
226420
+ return /\b(?:Page|Slice|Window)\s*</u.test(declarationWindow) && /\b(?:Pageable|Limit)\s+\w+\b/u.test(declarationWindow);
226421
+ }
226400
226422
  function collectOrmReadEvidence(method) {
226401
226423
  const patterns = [
226402
226424
  [/\b(?:this\.)?prisma\.\w+\.findMany\s*\(/giu, "prisma.findMany"],
@@ -226514,7 +226536,11 @@ function detectCodeQueryRiskFindings(files, config, astCache) {
226514
226536
  if (isProductionBackend) {
226515
226537
  findings.push(...detectCodeSqlQueryShapeFindings(file, config));
226516
226538
  }
226517
- for (const method of extractMethodBodies(file, astCache)) {
226539
+ if (!methodBodyScanExtensions.has(file.extension)) {
226540
+ continue;
226541
+ }
226542
+ const methods = extractMethodBodies(file, astCache);
226543
+ for (const method of methods) {
226518
226544
  if (!isProductionBackend) {
226519
226545
  continue;
226520
226546
  }
@@ -228136,6 +228162,8 @@ var transactionBoundaryDecoratorPattern = /@(?:Transactional|Transaction)\b|\[(?
228136
228162
  var explicitTransactionBoundaryPattern = /\b(?:\$transaction|DB::transaction)\s*\(|\b(?:knex|sequelize|typeorm|dataSource|connection|manager|transactionManager)\.transaction\s*\(|\b(?:this\.)?connection\.withTransaction\s*\(|\b(?:startTransaction|beginTransaction|BeginTransaction|BeginTransactionAsync|BeginTx)\s*\(|\bnew\s+TransactionScope\b|\bsession\.begin\s*\(|\bdb\.Transaction\s*\(/iu;
228137
228163
  var separateTransactionPropagationPattern = /\b(?:requiresNew|REQUIRES_NEW|Propagation\.REQUIRES_NEW)\b/u;
228138
228164
  var businessRulePattern = /\b(?:approve|reject|authorize|eligible|eligibility|policy|discount|limit|quota|balance|risk|compliance|settle|state|status|transition)\b/iu;
228165
+ var repositoryBusinessRulePattern = /\b(?:approve|reject|authorize|eligible|eligibility|policy|discount|quota|balance|risk|compliance|settle|transition)\b/iu;
228166
+ var decisionStructurePattern = /\bif\s*\(|\bswitch\s*\(|\bthrow\s+new\b/u;
228139
228167
  var transactionCallbackStartPattern = /\b(?:this\.)?(?:[\w.$:]+\.)?(?:\$transaction|transaction|Transaction)\s*\(\s*(?:async\s*)?(?:function\s*)?\(?\s*\w*\s*\)?\s*(?:=>)?\s*\{/gu;
228140
228168
  var explicitDomainModelEvidencePattern = /@(?:Entity|Embeddable|MappedSuperclass|Column|PrimaryGeneratedColumn|ObjectType|Field)\b|\[(?:Table|Key|Required|Column|StringLength|MaxLength)\b[^\]]*\]|\b(?:BaseModel|SQLModel|declarative_base|Mapped\[|Column\s*\(|@dataclass\b|extends\s+Model\b|\$fillable\b|\$casts\b|gorm\.Model|`[^`]*(?:gorm|db):)/u;
228141
228169
  var domainFrameworkLeakPattern = /\b(?:ControllerBase|IActionResult|HttpContext|Microsoft\.AspNetCore|APIRouter|FastAPI|Request|Response|Depends|Illuminate\\Http|Illuminate\\Routing|Route::|http\.ResponseWriter|http\.Request|gin\.Context|express|Router|org\.springframework\.web|jakarta\.servlet|HttpServletRequest|@Controller|@RestController|@Get|@Post|DbContext|EntityManager|Session|Repository)\b/u;
@@ -229281,6 +229309,16 @@ function hasEntitySpecificServiceBusinessRules(entityNames, methods) {
229281
229309
  return [...entityAliases].some((entityName) => new RegExp(`\\b${entityName}\\b`, "u").test(method.file.content));
229282
229310
  });
229283
229311
  }
229312
+ function hasScatteredDomainLogicEvidence(method) {
229313
+ const code = maskNonCodeEvidence2(method.body);
229314
+ if (!decisionStructurePattern.test(code)) {
229315
+ return false;
229316
+ }
229317
+ if (hasRole(method.classification, "repository")) {
229318
+ return repositoryBusinessRulePattern.test(code);
229319
+ }
229320
+ return businessRulePattern.test(code);
229321
+ }
229284
229322
  function detectDomainFindings(files, classifications, methods, contract, astCache) {
229285
229323
  const findings = [];
229286
229324
  for (const file of files) {
@@ -229350,7 +229388,7 @@ function detectDomainFindings(files, classifications, methods, contract, astCach
229350
229388
  }));
229351
229389
  }
229352
229390
  }
229353
- if (isProductionBackendGovernanceScope(method.classification.scope) && (hasRole(method.classification, "controller") || hasRole(method.classification, "repository")) && businessRulePattern.test(method.body) && /\bif\s*\(|\bswitch\s*\(|\bthrow\s+new\b/u.test(method.body)) {
229391
+ if (isProductionBackendGovernanceScope(method.classification.scope) && (hasRole(method.classification, "controller") || hasRole(method.classification, "repository")) && hasScatteredDomainLogicEvidence(method)) {
229354
229392
  findings.push(createFinding3({
229355
229393
  id: RuleIds2.DOM_LOGIC_SCATTERED,
229356
229394
  message: `Business rule evidence appears in a ${hasRole(method.classification, "controller") ? "controller" : "repository"} method '${method.name}'.`,
@@ -229666,6 +229704,11 @@ function httpMethodFromDecorator(decorators, extension) {
229666
229704
  }
229667
229705
  return value;
229668
229706
  }
229707
+ function routeDecoratorOffsetInGroup(decorators, extension) {
229708
+ const pattern = extension === ".java" ? /@(?:GetMapping|PostMapping|PutMapping|PatchMapping|DeleteMapping|RequestMapping)\b/u : /@(?:Get|Post|Put|Patch|Delete)\b/u;
229709
+ const match = pattern.exec(decorators);
229710
+ return match?.index ?? 0;
229711
+ }
229669
229712
  function methodPathFromDecorators(decorators, extension) {
229670
229713
  const pattern = extension === ".java" ? /@(?:GetMapping|PostMapping|PutMapping|PatchMapping|DeleteMapping|RequestMapping)\s*(?:\(([^)]*)\))?/u : /@(?:Get|Post|Put|Patch|Delete)\s*(?:\(([^)]*)\))?/u;
229671
229714
  const match = pattern.exec(decorators);
@@ -229685,11 +229728,12 @@ function extractDecoratorEndpoints(file, contract) {
229685
229728
  const returnType = file.extension === ".java" ? match[3] ?? "" : match[3] ?? "";
229686
229729
  const openBrace = (match.index ?? 0) + match[0].lastIndexOf("{");
229687
229730
  const end = findMatchingBrace4(file.content, openBrace);
229731
+ const routeDecoratorOffset = routeDecoratorOffsetInGroup(decorators, file.extension);
229688
229732
  endpoints.push({
229689
229733
  httpMethod,
229690
229734
  path: joinEndpointPath(basePath, methodPathFromDecorators(decorators, file.extension)),
229691
229735
  name,
229692
- line: lineNumberForOffset4(file.content, match.index ?? 0),
229736
+ line: lineNumberForOffset4(file.content, (match.index ?? 0) + routeDecoratorOffset),
229693
229737
  decorators,
229694
229738
  signature: `${returnType} ${match[0]}`,
229695
229739
  body: file.content.slice(openBrace, end + 1),
@@ -229970,8 +230014,15 @@ function isStaticCollectionBackedEndpoint(endpoint) {
229970
230014
  }
229971
230015
  return isStaticCollectionExpression(stripStaticLiteralNoise(expression));
229972
230016
  }
230017
+ function isUnconditionallyTerminalThrowEndpoint(endpoint) {
230018
+ const body = endpoint.body.trim().replace(/^\{\s*/u, "").replace(/\s*\}$/u, "").trim();
230019
+ if (!/^throw\s+new\s+[A-Z]\w*(?:Exception|Error)\s*\([^;{}]*\)\s*;?$/u.test(body)) {
230020
+ return false;
230021
+ }
230022
+ return !/\b(?:if|for|while|switch|try|catch|return|await|yield)\b/u.test(body);
230023
+ }
229973
230024
  function detectPaginationFindings(endpoints, contractEndpoints) {
229974
- return endpoints.filter((endpoint) => isCollectionGet(endpoint) && !hasPaginationEvidence2(endpoint, contractEndpoints) && !isStaticCollectionBackedEndpoint(endpoint)).map((endpoint) => createFinding4({
230025
+ return endpoints.filter((endpoint) => isCollectionGet(endpoint) && !hasPaginationEvidence2(endpoint, contractEndpoints) && !isStaticCollectionBackedEndpoint(endpoint) && !isUnconditionallyTerminalThrowEndpoint(endpoint)).map((endpoint) => createFinding4({
229975
230026
  id: RuleIds3.MISSING_PAGINATION,
229976
230027
  message: `Collection endpoint ${endpoint.httpMethod.toUpperCase()} ${endpoint.path} lacks visible pagination evidence.`,
229977
230028
  endpoint
@@ -230731,12 +230782,14 @@ function extractTypeScriptSignals(file, astCache) {
230731
230782
  ts6.forEachChild(node, visit);
230732
230783
  };
230733
230784
  visit(sourceFile);
230734
- return { publishes, handlers: handlers2, publicEventNames: /* @__PURE__ */ new Set() };
230785
+ return { publishes, handlers: handlers2, publicEventNames: /* @__PURE__ */ new Set(), eventTypeParents: /* @__PURE__ */ new Map() };
230735
230786
  }
230736
230787
  function extractJavaSignals(file) {
230737
230788
  const publishes = [];
230738
230789
  const handlers2 = [];
230739
- for (const match of file.content.matchAll(/@EventListener\s*(?:\(\s*([A-Za-z]\w*Event)\.class\s*\))?/gu)) {
230790
+ for (const match of file.content.matchAll(
230791
+ /@EventListener\s*(?:\(\s*(?:(?:classes|value)\s*=\s*)?(?:\{\s*)?([A-Za-z_]\w*)\.class\s*(?:\})?\s*\))?/gu
230792
+ )) {
230740
230793
  const eventName = match[1] ?? "ApplicationEvent";
230741
230794
  handlers2.push({
230742
230795
  eventName,
@@ -230748,6 +230801,21 @@ function extractJavaSignals(file) {
230748
230801
  publishes: []
230749
230802
  });
230750
230803
  }
230804
+ for (const match of file.content.matchAll(/\b(?:class|interface)\s+\w+[^{;]*\bApplicationListener\s*<\s*([A-Za-z_]\w*)\s*>/gu)) {
230805
+ const eventName = match[1];
230806
+ if (!eventName) {
230807
+ continue;
230808
+ }
230809
+ handlers2.push({
230810
+ eventName,
230811
+ normalizedName: normalizeEventName(eventName),
230812
+ file,
230813
+ line: lineNumberForOffset5(file, match.index ?? 0),
230814
+ module: file.module,
230815
+ style: classifyEventStyle(eventName),
230816
+ publishes: []
230817
+ });
230818
+ }
230751
230819
  for (const match of file.content.matchAll(/(?:await\s+)?(?:\w+\.)?(?:publishEvent|publish|emit)\s*\(\s*(?:(new)\s+([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\s*\(|(["'])([A-Za-z][\w.]*Event?|[a-z][\w.]+)\3|([A-Z][A-Za-z0-9_]*Event)\b)/gu)) {
230752
230820
  const constructedType = match[2]?.split(".").at(-1);
230753
230821
  const eventName = constructedType ?? match[4] ?? match[5];
@@ -230765,7 +230833,23 @@ function extractJavaSignals(file) {
230765
230833
  assignedOrReturned: false
230766
230834
  });
230767
230835
  }
230768
- return { publishes, handlers: handlers2, publicEventNames: /* @__PURE__ */ new Set() };
230836
+ return { publishes, handlers: handlers2, publicEventNames: /* @__PURE__ */ new Set(), eventTypeParents: extractJavaEventTypeParents(file) };
230837
+ }
230838
+ function extractJavaEventTypeParents(file) {
230839
+ const parentsByType = /* @__PURE__ */ new Map();
230840
+ for (const match of file.content.matchAll(/\b(?:class|record)\s+([A-Za-z_]\w*)(?:\s*<[^>{}]+>)?\s+extends\s+([A-Za-z_]\w*)/gu)) {
230841
+ const child = match[1];
230842
+ const parent = match[2];
230843
+ if (!child || !parent || !child.endsWith("Event")) {
230844
+ continue;
230845
+ }
230846
+ const normalizedChild = normalizeEventName(child);
230847
+ const normalizedParent = normalizeEventName(parent);
230848
+ const parents = parentsByType.get(normalizedChild) ?? /* @__PURE__ */ new Set();
230849
+ parents.add(normalizedParent);
230850
+ parentsByType.set(normalizedChild, parents);
230851
+ }
230852
+ return parentsByType;
230769
230853
  }
230770
230854
  function extractPublishSignalsFromText(file, body, baseOffset, pattern) {
230771
230855
  const publishes = [];
@@ -230811,7 +230895,7 @@ function extractCsharpSignals(file) {
230811
230895
  publishes: extractPublishSignalsFromText(file, classBody, classOpenBrace >= 0 ? classOpenBrace : classStart, csharpPublishPattern)
230812
230896
  });
230813
230897
  }
230814
- return { publishes, handlers: handlers2, publicEventNames: /* @__PURE__ */ new Set() };
230898
+ return { publishes, handlers: handlers2, publicEventNames: /* @__PURE__ */ new Set(), eventTypeParents: /* @__PURE__ */ new Map() };
230815
230899
  }
230816
230900
  function extractPythonSignals(file) {
230817
230901
  const publishes = extractPublishSignalsFromText(file, file.content, 0, pythonPublishPattern);
@@ -230834,7 +230918,7 @@ function extractPythonSignals(file) {
230834
230918
  publishes: extractPublishSignalsFromText(file, block, blockStart, pythonPublishPattern)
230835
230919
  });
230836
230920
  }
230837
- return { publishes, handlers: handlers2, publicEventNames: /* @__PURE__ */ new Set() };
230921
+ return { publishes, handlers: handlers2, publicEventNames: /* @__PURE__ */ new Set(), eventTypeParents: /* @__PURE__ */ new Map() };
230838
230922
  }
230839
230923
  function extractPhpSignals(file) {
230840
230924
  const publishes = extractPublishSignalsFromText(file, file.content, 0, phpEventPublishPattern);
@@ -230859,7 +230943,7 @@ function extractPhpSignals(file) {
230859
230943
  publishes: extractPublishSignalsFromText(file, classBody, classOpenBrace >= 0 ? classOpenBrace : classStart, phpEventPublishPattern)
230860
230944
  });
230861
230945
  }
230862
- return { publishes, handlers: handlers2, publicEventNames: /* @__PURE__ */ new Set() };
230946
+ return { publishes, handlers: handlers2, publicEventNames: /* @__PURE__ */ new Set(), eventTypeParents: /* @__PURE__ */ new Map() };
230863
230947
  }
230864
230948
  function extractGoSignals(file) {
230865
230949
  const publishes = extractPublishSignalsFromText(file, file.content, 0, goPublishPattern);
@@ -230879,7 +230963,7 @@ function extractGoSignals(file) {
230879
230963
  publishes: []
230880
230964
  });
230881
230965
  }
230882
- return { publishes, handlers: handlers2, publicEventNames: /* @__PURE__ */ new Set() };
230966
+ return { publishes, handlers: handlers2, publicEventNames: /* @__PURE__ */ new Set(), eventTypeParents: /* @__PURE__ */ new Map() };
230883
230967
  }
230884
230968
  function isTypeScriptLikeExtension(extension) {
230885
230969
  return [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].includes(extension);
@@ -230979,6 +231063,7 @@ function collectPublicTypeScriptEventNames(files, contract, astCache) {
230979
231063
  function collectSignals(files, contract, astCache) {
230980
231064
  const publishes = [];
230981
231065
  const handlers2 = [];
231066
+ const eventTypeParents = /* @__PURE__ */ new Map();
230982
231067
  const publicEventNames = collectPublicTypeScriptEventNames(files, contract, astCache);
230983
231068
  for (const file of files) {
230984
231069
  let signals;
@@ -231004,14 +231089,41 @@ function collectSignals(files, contract, astCache) {
231004
231089
  }
231005
231090
  publishes.push(...signals.publishes);
231006
231091
  handlers2.push(...signals.handlers);
231092
+ for (const [eventName, parents] of signals.eventTypeParents) {
231093
+ const existing = eventTypeParents.get(eventName) ?? /* @__PURE__ */ new Set();
231094
+ for (const parent of parents) {
231095
+ existing.add(parent);
231096
+ }
231097
+ eventTypeParents.set(eventName, existing);
231098
+ }
231007
231099
  }
231008
- return { publishes, handlers: handlers2, publicEventNames };
231100
+ return { publishes, handlers: handlers2, publicEventNames, eventTypeParents };
231101
+ }
231102
+ function isHandledEventName(normalizedName, handledNames, eventTypeParents) {
231103
+ const queue = [normalizedName];
231104
+ const visited = /* @__PURE__ */ new Set();
231105
+ while (queue.length > 0) {
231106
+ const current = queue.shift();
231107
+ if (!current || visited.has(current)) {
231108
+ continue;
231109
+ }
231110
+ if (handledNames.has(current)) {
231111
+ return true;
231112
+ }
231113
+ visited.add(current);
231114
+ for (const parent of eventTypeParents.get(current) ?? []) {
231115
+ if (!visited.has(parent)) {
231116
+ queue.push(parent);
231117
+ }
231118
+ }
231119
+ }
231120
+ return false;
231009
231121
  }
231010
231122
  function buildEventFindings(signals) {
231011
231123
  const findings = [];
231012
231124
  const handledNames = new Set(signals.handlers.map((handler) => handler.normalizedName));
231013
231125
  for (const publish of signals.publishes) {
231014
- if (!handledNames.has(publish.normalizedName)) {
231126
+ if (!isHandledEventName(publish.normalizedName, handledNames, signals.eventTypeParents)) {
231015
231127
  if (signals.publicEventNames.has(publish.normalizedName)) {
231016
231128
  continue;
231017
231129
  }
@@ -236969,6 +237081,41 @@ function formatRuleLine(ruleId) {
236969
237081
  function formatFindingLine(finding) {
236970
237082
  return `${formatRuleLine(finding.id)} | ${finding.message}`;
236971
237083
  }
237084
+ function formatRegressionFindingLine(finding) {
237085
+ const parts = [`${finding.id} ${finding.severity}`];
237086
+ const scope = finding.module && finding.target ? `${finding.module} -> ${finding.target}` : finding.sourceComponentName && finding.targetComponentName ? `${finding.sourceComponentName} -> ${finding.targetComponentName}` : finding.module ?? finding.target ?? finding.table ?? finding.api ?? finding.filePath;
237087
+ if (scope) {
237088
+ parts.push(scope);
237089
+ }
237090
+ if (finding.sourceFile) {
237091
+ parts.push(
237092
+ finding.sourceLine !== void 0 ? `${finding.sourceFile}:${finding.sourceLine}` : finding.sourceFile
237093
+ );
237094
+ }
237095
+ parts.push(finding.message);
237096
+ return parts.join(" | ");
237097
+ }
237098
+ function renderRegressionFindingLines(findings, limit) {
237099
+ if (findings.length === 0) {
237100
+ return "- (none)";
237101
+ }
237102
+ const sorted = [...findings].sort((left, right) => {
237103
+ const idCompare = left.id.localeCompare(right.id);
237104
+ if (idCompare !== 0) {
237105
+ return idCompare;
237106
+ }
237107
+ const sourceCompare = (left.sourceFile ?? "").localeCompare(right.sourceFile ?? "");
237108
+ if (sourceCompare !== 0) {
237109
+ return sourceCompare;
237110
+ }
237111
+ return left.message.localeCompare(right.message);
237112
+ });
237113
+ const rendered = sorted.slice(0, limit).map((finding) => `- ${formatRegressionFindingLine(finding)}`);
237114
+ if (sorted.length > limit) {
237115
+ rendered.push(`- ... ${sorted.length - limit} more`);
237116
+ }
237117
+ return rendered.join("\n");
237118
+ }
236972
237119
  function parseFindingKind2(value) {
236973
237120
  return value === "violation" || value === "setup-gap" || value === "guidance" ? value : void 0;
236974
237121
  }
@@ -236993,7 +237140,7 @@ function renderArchitecturePrCommentMarkdown(input2) {
236993
237140
  findingKindByRule.set(finding.id, byRule);
236994
237141
  findingKindByRuleAndMessage.set(`${finding.id}::${finding.message}`, kind);
236995
237142
  }
236996
- const resolveKind2 = (entry) => {
237143
+ const resolveKind3 = (entry) => {
236997
237144
  const explicit = readFindingKind(entry);
236998
237145
  if (explicit) {
236999
237146
  return explicit;
@@ -237011,28 +237158,28 @@ function renderArchitecturePrCommentMarkdown(input2) {
237011
237158
  return "violation";
237012
237159
  };
237013
237160
  const newQualityViolationLines = sortUniqueLines(
237014
- (input2.driftComparison?.newViolations ?? []).filter((entry) => resolveKind2(entry) === "violation").map((entry) => formatRuleLine(entry.ruleId))
237161
+ (input2.driftComparison?.newViolations ?? []).filter((entry) => resolveKind3(entry) === "violation").map((entry) => formatRuleLine(entry.ruleId))
237015
237162
  );
237016
237163
  const newSetupGapLines = sortUniqueLines(
237017
- (input2.driftComparison?.newViolations ?? []).filter((entry) => resolveKind2(entry) === "setup-gap").map((entry) => formatRuleLine(entry.ruleId))
237164
+ (input2.driftComparison?.newViolations ?? []).filter((entry) => resolveKind3(entry) === "setup-gap").map((entry) => formatRuleLine(entry.ruleId))
237018
237165
  );
237019
237166
  const newGuidanceLines = sortUniqueLines(
237020
- (input2.driftComparison?.newViolations ?? []).filter((entry) => resolveKind2(entry) === "guidance").map((entry) => formatRuleLine(entry.ruleId))
237167
+ (input2.driftComparison?.newViolations ?? []).filter((entry) => resolveKind3(entry) === "guidance").map((entry) => formatRuleLine(entry.ruleId))
237021
237168
  );
237022
237169
  const resolvedViolationLines = sortUniqueLines(
237023
- (input2.driftComparison?.resolvedViolations ?? []).filter((entry) => resolveKind2(entry) === "violation").map((entry) => formatRuleLine(entry.ruleId))
237170
+ (input2.driftComparison?.resolvedViolations ?? []).filter((entry) => resolveKind3(entry) === "violation").map((entry) => formatRuleLine(entry.ruleId))
237024
237171
  );
237025
237172
  const resolvedSetupGapLines = sortUniqueLines(
237026
- (input2.driftComparison?.resolvedViolations ?? []).filter((entry) => resolveKind2(entry) === "setup-gap").map((entry) => formatRuleLine(entry.ruleId))
237173
+ (input2.driftComparison?.resolvedViolations ?? []).filter((entry) => resolveKind3(entry) === "setup-gap").map((entry) => formatRuleLine(entry.ruleId))
237027
237174
  );
237028
237175
  const activeQualityViolationLines = sortUniqueLines(
237029
- input2.summary.findings.filter((entry) => resolveKind2({ ruleId: entry.id, message: entry.message }) === "violation").map((entry) => formatFindingLine(entry))
237176
+ input2.summary.findings.filter((entry) => resolveKind3({ ruleId: entry.id, message: entry.message }) === "violation").map((entry) => formatFindingLine(entry))
237030
237177
  );
237031
237178
  const activeSetupGapLines = sortUniqueLines(
237032
- input2.summary.findings.filter((entry) => resolveKind2({ ruleId: entry.id, message: entry.message }) === "setup-gap").map((entry) => formatFindingLine(entry))
237179
+ input2.summary.findings.filter((entry) => resolveKind3({ ruleId: entry.id, message: entry.message }) === "setup-gap").map((entry) => formatFindingLine(entry))
237033
237180
  );
237034
237181
  const activeGuidanceLines = sortUniqueLines(
237035
- input2.summary.findings.filter((entry) => resolveKind2({ ruleId: entry.id, message: entry.message }) === "guidance").map((entry) => formatFindingLine(entry))
237182
+ input2.summary.findings.filter((entry) => resolveKind3({ ruleId: entry.id, message: entry.message }) === "guidance").map((entry) => formatFindingLine(entry))
237036
237183
  );
237037
237184
  const activeQualityViolationCount = activeQualityViolationLines.length;
237038
237185
  const activeSetupGapCount = activeSetupGapLines.length;
@@ -237168,6 +237315,37 @@ function renderArchitecturePrCommentMarkdown(input2) {
237168
237315
  lines.push(`Setup gaps: ${activeSetupGapCount}`);
237169
237316
  }
237170
237317
  lines.push(trendLine);
237318
+ const baselineComparison = input2.summary.baselineComparison;
237319
+ if (baselineComparison) {
237320
+ lines.push("");
237321
+ lines.push("Architecture Regression Summary");
237322
+ const comparisonAvailable = baselineComparison.baselineAvailable !== false && baselineComparison.status !== "NO_BASELINE";
237323
+ if (!comparisonAvailable) {
237324
+ lines.push("Architecture regression comparison is unavailable.");
237325
+ if (baselineComparison.summary.toLowerCase().includes("no baseline")) {
237326
+ lines.push("No governance baseline exists.");
237327
+ lines.push("Create or refresh the governance baseline with `archpilot baseline refresh`.");
237328
+ }
237329
+ lines.push(`Summary: ${baselineComparison.summary}`);
237330
+ } else {
237331
+ lines.push(`Introduced: ${baselineComparison.newlyIntroducedFindings.length}`);
237332
+ lines.push(`Resolved: ${baselineComparison.resolvedFindings.length}`);
237333
+ lines.push(`Existing: ${baselineComparison.unchangedFindings.length}`);
237334
+ if (baselineComparison.newlyIntroducedFindings.length === 0) {
237335
+ lines.push("");
237336
+ lines.push("No new architecture regressions detected.");
237337
+ } else {
237338
+ lines.push("");
237339
+ lines.push("New architecture regressions");
237340
+ lines.push(renderRegressionFindingLines(baselineComparison.newlyIntroducedFindings, 10));
237341
+ }
237342
+ if (baselineComparison.resolvedFindings.length > 0) {
237343
+ lines.push("");
237344
+ lines.push("Resolved architecture findings");
237345
+ lines.push(renderRegressionFindingLines(baselineComparison.resolvedFindings, 8));
237346
+ }
237347
+ }
237348
+ }
237171
237349
  if (input2.driftComparison?.hasPreviousSnapshot) {
237172
237350
  lines.push(
237173
237351
  `Previous score: ${input2.driftComparison.previousScore ?? "n/a"}${input2.driftComparison.scoreDelta !== void 0 ? ` (${input2.driftComparison.scoreDelta >= 0 ? "+" : ""}${input2.driftComparison.scoreDelta})` : ""}`
@@ -238332,6 +238510,190 @@ async function checkArchitectureBaseline(workspaceRoot) {
238332
238510
  // ../core/src/architectureGovernanceBaseline.ts
238333
238511
  var path42 = __toESM(require("node:path"));
238334
238512
  var import_node_fs36 = require("node:fs");
238513
+
238514
+ // ../core/src/findingRegressionComparison.ts
238515
+ function normalizeToken2(value) {
238516
+ return value.replace(/\\/g, "/").trim().toLowerCase();
238517
+ }
238518
+ function normalizeOptionalToken(value) {
238519
+ if (typeof value !== "string") {
238520
+ return void 0;
238521
+ }
238522
+ const normalized = normalizeToken2(value);
238523
+ return normalized.length > 0 ? normalized : void 0;
238524
+ }
238525
+ function resolveKind(input2) {
238526
+ if (input2.kind === "violation" || input2.kind === "setup-gap" || input2.kind === "guidance") {
238527
+ return input2.kind;
238528
+ }
238529
+ if (input2.classification === "violation" || input2.classification === "setup-gap" || input2.classification === "guidance") {
238530
+ return input2.classification;
238531
+ }
238532
+ if (input2.type === "violation" || input2.type === "setup-gap" || input2.type === "guidance") {
238533
+ return input2.type;
238534
+ }
238535
+ return "violation";
238536
+ }
238537
+ function appendPart(parts, label, value) {
238538
+ const normalized = normalizeOptionalToken(value);
238539
+ if (normalized) {
238540
+ parts.push(`${label}=${normalized}`);
238541
+ }
238542
+ }
238543
+ function normalizeDeductionIdentity(deductionKey) {
238544
+ const normalized = normalizeOptionalToken(deductionKey);
238545
+ if (!normalized) {
238546
+ return void 0;
238547
+ }
238548
+ if (normalized.includes("|")) {
238549
+ const parts2 = normalized.split("|");
238550
+ if (parts2[0] === "quality" || parts2[0] === "setup" || parts2[0] === "guidance") {
238551
+ parts2.shift();
238552
+ }
238553
+ if (parts2[0]?.startsWith("ap-")) {
238554
+ parts2.shift();
238555
+ }
238556
+ if (parts2.length >= 4 && /^\d+$/u.test(parts2[3] ?? "")) {
238557
+ return parts2.filter((_, index) => index !== 3).join("|");
238558
+ }
238559
+ return parts2.join("|");
238560
+ }
238561
+ const parts = normalized.split(":");
238562
+ if (parts[0] === "quality" || parts[0] === "setup" || parts[0] === "guidance") {
238563
+ parts.shift();
238564
+ }
238565
+ if (parts[0]?.startsWith("ap-")) {
238566
+ parts.shift();
238567
+ }
238568
+ return parts.join(":");
238569
+ }
238570
+ function buildSemanticRegressionIdentity(input2) {
238571
+ const parts = [`rule=${normalizeToken2(input2.id)}`, `kind=${resolveKind(input2)}`];
238572
+ appendPart(parts, "findingtype", input2.findingType);
238573
+ appendPart(parts, "module", input2.module);
238574
+ appendPart(parts, "target", input2.target);
238575
+ appendPart(parts, "table", input2.table);
238576
+ appendPart(parts, "api", input2.api);
238577
+ appendPart(parts, "filepath", input2.filePath);
238578
+ appendPart(parts, "sourcefile", input2.sourceFile);
238579
+ appendPart(parts, "dependencytype", input2.dependencyType);
238580
+ appendPart(parts, "sourcecomponentid", input2.sourceComponentId);
238581
+ appendPart(parts, "targetcomponentid", input2.targetComponentId);
238582
+ appendPart(parts, "importspecifier", input2.importSpecifier);
238583
+ appendPart(parts, "resolvedtargetpath", input2.resolvedTargetPath);
238584
+ if (input2.isExternal !== void 0) {
238585
+ parts.push(`external=${input2.isExternal ? "true" : "false"}`);
238586
+ }
238587
+ const deductionIdentity = normalizeDeductionIdentity(input2.deductionKey);
238588
+ if (deductionIdentity) {
238589
+ parts.push(`deduction=${deductionIdentity}`);
238590
+ }
238591
+ if (parts.length <= 2) {
238592
+ return void 0;
238593
+ }
238594
+ return parts.join("|");
238595
+ }
238596
+ function normalizeLegacyKey(key) {
238597
+ const normalized = normalizeOptionalToken(key);
238598
+ if (!normalized) {
238599
+ return void 0;
238600
+ }
238601
+ return `legacy=${normalized}`;
238602
+ }
238603
+ function buildFindingRegressionIdentity(input2) {
238604
+ const persisted = normalizeOptionalToken(input2.regressionIdentity);
238605
+ if (persisted) {
238606
+ return { identity: persisted, source: "persisted" };
238607
+ }
238608
+ const semantic = buildSemanticRegressionIdentity(input2);
238609
+ if (semantic) {
238610
+ return { identity: semantic, source: "semantic" };
238611
+ }
238612
+ const legacy = normalizeLegacyKey(input2.key);
238613
+ if (legacy) {
238614
+ return { identity: legacy, source: "legacy-key" };
238615
+ }
238616
+ return {
238617
+ identity: [
238618
+ `rule=${normalizeToken2(input2.id)}`,
238619
+ `kind=${resolveKind(input2)}`,
238620
+ `message=${normalizeToken2(input2.message ?? "")}`
238621
+ ].join("|"),
238622
+ source: "legacy-key"
238623
+ };
238624
+ }
238625
+ function toComparableFindingRegression(input2) {
238626
+ const identity = buildFindingRegressionIdentity(input2);
238627
+ return {
238628
+ identity: identity.identity,
238629
+ id: input2.id,
238630
+ kind: resolveKind(input2),
238631
+ ...input2.severity ? { severity: input2.severity } : {},
238632
+ ...input2.message ? { message: input2.message } : {},
238633
+ ...input2.sourceFile ? { sourceFile: input2.sourceFile.replace(/\\/g, "/") } : {},
238634
+ ...input2.sourceLine !== void 0 ? { sourceLine: input2.sourceLine } : {},
238635
+ ...input2.sourceColumn !== void 0 ? { sourceColumn: input2.sourceColumn } : {},
238636
+ ...input2.module ? { module: input2.module } : {},
238637
+ ...input2.target ? { target: input2.target } : {},
238638
+ ...input2.findingType ? { findingType: input2.findingType } : {},
238639
+ regressionIdentitySource: identity.source
238640
+ };
238641
+ }
238642
+ function compareComparableFindings(left, right) {
238643
+ const identityCompare = left.identity.localeCompare(right.identity);
238644
+ if (identityCompare !== 0) {
238645
+ return identityCompare;
238646
+ }
238647
+ const idCompare = left.id.localeCompare(right.id);
238648
+ if (idCompare !== 0) {
238649
+ return idCompare;
238650
+ }
238651
+ const sourceCompare = (left.sourceFile ?? "").localeCompare(right.sourceFile ?? "");
238652
+ if (sourceCompare !== 0) {
238653
+ return sourceCompare;
238654
+ }
238655
+ const leftLine = left.sourceLine ?? Number.MAX_SAFE_INTEGER;
238656
+ const rightLine = right.sourceLine ?? Number.MAX_SAFE_INTEGER;
238657
+ if (leftLine !== rightLine) {
238658
+ return leftLine - rightLine;
238659
+ }
238660
+ const leftColumn = left.sourceColumn ?? Number.MAX_SAFE_INTEGER;
238661
+ const rightColumn = right.sourceColumn ?? Number.MAX_SAFE_INTEGER;
238662
+ if (leftColumn !== rightColumn) {
238663
+ return leftColumn - rightColumn;
238664
+ }
238665
+ const severityCompare = (left.severity ?? "").localeCompare(right.severity ?? "");
238666
+ if (severityCompare !== 0) {
238667
+ return severityCompare;
238668
+ }
238669
+ return (left.message ?? "").localeCompare(right.message ?? "");
238670
+ }
238671
+ function dedupeByIdentity(findings, toComparable) {
238672
+ const map = /* @__PURE__ */ new Map();
238673
+ for (const finding of findings) {
238674
+ const comparable = toComparable(finding);
238675
+ const existing = map.get(comparable.identity);
238676
+ if (!existing || compareComparableFindings(comparable, existing.comparable) < 0) {
238677
+ map.set(comparable.identity, { finding, comparable });
238678
+ }
238679
+ }
238680
+ return [...map.values()].sort(
238681
+ (left, right) => compareComparableFindings(left.comparable, right.comparable)
238682
+ );
238683
+ }
238684
+ function compareFindingRegressions(reference, current, options) {
238685
+ const referenceEntries = dedupeByIdentity(reference, options.toReferenceComparable);
238686
+ const currentEntries = dedupeByIdentity(current, options.toCurrentComparable);
238687
+ const referenceIdentities = new Set(referenceEntries.map((entry) => entry.comparable.identity));
238688
+ const currentIdentities = new Set(currentEntries.map((entry) => entry.comparable.identity));
238689
+ return {
238690
+ introduced: currentEntries.filter((entry) => !referenceIdentities.has(entry.comparable.identity)).map((entry) => entry.finding),
238691
+ existing: currentEntries.filter((entry) => referenceIdentities.has(entry.comparable.identity)).map((entry) => entry.finding),
238692
+ resolved: referenceEntries.filter((entry) => !currentIdentities.has(entry.comparable.identity)).map((entry) => entry.finding)
238693
+ };
238694
+ }
238695
+
238696
+ // ../core/src/architectureGovernanceBaseline.ts
238335
238697
  function sortUnique9(values) {
238336
238698
  return [...new Set(values)].sort((left, right) => left.localeCompare(right));
238337
238699
  }
@@ -238348,13 +238710,51 @@ function baselineFindingKey(entry) {
238348
238710
  ].join("|");
238349
238711
  }
238350
238712
  function toBaselineFinding(entry) {
238713
+ const regressionIdentity = entry.regressionIdentity ?? buildFindingRegressionIdentity({
238714
+ id: entry.id,
238715
+ kind: entry.kind,
238716
+ severity: entry.severity,
238717
+ message: entry.message,
238718
+ sourceFile: entry.sourceFile,
238719
+ findingType: entry.findingType,
238720
+ module: entry.module,
238721
+ target: entry.target,
238722
+ filePath: entry.filePath,
238723
+ table: entry.table,
238724
+ api: entry.api,
238725
+ dependencyType: entry.dependencyType,
238726
+ sourceComponentId: entry.sourceComponentId,
238727
+ sourceComponentName: entry.sourceComponentName,
238728
+ targetComponentId: entry.targetComponentId,
238729
+ targetComponentName: entry.targetComponentName,
238730
+ importSpecifier: entry.importSpecifier,
238731
+ resolvedTargetPath: entry.resolvedTargetPath,
238732
+ isExternal: entry.isExternal
238733
+ }).identity;
238351
238734
  return {
238352
238735
  key: baselineFindingKey(entry),
238736
+ regressionIdentity,
238353
238737
  id: entry.id,
238354
238738
  severity: entry.severity,
238355
238739
  kind: entry.kind,
238356
238740
  message: entry.message,
238357
- ...entry.sourceFile ? { sourceFile: normalizePath18(entry.sourceFile) } : {}
238741
+ ...entry.sourceFile ? { sourceFile: normalizePath18(entry.sourceFile) } : {},
238742
+ ...typeof entry.sourceLine === "number" && Number.isFinite(entry.sourceLine) ? { sourceLine: Math.max(1, Math.floor(entry.sourceLine)) } : {},
238743
+ ...typeof entry.sourceColumn === "number" && Number.isFinite(entry.sourceColumn) ? { sourceColumn: Math.max(1, Math.floor(entry.sourceColumn)) } : {},
238744
+ ...entry.findingType ? { findingType: entry.findingType } : {},
238745
+ ...entry.module ? { module: entry.module } : {},
238746
+ ...entry.target ? { target: entry.target } : {},
238747
+ ...entry.filePath ? { filePath: normalizePath18(entry.filePath) } : {},
238748
+ ...entry.table ? { table: entry.table } : {},
238749
+ ...entry.api ? { api: entry.api } : {},
238750
+ ...entry.dependencyType ? { dependencyType: entry.dependencyType } : {},
238751
+ ...entry.sourceComponentId ? { sourceComponentId: entry.sourceComponentId } : {},
238752
+ ...entry.sourceComponentName ? { sourceComponentName: entry.sourceComponentName } : {},
238753
+ ...entry.targetComponentId !== void 0 ? { targetComponentId: entry.targetComponentId } : {},
238754
+ ...entry.targetComponentName !== void 0 ? { targetComponentName: entry.targetComponentName } : {},
238755
+ ...entry.importSpecifier ? { importSpecifier: entry.importSpecifier } : {},
238756
+ ...entry.resolvedTargetPath !== void 0 ? { resolvedTargetPath: entry.resolvedTargetPath } : {},
238757
+ ...entry.isExternal !== void 0 ? { isExternal: entry.isExternal } : {}
238358
238758
  };
238359
238759
  }
238360
238760
  function compareFindings(left, right) {
@@ -238390,7 +238790,28 @@ function parseSummaryArtifact(value) {
238390
238790
  severity: entry.severity,
238391
238791
  kind: entry.kind,
238392
238792
  message: entry.message,
238393
- ...typeof entry.sourceFile === "string" ? { sourceFile: entry.sourceFile } : {}
238793
+ ...typeof entry.regressionIdentity === "string" ? { regressionIdentity: entry.regressionIdentity } : {},
238794
+ ...typeof entry.sourceFile === "string" ? { sourceFile: entry.sourceFile } : {},
238795
+ ...typeof entry.sourceLine === "number" && Number.isFinite(entry.sourceLine) ? { sourceLine: entry.sourceLine } : {},
238796
+ ...typeof entry.sourceColumn === "number" && Number.isFinite(entry.sourceColumn) ? { sourceColumn: entry.sourceColumn } : {},
238797
+ ...typeof entry.findingType === "string" ? {
238798
+ findingType: entry.findingType
238799
+ } : {},
238800
+ ...typeof entry.module === "string" ? { module: entry.module } : {},
238801
+ ...typeof entry.target === "string" ? { target: entry.target } : {},
238802
+ ...typeof entry.filePath === "string" ? { filePath: entry.filePath } : {},
238803
+ ...typeof entry.table === "string" ? { table: entry.table } : {},
238804
+ ...typeof entry.api === "string" ? { api: entry.api } : {},
238805
+ ...typeof entry.dependencyType === "string" ? {
238806
+ dependencyType: entry.dependencyType
238807
+ } : {},
238808
+ ...typeof entry.sourceComponentId === "string" ? { sourceComponentId: entry.sourceComponentId } : {},
238809
+ ...typeof entry.sourceComponentName === "string" ? { sourceComponentName: entry.sourceComponentName } : {},
238810
+ ...typeof entry.targetComponentId === "string" || entry.targetComponentId === null ? { targetComponentId: entry.targetComponentId } : {},
238811
+ ...typeof entry.targetComponentName === "string" || entry.targetComponentName === null ? { targetComponentName: entry.targetComponentName } : {},
238812
+ ...typeof entry.importSpecifier === "string" ? { importSpecifier: entry.importSpecifier } : {},
238813
+ ...typeof entry.resolvedTargetPath === "string" || entry.resolvedTargetPath === null ? { resolvedTargetPath: entry.resolvedTargetPath } : {},
238814
+ ...typeof entry.isExternal === "boolean" ? { isExternal: entry.isExternal } : {}
238394
238815
  }));
238395
238816
  const suppressionRaw = typed.suppressionSummary && typeof typed.suppressionSummary === "object" ? typed.suppressionSummary : void 0;
238396
238817
  const suppressionSummary = suppressionRaw && typeof suppressionRaw.configuredSuppressions === "number" && typeof suppressionRaw.activeSuppressions === "number" && typeof suppressionRaw.expiredSuppressions === "number" && typeof suppressionRaw.unusedSuppressions === "number" && typeof suppressionRaw.findingsSuppressed === "number" ? {
@@ -238459,6 +238880,57 @@ function buildBaselineFromSummaryArtifact(artifact, nowUtcIso2) {
238459
238880
  ...artifact.impactAnalysis ? { impactAnalysis: artifact.impactAnalysis } : {}
238460
238881
  };
238461
238882
  }
238883
+ function buildGovernanceBaselineFromReviewSummary(summary, nowUtcIso2) {
238884
+ return {
238885
+ reportVersion: 1,
238886
+ projectName: summary.projectName,
238887
+ capturedAtUtc: nowUtcIso2 ?? (/* @__PURE__ */ new Date()).toISOString(),
238888
+ sourceSummaryGeneratedAtUtc: summary.generatedAtUtc,
238889
+ healthScore: summary.healthScore,
238890
+ readinessScore: summary.readinessScore,
238891
+ categoryScores: summary.categoryScores,
238892
+ readinessCategoryScores: summary.readinessCategoryScores,
238893
+ counts: {
238894
+ errors: summary.counts.errors,
238895
+ warnings: summary.counts.warnings,
238896
+ info: summary.counts.info,
238897
+ passed: summary.counts.passed
238898
+ },
238899
+ findingCount: summary.findings.length,
238900
+ remainingViolationsCount: summary.findings.filter((finding) => finding.kind === "violation").length,
238901
+ findings: summary.findings.map(
238902
+ (entry) => toBaselineFinding({
238903
+ id: entry.id,
238904
+ severity: entry.severity,
238905
+ kind: entry.kind,
238906
+ message: entry.message,
238907
+ ...entry.sourceFile ? { sourceFile: entry.sourceFile } : {},
238908
+ ...entry.sourceLine !== void 0 ? { sourceLine: entry.sourceLine } : {},
238909
+ ...entry.sourceColumn !== void 0 ? { sourceColumn: entry.sourceColumn } : {},
238910
+ ...entry.findingType ? { findingType: entry.findingType } : {},
238911
+ ...entry.module ? { module: entry.module } : {},
238912
+ ...entry.target ? { target: entry.target } : {},
238913
+ ...entry.filePath ? { filePath: entry.filePath } : {},
238914
+ ...entry.table ? { table: entry.table } : {},
238915
+ ...entry.api ? { api: entry.api } : {},
238916
+ ...entry.dependencyType ? { dependencyType: entry.dependencyType } : {},
238917
+ ...entry.sourceComponentId ? { sourceComponentId: entry.sourceComponentId } : {},
238918
+ ...entry.sourceComponentName ? { sourceComponentName: entry.sourceComponentName } : {},
238919
+ ...entry.targetComponentId !== void 0 ? { targetComponentId: entry.targetComponentId } : {},
238920
+ ...entry.targetComponentName !== void 0 ? { targetComponentName: entry.targetComponentName } : {},
238921
+ ...entry.importSpecifier ? { importSpecifier: entry.importSpecifier } : {},
238922
+ ...entry.resolvedTargetPath !== void 0 ? { resolvedTargetPath: entry.resolvedTargetPath } : {},
238923
+ ...entry.isExternal !== void 0 ? { isExternal: entry.isExternal } : {}
238924
+ })
238925
+ ).sort(compareFindings),
238926
+ changeRisk: {
238927
+ riskLevel: summary.changeRisk.riskLevel,
238928
+ score: summary.changeRisk.score,
238929
+ summary: summary.changeRisk.summary
238930
+ },
238931
+ ...summary.impactAnalysis ? { impactAnalysis: summary.impactAnalysis } : {}
238932
+ };
238933
+ }
238462
238934
  function getGovernanceBaselinePath(workspaceRoot) {
238463
238935
  return path42.join(workspaceRoot, ".archpilot", "baseline", "baseline.json");
238464
238936
  }
@@ -238542,13 +239014,43 @@ async function readGovernanceBaseline(workspaceRoot) {
238542
239014
  }
238543
239015
  }
238544
239016
  function compareFindingsByKey(current, baseline) {
238545
- const currentMap = new Map(current.map((entry) => [entry.key, entry]));
238546
- const baselineMap = new Map(baseline.map((entry) => [entry.key, entry]));
238547
- const newlyIntroducedFindings = [...currentMap.entries()].filter(([key]) => !baselineMap.has(key)).map(([, entry]) => entry).sort(compareFindings);
238548
- const resolvedFindings = [...baselineMap.entries()].filter(([key]) => !currentMap.has(key)).map(([, entry]) => entry).sort(compareFindings);
238549
- const unchangedFindings = [...currentMap.entries()].filter(([key]) => baselineMap.has(key)).map(([, entry]) => entry).sort(compareFindings);
239017
+ const comparison = compareFindingRegressions(baseline, current, {
239018
+ toReferenceComparable: toComparableBaselineFinding,
239019
+ toCurrentComparable: toComparableBaselineFinding
239020
+ });
239021
+ const newlyIntroducedFindings = comparison.introduced.sort(compareFindings);
239022
+ const resolvedFindings = comparison.resolved.sort(compareFindings);
239023
+ const unchangedFindings = comparison.existing.sort(compareFindings);
238550
239024
  return { newlyIntroducedFindings, resolvedFindings, unchangedFindings };
238551
239025
  }
239026
+ function toRegressionInput(entry) {
239027
+ return {
239028
+ id: entry.id,
239029
+ kind: entry.kind,
239030
+ severity: entry.severity,
239031
+ message: entry.message,
239032
+ regressionIdentity: entry.regressionIdentity,
239033
+ key: entry.key,
239034
+ sourceFile: entry.sourceFile,
239035
+ findingType: entry.findingType,
239036
+ module: entry.module,
239037
+ target: entry.target,
239038
+ filePath: entry.filePath,
239039
+ table: entry.table,
239040
+ api: entry.api,
239041
+ dependencyType: entry.dependencyType,
239042
+ sourceComponentId: entry.sourceComponentId,
239043
+ sourceComponentName: entry.sourceComponentName,
239044
+ targetComponentId: entry.targetComponentId,
239045
+ targetComponentName: entry.targetComponentName,
239046
+ importSpecifier: entry.importSpecifier,
239047
+ resolvedTargetPath: entry.resolvedTargetPath,
239048
+ isExternal: entry.isExternal
239049
+ };
239050
+ }
239051
+ function toComparableBaselineFinding(entry) {
239052
+ return toComparableFindingRegression(toRegressionInput(entry));
239053
+ }
238552
239054
  function compareWithGovernanceBaseline(currentSummary, baselineLoad) {
238553
239055
  const currentFindings = currentSummary.findings.map(
238554
239056
  (entry) => toBaselineFinding({
@@ -238556,7 +239058,23 @@ function compareWithGovernanceBaseline(currentSummary, baselineLoad) {
238556
239058
  severity: entry.severity,
238557
239059
  kind: entry.kind,
238558
239060
  message: entry.message,
238559
- ...entry.sourceFile ? { sourceFile: entry.sourceFile } : {}
239061
+ ...entry.sourceFile ? { sourceFile: entry.sourceFile } : {},
239062
+ ...entry.sourceLine !== void 0 ? { sourceLine: entry.sourceLine } : {},
239063
+ ...entry.sourceColumn !== void 0 ? { sourceColumn: entry.sourceColumn } : {},
239064
+ ...entry.findingType ? { findingType: entry.findingType } : {},
239065
+ ...entry.module ? { module: entry.module } : {},
239066
+ ...entry.target ? { target: entry.target } : {},
239067
+ ...entry.filePath ? { filePath: entry.filePath } : {},
239068
+ ...entry.table ? { table: entry.table } : {},
239069
+ ...entry.api ? { api: entry.api } : {},
239070
+ ...entry.dependencyType ? { dependencyType: entry.dependencyType } : {},
239071
+ ...entry.sourceComponentId ? { sourceComponentId: entry.sourceComponentId } : {},
239072
+ ...entry.sourceComponentName ? { sourceComponentName: entry.sourceComponentName } : {},
239073
+ ...entry.targetComponentId !== void 0 ? { targetComponentId: entry.targetComponentId } : {},
239074
+ ...entry.targetComponentName !== void 0 ? { targetComponentName: entry.targetComponentName } : {},
239075
+ ...entry.importSpecifier ? { importSpecifier: entry.importSpecifier } : {},
239076
+ ...entry.resolvedTargetPath !== void 0 ? { resolvedTargetPath: entry.resolvedTargetPath } : {},
239077
+ ...entry.isExternal !== void 0 ? { isExternal: entry.isExternal } : {}
238560
239078
  })
238561
239079
  ).sort(compareFindings);
238562
239080
  const currentRemainingViolations = currentSummary.findings.filter(
@@ -238717,6 +239235,43 @@ function renderList(values) {
238717
239235
  }
238718
239236
  return values.map((value) => `- ${value}`).join("\n");
238719
239237
  }
239238
+ function formatRegressionFinding(finding) {
239239
+ const pieces = [`[${finding.id}]`, `(${finding.severity})`];
239240
+ const scope = finding.module && finding.target ? `${finding.module} -> ${finding.target}` : finding.sourceComponentName && finding.targetComponentName ? `${finding.sourceComponentName} -> ${finding.targetComponentName}` : finding.module ?? finding.target ?? finding.table ?? finding.api ?? finding.filePath;
239241
+ if (scope) {
239242
+ pieces.push(scope);
239243
+ }
239244
+ if (finding.sourceFile) {
239245
+ pieces.push(
239246
+ finding.sourceLine !== void 0 ? `${finding.sourceFile}:${finding.sourceLine}` : finding.sourceFile
239247
+ );
239248
+ }
239249
+ if (finding.message) {
239250
+ pieces.push(finding.message);
239251
+ }
239252
+ return pieces.join(" | ");
239253
+ }
239254
+ function renderRegressionFindingList(findings, limit) {
239255
+ if (findings.length === 0) {
239256
+ return "- (none)";
239257
+ }
239258
+ const sorted = [...findings].sort((left, right) => {
239259
+ const idCompare = left.id.localeCompare(right.id);
239260
+ if (idCompare !== 0) {
239261
+ return idCompare;
239262
+ }
239263
+ const sourceCompare = (left.sourceFile ?? "").localeCompare(right.sourceFile ?? "");
239264
+ if (sourceCompare !== 0) {
239265
+ return sourceCompare;
239266
+ }
239267
+ return (left.message ?? "").localeCompare(right.message ?? "");
239268
+ });
239269
+ const rendered = sorted.slice(0, limit).map((finding) => `- ${formatRegressionFinding(finding)}`);
239270
+ if (sorted.length > limit) {
239271
+ rendered.push(`- ... ${sorted.length - limit} more`);
239272
+ }
239273
+ return rendered.join("\n");
239274
+ }
238720
239275
  function renderStatusLine(input2) {
238721
239276
  const validationHasIssues = input2.validation.kind === "ok" && (input2.validation.data.counts.errors > 0 || input2.validation.data.counts.warnings > 0 || input2.validation.data.counts.info > 0);
238722
239277
  const driftHasChanges = input2.drift.kind === "ok" && (input2.drift.data.summaryCounts.totalChanges ?? input2.drift.data.summaryCounts.modulesAdded + input2.drift.data.summaryCounts.modulesRemoved + input2.drift.data.summaryCounts.dependenciesAdded + input2.drift.data.summaryCounts.dependenciesRemoved + input2.drift.data.summaryCounts.cyclesAdded + input2.drift.data.summaryCounts.cyclesRemoved) > 0;
@@ -238866,14 +239421,43 @@ function renderArchitecturePrReviewMarkdown(input2) {
238866
239421
  }
238867
239422
  if (input2.validation.kind === "ok" && input2.validation.data.baselineComparison) {
238868
239423
  const baseline = input2.validation.data.baselineComparison;
238869
- lines.push("## Baseline Comparison");
238870
- lines.push(`- Baseline status: ${baseline.status}`);
238871
- lines.push(`- Health score delta: ${baseline.deltas.healthScoreDelta}`);
238872
- lines.push(`- Readiness score delta: ${baseline.deltas.readinessScoreDelta}`);
238873
- lines.push(`- New findings: ${baseline.newlyIntroducedFindings.length}`);
238874
- lines.push(`- Resolved findings: ${baseline.resolvedFindings.length}`);
238875
- lines.push(`- Summary: ${baseline.summary}`);
238876
- lines.push("");
239424
+ const comparisonAvailable = baseline.baselineAvailable !== false && baseline.status !== "NO_BASELINE";
239425
+ lines.push("## Architecture Regression Summary");
239426
+ if (!comparisonAvailable) {
239427
+ lines.push("- Architecture regression comparison is unavailable.");
239428
+ if (baseline.summary.toLowerCase().includes("no baseline")) {
239429
+ lines.push("- No governance baseline exists.");
239430
+ lines.push("- Create or refresh the governance baseline with `archpilot baseline refresh`.");
239431
+ }
239432
+ lines.push(`- Summary: ${baseline.summary}`);
239433
+ lines.push("");
239434
+ } else {
239435
+ const introducedCount = baseline.newlyIntroducedFindings.length;
239436
+ const resolvedCount = baseline.resolvedFindings.length;
239437
+ const existingCount = baseline.unchangedFindings?.length ?? 0;
239438
+ lines.push("| | Count |");
239439
+ lines.push("| --- | ---: |");
239440
+ lines.push(`| Introduced | ${introducedCount} |`);
239441
+ lines.push(`| Resolved | ${resolvedCount} |`);
239442
+ lines.push(`| Existing | ${existingCount} |`);
239443
+ lines.push("");
239444
+ lines.push(
239445
+ `Existing means current findings already represented by the accepted governance baseline.`
239446
+ );
239447
+ lines.push("");
239448
+ if (introducedCount === 0) {
239449
+ lines.push("No new architecture regressions detected.");
239450
+ } else {
239451
+ lines.push("### New architecture regressions");
239452
+ lines.push(renderRegressionFindingList(baseline.newlyIntroducedFindings, 10));
239453
+ }
239454
+ if (resolvedCount > 0) {
239455
+ lines.push("");
239456
+ lines.push("### Resolved architecture findings");
239457
+ lines.push(renderRegressionFindingList(baseline.resolvedFindings, 8));
239458
+ }
239459
+ lines.push("");
239460
+ }
238877
239461
  }
238878
239462
  if (input2.validation.kind === "ok" && input2.validation.data.risk) {
238879
239463
  const governanceRisk = input2.validation.data.risk;
@@ -238964,23 +239548,32 @@ function renderArchitecturePrReviewMarkdown(input2) {
238964
239548
  }
238965
239549
  }
238966
239550
  if (input2.validation.kind === "ok") {
238967
- const topFindings = [...input2.validation.data.findings].sort((left, right) => {
238968
- const severityCompare = severityRank(left.severity) - severityRank(right.severity);
238969
- if (severityCompare !== 0) {
238970
- return severityCompare;
238971
- }
238972
- const idCompare = left.id.localeCompare(right.id);
238973
- if (idCompare !== 0) {
238974
- return idCompare;
238975
- }
238976
- return left.message.localeCompare(right.message);
238977
- }).slice(0, 8);
238978
239551
  lines.push("## Validation Findings");
238979
- lines.push(
238980
- topFindings.length === 0 ? "- (none)" : topFindings.map(
238981
- (finding) => `- [${finding.id}] (${finding.severity}) ${finding.title} | ${finding.message}`
238982
- ).join("\n")
238983
- );
239552
+ const baseline = input2.validation.data.baselineComparison;
239553
+ const comparisonAvailable = baseline && baseline.baselineAvailable !== false && baseline.status !== "NO_BASELINE";
239554
+ if (comparisonAvailable) {
239555
+ lines.push(`- Current canonical findings: ${input2.validation.data.findings.length}`);
239556
+ lines.push(
239557
+ "- Regression details above prioritize introduced and resolved findings; complete current findings remain in the validation artifacts."
239558
+ );
239559
+ } else {
239560
+ const topFindings = [...input2.validation.data.findings].sort((left, right) => {
239561
+ const severityCompare = severityRank(left.severity) - severityRank(right.severity);
239562
+ if (severityCompare !== 0) {
239563
+ return severityCompare;
239564
+ }
239565
+ const idCompare = left.id.localeCompare(right.id);
239566
+ if (idCompare !== 0) {
239567
+ return idCompare;
239568
+ }
239569
+ return left.message.localeCompare(right.message);
239570
+ }).slice(0, 8);
239571
+ lines.push(
239572
+ topFindings.length === 0 ? "- (none)" : topFindings.map(
239573
+ (finding) => `- [${finding.id}] (${finding.severity}) ${finding.title} | ${finding.message}`
239574
+ ).join("\n")
239575
+ );
239576
+ }
238984
239577
  lines.push("");
238985
239578
  }
238986
239579
  if (input2.drift.kind === "ok") {
@@ -239107,7 +239700,7 @@ function toAnnotatableFinding(workspaceRoot, finding) {
239107
239700
  if (finding.severity !== "error" && finding.severity !== "warning") {
239108
239701
  return void 0;
239109
239702
  }
239110
- if (typeof finding.title !== "string" || typeof finding.message !== "string") {
239703
+ if (typeof finding.message !== "string") {
239111
239704
  return void 0;
239112
239705
  }
239113
239706
  if (typeof finding.sourceFile !== "string") {
@@ -239124,7 +239717,7 @@ function toAnnotatableFinding(workspaceRoot, finding) {
239124
239717
  const column = typeof finding.sourceColumn === "number" && Number.isFinite(finding.sourceColumn) ? Math.max(1, Math.floor(finding.sourceColumn)) : void 0;
239125
239718
  return {
239126
239719
  id: finding.id,
239127
- title: finding.title,
239720
+ title: typeof finding.title === "string" ? finding.title : finding.id,
239128
239721
  message: finding.message,
239129
239722
  severity: finding.severity,
239130
239723
  file,
@@ -239244,9 +239837,47 @@ async function loadFromSummaryReport(workspaceRoot, maxAgeMinutes) {
239244
239837
  findings: findings.filter((entry) => !!entry && typeof entry === "object")
239245
239838
  };
239246
239839
  }
239840
+ async function loadFromBaselineComparisonReport(workspaceRoot, maxAgeMinutes) {
239841
+ const comparisonPath = path43.join(
239842
+ workspaceRoot,
239843
+ ".archpilot",
239844
+ "reports",
239845
+ "baseline-comparison.json"
239846
+ );
239847
+ let raw;
239848
+ try {
239849
+ raw = await import_node_fs37.promises.readFile(comparisonPath, { encoding: "utf8" });
239850
+ } catch (error) {
239851
+ if (error.code !== "ENOENT") {
239852
+ console.log("ArchPilot inline annotations: unable to read baseline-comparison.json.");
239853
+ }
239854
+ return void 0;
239855
+ }
239856
+ if (!await isFreshArtifact(comparisonPath, maxAgeMinutes)) {
239857
+ console.log(
239858
+ `ArchPilot inline annotations: baseline-comparison.json is stale (older than ${maxAgeMinutes} minutes).`
239859
+ );
239860
+ return void 0;
239861
+ }
239862
+ let parsed;
239863
+ try {
239864
+ parsed = JSON.parse(raw);
239865
+ } catch {
239866
+ console.log("ArchPilot inline annotations: baseline-comparison.json is invalid JSON.");
239867
+ return void 0;
239868
+ }
239869
+ if (parsed.baselineAvailable !== true) {
239870
+ return void 0;
239871
+ }
239872
+ const findings = Array.isArray(parsed.newlyIntroducedFindings) ? parsed.newlyIntroducedFindings : [];
239873
+ return {
239874
+ sourceLabel: ".archpilot/reports/baseline-comparison.json",
239875
+ findings: findings.filter((entry) => !!entry && typeof entry === "object")
239876
+ };
239877
+ }
239247
239878
  async function emitGithubInlineArchitectureAnnotations(workspaceRoot) {
239248
239879
  const maxAgeMinutes = readMaxArtifactAgeMinutes();
239249
- const loaded = await loadFromValidationStatus(workspaceRoot, maxAgeMinutes) ?? await loadFromSummaryReport(workspaceRoot, maxAgeMinutes);
239880
+ const loaded = await loadFromBaselineComparisonReport(workspaceRoot, maxAgeMinutes) ?? await loadFromValidationStatus(workspaceRoot, maxAgeMinutes) ?? await loadFromSummaryReport(workspaceRoot, maxAgeMinutes);
239250
239881
  if (!loaded) {
239251
239882
  const now = formatUtcDate(/* @__PURE__ */ new Date());
239252
239883
  console.log(
@@ -243013,8 +243644,6 @@ var ignoredRootPathPrefixes = [
243013
243644
  "static",
243014
243645
  "assets",
243015
243646
  "vendor",
243016
- "plugins",
243017
- "plugin",
243018
243647
  "themes",
243019
243648
  "theme",
243020
243649
  "generated",
@@ -243037,8 +243666,12 @@ function pushEvidence(evidence, message) {
243037
243666
  function safeReadDirectoryEntries(directoryPath) {
243038
243667
  try {
243039
243668
  return fs52.readdirSync(directoryPath, { withFileTypes: true });
243040
- } catch {
243041
- return [];
243669
+ } catch (error) {
243670
+ const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
243671
+ if (code === "ENOENT" || code === "ENOTDIR") {
243672
+ return [];
243673
+ }
243674
+ throw error;
243042
243675
  }
243043
243676
  }
243044
243677
  function safeReadJsonFile(jsonPath) {
@@ -245612,8 +246245,6 @@ function isIgnoredComponentCandidatePath(componentPath) {
245612
246245
  "static",
245613
246246
  "assets",
245614
246247
  "vendor",
245615
- "plugins",
245616
- "plugin",
245617
246248
  "themes",
245618
246249
  "theme",
245619
246250
  "generated",
@@ -245634,6 +246265,36 @@ function pathExists19(pathValue) {
245634
246265
  return false;
245635
246266
  }
245636
246267
  }
246268
+ function isSafeMissingPathError(error) {
246269
+ const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
246270
+ return code === "ENOENT" || code === "ENOTDIR";
246271
+ }
246272
+ function isDirectoryPath(pathValue) {
246273
+ try {
246274
+ return fs58.statSync(pathValue).isDirectory();
246275
+ } catch (error) {
246276
+ if (isSafeMissingPathError(error)) {
246277
+ return false;
246278
+ }
246279
+ throw error;
246280
+ }
246281
+ }
246282
+ function readDirectoryEntriesForTraversal(directoryPath, options) {
246283
+ if (!isDirectoryPath(directoryPath)) {
246284
+ return [];
246285
+ }
246286
+ try {
246287
+ return fs58.readdirSync(directoryPath, {
246288
+ withFileTypes: true,
246289
+ ...options?.recursive ? { recursive: true } : {}
246290
+ });
246291
+ } catch (error) {
246292
+ if (isSafeMissingPathError(error)) {
246293
+ return [];
246294
+ }
246295
+ throw error;
246296
+ }
246297
+ }
245637
246298
  function hasManifest2(workspaceRoot, componentPath) {
245638
246299
  const absoluteRoot = componentPath === "." ? workspaceRoot : path64.join(workspaceRoot, ...componentPath.split("/"));
245639
246300
  return [
@@ -245641,7 +246302,8 @@ function hasManifest2(workspaceRoot, componentPath) {
245641
246302
  "pom.xml",
245642
246303
  "requirements.txt",
245643
246304
  "go.mod",
245644
- "composer.json"
246305
+ "composer.json",
246306
+ "project.json"
245645
246307
  ].some((fileName) => pathExists19(path64.join(absoluteRoot, fileName)));
245646
246308
  }
245647
246309
  function safeReadJson(filePath) {
@@ -245664,6 +246326,10 @@ function recordKeys(value) {
245664
246326
  function hasObject(value) {
245665
246327
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
245666
246328
  }
246329
+ function readStringProperty(value, key) {
246330
+ const field = value?.[key];
246331
+ return typeof field === "string" && field.trim().length > 0 ? field.trim() : void 0;
246332
+ }
245667
246333
  function hasPackagePublishSurface(packageJson) {
245668
246334
  return hasObject(packageJson?.exports) || typeof packageJson?.main === "string" || typeof packageJson?.module === "string" || typeof packageJson?.types === "string" || hasObject(packageJson?.publishConfig) || Array.isArray(packageJson?.files);
245669
246335
  }
@@ -245686,6 +246352,132 @@ function readProjectJson(workspaceRoot, componentPath) {
245686
246352
  const absoluteRoot = absoluteComponentRoot(workspaceRoot, componentPath);
245687
246353
  return safeReadJson(path64.join(absoluteRoot, "project.json"));
245688
246354
  }
246355
+ function readNxWorkspaceLayout(workspaceRoot) {
246356
+ const nxJson = safeReadJson(path64.join(workspaceRoot, "nx.json"));
246357
+ const workspaceLayout = nxJson?.workspaceLayout;
246358
+ if (!hasObject(workspaceLayout)) {
246359
+ return {};
246360
+ }
246361
+ const layout = workspaceLayout;
246362
+ return {
246363
+ ...typeof layout.appsDir === "string" && layout.appsDir.trim().length > 0 ? { appsDir: normalizePath25(layout.appsDir.trim()) } : {},
246364
+ ...typeof layout.libsDir === "string" && layout.libsDir.trim().length > 0 ? { libsDir: normalizePath25(layout.libsDir.trim()) } : {}
246365
+ };
246366
+ }
246367
+ function readTargetMap(projectJson) {
246368
+ const targets = projectJson?.targets ?? projectJson?.architect;
246369
+ return hasObject(targets) ? targets : {};
246370
+ }
246371
+ function targetExecutorText(target) {
246372
+ if (!hasObject(target)) {
246373
+ return "";
246374
+ }
246375
+ const typed = target;
246376
+ const executor = typeof typed.executor === "string" ? typed.executor : "";
246377
+ const builder = typeof typed.builder === "string" ? typed.builder : "";
246378
+ const options = hasObject(typed.options) ? typed.options : {};
246379
+ const main = typeof options.main === "string" ? options.main : "";
246380
+ const index = typeof options.index === "string" ? options.index : "";
246381
+ const outputPath = typeof options.outputPath === "string" ? options.outputPath : "";
246382
+ return `${executor} ${builder} ${main} ${index} ${outputPath}`.toLowerCase();
246383
+ }
246384
+ function hasNxRuntimeProjectEvidence(projectJson) {
246385
+ const projectType = readStringProperty(projectJson, "projectType");
246386
+ if (projectType === "library") {
246387
+ return false;
246388
+ }
246389
+ const targets = readTargetMap(projectJson);
246390
+ const targetNames = Object.keys(targets);
246391
+ const targetText = targetNames.map((targetName) => `${targetName} ${targetExecutorText(targets[targetName])}`).join(" ");
246392
+ const hasRuntimeTarget = /\b(?:serve|start|preview|worker|package:container|deploy:static)\b/u.test(targetText);
246393
+ const hasBuildEntrypoint = /\bbuild\b/u.test(targetNames.join(" ")) && /(?:\bmain\b|src\/main|src\/index|public\/index|webpack|vite|next|node|container|browser|dev-server)/u.test(targetText);
246394
+ return projectType === "application" || hasRuntimeTarget || hasBuildEntrypoint;
246395
+ }
246396
+ function hasNxSupportOnlyProjectEvidence(projectJson) {
246397
+ const projectType = readStringProperty(projectJson, "projectType");
246398
+ const targets = readTargetMap(projectJson);
246399
+ const targetNames = Object.keys(targets);
246400
+ if (targetNames.length === 0) {
246401
+ return false;
246402
+ }
246403
+ const targetText = targetNames.map((targetName) => `${targetName} ${targetExecutorText(targets[targetName])}`).join(" ");
246404
+ const hasOnlySupportTargets = targetNames.every(
246405
+ (targetName) => /^(?:e2e|test|unit-test|integration-test|lint|typecheck|format|smoke|bench|benchmark)$/u.test(targetName)
246406
+ );
246407
+ const hasTestExecutor = /(?:cypress|playwright|jest|vitest|mocha|karma|protractor)/u.test(targetText);
246408
+ return projectType !== "library" && (hasOnlySupportTargets || hasTestExecutor);
246409
+ }
246410
+ function classifyNxRuntimeKind(projectJson) {
246411
+ if (!hasNxRuntimeProjectEvidence(projectJson)) {
246412
+ return void 0;
246413
+ }
246414
+ const targets = readTargetMap(projectJson);
246415
+ const targetText = Object.entries(targets).map(([targetName, target]) => `${targetName} ${targetExecutorText(target)}`).join(" ");
246416
+ if (/(?:webpack:dev-server|webpack-dev-server|src\/index\.(?:tsx|jsx|ts|js)|public\/index\.html|browser|react|vite|next|angular|deploy:static)/u.test(targetText)) {
246417
+ return "frontend";
246418
+ }
246419
+ if (/(?:@nx\/js:node|@nrwl\/node|src\/main\.(?:ts|js)|target:\s*node|node\b|container|worker|queue)/u.test(targetText)) {
246420
+ return /\bworker\b/u.test(targetText) ? "worker" : "backend";
246421
+ }
246422
+ return void 0;
246423
+ }
246424
+ function collectNxProjectCandidates(workspaceRoot) {
246425
+ if (!pathExists19(path64.join(workspaceRoot, "nx.json"))) {
246426
+ return [];
246427
+ }
246428
+ const candidates = /* @__PURE__ */ new Map();
246429
+ const workspaceLayout = readNxWorkspaceLayout(workspaceRoot);
246430
+ const preferredRoots = new Set(
246431
+ ["apps", "packages", "services", "libs", workspaceLayout.appsDir, workspaceLayout.libsDir].filter((entry) => typeof entry === "string" && entry.length > 0).map(normalizePath25)
246432
+ );
246433
+ const maxDepth = 5;
246434
+ const queue = [
246435
+ { absolutePath: workspaceRoot, relativePath: "", depth: 0 }
246436
+ ];
246437
+ const addCandidate = (candidatePath, evidence) => {
246438
+ const normalized = normalizePath25(candidatePath);
246439
+ const existing = candidates.get(normalized) ?? { path: normalized, evidence: [] };
246440
+ addEvidence6(existing.evidence, evidence);
246441
+ candidates.set(normalized, existing);
246442
+ };
246443
+ while (queue.length > 0) {
246444
+ const current = queue.shift();
246445
+ if (!current) {
246446
+ continue;
246447
+ }
246448
+ for (const entry of readDirectoryEntriesForTraversal(current.absolutePath)) {
246449
+ if (!entry.isDirectory()) {
246450
+ continue;
246451
+ }
246452
+ if ([".git", ".archpilot", "node_modules", "dist", "build", "out", "coverage"].includes(entry.name)) {
246453
+ continue;
246454
+ }
246455
+ const relativePath = normalizePath25(current.relativePath ? `${current.relativePath}/${entry.name}` : entry.name);
246456
+ if (current.depth >= maxDepth) {
246457
+ continue;
246458
+ }
246459
+ const projectJsonPath = path64.join(current.absolutePath, entry.name, "project.json");
246460
+ if (pathExists19(projectJsonPath)) {
246461
+ const projectJson = safeReadJson(projectJsonPath);
246462
+ const projectRoot = normalizePath25(readStringProperty(projectJson, "root") ?? relativePath);
246463
+ if (hasNxRuntimeProjectEvidence(projectJson)) {
246464
+ addCandidate(projectRoot, "Detected Nx runtime project metadata");
246465
+ } else if (readStringProperty(projectJson, "sourceRoot")) {
246466
+ addCandidate(projectRoot, "Detected Nx source-bearing project metadata");
246467
+ }
246468
+ }
246469
+ const firstSegment = relativePath.split("/")[0] ?? "";
246470
+ if (current.depth === 0 || preferredRoots.has(firstSegment) || preferredRoots.has(relativePath.split("/").slice(0, 2).join("/"))) {
246471
+ queue.push({
246472
+ absolutePath: path64.join(current.absolutePath, entry.name),
246473
+ relativePath,
246474
+ depth: current.depth + 1
246475
+ });
246476
+ }
246477
+ }
246478
+ }
246479
+ return [...candidates.values()].sort((left, right) => left.path.localeCompare(right.path));
246480
+ }
245689
246481
  function readTextFileSafe(filePath) {
245690
246482
  try {
245691
246483
  return fs58.readFileSync(filePath, { encoding: "utf8" });
@@ -245732,6 +246524,8 @@ function readMavenMetadata(workspaceRoot, componentPath) {
245732
246524
  testScopedDependencyCount: dependencyBlocks.filter((block) => /<scope>\s*test\s*<\/scope>/iu.test(block)).length,
245733
246525
  totalDependencyCount: dependencyBlocks.length,
245734
246526
  hasMavenPluginConfiguration: packaging === "maven-plugin" || /maven-plugin-plugin|<goalPrefix>|<mojo/iu.test(normalizedPom),
246527
+ hasAssemblyPackagingConfiguration: /maven-assembly-plugin|<goal>\s*single\s*<\/goal>|<descriptor>[^<]*(?:assembly|dist|distribution)[^<]*<\/descriptor>/iu.test(normalizedPom),
246528
+ hasDeploySkipConfiguration: /<maven\.deploy\.skip>\s*true\s*<\/maven\.deploy\.skip>|<artifactId>\s*maven-deploy-plugin\s*<\/artifactId>[\s\S]*?<skip>\s*true\s*<\/skip>/iu.test(normalizedPom),
245735
246529
  hasTestLifecycleConfiguration: /maven-surefire-plugin|maven-failsafe-plugin|integration-test|failsafe|surefire/iu.test(normalizedPom),
245736
246530
  hasArquillianEvidence: /arquillian/iu.test(normalizedPom),
245737
246531
  hasMainJava: pathExists19(path64.join(absoluteRoot, "src", "main", "java")),
@@ -245864,6 +246658,12 @@ function buildRootCandidates(input2) {
245864
246658
  addCandidate(root, `Detected project root ${root}`);
245865
246659
  }
245866
246660
  if (input2.workspaceRoot) {
246661
+ for (const candidate of collectNxProjectCandidates(input2.workspaceRoot)) {
246662
+ addCandidate(candidate.path, "Detected Nx project metadata");
246663
+ for (const evidence of candidate.evidence) {
246664
+ addCandidate(candidate.path, evidence);
246665
+ }
246666
+ }
245867
246667
  const mavenRoots = [".", ...input2.topology.detectedRoots ?? []];
245868
246668
  for (const candidate of collectMavenReactorCandidates(input2.workspaceRoot, mavenRoots)) {
245869
246669
  addCandidate(
@@ -245947,10 +246747,7 @@ function isUnderJvmResourceRoot4(componentPath) {
245947
246747
  return /(^|\/)src\/main\/resources(?:\/|$)/u.test(normalizePath25(componentPath).toLowerCase());
245948
246748
  }
245949
246749
  function hasFileUnderRoot(root, pattern) {
245950
- if (!pathExists19(root)) {
245951
- return false;
245952
- }
245953
- const entries = fs58.readdirSync(root, { recursive: true, withFileTypes: true });
246750
+ const entries = readDirectoryEntriesForTraversal(root, { recursive: true });
245954
246751
  return entries.some((entry) => entry.isFile() && pattern.test(entry.name));
245955
246752
  }
245956
246753
  var javaMainMethodPattern = /\bpublic\s+static\s+void\s+main\s*\(\s*String\s*(?:(?:\[\s*\]\s*)|\.\.\.\s*)[A-Za-z_$][\w$]*\s*(?:\[\s*\])?\s*\)/u;
@@ -246068,10 +246865,7 @@ function readMavenProductionSourceFiles(workspaceRoot, componentPath) {
246068
246865
  ];
246069
246866
  const files = [];
246070
246867
  for (const sourceRoot of mainRoots) {
246071
- if (!pathExists19(sourceRoot)) {
246072
- continue;
246073
- }
246074
- const entries = fs58.readdirSync(sourceRoot, { recursive: true, withFileTypes: true });
246868
+ const entries = readDirectoryEntriesForTraversal(sourceRoot, { recursive: true });
246075
246869
  for (const entry of entries) {
246076
246870
  if (!entry.isFile() || !/\.(?:java|kt|scala)$/iu.test(entry.name)) {
246077
246871
  continue;
@@ -246243,10 +247037,7 @@ function hasRuntimeSourceFiles(workspaceRoot, componentPath) {
246243
247037
  path64.join(absoluteRoot, "server")
246244
247038
  ];
246245
247039
  for (const sourceRoot of sourceRoots) {
246246
- if (!pathExists19(sourceRoot)) {
246247
- continue;
246248
- }
246249
- const entries = fs58.readdirSync(sourceRoot, { recursive: true, withFileTypes: true });
247040
+ const entries = readDirectoryEntriesForTraversal(sourceRoot, { recursive: true });
246250
247041
  if (entries.some((entry) => entry.isFile() && /\.(?:ts|tsx|js|jsx|java|cs|py|php|go)$/iu.test(entry.name))) {
246251
247042
  return true;
246252
247043
  }
@@ -246254,10 +247045,7 @@ function hasRuntimeSourceFiles(workspaceRoot, componentPath) {
246254
247045
  return false;
246255
247046
  }
246256
247047
  function hasFileMatchingUnderRoot(root, pattern) {
246257
- if (!pathExists19(root)) {
246258
- return false;
246259
- }
246260
- const entries = fs58.readdirSync(root, { recursive: true, withFileTypes: true });
247048
+ const entries = readDirectoryEntriesForTraversal(root, { recursive: true });
246261
247049
  return entries.some((entry) => entry.isFile() && pattern.test(normalizePath25(path64.join(entry.parentPath, entry.name))));
246262
247050
  }
246263
247051
  function hasNestRuntimeApplicationEvidence(workspaceRoot, componentPath, packageJson = readPackageJson(workspaceRoot, componentPath)) {
@@ -246297,16 +247085,19 @@ function hasNestRuntimeApplicationEvidence(workspaceRoot, componentPath, package
246297
247085
  ].filter(Boolean).length;
246298
247086
  return hasBootstrapEntrypoint || hasRuntimePlatform && structuralEvidenceCount >= 2 || hasRunnableScript && hasRuntimeSourceShape && structuralEvidenceCount >= 3;
246299
247087
  }
246300
- function resolveKind(workspaceRoot, candidate, localProjectKinds) {
247088
+ function resolveKind2(workspaceRoot, candidate, localProjectKinds) {
246301
247089
  const lowerPath = candidate.path.toLowerCase();
246302
247090
  const packageJson = readPackageJson(workspaceRoot, candidate.path);
247091
+ const projectJson = readProjectJson(workspaceRoot, candidate.path);
247092
+ const nxProjectType = readStringProperty(projectJson, "projectType");
246303
247093
  const maven = readMavenMetadata(workspaceRoot, candidate.path);
246304
247094
  const dependencies = new Set(packageDependencyNames(packageJson));
246305
247095
  const hasRunnableFrontendEvidence = hasRunnableFrontendComponentEvidence(workspaceRoot, candidate.path);
246306
247096
  const hasNestRuntimeEvidence = hasNestRuntimeApplicationEvidence(workspaceRoot, candidate.path, packageJson);
247097
+ const nxRuntimeKind = classifyNxRuntimeKind(projectJson);
246307
247098
  const hasPublishSurface = hasPackagePublishSurface(packageJson);
246308
247099
  const hasMavenRunnableEvidence = Boolean(
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))
247100
+ maven && (hasMavenMainEntrypoint(workspaceRoot, candidate.path) || hasMavenConfiguredMainClassEntrypoint(workspaceRoot, candidate.path, maven.configuredMainClass) || maven.configuredMainClass && maven.hasSpringBootStarterDependency || maven.hasSpringBootPlugin && maven.hasSpringBootRepackageGoal && hasMavenMainEntrypoint(workspaceRoot, candidate.path) || maven.packaging === "war" && hasMavenMainEntrypoint(workspaceRoot, candidate.path))
246310
247101
  );
246311
247102
  const hasMavenLibraryEvidence = Boolean(
246312
247103
  maven && !hasMavenRunnableEvidence && (maven.packaging === "jar" || maven.packaging === "maven-plugin" || maven.packaging === "pom") && (hasMavenMainJvmSource(workspaceRoot, candidate.path) || maven.hasMainResources || maven.hasDependencyManagement || maven.hasSpringBootAutoconfigureDependency || maven.hasSpringBootAutoconfigureProcessor || hasMavenAutoConfigurationMetadata(workspaceRoot, candidate.path) || maven.hasDocumentationTooling || maven.hasTestJava || maven.hasTestResources)
@@ -246314,6 +247105,12 @@ function resolveKind(workspaceRoot, candidate, localProjectKinds) {
246314
247105
  if (lowerPath.endsWith("/db") || lowerPath.endsWith("/database") || lowerPath.endsWith("/data")) {
246315
247106
  return "data";
246316
247107
  }
247108
+ if (nxRuntimeKind) {
247109
+ return nxRuntimeKind;
247110
+ }
247111
+ if (nxProjectType === "library") {
247112
+ return "library";
247113
+ }
246317
247114
  if (hasMavenLibraryEvidence) {
246318
247115
  return "library";
246319
247116
  }
@@ -246413,7 +247210,10 @@ function classifyGovernanceRole(input2) {
246413
247210
  const scriptText = scripts.join(" ").toLowerCase();
246414
247211
  const projectMetadataText = `${projectTags.join(" ")} ${projectTargets.join(" ")}`.toLowerCase();
246415
247212
  const dependencyText = [...dependencies, ...devDependencies].join(" ").toLowerCase();
247213
+ const hasRunnableFrontendEvidence = hasRunnableFrontendComponentEvidence(input2.workspaceRoot, input2.candidate.path);
246416
247214
  const hasNestRuntimeEvidence = hasNestRuntimeApplicationEvidence(input2.workspaceRoot, input2.candidate.path, packageJson);
247215
+ const nxRuntimeKind = classifyNxRuntimeKind(projectJson);
247216
+ const hasNxSupportOnlyEvidence = hasNxSupportOnlyProjectEvidence(projectJson);
246417
247217
  const hasStrongSupportPath = hasAnyToken(pathText, [
246418
247218
  /(^|\/)(testing|e2e-testing|test-plugins|cypress-tests|fixtures?|benchmarks?|smoke)(\/|$)/u,
246419
247219
  /(^|\/)(dev-server|scaffold|scaffolding|generators?|devkit|tooling|tools|templates?)(\/|$)/u
@@ -246428,9 +247228,11 @@ function classifyGovernanceRole(input2) {
246428
247228
  const hasMavenMainEntrypointEvidence = Boolean(maven && hasMavenMainEntrypoint(input2.workspaceRoot, input2.candidate.path));
246429
247229
  const hasMavenMainJvmSourceEvidence = Boolean(maven && hasMavenMainJvmSource(input2.workspaceRoot, input2.candidate.path));
246430
247230
  const hasMavenAutoConfigurationEvidence = Boolean(maven && (hasMavenAutoConfigurationMetadata(input2.workspaceRoot, input2.candidate.path) || maven.hasSpringBootAutoconfigureDependency || maven.hasSpringBootAutoconfigureProcessor));
246431
- const hasMavenExecutableConfiguration = Boolean(maven && (hasMavenConfiguredMainClassEntrypoint(input2.workspaceRoot, input2.candidate.path, maven.configuredMainClass) || maven.hasSpringBootPlugin && maven.hasSpringBootRepackageGoal && hasMavenMainEntrypointEvidence || maven.packaging === "war" && hasMavenMainEntrypointEvidence));
247231
+ const hasMavenExecutableConfiguration = Boolean(maven && (hasMavenConfiguredMainClassEntrypoint(input2.workspaceRoot, input2.candidate.path, maven.configuredMainClass) || maven.configuredMainClass && maven.hasSpringBootStarterDependency || maven.hasSpringBootPlugin && maven.hasSpringBootRepackageGoal && hasMavenMainEntrypointEvidence || maven.packaging === "war" && hasMavenMainEntrypointEvidence));
246432
247232
  const hasMavenRunnableApplicationEvidence = Boolean(hasMavenMainEntrypointEvidence || hasMavenExecutableConfiguration);
246433
- const isMavenPomOnly = Boolean(maven?.packaging === "pom" && !hasMavenMainSource && !hasMavenTestSource);
247233
+ const isMavenPomOnly = Boolean(
247234
+ maven?.packaging === "pom" && !hasMavenRunnableApplicationEvidence && !hasMavenMainSource && !hasMavenTestSource
247235
+ );
246434
247236
  const hasMavenTestContext = hasAnyToken(pathText, [
246435
247237
  /(^|\/)(test-framework|testsuite|integration-tests?|test-apps)(\/|$)/u
246436
247238
  ]) && (hasAnyToken(mavenMetadataText, [/\b(test|testsuite|test framework|arquillian|integration test)\b/u]) || Boolean(maven?.hasTestLifecycleConfiguration || maven?.hasArquillianEvidence || hasMavenTestSource));
@@ -246438,7 +247240,7 @@ function classifyGovernanceRole(input2) {
246438
247240
  maven?.packaging === "pom" && maven.hasDependencyManagement && !hasMavenMainSource && hasAnyToken(mavenMetadataText, [/\b(bom|bill of materials|dependency management|platform)\b/u])
246439
247241
  );
246440
247242
  const hasMavenDistributionPackagingEvidence = Boolean(
246441
- maven?.packaging === "pom" && !hasMavenMainSource && hasAnyToken(pathText, [/(^|\/)(distribution|dist|downloads|assembl(y|ies)|feature-packs?)(\/|$)/u])
247243
+ maven && !hasMavenMainSource && (maven.hasAssemblyPackagingConfiguration || maven.hasDeploySkipConfiguration && maven.hasDocumentationTooling || hasAnyToken(mavenMetadataText, [/\b(distribution|assembly|release package|packaging bundle)\b/u]))
246442
247244
  );
246443
247245
  const hasMavenStarterPackagingEvidence = Boolean(
246444
247246
  maven && !hasMavenMainJvmSourceEvidence && hasAnyToken(`${pathText} ${mavenMetadataText}`, [/\bstarters?\b|\bspring-boot-starter\b|\bstarter[-\s]/u])
@@ -246465,6 +247267,18 @@ function classifyGovernanceRole(input2) {
246465
247267
  runtimeScore += 3;
246466
247268
  addEvidence6(evidence, "NestJS runtime application evidence");
246467
247269
  }
247270
+ if (input2.kind === "frontend" && hasRunnableFrontendEvidence) {
247271
+ runtimeScore += 3;
247272
+ addEvidence6(evidence, "Runnable frontend application evidence");
247273
+ }
247274
+ if (nxRuntimeKind) {
247275
+ runtimeScore += 3;
247276
+ addEvidence6(evidence, `Nx runtime project evidence: ${nxRuntimeKind}`);
247277
+ }
247278
+ if (hasNxSupportOnlyEvidence) {
247279
+ supportScore += 4;
247280
+ addEvidence6(evidence, "Nx project metadata indicates test/tooling-only workspace");
247281
+ }
246468
247282
  if (hasPublishSurface && !isPrivate) {
246469
247283
  runtimeScore += 1;
246470
247284
  addEvidence6(evidence, "Published package surface signal");
@@ -246475,7 +247289,7 @@ function classifyGovernanceRole(input2) {
246475
247289
  if (maven?.hasPom) {
246476
247290
  addEvidence6(evidence, `Maven packaging ${maven.packaging ?? "jar"}`);
246477
247291
  }
246478
- if (maven && (maven.packaging === "jar" || maven.packaging === "war") && hasMavenRunnableApplicationEvidence) {
247292
+ if (maven && hasMavenRunnableApplicationEvidence) {
246479
247293
  runtimeScore += 3;
246480
247294
  addEvidence6(evidence, "Maven executable application evidence");
246481
247295
  }
@@ -246512,6 +247326,10 @@ function classifyGovernanceRole(input2) {
246512
247326
  supportScore += 1;
246513
247327
  addEvidence6(evidence, "Path contains support/tooling role tokens");
246514
247328
  }
247329
+ if (maven && hasStrongSupportPath && !hasMavenRunnableApplicationEvidence) {
247330
+ supportScore += 3;
247331
+ addEvidence6(evidence, "Maven module path and metadata indicate support/tooling role");
247332
+ }
246515
247333
  if (hasAnyToken(pathText, [/(^|\/)(?:src\/constants\/)?templates?(\/|$)/u])) {
246516
247334
  supportScore += 4;
246517
247335
  addEvidence6(evidence, "Nested scaffold/template source surface");
@@ -246544,6 +247362,10 @@ function classifyGovernanceRole(input2) {
246544
247362
  supportScore += 4;
246545
247363
  addEvidence6(evidence, "Maven documentation tooling without JVM runtime source");
246546
247364
  }
247365
+ if (maven?.hasDeploySkipConfiguration && !hasMavenMainJvmSourceEvidence && !hasMavenRunnableApplicationEvidence) {
247366
+ supportScore += 4;
247367
+ addEvidence6(evidence, "Maven deploy-skipped artifact without runtime source");
247368
+ }
246547
247369
  if (hasMavenBomEvidence) {
246548
247370
  supportScore += 5;
246549
247371
  addEvidence6(evidence, "Maven BOM/dependency-management module without runtime source");
@@ -246572,6 +247394,10 @@ function classifyGovernanceRole(input2) {
246572
247394
  supportScore += 4;
246573
247395
  addEvidence6(evidence, "Maven starter/resource artifact without JVM runtime source");
246574
247396
  }
247397
+ if (hasMavenDistributionPackagingEvidence) {
247398
+ supportScore += 4;
247399
+ addEvidence6(evidence, "Maven distribution/release packaging without runtime source");
247400
+ }
246575
247401
  if (isPlainResourcesSurface) {
246576
247402
  supportScore += 4;
246577
247403
  addEvidence6(evidence, "Resources-only surface without runtime source or manifest evidence");
@@ -246594,7 +247420,7 @@ function classifyGovernanceRole(input2) {
246594
247420
  supportScore += 3;
246595
247421
  addEvidence6(evidence, "Maven documentation/build/distribution packaging evidence without runtime source");
246596
247422
  }
246597
- if (supportScore >= 3 && supportScore > runtimeScore || hasStrongSupportPath && supportScore >= 2 && runtimeScore <= 5 || maven?.packaging === "maven-plugin" && input2.candidate.path !== "." && supportScore >= 5 || maven && supportScore >= 5 && !hasMavenMainSource || maven && supportScore >= 4 && hasMavenTestContext || maven && supportScore >= 4 && hasMavenDistributionPackagingEvidence && hasAnyToken(mavenMetadataText, [/\b(docs?|documentation|metadata tests?|licenses?|maven plugins?|builds?)\b/u])) {
247423
+ if (supportScore >= 3 && supportScore > runtimeScore || hasStrongSupportPath && supportScore >= 2 && runtimeScore <= 5 || maven?.packaging === "maven-plugin" && input2.candidate.path !== "." && supportScore >= 5 || maven && supportScore >= 5 && !hasMavenMainJvmSourceEvidence && !hasMavenRunnableApplicationEvidence || maven && supportScore >= 4 && maven.hasDeploySkipConfiguration && !hasMavenMainJvmSourceEvidence && !hasMavenRunnableApplicationEvidence || maven && supportScore >= 4 && hasStrongSupportPath && !hasMavenMainJvmSourceEvidence && !hasMavenRunnableApplicationEvidence || maven && supportScore >= 4 && hasMavenTestContext || maven && supportScore >= 4 && hasMavenDistributionPackagingEvidence && hasAnyToken(mavenMetadataText, [/\b(docs?|documentation|metadata tests?|licenses?|maven plugins?|builds?)\b/u])) {
246598
247424
  return {
246599
247425
  governanceRole: "support",
246600
247426
  governanceEvidence: uniqueSorted4(evidence),
@@ -246672,7 +247498,7 @@ function detectComponents(input2) {
246672
247498
  }
246673
247499
  const localProjectKinds = input2.runContext?.detectProjectKinds(absoluteRoot) ?? detectProjectKinds(absoluteRoot);
246674
247500
  const stackDetection = input2.runContext?.detectStacks(absoluteRoot) ?? detectStacks(absoluteRoot);
246675
- const kind = resolveKind(input2.workspaceRoot, candidate, localProjectKinds);
247501
+ const kind = resolveKind2(input2.workspaceRoot, candidate, localProjectKinds);
246676
247502
  const stack = resolveStack(kind, stackDetection);
246677
247503
  const evidence = [...candidate.evidence];
246678
247504
  if (candidate.architectureRoot?.classification && candidate.architectureRoot.classification !== "unknown") {
@@ -249938,7 +250764,30 @@ async function runStandardValidationWorkflowForWorkspace(input2) {
249938
250764
  "Loading governance baseline",
249939
250765
  () => readGovernanceBaseline(input2.workspaceRoot)
249940
250766
  );
249941
- const baselineComparison = compareWithGovernanceBaseline(reviewSummary, governanceBaseline);
250767
+ applyCanonicalValidationSummary(reviewSummary, runResult.status.summary);
250768
+ let effectiveGovernanceBaseline = governanceBaseline;
250769
+ let initialGovernanceBaselinePath;
250770
+ if (governanceBaseline.kind === "missing" && !input2.ciMode) {
250771
+ const baseline = buildGovernanceBaselineFromReviewSummary(reviewSummary);
250772
+ initialGovernanceBaselinePath = await phase(
250773
+ "Creating initial governance baseline",
250774
+ () => writeGovernanceBaseline(input2.workspaceRoot, baseline)
250775
+ );
250776
+ effectiveGovernanceBaseline = {
250777
+ kind: "ok",
250778
+ baseline,
250779
+ baselinePath: initialGovernanceBaselinePath
250780
+ };
250781
+ emitOperationalMessage(
250782
+ input2.onOperationalMessage,
250783
+ "info",
250784
+ "Governance baseline created from initial validation. Future validations will highlight new architecture regressions."
250785
+ );
250786
+ }
250787
+ const baselineComparison = compareWithGovernanceBaseline(
250788
+ reviewSummary,
250789
+ effectiveGovernanceBaseline
250790
+ );
249942
250791
  reviewSummary.enforcement = buildEnforcementResult(
249943
250792
  reviewSummary.risk,
249944
250793
  riskPolicyLoad.config,
@@ -249959,7 +250808,6 @@ async function runStandardValidationWorkflowForWorkspace(input2) {
249959
250808
  })
249960
250809
  );
249961
250810
  reviewSummary.baselineComparison = baselineComparison;
249962
- applyCanonicalValidationSummary(reviewSummary, runResult.status.summary);
249963
250811
  let baselineComparisonPath;
249964
250812
  try {
249965
250813
  baselineComparisonPath = await writeBaselineComparisonReport(
@@ -250024,6 +250872,7 @@ async function runStandardValidationWorkflowForWorkspace(input2) {
250024
250872
  impactAnalysisPath,
250025
250873
  baselineComparison,
250026
250874
  ...baselineComparisonPath ? { baselineComparisonPath } : {},
250875
+ ...initialGovernanceBaselinePath ? { initialGovernanceBaselinePath } : {},
250027
250876
  ...prCommentPath ? { prCommentPath } : {}
250028
250877
  };
250029
250878
  }
@@ -250209,6 +251058,7 @@ function parseValidateFlags(args) {
250209
251058
  json: args.includes("--json"),
250210
251059
  changed: args.includes("--changed"),
250211
251060
  diff: args.includes("--diff"),
251061
+ regressions: args.includes("--regressions"),
250212
251062
  base: base.length > 0 ? base : "main"
250213
251063
  };
250214
251064
  }
@@ -250255,8 +251105,8 @@ function readFlagValue(args, flag) {
250255
251105
  return value && value.length > 0 ? value : void 0;
250256
251106
  }
250257
251107
  async function readCliPackageVersion() {
250258
- if ("0.2.5".trim().length > 0) {
250259
- return "0.2.5".trim();
251108
+ if ("0.2.7".trim().length > 0) {
251109
+ return "0.2.7".trim();
250260
251110
  }
250261
251111
  const candidatePackageJsonPaths = [
250262
251112
  path73.join(__dirname, "..", "package.json"),
@@ -250313,7 +251163,7 @@ function printUsage() {
250313
251163
  console.error("Usage: archpilot init [--yes]");
250314
251164
  console.error(" archpilot init refresh --preview");
250315
251165
  console.error(" archpilot init refresh --apply");
250316
- console.error("Usage: archpilot validate [--ci] [--json] [--changed] [--diff] [--base <branch>]");
251166
+ console.error("Usage: archpilot validate [--ci] [--json] [--regressions] [--changed] [--diff] [--base <branch>]");
250317
251167
  console.error(" archpilot impact <file-or-module> [--json] [--stdout] [--module] [--force]");
250318
251168
  console.error(" archpilot fix <RULE_ID>");
250319
251169
  console.error(" archpilot fix");
@@ -250400,11 +251250,12 @@ var cliHelpTopics = [
250400
251250
  {
250401
251251
  commandPath: ["validate"],
250402
251252
  lines: [
250403
- "Usage: archpilot validate [--ci] [--json] [--changed] [--diff] [--base <branch>]",
251253
+ "Usage: archpilot validate [--ci] [--json] [--regressions] [--changed] [--diff] [--base <branch>]",
250404
251254
  "",
250405
251255
  "Options:",
250406
251256
  " --ci CI mode",
250407
251257
  " --json JSON output",
251258
+ " --regressions show findings introduced or resolved since the governance baseline",
250408
251259
  " --changed validate changed files only",
250409
251260
  " --diff compare with baseline",
250410
251261
  " --base <branch> compare against branch"
@@ -252481,12 +253332,136 @@ function applyCanonicalValidationSummary2(reviewSummary, validationSummary) {
252481
253332
  reviewSummary.guidanceFindings = validationSummary.guidanceFindings;
252482
253333
  reviewSummary.qualityFindings = validationSummary.qualityFindings;
252483
253334
  }
253335
+ function getRegressionComparisonUnavailableReason(comparison) {
253336
+ if (comparison.baselineAvailable) {
253337
+ return void 0;
253338
+ }
253339
+ return comparison.summary.startsWith("Baseline unavailable:") ? "INVALID_BASELINE" : "NO_BASELINE";
253340
+ }
253341
+ function formatRegressionFindingLine2(finding) {
253342
+ const location = finding.module && finding.target ? `${finding.module} -> ${finding.target}` : finding.module ?? finding.target ?? finding.sourceFile ?? finding.filePath ?? finding.table ?? finding.api;
253343
+ const header = `${finding.id} ${finding.severity}`;
253344
+ return [
253345
+ location ? `${header}
253346
+ ${location}` : header,
253347
+ finding.message
253348
+ ].join("\n");
253349
+ }
253350
+ function renderRegressionFindingSection(title, findings) {
253351
+ const lines = [];
253352
+ lines.push(title);
253353
+ lines.push("-".repeat(title.length));
253354
+ lines.push("");
253355
+ if (findings.length === 0) {
253356
+ lines.push("- (none)");
253357
+ return lines;
253358
+ }
253359
+ for (const finding of findings) {
253360
+ lines.push(formatRegressionFindingLine2(finding));
253361
+ lines.push("");
253362
+ }
253363
+ while (lines.at(-1) === "") {
253364
+ lines.pop();
253365
+ }
253366
+ return lines;
253367
+ }
253368
+ function renderValidationRegressionConsole(comparison) {
253369
+ const unavailableReason = getRegressionComparisonUnavailableReason(comparison);
253370
+ const lines = [];
253371
+ lines.push("Regression Summary");
253372
+ lines.push("------------------");
253373
+ if (unavailableReason) {
253374
+ lines.push("Regression comparison unavailable.");
253375
+ lines.push("");
253376
+ if (unavailableReason === "NO_BASELINE") {
253377
+ lines.push("No governance baseline was found.");
253378
+ lines.push("");
253379
+ lines.push("Create a governance baseline first:");
253380
+ lines.push(" archpilot baseline refresh");
253381
+ } else {
253382
+ lines.push(comparison.summary);
253383
+ }
253384
+ return `${lines.join("\n")}
253385
+ `;
253386
+ }
253387
+ lines.push(`Introduced: ${comparison.newlyIntroducedFindings.length}`);
253388
+ lines.push(`Resolved: ${comparison.resolvedFindings.length}`);
253389
+ lines.push(`Existing: ${comparison.unchangedFindings.length}`);
253390
+ lines.push("");
253391
+ if (comparison.newlyIntroducedFindings.length === 0) {
253392
+ lines.push("No new architecture regressions detected.");
253393
+ } else {
253394
+ lines.push(
253395
+ ...renderRegressionFindingSection(
253396
+ "Introduced architecture findings",
253397
+ comparison.newlyIntroducedFindings
253398
+ )
253399
+ );
253400
+ }
253401
+ if (comparison.resolvedFindings.length > 0) {
253402
+ lines.push("");
253403
+ lines.push(...renderRegressionFindingSection("Resolved", comparison.resolvedFindings));
253404
+ }
253405
+ return `${lines.join("\n").trimEnd()}
253406
+ `;
253407
+ }
253408
+ function buildRegressionComparisonJson(comparison) {
253409
+ const unavailableReason = getRegressionComparisonUnavailableReason(comparison);
253410
+ if (unavailableReason) {
253411
+ return {
253412
+ available: false,
253413
+ reason: unavailableReason,
253414
+ introducedCount: 0,
253415
+ resolvedCount: 0,
253416
+ existingCount: 0,
253417
+ introduced: [],
253418
+ resolved: []
253419
+ };
253420
+ }
253421
+ return {
253422
+ available: true,
253423
+ introducedCount: comparison.newlyIntroducedFindings.length,
253424
+ resolvedCount: comparison.resolvedFindings.length,
253425
+ existingCount: comparison.unchangedFindings.length,
253426
+ introduced: comparison.newlyIntroducedFindings,
253427
+ resolved: comparison.resolvedFindings
253428
+ };
253429
+ }
253430
+ function buildScopedRegressionComparisonUnavailable(summary, scopeMode) {
253431
+ return {
253432
+ reportVersion: 1,
253433
+ baselineAvailable: false,
253434
+ status: "NO_BASELINE",
253435
+ summary: `Regression comparison unavailable for ${scopeMode} validation scope. Full governance baselines cannot be compared safely against scoped current findings.`,
253436
+ current: {
253437
+ projectName: summary.projectName,
253438
+ generatedAtUtc: summary.generatedAtUtc,
253439
+ healthScore: summary.healthScore,
253440
+ readinessScore: summary.readinessScore
253441
+ },
253442
+ deltas: {
253443
+ healthScoreDelta: 0,
253444
+ readinessScoreDelta: 0,
253445
+ errorDelta: 0,
253446
+ warningDelta: 0,
253447
+ findingDelta: 0,
253448
+ remainingViolationsDelta: 0
253449
+ },
253450
+ newlyIntroducedFindings: [],
253451
+ resolvedFindings: [],
253452
+ unchangedFindings: []
253453
+ };
253454
+ }
252484
253455
  async function runValidateCommand(args) {
252485
253456
  const flags = parseValidateFlags(args);
252486
253457
  if (flags.changed && flags.diff) {
252487
253458
  console.error("Choose one scope mode: --changed or --diff.");
252488
253459
  return 1;
252489
253460
  }
253461
+ if (flags.regressions && (flags.changed || flags.diff)) {
253462
+ console.error("--regressions cannot currently be combined with --changed/--diff.");
253463
+ return 1;
253464
+ }
252490
253465
  if (args.includes("--base") && !flags.diff) {
252491
253466
  console.error("--base can only be used with --diff.");
252492
253467
  return 1;
@@ -252643,8 +253618,7 @@ async function runValidateCommand(args) {
252643
253618
  }
252644
253619
  }
252645
253620
  });
252646
- const governanceBaseline = await readGovernanceBaseline(workspaceRoot);
252647
- baselineComparison = compareWithGovernanceBaseline(reviewSummary, governanceBaseline);
253621
+ baselineComparison = buildScopedRegressionComparisonUnavailable(reviewSummary, scopeMode);
252648
253622
  reviewSummary.enforcement = buildEnforcementResult2(
252649
253623
  reviewSummary.risk,
252650
253624
  riskPolicyLoad.config,
@@ -252743,6 +253717,7 @@ async function runValidateCommand(args) {
252743
253717
  impactAnalysis: reviewSummary.impactAnalysis,
252744
253718
  impactAnalysisPath: toWorkspaceRelativePath(workspaceRoot, impactAnalysisPath),
252745
253719
  baselineComparison,
253720
+ ...flags.regressions ? { regressionComparison: buildRegressionComparisonJson(baselineComparison) } : {},
252746
253721
  ...baselineComparisonPath ? {
252747
253722
  baselineComparisonPath: toWorkspaceRelativePath(
252748
253723
  workspaceRoot,
@@ -252787,24 +253762,36 @@ async function runValidateCommand(args) {
252787
253762
  `
252788
253763
  );
252789
253764
  }
252790
- process.stdout.write(
252791
- renderArchitectureReviewConsole(reviewSummary, {
252792
- ...reportArtifacts.reportWriteError ? { reportWriteError: reportArtifacts.reportWriteError } : {
252793
- reportPaths: {
252794
- markdownPath: reportArtifacts.reviewMarkdownPath,
252795
- jsonPath: reportArtifacts.reviewJsonPath,
252796
- compactMarkdownPath: reportArtifacts.reviewCompactMarkdownPath
252797
- }
252798
- },
252799
- remediation,
252800
- discoveredAdrs,
252801
- healthTrend: runResult.status.summary.healthTrend,
252802
- driftComparison: runResult.status.driftComparison,
252803
- adrSummary: runResult.status.adrSummary,
252804
- suppressionSummary: runResult.status.suppressionSummary,
252805
- baselineComparison
252806
- })
252807
- );
253765
+ if (flags.regressions) {
253766
+ process.stdout.write("ArchPilot Architecture Review\n\n");
253767
+ process.stdout.write(`Architecture Health Score: ${reviewSummary.healthScore} / 100
253768
+ `);
253769
+ process.stdout.write(`Setup Readiness Score: ${reviewSummary.readinessScore} / 100
253770
+ `);
253771
+ process.stdout.write(`Current findings: ${reviewSummary.findings.length}
253772
+
253773
+ `);
253774
+ process.stdout.write(renderValidationRegressionConsole(baselineComparison));
253775
+ } else {
253776
+ process.stdout.write(
253777
+ renderArchitectureReviewConsole(reviewSummary, {
253778
+ ...reportArtifacts.reportWriteError ? { reportWriteError: reportArtifacts.reportWriteError } : {
253779
+ reportPaths: {
253780
+ markdownPath: reportArtifacts.reviewMarkdownPath,
253781
+ jsonPath: reportArtifacts.reviewJsonPath,
253782
+ compactMarkdownPath: reportArtifacts.reviewCompactMarkdownPath
253783
+ }
253784
+ },
253785
+ remediation,
253786
+ discoveredAdrs,
253787
+ healthTrend: runResult.status.summary.healthTrend,
253788
+ driftComparison: runResult.status.driftComparison,
253789
+ adrSummary: runResult.status.adrSummary,
253790
+ suppressionSummary: runResult.status.suppressionSummary,
253791
+ baselineComparison
253792
+ })
253793
+ );
253794
+ }
252808
253795
  process.stdout.write("\n");
252809
253796
  process.stdout.write(renderSetupNextStepsConsole(nextSteps));
252810
253797
  process.stdout.write(`${setupNextStepsReportPointerLine}