playwright-intelligence-engine 0.0.0-stage → 2.0.0
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/LICENSE +191 -0
- package/README.md +229 -3
- package/bin/pw-intel.js +37 -0
- package/dist/authoring/author.d.ts +83 -0
- package/dist/authoring/author.d.ts.map +1 -0
- package/dist/authoring/generation-contract.d.ts +90 -0
- package/dist/authoring/generation-contract.d.ts.map +1 -0
- package/dist/authoring/hybrid-author.d.ts +6 -0
- package/dist/authoring/hybrid-author.d.ts.map +1 -0
- package/dist/authoring/index.d.ts +7 -0
- package/dist/authoring/index.d.ts.map +1 -0
- package/dist/authoring/llm-author.d.ts +28 -0
- package/dist/authoring/llm-author.d.ts.map +1 -0
- package/dist/authoring/naming/index.d.ts +4 -0
- package/dist/authoring/naming/index.d.ts.map +1 -0
- package/dist/authoring/naming/name-plan.d.ts +45 -0
- package/dist/authoring/naming/name-plan.d.ts.map +1 -0
- package/dist/authoring/naming/name-planner.d.ts +25 -0
- package/dist/authoring/naming/name-planner.d.ts.map +1 -0
- package/dist/authoring/naming/naming-linter.d.ts +20 -0
- package/dist/authoring/naming/naming-linter.d.ts.map +1 -0
- package/dist/authoring/template-author.d.ts +6 -0
- package/dist/authoring/template-author.d.ts.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/collectors/collector-interface.d.ts +7 -0
- package/dist/collectors/collector-interface.d.ts.map +1 -0
- package/dist/collectors/environment-collector.d.ts +12 -0
- package/dist/collectors/environment-collector.d.ts.map +1 -0
- package/dist/collectors/error-collector.d.ts +18 -0
- package/dist/collectors/error-collector.d.ts.map +1 -0
- package/dist/collectors/git-diff-collector.d.ts +20 -0
- package/dist/collectors/git-diff-collector.d.ts.map +1 -0
- package/dist/collectors/live-browser-collector.d.ts +45 -0
- package/dist/collectors/live-browser-collector.d.ts.map +1 -0
- package/dist/collectors/network-collector.d.ts +28 -0
- package/dist/collectors/network-collector.d.ts.map +1 -0
- package/dist/collectors/static-ast-collector.d.ts +32 -0
- package/dist/collectors/static-ast-collector.d.ts.map +1 -0
- package/dist/collectors/trace-collector.d.ts +12 -0
- package/dist/collectors/trace-collector.d.ts.map +1 -0
- package/dist/config/project-config.d.ts +33 -0
- package/dist/config/project-config.d.ts.map +1 -0
- package/dist/core/errors/engine-errors.d.ts +34 -0
- package/dist/core/errors/engine-errors.d.ts.map +1 -0
- package/dist/core/types/diagnostic-context.d.ts +151 -0
- package/dist/core/types/diagnostic-context.d.ts.map +1 -0
- package/dist/core/types/repair-evidence.d.ts +56 -0
- package/dist/core/types/repair-evidence.d.ts.map +1 -0
- package/dist/core/version.d.ts +13 -0
- package/dist/core/version.d.ts.map +1 -0
- package/dist/diagnostics/diagnostic-contract.d.ts +52 -0
- package/dist/diagnostics/diagnostic-contract.d.ts.map +1 -0
- package/dist/diagnostics/diagnostic-report.d.ts +11 -0
- package/dist/diagnostics/diagnostic-report.d.ts.map +1 -0
- package/dist/diagnostics/evidence-correlator.d.ts +22 -0
- package/dist/diagnostics/evidence-correlator.d.ts.map +1 -0
- package/dist/diagnostics/external-failure-evidence.d.ts +18 -0
- package/dist/diagnostics/external-failure-evidence.d.ts.map +1 -0
- package/dist/diagnostics/failure-category.d.ts +4 -0
- package/dist/diagnostics/failure-category.d.ts.map +1 -0
- package/dist/diagnostics/failure-classifier.d.ts +58 -0
- package/dist/diagnostics/failure-classifier.d.ts.map +1 -0
- package/dist/evaluation/evaluation-scorecard.d.ts +72 -0
- package/dist/evaluation/evaluation-scorecard.d.ts.map +1 -0
- package/dist/evaluation/repository-inspector.d.ts +21 -0
- package/dist/evaluation/repository-inspector.d.ts.map +1 -0
- package/dist/evaluation/repository-runner.d.ts +24 -0
- package/dist/evaluation/repository-runner.d.ts.map +1 -0
- package/dist/evidence/evidence-model.d.ts +88 -0
- package/dist/evidence/evidence-model.d.ts.map +1 -0
- package/dist/evidence/evidence-normalizer.d.ts +19 -0
- package/dist/evidence/evidence-normalizer.d.ts.map +1 -0
- package/dist/evidence/evidence-redactor.d.ts +37 -0
- package/dist/evidence/evidence-redactor.d.ts.map +1 -0
- package/dist/framework/config-adapter.d.ts +28 -0
- package/dist/framework/config-adapter.d.ts.map +1 -0
- package/dist/gate/index.d.ts +21 -0
- package/dist/gate/index.d.ts.map +1 -0
- package/dist/gate/oracle.d.ts +26 -0
- package/dist/gate/oracle.d.ts.map +1 -0
- package/dist/gate/quality.d.ts +27 -0
- package/dist/gate/quality.d.ts.map +1 -0
- package/dist/gate/safety.d.ts +28 -0
- package/dist/gate/safety.d.ts.map +1 -0
- package/dist/generator/architecture-generator.d.ts +55 -0
- package/dist/generator/architecture-generator.d.ts.map +1 -0
- package/dist/generator/assertion-quality-gate.d.ts +18 -0
- package/dist/generator/assertion-quality-gate.d.ts.map +1 -0
- package/dist/generator/candidate-writer.d.ts +34 -0
- package/dist/generator/candidate-writer.d.ts.map +1 -0
- package/dist/generator/test-generator.d.ts +26 -0
- package/dist/generator/test-generator.d.ts.map +1 -0
- package/dist/governance/audit-log.d.ts +23 -0
- package/dist/governance/audit-log.d.ts.map +1 -0
- package/dist/governance/governance-policy.d.ts +24 -0
- package/dist/governance/governance-policy.d.ts.map +1 -0
- package/dist/governance/team-dashboard.d.ts +51 -0
- package/dist/governance/team-dashboard.d.ts.map +1 -0
- package/dist/governance/workflow-audit.d.ts +13 -0
- package/dist/governance/workflow-audit.d.ts.map +1 -0
- package/dist/index.d.ts +90 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20318 -0
- package/dist/integrations/ci-summary.d.ts +14 -0
- package/dist/integrations/ci-summary.d.ts.map +1 -0
- package/dist/integrations/playwright-report-adapter.d.ts +22 -0
- package/dist/integrations/playwright-report-adapter.d.ts.map +1 -0
- package/dist/integrations/quality-gate.d.ts +35 -0
- package/dist/integrations/quality-gate.d.ts.map +1 -0
- package/dist/integrations/sarif-report.d.ts +48 -0
- package/dist/integrations/sarif-report.d.ts.map +1 -0
- package/dist/intelligence/autonomous-repair-workflow.d.ts +31 -0
- package/dist/intelligence/autonomous-repair-workflow.d.ts.map +1 -0
- package/dist/intelligence/business-intent.d.ts +58 -0
- package/dist/intelligence/business-intent.d.ts.map +1 -0
- package/dist/intelligence/evidence-bundle.d.ts +364 -0
- package/dist/intelligence/evidence-bundle.d.ts.map +1 -0
- package/dist/intelligence/failure-clustering.d.ts +38 -0
- package/dist/intelligence/failure-clustering.d.ts.map +1 -0
- package/dist/intelligence/generation-agent.d.ts +120 -0
- package/dist/intelligence/generation-agent.d.ts.map +1 -0
- package/dist/intelligence/generation-verifier.d.ts +151 -0
- package/dist/intelligence/generation-verifier.d.ts.map +1 -0
- package/dist/intelligence/llm-proposal.d.ts +377 -0
- package/dist/intelligence/llm-proposal.d.ts.map +1 -0
- package/dist/intelligence/locator-disambiguation-engine.d.ts +38 -0
- package/dist/intelligence/locator-disambiguation-engine.d.ts.map +1 -0
- package/dist/intelligence/model-contract.d.ts +86 -0
- package/dist/intelligence/model-contract.d.ts.map +1 -0
- package/dist/intelligence/model-provider.d.ts +75 -0
- package/dist/intelligence/model-provider.d.ts.map +1 -0
- package/dist/intelligence/project-intelligence.d.ts +22 -0
- package/dist/intelligence/project-intelligence.d.ts.map +1 -0
- package/dist/intelligence/repair-agent.d.ts +46 -0
- package/dist/intelligence/repair-agent.d.ts.map +1 -0
- package/dist/intelligence/repository-profile.d.ts +131 -0
- package/dist/intelligence/repository-profile.d.ts.map +1 -0
- package/dist/intelligence/suite-intelligence.d.ts +59 -0
- package/dist/intelligence/suite-intelligence.d.ts.map +1 -0
- package/dist/intelligence/test-intelligence.d.ts +44 -0
- package/dist/intelligence/test-intelligence.d.ts.map +1 -0
- package/dist/intelligence/trace-proposal-workflow.d.ts +30 -0
- package/dist/intelligence/trace-proposal-workflow.d.ts.map +1 -0
- package/dist/intelligence/verified-llm-repair-workflow.d.ts +21 -0
- package/dist/intelligence/verified-llm-repair-workflow.d.ts.map +1 -0
- package/dist/intelligence/workflow-planner.d.ts +278 -0
- package/dist/intelligence/workflow-planner.d.ts.map +1 -0
- package/dist/mcp/server.d.ts +32 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/memory/stability-ledger.d.ts +46 -0
- package/dist/memory/stability-ledger.d.ts.map +1 -0
- package/dist/perception/app-model.d.ts +55 -0
- package/dist/perception/app-model.d.ts.map +1 -0
- package/dist/perception/crawler.d.ts +18 -0
- package/dist/perception/crawler.d.ts.map +1 -0
- package/dist/perception/index.d.ts +3 -0
- package/dist/perception/index.d.ts.map +1 -0
- package/dist/repair/approval-service.d.ts +139 -0
- package/dist/repair/approval-service.d.ts.map +1 -0
- package/dist/repair/assertion-hasher.d.ts +62 -0
- package/dist/repair/assertion-hasher.d.ts.map +1 -0
- package/dist/repair/ast-patcher.d.ts +36 -0
- package/dist/repair/ast-patcher.d.ts.map +1 -0
- package/dist/repair/browser-operation-executor.d.ts +27 -0
- package/dist/repair/browser-operation-executor.d.ts.map +1 -0
- package/dist/repair/diff-allowlist.d.ts +74 -0
- package/dist/repair/diff-allowlist.d.ts.map +1 -0
- package/dist/repair/intent-validator.d.ts +157 -0
- package/dist/repair/intent-validator.d.ts.map +1 -0
- package/dist/repair/repair-operations.d.ts +40 -0
- package/dist/repair/repair-operations.d.ts.map +1 -0
- package/dist/repair/repair-planner.d.ts +98 -0
- package/dist/repair/repair-planner.d.ts.map +1 -0
- package/dist/repair/repair-workflow.d.ts +42 -0
- package/dist/repair/repair-workflow.d.ts.map +1 -0
- package/dist/repair/safe-operation-compiler.d.ts +15 -0
- package/dist/repair/safe-operation-compiler.d.ts.map +1 -0
- package/dist/repair/source-mapper.d.ts +19 -0
- package/dist/repair/source-mapper.d.ts.map +1 -0
- package/dist/repair/statement-patcher.d.ts +75 -0
- package/dist/repair/statement-patcher.d.ts.map +1 -0
- package/dist/repair/verification-gates.d.ts +25 -0
- package/dist/repair/verification-gates.d.ts.map +1 -0
- package/dist/repair/workflow-plan.d.ts +35 -0
- package/dist/repair/workflow-plan.d.ts.map +1 -0
- package/dist/repair/workspace-promoter.d.ts +43 -0
- package/dist/repair/workspace-promoter.d.ts.map +1 -0
- package/dist/reproduce/diagnostic-rerunner.d.ts +40 -0
- package/dist/reproduce/diagnostic-rerunner.d.ts.map +1 -0
- package/dist/reproduce/direct-browser-investigator.d.ts +41 -0
- package/dist/reproduce/direct-browser-investigator.d.ts.map +1 -0
- package/dist/reproduce/interactive-workflow-runner.d.ts +43 -0
- package/dist/reproduce/interactive-workflow-runner.d.ts.map +1 -0
- package/dist/sandbox/dependency-strategy.d.ts +45 -0
- package/dist/sandbox/dependency-strategy.d.ts.map +1 -0
- package/dist/sandbox/execution-runner.d.ts +81 -0
- package/dist/sandbox/execution-runner.d.ts.map +1 -0
- package/dist/sandbox/worktree-manager.d.ts +51 -0
- package/dist/sandbox/worktree-manager.d.ts.map +1 -0
- package/dist/selector/abstention-policy.d.ts +34 -0
- package/dist/selector/abstention-policy.d.ts.map +1 -0
- package/dist/selector/candidate-extractor.d.ts +71 -0
- package/dist/selector/candidate-extractor.d.ts.map +1 -0
- package/dist/selector/live-locator-verifier.d.ts +28 -0
- package/dist/selector/live-locator-verifier.d.ts.map +1 -0
- package/dist/selector/repair-candidate-ranker.d.ts +10 -0
- package/dist/selector/repair-candidate-ranker.d.ts.map +1 -0
- package/dist/selector/selector-scorer.d.ts +43 -0
- package/dist/selector/selector-scorer.d.ts.map +1 -0
- package/dist/trace/adapters/adapter-registry.d.ts +18 -0
- package/dist/trace/adapters/adapter-registry.d.ts.map +1 -0
- package/dist/trace/adapters/trace-adapter.d.ts +21 -0
- package/dist/trace/adapters/trace-adapter.d.ts.map +1 -0
- package/dist/trace/adapters/unknown-adapter.d.ts +10 -0
- package/dist/trace/adapters/unknown-adapter.d.ts.map +1 -0
- package/dist/trace/adapters/v10-adapter.d.ts +10 -0
- package/dist/trace/adapters/v10-adapter.d.ts.map +1 -0
- package/dist/trace/adapters/v4-adapter.d.ts +11 -0
- package/dist/trace/adapters/v4-adapter.d.ts.map +1 -0
- package/dist/trace/adapters/v8-adapter.d.ts +10 -0
- package/dist/trace/adapters/v8-adapter.d.ts.map +1 -0
- package/dist/trace/adapters/v9-adapter.d.ts +10 -0
- package/dist/trace/adapters/v9-adapter.d.ts.map +1 -0
- package/dist/trace/archive-reader.d.ts +44 -0
- package/dist/trace/archive-reader.d.ts.map +1 -0
- package/dist/trace/snapshot-resolver.d.ts +55 -0
- package/dist/trace/snapshot-resolver.d.ts.map +1 -0
- package/dist/trace/trace-parser.d.ts +50 -0
- package/dist/trace/trace-parser.d.ts.map +1 -0
- package/dist/trace/version-detector.d.ts +20 -0
- package/dist/trace/version-detector.d.ts.map +1 -0
- package/package.json +76 -6
- package/skills/README.md +20 -0
- package/skills/pie-failure-investigation/SKILL.md +16 -0
- package/skills/pie-quality-engineer/SKILL.md +66 -0
- package/skills/pie-release-readiness/SKILL.md +22 -0
- package/skills/pie-repair-review/SKILL.md +21 -0
- package/skills/pie-trace-to-repair/SKILL.md +35 -0
- package/src/authoring/author.ts +81 -0
- package/src/authoring/generation-contract.ts +283 -0
- package/src/authoring/hybrid-author.ts +115 -0
- package/src/authoring/index.ts +7 -0
- package/src/authoring/llm-author.ts +282 -0
- package/src/authoring/naming/index.ts +4 -0
- package/src/authoring/naming/name-plan.ts +28 -0
- package/src/authoring/naming/name-planner.ts +339 -0
- package/src/authoring/naming/naming-linter.ts +292 -0
- package/src/authoring/template-author.ts +39 -0
- package/src/cli/index.ts +1172 -0
- package/src/collectors/collector-interface.ts +8 -0
- package/src/collectors/environment-collector.ts +103 -0
- package/src/collectors/error-collector.ts +177 -0
- package/src/collectors/git-diff-collector.ts +190 -0
- package/src/collectors/live-browser-collector.ts +412 -0
- package/src/collectors/network-collector.ts +90 -0
- package/src/collectors/static-ast-collector.ts +226 -0
- package/src/collectors/trace-collector.ts +66 -0
- package/src/config/project-config.ts +114 -0
- package/src/core/errors/engine-errors.ts +90 -0
- package/src/core/types/diagnostic-context.ts +94 -0
- package/src/core/types/repair-evidence.ts +45 -0
- package/src/core/version.ts +25 -0
- package/src/diagnostics/diagnostic-contract.ts +79 -0
- package/src/diagnostics/diagnostic-report.ts +204 -0
- package/src/diagnostics/evidence-correlator.ts +117 -0
- package/src/diagnostics/external-failure-evidence.ts +72 -0
- package/src/diagnostics/failure-category.ts +40 -0
- package/src/diagnostics/failure-classifier.ts +701 -0
- package/src/evaluation/evaluation-scorecard.ts +198 -0
- package/src/evaluation/repository-inspector.ts +143 -0
- package/src/evaluation/repository-runner.ts +197 -0
- package/src/evidence/evidence-model.ts +76 -0
- package/src/evidence/evidence-normalizer.ts +322 -0
- package/src/evidence/evidence-redactor.ts +218 -0
- package/src/framework/config-adapter.ts +131 -0
- package/src/gate/index.ts +43 -0
- package/src/gate/oracle.ts +69 -0
- package/src/gate/quality.ts +101 -0
- package/src/gate/safety.ts +94 -0
- package/src/generator/architecture-generator.ts +582 -0
- package/src/generator/assertion-quality-gate.ts +88 -0
- package/src/generator/candidate-writer.ts +288 -0
- package/src/generator/test-generator.ts +52 -0
- package/src/governance/audit-log.ts +115 -0
- package/src/governance/governance-policy.ts +72 -0
- package/src/governance/team-dashboard.ts +189 -0
- package/src/governance/workflow-audit.ts +72 -0
- package/src/index.ts +107 -0
- package/src/integrations/ci-summary.ts +37 -0
- package/src/integrations/playwright-report-adapter.ts +173 -0
- package/src/integrations/quality-gate.ts +95 -0
- package/src/integrations/sarif-report.ts +110 -0
- package/src/intelligence/autonomous-repair-workflow.ts +139 -0
- package/src/intelligence/business-intent.ts +163 -0
- package/src/intelligence/evidence-bundle.ts +300 -0
- package/src/intelligence/failure-clustering.ts +134 -0
- package/src/intelligence/generation-agent.ts +561 -0
- package/src/intelligence/generation-verifier.ts +1115 -0
- package/src/intelligence/llm-proposal.ts +519 -0
- package/src/intelligence/locator-disambiguation-engine.ts +331 -0
- package/src/intelligence/model-contract.ts +205 -0
- package/src/intelligence/model-provider.ts +348 -0
- package/src/intelligence/project-intelligence.ts +140 -0
- package/src/intelligence/repair-agent.ts +297 -0
- package/src/intelligence/repository-profile.ts +796 -0
- package/src/intelligence/suite-intelligence.ts +305 -0
- package/src/intelligence/test-intelligence.ts +136 -0
- package/src/intelligence/trace-proposal-workflow.ts +318 -0
- package/src/intelligence/verified-llm-repair-workflow.ts +48 -0
- package/src/intelligence/workflow-planner.ts +966 -0
- package/src/mcp/server.ts +970 -0
- package/src/memory/stability-ledger.ts +243 -0
- package/src/perception/app-model.ts +32 -0
- package/src/perception/crawler.ts +263 -0
- package/src/perception/index.ts +3 -0
- package/src/repair/approval-service.ts +541 -0
- package/src/repair/assertion-hasher.ts +386 -0
- package/src/repair/ast-patcher.ts +223 -0
- package/src/repair/browser-operation-executor.ts +263 -0
- package/src/repair/diff-allowlist.ts +612 -0
- package/src/repair/intent-validator.ts +761 -0
- package/src/repair/repair-operations.ts +65 -0
- package/src/repair/repair-planner.ts +467 -0
- package/src/repair/repair-workflow.ts +241 -0
- package/src/repair/safe-operation-compiler.ts +68 -0
- package/src/repair/source-mapper.ts +156 -0
- package/src/repair/statement-patcher.ts +544 -0
- package/src/repair/verification-gates.ts +46 -0
- package/src/repair/workflow-plan.ts +59 -0
- package/src/repair/workspace-promoter.ts +313 -0
- package/src/reproduce/diagnostic-rerunner.ts +287 -0
- package/src/reproduce/direct-browser-investigator.ts +208 -0
- package/src/reproduce/interactive-workflow-runner.ts +828 -0
- package/src/sandbox/dependency-strategy.ts +215 -0
- package/src/sandbox/execution-runner.ts +408 -0
- package/src/sandbox/worktree-manager.ts +287 -0
- package/src/selector/abstention-policy.ts +175 -0
- package/src/selector/candidate-extractor.ts +343 -0
- package/src/selector/live-locator-verifier.ts +139 -0
- package/src/selector/repair-candidate-ranker.ts +92 -0
- package/src/selector/selector-scorer.ts +344 -0
- package/src/trace/adapters/adapter-registry.ts +49 -0
- package/src/trace/adapters/trace-adapter.ts +28 -0
- package/src/trace/adapters/unknown-adapter.ts +50 -0
- package/src/trace/adapters/v10-adapter.ts +39 -0
- package/src/trace/adapters/v4-adapter.ts +31 -0
- package/src/trace/adapters/v8-adapter.ts +42 -0
- package/src/trace/adapters/v9-adapter.ts +39 -0
- package/src/trace/archive-reader.ts +246 -0
- package/src/trace/snapshot-resolver.ts +308 -0
- package/src/trace/trace-parser.ts +467 -0
- package/src/trace/version-detector.ts +81 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { DiagnosticContext, FailureCategory } from '../core/types/diagnostic-context.js';
|
|
5
|
+
import { EvidenceBundle, EvidenceBundleSchema } from './llm-proposal.js';
|
|
6
|
+
import { EvidenceRedactor } from '../evidence/evidence-redactor.js';
|
|
7
|
+
import type { BrownfieldRepositoryGraph } from './repository-profile.js';
|
|
8
|
+
|
|
9
|
+
export interface EvidenceBundleBuildInput {
|
|
10
|
+
executionId: string;
|
|
11
|
+
testIdentity: string;
|
|
12
|
+
context: DiagnosticContext;
|
|
13
|
+
failureCategory?: FailureCategory | string | undefined;
|
|
14
|
+
sourceContext?: EvidenceBundle['sourceContext'] | undefined;
|
|
15
|
+
artifacts?: EvidenceBundle['artifacts'] | undefined;
|
|
16
|
+
redactionComplete: boolean;
|
|
17
|
+
limitations?: string[] | undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Builds the only payload that may cross the model-provider boundary. The
|
|
22
|
+
* builder carries references to artifacts rather than reading or uploading
|
|
23
|
+
* trace archives, and requires the caller to explicitly attest redaction.
|
|
24
|
+
*/
|
|
25
|
+
export function buildEvidenceBundle(input: EvidenceBundleBuildInput): EvidenceBundle {
|
|
26
|
+
const limitations = [...(input.limitations ?? [])];
|
|
27
|
+
if (!input.context.evidenceCompleteness.domAvailable) limitations.push('DOM/accessibility snapshot is unavailable');
|
|
28
|
+
if (!input.context.evidenceCompleteness.networkAvailable) limitations.push('Network timeline is unavailable');
|
|
29
|
+
if (!input.context.evidenceCompleteness.sourceAvailable) limitations.push('Source location is unavailable');
|
|
30
|
+
if (!input.redactionComplete) limitations.push('Caller has not attested that evidence is redaction-complete');
|
|
31
|
+
|
|
32
|
+
const redact = (value: string | undefined): string | undefined => value === undefined ? undefined : EvidenceRedactor.redactText(value).redactedText;
|
|
33
|
+
const diagnostic = {
|
|
34
|
+
playwrightVersion: input.context.playwrightVersion,
|
|
35
|
+
traceFormatVersion: input.context.traceFormatVersion,
|
|
36
|
+
failedAction: {
|
|
37
|
+
callId: input.context.failedAction.callId,
|
|
38
|
+
action: input.context.failedAction.action,
|
|
39
|
+
...(redact(input.context.failedAction.selector) ? { selector: redact(input.context.failedAction.selector) } : {}),
|
|
40
|
+
...(redact(input.context.failedAction.sourceFile) ? { sourceFile: redact(input.context.failedAction.sourceFile) } : {}),
|
|
41
|
+
...(input.context.failedAction.line !== undefined ? { line: input.context.failedAction.line } : {}),
|
|
42
|
+
...(input.context.failedAction.column !== undefined ? { column: input.context.failedAction.column } : {}),
|
|
43
|
+
...(redact(input.context.failedAction.errorMessage) ? { errorMessage: redact(input.context.failedAction.errorMessage) } : {}),
|
|
44
|
+
...(redact(input.context.failedAction.errorStack) ? { errorStack: redact(input.context.failedAction.errorStack) } : {}),
|
|
45
|
+
...(input.context.failedAction.durationMs !== undefined ? { durationMs: input.context.failedAction.durationMs } : {}),
|
|
46
|
+
...(input.context.failedAction.logs ? { logs: input.context.failedAction.logs.map((log) => EvidenceRedactor.redactText(log).redactedText) } : {}),
|
|
47
|
+
},
|
|
48
|
+
snapshots: {
|
|
49
|
+
...(input.context.snapshots.before ? { before: EvidenceRedactor.redactDomContent(input.context.snapshots.before) } : {}),
|
|
50
|
+
...(input.context.snapshots.action ? { action: EvidenceRedactor.redactDomContent(input.context.snapshots.action) } : {}),
|
|
51
|
+
...(input.context.snapshots.after ? { after: EvidenceRedactor.redactDomContent(input.context.snapshots.after) } : {}),
|
|
52
|
+
},
|
|
53
|
+
networkEvents: input.context.networkEvents.slice(0, 100).map((event) => ({
|
|
54
|
+
method: event.method,
|
|
55
|
+
url: EvidenceRedactor.redactText(event.url).redactedText,
|
|
56
|
+
status: event.status,
|
|
57
|
+
...(event.statusText ? { statusText: EvidenceRedactor.redactText(event.statusText).redactedText } : {}),
|
|
58
|
+
...(event.errorText ? { errorText: EvidenceRedactor.redactText(event.errorText).redactedText } : {}),
|
|
59
|
+
})),
|
|
60
|
+
consoleEvents: input.context.consoleEvents.slice(0, 100).map((event) => ({
|
|
61
|
+
type: event.type,
|
|
62
|
+
text: EvidenceRedactor.redactText(event.text).redactedText,
|
|
63
|
+
})),
|
|
64
|
+
evidenceCompleteness: input.context.evidenceCompleteness,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return EvidenceBundleSchema.parse({
|
|
68
|
+
bundleId: `bundle-${input.executionId}`,
|
|
69
|
+
executionId: input.executionId,
|
|
70
|
+
testIdentity: input.testIdentity,
|
|
71
|
+
failedActionId: input.context.failedAction.callId,
|
|
72
|
+
failureCategory: input.failureCategory,
|
|
73
|
+
sourceContext: (input.sourceContext ?? []).map((source) => ({
|
|
74
|
+
...source,
|
|
75
|
+
content: EvidenceRedactor.redactText(source.content).redactedText,
|
|
76
|
+
})),
|
|
77
|
+
artifacts: input.artifacts ?? [],
|
|
78
|
+
diagnostic,
|
|
79
|
+
redactionComplete: input.redactionComplete,
|
|
80
|
+
limitations: [...new Set(limitations)],
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const VerificationLayerStatusSchema = z.enum([
|
|
85
|
+
'PASS',
|
|
86
|
+
'FAIL',
|
|
87
|
+
'NOT_VERIFIED',
|
|
88
|
+
'NOT_APPLICABLE',
|
|
89
|
+
]);
|
|
90
|
+
export type VerificationLayerStatus = z.infer<typeof VerificationLayerStatusSchema>;
|
|
91
|
+
|
|
92
|
+
export const ExplorationFindingCategorySchema = z.enum([
|
|
93
|
+
'SPEC_VIOLATION',
|
|
94
|
+
'RUNTIME_ERROR',
|
|
95
|
+
'AUTOMATION_GAP',
|
|
96
|
+
'OBSERVED_BEHAVIOR',
|
|
97
|
+
'NOT_VERIFIED',
|
|
98
|
+
]);
|
|
99
|
+
export type ExplorationFindingCategory = z.infer<typeof ExplorationFindingCategorySchema>;
|
|
100
|
+
|
|
101
|
+
export const VerifiedLocatorEvidenceSchema = z.object({
|
|
102
|
+
id: z.string().min(1),
|
|
103
|
+
route: z.string().min(1),
|
|
104
|
+
strategy: z.enum(['role', 'label', 'placeholder', 'testId', 'text', 'css', 'frame']),
|
|
105
|
+
expression: z.string().min(1),
|
|
106
|
+
role: z.string().optional(),
|
|
107
|
+
accessibleName: z.string().optional(),
|
|
108
|
+
label: z.string().optional(),
|
|
109
|
+
placeholder: z.string().optional(),
|
|
110
|
+
testId: z.string().optional(),
|
|
111
|
+
frameSelector: z.string().optional(),
|
|
112
|
+
matchCount: z.number().int().min(0),
|
|
113
|
+
visible: z.boolean(),
|
|
114
|
+
enabled: z.boolean(),
|
|
115
|
+
confidence: z.number().min(0).max(1),
|
|
116
|
+
timestamp: z.string(),
|
|
117
|
+
});
|
|
118
|
+
export type VerifiedLocatorEvidence = z.infer<typeof VerifiedLocatorEvidenceSchema>;
|
|
119
|
+
|
|
120
|
+
export const ExplorationNetworkRecordSchema = z.object({
|
|
121
|
+
id: z.string().min(1),
|
|
122
|
+
scenarioId: z.string().optional(),
|
|
123
|
+
method: z.string(),
|
|
124
|
+
url: z.string(),
|
|
125
|
+
status: z.number().int(),
|
|
126
|
+
durationMs: z.number().optional(),
|
|
127
|
+
isError: z.boolean(),
|
|
128
|
+
resourceType: z.string().optional(),
|
|
129
|
+
errorText: z.string().optional(),
|
|
130
|
+
timestamp: z.string(),
|
|
131
|
+
});
|
|
132
|
+
export type ExplorationNetworkRecord = z.infer<typeof ExplorationNetworkRecordSchema>;
|
|
133
|
+
|
|
134
|
+
export const ExplorationActionRecordSchema = z.object({
|
|
135
|
+
id: z.string().min(1),
|
|
136
|
+
scenarioId: z.string().min(1),
|
|
137
|
+
stepIndex: z.number().int().min(0),
|
|
138
|
+
action: z.string().min(1),
|
|
139
|
+
targetLocatorId: z.string().optional(),
|
|
140
|
+
targetExpression: z.string().optional(),
|
|
141
|
+
valueRedacted: z.string().optional(),
|
|
142
|
+
routeBefore: z.string(),
|
|
143
|
+
routeAfter: z.string(),
|
|
144
|
+
domBeforeSummary: z.string(),
|
|
145
|
+
domAfterSummary: z.string(),
|
|
146
|
+
domDiff: z.array(z.string()).default([]),
|
|
147
|
+
networkIds: z.array(z.string()).default([]),
|
|
148
|
+
consoleErrors: z.array(z.string()).default([]),
|
|
149
|
+
screenshotPath: z.string().optional(),
|
|
150
|
+
status: z.enum(['EXECUTED', 'FAILED', 'BLOCKED_BY_POLICY', 'SKIPPED']),
|
|
151
|
+
timestamp: z.string(),
|
|
152
|
+
});
|
|
153
|
+
export type ExplorationActionRecord = z.infer<typeof ExplorationActionRecordSchema>;
|
|
154
|
+
|
|
155
|
+
export const ExplorationFindingSchema = z.object({
|
|
156
|
+
id: z.string().min(1),
|
|
157
|
+
category: ExplorationFindingCategorySchema,
|
|
158
|
+
scenarioId: z.string().optional(),
|
|
159
|
+
evidenceIds: z.array(z.string()).default([]),
|
|
160
|
+
expectedSource: z.string().optional(),
|
|
161
|
+
expectedBehavior: z.string().optional(),
|
|
162
|
+
actualBehavior: z.string().min(1),
|
|
163
|
+
confidence: z.number().min(0).max(1),
|
|
164
|
+
severity: z.enum(['critical', 'high', 'medium', 'low', 'info']).default('medium'),
|
|
165
|
+
});
|
|
166
|
+
export type ExplorationFinding = z.infer<typeof ExplorationFindingSchema>;
|
|
167
|
+
|
|
168
|
+
export const ScenarioExplorationStatusSchema = z.object({
|
|
169
|
+
scenarioId: z.string().min(1),
|
|
170
|
+
status: z.enum(['VERIFIED', 'BLOCKED', 'DEFECT_DETECTED', 'NOT_VERIFIED', 'PARTIAL']),
|
|
171
|
+
verificationLayers: z.object({
|
|
172
|
+
UI: VerificationLayerStatusSchema,
|
|
173
|
+
API: VerificationLayerStatusSchema,
|
|
174
|
+
CLIENT_RELOAD: VerificationLayerStatusSchema,
|
|
175
|
+
DB: VerificationLayerStatusSchema,
|
|
176
|
+
}),
|
|
177
|
+
reason: z.string().optional(),
|
|
178
|
+
evidenceIds: z.array(z.string()).default([]),
|
|
179
|
+
});
|
|
180
|
+
export type ScenarioExplorationStatus = z.infer<typeof ScenarioExplorationStatusSchema>;
|
|
181
|
+
|
|
182
|
+
export const ExplorationRouteObservationSchema = z.object({
|
|
183
|
+
route: z.string().min(1),
|
|
184
|
+
title: z.string().optional(),
|
|
185
|
+
discoveredAt: z.string(),
|
|
186
|
+
locatorIds: z.array(z.string()).default([]),
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
export const EvidenceIndexEntrySchema = z.object({
|
|
190
|
+
id: z.string().min(1),
|
|
191
|
+
kind: z.string().min(1),
|
|
192
|
+
source: z.string().min(1),
|
|
193
|
+
timestamp: z.string(),
|
|
194
|
+
confidence: z.number().min(0).max(1),
|
|
195
|
+
scenarioIds: z.array(z.string()).default([]),
|
|
196
|
+
description: z.string(),
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
export const ExplorationContextBundleSchema = z.object({
|
|
200
|
+
bundleVersion: z.literal('1.0').default('1.0'),
|
|
201
|
+
bundleId: z.string().min(1),
|
|
202
|
+
createdAt: z.string(),
|
|
203
|
+
topology: z.enum(['monorepo', 'standalone', 'source-only']),
|
|
204
|
+
runtimeVerified: z.boolean(),
|
|
205
|
+
baseUrl: z.string().optional(),
|
|
206
|
+
authentication: z.object({
|
|
207
|
+
configured: z.boolean(),
|
|
208
|
+
method: z.enum(['none', 'storage-state', 'login-flow', 'existing-fixture']),
|
|
209
|
+
authenticated: z.boolean(),
|
|
210
|
+
secretsExposed: z.literal(false).default(false),
|
|
211
|
+
}),
|
|
212
|
+
requirements: z.object({
|
|
213
|
+
featureName: z.string().min(1),
|
|
214
|
+
requirementIds: z.array(z.string()).default([]),
|
|
215
|
+
scenarioIds: z.array(z.string()).default([]),
|
|
216
|
+
workflowPlanHash: z.string().optional(),
|
|
217
|
+
}),
|
|
218
|
+
routes: z.array(ExplorationRouteObservationSchema).default([]),
|
|
219
|
+
locators: z.array(VerifiedLocatorEvidenceSchema).default([]),
|
|
220
|
+
actions: z.array(ExplorationActionRecordSchema).default([]),
|
|
221
|
+
network: z.array(ExplorationNetworkRecordSchema).default([]),
|
|
222
|
+
consoleErrors: z.array(z.string()).default([]),
|
|
223
|
+
persistenceObservations: z.array(z.object({
|
|
224
|
+
scenarioId: z.string(),
|
|
225
|
+
clientReloadPreserved: z.boolean().optional(),
|
|
226
|
+
dbStatus: VerificationLayerStatusSchema,
|
|
227
|
+
note: z.string(),
|
|
228
|
+
})).default([]),
|
|
229
|
+
scenarioStatuses: z.array(ScenarioExplorationStatusSchema).default([]),
|
|
230
|
+
findings: z.array(ExplorationFindingSchema).default([]),
|
|
231
|
+
repositoryGraph: z.custom<BrownfieldRepositoryGraph>().optional(),
|
|
232
|
+
evidenceIndex: z.array(EvidenceIndexEntrySchema).default([]),
|
|
233
|
+
limitations: z.array(z.string()).default([]),
|
|
234
|
+
redactionComplete: z.literal(true).default(true),
|
|
235
|
+
});
|
|
236
|
+
export type ExplorationContextBundle = z.infer<typeof ExplorationContextBundleSchema>;
|
|
237
|
+
|
|
238
|
+
export function sanitizeUrlSecrets(rawUrl: string): string {
|
|
239
|
+
const redactedText = EvidenceRedactor.redactText(rawUrl).redactedText;
|
|
240
|
+
try {
|
|
241
|
+
const parsed = new URL(redactedText);
|
|
242
|
+
const sensitiveParamRe = /^(?:token|access_token|id_token|refresh_token|auth|secret|password|passwd|api_?key|key|session|jwt|code)$/i;
|
|
243
|
+
let changed = false;
|
|
244
|
+
for (const paramKey of [...parsed.searchParams.keys()]) {
|
|
245
|
+
if (sensitiveParamRe.test(paramKey)) {
|
|
246
|
+
parsed.searchParams.set(paramKey, '[REDACTED]');
|
|
247
|
+
changed = true;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return changed ? parsed.toString() : redactedText;
|
|
251
|
+
} catch {
|
|
252
|
+
return redactedText.replace(/([?&](?:token|access_token|id_token|refresh_token|auth|secret|password|api_?key|key|session|jwt)=)[^&\s]+/gi, '$1[REDACTED]');
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function redactExplorationBundle(bundle: ExplorationContextBundle): ExplorationContextBundle {
|
|
257
|
+
const redact = (value: string): string => EvidenceRedactor.redactText(value).redactedText;
|
|
258
|
+
return ExplorationContextBundleSchema.parse({
|
|
259
|
+
...bundle,
|
|
260
|
+
...(bundle.baseUrl ? { baseUrl: sanitizeUrlSecrets(bundle.baseUrl) } : {}),
|
|
261
|
+
authentication: {
|
|
262
|
+
...bundle.authentication,
|
|
263
|
+
secretsExposed: false,
|
|
264
|
+
},
|
|
265
|
+
actions: bundle.actions.map((a) => ({
|
|
266
|
+
...a,
|
|
267
|
+
domBeforeSummary: EvidenceRedactor.redactDomContent(a.domBeforeSummary),
|
|
268
|
+
domAfterSummary: EvidenceRedactor.redactDomContent(a.domAfterSummary),
|
|
269
|
+
domDiff: a.domDiff.map(redact),
|
|
270
|
+
consoleErrors: a.consoleErrors.map(redact),
|
|
271
|
+
...(a.valueRedacted ? { valueRedacted: redact(a.valueRedacted) } : {}),
|
|
272
|
+
})),
|
|
273
|
+
network: bundle.network.map((n) => ({
|
|
274
|
+
...n,
|
|
275
|
+
url: sanitizeUrlSecrets(n.url),
|
|
276
|
+
...(n.errorText ? { errorText: redact(n.errorText) } : {}),
|
|
277
|
+
})),
|
|
278
|
+
consoleErrors: bundle.consoleErrors.map(redact),
|
|
279
|
+
findings: bundle.findings.map((f) => ({
|
|
280
|
+
...f,
|
|
281
|
+
actualBehavior: redact(f.actualBehavior),
|
|
282
|
+
...(f.expectedBehavior ? { expectedBehavior: redact(f.expectedBehavior) } : {}),
|
|
283
|
+
})),
|
|
284
|
+
redactionComplete: true,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export async function saveExplorationContextBundle(
|
|
289
|
+
bundlePath: string,
|
|
290
|
+
bundle: ExplorationContextBundle,
|
|
291
|
+
): Promise<void> {
|
|
292
|
+
const clean = redactExplorationBundle(bundle);
|
|
293
|
+
await fs.mkdir(path.dirname(bundlePath), { recursive: true });
|
|
294
|
+
await fs.writeFile(bundlePath, `${JSON.stringify(clean, null, 2)}\n`, 'utf8');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export async function loadExplorationContextBundle(bundlePath: string): Promise<ExplorationContextBundle> {
|
|
298
|
+
const raw = await fs.readFile(bundlePath, 'utf8');
|
|
299
|
+
return ExplorationContextBundleSchema.parse(JSON.parse(raw));
|
|
300
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { normalizeFailureCategory } from '../diagnostics/failure-category.js';
|
|
3
|
+
|
|
4
|
+
export interface FailureObservation {
|
|
5
|
+
id: string;
|
|
6
|
+
repository?: string | undefined;
|
|
7
|
+
testTitle?: string | undefined;
|
|
8
|
+
sourceFile?: string | undefined;
|
|
9
|
+
category: string;
|
|
10
|
+
action?: string | undefined;
|
|
11
|
+
selector?: string | undefined;
|
|
12
|
+
errorMessage?: string | undefined;
|
|
13
|
+
errorStack?: string | undefined;
|
|
14
|
+
networkStatuses?: number[] | undefined;
|
|
15
|
+
tracePath?: string | undefined;
|
|
16
|
+
timestamp?: number | undefined;
|
|
17
|
+
durationMs?: number | undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface FailureFingerprint {
|
|
21
|
+
key: string;
|
|
22
|
+
category: string;
|
|
23
|
+
action: string;
|
|
24
|
+
messageSignature: string;
|
|
25
|
+
networkSignature: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface FailureCluster {
|
|
29
|
+
clusterId: string;
|
|
30
|
+
fingerprint: FailureFingerprint;
|
|
31
|
+
count: number;
|
|
32
|
+
observationIds: string[];
|
|
33
|
+
affectedRepositories: string[];
|
|
34
|
+
affectedTests: string[];
|
|
35
|
+
representative: FailureObservation;
|
|
36
|
+
severity: 'critical' | 'high' | 'medium' | 'low' | 'unknown';
|
|
37
|
+
evidenceConsistency: number;
|
|
38
|
+
firstSeen?: number | undefined;
|
|
39
|
+
lastSeen?: number | undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const ANSI_PATTERN = /\u001b\[[0-?]*[ -/]*[@-~]/g;
|
|
43
|
+
const PATH_PATTERN = /(?:[a-zA-Z]:[\\/]|\/)[^\s'"`]+/g;
|
|
44
|
+
const UUID_PATTERN = /\b[0-9a-f]{8}-[0-9a-f-]{27,}\b/gi;
|
|
45
|
+
const HASH_PATTERN = /\b[0-9a-f]{8,}\b/gi;
|
|
46
|
+
const NUMBER_PATTERN = /\d+(?:\.\d+)?/g;
|
|
47
|
+
|
|
48
|
+
function normalizeText(value: string | undefined): string {
|
|
49
|
+
return (value ?? '')
|
|
50
|
+
.replace(ANSI_PATTERN, '')
|
|
51
|
+
.replace(PATH_PATTERN, '<path>')
|
|
52
|
+
.replace(UUID_PATTERN, '<uuid>')
|
|
53
|
+
.replace(HASH_PATTERN, '<hash>')
|
|
54
|
+
.replace(NUMBER_PATTERN, '<n>')
|
|
55
|
+
.replace(/\s+/g, ' ')
|
|
56
|
+
.trim()
|
|
57
|
+
.toLowerCase();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function messageSignature(observation: FailureObservation): string {
|
|
61
|
+
const source = observation.errorMessage || observation.errorStack || 'no-error-message';
|
|
62
|
+
const firstUsefulLine = source
|
|
63
|
+
.split(/\r?\n/)
|
|
64
|
+
.map((line) => normalizeText(line))
|
|
65
|
+
.find((line) => line.length > 0) ?? 'no-error-message';
|
|
66
|
+
return firstUsefulLine.slice(0, 300);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function networkSignature(statuses: number[] | undefined): string {
|
|
70
|
+
return [...new Set(statuses ?? [])].sort((left, right) => left - right).join(',') || 'none';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function fingerprintFailure(observation: FailureObservation): FailureFingerprint {
|
|
74
|
+
const category = normalizeFailureCategory(observation.category);
|
|
75
|
+
const action = normalizeText(observation.action) || 'unknown-action';
|
|
76
|
+
const message = messageSignature(observation);
|
|
77
|
+
const network = networkSignature(observation.networkStatuses);
|
|
78
|
+
const material = [category, action, message, network].join('|');
|
|
79
|
+
const digest = crypto.createHash('sha256').update(material).digest('hex').slice(0, 16);
|
|
80
|
+
return {
|
|
81
|
+
key: `failure-${digest}`,
|
|
82
|
+
category,
|
|
83
|
+
action,
|
|
84
|
+
messageSignature: message,
|
|
85
|
+
networkSignature: network,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function severityFor(category: string): FailureCluster['severity'] {
|
|
90
|
+
const normalized = category.toUpperCase();
|
|
91
|
+
if (['BACKEND_500', 'ASSERTION_FAILURE', 'ASSERTION_FAILED', 'BUSINESS_DEFECT', 'TEST_FAILURE'].includes(normalized)) return 'critical';
|
|
92
|
+
if (['AUTH_EXPIRED', 'AUTH_SESSION_EXPIRED', 'NETWORK_OFFLINE', 'ENVIRONMENT_FAILURE'].includes(normalized)) return 'high';
|
|
93
|
+
if (['LOCATOR_DRIFT', 'AMBIGUOUS_MATCH', 'OBSTRUCTED_ELEMENT', 'DISABLED_ELEMENT', 'FRAME_OR_IFRAME'].includes(normalized)) return 'medium';
|
|
94
|
+
if (['TIMING_RACE', 'DYNAMIC_CONTENT_SLOW', 'HYDRATION_OR_TIMING'].includes(normalized)) return 'low';
|
|
95
|
+
return 'unknown';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function uniqueSorted(values: Array<string | undefined>): string[] {
|
|
99
|
+
return [...new Set(values.filter((value): value is string => Boolean(value)))].sort();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function clusterFailures(observations: FailureObservation[]): FailureCluster[] {
|
|
103
|
+
const groups = new Map<string, { fingerprint: FailureFingerprint; observations: FailureObservation[] }>();
|
|
104
|
+
for (const observation of observations) {
|
|
105
|
+
const fingerprint = fingerprintFailure(observation);
|
|
106
|
+
const group = groups.get(fingerprint.key);
|
|
107
|
+
if (group) group.observations.push(observation);
|
|
108
|
+
else groups.set(fingerprint.key, { fingerprint, observations: [observation] });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return [...groups.values()].map(({ fingerprint, observations }) => {
|
|
112
|
+
const timestamps = observations.map((observation) => observation.timestamp).filter((value): value is number => typeof value === 'number');
|
|
113
|
+
const repositories = uniqueSorted(observations.map((observation) => observation.repository));
|
|
114
|
+
const tests = uniqueSorted(observations.map((observation) => observation.testTitle));
|
|
115
|
+
const categoryAgreement = observations.every((observation) => normalizeFailureCategory(observation.category) === fingerprint.category) ? 1 : 0.5;
|
|
116
|
+
const actionAgreement = observations.every((observation) => normalizeText(observation.action) === fingerprint.action) ? 1 : 0.5;
|
|
117
|
+
return {
|
|
118
|
+
clusterId: fingerprint.key,
|
|
119
|
+
fingerprint,
|
|
120
|
+
count: observations.length,
|
|
121
|
+
observationIds: observations.map((observation) => observation.id).sort(),
|
|
122
|
+
affectedRepositories: repositories,
|
|
123
|
+
affectedTests: tests,
|
|
124
|
+
representative: observations[0]!,
|
|
125
|
+
severity: severityFor(fingerprint.category),
|
|
126
|
+
evidenceConsistency: Number(((categoryAgreement + actionAgreement) / 2).toFixed(3)),
|
|
127
|
+
firstSeen: timestamps.length > 0 ? Math.min(...timestamps) : undefined,
|
|
128
|
+
lastSeen: timestamps.length > 0 ? Math.max(...timestamps) : undefined,
|
|
129
|
+
};
|
|
130
|
+
}).sort((left, right) => {
|
|
131
|
+
const severityRank = { critical: 4, high: 3, medium: 2, low: 1, unknown: 0 };
|
|
132
|
+
return severityRank[right.severity] - severityRank[left.severity] || right.count - left.count || left.clusterId.localeCompare(right.clusterId);
|
|
133
|
+
});
|
|
134
|
+
}
|