@archpilotlabs/archpilot 0.2.6 → 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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.7
4
+
5
+ ### Added
6
+
7
+ - Added `archpilot validate --regressions` to focus validation output on architecture findings introduced since the accepted governance baseline.
8
+ - Added regression summaries showing introduced, resolved, and existing architecture findings.
9
+ - Added regression comparison data to JSON validation output for automation and tooling.
10
+ - Added automatic initial governance baseline creation after the first successful full local validation, so regression tracking works without additional setup.
11
+
12
+ ### Improved
13
+
14
+ - Improved CI and pull request reporting to highlight newly introduced architecture regressions instead of presenting existing architecture debt as newly actionable.
15
+ - Improved GitHub inline annotations to prioritize newly introduced supported findings when a governance baseline is available.
16
+ - Improved regression reporting with separate visibility for resolved findings and existing baseline findings.
17
+ - Improved missing-baseline guidance to clearly distinguish the governance baseline from the structural architecture baseline.
18
+ - Scoped `--changed` and `--diff` validation now avoids unsafe full-baseline comparisons that could incorrectly report findings as resolved.
19
+
20
+ ### Notes
21
+
22
+ - Regression comparison uses the accepted governance baseline at `.archpilot/baseline/baseline.json`.
23
+ - The initial governance baseline is created automatically only after a successful full local validation. Existing baselines are never automatically refreshed.
24
+ - Use `archpilot baseline refresh` when you intentionally want to accept the current architecture findings as the new governance baseline.
25
+ - `--regressions` does not change Architecture Health Score, Setup Readiness Score, rule severities, scoring, or normal validation findings.
26
+ - `--regressions` does not currently combine with `--changed` or `--diff`.
27
+ - Regression reporting does not independently change CI exit or policy enforcement behavior.
28
+
29
+ ---
30
+
3
31
  ## 0.2.6
4
32
 
5
33
  ### Improved
package/README.md CHANGED
@@ -45,7 +45,7 @@ ArchPilot turns architecture into something you can actually run and verify:
45
45
  - Generate architecture scores and reports
46
46
  - Surface architecture feedback in local development, PRs, and CI
47
47
 
48
- No runtime agents. No external services. Fully deterministic and local-first.
48
+ **No cloud scan required. Deterministic and local-first.**
49
49
 
50
50
  ArchPilot works alongside GitHub Copilot, Claude Code, Codex, Cursor, and other AI coding tools by continuously validating architectural boundaries before architectural drift becomes technical debt.
51
51
 
@@ -172,6 +172,22 @@ Creates `.archpilot/` with architecture config, rules, and baseline artifacts.
172
172
  archpilot validate
173
173
  ```
174
174
 
175
+ To focus on architecture changes introduced since the governance baseline:
176
+
177
+ ```bash
178
+ archpilot validate --regressions
179
+ ```
180
+
181
+ ArchPilot automatically creates the initial governance baseline after the first successful full local validation. Regression validation can then distinguish newly introduced, existing, and resolved architecture findings.
182
+
183
+ When you intentionally accept the current architecture findings as the new reference, run:
184
+
185
+ ```bash
186
+ archpilot baseline refresh
187
+ ```
188
+
189
+ The governance baseline used for regression comparison is separate from the structural baseline used by `archpilot baseline create`.
190
+
175
191
  Example output:
176
192
 
177
193
  ```text
@@ -317,6 +333,5 @@ Proprietary. See LICENSE file for details.
317
333
 
318
334
  - Website: https://archpilot.org
319
335
  - Documentation: https://archpilot.org/docs
320
- - [Changelog](https://github.com/archpilotlabs/archpilot-vscode/blob/main/packages/cli/CHANGELOG.md)
321
336
 
322
337
  ---
@@ -228162,6 +228162,8 @@ var transactionBoundaryDecoratorPattern = /@(?:Transactional|Transaction)\b|\[(?
228162
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;
228163
228163
  var separateTransactionPropagationPattern = /\b(?:requiresNew|REQUIRES_NEW|Propagation\.REQUIRES_NEW)\b/u;
228164
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;
228165
228167
  var transactionCallbackStartPattern = /\b(?:this\.)?(?:[\w.$:]+\.)?(?:\$transaction|transaction|Transaction)\s*\(\s*(?:async\s*)?(?:function\s*)?\(?\s*\w*\s*\)?\s*(?:=>)?\s*\{/gu;
228166
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;
228167
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;
@@ -229307,6 +229309,16 @@ function hasEntitySpecificServiceBusinessRules(entityNames, methods) {
229307
229309
  return [...entityAliases].some((entityName) => new RegExp(`\\b${entityName}\\b`, "u").test(method.file.content));
229308
229310
  });
229309
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
+ }
229310
229322
  function detectDomainFindings(files, classifications, methods, contract, astCache) {
229311
229323
  const findings = [];
229312
229324
  for (const file of files) {
@@ -229376,7 +229388,7 @@ function detectDomainFindings(files, classifications, methods, contract, astCach
229376
229388
  }));
229377
229389
  }
229378
229390
  }
229379
- 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)) {
229380
229392
  findings.push(createFinding3({
229381
229393
  id: RuleIds2.DOM_LOGIC_SCATTERED,
229382
229394
  message: `Business rule evidence appears in a ${hasRole(method.classification, "controller") ? "controller" : "repository"} method '${method.name}'.`,
@@ -229692,6 +229704,11 @@ function httpMethodFromDecorator(decorators, extension) {
229692
229704
  }
229693
229705
  return value;
229694
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
+ }
229695
229712
  function methodPathFromDecorators(decorators, extension) {
229696
229713
  const pattern = extension === ".java" ? /@(?:GetMapping|PostMapping|PutMapping|PatchMapping|DeleteMapping|RequestMapping)\s*(?:\(([^)]*)\))?/u : /@(?:Get|Post|Put|Patch|Delete)\s*(?:\(([^)]*)\))?/u;
229697
229714
  const match = pattern.exec(decorators);
@@ -229711,11 +229728,12 @@ function extractDecoratorEndpoints(file, contract) {
229711
229728
  const returnType = file.extension === ".java" ? match[3] ?? "" : match[3] ?? "";
229712
229729
  const openBrace = (match.index ?? 0) + match[0].lastIndexOf("{");
229713
229730
  const end = findMatchingBrace4(file.content, openBrace);
229731
+ const routeDecoratorOffset = routeDecoratorOffsetInGroup(decorators, file.extension);
229714
229732
  endpoints.push({
229715
229733
  httpMethod,
229716
229734
  path: joinEndpointPath(basePath, methodPathFromDecorators(decorators, file.extension)),
229717
229735
  name,
229718
- line: lineNumberForOffset4(file.content, match.index ?? 0),
229736
+ line: lineNumberForOffset4(file.content, (match.index ?? 0) + routeDecoratorOffset),
229719
229737
  decorators,
229720
229738
  signature: `${returnType} ${match[0]}`,
229721
229739
  body: file.content.slice(openBrace, end + 1),
@@ -237063,6 +237081,41 @@ function formatRuleLine(ruleId) {
237063
237081
  function formatFindingLine(finding) {
237064
237082
  return `${formatRuleLine(finding.id)} | ${finding.message}`;
237065
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
+ }
237066
237119
  function parseFindingKind2(value) {
237067
237120
  return value === "violation" || value === "setup-gap" || value === "guidance" ? value : void 0;
237068
237121
  }
@@ -237087,7 +237140,7 @@ function renderArchitecturePrCommentMarkdown(input2) {
237087
237140
  findingKindByRule.set(finding.id, byRule);
237088
237141
  findingKindByRuleAndMessage.set(`${finding.id}::${finding.message}`, kind);
237089
237142
  }
237090
- const resolveKind2 = (entry) => {
237143
+ const resolveKind3 = (entry) => {
237091
237144
  const explicit = readFindingKind(entry);
237092
237145
  if (explicit) {
237093
237146
  return explicit;
@@ -237105,28 +237158,28 @@ function renderArchitecturePrCommentMarkdown(input2) {
237105
237158
  return "violation";
237106
237159
  };
237107
237160
  const newQualityViolationLines = sortUniqueLines(
237108
- (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))
237109
237162
  );
237110
237163
  const newSetupGapLines = sortUniqueLines(
237111
- (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))
237112
237165
  );
237113
237166
  const newGuidanceLines = sortUniqueLines(
237114
- (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))
237115
237168
  );
237116
237169
  const resolvedViolationLines = sortUniqueLines(
237117
- (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))
237118
237171
  );
237119
237172
  const resolvedSetupGapLines = sortUniqueLines(
237120
- (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))
237121
237174
  );
237122
237175
  const activeQualityViolationLines = sortUniqueLines(
237123
- 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))
237124
237177
  );
237125
237178
  const activeSetupGapLines = sortUniqueLines(
237126
- 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))
237127
237180
  );
237128
237181
  const activeGuidanceLines = sortUniqueLines(
237129
- 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))
237130
237183
  );
237131
237184
  const activeQualityViolationCount = activeQualityViolationLines.length;
237132
237185
  const activeSetupGapCount = activeSetupGapLines.length;
@@ -237262,6 +237315,37 @@ function renderArchitecturePrCommentMarkdown(input2) {
237262
237315
  lines.push(`Setup gaps: ${activeSetupGapCount}`);
237263
237316
  }
237264
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
+ }
237265
237349
  if (input2.driftComparison?.hasPreviousSnapshot) {
237266
237350
  lines.push(
237267
237351
  `Previous score: ${input2.driftComparison.previousScore ?? "n/a"}${input2.driftComparison.scoreDelta !== void 0 ? ` (${input2.driftComparison.scoreDelta >= 0 ? "+" : ""}${input2.driftComparison.scoreDelta})` : ""}`
@@ -238426,6 +238510,190 @@ async function checkArchitectureBaseline(workspaceRoot) {
238426
238510
  // ../core/src/architectureGovernanceBaseline.ts
238427
238511
  var path42 = __toESM(require("node:path"));
238428
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
238429
238697
  function sortUnique9(values) {
238430
238698
  return [...new Set(values)].sort((left, right) => left.localeCompare(right));
238431
238699
  }
@@ -238442,13 +238710,51 @@ function baselineFindingKey(entry) {
238442
238710
  ].join("|");
238443
238711
  }
238444
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;
238445
238734
  return {
238446
238735
  key: baselineFindingKey(entry),
238736
+ regressionIdentity,
238447
238737
  id: entry.id,
238448
238738
  severity: entry.severity,
238449
238739
  kind: entry.kind,
238450
238740
  message: entry.message,
238451
- ...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 } : {}
238452
238758
  };
238453
238759
  }
238454
238760
  function compareFindings(left, right) {
@@ -238484,7 +238790,28 @@ function parseSummaryArtifact(value) {
238484
238790
  severity: entry.severity,
238485
238791
  kind: entry.kind,
238486
238792
  message: entry.message,
238487
- ...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 } : {}
238488
238815
  }));
238489
238816
  const suppressionRaw = typed.suppressionSummary && typeof typed.suppressionSummary === "object" ? typed.suppressionSummary : void 0;
238490
238817
  const suppressionSummary = suppressionRaw && typeof suppressionRaw.configuredSuppressions === "number" && typeof suppressionRaw.activeSuppressions === "number" && typeof suppressionRaw.expiredSuppressions === "number" && typeof suppressionRaw.unusedSuppressions === "number" && typeof suppressionRaw.findingsSuppressed === "number" ? {
@@ -238553,6 +238880,57 @@ function buildBaselineFromSummaryArtifact(artifact, nowUtcIso2) {
238553
238880
  ...artifact.impactAnalysis ? { impactAnalysis: artifact.impactAnalysis } : {}
238554
238881
  };
238555
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
+ }
238556
238934
  function getGovernanceBaselinePath(workspaceRoot) {
238557
238935
  return path42.join(workspaceRoot, ".archpilot", "baseline", "baseline.json");
238558
238936
  }
@@ -238636,13 +239014,43 @@ async function readGovernanceBaseline(workspaceRoot) {
238636
239014
  }
238637
239015
  }
238638
239016
  function compareFindingsByKey(current, baseline) {
238639
- const currentMap = new Map(current.map((entry) => [entry.key, entry]));
238640
- const baselineMap = new Map(baseline.map((entry) => [entry.key, entry]));
238641
- const newlyIntroducedFindings = [...currentMap.entries()].filter(([key]) => !baselineMap.has(key)).map(([, entry]) => entry).sort(compareFindings);
238642
- const resolvedFindings = [...baselineMap.entries()].filter(([key]) => !currentMap.has(key)).map(([, entry]) => entry).sort(compareFindings);
238643
- 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);
238644
239024
  return { newlyIntroducedFindings, resolvedFindings, unchangedFindings };
238645
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
+ }
238646
239054
  function compareWithGovernanceBaseline(currentSummary, baselineLoad) {
238647
239055
  const currentFindings = currentSummary.findings.map(
238648
239056
  (entry) => toBaselineFinding({
@@ -238650,7 +239058,23 @@ function compareWithGovernanceBaseline(currentSummary, baselineLoad) {
238650
239058
  severity: entry.severity,
238651
239059
  kind: entry.kind,
238652
239060
  message: entry.message,
238653
- ...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 } : {}
238654
239078
  })
238655
239079
  ).sort(compareFindings);
238656
239080
  const currentRemainingViolations = currentSummary.findings.filter(
@@ -238811,6 +239235,43 @@ function renderList(values) {
238811
239235
  }
238812
239236
  return values.map((value) => `- ${value}`).join("\n");
238813
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
+ }
238814
239275
  function renderStatusLine(input2) {
238815
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);
238816
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;
@@ -238960,14 +239421,43 @@ function renderArchitecturePrReviewMarkdown(input2) {
238960
239421
  }
238961
239422
  if (input2.validation.kind === "ok" && input2.validation.data.baselineComparison) {
238962
239423
  const baseline = input2.validation.data.baselineComparison;
238963
- lines.push("## Baseline Comparison");
238964
- lines.push(`- Baseline status: ${baseline.status}`);
238965
- lines.push(`- Health score delta: ${baseline.deltas.healthScoreDelta}`);
238966
- lines.push(`- Readiness score delta: ${baseline.deltas.readinessScoreDelta}`);
238967
- lines.push(`- New findings: ${baseline.newlyIntroducedFindings.length}`);
238968
- lines.push(`- Resolved findings: ${baseline.resolvedFindings.length}`);
238969
- lines.push(`- Summary: ${baseline.summary}`);
238970
- 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
+ }
238971
239461
  }
238972
239462
  if (input2.validation.kind === "ok" && input2.validation.data.risk) {
238973
239463
  const governanceRisk = input2.validation.data.risk;
@@ -239058,23 +239548,32 @@ function renderArchitecturePrReviewMarkdown(input2) {
239058
239548
  }
239059
239549
  }
239060
239550
  if (input2.validation.kind === "ok") {
239061
- const topFindings = [...input2.validation.data.findings].sort((left, right) => {
239062
- const severityCompare = severityRank(left.severity) - severityRank(right.severity);
239063
- if (severityCompare !== 0) {
239064
- return severityCompare;
239065
- }
239066
- const idCompare = left.id.localeCompare(right.id);
239067
- if (idCompare !== 0) {
239068
- return idCompare;
239069
- }
239070
- return left.message.localeCompare(right.message);
239071
- }).slice(0, 8);
239072
239551
  lines.push("## Validation Findings");
239073
- lines.push(
239074
- topFindings.length === 0 ? "- (none)" : topFindings.map(
239075
- (finding) => `- [${finding.id}] (${finding.severity}) ${finding.title} | ${finding.message}`
239076
- ).join("\n")
239077
- );
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
+ }
239078
239577
  lines.push("");
239079
239578
  }
239080
239579
  if (input2.drift.kind === "ok") {
@@ -239201,7 +239700,7 @@ function toAnnotatableFinding(workspaceRoot, finding) {
239201
239700
  if (finding.severity !== "error" && finding.severity !== "warning") {
239202
239701
  return void 0;
239203
239702
  }
239204
- if (typeof finding.title !== "string" || typeof finding.message !== "string") {
239703
+ if (typeof finding.message !== "string") {
239205
239704
  return void 0;
239206
239705
  }
239207
239706
  if (typeof finding.sourceFile !== "string") {
@@ -239218,7 +239717,7 @@ function toAnnotatableFinding(workspaceRoot, finding) {
239218
239717
  const column = typeof finding.sourceColumn === "number" && Number.isFinite(finding.sourceColumn) ? Math.max(1, Math.floor(finding.sourceColumn)) : void 0;
239219
239718
  return {
239220
239719
  id: finding.id,
239221
- title: finding.title,
239720
+ title: typeof finding.title === "string" ? finding.title : finding.id,
239222
239721
  message: finding.message,
239223
239722
  severity: finding.severity,
239224
239723
  file,
@@ -239338,9 +239837,47 @@ async function loadFromSummaryReport(workspaceRoot, maxAgeMinutes) {
239338
239837
  findings: findings.filter((entry) => !!entry && typeof entry === "object")
239339
239838
  };
239340
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
+ }
239341
239878
  async function emitGithubInlineArchitectureAnnotations(workspaceRoot) {
239342
239879
  const maxAgeMinutes = readMaxArtifactAgeMinutes();
239343
- 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);
239344
239881
  if (!loaded) {
239345
239882
  const now = formatUtcDate(/* @__PURE__ */ new Date());
239346
239883
  console.log(
@@ -246548,7 +247085,7 @@ function hasNestRuntimeApplicationEvidence(workspaceRoot, componentPath, package
246548
247085
  ].filter(Boolean).length;
246549
247086
  return hasBootstrapEntrypoint || hasRuntimePlatform && structuralEvidenceCount >= 2 || hasRunnableScript && hasRuntimeSourceShape && structuralEvidenceCount >= 3;
246550
247087
  }
246551
- function resolveKind(workspaceRoot, candidate, localProjectKinds) {
247088
+ function resolveKind2(workspaceRoot, candidate, localProjectKinds) {
246552
247089
  const lowerPath = candidate.path.toLowerCase();
246553
247090
  const packageJson = readPackageJson(workspaceRoot, candidate.path);
246554
247091
  const projectJson = readProjectJson(workspaceRoot, candidate.path);
@@ -246961,7 +247498,7 @@ function detectComponents(input2) {
246961
247498
  }
246962
247499
  const localProjectKinds = input2.runContext?.detectProjectKinds(absoluteRoot) ?? detectProjectKinds(absoluteRoot);
246963
247500
  const stackDetection = input2.runContext?.detectStacks(absoluteRoot) ?? detectStacks(absoluteRoot);
246964
- const kind = resolveKind(input2.workspaceRoot, candidate, localProjectKinds);
247501
+ const kind = resolveKind2(input2.workspaceRoot, candidate, localProjectKinds);
246965
247502
  const stack = resolveStack(kind, stackDetection);
246966
247503
  const evidence = [...candidate.evidence];
246967
247504
  if (candidate.architectureRoot?.classification && candidate.architectureRoot.classification !== "unknown") {
@@ -250227,7 +250764,30 @@ async function runStandardValidationWorkflowForWorkspace(input2) {
250227
250764
  "Loading governance baseline",
250228
250765
  () => readGovernanceBaseline(input2.workspaceRoot)
250229
250766
  );
250230
- 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
+ );
250231
250791
  reviewSummary.enforcement = buildEnforcementResult(
250232
250792
  reviewSummary.risk,
250233
250793
  riskPolicyLoad.config,
@@ -250248,7 +250808,6 @@ async function runStandardValidationWorkflowForWorkspace(input2) {
250248
250808
  })
250249
250809
  );
250250
250810
  reviewSummary.baselineComparison = baselineComparison;
250251
- applyCanonicalValidationSummary(reviewSummary, runResult.status.summary);
250252
250811
  let baselineComparisonPath;
250253
250812
  try {
250254
250813
  baselineComparisonPath = await writeBaselineComparisonReport(
@@ -250313,6 +250872,7 @@ async function runStandardValidationWorkflowForWorkspace(input2) {
250313
250872
  impactAnalysisPath,
250314
250873
  baselineComparison,
250315
250874
  ...baselineComparisonPath ? { baselineComparisonPath } : {},
250875
+ ...initialGovernanceBaselinePath ? { initialGovernanceBaselinePath } : {},
250316
250876
  ...prCommentPath ? { prCommentPath } : {}
250317
250877
  };
250318
250878
  }
@@ -250498,6 +251058,7 @@ function parseValidateFlags(args) {
250498
251058
  json: args.includes("--json"),
250499
251059
  changed: args.includes("--changed"),
250500
251060
  diff: args.includes("--diff"),
251061
+ regressions: args.includes("--regressions"),
250501
251062
  base: base.length > 0 ? base : "main"
250502
251063
  };
250503
251064
  }
@@ -250544,8 +251105,8 @@ function readFlagValue(args, flag) {
250544
251105
  return value && value.length > 0 ? value : void 0;
250545
251106
  }
250546
251107
  async function readCliPackageVersion() {
250547
- if ("0.2.6".trim().length > 0) {
250548
- return "0.2.6".trim();
251108
+ if ("0.2.7".trim().length > 0) {
251109
+ return "0.2.7".trim();
250549
251110
  }
250550
251111
  const candidatePackageJsonPaths = [
250551
251112
  path73.join(__dirname, "..", "package.json"),
@@ -250602,7 +251163,7 @@ function printUsage() {
250602
251163
  console.error("Usage: archpilot init [--yes]");
250603
251164
  console.error(" archpilot init refresh --preview");
250604
251165
  console.error(" archpilot init refresh --apply");
250605
- console.error("Usage: archpilot validate [--ci] [--json] [--changed] [--diff] [--base <branch>]");
251166
+ console.error("Usage: archpilot validate [--ci] [--json] [--regressions] [--changed] [--diff] [--base <branch>]");
250606
251167
  console.error(" archpilot impact <file-or-module> [--json] [--stdout] [--module] [--force]");
250607
251168
  console.error(" archpilot fix <RULE_ID>");
250608
251169
  console.error(" archpilot fix");
@@ -250689,11 +251250,12 @@ var cliHelpTopics = [
250689
251250
  {
250690
251251
  commandPath: ["validate"],
250691
251252
  lines: [
250692
- "Usage: archpilot validate [--ci] [--json] [--changed] [--diff] [--base <branch>]",
251253
+ "Usage: archpilot validate [--ci] [--json] [--regressions] [--changed] [--diff] [--base <branch>]",
250693
251254
  "",
250694
251255
  "Options:",
250695
251256
  " --ci CI mode",
250696
251257
  " --json JSON output",
251258
+ " --regressions show findings introduced or resolved since the governance baseline",
250697
251259
  " --changed validate changed files only",
250698
251260
  " --diff compare with baseline",
250699
251261
  " --base <branch> compare against branch"
@@ -252770,12 +253332,136 @@ function applyCanonicalValidationSummary2(reviewSummary, validationSummary) {
252770
253332
  reviewSummary.guidanceFindings = validationSummary.guidanceFindings;
252771
253333
  reviewSummary.qualityFindings = validationSummary.qualityFindings;
252772
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
+ }
252773
253455
  async function runValidateCommand(args) {
252774
253456
  const flags = parseValidateFlags(args);
252775
253457
  if (flags.changed && flags.diff) {
252776
253458
  console.error("Choose one scope mode: --changed or --diff.");
252777
253459
  return 1;
252778
253460
  }
253461
+ if (flags.regressions && (flags.changed || flags.diff)) {
253462
+ console.error("--regressions cannot currently be combined with --changed/--diff.");
253463
+ return 1;
253464
+ }
252779
253465
  if (args.includes("--base") && !flags.diff) {
252780
253466
  console.error("--base can only be used with --diff.");
252781
253467
  return 1;
@@ -252932,8 +253618,7 @@ async function runValidateCommand(args) {
252932
253618
  }
252933
253619
  }
252934
253620
  });
252935
- const governanceBaseline = await readGovernanceBaseline(workspaceRoot);
252936
- baselineComparison = compareWithGovernanceBaseline(reviewSummary, governanceBaseline);
253621
+ baselineComparison = buildScopedRegressionComparisonUnavailable(reviewSummary, scopeMode);
252937
253622
  reviewSummary.enforcement = buildEnforcementResult2(
252938
253623
  reviewSummary.risk,
252939
253624
  riskPolicyLoad.config,
@@ -253032,6 +253717,7 @@ async function runValidateCommand(args) {
253032
253717
  impactAnalysis: reviewSummary.impactAnalysis,
253033
253718
  impactAnalysisPath: toWorkspaceRelativePath(workspaceRoot, impactAnalysisPath),
253034
253719
  baselineComparison,
253720
+ ...flags.regressions ? { regressionComparison: buildRegressionComparisonJson(baselineComparison) } : {},
253035
253721
  ...baselineComparisonPath ? {
253036
253722
  baselineComparisonPath: toWorkspaceRelativePath(
253037
253723
  workspaceRoot,
@@ -253076,24 +253762,36 @@ async function runValidateCommand(args) {
253076
253762
  `
253077
253763
  );
253078
253764
  }
253079
- process.stdout.write(
253080
- renderArchitectureReviewConsole(reviewSummary, {
253081
- ...reportArtifacts.reportWriteError ? { reportWriteError: reportArtifacts.reportWriteError } : {
253082
- reportPaths: {
253083
- markdownPath: reportArtifacts.reviewMarkdownPath,
253084
- jsonPath: reportArtifacts.reviewJsonPath,
253085
- compactMarkdownPath: reportArtifacts.reviewCompactMarkdownPath
253086
- }
253087
- },
253088
- remediation,
253089
- discoveredAdrs,
253090
- healthTrend: runResult.status.summary.healthTrend,
253091
- driftComparison: runResult.status.driftComparison,
253092
- adrSummary: runResult.status.adrSummary,
253093
- suppressionSummary: runResult.status.suppressionSummary,
253094
- baselineComparison
253095
- })
253096
- );
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
+ }
253097
253795
  process.stdout.write("\n");
253098
253796
  process.stdout.write(renderSetupNextStepsConsole(nextSteps));
253099
253797
  process.stdout.write(`${setupNextStepsReportPointerLine}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archpilotlabs/archpilot",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Executable architecture governance CLI for Smart Init, validation, drift detection, and CI.",
5
5
  "homepage": "https://archpilot.org",
6
6
  "bugs": {