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,287 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import fsSync from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { execFile } from 'node:child_process';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import { WorktreeOperationError } from '../core/errors/engine-errors.js';
|
|
7
|
+
import { DependencyStrategyResolver, IsolationLevel } from './dependency-strategy.js';
|
|
8
|
+
|
|
9
|
+
const execFileAsync = promisify(execFile);
|
|
10
|
+
|
|
11
|
+
export interface WorktreeInfo {
|
|
12
|
+
id: string;
|
|
13
|
+
worktreePath: string;
|
|
14
|
+
branch: string;
|
|
15
|
+
createdAt: number;
|
|
16
|
+
ownerPid: number;
|
|
17
|
+
isolationLevel: IsolationLevel;
|
|
18
|
+
strategy: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface WorktreeRegistry {
|
|
22
|
+
worktrees: Record<string, WorktreeInfo>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class WorktreeManager {
|
|
26
|
+
private readonly rootDir: string;
|
|
27
|
+
private readonly baseWorktreeDir: string;
|
|
28
|
+
private readonly registryPath: string;
|
|
29
|
+
|
|
30
|
+
constructor(rootDir: string) {
|
|
31
|
+
this.rootDir = path.resolve(rootDir);
|
|
32
|
+
this.baseWorktreeDir = path.resolve(this.rootDir, '.pw-intelligence', 'worktrees');
|
|
33
|
+
this.registryPath = path.resolve(this.baseWorktreeDir, 'registry.json');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private static readonly VALID_ID_REGEX = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Checks whether a given process ID is currently alive on the host system.
|
|
40
|
+
*/
|
|
41
|
+
public static isProcessAlive(pid: number): boolean {
|
|
42
|
+
try {
|
|
43
|
+
process.kill(pid, 0);
|
|
44
|
+
return true;
|
|
45
|
+
} catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Asserts that a worktree ID is valid and that the resolved path is strictly contained
|
|
52
|
+
* within the base worktree directory using canonical real paths (AUDIT-05 / AUDIT-06).
|
|
53
|
+
* Prevents symlink/junction escape, directory traversal, and recursive host deletion.
|
|
54
|
+
*/
|
|
55
|
+
private assertWorktreeContainment(id: string): string {
|
|
56
|
+
if (!WorktreeManager.VALID_ID_REGEX.test(id)) {
|
|
57
|
+
throw new WorktreeOperationError(
|
|
58
|
+
'validate-id',
|
|
59
|
+
`Security Violation: Invalid worktree ID '${id}'. Must match /^[a-zA-Z0-9_-]{1,64}$/`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
fsSync.mkdirSync(this.baseWorktreeDir, { recursive: true });
|
|
64
|
+
let canonicalBase: string;
|
|
65
|
+
try {
|
|
66
|
+
canonicalBase = fsSync.realpathSync(this.baseWorktreeDir);
|
|
67
|
+
} catch (err: any) {
|
|
68
|
+
throw new WorktreeOperationError(
|
|
69
|
+
'containment',
|
|
70
|
+
`Security Violation: Failed to resolve canonical realpath of base worktree directory '${this.baseWorktreeDir}': ${err.message}`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const worktreePath = path.resolve(canonicalBase, id);
|
|
75
|
+
const rel = path.relative(canonicalBase, worktreePath);
|
|
76
|
+
|
|
77
|
+
if (rel.startsWith('..') || path.isAbsolute(rel) || rel === '') {
|
|
78
|
+
throw new WorktreeOperationError(
|
|
79
|
+
'containment',
|
|
80
|
+
`Security Violation: Worktree path '${worktreePath}' escapes base worktree directory '${canonicalBase}'.`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (fsSync.existsSync(worktreePath)) {
|
|
85
|
+
try {
|
|
86
|
+
const canonicalWorktree = fsSync.realpathSync(worktreePath);
|
|
87
|
+
const relWorktree = path.relative(canonicalBase, canonicalWorktree);
|
|
88
|
+
if (relWorktree.startsWith('..') || path.isAbsolute(relWorktree)) {
|
|
89
|
+
throw new WorktreeOperationError(
|
|
90
|
+
'containment',
|
|
91
|
+
`Security Violation: Canonical worktree '${canonicalWorktree}' escapes base worktree directory '${canonicalBase}'.`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
return canonicalWorktree;
|
|
95
|
+
} catch (err: any) {
|
|
96
|
+
if (err instanceof WorktreeOperationError) throw err;
|
|
97
|
+
throw new WorktreeOperationError(
|
|
98
|
+
'containment',
|
|
99
|
+
`Security Violation: Failed to resolve canonical realpath of worktree '${worktreePath}': ${err.message}`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return worktreePath;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Creates an isolated detached Git worktree using the appropriate DependencyStrategy.
|
|
109
|
+
* Enforces fail-closed dependency validation (AUDIT-16).
|
|
110
|
+
*/
|
|
111
|
+
public async createWorktree(worktreeId?: string): Promise<WorktreeInfo> {
|
|
112
|
+
const id = worktreeId ?? `repair-${Date.now().toString(36)}-${Math.random().toString(36).substring(2, 7)}`;
|
|
113
|
+
const worktreePath = this.assertWorktreeContainment(id);
|
|
114
|
+
const branch = `pw-repair/${id}`;
|
|
115
|
+
|
|
116
|
+
await fs.mkdir(this.baseWorktreeDir, { recursive: true });
|
|
117
|
+
|
|
118
|
+
// Step 1: Detect dirty workspace state and create snapshot if uncommitted changes exist
|
|
119
|
+
let baseRef = 'HEAD';
|
|
120
|
+
let untrackedFiles: string[] = [];
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const { stdout: statusOut } = await execFileAsync('git', ['status', '--porcelain'], { cwd: this.rootDir });
|
|
124
|
+
const isDirty = statusOut.trim().length > 0;
|
|
125
|
+
|
|
126
|
+
if (isDirty) {
|
|
127
|
+
// Create an in-memory commit representing the exact staged and unstaged state without touching workspace
|
|
128
|
+
const { stdout: stashSha } = await execFileAsync('git', ['stash', 'create'], { cwd: this.rootDir });
|
|
129
|
+
const trimmedSha = stashSha.trim();
|
|
130
|
+
if (trimmedSha) {
|
|
131
|
+
baseRef = trimmedSha;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Identify untracked, non-ignored files to mirror
|
|
135
|
+
const { stdout: untrackedOut } = await execFileAsync('git', ['ls-files', '--others', '--exclude-standard'], { cwd: this.rootDir });
|
|
136
|
+
untrackedFiles = untrackedOut.split('\n').map((f) => f.trim()).filter(Boolean);
|
|
137
|
+
}
|
|
138
|
+
} catch {
|
|
139
|
+
// Fall back to HEAD if status check encounters a non-git environment
|
|
140
|
+
baseRef = 'HEAD';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Step 2: Create Git worktree
|
|
144
|
+
try {
|
|
145
|
+
await execFileAsync('git', ['worktree', 'add', '-b', branch, worktreePath, baseRef], {
|
|
146
|
+
cwd: this.rootDir,
|
|
147
|
+
});
|
|
148
|
+
} catch (err: any) {
|
|
149
|
+
throw new WorktreeOperationError('create', `Git worktree add failed: ${err.message}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Step 3: Mirror untracked files into worktree
|
|
153
|
+
if (untrackedFiles.length > 0) {
|
|
154
|
+
for (const relFile of untrackedFiles) {
|
|
155
|
+
try {
|
|
156
|
+
const src = path.resolve(this.rootDir, relFile);
|
|
157
|
+
const dest = path.resolve(worktreePath, relFile);
|
|
158
|
+
await fs.mkdir(path.dirname(dest), { recursive: true });
|
|
159
|
+
await fs.copyFile(src, dest);
|
|
160
|
+
} catch {
|
|
161
|
+
// Non-critical file copy failure
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Step 2: Prepare dependencies via DependencyStrategy and enforce validation (AUDIT-16)
|
|
167
|
+
const strategy = await DependencyStrategyResolver.resolve(this.rootDir);
|
|
168
|
+
let prepResult;
|
|
169
|
+
try {
|
|
170
|
+
prepResult = await strategy.prepare(this.rootDir, worktreePath);
|
|
171
|
+
// Validate dependency strategy isolation
|
|
172
|
+
const validation = await strategy.validate(worktreePath);
|
|
173
|
+
if (!validation.isValid) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
`Dependency validation failed for '${prepResult.strategy}' (isolation: ${prepResult.isolationLevel}): ${validation.error ?? 'Unmet dependency requirements'}`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
} catch (err: any) {
|
|
179
|
+
await this.destroyWorktree(id).catch(() => {});
|
|
180
|
+
throw new WorktreeOperationError('prepare-dependencies', err.message);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const info: WorktreeInfo = {
|
|
184
|
+
id,
|
|
185
|
+
worktreePath,
|
|
186
|
+
branch,
|
|
187
|
+
createdAt: Date.now(),
|
|
188
|
+
ownerPid: process.pid,
|
|
189
|
+
isolationLevel: prepResult.isolationLevel,
|
|
190
|
+
strategy: prepResult.strategy,
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
await this.registerWorktree(info);
|
|
194
|
+
return info;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Destroys an isolated worktree safely:
|
|
199
|
+
* 1. Unlinks the dependency junction/symlink FIRST (CRITICAL: prevents wiping root dependencies)
|
|
200
|
+
* 2. Runs git worktree remove
|
|
201
|
+
* 3. Deletes leftover branch
|
|
202
|
+
* 4. Cleans up registry
|
|
203
|
+
*/
|
|
204
|
+
public async destroyWorktree(id: string): Promise<void> {
|
|
205
|
+
const worktreePath = this.assertWorktreeContainment(id);
|
|
206
|
+
const branch = `pw-repair/${id}`;
|
|
207
|
+
|
|
208
|
+
// Step 1: Cleanup dependency links via strategy
|
|
209
|
+
const strategy = await DependencyStrategyResolver.resolve(this.rootDir);
|
|
210
|
+
await strategy.cleanup(worktreePath).catch(() => {});
|
|
211
|
+
|
|
212
|
+
// Step 2: Git worktree remove
|
|
213
|
+
try {
|
|
214
|
+
await execFileAsync('git', ['worktree', 'remove', '--force', worktreePath], {
|
|
215
|
+
cwd: this.rootDir,
|
|
216
|
+
});
|
|
217
|
+
} catch {
|
|
218
|
+
// Fallback: prune if removal failed
|
|
219
|
+
await execFileAsync('git', ['worktree', 'prune'], { cwd: this.rootDir }).catch(() => {});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Step 3: Resilient directory cleanup with backoff for Windows file locks
|
|
223
|
+
await this.resilientRemove(worktreePath).catch(() => {});
|
|
224
|
+
|
|
225
|
+
// Step 4: Delete temporary branch
|
|
226
|
+
try {
|
|
227
|
+
await execFileAsync('git', ['branch', '-D', branch], { cwd: this.rootDir });
|
|
228
|
+
} catch {
|
|
229
|
+
// Ignore if branch was already deleted
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
await this.unregisterWorktree(id);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Robust directory removal with exponential backoff to handle temporary
|
|
237
|
+
* Windows anti-virus or search indexer file locks.
|
|
238
|
+
*/
|
|
239
|
+
private async resilientRemove(targetPath: string, maxRetries = 5): Promise<void> {
|
|
240
|
+
let delayMs = 100;
|
|
241
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
242
|
+
try {
|
|
243
|
+
await fs.rm(targetPath, { recursive: true, force: true });
|
|
244
|
+
return;
|
|
245
|
+
} catch (err: any) {
|
|
246
|
+
if (err.code === 'ENOENT') {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (attempt === maxRetries - 1) {
|
|
250
|
+
// Fallback: attempt sync removal or log warning
|
|
251
|
+
try {
|
|
252
|
+
fsSync.rmSync(targetPath, { recursive: true, force: true });
|
|
253
|
+
return;
|
|
254
|
+
} catch {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
await new Promise((res) => setTimeout(res, delayMs));
|
|
259
|
+
delayMs *= 2;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private async registerWorktree(info: WorktreeInfo): Promise<void> {
|
|
265
|
+
let reg: WorktreeRegistry = { worktrees: {} };
|
|
266
|
+
try {
|
|
267
|
+
const data = await fs.readFile(this.registryPath, 'utf-8');
|
|
268
|
+
reg = JSON.parse(data);
|
|
269
|
+
} catch {
|
|
270
|
+
// Default to empty registry if unreadable
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
reg.worktrees[info.id] = info;
|
|
274
|
+
await fs.writeFile(this.registryPath, JSON.stringify(reg, null, 2), 'utf-8');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private async unregisterWorktree(id: string): Promise<void> {
|
|
278
|
+
try {
|
|
279
|
+
const data = await fs.readFile(this.registryPath, 'utf-8');
|
|
280
|
+
const reg: WorktreeRegistry = JSON.parse(data);
|
|
281
|
+
delete reg.worktrees[id];
|
|
282
|
+
await fs.writeFile(this.registryPath, JSON.stringify(reg, null, 2), 'utf-8');
|
|
283
|
+
} catch {
|
|
284
|
+
// Ignore registry read errors
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { ScoreBreakdown } from './selector-scorer.js';
|
|
2
|
+
import { ResolvedElement } from '../trace/snapshot-resolver.js';
|
|
3
|
+
|
|
4
|
+
export interface ScoredCandidate {
|
|
5
|
+
element: ResolvedElement;
|
|
6
|
+
breakdown: ScoreBreakdown;
|
|
7
|
+
locatorExpression?: string | undefined;
|
|
8
|
+
originalSelector?: string | undefined;
|
|
9
|
+
intendedAction?: string | undefined; // e.g. "click", "fill"
|
|
10
|
+
parentScopeVerified?: boolean | undefined;
|
|
11
|
+
disambiguationStrategy?: string | undefined;
|
|
12
|
+
disambiguationExplanation?: string | undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type DecisionStage =
|
|
16
|
+
| 'SUPPORTED_ELEMENT'
|
|
17
|
+
| 'TARGET_IDENTITY'
|
|
18
|
+
| 'STRICT_UNIQUENESS'
|
|
19
|
+
| 'ACTION_SEMANTICS'
|
|
20
|
+
| 'SCORE_CONFIDENCE'
|
|
21
|
+
| 'AMBIGUITY_DELTA'
|
|
22
|
+
| 'AUTHORIZED';
|
|
23
|
+
|
|
24
|
+
export interface AbstentionDecision {
|
|
25
|
+
shouldAbstain: boolean;
|
|
26
|
+
stage: DecisionStage;
|
|
27
|
+
selectedCandidate?: ScoredCandidate | undefined;
|
|
28
|
+
reason?: string | undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class AbstentionPolicy {
|
|
32
|
+
public static readonly MINIMUM_CONFIDENCE_THRESHOLD = 0.70;
|
|
33
|
+
public static readonly AMBIGUITY_DELTA_THRESHOLD = 0.08;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Applies the formal Multi-Stage Decision Pipeline to candidate locators:
|
|
37
|
+
* 1. Supported Element? (Must be recognized interactive element)
|
|
38
|
+
* 2. Target Identity Verified? (Affinity to intended business entity)
|
|
39
|
+
* 3. Exactly One Scoped Match? (Strict Uniqueness: matchCount === 1, unless parentScopeVerified)
|
|
40
|
+
* 4. Action Semantics Preserved? (e.g. click -> clickable element, fill -> input/textarea)
|
|
41
|
+
* 5. Score & Calibrated Threshold Pass? (rawScore >= 0.70)
|
|
42
|
+
* 6. Ambiguity Delta Pass? (top.score - runnerUp.score >= 0.08)
|
|
43
|
+
*/
|
|
44
|
+
public static evaluate(candidates: ScoredCandidate[]): AbstentionDecision {
|
|
45
|
+
if (candidates.length === 0) {
|
|
46
|
+
return {
|
|
47
|
+
shouldAbstain: true,
|
|
48
|
+
stage: 'SUPPORTED_ELEMENT',
|
|
49
|
+
reason: 'Zero matching candidate elements found in DOM snapshot',
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Sort descending by score
|
|
54
|
+
const sorted = [...candidates].sort((a, b) => b.breakdown.score - a.breakdown.score);
|
|
55
|
+
const top = sorted[0]!;
|
|
56
|
+
|
|
57
|
+
// Stage 1: Supported Element Check
|
|
58
|
+
const supportedTags = new Set([
|
|
59
|
+
'button',
|
|
60
|
+
'a',
|
|
61
|
+
'input',
|
|
62
|
+
'select',
|
|
63
|
+
'textarea',
|
|
64
|
+
'option',
|
|
65
|
+
'div',
|
|
66
|
+
'span',
|
|
67
|
+
'li',
|
|
68
|
+
'td',
|
|
69
|
+
'th',
|
|
70
|
+
'p',
|
|
71
|
+
'h1',
|
|
72
|
+
'h2',
|
|
73
|
+
'h3',
|
|
74
|
+
'h4',
|
|
75
|
+
'label',
|
|
76
|
+
'img',
|
|
77
|
+
'form',
|
|
78
|
+
]);
|
|
79
|
+
if (!supportedTags.has(top.element.tag) && !top.element.role) {
|
|
80
|
+
return {
|
|
81
|
+
shouldAbstain: true,
|
|
82
|
+
stage: 'SUPPORTED_ELEMENT',
|
|
83
|
+
reason: `Unsupported DOM tag '<${top.element.tag}>' without ARIA role cannot be safely automated.`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Stage 2: Target Identity Verification
|
|
88
|
+
if (top.breakdown.identityMatch < 0.4) {
|
|
89
|
+
return {
|
|
90
|
+
shouldAbstain: true,
|
|
91
|
+
stage: 'TARGET_IDENTITY',
|
|
92
|
+
reason: `Target identity mismatch: candidate accessible name '${top.element.accessibleName ?? 'unnamed'}' does not align with original selector intent.`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Stage 3: Strict Uniqueness Gate (CRITICAL: matchCount !== 1 triggers ABSTAIN)
|
|
97
|
+
// Exactly 2 or more matches fail the uniqueness requirement unless parentScopeVerified is true
|
|
98
|
+
if (top.breakdown.matchCount !== 1 && !top.parentScopeVerified) {
|
|
99
|
+
return {
|
|
100
|
+
shouldAbstain: true,
|
|
101
|
+
stage: 'STRICT_UNIQUENESS',
|
|
102
|
+
reason: `NON_UNIQUE_CANDIDATE: Candidate element matches ${top.breakdown.matchCount} elements in DOM. Strict uniqueness requires exactly 1 match (U = 1.0) unless scoped by a verified parent container.`,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Stage 4: Action Semantics Check
|
|
107
|
+
if (top.intendedAction) {
|
|
108
|
+
const action = top.intendedAction.toLowerCase();
|
|
109
|
+
if (action.includes('click')) {
|
|
110
|
+
const isClickable =
|
|
111
|
+
top.element.tag === 'button' ||
|
|
112
|
+
top.element.tag === 'a' ||
|
|
113
|
+
top.element.role === 'button' ||
|
|
114
|
+
top.element.role === 'link' ||
|
|
115
|
+
top.element.role === 'tab' ||
|
|
116
|
+
top.element.role === 'menuitem' ||
|
|
117
|
+
Boolean(top.element.attributes?.['onclick']) ||
|
|
118
|
+
Boolean(top.element.attributes?.['href']);
|
|
119
|
+
|
|
120
|
+
if (!isClickable && top.element.tag !== 'div' && top.element.tag !== 'span' && top.element.tag !== 'td') {
|
|
121
|
+
return {
|
|
122
|
+
shouldAbstain: true,
|
|
123
|
+
stage: 'ACTION_SEMANTICS',
|
|
124
|
+
reason: `Action semantics violation: action '${top.intendedAction}' cannot be safely dispatched to non-clickable element <${top.element.tag}>.`,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
} else if (action.includes('fill') || action.includes('type')) {
|
|
128
|
+
const isInput =
|
|
129
|
+
top.element.tag === 'input' ||
|
|
130
|
+
top.element.tag === 'textarea' ||
|
|
131
|
+
top.element.role === 'textbox' ||
|
|
132
|
+
top.element.attributes['contenteditable'] === 'true';
|
|
133
|
+
|
|
134
|
+
if (!isInput) {
|
|
135
|
+
return {
|
|
136
|
+
shouldAbstain: true,
|
|
137
|
+
stage: 'ACTION_SEMANTICS',
|
|
138
|
+
reason: `Action semantics violation: fill/type action requires input or textbox element, but found <${top.element.tag}>.`,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Stage 5: Confidence Threshold Gate
|
|
145
|
+
if (top.breakdown.score < AbstentionPolicy.MINIMUM_CONFIDENCE_THRESHOLD) {
|
|
146
|
+
return {
|
|
147
|
+
shouldAbstain: true,
|
|
148
|
+
stage: 'SCORE_CONFIDENCE',
|
|
149
|
+
reason: `Highest candidate confidence (${(top.breakdown.score * 100).toFixed(1)}%) is below minimum safety threshold (${(AbstentionPolicy.MINIMUM_CONFIDENCE_THRESHOLD * 100).toFixed(1)}%)`,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Stage 6: Ambiguity Delta Gate (if top two candidates are neck and neck, guess is unsafe)
|
|
154
|
+
if (sorted.length > 1) {
|
|
155
|
+
const runnerUp = sorted[1]!;
|
|
156
|
+
const delta = parseFloat((top.breakdown.score - runnerUp.breakdown.score).toFixed(3));
|
|
157
|
+
const identitySeparation = top.breakdown.identityMatch - runnerUp.breakdown.identityMatch;
|
|
158
|
+
const hasStrongIdentitySeparation = top.breakdown.identityMatch >= 0.9 && identitySeparation >= 0.12;
|
|
159
|
+
if (delta < AbstentionPolicy.AMBIGUITY_DELTA_THRESHOLD && !hasStrongIdentitySeparation) {
|
|
160
|
+
return {
|
|
161
|
+
shouldAbstain: true,
|
|
162
|
+
stage: 'AMBIGUITY_DELTA',
|
|
163
|
+
reason: `Ambiguous match: top two candidate elements have nearly identical scores (${top.breakdown.score} vs ${runnerUp.breakdown.score}, delta: ${delta.toFixed(3)} < threshold ${AbstentionPolicy.AMBIGUITY_DELTA_THRESHOLD}). Abstaining to prevent incorrect action dispatch.`,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
shouldAbstain: false,
|
|
170
|
+
stage: 'AUTHORIZED',
|
|
171
|
+
selectedCandidate: top,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|