playwright-intelligence-engine 0.0.0-stage → 2.0.1
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,241 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import fsSync from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { ArchiveReader } from '../trace/archive-reader.js';
|
|
5
|
+
import { TraceParser } from '../trace/trace-parser.js';
|
|
6
|
+
import { EvidenceCorrelator, CorrelatedEvidence } from '../diagnostics/evidence-correlator.js';
|
|
7
|
+
import { DiagnosticReportGenerator } from '../diagnostics/diagnostic-report.js';
|
|
8
|
+
import { bootstrapProjectIntelligence, recordProjectSemanticVerification } from '../intelligence/project-intelligence.js';
|
|
9
|
+
import { parseExternalFailureEvidence, supplementDiagnosticContext } from '../diagnostics/external-failure-evidence.js';
|
|
10
|
+
import { ApprovalService, StagedProposal } from './approval-service.js';
|
|
11
|
+
import { RepairPlanner, RepairProposal, VerifiedRepairResult } from './repair-planner.js';
|
|
12
|
+
import { WorktreeManager, WorktreeInfo } from '../sandbox/worktree-manager.js';
|
|
13
|
+
import { recordRepairWorkflow } from '../governance/workflow-audit.js';
|
|
14
|
+
import { compileSafeRepairOperation } from './safe-operation-compiler.js';
|
|
15
|
+
import { discoverRepairSourceMapping } from './source-mapper.js';
|
|
16
|
+
|
|
17
|
+
export type RepairWorkflowStatus = 'ABSTAINED' | 'INPUT_REQUIRED' | 'OPERATION_PROPOSAL_ONLY' | 'PROPOSAL_READY' | 'VERIFIED_READY_FOR_APPROVAL' | 'REJECTED';
|
|
18
|
+
|
|
19
|
+
export interface RepairWorkflowInput {
|
|
20
|
+
tracePath: string;
|
|
21
|
+
workspaceRoot?: string | undefined;
|
|
22
|
+
errorContextPath?: string | undefined;
|
|
23
|
+
testFilePath?: string | undefined;
|
|
24
|
+
pomFilePath?: string | undefined;
|
|
25
|
+
className?: string | undefined;
|
|
26
|
+
memberName?: string | undefined;
|
|
27
|
+
configPath?: string | undefined;
|
|
28
|
+
project?: string | undefined;
|
|
29
|
+
execute?: boolean | undefined;
|
|
30
|
+
worktreeId?: string | undefined;
|
|
31
|
+
operation?: unknown;
|
|
32
|
+
requireOperationTargetMatch?: boolean | undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface RepairWorkflowResult {
|
|
36
|
+
status: RepairWorkflowStatus;
|
|
37
|
+
correlated: CorrelatedEvidence;
|
|
38
|
+
proposedLocator?: string | undefined;
|
|
39
|
+
candidate?: {
|
|
40
|
+
locatorExpression: string;
|
|
41
|
+
score: number;
|
|
42
|
+
element: RepairProposal['candidateElement'];
|
|
43
|
+
} | undefined;
|
|
44
|
+
proposal?: RepairProposal | undefined;
|
|
45
|
+
stagedProposal?: StagedProposal | undefined;
|
|
46
|
+
worktree?: WorktreeInfo | undefined;
|
|
47
|
+
verification?: VerifiedRepairResult | undefined;
|
|
48
|
+
reason?: string | undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function assertWorkspacePath(root: string, target: string, label: string): string {
|
|
52
|
+
const canonicalRoot = fsSync.realpathSync(root);
|
|
53
|
+
const resolved = path.resolve(root, target);
|
|
54
|
+
if (!fsSync.existsSync(resolved)) throw new Error(`${label} not found: '${resolved}'`);
|
|
55
|
+
const canonicalTarget = fsSync.realpathSync(resolved);
|
|
56
|
+
const relative = path.relative(canonicalRoot, canonicalTarget);
|
|
57
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) throw new Error(`${label} escapes workspace root: '${target}'`);
|
|
58
|
+
return canonicalTarget;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function resolveReadOnlyPath(root: string, target: string, label: string): string {
|
|
62
|
+
const resolved = path.isAbsolute(target) ? target : path.resolve(root, target);
|
|
63
|
+
if (!fsSync.existsSync(resolved)) throw new Error(`${label} not found: '${resolved}'`);
|
|
64
|
+
return fsSync.realpathSync(resolved);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function loadCorrelatedEvidence(input: RepairWorkflowInput, workspaceRoot: string): Promise<CorrelatedEvidence> {
|
|
68
|
+
const tracePath = resolveReadOnlyPath(workspaceRoot, input.tracePath, 'Trace archive');
|
|
69
|
+
const { traceEntryName, networkEntryName, traceEntryNames, networkEntryNames } = ArchiveReader.findTraceFileEntries(tracePath);
|
|
70
|
+
if (!traceEntryName) throw new Error(`No .trace event stream found in archive '${tracePath}'`);
|
|
71
|
+
const traceText = await ArchiveReader.readEntriesAsText(tracePath, traceEntryNames ?? [traceEntryName]);
|
|
72
|
+
const networkText = (networkEntryNames ?? (networkEntryName ? [networkEntryName] : [])).length > 0
|
|
73
|
+
? await ArchiveReader.readEntriesAsText(tracePath, networkEntryNames ?? [networkEntryName!])
|
|
74
|
+
: '';
|
|
75
|
+
let context = TraceParser.assembleDiagnosticContext(traceText.split('\n'), networkText.split('\n'));
|
|
76
|
+
if (input.errorContextPath) {
|
|
77
|
+
const errorContextPath = resolveReadOnlyPath(workspaceRoot, input.errorContextPath, 'Error context');
|
|
78
|
+
context = supplementDiagnosticContext(context, parseExternalFailureEvidence(await fs.readFile(errorContextPath, 'utf8')));
|
|
79
|
+
}
|
|
80
|
+
return EvidenceCorrelator.correlate(context);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function stageProposal(proposal: RepairProposal, worktreeId: string): StagedProposal {
|
|
84
|
+
const testFileContent = fsSync.readFileSync(proposal.testFilePath, 'utf8');
|
|
85
|
+
return ApprovalService.getInstance().stageProposal({
|
|
86
|
+
targetFilePath: proposal.pomFilePath,
|
|
87
|
+
targetClassName: proposal.className,
|
|
88
|
+
targetMemberName: proposal.memberName,
|
|
89
|
+
originalSourceContent: proposal.originalSource,
|
|
90
|
+
replacementSelector: proposal.replacementSelector,
|
|
91
|
+
fullDiff: proposal.fullDiff ?? '',
|
|
92
|
+
patchedSource: proposal.proposedPatchedSource,
|
|
93
|
+
testFilePath: proposal.testFilePath,
|
|
94
|
+
testFileContent,
|
|
95
|
+
worktreeId,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Trace-to-repair bridge. It is proposal-only by default. `execute: true`
|
|
101
|
+
* creates a dedicated worktree and runs the existing verification lifecycle;
|
|
102
|
+
* it never writes the active workspace or issues an approval token.
|
|
103
|
+
*/
|
|
104
|
+
export async function runRepairWorkflow(input: RepairWorkflowInput): Promise<RepairWorkflowResult> {
|
|
105
|
+
const workspaceRoot = path.resolve(input.workspaceRoot ?? process.cwd());
|
|
106
|
+
if (!fsSync.existsSync(workspaceRoot)) throw new Error(`Workspace root does not exist: '${workspaceRoot}'`);
|
|
107
|
+
const correlated = await loadCorrelatedEvidence(input, workspaceRoot);
|
|
108
|
+
const intelligence = await bootstrapProjectIntelligence(workspaceRoot);
|
|
109
|
+
const selected = DiagnosticReportGenerator.selectRepairCandidate(correlated, intelligence);
|
|
110
|
+
|
|
111
|
+
if (correlated.classification.shouldAbstain) {
|
|
112
|
+
const result = { status: 'ABSTAINED' as const, correlated, reason: correlated.classification.abstentionReason ?? correlated.classification.reason };
|
|
113
|
+
await recordRepairWorkflow(workspaceRoot, { status: result.status, category: correlated.classification.category });
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
if (!selected) {
|
|
117
|
+
const result = { status: 'ABSTAINED' as const, correlated, reason: 'No unique, semantically compatible repair candidate survived PIE abstention gates.' };
|
|
118
|
+
await recordRepairWorkflow(workspaceRoot, { status: result.status, category: correlated.classification.category });
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const candidate = { locatorExpression: selected.locatorExpression, score: selected.breakdown.score, element: selected.element };
|
|
123
|
+
let explicitReplacement: string | undefined;
|
|
124
|
+
if (input.operation !== undefined) {
|
|
125
|
+
if (input.requireOperationTargetMatch === true) {
|
|
126
|
+
const operation = input.operation && typeof input.operation === 'object' ? input.operation as Record<string, unknown> : undefined;
|
|
127
|
+
const failedSelector = correlated.context.failedAction.selector?.trim();
|
|
128
|
+
const operationTarget = typeof operation?.target === 'string' ? operation.target.trim() : undefined;
|
|
129
|
+
if (!failedSelector || !operationTarget || operationTarget !== failedSelector) {
|
|
130
|
+
const result = { status: 'ABSTAINED' as const, correlated, proposedLocator: selected.locatorExpression, candidate, reason: 'The proposed operation target does not exactly match the deterministic failed selector.' };
|
|
131
|
+
await recordRepairWorkflow(workspaceRoot, { status: result.status, category: correlated.classification.category });
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const compiled = compileSafeRepairOperation(input.operation);
|
|
136
|
+
if (compiled.readiness === 'ABSTAIN') {
|
|
137
|
+
const result = { status: 'ABSTAINED' as const, correlated, proposedLocator: selected.locatorExpression, candidate, reason: compiled.reason };
|
|
138
|
+
await recordRepairWorkflow(workspaceRoot, { status: result.status, category: correlated.classification.category });
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
if (compiled.readiness === 'PROPOSAL_ONLY') {
|
|
142
|
+
const result = { status: 'OPERATION_PROPOSAL_ONLY' as const, correlated, proposedLocator: selected.locatorExpression, candidate, reason: compiled.reason };
|
|
143
|
+
await recordRepairWorkflow(workspaceRoot, { status: result.status, category: correlated.classification.category });
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
explicitReplacement = compiled.replacementLocator;
|
|
147
|
+
}
|
|
148
|
+
const mapping = input.testFilePath && input.pomFilePath && input.className && input.memberName
|
|
149
|
+
? undefined
|
|
150
|
+
: await discoverRepairSourceMapping({ workspaceRoot, failedAction: correlated.context.failedAction });
|
|
151
|
+
const resolvedTestFilePath = input.testFilePath ?? mapping?.testFilePath;
|
|
152
|
+
const resolvedPomFilePath = input.pomFilePath ?? mapping?.pomFilePath;
|
|
153
|
+
const resolvedClassName = input.className ?? mapping?.className;
|
|
154
|
+
const resolvedMemberName = input.memberName ?? mapping?.memberName;
|
|
155
|
+
const required = [
|
|
156
|
+
['testFilePath', resolvedTestFilePath],
|
|
157
|
+
['pomFilePath', resolvedPomFilePath],
|
|
158
|
+
['className', resolvedClassName],
|
|
159
|
+
['memberName', resolvedMemberName],
|
|
160
|
+
] as const;
|
|
161
|
+
const missing = required.filter(([, value]) => !value).map(([name]) => name);
|
|
162
|
+
if (missing.length > 0) {
|
|
163
|
+
const result = { status: 'INPUT_REQUIRED' as const, correlated, proposedLocator: selected.locatorExpression, candidate, reason: `Repair mapping is required: ${missing.join(', ')}` };
|
|
164
|
+
await recordRepairWorkflow(workspaceRoot, { status: result.status, category: correlated.classification.category });
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const testFilePath = assertWorkspacePath(workspaceRoot, resolvedTestFilePath!, 'Test file');
|
|
169
|
+
const pomFilePath = assertWorkspacePath(workspaceRoot, resolvedPomFilePath!, 'Page Object file');
|
|
170
|
+
const proposal = RepairPlanner.planRepair({
|
|
171
|
+
originalActionId: correlated.context.failedAction.callId,
|
|
172
|
+
originalSelector: correlated.context.failedAction.selector ?? '',
|
|
173
|
+
candidateElement: selected.element,
|
|
174
|
+
candidateScore: selected.breakdown.score,
|
|
175
|
+
testFilePath,
|
|
176
|
+
pomFilePath,
|
|
177
|
+
className: resolvedClassName!,
|
|
178
|
+
memberName: resolvedMemberName!,
|
|
179
|
+
intendedAction: correlated.context.failedAction.action,
|
|
180
|
+
replacementSelector: explicitReplacement,
|
|
181
|
+
worktreeId: input.worktreeId,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
if (!input.execute) {
|
|
185
|
+
const stagedProposal = stageProposal(proposal, 'proposal-only');
|
|
186
|
+
await recordRepairWorkflow(workspaceRoot, {
|
|
187
|
+
status: 'PROPOSAL_READY',
|
|
188
|
+
category: correlated.classification.category,
|
|
189
|
+
proposalId: stagedProposal.proposalId,
|
|
190
|
+
});
|
|
191
|
+
return { status: 'PROPOSAL_READY', correlated, proposedLocator: selected.locatorExpression, candidate, proposal, stagedProposal };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const configPath = input.configPath
|
|
195
|
+
? assertWorkspacePath(workspaceRoot, input.configPath, 'Playwright config')
|
|
196
|
+
: undefined;
|
|
197
|
+
const manager = new WorktreeManager(workspaceRoot);
|
|
198
|
+
const worktree = await manager.createWorktree(input.worktreeId);
|
|
199
|
+
const verification = await RepairPlanner.executeAndVerifyInWorktree({
|
|
200
|
+
proposal,
|
|
201
|
+
worktreePath: worktree.worktreePath,
|
|
202
|
+
worktreeId: worktree.id,
|
|
203
|
+
workspaceRoot,
|
|
204
|
+
configPath,
|
|
205
|
+
project: input.project,
|
|
206
|
+
});
|
|
207
|
+
const stagedProposal = ApprovalService.getInstance().stageProposal({
|
|
208
|
+
targetFilePath: proposal.pomFilePath,
|
|
209
|
+
targetClassName: proposal.className,
|
|
210
|
+
targetMemberName: proposal.memberName,
|
|
211
|
+
originalSourceContent: proposal.originalSource,
|
|
212
|
+
replacementSelector: proposal.replacementSelector,
|
|
213
|
+
fullDiff: proposal.fullDiff ?? '',
|
|
214
|
+
patchedSource: proposal.proposedPatchedSource,
|
|
215
|
+
testFilePath: proposal.testFilePath,
|
|
216
|
+
testFileContent: fsSync.readFileSync(proposal.testFilePath, 'utf8'),
|
|
217
|
+
worktreeId: worktree.id,
|
|
218
|
+
...(verification.isReadyForApproval ? { proof: verification.executionProof } : {}),
|
|
219
|
+
});
|
|
220
|
+
await recordRepairWorkflow(workspaceRoot, {
|
|
221
|
+
status: verification.isReadyForApproval ? 'VERIFIED_READY_FOR_APPROVAL' : 'REJECTED',
|
|
222
|
+
category: correlated.classification.category,
|
|
223
|
+
proposalId: stagedProposal.proposalId,
|
|
224
|
+
worktreeId: worktree.id,
|
|
225
|
+
executionPassed: verification.executionProof.passed,
|
|
226
|
+
});
|
|
227
|
+
if (verification.isReadyForApproval) {
|
|
228
|
+
await recordProjectSemanticVerification(workspaceRoot, proposal.replacementSelector);
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
status: verification.isReadyForApproval ? 'VERIFIED_READY_FOR_APPROVAL' : 'REJECTED',
|
|
232
|
+
correlated,
|
|
233
|
+
proposedLocator: selected.locatorExpression,
|
|
234
|
+
candidate,
|
|
235
|
+
proposal,
|
|
236
|
+
stagedProposal,
|
|
237
|
+
worktree,
|
|
238
|
+
verification,
|
|
239
|
+
reason: verification.isReadyForApproval ? 'Isolated verification passed; explicit approval is still required.' : 'Isolated verification did not satisfy all repair gates.',
|
|
240
|
+
};
|
|
241
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { DiffAllowlistValidator } from './diff-allowlist.js';
|
|
2
|
+
import { RepairOperation, validateRepairOperation } from './repair-operations.js';
|
|
3
|
+
|
|
4
|
+
export type SafeOperationReadiness = 'READY_FOR_ISOLATED_VERIFICATION' | 'PROPOSAL_ONLY' | 'ABSTAIN';
|
|
5
|
+
|
|
6
|
+
export interface CompiledSafeOperation {
|
|
7
|
+
readiness: SafeOperationReadiness;
|
|
8
|
+
kind: RepairOperation['kind'];
|
|
9
|
+
target?: string | undefined;
|
|
10
|
+
replacementLocator?: string | undefined;
|
|
11
|
+
reason: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function quote(value: string): string {
|
|
15
|
+
return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function composeFrameScope(target: string, frameLocator: string): string | undefined {
|
|
19
|
+
const rootMatch = target.match(/^(this\.page|page)\.(.+)$/);
|
|
20
|
+
if (!rootMatch) return undefined;
|
|
21
|
+
return `${rootMatch[1]}.frameLocator(${quote(frameLocator)}).${rootMatch[2]}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function composeShadowScope(target: string, shadowPath: string[]): string {
|
|
25
|
+
return shadowPath.reduce((expression, selector) => `${expression}.locator(${quote(selector)})`, target);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Converts allowlisted workflow operations into immutable locator proposals.
|
|
30
|
+
* It never writes source files and never claims browser execution succeeded.
|
|
31
|
+
*/
|
|
32
|
+
export function compileSafeRepairOperation(input: unknown): CompiledSafeOperation {
|
|
33
|
+
const validation = validateRepairOperation(input);
|
|
34
|
+
if (!validation.isValid || !validation.operation) {
|
|
35
|
+
return { readiness: 'ABSTAIN', kind: 'abstain', reason: validation.reason ?? 'Invalid repair operation' };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const operation = validation.operation;
|
|
39
|
+
if (operation.kind === 'abstain') {
|
|
40
|
+
return { readiness: 'ABSTAIN', kind: operation.kind, reason: operation.reason ?? 'Proposal requested abstention' };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let replacementLocator: string | undefined;
|
|
44
|
+
if (operation.kind === 'replace_locator') replacementLocator = operation.locator;
|
|
45
|
+
if (operation.kind === 'add_frame_scope') replacementLocator = composeFrameScope(operation.target!, operation.frameLocator!);
|
|
46
|
+
if (operation.kind === 'add_shadow_scope') replacementLocator = composeShadowScope(operation.target!, operation.shadowPath!);
|
|
47
|
+
|
|
48
|
+
if (replacementLocator) {
|
|
49
|
+
const grammar = DiffAllowlistValidator.validateLocatorGrammar(replacementLocator);
|
|
50
|
+
if (!grammar.isValid) {
|
|
51
|
+
return { readiness: 'ABSTAIN', kind: operation.kind, target: operation.target, replacementLocator, reason: grammar.reason ?? 'Locator grammar rejected' };
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
readiness: 'READY_FOR_ISOLATED_VERIFICATION',
|
|
55
|
+
kind: operation.kind,
|
|
56
|
+
target: operation.target,
|
|
57
|
+
replacementLocator,
|
|
58
|
+
reason: 'Static locator transformation passed the PIE allowlist and requires isolated browser verification',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
readiness: 'PROPOSAL_ONLY',
|
|
64
|
+
kind: operation.kind,
|
|
65
|
+
target: operation.target,
|
|
66
|
+
reason: `Operation '${operation.kind}' requires a dedicated browser-aware executor and remains proposal-only`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { FailedAction } from '../core/types/diagnostic-context.js';
|
|
4
|
+
import { StaticAstCollector, ExtractedLocatorAst } from '../collectors/static-ast-collector.js';
|
|
5
|
+
|
|
6
|
+
export interface RepairSourceMapping {
|
|
7
|
+
testFilePath: string;
|
|
8
|
+
pomFilePath: string;
|
|
9
|
+
className: string;
|
|
10
|
+
memberName: string;
|
|
11
|
+
confidence: number;
|
|
12
|
+
evidence: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface AstPayload {
|
|
16
|
+
isPageObject?: boolean;
|
|
17
|
+
locators?: ExtractedLocatorAst[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function listSourceFiles(root: string, depth = 0): Promise<string[]> {
|
|
21
|
+
if (depth > 6) return [];
|
|
22
|
+
const ignored = new Set([
|
|
23
|
+
'node_modules', '.git', 'dist', 'build', 'coverage', 'test-results',
|
|
24
|
+
'playwright-report', '.next', '.turbo', '.pie', '.pw-intelligence',
|
|
25
|
+
'repositories', 'pie-evaluation', 'generation', 'repair-worktrees',
|
|
26
|
+
'temp', 'tmp'
|
|
27
|
+
]);
|
|
28
|
+
let entries;
|
|
29
|
+
try {
|
|
30
|
+
entries = await fs.readdir(root, { withFileTypes: true });
|
|
31
|
+
} catch {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
const files: string[] = [];
|
|
35
|
+
for (const entry of entries) {
|
|
36
|
+
if (ignored.has(entry.name)) continue;
|
|
37
|
+
const fullPath = path.join(root, entry.name);
|
|
38
|
+
if (entry.isDirectory()) files.push(...await listSourceFiles(fullPath, depth + 1));
|
|
39
|
+
else if (/\.(?:ts|tsx|js|jsx|mjs|cjs)$/i.test(entry.name)) files.push(fullPath);
|
|
40
|
+
}
|
|
41
|
+
return files;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalize(value: string): string {
|
|
45
|
+
return value.replace(/\s+/g, '').replace(/;$/, '');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function selectorMatches(selector: string, locator: ExtractedLocatorAst): { score: number; evidence: string[] } | undefined {
|
|
49
|
+
const wanted = normalize(selector);
|
|
50
|
+
const source = normalize(locator.selectorText);
|
|
51
|
+
if (!wanted || !source) return undefined;
|
|
52
|
+
|
|
53
|
+
if (source.includes(wanted)) {
|
|
54
|
+
return { score: 100, evidence: ['Failed selector expression matched a Page Object locator expression'] };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Trace formats sometimes emit only the locator argument (for example
|
|
58
|
+
// "#submit") instead of the full page.locator("#submit") expression.
|
|
59
|
+
const argumentMatch = source.match(/(?:locator|getByRole|getByText|getByTestId|getByLabel|getByPlaceholder|getByAltText|getByTitle)\((.*)\)/);
|
|
60
|
+
if (argumentMatch?.[1] && (argumentMatch[1].includes(wanted) || wanted.includes(argumentMatch[1]))) {
|
|
61
|
+
return { score: 80, evidence: ['Failed selector value matched a Page Object locator argument'] };
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function normalizedPath(value: string): string {
|
|
67
|
+
return value.replace(/\\/g, '/').replace(/^\.\//, '').toLowerCase();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Trace source locations frequently point at a disposable checkout used by
|
|
72
|
+
* Playwright. Resolve that location back into the active repository only when
|
|
73
|
+
* the suffix identifies exactly one source file; never guess between twins.
|
|
74
|
+
*/
|
|
75
|
+
function resolveTestSourcePath(root: string, sourceFiles: string[], sourceFile: string | undefined): { path?: string; remapped: boolean } {
|
|
76
|
+
if (!sourceFile) return { remapped: false };
|
|
77
|
+
const resolved = path.resolve(root, sourceFile);
|
|
78
|
+
const inside = path.relative(root, resolved);
|
|
79
|
+
if (!inside.startsWith('..') && !path.isAbsolute(inside)) {
|
|
80
|
+
const exact = sourceFiles.find((file) => path.resolve(file) === resolved);
|
|
81
|
+
if (exact) return { path: exact, remapped: false };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const wanted = normalizedPath(sourceFile);
|
|
85
|
+
const suffixMatches = sourceFiles.filter((file) => {
|
|
86
|
+
const candidate = normalizedPath(path.relative(root, file));
|
|
87
|
+
return wanted.endsWith(`/${candidate}`) || candidate.endsWith(`/${wanted}`) || candidate === wanted;
|
|
88
|
+
});
|
|
89
|
+
if (suffixMatches.length === 1 && suffixMatches[0]) return { path: suffixMatches[0], remapped: true };
|
|
90
|
+
|
|
91
|
+
const basename = path.basename(sourceFile).toLowerCase();
|
|
92
|
+
const basenameMatches = sourceFiles.filter((file) => path.basename(file).toLowerCase() === basename);
|
|
93
|
+
return basenameMatches.length === 1 && basenameMatches[0]
|
|
94
|
+
? { path: basenameMatches[0], remapped: true }
|
|
95
|
+
: { remapped: false };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Finds a unique Page Object locator for a failed trace action. This is
|
|
100
|
+
* intentionally selector-based and fail-closed; it does not infer a class or
|
|
101
|
+
* member from naming conventions when the selector evidence is absent.
|
|
102
|
+
*/
|
|
103
|
+
export async function discoverRepairSourceMapping(input: {
|
|
104
|
+
workspaceRoot: string;
|
|
105
|
+
failedAction: FailedAction;
|
|
106
|
+
}): Promise<RepairSourceMapping | undefined> {
|
|
107
|
+
const root = path.resolve(input.workspaceRoot);
|
|
108
|
+
const selector = input.failedAction.selector?.trim();
|
|
109
|
+
if (!selector) return undefined;
|
|
110
|
+
const normalizedSelector = normalize(selector);
|
|
111
|
+
const sourceFiles = await listSourceFiles(root);
|
|
112
|
+
const resolvedTestSource = resolveTestSourcePath(root, sourceFiles, input.failedAction.sourceFile);
|
|
113
|
+
if (input.failedAction.sourceFile && !resolvedTestSource.path) return undefined;
|
|
114
|
+
const matches: Array<RepairSourceMapping & { score: number }> = [];
|
|
115
|
+
|
|
116
|
+
for (const filePath of sourceFiles) {
|
|
117
|
+
// Avoid parsing every source file in large repositories. AST inspection is
|
|
118
|
+
// reserved for files containing the failed selector text.
|
|
119
|
+
let sourceText: string;
|
|
120
|
+
try {
|
|
121
|
+
sourceText = await fs.readFile(filePath, 'utf8');
|
|
122
|
+
} catch {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (!normalize(sourceText).includes(normalizedSelector)) continue;
|
|
126
|
+
const result = await new StaticAstCollector().collect({ targetFilePath: filePath });
|
|
127
|
+
const payload = result[0]?.payload as AstPayload | undefined;
|
|
128
|
+
if (!payload?.isPageObject || !payload.locators) continue;
|
|
129
|
+
for (const locator of payload.locators) {
|
|
130
|
+
if (!locator.className || locator.pattern === 'inline') continue;
|
|
131
|
+
const matched = selectorMatches(input.failedAction.selector ?? '', locator);
|
|
132
|
+
if (!matched) continue;
|
|
133
|
+
matches.push({
|
|
134
|
+
testFilePath: resolvedTestSource.path ?? '',
|
|
135
|
+
pomFilePath: filePath,
|
|
136
|
+
className: locator.className,
|
|
137
|
+
memberName: locator.propertyName,
|
|
138
|
+
confidence: matched.score / 100,
|
|
139
|
+
evidence: [
|
|
140
|
+
...matched.evidence,
|
|
141
|
+
...(resolvedTestSource.remapped ? ['Trace source path remapped to the unique active-repository source file'] : []),
|
|
142
|
+
`Page Object member ${locator.className}.${locator.propertyName}`,
|
|
143
|
+
`Source location ${path.relative(root, filePath)}:${locator.line}`,
|
|
144
|
+
],
|
|
145
|
+
score: matched.score,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (matches.length === 0) return undefined;
|
|
151
|
+
const unique = [...new Map(matches.map((match) => [`${match.pomFilePath}:${match.className}:${match.memberName}`, match])).values()];
|
|
152
|
+
if (unique.length !== 1) return undefined;
|
|
153
|
+
const match = unique[0]!;
|
|
154
|
+
if (!match.testFilePath || !path.extname(match.testFilePath)) return undefined;
|
|
155
|
+
return match;
|
|
156
|
+
}
|