@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 +28 -0
- package/README.md +17 -2
- package/dist/archpilot-cli.js +772 -74
- package/package.json +1 -1
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
|
|
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
|
---
|
package/dist/archpilot-cli.js
CHANGED
|
@@ -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")) &&
|
|
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
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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.
|
|
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
|
|
238640
|
-
|
|
238641
|
-
|
|
238642
|
-
|
|
238643
|
-
const
|
|
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
|
-
|
|
238964
|
-
lines.push(
|
|
238965
|
-
|
|
238966
|
-
|
|
238967
|
-
|
|
238968
|
-
|
|
238969
|
-
|
|
238970
|
-
|
|
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
|
-
|
|
239074
|
-
|
|
239075
|
-
|
|
239076
|
-
|
|
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.
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
250548
|
-
return "0.2.
|
|
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
|
-
|
|
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
|
-
|
|
253080
|
-
|
|
253081
|
-
|
|
253082
|
-
|
|
253083
|
-
|
|
253084
|
-
|
|
253085
|
-
|
|
253086
|
-
|
|
253087
|
-
|
|
253088
|
-
|
|
253089
|
-
|
|
253090
|
-
|
|
253091
|
-
|
|
253092
|
-
|
|
253093
|
-
|
|
253094
|
-
|
|
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