@skyramp/mcp 0.4.1 → 0.4.2-rc.2
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/build/commands/localDevTestChangesCommand.js +2 -1
- package/build/commands/recommendTestsAndExecuteCommand.js +15 -7
- package/build/commands/testThisEndpointCommand.js +35 -6
- package/build/execution/wrapperConfig.d.ts +56 -0
- package/build/execution/wrapperConfig.js +155 -0
- package/build/index.js +40 -10
- package/build/playwright/blueprintDigest.js +28 -7
- package/build/playwright/registerPlaywrightTools.js +47 -28
- package/build/playwright/traceExportStore.d.ts +22 -0
- package/build/playwright/traceExportStore.js +81 -0
- package/build/playwright/traceRecordingPrompt.js +11 -3
- package/build/prompts/code-reuse.js +118 -49
- package/build/prompts/fix-error-prompt.d.ts +9 -1
- package/build/prompts/fix-error-prompt.js +31 -28
- package/build/prompts/local-dev/local-dev-plan.d.ts +3 -0
- package/build/prompts/local-dev/local-dev-plan.js +8 -23
- package/build/prompts/local-dev/local-dev-prompts.d.ts +1 -1
- package/build/prompts/local-dev/local-dev-prompts.js +31 -4
- package/build/prompts/modularization/integration-test-modularization.js +13 -6
- package/build/prompts/modularization/ui-test-modularization.js +1 -1
- package/build/prompts/personas.js +1 -1
- package/build/prompts/pom-aware-code-reuse.js +7 -9
- package/build/prompts/reuse-hand-off.d.ts +64 -0
- package/build/prompts/reuse-hand-off.js +130 -0
- package/build/prompts/shared-helper-policy.d.ts +124 -2
- package/build/prompts/shared-helper-policy.js +178 -12
- package/build/prompts/startTraceCollectionPrompts.js +1 -1
- package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +7 -8
- package/build/prompts/sut-setup/modes/dockerComposePrompt.js +1 -1
- package/build/prompts/sut-setup/shared.d.ts +4 -1
- package/build/prompts/sut-setup/shared.js +6 -4
- package/build/prompts/test-maintenance/actionsInstructions.d.ts +19 -11
- package/build/prompts/test-maintenance/actionsInstructions.js +47 -26
- package/build/prompts/test-maintenance/drift-analysis-prompt.d.ts +16 -8
- package/build/prompts/test-maintenance/drift-analysis-prompt.js +93 -38
- package/build/prompts/test-maintenance/driftAnalysisSections.js +5 -3
- package/build/prompts/test-maintenance/driftAnalysisShared.js +4 -2
- package/build/prompts/test-maintenance/uiDriftAnalysisSections.js +8 -5
- package/build/prompts/test-recommendation/recommendationSections.js +19 -8
- package/build/prompts/test-recommendation/recommendationShared.d.ts +1 -1
- package/build/prompts/test-recommendation/recommendationShared.js +0 -1
- package/build/prompts/test-recommendation/registerRecommendTestsPrompt.js +5 -2
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +10 -7
- package/build/prompts/testbot/testbot-prompts.js +133 -63
- package/build/recommendation/answers.js +18 -9
- package/build/recommendation/pullRequestText.js +1 -1
- package/build/recommendation/registerPlan.js +31 -17
- package/build/recommendation/subjectStep.d.ts +8 -10
- package/build/recommendation/subjectStep.js +17 -16
- package/build/recommendation/types.d.ts +71 -17
- package/build/recommendation/types.js +9 -15
- package/build/recommendation/verifierContracts.d.ts +7 -3
- package/build/recommendation/verifierContracts.js +8 -4
- package/build/recommendation/verifiers/changedFile.js +11 -13
- package/build/recommendation/verifiers/citedPath.d.ts +8 -0
- package/build/recommendation/verifiers/citedPath.js +16 -2
- package/build/recommendation/verifiers/coverage.d.ts +2 -2
- package/build/recommendation/verifiers/coverage.js +190 -134
- package/build/recommendation/verifiers/defects.js +56 -16
- package/build/recommendation/verifiers/deliveredMatchesPlan.d.ts +7 -1
- package/build/recommendation/verifiers/deliveredMatchesPlan.js +28 -14
- package/build/recommendation/verifiers/endpointGrounded.js +14 -8
- package/build/recommendation/verifiers/existingCoverage.d.ts +6 -0
- package/build/recommendation/verifiers/existingCoverage.js +30 -21
- package/build/recommendation/verifiers/expectedOutcome.js +17 -8
- package/build/recommendation/verifiers/expectedValueSourced.js +54 -43
- package/build/recommendation/verifiers/issueTraceability.d.ts +5 -0
- package/build/recommendation/verifiers/issueTraceability.js +64 -7
- package/build/recommendation/verifiers/removedElementGuarded.js +19 -11
- package/build/recommendation/verifiers/reportedCategory.js +9 -4
- package/build/recommendation/verifiers/requirementSourced.js +34 -21
- package/build/recommendation/verifiers/screenRoute.js +15 -10
- package/build/recommendation/verifiers/statedDifference.js +15 -8
- package/build/recommendation/verifiers/uiElementGrounded.js +50 -16
- package/build/resources/analysisResources.js +7 -3
- package/build/resources/progressResource.js +4 -2
- package/build/resources/sutSetupResource.js +20 -2
- package/build/resources/testbotResource.js +19 -1
- package/build/services/AnalyticsService.js +3 -1
- package/build/services/ScenarioGenerationService.js +5 -5
- package/build/services/TestDiscoveryService.js +53 -9
- package/build/services/TestExecutionService.js +63 -21
- package/build/services/TestGenerationService.d.ts +1 -1
- package/build/services/TestGenerationService.js +43 -24
- package/build/skills/enhanceAssertionsSkill.d.ts +45 -0
- package/build/skills/enhanceAssertionsSkill.js +103 -0
- package/build/skills/fixTestImportErrorsSkill.d.ts +2 -2
- package/build/skills/fixTestImportErrorsSkill.js +2 -2
- package/build/skills/runTestSkill.d.ts +6 -0
- package/build/skills/runTestSkill.js +17 -0
- package/build/skills/skillFiles.d.ts +38 -0
- package/build/skills/skillFiles.js +94 -0
- package/build/skills/validateAssertionAlignmentSkill.d.ts +34 -0
- package/build/skills/validateAssertionAlignmentSkill.js +59 -0
- package/build/tool-phases.js +4 -1
- package/build/tools/auth/loginTool.js +3 -1
- package/build/tools/auth/logoutTool.js +3 -1
- package/build/tools/budgetExcuse.d.ts +15 -0
- package/build/tools/budgetExcuse.js +113 -0
- package/build/tools/code-refactor/alignAssertionsTool.d.ts +2 -0
- package/build/tools/code-refactor/alignAssertionsTool.js +51 -0
- package/build/tools/code-refactor/assertion-state.d.ts +1 -1
- package/build/tools/code-refactor/assertion-state.js +1 -1
- package/build/tools/code-refactor/assertionOperations.d.ts +44 -0
- package/build/tools/code-refactor/assertionOperations.js +82 -0
- package/build/tools/code-refactor/assertionSkillTools.d.ts +3 -0
- package/build/tools/code-refactor/assertionSkillTools.js +86 -0
- package/build/tools/code-refactor/codeReuseTool.js +27 -3
- package/build/tools/code-refactor/enhanceAssertionsTool.js +19 -83
- package/build/tools/code-refactor/gate-markers.d.ts +51 -0
- package/build/tools/code-refactor/gate-markers.js +95 -0
- package/build/tools/code-refactor/retrofit-state.d.ts +3 -1
- package/build/tools/code-refactor/retrofit-state.js +44 -2
- package/build/tools/code-refactor/reuse-outcome.d.ts +23 -1
- package/build/tools/code-refactor/reuse-outcome.js +36 -13
- package/build/tools/code-refactor/reuse-state.d.ts +90 -16
- package/build/tools/code-refactor/reuse-state.js +191 -61
- package/build/tools/code-refactor/utils-verify-gates.d.ts +4 -0
- package/build/tools/code-refactor/utils-verify-gates.js +269 -30
- package/build/tools/code-refactor/verify-gates.d.ts +15 -1
- package/build/tools/code-refactor/verify-gates.js +36 -4
- package/build/tools/enrichTestWithMocksTool.d.ts +1 -1
- package/build/tools/enrichTestWithMocksTool.js +9 -5
- package/build/tools/executeSkyrampTestTool.d.ts +118 -48
- package/build/tools/executeSkyrampTestTool.js +998 -372
- package/build/tools/execution-video-state.js +1 -1
- package/build/tools/fixErrorTool.js +5 -6
- package/build/tools/generate-tests/batchMockGenerationTool.js +1 -1
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +114 -73
- package/build/tools/generate-tests/generateContractRestTool.js +34 -16
- package/build/tools/generate-tests/generateE2ERestTool.d.ts +1 -0
- package/build/tools/generate-tests/generateE2ERestTool.js +9 -1
- package/build/tools/generate-tests/generateIntegrationRestTool.js +22 -7
- package/build/tools/generate-tests/generateMockRestTool.js +3 -1
- package/build/tools/generate-tests/generateUIRestTool.d.ts +2 -0
- package/build/tools/generate-tests/generateUIRestTool.js +11 -2
- package/build/tools/generate-tests/loadTestSchema.js +1 -3
- package/build/tools/generate-tests/planGuard.js +6 -3
- package/build/tools/generate-tests/scenarioFileIdentity.js +4 -1
- package/build/tools/generate-tests/scenarioLint.js +17 -5
- package/build/tools/generate-tests/trace-reuse-guard.js +5 -2
- package/build/tools/generateEnrichedIntegrationTestTool.js +9 -3
- package/build/tools/one-click/oneClickTool.js +3 -1
- package/build/tools/preflightMockCheckTool.js +23 -7
- package/build/tools/submitReportTool.d.ts +51 -12
- package/build/tools/submitReportTool.js +988 -161
- package/build/tools/test-management/actionsTool.js +241 -51
- package/build/tools/test-management/analyzeChangesTool.d.ts +8 -9
- package/build/tools/test-management/analyzeChangesTool.js +127 -68
- package/build/tools/test-management/analyzeTestHealthTool.d.ts +0 -11
- package/build/tools/test-management/analyzeTestHealthTool.js +42 -76
- package/build/tools/test-management/registerTestPlanTool.d.ts +44 -34
- package/build/tools/test-management/registerTestPlanTool.js +255 -111
- package/build/tools/test-management/resolveScreenTool.js +33 -9
- package/build/tools/test-management/testsOwedBeforeRun.d.ts +28 -0
- package/build/tools/test-management/testsOwedBeforeRun.js +53 -0
- package/build/tools/trace/startTraceCollectionTool.js +3 -1
- package/build/tools/trace/stopTraceCollectionTool.js +42 -6
- package/build/tools/verifyTestDependenciesTool.d.ts +3 -0
- package/build/tools/verifyTestDependenciesTool.js +54 -0
- package/build/tools/workspace/initScanWorkspaceTool.js +9 -3
- package/build/tools/workspace/initializeWorkspaceTool.js +3 -1
- package/build/types/AssertionOutcome.d.ts +1 -1
- package/build/types/EnhanceType.d.ts +6 -0
- package/build/types/EnhanceType.js +1 -0
- package/build/types/RepositoryAnalysis.d.ts +32 -72
- package/build/types/ReuseOutcome.d.ts +100 -7
- package/build/types/ReuseOutcome.js +16 -0
- package/build/types/StepMethod.js +20 -6
- package/build/types/TestAnalysis.d.ts +10 -2
- package/build/types/TestExecution.d.ts +45 -0
- package/build/types/TestRecommendation.d.ts +1 -1
- package/build/types/TestRecommendation.js +4 -1
- package/build/types/TestTypes.d.ts +16 -0
- package/build/types/TestTypes.js +40 -3
- package/build/types/TestbotPromptOptions.d.ts +9 -1
- package/build/types/TestbotReport.d.ts +45 -9
- package/build/utils/AnalysisStateManager.d.ts +137 -34
- package/build/utils/AnalysisStateManager.js +228 -44
- package/build/utils/assertion-verify/api-shared-lints.js +34 -16
- package/build/utils/assertion-verify/metrics.js +39 -6
- package/build/utils/assertion-verify/ui-lints.js +4 -2
- package/build/utils/branchDiff.js +47 -12
- package/build/utils/canonicalJson.js +3 -1
- package/build/utils/connectionErrors.d.ts +10 -0
- package/build/utils/connectionErrors.js +10 -0
- package/build/utils/dartRouteExtractor.js +36 -7
- package/build/utils/fixAttempts.d.ts +26 -0
- package/build/utils/fixAttempts.js +109 -0
- package/build/utils/frontendSelectors.js +23 -4
- package/build/utils/generatedTestRecord.d.ts +19 -0
- package/build/utils/generatedTestRecord.js +61 -0
- package/build/utils/gitStaging.js +7 -2
- package/build/utils/initAgent.js +26 -6
- package/build/utils/language-helper.js +60 -45
- package/build/utils/pathMatching.js +2 -1
- package/build/utils/pathSignatures.js +6 -2
- package/build/utils/planMatchKeys.d.ts +2 -2
- package/build/utils/planMatchKeys.js +20 -14
- package/build/utils/pom-catalog-parse.js +7 -2
- package/build/utils/pom-scope/import-expansion.js +6 -1
- package/build/utils/pom-scope/index.js +50 -12
- package/build/utils/pom-scope/scoring.js +13 -3
- package/build/utils/pom-scope/selector-extractor.js +16 -3
- package/build/utils/pom-scope/strip.d.ts +8 -0
- package/build/utils/pom-scope/strip.js +238 -0
- package/build/utils/pom-scope/testIdDiscovery.js +14 -2
- package/build/utils/pom-verify/__fixtures__/af-style/asset-list.page.d.ts +1 -1
- package/build/utils/pom-verify/__fixtures__/af-style/asset-list.page.js +4 -2
- package/build/utils/pom-verify/__fixtures__/af-style/report.iframe.page.js +3 -1
- package/build/utils/pom-verify/__fixtures__/af-style/workflow-footer.page.js +3 -1
- package/build/utils/pom-verify/bindings.js +5 -1
- package/build/utils/pom-verify/calls.js +9 -2
- package/build/utils/pom-verify/verify.js +27 -5
- package/build/utils/pr-comment-parser.js +20 -7
- package/build/utils/progress.d.ts +1 -1
- package/build/utils/progress.js +1 -1
- package/build/utils/proxy-terminal.d.ts +19 -1
- package/build/utils/proxy-terminal.js +346 -21
- package/build/utils/rebaselineSnapshots.d.ts +1 -1
- package/build/utils/rebaselineSnapshots.js +6 -16
- package/build/utils/removedUiElements.js +1 -1
- package/build/utils/reportLanguage.js +35 -7
- package/build/utils/reportVerification.d.ts +14 -8
- package/build/utils/reportVerification.js +19 -19
- package/build/utils/repositorySlug.d.ts +32 -0
- package/build/utils/repositorySlug.js +77 -0
- package/build/utils/reuseRouting.d.ts +10 -0
- package/build/utils/reuseRouting.js +21 -2
- package/build/utils/runContextGauge.d.ts +27 -0
- package/build/utils/runContextGauge.js +181 -0
- package/build/utils/runSerialized.d.ts +3 -0
- package/build/utils/runSerialized.js +39 -0
- package/build/utils/screenRoutes.js +74 -18
- package/build/utils/skyrampMdContent.d.ts +1 -1
- package/build/utils/skyrampMdContent.js +1 -1
- package/build/utils/skyrampSdkVersion.d.ts +9 -0
- package/build/utils/skyrampSdkVersion.js +16 -0
- package/build/utils/sourceRouteExtractor.js +13 -6
- package/build/utils/telemetry.d.ts +1 -0
- package/build/utils/telemetry.js +8 -5
- package/build/utils/testDependencyPolicy.d.ts +9 -0
- package/build/utils/testDependencyPolicy.js +809 -0
- package/build/utils/testExecutionRecord.d.ts +94 -0
- package/build/utils/testExecutionRecord.js +269 -0
- package/build/utils/testFileClassification.d.ts +8 -0
- package/build/utils/testFileClassification.js +39 -4
- package/build/utils/trace-parser.js +62 -14
- package/build/utils/urlPath.js +3 -1
- package/build/utils/utils-verify/action-key.d.ts +46 -0
- package/build/utils/utils-verify/action-key.js +120 -38
- package/build/utils/utils-verify/action-sites.d.ts +32 -0
- package/build/utils/utils-verify/action-sites.js +202 -0
- package/build/utils/utils-verify/allow.d.ts +122 -3
- package/build/utils/utils-verify/allow.js +146 -21
- package/build/utils/utils-verify/body-reach.d.ts +120 -0
- package/build/utils/utils-verify/body-reach.js +333 -0
- package/build/utils/utils-verify/call-sites.d.ts +31 -7
- package/build/utils/utils-verify/call-sites.js +80 -13
- package/build/utils/utils-verify/delivered-imports.d.ts +43 -0
- package/build/utils/utils-verify/delivered-imports.js +84 -0
- package/build/utils/utils-verify/fixed-sleep.d.ts +96 -0
- package/build/utils/utils-verify/fixed-sleep.js +461 -0
- package/build/utils/utils-verify/in-house.d.ts +93 -0
- package/build/utils/utils-verify/in-house.js +719 -0
- package/build/utils/utils-verify/incumbent.d.ts +3 -0
- package/build/utils/utils-verify/incumbent.js +75 -0
- package/build/utils/utils-verify/index.d.ts +2 -0
- package/build/utils/utils-verify/index.js +2 -0
- package/build/utils/utils-verify/language-spec.d.ts +51 -10
- package/build/utils/utils-verify/language-spec.js +232 -21
- package/build/utils/utils-verify/locate.d.ts +26 -0
- package/build/utils/utils-verify/locate.js +125 -23
- package/build/utils/utils-verify/module-name.d.ts +40 -0
- package/build/utils/utils-verify/module-name.js +98 -0
- package/build/utils/utils-verify/parse.d.ts +56 -7
- package/build/utils/utils-verify/parse.js +195 -40
- package/build/utils/utils-verify/retrofit-equivalence.d.ts +5 -0
- package/build/utils/utils-verify/retrofit-equivalence.js +4 -2
- package/build/utils/utils-verify/stage.d.ts +5 -0
- package/build/utils/utils-verify/stage.js +47 -2
- package/build/utils/utils-verify/status-once.d.ts +62 -0
- package/build/utils/utils-verify/status-once.js +207 -0
- package/build/utils/utils-verify/typecheck.d.ts +61 -0
- package/build/utils/utils-verify/typecheck.js +338 -0
- package/build/utils/utils-verify/verify.d.ts +79 -3
- package/build/utils/utils-verify/verify.js +531 -61
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/build/utils/workspaceAuth.js +107 -37
- package/build/workspace/queryParamResolution.js +11 -4
- package/build/workspace/workspace.d.ts +72 -52
- package/build/workspace/workspace.js +19 -15
- package/node_modules/playwright/ThirdPartyNotices.txt +19 -19
- package/node_modules/playwright/lib/mcp/skyramp/assertTool.js +8 -2
- package/node_modules/playwright/lib/mcp/skyramp/common/cssValue.js +87 -0
- package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +31 -0
- package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +3 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +181 -15
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +24 -0
- package/node_modules/playwright/lib/transform/babelBundleImpl.js +2353 -190
- package/node_modules/playwright/node_modules/playwright-core/ThirdPartyNotices.txt +62 -34
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/.package-lock.json +72 -41
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/@hono/node-server/dist/serve-static.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/@hono/node-server/dist/serve-static.mjs +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/@hono/node-server/package.json +2 -2
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/body-parser/README.md +16 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/body-parser/index.js +4 -20
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/body-parser/lib/read.js +17 -17
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/body-parser/lib/types/json.js +60 -32
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/body-parser/lib/types/raw.js +3 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/body-parser/lib/types/text.js +3 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/body-parser/lib/types/urlencoded.js +16 -20
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/body-parser/lib/utils.js +18 -16
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/body-parser/node_modules/content-type/LICENSE +22 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/body-parser/node_modules/content-type/README.md +71 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/body-parser/node_modules/content-type/dist/index.d.ts +46 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/body-parser/node_modules/content-type/dist/index.js +176 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/body-parser/node_modules/content-type/dist/index.js.map +1 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/body-parser/node_modules/content-type/package.json +52 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/body-parser/package.json +23 -10
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/es-object-atoms/CHANGELOG.md +21 -14
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/es-object-atoms/isObject.d.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/es-object-atoms/package.json +6 -7
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/es-object-atoms/tsconfig.json +1 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/fast-uri/index.js +266 -45
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/fast-uri/lib/schemes.js +9 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/fast-uri/lib/utils.js +396 -92
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/fast-uri/package.json +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/fast-uri/test/component-safe-serialization.test.js +163 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/fast-uri/test/equal.test.js +31 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/fast-uri/test/fixtures/uri-js-parse.json +2 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/fast-uri/test/ipv6-canonical.test.js +34 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/fast-uri/test/ipv6-validation.test.js +124 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/fast-uri/test/malformed-percent.test.js +77 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/fast-uri/test/malformed-urn.test.js +61 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/fast-uri/test/parse.test.js +7 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/fast-uri/test/query-fragment-normalization.test.js +33 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/fast-uri/test/reserved-path-normalization.test.js +109 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/fast-uri/test/scheme-validation.test.js +124 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/fast-uri/test/security-normalization.test.js +101 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/fast-uri/test/security.test.js +301 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/fast-uri/test/urn-full-input.test.js +29 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/fast-uri/test/websocket-query-preservation.test.js +24 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hasown/CHANGELOG.md +7 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hasown/index.d.ts +0 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hasown/package.json +4 -5
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/client/client.js +30 -16
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/client/utils.js +4 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/context.js +32 -13
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/helper/accepts/accepts.js +36 -2
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/helper/proxy/index.js +4 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/helper/ssg/ssg.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/helper/ssg/utils.js +30 -10
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/helper/streaming/sse.js +5 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/hono-base.js +10 -8
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/jsx/base.js +44 -23
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/jsx/components.js +25 -26
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/jsx/context.js +5 -5
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/jsx/dom/render.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/jsx/dom/server.js +5 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/jsx/hooks/index.js +16 -13
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/jsx/intrinsic-element/components.js +3 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/jsx/streaming.js +4 -5
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/cache/index.js +103 -8
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/compress/index.js +5 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/cors/index.js +17 -14
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/csrf/index.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/etag/digest.js +47 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/etag/index.js +7 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/jwk/jwk.js +9 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/jwt/jwt.js +9 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/language/language.js +10 -6
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/method-not-allowed/index.js +90 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/pretty-json/index.js +3 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/secure-headers/secure-headers.js +16 -7
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/request.js +20 -13
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/router/linear-router/router.js +7 -2
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/router/pattern-router/router.js +3 -9
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/router/reg-exp-router/node.js +65 -59
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/router/reg-exp-router/router.js +71 -128
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/router/reg-exp-router/trie.js +14 -5
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/router/trie-router/node.js +47 -70
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/router/trie-router/router.js +3 -11
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/router/utils.js +27 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/router.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/utils/accept.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/utils/body.js +21 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/utils/cookie.js +4 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/utils/ipaddr.js +5 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/utils/stream.js +12 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/utils/url.js +19 -11
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/client/client.js +30 -16
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/client/utils.js +4 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/context.js +32 -13
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/helper/accepts/accepts.js +36 -2
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/helper/proxy/index.js +4 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/helper/ssg/ssg.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/helper/ssg/utils.js +30 -10
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/helper/streaming/sse.js +5 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/hono-base.js +10 -8
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/jsx/base.js +41 -23
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/jsx/components.js +26 -27
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/jsx/context.js +6 -6
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/jsx/dom/render.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/jsx/dom/server.js +5 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/jsx/hooks/index.js +16 -13
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/jsx/intrinsic-element/components.js +4 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/jsx/streaming.js +5 -6
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/cache/index.js +103 -8
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/compress/index.js +5 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/cors/index.js +17 -14
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/csrf/index.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/etag/digest.js +47 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/etag/index.js +7 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/jwk/jwk.js +9 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/jwt/jwt.js +9 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/language/language.js +10 -6
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/method-not-allowed/index.js +68 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/pretty-json/index.js +3 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/secure-headers/secure-headers.js +16 -7
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/request.js +21 -14
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/router/linear-router/router.js +7 -2
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/router/pattern-router/router.js +3 -9
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/router/reg-exp-router/node.js +61 -58
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/router/reg-exp-router/router.js +77 -129
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/router/reg-exp-router/trie.js +14 -5
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/router/trie-router/node.js +47 -70
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/router/trie-router/router.js +3 -11
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/router/utils.js +5 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/router.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/adapter/aws-lambda/types.d.ts +9 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/client/types.d.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/context.d.ts +6 -2
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/hono-base.d.ts +4 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/jsx/base.d.ts +7 -2
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/jsx/dom/index.d.ts +5 -5
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/jsx/dom/intrinsic-element/components.d.ts +2 -2
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/jsx/dom/server.d.ts +5 -5
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/jsx/hooks/index.d.ts +8 -6
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/jsx/index.d.ts +5 -5
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/middleware/cache/index.d.ts +6 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/middleware/combine/index.d.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/middleware/cors/index.d.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/middleware/jsx-renderer/index.d.ts +2 -2
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/middleware/jwk/jwk.d.ts +2 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/middleware/jwt/jwt.d.ts +2 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/middleware/method-not-allowed/index.d.ts +49 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/middleware/secure-headers/permissions-policy.d.ts +3 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/router/reg-exp-router/node.d.ts +4 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/router/reg-exp-router/trie.d.ts +2 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/router/trie-router/node.d.ts +1 -2
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/router/trie-router/router.d.ts +0 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/router/utils.d.ts +1 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/router.d.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/utils/headers.d.ts +2 -2
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/utils/url.d.ts +5 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/utils/accept.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/utils/body.js +21 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/utils/cookie.js +5 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/utils/ipaddr.js +5 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/utils/stream.js +12 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/utils/url.js +17 -10
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/package.json +11 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/ip-address/README.md +173 -143
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/ip-address/dist/address-error.d.ts +11 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/ip-address/dist/address-error.js.map +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/ip-address/dist/common.d.ts +49 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/ip-address/dist/common.js +79 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/ip-address/dist/common.js.map +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/ip-address/dist/ipv4.d.ts +78 -5
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/ip-address/dist/ipv4.js +119 -24
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/ip-address/dist/ipv4.js.map +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/ip-address/dist/ipv6.d.ts +151 -10
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/ip-address/dist/ipv6.js +316 -90
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/ip-address/dist/ipv6.js.map +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/ip-address/dist/v4/constants.d.ts +12 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/ip-address/dist/v4/constants.js +45 -2
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/ip-address/dist/v4/constants.js.map +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/ip-address/dist/v6/constants.d.ts +14 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/ip-address/dist/v6/constants.js +50 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/ip-address/dist/v6/constants.js.map +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/ip-address/package.json +6 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/qs/.github/THREAT_MODEL.md +3 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/qs/CHANGELOG.md +26 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/qs/README.md +19 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/qs/dist/qs.js +25 -25
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/qs/eslint.config.mjs +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/qs/lib/parse.js +16 -6
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/qs/lib/stringify.js +23 -8
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/qs/lib/utils.js +57 -11
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/qs/package.json +8 -7
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/qs/test/parse.js +419 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/qs/test/stringify.js +317 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/qs/test/utils.js +206 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/side-channel/CHANGELOG.md +10 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/side-channel/README.md +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/side-channel/index.js +5 -2
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/side-channel/package.json +10 -10
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/side-channel/test/index.js +16 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/type-is/index.js +8 -18
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/type-is/node_modules/content-type/LICENSE +22 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/type-is/node_modules/content-type/README.md +71 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/type-is/node_modules/content-type/dist/index.d.ts +46 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/type-is/node_modules/content-type/dist/index.js +176 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/type-is/node_modules/content-type/dist/index.js.map +1 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/type-is/node_modules/content-type/package.json +52 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/type-is/package.json +9 -5
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/package-lock.json +72 -41
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/.package-lock.json +6 -6
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/brace-expansion/README.md +23 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/brace-expansion/index.js +265 -86
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/brace-expansion/package.json +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/ip-address/README.md +173 -143
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/ip-address/dist/address-error.d.ts +11 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/ip-address/dist/address-error.js.map +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/ip-address/dist/common.d.ts +49 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/ip-address/dist/common.js +79 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/ip-address/dist/common.js.map +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/ip-address/dist/ipv4.d.ts +78 -5
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/ip-address/dist/ipv4.js +119 -24
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/ip-address/dist/ipv4.js.map +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/ip-address/dist/ipv6.d.ts +151 -10
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/ip-address/dist/ipv6.js +316 -90
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/ip-address/dist/ipv6.js.map +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/ip-address/dist/v4/constants.d.ts +12 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/ip-address/dist/v4/constants.js +45 -2
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/ip-address/dist/v4/constants.js.map +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/ip-address/dist/v6/constants.d.ts +14 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/ip-address/dist/v6/constants.js +50 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/ip-address/dist/v6/constants.js.map +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/ip-address/package.json +6 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/utils/package-lock.json +6 -6
- package/node_modules/playwright/node_modules/playwright-core/lib/cli/program.js +18 -9
- package/node_modules/playwright/node_modules/playwright-core/lib/mcpBundleImpl/index.js +47 -47
- package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +3 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderRunner.js +42 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/utilsBundleImpl/index.js +121 -121
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.B7KbSQcC.js → index.BAkLd5DX.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +1 -1
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/cli/program.ts +31 -9
- package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +1 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderRunner.ts +57 -1
- package/node_modules/playwright/package.json +2 -2
- package/package.json +7 -6
- package/plugin/prompts/code-reuse/hand-off.md +30 -0
- package/plugin/prompts/generate-tests/generation.md +1 -1
- package/plugin/prompts/plan-tests.md +14 -14
- package/plugin/prompts/testbot-task1.md +2 -2
- package/plugin/skills/enhance-assertions/SKILL.md +25 -0
- package/plugin/skills/enhance-assertions/reference/contract.md +51 -0
- package/plugin/skills/enhance-assertions/reference/integration.md +58 -0
- package/plugin/skills/enhance-assertions/reference/shared-rules.md +220 -0
- package/plugin/skills/enhance-assertions/reference/ui.md +373 -0
- package/plugin/skills/fix-test-import-errors/SKILL.md +45 -50
- package/plugin/skills/run-test/SKILL.md +16 -0
- package/plugin/skills/validate-assertion-alignment-post-execution/SKILL.md +32 -0
- package/plugin/skills/validate-assertion-alignment-post-execution/reference/checks.md +44 -0
- package/plugin/skills/validate-assertion-alignment-post-execution/reference/evidence.md +47 -0
- package/build/adapters/jestAdapter.d.ts +0 -14
- package/build/adapters/jestAdapter.js +0 -113
- package/build/adapters/mochaAdapter.d.ts +0 -13
- package/build/adapters/mochaAdapter.js +0 -87
- package/build/adapters/playwrightAdapter.d.ts +0 -17
- package/build/adapters/playwrightAdapter.js +0 -182
- package/build/adapters/pytestAdapter.d.ts +0 -15
- package/build/adapters/pytestAdapter.js +0 -108
- package/build/prompts/enhance-assertions/contractProviderAssertionsPrompt.d.ts +0 -2
- package/build/prompts/enhance-assertions/contractProviderAssertionsPrompt.js +0 -29
- package/build/prompts/enhance-assertions/integrationAssertionsPrompt.d.ts +0 -2
- package/build/prompts/enhance-assertions/integrationAssertionsPrompt.js +0 -36
- package/build/prompts/enhance-assertions/sharedAssertionRules.d.ts +0 -16
- package/build/prompts/enhance-assertions/sharedAssertionRules.js +0 -284
- package/build/prompts/enhance-assertions/uiAssertionsPrompt.d.ts +0 -2
- package/build/prompts/enhance-assertions/uiAssertionsPrompt.js +0 -388
- package/build/tools/runExistingTestsTool.d.ts +0 -138
- package/build/tools/runExistingTestsTool.js +0 -644
- package/build/types/ExternalTestExecution.d.ts +0 -67
- package/build/types/ExternalTestExecution.js +0 -8
- package/build/workspace/testSuites.d.ts +0 -20
- package/build/workspace/testSuites.js +0 -17
- package/node_modules/playwright/node_modules/playwright-core/.DS_Store +0 -0
|
@@ -1,28 +1,36 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { logger } from "../utils/logger.js";
|
|
3
3
|
import * as fs from "fs/promises";
|
|
4
|
+
import { constants as fsConstants } from "fs";
|
|
4
5
|
import * as path from "path";
|
|
5
6
|
import { AnalyticsService } from "../services/AnalyticsService.js";
|
|
6
7
|
import { SCENARIO_CATEGORIES, categoryExpectsRed, } from "../types/TestRecommendation.js";
|
|
7
|
-
import { TestType } from "../types/TestTypes.js";
|
|
8
|
-
import { STEP_METHOD_DESCRIPTION, stepMethodSchema } from "../types/StepMethod.js";
|
|
8
|
+
import { TESTBOT_SUPPORTED_TEST_TYPES, TestType } from "../types/TestTypes.js";
|
|
9
|
+
import { STEP_METHOD_DESCRIPTION, stepMethodSchema, } from "../types/StepMethod.js";
|
|
9
10
|
import { DriftAction } from "../types/TestAnalysis.js";
|
|
10
11
|
import { TestExecutionStatus } from "../types/TestExecution.js";
|
|
11
12
|
import { IssueFoundCategory } from "../types/TestbotReport.js";
|
|
12
|
-
import { StateManager, runArtifactDir, getTestsRepoDir,
|
|
13
|
+
import { StateManager, runArtifactDir, getTestsRepoDir, resolveOwnRunStatePath, } from "../utils/AnalysisStateManager.js";
|
|
13
14
|
import { toolError, testFileMatches } from "../utils/utils.js";
|
|
14
|
-
import { isTestbotEnabled, } from "../utils/featureFlags.js";
|
|
15
|
-
import { answerFor, unknownAnswerObjections } from "../recommendation/answers.js";
|
|
16
|
-
import { checkDeliveredMatchesPlan, checkMaintenanceDelivered } from "../recommendation/verifiers/deliveredMatchesPlan.js";
|
|
15
|
+
import { isTestbotEnabled, isUtilsReuseEnabled, } from "../utils/featureFlags.js";
|
|
16
|
+
import { answerFor, unknownAnswerObjections, } from "../recommendation/answers.js";
|
|
17
|
+
import { checkDeliveredMatchesPlan, checkMaintenanceDelivered, } from "../recommendation/verifiers/deliveredMatchesPlan.js";
|
|
17
18
|
import { checkReportedCategoryMatchesPlan, checkRequirementConflictReported, } from "../recommendation/verifiers/reportedCategory.js";
|
|
18
|
-
import { checkDefectsReported, checkIssueTraceability } from "../recommendation/verifiers/issueTraceability.js";
|
|
19
|
+
import { checkDefectsReported, checkIssueTraceability, } from "../recommendation/verifiers/issueTraceability.js";
|
|
19
20
|
import { checkExpectedOutcomeAfterExecution, } from "../recommendation/verifiers/expectedOutcome.js";
|
|
20
21
|
import { findInvalidSourceCitations, findUnchangedFileClaims, listChangedFiles, listChangedFilesAcross, listChangedFilesAbs, } from "../utils/reportVerification.js";
|
|
21
22
|
import { isPlanOnlyMode } from "../utils/planOnlyMode.js";
|
|
23
|
+
import { contextGaugeSentence } from "../utils/runContextGauge.js";
|
|
24
|
+
import { budgetExcuseRefusals, citesBudget, countBudgetExcuseRefusal, MAX_BUDGET_EXCUSE_REFUSALS, } from "./budgetExcuse.js";
|
|
25
|
+
import { countUiDeliveryRefusal, MAX_UI_DELIVERY_REFUSALS, traceExportAttempts, uiDeliveryRefusals, } from "../playwright/traceExportStore.js";
|
|
22
26
|
import { getReportLanguage, isEnforcedReportLanguage, findLanguageViolations, findLanguageNearMisses, reportLanguageDisplayName, } from "../utils/reportLanguage.js";
|
|
23
27
|
import { canonicalTestPath, findAssertionRecordByFileName, rederiveAssertionOutcome, } from "./code-refactor/assertion-state.js";
|
|
24
|
-
import { rederiveReuse, REUSE_SUBMIT_MAX_REFUSALS, reuseChainSkipped, samePath, } from "./code-refactor/reuse-state.js";
|
|
28
|
+
import { rederiveReuse, deriveReuseFromDelivered, REUSE_SUBMIT_MAX_REFUSALS, reuseChainSkipped, samePath, } from "./code-refactor/reuse-state.js";
|
|
25
29
|
import { retrofitGate, } from "./code-refactor/retrofit-state.js";
|
|
30
|
+
import { onlyCommentLinesRemoved, stripGateMarkers, } from "./code-refactor/gate-markers.js";
|
|
31
|
+
import { parseKeptInline } from "./code-refactor/reuse-outcome.js";
|
|
32
|
+
import { hasUtilsHeader, locateUtilsFiles, parseUtilsAllows, realpath, utilsSpecForFile, } from "../utils/utils-verify/index.js";
|
|
33
|
+
import { verifyTestDependencyChanges } from "../utils/testDependencyPolicy.js";
|
|
26
34
|
// Mirrors the tools wired to planGuard: UI and E2E are not gated at generation
|
|
27
35
|
// time, so gating them here would be a report-time-only surprise.
|
|
28
36
|
const PLAN_GATED_TEST_TYPES = new Set([
|
|
@@ -65,7 +73,9 @@ const MAINTENANCE_COVERAGE_ACTIONS = new Set([
|
|
|
65
73
|
* an external test's REGENERATE keeps its real action and touches no file, so counting it
|
|
66
74
|
* would credit a claim nothing backs. */
|
|
67
75
|
function rowsLeavingCoverage(rows) {
|
|
68
|
-
return (rows ?? []).filter((row) => row?.action !== undefined &&
|
|
76
|
+
return (rows ?? []).filter((row) => row?.action !== undefined &&
|
|
77
|
+
MAINTENANCE_COVERAGE_ACTIONS.has(row.action) &&
|
|
78
|
+
row?.reportOnly !== true);
|
|
69
79
|
}
|
|
70
80
|
/** Objection ids for an untested declared change, `coverage:change:<id>`. The
|
|
71
81
|
* change table reads the agent's answer back off them. */
|
|
@@ -74,22 +84,13 @@ const TOOL_NAME = "skyramp_submit_report";
|
|
|
74
84
|
const DEFAULT_COMMIT_MESSAGE = "Added recommendations by Skyramp Testbot.";
|
|
75
85
|
// Per-repo attribution. In a multi-repo run, every report item carries the
|
|
76
86
|
// owner/repo it pertains to (the primary's owner/repo for primary objections, or a
|
|
77
|
-
// related repo's owner/repo for objections from that repo's diff).
|
|
78
|
-
//
|
|
79
|
-
// distinct repo. Single-repo runs may omit it.
|
|
87
|
+
// related repo's owner/repo for objections from that repo's diff). Trimmed, and
|
|
88
|
+
// a blank string is rejected: every item names its repo, single-repo runs too.
|
|
80
89
|
const repositoryField = z
|
|
81
90
|
.string()
|
|
82
|
-
.
|
|
83
|
-
.
|
|
84
|
-
|
|
85
|
-
* value to undefined so it is never serialized as a distinct empty repo
|
|
86
|
-
* (downstream consumers treat absence as "the primary repo"). */
|
|
87
|
-
function normalizeRepository(item) {
|
|
88
|
-
const trimmed = item.repository?.trim();
|
|
89
|
-
return trimmed
|
|
90
|
-
? { ...item, repository: trimmed }
|
|
91
|
-
: { ...item, repository: undefined };
|
|
92
|
-
}
|
|
91
|
+
.trim()
|
|
92
|
+
.min(1)
|
|
93
|
+
.describe("The 'owner/repo' this item pertains to (e.g. 'letsramp/api-insight'). Set it on every item — the primary's owner/repo for primary-repo objections, or a related repo's owner/repo for objections derived from that repository's diff.");
|
|
93
94
|
// videoPath is deliberately absent from this input contract: it is attached server-side
|
|
94
95
|
// from the run's execution records (see attachVideoPath), and zod strips any the model
|
|
95
96
|
// supplies anyway. SKYR-4156 is what happens when the agent owns that field instead.
|
|
@@ -347,7 +348,7 @@ const issueFoundSchema = z
|
|
|
347
348
|
.optional()
|
|
348
349
|
.describe("The `id` of the plan defect this issue reports, from the `defects` list you registered with skyramp_register_test_plan. Spell it as that list spells it: an id the plan does not declare traces nothing. " +
|
|
349
350
|
"Set it on the `category: bug` entry that reports the defect. A `defectId` on a `lint`, `type` or `config` entry does not report the defect — the report renders those in its own Configuration Errors section. " +
|
|
350
|
-
"CHECKED: every plan defect has a `bug` issue that names it, and a `bug` entry with no `plannedTestId` traces through this defect when a delivered test that expects to fail cites it, or when you answered `defects:untested:<id>` at plan time. Leave it out for an issue the plan did not declare."),
|
|
351
|
+
"CHECKED: every plan defect has a `bug` issue that names it, and a `bug` entry with no `plannedTestId` traces through this defect when a delivered test that expects to fail cites it, when a `maintains` entry you registered cites it, or when you answered `defects:untested:<id>` at plan time. Leave it out for an issue the plan did not declare."),
|
|
351
352
|
sourceFile: citationString.describe("Path of the application file whose code is missing or wrong, relative to the repository root (e.g. 'src/crud/products.py'). " +
|
|
352
353
|
"REQUIRED when category is 'bug'. " +
|
|
353
354
|
"For code that is MISSING — an unmounted router, an unregistered route, an import never added — name the file where the line should be, not the file that defines what is unmounted. " +
|
|
@@ -538,6 +539,12 @@ const testMaintenanceSchema = z.object({
|
|
|
538
539
|
"For failing runs: failure name and one-line root cause, e.g. " +
|
|
539
540
|
"'FAILED test_foo — check_schema fails, order_id=1 has discount from prior PATCH test'. " +
|
|
540
541
|
"Empty string for VERIFY/IGNORE/DELETE entries where no after-execution was run."),
|
|
542
|
+
/** The ids of the plan defects this maintained test's own run proves. Written
|
|
543
|
+
* at report time, not at plan time: by here the test has been executed and the
|
|
544
|
+
* agent is recording what it saw, so naming a defect here never displaces the
|
|
545
|
+
* planned test that also proves it. A defect may be named by a planned test, by
|
|
546
|
+
* a maintenance row, or by both. */
|
|
547
|
+
defects: z.array(z.string()).default([]),
|
|
541
548
|
// Server-populated from the verdict's own state section, falling back to the
|
|
542
549
|
// run's primary repo. Always set: a consumer that has to treat absence as
|
|
543
550
|
// "probably the primary" cannot tell a single-repo row from a mis-stamped one.
|
|
@@ -546,18 +553,28 @@ const testMaintenanceSchema = z.object({
|
|
|
546
553
|
beforeStatus: z.nativeEnum(TestExecutionStatus),
|
|
547
554
|
afterStatus: z.nativeEnum(TestExecutionStatus),
|
|
548
555
|
});
|
|
549
|
-
// LLM input contract: status fields are server-populated from stateFile
|
|
556
|
+
// LLM input contract: status fields are server-populated from stateFile, and
|
|
557
|
+
// `defects` is attached from the per-file detail the agent drafts, not from the
|
|
558
|
+
// verdict rows this schema describes.
|
|
550
559
|
const testMaintenanceLLMSchema = testMaintenanceSchema.omit({
|
|
551
560
|
beforeStatus: true,
|
|
552
561
|
afterStatus: true,
|
|
562
|
+
defects: true,
|
|
553
563
|
});
|
|
554
564
|
// LLM only drafts beforeDetails/afterDetails per file — action/testType/endpoint/description
|
|
555
565
|
// come from stateData.maintenanceVerdicts. Picked from testMaintenanceLLMSchema to avoid
|
|
556
566
|
// redeclaring those fields.
|
|
557
|
-
const testMaintenanceDetailSchema = testMaintenanceLLMSchema
|
|
567
|
+
const testMaintenanceDetailSchema = testMaintenanceLLMSchema
|
|
568
|
+
.pick({
|
|
558
569
|
testFilePath: true,
|
|
559
570
|
beforeDetails: true,
|
|
560
571
|
afterDetails: true,
|
|
572
|
+
})
|
|
573
|
+
.extend({
|
|
574
|
+
defects: z
|
|
575
|
+
.array(z.string())
|
|
576
|
+
.optional()
|
|
577
|
+
.describe("The ids of the plan defects this test's own run proves, from the `defects` list you registered with skyramp_register_test_plan. Name a defect here when this test failed on it — the failure you summarised in `afterDetails` is the evidence. Spell each id as that list spells it; the report is rejected if an id is not one the plan declares. Naming a defect here does not replace a planned test that proves it: name it here as well."),
|
|
561
578
|
});
|
|
562
579
|
/** The key an answer is matched by: the objection's id AND its message. The id
|
|
563
580
|
* alone is not unique — one verifier raises several different objections under
|
|
@@ -647,45 +664,156 @@ async function attachAssertionOutcome(test, outcomes, checkouts) {
|
|
|
647
664
|
return test;
|
|
648
665
|
return { ...test, assertions: await rederiveAssertionOutcome(record) };
|
|
649
666
|
}
|
|
667
|
+
/**
|
|
668
|
+
* The absolute path of a `newTestsCreated` row's delivered file, from the paths the
|
|
669
|
+
* generators recorded — or the reason it cannot be answered, which the row states.
|
|
670
|
+
*
|
|
671
|
+
* A created row carries a `fileName` the agent wrote, which may be a basename or a
|
|
672
|
+
* repository-relative path, so it cannot be resolved on its own. `generatedTests` is
|
|
673
|
+
* keyed by canonical absolute path and written by the generators, so the row selects
|
|
674
|
+
* from it through `testFileMatches` — the segment-aware predicate the delivered-
|
|
675
|
+
* matches-plan verifier already uses for this question. A row that names a directory
|
|
676
|
+
* matches only inside it; a bare basename matches on the name alone. Matching by
|
|
677
|
+
* basename and treating the directory as a tie-breaker let a single same-named file
|
|
678
|
+
* win against a row whose own path contradicted it, and the row then reported another
|
|
679
|
+
* file's helpers.
|
|
680
|
+
*
|
|
681
|
+
* Both sides are put in posix form first. That predicate reads a path-qualified
|
|
682
|
+
* candidate by its `/` separators while these keys carry the host's, so on Windows a
|
|
683
|
+
* uniquely recorded file matched nothing and its row said it could not be found.
|
|
684
|
+
* Untested: `path.sep` is `/` on the platforms this runs on, so no test on them can
|
|
685
|
+
* tell the normalization from its absence.
|
|
686
|
+
*
|
|
687
|
+
* An unresolved row returns a REASON rather than nothing. Nothing is read by the
|
|
688
|
+
* caller as "no file to derive from", which ships the ambiguous `reuse: null` this
|
|
689
|
+
* whole derivation exists to remove — so an ambiguity would have recreated the fault
|
|
690
|
+
* under a different cause.
|
|
691
|
+
*/
|
|
692
|
+
function generatedPathFor(fileName, generated) {
|
|
693
|
+
const posix = (p) => p.split(path.sep).join("/");
|
|
694
|
+
const wanted = posix(fileName);
|
|
695
|
+
const matches = Object.keys(generated).filter((p) => testFileMatches(posix(p), wanted));
|
|
696
|
+
if (matches.length === 1)
|
|
697
|
+
return matches[0];
|
|
698
|
+
if (matches.length === 0)
|
|
699
|
+
return {
|
|
700
|
+
reason: `this run recorded no generated file matching ${fileName}, so its imports could not be read`,
|
|
701
|
+
};
|
|
702
|
+
// Several specs of one name in different directories, and a row that names only
|
|
703
|
+
// the name. Reading the wrong spec's imports is worse than reading none.
|
|
704
|
+
return {
|
|
705
|
+
reason: `${matches.length} generated files match ${fileName} and this row does not say which, so its imports could not be read`,
|
|
706
|
+
};
|
|
707
|
+
}
|
|
650
708
|
async function attachReuseOutcome(test, outcomes, handOffs, retrofits,
|
|
651
709
|
// A blocking verdict measured on the delivered files is collected here rather
|
|
652
710
|
// than written into the row: the caller refuses the report on it. Required, not
|
|
653
711
|
// defaulted — a caller that forgot it would ship the report with no refusal and
|
|
654
712
|
// no compiler error. See the refusal below the row map.
|
|
655
|
-
blocking
|
|
713
|
+
blocking,
|
|
714
|
+
/** Declines this run removed from the delivered files. See stripDeliveredGateMarkers. */
|
|
715
|
+
carriedDeclines,
|
|
716
|
+
/** Filled with the carried declines a finding consumed. What consumed nothing is
|
|
717
|
+
* retired: permission that covers no fault authorises the NEXT one instead. */
|
|
718
|
+
consumed,
|
|
719
|
+
/** Filled with the files whose declines this pass actually MEASURED. Only those may
|
|
720
|
+
* be retired: a pass that read nothing consumes nothing, and deleting on that
|
|
721
|
+
* silence drops permission while the fault it covers is still in the file. */
|
|
722
|
+
evaluated,
|
|
723
|
+
/** The row's absolute path when the caller has it (a maintenance row does; a
|
|
724
|
+
* newTestsCreated row carries a basename only). With it, a record or hand-off
|
|
725
|
+
* that names a same-named spec in another directory is not this row's. */
|
|
726
|
+
specPath,
|
|
727
|
+
/** The delivered file this row is about, absolute — a maintenance row's own path,
|
|
728
|
+
* or the path the generators recorded for a newTestsCreated row. Used ONLY for the
|
|
729
|
+
* import-derived fallback below, never for record matching, so passing it cannot
|
|
730
|
+
* change which record a row attaches.
|
|
731
|
+
*
|
|
732
|
+
* A `{ reason }` says the row HAS a delivered file that could not be identified,
|
|
733
|
+
* and the row states that reason. Omitting it altogether says the row has no
|
|
734
|
+
* delivered file to read — a VERIFY row, or one this run never wrote. */
|
|
735
|
+
deliveredPath) {
|
|
656
736
|
// POM records describe browser specs; a basename collision with an API test's
|
|
657
737
|
// fileName must not attach them there. A utils-path record is attachable anywhere.
|
|
658
|
-
const
|
|
738
|
+
const byBasename = outcomes?.[path.basename(test.fileName)];
|
|
739
|
+
const record = specPath &&
|
|
740
|
+
byBasename?.testFilePath &&
|
|
741
|
+
!samePath(byBasename.testFilePath, specPath)
|
|
742
|
+
? undefined
|
|
743
|
+
: byBasename;
|
|
744
|
+
if (specPath && handOffs) {
|
|
745
|
+
handOffs = Object.fromEntries(Object.entries(handOffs).filter(([file]) => samePath(file, specPath)));
|
|
746
|
+
}
|
|
659
747
|
// A utils record carries the test type its verify ran under: a mismatch is a
|
|
660
748
|
// basename collision with another test, not this row's outcome.
|
|
661
749
|
const typeMatches = !record?.utils?.testType || record.utils.testType === test.testType;
|
|
662
750
|
const found = record && typeMatches && (test.testType === TestType.UI || record.utils)
|
|
663
751
|
? record
|
|
664
752
|
: undefined;
|
|
665
|
-
const rederived = found
|
|
753
|
+
const rederived = found
|
|
754
|
+
? await rederiveReuse(found, carriedDeclines)
|
|
755
|
+
: undefined;
|
|
756
|
+
consumed.push(...(rederived?.carriedConsumed ?? []));
|
|
757
|
+
// A measured pass answers for every decline it was handed, including the ones it
|
|
758
|
+
// found nothing for — that is the answer retirement needs. An unmeasured one
|
|
759
|
+
// (no record attached here, a missing spec identity, or a verifier that threw)
|
|
760
|
+
// answers for none of them, and its files stay out of this set.
|
|
761
|
+
if (rederived?.carriedEvaluated)
|
|
762
|
+
for (const d of carriedDeclines)
|
|
763
|
+
if (d.file)
|
|
764
|
+
evaluated.add(d.file);
|
|
666
765
|
const derived = rederived?.outcome;
|
|
667
766
|
if (rederived?.blocking)
|
|
668
767
|
blocking.push(rederived.blocking);
|
|
669
|
-
// Pre-existing generated tests this spec's reuse pass rewired (SKYR-4276 A4): the
|
|
670
|
-
// report names each with its recorded execution, so a reviewer sees that the
|
|
671
|
-
// module became a dependency of code they already owned — and that it still runs.
|
|
672
|
-
if (derived?.helpers && found?.testFilePath) {
|
|
673
|
-
const specPath = found.testFilePath;
|
|
674
|
-
const mine = retrofits.filter((r) => samePath(r.testFile, specPath));
|
|
675
|
-
if (mine.length > 0)
|
|
676
|
-
derived.helpers.retrofits = mine.map((r) => ({
|
|
677
|
-
file: path.basename(r.file),
|
|
678
|
-
...(r.execution ? { execution: r.execution } : {}),
|
|
679
|
-
}));
|
|
680
|
-
}
|
|
681
768
|
// The RAW record: a colliding record of any kind means reuse ran for this basename,
|
|
682
|
-
// and the chain claim must not be made on the filtered view
|
|
683
|
-
|
|
769
|
+
// and the chain claim must not be made on the filtered view — pendingReuseChain
|
|
770
|
+
// fails open on a record it cannot attribute to the spec in front of it, so
|
|
771
|
+
// claiming the chain was skipped here would put the report and the execute gate
|
|
772
|
+
// in direct disagreement. The hand-offs ARE the path-filtered view, which is the
|
|
773
|
+
// same exact-path lookup that gate makes.
|
|
774
|
+
const chainSkipped = await reuseChainSkipped(test.fileName, test.testType, byBasename, handOffs);
|
|
775
|
+
// No record and no chain claim leaves the row with `reuse: null`, which reads
|
|
776
|
+
// identically to "this test reused nothing" — and is read that way. The record is
|
|
777
|
+
// written by the verify pass, so a row only ever got one when the agent chose to
|
|
778
|
+
// call `skyramp_reuse_code` with `verify: true` on that file: a field describing
|
|
779
|
+
// the tool's own activity, presented as a fact about the delivered test. Contract
|
|
780
|
+
// rows are where it shows, because nothing hands the contract chain a reuse step,
|
|
781
|
+
// but the delivered file still imports and calls the helpers.
|
|
782
|
+
//
|
|
783
|
+
// So answer the row from the file instead. Only where there is nothing else to
|
|
784
|
+
// report: a row that already carries a record or a chain claim is untouched, and
|
|
785
|
+
// this can neither overwrite a measured value nor refuse a report.
|
|
786
|
+
const fallback = !derived && !chainSkipped && deliveredPath && isUtilsReuseEnabled()
|
|
787
|
+
? typeof deliveredPath === "string"
|
|
788
|
+
? await deriveReuseFromDelivered(deliveredPath)
|
|
789
|
+
: { notDerived: deliveredPath.reason }
|
|
790
|
+
: undefined;
|
|
684
791
|
const reuse = derived || chainSkipped
|
|
685
792
|
? { ...(derived ?? {}), ...(chainSkipped ? { chainSkipped } : {}) }
|
|
686
|
-
:
|
|
793
|
+
: fallback;
|
|
687
794
|
if (!reuse)
|
|
688
795
|
return test;
|
|
796
|
+
// Pre-existing generated tests this spec's reuse pass rewired (SKYR-4276 A4): the
|
|
797
|
+
// report names each with its recorded execution, so a reviewer sees that the module
|
|
798
|
+
// became a dependency of code they already owned — and that it still runs.
|
|
799
|
+
//
|
|
800
|
+
// Attached to WHICHEVER helpers outcome reached the row, from the row's own spec
|
|
801
|
+
// path. Gating it on a stored record instead dropped every retrofit from a row
|
|
802
|
+
// answered by the fallback — and the retrofit gate still refuses the report until
|
|
803
|
+
// each one has run, so the run was blocked on a disclosure the report never made.
|
|
804
|
+
const rowSpecPath = found?.testFilePath ??
|
|
805
|
+
(typeof deliveredPath === "string" ? deliveredPath : undefined);
|
|
806
|
+
if (reuse.helpers && rowSpecPath) {
|
|
807
|
+
const mine = retrofits.filter((r) => samePath(r.testFile, rowSpecPath));
|
|
808
|
+
if (mine.length > 0)
|
|
809
|
+
reuse.helpers = {
|
|
810
|
+
...reuse.helpers,
|
|
811
|
+
retrofits: mine.map((r) => ({
|
|
812
|
+
file: path.basename(r.file),
|
|
813
|
+
...(r.execution ? { execution: r.execution } : {}),
|
|
814
|
+
})),
|
|
815
|
+
};
|
|
816
|
+
}
|
|
689
817
|
// A record that re-derives to nothing (e.g. a utils path that wrote no file) must
|
|
690
818
|
// not attach an empty object — consumers treat presence as "reuse ran".
|
|
691
819
|
const hasContent = Object.values(reuse).some((v) => v !== undefined);
|
|
@@ -708,6 +836,164 @@ function attachVideoPath(row, videos) {
|
|
|
708
836
|
videoPath: videos?.[path.basename(row.testFilePath)]?.videoPath,
|
|
709
837
|
};
|
|
710
838
|
}
|
|
839
|
+
/**
|
|
840
|
+
* Remove every comment addressed to a Skyramp gate from the files this run delivers.
|
|
841
|
+
*
|
|
842
|
+
* Two gates ask the agent to write a decline INTO the delivered file — the utils
|
|
843
|
+
* verifier's `reuse-verify: allow` and the POM gate's `// kept inline:` — because a
|
|
844
|
+
* gate on a later run can only read what the repository holds. The customer ends up
|
|
845
|
+
* owning both, and a marker records the state at the moment it was written: nothing
|
|
846
|
+
* re-checks it when the file changes afterwards, so a delivered file can carry a
|
|
847
|
+
* claim its own code contradicts.
|
|
848
|
+
*
|
|
849
|
+
* Called only once the report is WRITTEN, which is the one point that is certainly
|
|
850
|
+
* the accepted path. A refusal must leave every marker in place: the agent repairs
|
|
851
|
+
* and resubmits, and a stripped marker would fire the gate again on a fault it has
|
|
852
|
+
* already declined. Nothing downstream reads a marker — the re-derivation that
|
|
853
|
+
* builds the report rows has already run, and the action stages current working-tree
|
|
854
|
+
* content before it commits (`git update-index --again`, plus the test-directory
|
|
855
|
+
* adds), so an edit made here reaches the commit without any change on that side.
|
|
856
|
+
*
|
|
857
|
+
* The accepted cost: a later run re-detects the same fault and the agent declines it
|
|
858
|
+
* again. That costs turns inside the run and never reaches the customer — and each
|
|
859
|
+
* run then judges the file in front of it rather than honouring a verdict taken
|
|
860
|
+
* against an older one.
|
|
861
|
+
*
|
|
862
|
+
* Best-effort throughout. The report is already on disk, so nothing here may throw.
|
|
863
|
+
*/
|
|
864
|
+
/**
|
|
865
|
+
* The delivered files one reuse record owns: the spec itself, and the shared modules
|
|
866
|
+
* located beside it.
|
|
867
|
+
*
|
|
868
|
+
* Only files this RUN changed. `locateUtilsFiles` also returns a module the spec
|
|
869
|
+
* merely imports, and a marker an older run left in one of those is not this run's to
|
|
870
|
+
* remove — editing it would put a file the run never touched into the customer's pull
|
|
871
|
+
* request. When the change list cannot be read the located set is used unchanged,
|
|
872
|
+
* which is the behaviour every other reuse check falls back to.
|
|
873
|
+
*/
|
|
874
|
+
export async function deliveredFilesOf(record, changed) {
|
|
875
|
+
const testFile = record.testFilePath;
|
|
876
|
+
if (!testFile)
|
|
877
|
+
return [];
|
|
878
|
+
// Both sides of the comparison are canonicalised HERE, because this function owns
|
|
879
|
+
// the comparison. `listChangedFilesAbs` resolves its paths and the located files are
|
|
880
|
+
// real paths, so on a repository whose path traverses a symlink the two spellings
|
|
881
|
+
// never matched and nothing was stripped — silently, and only on those setups.
|
|
882
|
+
const changedReal = changed
|
|
883
|
+
? new Set(await Promise.all([...changed].map(realpath)))
|
|
884
|
+
: undefined;
|
|
885
|
+
// The spec takes the same membership check as the modules beside it. Exempting it
|
|
886
|
+
// let the strip edit a spec this run never touched — the same fault as an
|
|
887
|
+
// imported-only module, and the same new diff in the customer's pull request.
|
|
888
|
+
const spec = await realpath(testFile);
|
|
889
|
+
const files = !changedReal || changedReal.has(spec) ? [spec] : [];
|
|
890
|
+
try {
|
|
891
|
+
for (const utilsFile of await locateUtilsFiles({
|
|
892
|
+
testFile,
|
|
893
|
+
language: record.language,
|
|
894
|
+
})) {
|
|
895
|
+
const real = await realpath(utilsFile);
|
|
896
|
+
if (!changedReal || changedReal.has(real))
|
|
897
|
+
files.push(real);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
catch (err) {
|
|
901
|
+
logger.warning("Could not locate the shared utils files for a delivered spec", { testFile, error: String(err) });
|
|
902
|
+
}
|
|
903
|
+
return files;
|
|
904
|
+
}
|
|
905
|
+
async function stripDeliveredGateMarkers(outcomes, retrofits,
|
|
906
|
+
/** Absolute paths this run changed, or undefined when they cannot be read. */
|
|
907
|
+
changed) {
|
|
908
|
+
const stripped = {};
|
|
909
|
+
const files = new Set();
|
|
910
|
+
// Each grammar has ONE file role its gate reads it from: `verifyUtils` reads
|
|
911
|
+
// `reuse-verify: allow` out of a located utils module, and the re-derivation reads
|
|
912
|
+
// `// kept inline:` out of the record's spec. Parsing both out of every file made a
|
|
913
|
+
// marker in the wrong place MORE powerful than the gate treats it: an allow the
|
|
914
|
+
// agent wrote into the spec is ignored where it sits, but was recorded here and
|
|
915
|
+
// then authorised the next matching helper fault. The strip still removes it —
|
|
916
|
+
// nothing addressed to the gate ships — it is only not carried.
|
|
917
|
+
const specPaths = new Set(await Promise.all(Object.values(outcomes ?? {})
|
|
918
|
+
.map((r) => r?.testFilePath)
|
|
919
|
+
.filter((p) => !!p)
|
|
920
|
+
.map(realpath)));
|
|
921
|
+
// Every path is canonicalised here and in the record, for the reason `reuseHandOffs`
|
|
922
|
+
// keys on `realpath`: a tool-supplied path may reach the same file through a symlink
|
|
923
|
+
// (macOS resolves /tmp to /private/tmp), so a raw key written by one source and read
|
|
924
|
+
// from another names one file twice.
|
|
925
|
+
// Every file this run CHANGED, when the change list can be read. Recording a reuse
|
|
926
|
+
// outcome is best-effort, so a changed file with no record would never be scanned —
|
|
927
|
+
// and its marker would ship, against what AC5 and this tool's description promise.
|
|
928
|
+
// The records are the fallback for when the change list is not available.
|
|
929
|
+
// Canonicalised on the way in, like every other source. `listChangedFilesAbs` only
|
|
930
|
+
// RESOLVES its names, against the root git was given, so a checkout reached through
|
|
931
|
+
// a symlink keeps that spelling — and the record would name a file the next
|
|
932
|
+
// submission looks up by real path, losing the decline it just carried.
|
|
933
|
+
if (changed)
|
|
934
|
+
for (const file of changed)
|
|
935
|
+
files.add(await realpath(file));
|
|
936
|
+
else
|
|
937
|
+
for (const record of Object.values(outcomes ?? {}))
|
|
938
|
+
for (const file of await deliveredFilesOf(record ?? {}, undefined))
|
|
939
|
+
files.add(file);
|
|
940
|
+
// Pre-existing generated tests this run rewired: the delivery commits them too.
|
|
941
|
+
for (const retrofit of retrofits)
|
|
942
|
+
files.add(await realpath(retrofit.file));
|
|
943
|
+
for (const file of files) {
|
|
944
|
+
const spec = utilsSpecForFile(file);
|
|
945
|
+
if (!spec)
|
|
946
|
+
continue;
|
|
947
|
+
try {
|
|
948
|
+
const before = await fs.readFile(file, "utf-8");
|
|
949
|
+
const { content, removed, unremovable } = stripGateMarkers(before, spec);
|
|
950
|
+
if (unremovable.length > 0) {
|
|
951
|
+
logger.warning("A gate-addressed comment could not be removed from a delivered file without risking its syntax — it ships as it stands", { file, markers: unremovable });
|
|
952
|
+
}
|
|
953
|
+
if (removed.length === 0)
|
|
954
|
+
continue;
|
|
955
|
+
// The last check before the customer's file changes, and the only one that does
|
|
956
|
+
// not depend on the strip being right. Every defect this had was a case nobody
|
|
957
|
+
// foresaw; this states the one edit that is permitted and refuses the rest, so
|
|
958
|
+
// an unforeseen case delivers the marker instead of a file that does not build.
|
|
959
|
+
if (!onlyCommentLinesRemoved(before, content, spec.commentPrefix)) {
|
|
960
|
+
logger.warning("The gate-comment strip would have changed more than whole comment lines — the file is left exactly as delivered", { file, removed });
|
|
961
|
+
continue;
|
|
962
|
+
}
|
|
963
|
+
await fs.writeFile(file, content, "utf-8");
|
|
964
|
+
// Parsed, not kept as text: what a later call needs is WHICH decline was made,
|
|
965
|
+
// so it can read it the way it reads a marker still in the file. Keeping the
|
|
966
|
+
// line and appending it back edited the customer's file a second time, and an
|
|
967
|
+
// agent that reformatted or reworded the decline in between defeated the
|
|
968
|
+
// substring match in both directions.
|
|
969
|
+
const text = removed.join("\n");
|
|
970
|
+
const declines = [
|
|
971
|
+
...((await hasUtilsHeader(file))
|
|
972
|
+
? parseUtilsAllows(text, spec).allows.map(({ kind, helper }) => ({
|
|
973
|
+
kind,
|
|
974
|
+
helper,
|
|
975
|
+
}))
|
|
976
|
+
: []),
|
|
977
|
+
...(specPaths.has(file)
|
|
978
|
+
? parseKeptInline(text).map(({ pageObject, reason }) => ({
|
|
979
|
+
keptInline: pageObject,
|
|
980
|
+
reason,
|
|
981
|
+
}))
|
|
982
|
+
: []),
|
|
983
|
+
];
|
|
984
|
+
if (declines.length > 0)
|
|
985
|
+
stripped[file] = declines;
|
|
986
|
+
logger.info("Removed gate-addressed comment(s) from a delivered file", {
|
|
987
|
+
file,
|
|
988
|
+
removed,
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
catch (err) {
|
|
992
|
+
logger.warning("Could not strip gate-addressed comments from a delivered file", { file, error: String(err) });
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
return stripped;
|
|
996
|
+
}
|
|
711
997
|
/** Two-space indent on every line, so a multi-line verdict reads as one bullet's body. */
|
|
712
998
|
function indent(text) {
|
|
713
999
|
return text
|
|
@@ -740,20 +1026,13 @@ function deduplicateById(items) {
|
|
|
740
1026
|
* basename. A file can own several rows: failed if ANY failed, first failure
|
|
741
1027
|
* carries the text; `Skipped` rows are evidence in neither direction. */
|
|
742
1028
|
function collectExecutionOutcomes(delivered, results) {
|
|
743
|
-
// Absent names nothing to disagree with, so it matches any — the same rule
|
|
744
|
-
// `checkEveryDeliveredTestRan` uses.
|
|
745
|
-
const sameRepository = (a, b) => {
|
|
746
|
-
const left = (a ?? "").trim();
|
|
747
|
-
const right = (b ?? "").trim();
|
|
748
|
-
return left === "" || right === "" || left === right;
|
|
749
|
-
};
|
|
750
1029
|
const outcomes = [];
|
|
751
1030
|
for (const test of delivered) {
|
|
752
1031
|
const plannedTestId = test.plannedTestId?.trim();
|
|
753
1032
|
if (!plannedTestId)
|
|
754
1033
|
continue;
|
|
755
1034
|
const rows = results.filter((row) => row.status !== "Skipped" &&
|
|
756
|
-
|
|
1035
|
+
row.repository === test.repository &&
|
|
757
1036
|
testFileMatches(row.testFilePath, test.fileName));
|
|
758
1037
|
if (rows.length === 0)
|
|
759
1038
|
continue;
|
|
@@ -819,23 +1098,15 @@ function checkOneEntryPerCandidate(delivered) {
|
|
|
819
1098
|
* a planned test with no outcome, so a filename the results do not carry was the
|
|
820
1099
|
* way past it. This is the check that says so. */
|
|
821
1100
|
function checkEveryDeliveredTestRan(delivered, results) {
|
|
822
|
-
// A side that names no repository names none to disagree with, so it matches
|
|
823
|
-
// any. A single-repo run fills the field on one list and not the other often
|
|
824
|
-
// enough that requiring both to agree reported tests that had plainly run.
|
|
825
|
-
const sameRepository = (a, b) => {
|
|
826
|
-
const left = (a ?? "").trim();
|
|
827
|
-
const right = (b ?? "").trim();
|
|
828
|
-
return left === "" || right === "" || left === right;
|
|
829
|
-
};
|
|
830
1101
|
return delivered
|
|
831
|
-
.filter((test) => !results.some((row) =>
|
|
1102
|
+
.filter((test) => !results.some((row) => row.repository === test.repository &&
|
|
832
1103
|
testFileMatches(row.testFilePath, test.fileName)))
|
|
833
1104
|
.map((test) => {
|
|
834
1105
|
// Say which half failed. "No row names that file" reads as a missing run
|
|
835
1106
|
// when the file is there and only the repository differs.
|
|
836
1107
|
const byName = results.filter((row) => testFileMatches(row.testFilePath, test.fileName));
|
|
837
1108
|
const evidence = byName.length
|
|
838
|
-
? `reported as ${test.fileName} for ${test.repository
|
|
1109
|
+
? `reported as ${test.fileName} for ${test.repository}, and the ${byName.length === 1 ? "row" : "rows"} naming that file ${byName.length === 1 ? "is" : "are"} for ${byName.map((row) => row.repository).join(", ")}`
|
|
839
1110
|
: `reported as ${test.fileName}, no testResults row names that file`;
|
|
840
1111
|
return {
|
|
841
1112
|
objectionId: `deliveredTestRan:${test.testId}`,
|
|
@@ -846,15 +1117,118 @@ function checkEveryDeliveredTestRan(delivered, results) {
|
|
|
846
1117
|
};
|
|
847
1118
|
});
|
|
848
1119
|
}
|
|
849
|
-
/**
|
|
850
|
-
* is
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1120
|
+
/** A report cannot treat an explicit skip as a completed execution. Unlike an absent
|
|
1121
|
+
* result, this is a direct claim that the agent chose not to run a delivered test. */
|
|
1122
|
+
function skippedDeliveredTests(delivered, results) {
|
|
1123
|
+
return delivered.filter((test) => {
|
|
1124
|
+
const matching = results.filter((row) => row.repository === test.repository &&
|
|
1125
|
+
testFileMatches(row.testFilePath, test.fileName));
|
|
1126
|
+
return (matching.some((row) => row.status === "Skipped") &&
|
|
1127
|
+
!matching.some((row) => row.status !== "Skipped"));
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
/** A planned test the plan typed `ui`. The plan's `testType` is free-form text —
|
|
1131
|
+
* the unapproved-entry check above says so — so it is trimmed and lower-cased
|
|
1132
|
+
* rather than compared against the enum. */
|
|
1133
|
+
function plannedUiTests(plannedTests) {
|
|
1134
|
+
return plannedTests.filter((test) => !!test?.plannedTestId?.trim() &&
|
|
1135
|
+
String(test?.scenario?.testType ?? "")
|
|
1136
|
+
.trim()
|
|
1137
|
+
.toLowerCase() === TestType.UI);
|
|
1138
|
+
}
|
|
1139
|
+
/** Planned UI tests with no entry in the report: the recordings that never
|
|
1140
|
+
* happened, named by the ids the agent would otherwise only have to answer for. */
|
|
1141
|
+
/** Every planned test with no entry in the report, whatever its type. The UI
|
|
1142
|
+
* filter below narrows this; the budget-excuse gate does not, because a dropped
|
|
1143
|
+
* contract test is dropped the same way a dropped recording is. */
|
|
1144
|
+
function undeliveredPlannedTests(plannedTests, delivered) {
|
|
1145
|
+
const shipped = new Set(delivered
|
|
1146
|
+
.map((test) => test.plannedTestId?.trim())
|
|
1147
|
+
.filter((id) => !!id));
|
|
1148
|
+
return plannedTests.filter((test) => !!test?.plannedTestId?.trim() && !shipped.has(test.plannedTestId.trim()));
|
|
1149
|
+
}
|
|
1150
|
+
function undeliveredPlannedUiTests(plannedTests, delivered) {
|
|
1151
|
+
return undeliveredPlannedTests(plannedUiTests(plannedTests), delivered);
|
|
1152
|
+
}
|
|
1153
|
+
/** Whether the file can be read. The written record outlives the file, and a
|
|
1154
|
+
* path that exists but cannot be opened owes no run either — the objection
|
|
1155
|
+
* would name a file nobody can look at. */
|
|
1156
|
+
async function pathExists(file) {
|
|
1157
|
+
try {
|
|
1158
|
+
await fs.access(file, fsConstants.R_OK);
|
|
1159
|
+
return true;
|
|
1160
|
+
}
|
|
1161
|
+
catch {
|
|
1162
|
+
return false;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
/** Whether the recorded run is a run of what this run WROTE.
|
|
1166
|
+
*
|
|
1167
|
+
* An edited file is run twice — once before the edit for a baseline, once
|
|
1168
|
+
* after — and only the second says anything about the edit, so a file left with
|
|
1169
|
+
* just its baseline has not been run in the sense this check is about.
|
|
1170
|
+
*
|
|
1171
|
+
* A generated file has no baseline of its own, but the path may still carry an
|
|
1172
|
+
* older run: REGENERATE rewrites a file that already existed and was already
|
|
1173
|
+
* run, and generating twice to one path does the same. The run has to be later
|
|
1174
|
+
* than the write, or it is a run of bytes that are gone. An unreadable or
|
|
1175
|
+
* missing timestamp on either side falls back to "a run is a run", which is the
|
|
1176
|
+
* behaviour before this and never invents an objection. */
|
|
1177
|
+
function runOfWhatWasWritten(file) {
|
|
1178
|
+
if (file.how === "updated")
|
|
1179
|
+
return file.recordedPhase === "after";
|
|
1180
|
+
if (file.recordedPhase === undefined)
|
|
1181
|
+
return false;
|
|
1182
|
+
const wrote = file.generatedAt;
|
|
1183
|
+
// The execution record keeps an ISO string, written by the execution path
|
|
1184
|
+
// rather than here, so it is parsed rather than changed in this PR.
|
|
1185
|
+
const ran = Date.parse(file.executedAt ?? "");
|
|
1186
|
+
if (wrote === undefined || Number.isNaN(ran))
|
|
1187
|
+
return true;
|
|
1188
|
+
return ran >= wrote;
|
|
1189
|
+
}
|
|
1190
|
+
/** Every test file this run wrote has an execution this server performed.
|
|
1191
|
+
*
|
|
1192
|
+
* Informational, like every check here: it surfaces the gap for a person to read
|
|
1193
|
+
* and the agent can answer it. The agent is not blocked — a hard ban on running a
|
|
1194
|
+
* test outside the tool can dead-end a run, which is why the prompt states a
|
|
1195
|
+
* preference rather than a rule.
|
|
1196
|
+
*
|
|
1197
|
+
* The two lists it joins are both the server's own, keyed by canonical absolute
|
|
1198
|
+
* path: `generatedTests`, written by the generators, and `testExecutions`,
|
|
1199
|
+
* written by skyramp_execute_test. The agent supplies neither, so unlike the
|
|
1200
|
+
* report-side checks this cannot be satisfied by what the report claims.
|
|
1201
|
+
*
|
|
1202
|
+
* What it proves is that the file was handed to the runner. It is NOT proof that
|
|
1203
|
+
* a test ran: a runner can exit 0 having executed nothing. */
|
|
1204
|
+
function checkEveryWrittenTestRan(written) {
|
|
1205
|
+
return written
|
|
1206
|
+
.filter((file) => !runOfWhatWasWritten(file))
|
|
1207
|
+
.map((file) => ({
|
|
1208
|
+
// The path is the id, because the agent quotes the id back in `answers` and a
|
|
1209
|
+
// path is what it can recognise. Everything after the first colon is that
|
|
1210
|
+
// path, colons included — a reader must not split the id on every colon.
|
|
1211
|
+
objectionId: `testRanOnThisServer:${file.file}`,
|
|
1212
|
+
verifier: "testRanOnThisServer",
|
|
1213
|
+
// Says what is recorded, never that the file went unrun: the agent is
|
|
1214
|
+
// allowed to run a test with the repository's own runner, and a run it
|
|
1215
|
+
// drove itself is not visible here.
|
|
1216
|
+
message: file.how === "generated"
|
|
1217
|
+
? "This run generated this test file, and this server recorded no run of it after it was written."
|
|
1218
|
+
: file.recordedPhase === "before"
|
|
1219
|
+
? "This run edited this test file, and the only run of it this server recorded is from before the edit."
|
|
1220
|
+
: "This run edited this test file, and this server recorded no run of it.",
|
|
1221
|
+
evidence: file.recordedPhase === "before"
|
|
1222
|
+
? `${file.file} — the only recorded skyramp_execute_test run of this file is the pre-edit baseline`
|
|
1223
|
+
: file.how === "generated" && file.recordedPhase !== undefined
|
|
1224
|
+
? `${file.file} — the last recorded skyramp_execute_test run of this path is older than the file this run wrote`
|
|
1225
|
+
: `${file.file} — no skyramp_execute_test run of this file is recorded`,
|
|
1226
|
+
suggestion: "Either run the file with `skyramp_execute_test`, or answer this objection in `answers` — say how the test was run, or why it was not run. A `testResults` row does not answer this: the run is checked against the executions this server performed, not against what the report claims.",
|
|
1227
|
+
}));
|
|
1228
|
+
}
|
|
1229
|
+
/** The report accepts every `TestType`, so an entry outside the four a plan can
|
|
1230
|
+
* express can never name a planned test and must not be asked to. */
|
|
1231
|
+
const PLANNABLE_TEST_TYPES = new Set(TESTBOT_SUPPORTED_TEST_TYPES);
|
|
858
1232
|
function runPostExecutionChecks(plan, delivered, results, issues,
|
|
859
1233
|
/** The plan-only lane delivers nothing, so there a planned test is proof
|
|
860
1234
|
* enough for a bug. Passed in, not read here, so the checks stay pure. */
|
|
@@ -864,9 +1238,12 @@ planOnly, maintained = []) {
|
|
|
864
1238
|
// unplanned: a plan cannot hold one, so the objection would name a mistake the
|
|
865
1239
|
// agent has no way to fix.
|
|
866
1240
|
.filter((test) => PLANNABLE_TEST_TYPES.has(test.testType))
|
|
867
|
-
.
|
|
868
|
-
.
|
|
869
|
-
|
|
1241
|
+
.flatMap((test) => {
|
|
1242
|
+
const plannedTestId = test.plannedTestId?.trim();
|
|
1243
|
+
return plannedTestId
|
|
1244
|
+
? [{ plannedTestId, repository: test.repository }]
|
|
1245
|
+
: [];
|
|
1246
|
+
});
|
|
870
1247
|
return [
|
|
871
1248
|
...runPostExecutionCheck("deliveredMatchesPlan", "deliveredMatchesPlan", () => checkDeliveredMatchesPlan(plan, shipped)),
|
|
872
1249
|
// The maintenance mirror: a `maintains` entry credited coverage at plan time,
|
|
@@ -881,17 +1258,37 @@ planOnly, maintained = []) {
|
|
|
881
1258
|
...runPostExecutionCheck("defectsReported", "defects:unreported", () => checkDefectsReported(plan, issues)),
|
|
882
1259
|
];
|
|
883
1260
|
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Dependency edits can land in the primary checkout, a separate tests checkout,
|
|
1263
|
+
* or a related repository. Verify each configured path once, skipping absent paths.
|
|
1264
|
+
*/
|
|
1265
|
+
function collectDependencyVerificationRoots(primaryRepoRoot, testsRepoRoot, relatedRepos) {
|
|
1266
|
+
const roots = new Set([primaryRepoRoot]);
|
|
1267
|
+
if (testsRepoRoot) {
|
|
1268
|
+
roots.add(testsRepoRoot);
|
|
1269
|
+
}
|
|
1270
|
+
for (const repo of Object.values(relatedRepos ?? {})) {
|
|
1271
|
+
if (repo.repositoryPath) {
|
|
1272
|
+
roots.add(repo.repositoryPath);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
return Array.from(roots);
|
|
1276
|
+
}
|
|
884
1277
|
export function registerSubmitReportTool(server) {
|
|
885
1278
|
server.registerTool(TOOL_NAME, {
|
|
886
1279
|
annotations: {
|
|
887
1280
|
readOnlyHint: false,
|
|
888
|
-
|
|
1281
|
+
// Overwrites the report file on disk, AND edits delivered source files on an
|
|
1282
|
+
// accepted call: a comment addressed to a Skyramp gate is removed from each
|
|
1283
|
+
// file this run changed. See stripDeliveredGateMarkers.
|
|
1284
|
+
destructiveHint: true,
|
|
889
1285
|
idempotentHint: false,
|
|
890
1286
|
openWorldHint: false,
|
|
891
1287
|
},
|
|
892
1288
|
description: "Submit the final testbot report. Call this tool once, after all test analysis, generation and execution. " +
|
|
893
1289
|
"This is the ONLY way to submit the report — do NOT write the report to a file manually. " +
|
|
894
|
-
"If a check objects to what you reported, the objection names an id: fix what you can, and answer the rest through `answers` on a second call."
|
|
1290
|
+
"If a check objects to what you reported, the objection names an id: fix what you can, and answer the rest through `answers` on a second call. " +
|
|
1291
|
+
"On an ACCEPTED call this tool also edits the files this run changed: a comment addressed to a Skyramp gate — `reuse-verify: allow …` or `// kept inline: …` — is removed from each of them, because it speaks to a gate and not to the customer who owns the file. Nothing else in those files is touched. A REFUSED call leaves every source file exactly as it found it; it still records its own run state, such as the refusal count and the objections it raised.",
|
|
895
1292
|
inputSchema: {
|
|
896
1293
|
businessCaseAnalysis: z
|
|
897
1294
|
.string()
|
|
@@ -951,8 +1348,9 @@ export function registerSubmitReportTool(server) {
|
|
|
951
1348
|
stateFile: z
|
|
952
1349
|
.string()
|
|
953
1350
|
.optional()
|
|
954
|
-
.describe("Optional
|
|
955
|
-
"
|
|
1351
|
+
.describe("Optional, and a Testbot run leaves it out: the run finds its own state file. " +
|
|
1352
|
+
"A path is accepted only when it names that same file — one from another run holds " +
|
|
1353
|
+
"another run's execution results, and the call is refused. " +
|
|
956
1354
|
"The report is written beside it, and it holds the execution results, the " +
|
|
957
1355
|
"page captures and the videos this report needs."),
|
|
958
1356
|
},
|
|
@@ -967,7 +1365,9 @@ export function registerSubmitReportTool(server) {
|
|
|
967
1365
|
// event in the finally. Empty (no properties) for non-enforced runs.
|
|
968
1366
|
const languageTelemetry = {};
|
|
969
1367
|
// The run's own state file, unless the agent named one.
|
|
970
|
-
const stateFile =
|
|
1368
|
+
const { path: stateFile, refusal } = resolveOwnRunStatePath(params.stateFile);
|
|
1369
|
+
if (refusal)
|
|
1370
|
+
return toolError(refusal);
|
|
971
1371
|
if (!stateFile) {
|
|
972
1372
|
return toolError("This run has no state file. Call skyramp_analyze_changes first — it writes the state file this report goes beside.");
|
|
973
1373
|
}
|
|
@@ -1145,8 +1545,7 @@ export function registerSubmitReportTool(server) {
|
|
|
1145
1545
|
const plannedTest = plannedById.get((t.plannedTestId ?? "").trim());
|
|
1146
1546
|
if (!plannedTest)
|
|
1147
1547
|
return true;
|
|
1148
|
-
|
|
1149
|
-
return plannedType !== "" && plannedType !== t.testType;
|
|
1548
|
+
return plannedTest.scenario.testType !== t.testType;
|
|
1150
1549
|
});
|
|
1151
1550
|
if (unapproved.length > 0) {
|
|
1152
1551
|
const plannedList = plannedById.size > 0
|
|
@@ -1168,13 +1567,10 @@ export function registerSubmitReportTool(server) {
|
|
|
1168
1567
|
// Read once — the file can carry a large diffText and readRepoData would
|
|
1169
1568
|
// re-parse it twice per repo.
|
|
1170
1569
|
const fullMaintenanceState = await stateManager.readFullState();
|
|
1171
|
-
// The
|
|
1172
|
-
//
|
|
1173
|
-
|
|
1174
|
-
const
|
|
1175
|
-
process.env.GITHUB_REPOSITORY?.trim() ||
|
|
1176
|
-
undefined;
|
|
1177
|
-
const maintenanceSections = [{ data: stateData }];
|
|
1570
|
+
// The handler already refused above when the file was missing or empty,
|
|
1571
|
+
// and the run that created it names the primary on the root.
|
|
1572
|
+
const primaryRepository = fullMaintenanceState.metadata.repository;
|
|
1573
|
+
const maintenanceSections = [{ repository: primaryRepository, data: stateData }];
|
|
1178
1574
|
for (const [repo, section] of Object.entries(fullMaintenanceState?.relatedRepos ?? {})) {
|
|
1179
1575
|
// Only these two keys resolve back to the primary, already collected
|
|
1180
1576
|
// above. Any other key — a case variant, or another name over the same
|
|
@@ -1189,27 +1585,16 @@ export function registerSubmitReportTool(server) {
|
|
|
1189
1585
|
maintenanceSections.push({ repository: repo, data });
|
|
1190
1586
|
}
|
|
1191
1587
|
// Stamp every row, primary included and single-repo runs included — as the
|
|
1192
|
-
// prompt already requires of the other report sections.
|
|
1193
|
-
//
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
// the primary and the run is not on a GitHub runner either. Only sections
|
|
1197
|
-
// that actually produce rows need a name — a run with no verdicts at all
|
|
1198
|
-
// has nothing to attribute and must still be able to submit.
|
|
1199
|
-
if (!primaryRepository &&
|
|
1200
|
-
maintenanceSections.some((section) => !section.repository &&
|
|
1201
|
-
(section.data.maintenanceVerdicts?.length ?? 0) > 0)) {
|
|
1202
|
-
errorResult = toolError("Cannot name the repository for the maintenance rows: the stateFile records no primary repository " +
|
|
1203
|
-
"and GITHUB_REPOSITORY is unset. Pass `repository` (and `primaryRepository` in a multi-repo run) " +
|
|
1204
|
-
"to skyramp_analyze_changes, then re-run it.");
|
|
1205
|
-
return errorResult;
|
|
1206
|
-
}
|
|
1588
|
+
// prompt already requires of the other report sections. The primary's
|
|
1589
|
+
// section carries the name the state file's root records; a related repo's
|
|
1590
|
+
// carries its own section key.
|
|
1591
|
+
const repositoryOf = (section) => section.repository;
|
|
1207
1592
|
// Reject rather than throw: a non-array here, or a missing testFilePath
|
|
1208
1593
|
// below, escapes the handler and loses the analytics event in its `finally`.
|
|
1209
1594
|
const malformedSections = maintenanceSections
|
|
1210
1595
|
.filter(({ data }) => data.maintenanceVerdicts !== undefined &&
|
|
1211
1596
|
!Array.isArray(data.maintenanceVerdicts))
|
|
1212
|
-
.map(({ repository }) => repository
|
|
1597
|
+
.map(({ repository }) => repository);
|
|
1213
1598
|
if (malformedSections.length > 0) {
|
|
1214
1599
|
errorResult = toolError(`stateFile has a non-array maintenanceVerdicts for: ${malformedSections.join(", ")}. ` +
|
|
1215
1600
|
`Re-run skyramp_actions for that repository to rewrite the section.`);
|
|
@@ -1225,9 +1610,11 @@ export function registerSubmitReportTool(server) {
|
|
|
1225
1610
|
// verdict PER page object, so a spec-keyed map keeps only the last.
|
|
1226
1611
|
const rawMaintenance = maintenanceSections.flatMap((section) => (section.data.maintenanceVerdicts ?? [])
|
|
1227
1612
|
.filter((v) => {
|
|
1228
|
-
if (v &&
|
|
1613
|
+
if (v &&
|
|
1614
|
+
typeof v.testFilePath === "string" &&
|
|
1615
|
+
v.testFilePath.trim())
|
|
1229
1616
|
return true;
|
|
1230
|
-
badVerdicts.push(section.repository
|
|
1617
|
+
badVerdicts.push(section.repository);
|
|
1231
1618
|
return false;
|
|
1232
1619
|
})
|
|
1233
1620
|
.map((v) => ({
|
|
@@ -1237,6 +1624,7 @@ export function registerSubmitReportTool(server) {
|
|
|
1237
1624
|
testType: v.testType,
|
|
1238
1625
|
endpoint: v.endpoint,
|
|
1239
1626
|
action: v.action,
|
|
1627
|
+
reportOnly: v.reportOnly,
|
|
1240
1628
|
// A baseline refresh (SKYR-4298) leaves the spec untouched, so name the
|
|
1241
1629
|
// replaced PNGs here — the reviewer's cue to open the image diff in the PR.
|
|
1242
1630
|
description: v.rebaselineSnapshots?.length
|
|
@@ -1273,6 +1661,12 @@ export function registerSubmitReportTool(server) {
|
|
|
1273
1661
|
// draft a summary for it) — collected here and rejected below, rather than
|
|
1274
1662
|
// silently shipping a blank field.
|
|
1275
1663
|
const missingDetails = [];
|
|
1664
|
+
// An id no plan defect declares traces nothing, exactly as `defectId` on an
|
|
1665
|
+
// issue does not: without this the row could name a defect the run invented.
|
|
1666
|
+
const declaredDefectIds = new Set((stateData.plan?.defects ?? [])
|
|
1667
|
+
.map((d) => String(d?.id ?? "").trim())
|
|
1668
|
+
.filter((id) => id.length > 0));
|
|
1669
|
+
const undeclaredDefects = [];
|
|
1276
1670
|
if (dedupedMaintenance.length > 0) {
|
|
1277
1671
|
// beforeStatus/afterStatus are always stateFile-authoritative. beforeDetails/
|
|
1278
1672
|
// afterDetails come from the LLM's drafted summary (testMaintenanceDetails) —
|
|
@@ -1299,20 +1693,39 @@ export function registerSubmitReportTool(server) {
|
|
|
1299
1693
|
// must not satisfy the "drafted a summary" requirement.
|
|
1300
1694
|
const beforeDetails = detail?.beforeDetails?.trim() ?? "";
|
|
1301
1695
|
const afterDetails = detail?.afterDetails?.trim() ?? "";
|
|
1302
|
-
|
|
1696
|
+
// A recorded run owes a summary. The Unknown placeholder a reservation
|
|
1697
|
+
// leaves when the run never landed a result (SKYR-4460) is not one:
|
|
1698
|
+
// there is no output to summarise, and the row publishes Unknown.
|
|
1699
|
+
const recordedBefore = recorded?.executionBefore &&
|
|
1700
|
+
recorded.executionBefore.status !== TestExecutionStatus.Unknown;
|
|
1701
|
+
const recordedAfter = recorded?.executionAfter &&
|
|
1702
|
+
recorded.executionAfter.status !== TestExecutionStatus.Unknown;
|
|
1703
|
+
if (recordedBefore && !beforeDetails)
|
|
1303
1704
|
missingDetails.push(`${displayName} (beforeDetails)`);
|
|
1304
|
-
if (
|
|
1705
|
+
if (recordedAfter && !afterDetails)
|
|
1305
1706
|
missingDetails.push(`${displayName} (afterDetails)`);
|
|
1306
1707
|
logger.info(`${displayName}: before=${beforeStatus} after=${afterStatus}`);
|
|
1708
|
+
const defects = (detail?.defects ?? [])
|
|
1709
|
+
.map((id) => String(id ?? "").trim())
|
|
1710
|
+
.filter((id) => id.length > 0);
|
|
1711
|
+
for (const id of defects)
|
|
1712
|
+
if (!declaredDefectIds.has(id))
|
|
1713
|
+
undeclaredDefects.push(`${displayName}: ${id}`);
|
|
1307
1714
|
return {
|
|
1308
1715
|
...m,
|
|
1309
1716
|
beforeDetails,
|
|
1310
1717
|
afterDetails,
|
|
1718
|
+
defects,
|
|
1311
1719
|
beforeStatus,
|
|
1312
1720
|
afterStatus,
|
|
1313
1721
|
};
|
|
1314
1722
|
});
|
|
1315
1723
|
}
|
|
1724
|
+
if (undeclaredDefects.length > 0) {
|
|
1725
|
+
errorResult = toolError(`${undeclaredDefects.length} maintenance row(s) name a defect the plan does not declare: ${undeclaredDefects.join(", ")}. ` +
|
|
1726
|
+
"Spell each id as your registered `defects` list spells it, or leave it out.");
|
|
1727
|
+
return errorResult;
|
|
1728
|
+
}
|
|
1316
1729
|
if (missingDetails.length > 0) {
|
|
1317
1730
|
const uniqueMissing = [...new Set(missingDetails)];
|
|
1318
1731
|
errorResult = toolError(`${uniqueMissing.length} maintenance row(s) have a recorded execution but no drafted summary: ${uniqueMissing.join(", ")}. ` +
|
|
@@ -1324,6 +1737,9 @@ export function registerSubmitReportTool(server) {
|
|
|
1324
1737
|
// rows below; an unexecuted one refuses the report (inside the SKYR-3883 block,
|
|
1325
1738
|
// which already enumerates the working tree).
|
|
1326
1739
|
let retrofitViews = [];
|
|
1740
|
+
/** Absolute paths this run changed. Undefined when they cannot be read — every
|
|
1741
|
+
* reuse check falls back to acting on what it located. */
|
|
1742
|
+
let changedFilesAbs;
|
|
1327
1743
|
// SKYR-3883: in a testbot run, refuse to ship a report that claims file work
|
|
1328
1744
|
// the working tree doesn't reflect. The delivery step can only commit what the
|
|
1329
1745
|
// agent actually created/edited, so a report claiming otherwise erodes trust
|
|
@@ -1335,6 +1751,25 @@ export function registerSubmitReportTool(server) {
|
|
|
1335
1751
|
const fullState = await stateManager.readFullState();
|
|
1336
1752
|
const repoRoot = fullState?.metadata?.repositoryPath;
|
|
1337
1753
|
if (repoRoot && repoRoot !== "unknown") {
|
|
1754
|
+
const dependencyRoots = collectDependencyVerificationRoots(repoRoot, getTestsRepoDir(), fullState?.relatedRepos);
|
|
1755
|
+
for (const dependencyRoot of dependencyRoots) {
|
|
1756
|
+
try {
|
|
1757
|
+
const dependencyVerification = await verifyTestDependencyChanges(dependencyRoot);
|
|
1758
|
+
if (!dependencyVerification.ok) {
|
|
1759
|
+
errorResult = toolError("The working tree violates the safe test-only dependency policy:\n" +
|
|
1760
|
+
dependencyVerification.violations
|
|
1761
|
+
.map((violation) => ` - ${violation}`)
|
|
1762
|
+
.join("\n") +
|
|
1763
|
+
"\nMove approved packages to the required development/test scope, remove unapproved or unrelated manifest edits, run skyramp_verify_test_dependencies, and submit the report again.");
|
|
1764
|
+
return errorResult;
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
catch (err) {
|
|
1768
|
+
errorResult = toolError(`Could not verify test dependency changes in ${dependencyRoot}: ${err.message}. ` +
|
|
1769
|
+
"Testbot will not submit the report until every configured repository can be checked.");
|
|
1770
|
+
return errorResult;
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1338
1773
|
try {
|
|
1339
1774
|
// A run writes tests into up to three kinds of trees: the primary
|
|
1340
1775
|
// checkout, the tests-repo checkout (testRepoPath), and related
|
|
@@ -1363,7 +1798,11 @@ export function registerSubmitReportTool(server) {
|
|
|
1363
1798
|
changedFiles,
|
|
1364
1799
|
newTests: dedupedNewTests,
|
|
1365
1800
|
verdicts: stateData.maintenanceVerdicts ?? [],
|
|
1366
|
-
primaryRepository
|
|
1801
|
+
primaryRepository,
|
|
1802
|
+
knownRepositories: [
|
|
1803
|
+
primaryRepository,
|
|
1804
|
+
...Object.keys(fullState?.relatedRepos ?? {}),
|
|
1805
|
+
],
|
|
1367
1806
|
// SKYR-4250: captured at prompt-render time, since planOnly is a
|
|
1368
1807
|
// prompt-only argument (mirrors the SKYR-4185 language capture).
|
|
1369
1808
|
planOnly: isPlanOnlyMode(),
|
|
@@ -1392,7 +1831,7 @@ export function registerSubmitReportTool(server) {
|
|
|
1392
1831
|
`Files with actual working-tree changes:\n${changedList || " (none)"}`);
|
|
1393
1832
|
return errorResult;
|
|
1394
1833
|
}
|
|
1395
|
-
|
|
1834
|
+
changedFilesAbs = await listChangedFilesAbs([
|
|
1396
1835
|
repoRoot,
|
|
1397
1836
|
getTestsRepoDir(),
|
|
1398
1837
|
...Object.values(fullState?.relatedRepos ?? {}).map((section) => section.repositoryPath),
|
|
@@ -1417,7 +1856,7 @@ export function registerSubmitReportTool(server) {
|
|
|
1417
1856
|
// `repository` is normalized BEFORE the check so the attribution the check
|
|
1418
1857
|
// reconciles is the one the report ships, and so the stamp below survives
|
|
1419
1858
|
// into the written file — this array is what the report is built from.
|
|
1420
|
-
const issuesFound = params.issuesFound
|
|
1859
|
+
const issuesFound = params.issuesFound;
|
|
1421
1860
|
if (issuesFound.some((issue) => issue.sourceFile?.trim())) {
|
|
1422
1861
|
const citations = await findInvalidSourceCitations({
|
|
1423
1862
|
checkouts: await stateManager.listRepoCheckouts(),
|
|
@@ -1446,11 +1885,134 @@ export function registerSubmitReportTool(server) {
|
|
|
1446
1885
|
const assertionCheckouts = await stateManager
|
|
1447
1886
|
.listRepoCheckouts()
|
|
1448
1887
|
.catch(() => []);
|
|
1888
|
+
// Declines an earlier accepted submission removed from the delivered files.
|
|
1889
|
+
// They are handed to the re-derivation rather than written back into the files:
|
|
1890
|
+
// the run still knows what it declined, and the customer's copy is edited once.
|
|
1891
|
+
//
|
|
1892
|
+
// Keyed by the SPEC that owns the file the decline came from. Handing every
|
|
1893
|
+
// row every decline let one about one spec's helper clear a live finding in
|
|
1894
|
+
// another spec that defines a helper of the same name.
|
|
1895
|
+
const changedSet = changedFilesAbs ? new Set(changedFilesAbs) : undefined;
|
|
1896
|
+
const declinesBySpec = new Map();
|
|
1897
|
+
/** Files whose declines some record can still reach. One no record owns can
|
|
1898
|
+
* never be consumed by anything, so it is retired without a measurement; one
|
|
1899
|
+
* that IS owned is retired only on a measurement that found no fault. */
|
|
1900
|
+
const reachableDeclineFiles = new Set();
|
|
1901
|
+
for (const [name, record] of Object.entries(stateData.reuseOutcomes ?? {})) {
|
|
1902
|
+
const mine = [];
|
|
1903
|
+
// Every file the record owns, NOT only the ones this run changed. The filter
|
|
1904
|
+
// decides what the strip may EDIT; a decline already recorded against a file
|
|
1905
|
+
// is this run's whatever the working tree says, and dropping it makes the
|
|
1906
|
+
// gate refuse a fault the run already declined.
|
|
1907
|
+
for (const file of await deliveredFilesOf(record ?? {}, undefined)) {
|
|
1908
|
+
if ((stateData.strippedDeclines?.[file] ?? []).length > 0)
|
|
1909
|
+
reachableDeclineFiles.add(file);
|
|
1910
|
+
mine.push(...(stateData.strippedDeclines?.[file] ?? []).map(
|
|
1911
|
+
// Tagged with where it was read from, so a consumption retires THAT
|
|
1912
|
+
// file's record and not an identical one stored for another spec.
|
|
1913
|
+
(d) => ({ ...d, file })));
|
|
1914
|
+
}
|
|
1915
|
+
if (mine.length > 0)
|
|
1916
|
+
declinesBySpec.set(name, mine);
|
|
1917
|
+
}
|
|
1449
1918
|
const reuseBlocking = [];
|
|
1919
|
+
/** Carried declines a finding consumed on this call. */
|
|
1920
|
+
const consumedDeclines = [];
|
|
1921
|
+
/** Files whose stored declines a re-derivation actually measured on this call. */
|
|
1922
|
+
const evaluatedDeclineFiles = new Set();
|
|
1450
1923
|
const sanitizedNewTests = await Promise.all(dedupedNewTests.map(async ({ scenarioFile: _sf, traceFile: _tf, frontendTrace: _ft, ...rest }) => {
|
|
1451
|
-
const row = await attachReuseOutcome(
|
|
1924
|
+
const row = await attachReuseOutcome(rest, stateData.reuseOutcomes, stateData.reuseHandOffs, retrofitViews, reuseBlocking, declinesBySpec.get(path.basename(rest.fileName)) ?? [], consumedDeclines, evaluatedDeclineFiles, undefined, generatedPathFor(rest.fileName, stateData.generatedTests ?? {}));
|
|
1452
1925
|
return attachAssertionOutcome(row, stateData.assertionOutcomes ?? {}, assertionCheckouts);
|
|
1453
1926
|
}));
|
|
1927
|
+
const maintenanceRows = testMaintenance
|
|
1928
|
+
? await Promise.all(testMaintenance.map(async ({ testFilePath, pomFile, reportOnly, ...row }) => {
|
|
1929
|
+
// `pomFile` is the file skyramp_actions told the agent to edit;
|
|
1930
|
+
// `testFilePath` stays the spec, which is what ran. Name both when
|
|
1931
|
+
// they differ, or a page-object edit is reported against the spec.
|
|
1932
|
+
// Maintenance rows still carry the absolute path here, so the
|
|
1933
|
+
// assertion summary uses an EXACT canonical-path lookup — no
|
|
1934
|
+
// basename ambiguity. This is what carries the maintenance
|
|
1935
|
+
// honesty labels (nothing-to-verify vs verified) into the
|
|
1936
|
+
// report instead of leaving them as tool text the agent can
|
|
1937
|
+
// paraphrase.
|
|
1938
|
+
const record = stateData.assertionOutcomes?.[canonicalTestPath(testFilePath)];
|
|
1939
|
+
const assertions = record
|
|
1940
|
+
? await rederiveAssertionOutcome(record)
|
|
1941
|
+
: undefined;
|
|
1942
|
+
// A maintained test goes through modularize → reuse like a
|
|
1943
|
+
// generated one, so its row carries the same server-derived
|
|
1944
|
+
// reuse outcome, matched by its exact path. Wraps the
|
|
1945
|
+
// repository-attributed row, the same composition
|
|
1946
|
+
// newTestsCreated uses.
|
|
1947
|
+
return attachReuseOutcome({
|
|
1948
|
+
...row,
|
|
1949
|
+
fileName: path.basename(testFilePath),
|
|
1950
|
+
...(pomFile &&
|
|
1951
|
+
path.basename(pomFile) !== path.basename(testFilePath)
|
|
1952
|
+
? { editedFileName: path.basename(pomFile) }
|
|
1953
|
+
: {}),
|
|
1954
|
+
...(assertions ? { assertions } : {}),
|
|
1955
|
+
}, stateData.reuseOutcomes, stateData.reuseHandOffs, retrofitViews, reuseBlocking, declinesBySpec.get(path.basename(testFilePath)) ?? [], consumedDeclines, evaluatedDeclineFiles, testFilePath,
|
|
1956
|
+
// Only where the run edited the file and left a test standing.
|
|
1957
|
+
// VERIFY and IGNORE read a repository-owned test and changed nothing,
|
|
1958
|
+
// and DELETE removed it — none of the three has reuse to state, so a
|
|
1959
|
+
// sentence about what they import is noise on a complete row. A
|
|
1960
|
+
// report-only row keeps a real REGENERATE or DELETE while this run
|
|
1961
|
+
// touched nothing, so the action alone does not answer it; the same
|
|
1962
|
+
// pair of conditions `rowsLeavingCoverage` applies.
|
|
1963
|
+
MAINTENANCE_COVERAGE_ACTIONS.has(row.action) &&
|
|
1964
|
+
reportOnly !== true
|
|
1965
|
+
? testFilePath
|
|
1966
|
+
: undefined);
|
|
1967
|
+
}))
|
|
1968
|
+
: undefined;
|
|
1969
|
+
// Retirement happens HERE: after BOTH row kinds have re-derived, and still
|
|
1970
|
+
// before any refusal can return. Running it between them left a decline owned
|
|
1971
|
+
// only by a maintained spec unmeasured at this point — retired on that silence
|
|
1972
|
+
// before the fix below, and then kept forever once silence stopped retiring. A decline whose fault
|
|
1973
|
+
// the agent repaired has nothing left to authorise, and a submission that is
|
|
1974
|
+
// then refused for an UNRELATED fault used to leave it in place — so while
|
|
1975
|
+
// repairing that one the agent could reintroduce the first and the next
|
|
1976
|
+
// submission would authorise it in silence. Only the physical strip has to wait
|
|
1977
|
+
// for the accepted path; the record does not.
|
|
1978
|
+
//
|
|
1979
|
+
// Reconciled whenever any decline is stored, not when a count says it changed:
|
|
1980
|
+
// `consumedDeclines` accumulates per report ROW, so two rows consuming one
|
|
1981
|
+
// decline made the totals match while a second, stale decline survived.
|
|
1982
|
+
if (Object.keys(stateData.strippedDeclines ?? {}).length > 0) {
|
|
1983
|
+
const budget = [...consumedDeclines];
|
|
1984
|
+
const kept = Object.fromEntries(Object.entries(stateData.strippedDeclines ?? {}).map(([file, stored]) => [
|
|
1985
|
+
file,
|
|
1986
|
+
// Retire on an ANSWER, not on a silence. A file a record still owns,
|
|
1987
|
+
// whose re-derivation did not measure it — no row for that spec on this
|
|
1988
|
+
// call, a record with no spec identity, a verifier that threw —
|
|
1989
|
+
// consumed nothing for want of a reading, not for want of a fault, and
|
|
1990
|
+
// deleting it there drops permission while the fault is still in the
|
|
1991
|
+
// file. A file no record owns is retired without a measurement: nothing
|
|
1992
|
+
// can consume it, so no reading would ever come.
|
|
1993
|
+
reachableDeclineFiles.has(file) &&
|
|
1994
|
+
!evaluatedDeclineFiles.has(file)
|
|
1995
|
+
? stored
|
|
1996
|
+
: stored.filter((d) => {
|
|
1997
|
+
const i = budget.findIndex((c) => c.file === file &&
|
|
1998
|
+
c.kind === d.kind &&
|
|
1999
|
+
c.helper === d.helper &&
|
|
2000
|
+
c.keptInline === d.keptInline);
|
|
2001
|
+
if (i === -1)
|
|
2002
|
+
return false;
|
|
2003
|
+
budget.splice(i, 1);
|
|
2004
|
+
return true;
|
|
2005
|
+
}),
|
|
2006
|
+
]));
|
|
2007
|
+
try {
|
|
2008
|
+
const latest = (await stateManager.readData()) ?? stateData;
|
|
2009
|
+
await stateManager.appendData({ ...latest, strippedDeclines: kept });
|
|
2010
|
+
stateData.strippedDeclines = kept;
|
|
2011
|
+
}
|
|
2012
|
+
catch (err) {
|
|
2013
|
+
logger.warning("Could not retire the declines this call did not consume — one may authorise a later fault", { error: String(err) });
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
1454
2016
|
// A blocking reuse verdict on the delivered files refuses the report. The
|
|
1455
2017
|
// live verify pass is a one-time checkpoint on a file that keeps changing —
|
|
1456
2018
|
// the execution fix loop and a post-verification rewrite both edit after it —
|
|
@@ -1469,6 +2031,10 @@ export function registerSubmitReportTool(server) {
|
|
|
1469
2031
|
// off than one with a disclosed fault (see REUSE_SUBMIT_MAX_REFUSALS). The
|
|
1470
2032
|
// count is persisted per spec path; a count that cannot be persisted cannot
|
|
1471
2033
|
// bound anything, so a failed write accepts rather than refuses forever.
|
|
2034
|
+
// Built BEFORE the blocking-verdict refusal below, not inside the report
|
|
2035
|
+
// object: a maintained test goes through the same reuse chain as a generated
|
|
2036
|
+
// one, so its delivered files can fail the same check, and a verdict collected
|
|
2037
|
+
// after the refusal would never refuse anything.
|
|
1472
2038
|
// `reuseOutcomes` is keyed by basename, so two rows sharing a file name find
|
|
1473
2039
|
// one record and push one verdict twice — deduplicated by path here, as
|
|
1474
2040
|
// pendingReuseVerification skips that case on its side. Sorted by file so the
|
|
@@ -1484,7 +2050,10 @@ export function registerSubmitReportTool(server) {
|
|
|
1484
2050
|
for (const v of owed)
|
|
1485
2051
|
refusals[v.file] = (refusals[v.file] ?? 0) + 1;
|
|
1486
2052
|
try {
|
|
1487
|
-
await stateManager.
|
|
2053
|
+
await stateManager.appendData({
|
|
2054
|
+
...stateData,
|
|
2055
|
+
reuseRefusals: refusals,
|
|
2056
|
+
});
|
|
1488
2057
|
}
|
|
1489
2058
|
catch (err) {
|
|
1490
2059
|
persisted = false;
|
|
@@ -1532,6 +2101,202 @@ export function registerSubmitReportTool(server) {
|
|
|
1532
2101
|
test.expectedToFail = true;
|
|
1533
2102
|
}
|
|
1534
2103
|
}
|
|
2104
|
+
// Every file this run wrote, from the server's own records — one canonical
|
|
2105
|
+
// absolute path each, which is also the key an execution is recorded under.
|
|
2106
|
+
// Generated files come from the generators; edited files from the maintenance
|
|
2107
|
+
// triage, narrowed by `rowsLeavingCoverage` to UPDATE and REGENERATE. The rest
|
|
2108
|
+
// cannot be run: DELETE removed the file, VERIFY and IGNORE never edited one,
|
|
2109
|
+
// and a `reportOnly` row only advised a change this run did not make.
|
|
2110
|
+
//
|
|
2111
|
+
// A verdict names the SPEC, never `pomFile`: a POM-backed row edits the page
|
|
2112
|
+
// object, but the execution is recorded against the spec.
|
|
2113
|
+
// A file a DELETE verdict names is gone by report time, so no run is owed for
|
|
2114
|
+
// it. The verdicts keep every recommendation, not the winning one, so one file
|
|
2115
|
+
// can carry both a DELETE and an UPDATE — and the DELETE is what was applied.
|
|
2116
|
+
const deletedPaths = new Set(maintenanceSections.flatMap((section) => (section.data.maintenanceVerdicts ?? [])
|
|
2117
|
+
.filter((verdict) => verdict?.action === DriftAction.Delete &&
|
|
2118
|
+
verdict?.reportOnly !== true)
|
|
2119
|
+
.map((verdict) => canonicalTestPath(verdict.testFilePath))));
|
|
2120
|
+
const filesWritten = new Map();
|
|
2121
|
+
// When each generated path was last written, so a run recorded before that
|
|
2122
|
+
// write is not credited for the bytes that replaced it.
|
|
2123
|
+
const generatedAt = new Map();
|
|
2124
|
+
for (const section of maintenanceSections) {
|
|
2125
|
+
for (const [generated, record] of Object.entries(section.data.generatedTests ?? {}))
|
|
2126
|
+
if (!deletedPaths.has(generated)) {
|
|
2127
|
+
filesWritten.set(generated, "generated");
|
|
2128
|
+
if (typeof record?.generatedAt === "number")
|
|
2129
|
+
generatedAt.set(generated, record.generatedAt);
|
|
2130
|
+
}
|
|
2131
|
+
for (const verdict of rowsLeavingCoverage(section.data.maintenanceVerdicts)) {
|
|
2132
|
+
// A rename UPDATE moves the spec before it is executed, so the run is
|
|
2133
|
+
// recorded under the new name while `testFilePath` keeps the old one.
|
|
2134
|
+
const raw = verdict._suggestedNewFile?.trim() || verdict.testFilePath.trim();
|
|
2135
|
+
if (!raw)
|
|
2136
|
+
continue;
|
|
2137
|
+
// Canonical because that is how an execution is keyed: a checkout under a
|
|
2138
|
+
// symlinked temp or home directory spells one file two ways.
|
|
2139
|
+
const key = canonicalTestPath(raw);
|
|
2140
|
+
if (!filesWritten.has(key) && !deletedPaths.has(key))
|
|
2141
|
+
filesWritten.set(key, "updated");
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
// The LATEST run of each file, because a re-run replaces the record. An edited
|
|
2145
|
+
// file needs a run of the edit, so its pre-edit baseline does not answer for
|
|
2146
|
+
// it — `phase` is what tells the two apart. A generated file has no baseline,
|
|
2147
|
+
// so any run of it is a run of what was written.
|
|
2148
|
+
const executions = new Map(maintenanceSections.flatMap((section) => Object.entries(section.data.testExecutions ?? {}).map(([file, record]) => [
|
|
2149
|
+
file,
|
|
2150
|
+
{
|
|
2151
|
+
phase: String(record?.phase ?? ""),
|
|
2152
|
+
executedAt: String(record?.executedAt ?? ""),
|
|
2153
|
+
},
|
|
2154
|
+
])));
|
|
2155
|
+
// A record is never removed once written, so a file the agent generated and
|
|
2156
|
+
// then deleted would object forever — and name a path that is not there. What
|
|
2157
|
+
// is gone owes no run. Unreadable counts as gone: a false objection about a
|
|
2158
|
+
// file nobody can open is worse than the silence.
|
|
2159
|
+
const written = [];
|
|
2160
|
+
for (const [file, how] of filesWritten) {
|
|
2161
|
+
if (!(await pathExists(file)))
|
|
2162
|
+
continue;
|
|
2163
|
+
const run = executions.get(file);
|
|
2164
|
+
written.push({
|
|
2165
|
+
file,
|
|
2166
|
+
how,
|
|
2167
|
+
recordedPhase: run?.phase,
|
|
2168
|
+
executedAt: run?.executedAt,
|
|
2169
|
+
generatedAt: generatedAt.get(file),
|
|
2170
|
+
});
|
|
2171
|
+
}
|
|
2172
|
+
// The counts a reader compares at a glance, from the same two sets the check
|
|
2173
|
+
// below joins — so a shortfall here is exactly the files it names.
|
|
2174
|
+
const executionCoverage = {
|
|
2175
|
+
generated: written.filter((f) => f.how === "generated").length,
|
|
2176
|
+
updated: written.filter((f) => f.how === "updated").length,
|
|
2177
|
+
executed: written.filter(runOfWhatWasWritten).length,
|
|
2178
|
+
};
|
|
2179
|
+
// A skipped test is not an execution. Refuse before writing a report so the
|
|
2180
|
+
// agent must execute every delivered plan test instead of declaring a time
|
|
2181
|
+
// constraint in a Skipped row and ending the run.
|
|
2182
|
+
const registeredPlanTestIds = new Set(Array.isArray(stateData.plan?.plannedTests)
|
|
2183
|
+
? stateData.plan.plannedTests
|
|
2184
|
+
.map((test) => test?.plannedTestId?.trim())
|
|
2185
|
+
.filter((id) => !!id)
|
|
2186
|
+
: []);
|
|
2187
|
+
const skippedTests = isPlanOnlyMode()
|
|
2188
|
+
? []
|
|
2189
|
+
: skippedDeliveredTests(dedupedNewTests.filter((test) => registeredPlanTestIds.has((test.plannedTestId ?? "").trim())), params.testResults);
|
|
2190
|
+
if (skippedTests.length > 0) {
|
|
2191
|
+
errorResult = toolError(`Cannot submit the report: ${skippedTests.length} registered plan test${skippedTests.length === 1 ? "" : "s"} ${skippedTests
|
|
2192
|
+
.map((test) => `${test.plannedTestId?.trim() || test.testId}`)
|
|
2193
|
+
.join(", ")} ${skippedTests.length === 1 ? "has" : "have"} no non-skipped execution result. ` +
|
|
2194
|
+
"Do not skip because you believe you are low on context or time, or because you speculate that another attempt will not help. " +
|
|
2195
|
+
contextGaugeSentence() +
|
|
2196
|
+
"Run each remaining test with skyramp_execute_test, include its Pass or Fail result in testResults, then call skyramp_submit_report again.");
|
|
2197
|
+
return errorResult;
|
|
2198
|
+
}
|
|
2199
|
+
// A planned UI test with no report entry is a recording that never happened.
|
|
2200
|
+
// `deliveredMatchesPlan` already raises it and already accepts an answer, and
|
|
2201
|
+
// that is how one run shipped a 14-test plan with ZERO UI tests: a single
|
|
2202
|
+
// skyramp_export_zip, a trace that came out polluted, and an answer blaming
|
|
2203
|
+
// the recorder. Refuse while the run has recorded fewer times than it planned
|
|
2204
|
+
// UI tests; past that it has genuinely tried each one and the answer stands.
|
|
2205
|
+
//
|
|
2206
|
+
// WHY THE RETRY IS NOT FUTILE, and why the message says so: a successful
|
|
2207
|
+
// skyramp_export_zip DRAINS the recorder and closes the browser, so the next
|
|
2208
|
+
// recording starts from an empty buffer. Nothing the agent can read says that,
|
|
2209
|
+
// and that run reasoned the opposite and stopped one call short of a clean
|
|
2210
|
+
// trace.
|
|
2211
|
+
const undeliveredUi = isPlanOnlyMode() || planUnreadable
|
|
2212
|
+
? []
|
|
2213
|
+
: undeliveredPlannedUiTests(stateData.plan?.plannedTests ?? [], dedupedNewTests);
|
|
2214
|
+
const plannedUiCount = planUnreadable
|
|
2215
|
+
? 0
|
|
2216
|
+
: plannedUiTests(stateData.plan?.plannedTests ?? []).length;
|
|
2217
|
+
// ATTEMPTS, so one more recording always answers a refusal and the gate opens
|
|
2218
|
+
// after at most `plannedUiCount` of them. The refusal cap is the second bound,
|
|
2219
|
+
// for the agent that resubmits having recorded nothing: this check is meant to
|
|
2220
|
+
// cost a recording, never the whole run.
|
|
2221
|
+
const recordings = traceExportAttempts();
|
|
2222
|
+
if (undeliveredUi.length > 0 &&
|
|
2223
|
+
recordings < plannedUiCount &&
|
|
2224
|
+
uiDeliveryRefusals() < MAX_UI_DELIVERY_REFUSALS) {
|
|
2225
|
+
const refusal = countUiDeliveryRefusal();
|
|
2226
|
+
const ids = undeliveredUi
|
|
2227
|
+
.map((test) => test.plannedTestId.trim())
|
|
2228
|
+
.join(", ");
|
|
2229
|
+
errorResult = toolError(`Cannot submit the report: ${undeliveredUi.length} registered UI plan test${undeliveredUi.length === 1 ? "" : "s"} ` +
|
|
2230
|
+
`${ids} ${undeliveredUi.length === 1 ? "has" : "have"} no entry in newTestsCreated, and this run recorded ` +
|
|
2231
|
+
`${recordings} trace${recordings === 1 ? "" : "s"} for ${plannedUiCount} planned UI test${plannedUiCount === 1 ? "" : "s"}. ` +
|
|
2232
|
+
"Do not skip because you believe you are low on context or time, or because you speculate that another attempt will not help. " +
|
|
2233
|
+
contextGaugeSentence() +
|
|
2234
|
+
"A successful skyramp_export_zip clears the recording buffer and closes the browser, so the next recording starts clean — " +
|
|
2235
|
+
"a trace that came out polluted is a reason to record again, not a reason to stop. " +
|
|
2236
|
+
"Record each remaining test in its own pass (browser_navigate to the start URL, the flow, skyramp_export_zip, skyramp_ui_test_generation), " +
|
|
2237
|
+
"add it to newTestsCreated with its plannedTestId, then call skyramp_submit_report again. " +
|
|
2238
|
+
`The plan and every answer you have given are in the state file and survive this refusal (refusal ${refusal} of ${MAX_UI_DELIVERY_REFUSALS}).`);
|
|
2239
|
+
return errorResult;
|
|
2240
|
+
}
|
|
2241
|
+
// A planned test dropped because the run says it was running out.
|
|
2242
|
+
//
|
|
2243
|
+
// `deliveredMatchesPlan` raises "planned but not delivered" and suggests
|
|
2244
|
+
// "add the test to the report, or record in the report why it was dropped".
|
|
2245
|
+
// Run 9dfc8405 recorded the reason seven times — "this run exhausted its
|
|
2246
|
+
// working budget" — and shipped 2 of 9 planned tests with the report
|
|
2247
|
+
// ACCEPTED. It wrote that at 46% of a 1M context window with no warning from
|
|
2248
|
+
// anywhere, and the word appears nowhere in its reasoning: the budget was not
|
|
2249
|
+
// a finding, it was a sentence that closed an objection.
|
|
2250
|
+
//
|
|
2251
|
+
// This refuses that ONE sentence, not a budget. A test blocked by something
|
|
2252
|
+
// outside the run answers and ships exactly as before, which is why the
|
|
2253
|
+
// message says what a reason that stands looks like.
|
|
2254
|
+
//
|
|
2255
|
+
// Answers from earlier calls count too: one that closed the objection on
|
|
2256
|
+
// submit N still closes it on N+1 without being re-sent, and a gate reading
|
|
2257
|
+
// only this call would be walked past by not repeating it.
|
|
2258
|
+
const answerText = new Map();
|
|
2259
|
+
const carriedAnswers = Array.isArray(stateData.reportObjections?.answeredObjections)
|
|
2260
|
+
? stateData.reportObjections.answeredObjections
|
|
2261
|
+
: [];
|
|
2262
|
+
for (const entry of carriedAnswers) {
|
|
2263
|
+
const id = entry
|
|
2264
|
+
?.objection?.objectionId;
|
|
2265
|
+
const answer = entry?.answer;
|
|
2266
|
+
if (typeof id === "string" && typeof answer === "string")
|
|
2267
|
+
answerText.set(id, answer);
|
|
2268
|
+
}
|
|
2269
|
+
for (const entry of params.answers ?? []) {
|
|
2270
|
+
if (typeof entry?.objectionId === "string" &&
|
|
2271
|
+
typeof entry?.answer === "string")
|
|
2272
|
+
answerText.set(entry.objectionId, entry.answer);
|
|
2273
|
+
}
|
|
2274
|
+
const droppedForBudget = isPlanOnlyMode() || planUnreadable
|
|
2275
|
+
? []
|
|
2276
|
+
: undeliveredPlannedTests(stateData.plan?.plannedTests ?? [], dedupedNewTests).filter((test) => citesBudget(answerText.get(`deliveredMatchesPlan:${test.plannedTestId.trim()}`)));
|
|
2277
|
+
if (droppedForBudget.length > 0 &&
|
|
2278
|
+
budgetExcuseRefusals() < MAX_BUDGET_EXCUSE_REFUSALS) {
|
|
2279
|
+
const refusal = countBudgetExcuseRefusal();
|
|
2280
|
+
const ids = droppedForBudget
|
|
2281
|
+
.map((test) => test.plannedTestId.trim())
|
|
2282
|
+
.join(", ");
|
|
2283
|
+
const plural = droppedForBudget.length === 1;
|
|
2284
|
+
errorResult = toolError(`Cannot submit the report: ${droppedForBudget.length} planned test${plural ? "" : "s"} ` +
|
|
2285
|
+
`${ids} ${plural ? "is" : "are"} missing from newTestsCreated, and the answer given for ` +
|
|
2286
|
+
`${plural ? "it" : "each"} is that the run was short of budget, context or time. ` +
|
|
2287
|
+
"That is not a reason this report accepts. " +
|
|
2288
|
+
contextGaugeSentence() +
|
|
2289
|
+
"A reason that stands names something outside the run that stopped the test: a service that is not " +
|
|
2290
|
+
"running, a branch that no longer exists, a credential this run does not hold, a behaviour the " +
|
|
2291
|
+
"application will not reach. Running low is not one of those — it is a reason to write the test now, " +
|
|
2292
|
+
"with the rest of the report already built. " +
|
|
2293
|
+
`Write the missing test${plural ? "" : "s"}, add ${plural ? "it" : "them"} to newTestsCreated with the ` +
|
|
2294
|
+
"plannedTestId, and call skyramp_submit_report again — or replace the answer with what actually " +
|
|
2295
|
+
"stopped it. " +
|
|
2296
|
+
`The plan, the report and every answer you have given are in the state file and survive this refusal ` +
|
|
2297
|
+
`(refusal ${refusal} of ${MAX_BUDGET_EXCUSE_REFUSALS}).`);
|
|
2298
|
+
return errorResult;
|
|
2299
|
+
}
|
|
1535
2300
|
// Report-time checks on what the customer will read. They need no plan, so a
|
|
1536
2301
|
// run without one still gets them.
|
|
1537
2302
|
const reportChecks = [
|
|
@@ -1541,6 +2306,15 @@ export function registerSubmitReportTool(server) {
|
|
|
1541
2306
|
...(isPlanOnlyMode()
|
|
1542
2307
|
? []
|
|
1543
2308
|
: runPostExecutionCheck("deliveredTestRan", "deliveredTestRan", () => checkEveryDeliveredTestRan(dedupedNewTests, params.testResults))),
|
|
2309
|
+
// Every lane, not just testbot: both records are written whenever the
|
|
2310
|
+
// tools run, and a file written and never run is the same defect
|
|
2311
|
+
// wherever it happens. Silent when the run recorded nothing — an older
|
|
2312
|
+
// state file has no `generatedTests`, and the check must not read that
|
|
2313
|
+
// as "nothing was run". Skipped in the plan-only lane, which has no
|
|
2314
|
+
// application to run anything against.
|
|
2315
|
+
...(isPlanOnlyMode()
|
|
2316
|
+
? []
|
|
2317
|
+
: runPostExecutionCheck("testRanOnThisServer", "testRanOnThisServer", () => checkEveryWrittenTestRan(written))),
|
|
1544
2318
|
];
|
|
1545
2319
|
// Run against an EMPTY plan when none was submitted, never skipped: the gate
|
|
1546
2320
|
// above covers contract and integration only. An empty plan raises exactly
|
|
@@ -1548,7 +2322,14 @@ export function registerSubmitReportTool(server) {
|
|
|
1548
2322
|
let objections;
|
|
1549
2323
|
let changeTable;
|
|
1550
2324
|
{
|
|
1551
|
-
const postExecution = runPostExecutionChecks(stateData.plan ??
|
|
2325
|
+
const postExecution = runPostExecutionChecks(stateData.plan ??
|
|
2326
|
+
{
|
|
2327
|
+
plannedTests: [],
|
|
2328
|
+
registrationNumber: 0,
|
|
2329
|
+
answers: [],
|
|
2330
|
+
openObjections: [],
|
|
2331
|
+
answeredObjections: [],
|
|
2332
|
+
}, dedupedNewTests, params.testResults, params.issuesFound ?? [], isPlanOnlyMode(),
|
|
1552
2333
|
// The triage's own verdicts, not this tool's input, so the join is against
|
|
1553
2334
|
// what the run is recorded as having edited — and only the rows that left a
|
|
1554
2335
|
// test standing and were actually applied, so a no-op assessment, a deletion
|
|
@@ -1610,7 +2391,10 @@ export function registerSubmitReportTool(server) {
|
|
|
1610
2391
|
// objection and answers it in the same call: fixing it stops the check
|
|
1611
2392
|
// raising it, so matching against this call alone called the answer unknown
|
|
1612
2393
|
// and objected to the agent for doing both (run 33946409570, sb19).
|
|
1613
|
-
const answerable = [
|
|
2394
|
+
const answerable = [
|
|
2395
|
+
...raised,
|
|
2396
|
+
...previouslyRaised.map((entry) => entry.objection),
|
|
2397
|
+
];
|
|
1614
2398
|
const reportStageObjections = [
|
|
1615
2399
|
...raised,
|
|
1616
2400
|
...unknownAnswerObjections(answerable, acknowledged, {
|
|
@@ -1623,24 +2407,31 @@ export function registerSubmitReportTool(server) {
|
|
|
1623
2407
|
// their silence is not evidence: only a call carrying results can resolve an
|
|
1624
2408
|
// earlier objection.
|
|
1625
2409
|
const judgedThisCall = (params.testResults ?? []).length > 0;
|
|
2410
|
+
// These verifiers read the server's own records, not `testResults`, so
|
|
2411
|
+
// they reach a verdict on every call. An empty resubmission leaves them
|
|
2412
|
+
// judged, and an objection they no longer raise is resolved — without
|
|
2413
|
+
// this it would vanish from the report and sit in `raisedObjections`
|
|
2414
|
+
// for the rest of the run with nothing able to clear it.
|
|
2415
|
+
const stateBackedVerifiers = new Set(["testRanOnThisServer"]);
|
|
2416
|
+
const judged = (objection) => judgedThisCall || stateBackedVerifiers.has(objection.verifier);
|
|
1626
2417
|
// Resolved = raised earlier, absent from a call that could have raised it.
|
|
1627
2418
|
// One that comes back drops out: publishing it as both says two things.
|
|
1628
2419
|
const resolvedObjections = [
|
|
1629
2420
|
...previouslyResolved,
|
|
1630
|
-
...
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
: []),
|
|
2421
|
+
...previouslyRaised
|
|
2422
|
+
.filter((entry) => judged(entry.objection) &&
|
|
2423
|
+
!raisedNow.has(answerKey(entry.objection)))
|
|
2424
|
+
.map((entry) => ({
|
|
2425
|
+
objection: entry.objection,
|
|
2426
|
+
resolvedBy: `submission ${submissionNumber}`,
|
|
2427
|
+
})),
|
|
1638
2428
|
].filter((entry, index, all) => !raisedNow.has(answerKey(entry.objection)) &&
|
|
1639
2429
|
all.findIndex((other) => answerKey(other.objection) === answerKey(entry.objection)) === index);
|
|
1640
2430
|
const answeredReportObjections = [];
|
|
1641
2431
|
const openReportObjections = [];
|
|
1642
2432
|
for (const objection of reportStageObjections) {
|
|
1643
|
-
const answer = answerFor(objection, acknowledged) ??
|
|
2433
|
+
const answer = answerFor(objection, acknowledged) ??
|
|
2434
|
+
carriedByKey.get(answerKey(objection))?.answer;
|
|
1644
2435
|
if (answer)
|
|
1645
2436
|
answeredReportObjections.push({ objection, answer });
|
|
1646
2437
|
else
|
|
@@ -1715,14 +2506,19 @@ export function registerSubmitReportTool(server) {
|
|
|
1715
2506
|
// corroborate a claim that an existing test covers the change.
|
|
1716
2507
|
const editedPaths = [];
|
|
1717
2508
|
for (const row of rowsLeavingCoverage(stateData.maintenanceVerdicts)) {
|
|
1718
|
-
for (const full of [
|
|
2509
|
+
for (const full of [
|
|
2510
|
+
row?.testFilePath,
|
|
2511
|
+
row?.pomFile,
|
|
2512
|
+
]) {
|
|
1719
2513
|
const path = typeof full === "string" ? full.trim() : "";
|
|
1720
2514
|
if (path)
|
|
1721
2515
|
editedPaths.push(path);
|
|
1722
2516
|
}
|
|
1723
2517
|
}
|
|
1724
2518
|
const maintainedByChange = new Map();
|
|
1725
|
-
for (const entry of Array.isArray(stateData.plan?.maintains)
|
|
2519
|
+
for (const entry of Array.isArray(stateData.plan?.maintains)
|
|
2520
|
+
? stateData.plan.maintains
|
|
2521
|
+
: []) {
|
|
1726
2522
|
const file = String(entry?.file ?? "").trim();
|
|
1727
2523
|
if (!file || !Array.isArray(entry?.changes))
|
|
1728
2524
|
continue;
|
|
@@ -1742,7 +2538,9 @@ export function registerSubmitReportTool(server) {
|
|
|
1742
2538
|
for (const objection of objections) {
|
|
1743
2539
|
if (!objection.objectionId.startsWith(CHANGE_OBJECTION_PREFIX))
|
|
1744
2540
|
continue;
|
|
1745
|
-
const answer = typeof objection.answer === "string"
|
|
2541
|
+
const answer = typeof objection.answer === "string"
|
|
2542
|
+
? objection.answer.trim()
|
|
2543
|
+
: "";
|
|
1746
2544
|
if (answer)
|
|
1747
2545
|
answerByChange.set(objection.objectionId.slice(CHANGE_OBJECTION_PREFIX.length), answer);
|
|
1748
2546
|
}
|
|
@@ -1750,7 +2548,8 @@ export function registerSubmitReportTool(server) {
|
|
|
1750
2548
|
const id = String(change?.id ?? "").trim();
|
|
1751
2549
|
const testedBy = dedupedNewTests
|
|
1752
2550
|
.map((test) => (test.plannedTestId ?? "").trim())
|
|
1753
|
-
.filter((plannedTestId) => plannedTestId &&
|
|
2551
|
+
.filter((plannedTestId) => plannedTestId &&
|
|
2552
|
+
(changesByCandidate.get(plannedTestId) ?? []).includes(id));
|
|
1754
2553
|
const answer = answerByChange.get(id);
|
|
1755
2554
|
const maintainedBy = maintainedByChange.get(id) ?? [];
|
|
1756
2555
|
return {
|
|
@@ -1771,7 +2570,7 @@ export function registerSubmitReportTool(server) {
|
|
|
1771
2570
|
// The report is the deliverable, so a failed state write must not cost
|
|
1772
2571
|
// it; the answer is simply repeated on the next call.
|
|
1773
2572
|
try {
|
|
1774
|
-
await stateManager.
|
|
2573
|
+
await stateManager.appendData({
|
|
1775
2574
|
...stateData,
|
|
1776
2575
|
reportObjections: {
|
|
1777
2576
|
answeredObjections: [
|
|
@@ -1785,9 +2584,8 @@ export function registerSubmitReportTool(server) {
|
|
|
1785
2584
|
objection,
|
|
1786
2585
|
submission: firstRaisedAt.get(answerKey(objection)) ?? submissionNumber,
|
|
1787
2586
|
})),
|
|
1788
|
-
...(
|
|
1789
|
-
|
|
1790
|
-
: previouslyRaised.filter((entry) => !raisedNow.has(answerKey(entry.objection)))),
|
|
2587
|
+
...previouslyRaised.filter((entry) => !judged(entry.objection) &&
|
|
2588
|
+
!raisedNow.has(answerKey(entry.objection))),
|
|
1791
2589
|
],
|
|
1792
2590
|
resolvedObjections,
|
|
1793
2591
|
submissionCount: submissionNumber,
|
|
@@ -1801,44 +2599,20 @@ export function registerSubmitReportTool(server) {
|
|
|
1801
2599
|
const report = {
|
|
1802
2600
|
businessCaseAnalysis: params.businessCaseAnalysis,
|
|
1803
2601
|
newTestsCreated: sanitizedNewTests,
|
|
1804
|
-
additionalRecommendations: dedupedRecommendations
|
|
2602
|
+
additionalRecommendations: dedupedRecommendations,
|
|
1805
2603
|
// Report wire format keeps main's original `fileName` (basename) — testFilePath is
|
|
1806
2604
|
// an internal-only field, needed for matching but never meant to reach the report.
|
|
1807
|
-
testMaintenance:
|
|
1808
|
-
? await Promise.all(testMaintenance.map(async ({ testFilePath, pomFile, ...row }) => {
|
|
1809
|
-
// `pomFile` is the file skyramp_actions told the agent to edit;
|
|
1810
|
-
// `testFilePath` stays the spec, which is what ran. Name both when
|
|
1811
|
-
// they differ, or a page-object edit is reported against the spec.
|
|
1812
|
-
// Maintenance rows still carry the absolute path here, so the
|
|
1813
|
-
// assertion summary uses an EXACT canonical-path lookup — no
|
|
1814
|
-
// basename ambiguity. This is what carries the maintenance
|
|
1815
|
-
// honesty labels (nothing-to-verify vs verified) into the
|
|
1816
|
-
// report instead of leaving them as tool text the agent can
|
|
1817
|
-
// paraphrase.
|
|
1818
|
-
const record = stateData.assertionOutcomes?.[canonicalTestPath(testFilePath)];
|
|
1819
|
-
const assertions = record
|
|
1820
|
-
? await rederiveAssertionOutcome(record)
|
|
1821
|
-
: undefined;
|
|
1822
|
-
return normalizeRepository({
|
|
1823
|
-
...row,
|
|
1824
|
-
fileName: path.basename(testFilePath),
|
|
1825
|
-
...(pomFile &&
|
|
1826
|
-
path.basename(pomFile) !== path.basename(testFilePath)
|
|
1827
|
-
? { editedFileName: path.basename(pomFile) }
|
|
1828
|
-
: {}),
|
|
1829
|
-
...(assertions ? { assertions } : {}),
|
|
1830
|
-
});
|
|
1831
|
-
}))
|
|
1832
|
-
: undefined,
|
|
2605
|
+
testMaintenance: maintenanceRows,
|
|
1833
2606
|
// videoPath is filled from the run's execution records; testFilePath is the
|
|
1834
2607
|
// match-only key and is stripped from the wire format, the same line drawn for
|
|
1835
2608
|
// testMaintenance's own testFilePath above (downstream scoring scripts traverse
|
|
1836
2609
|
// these objects and don't expect it).
|
|
1837
2610
|
testResults: params.testResults.map((row) => {
|
|
1838
|
-
const { testFilePath: _tfp, ...wire } = attachVideoPath(
|
|
2611
|
+
const { testFilePath: _tfp, ...wire } = attachVideoPath(row, stateData.executionVideos);
|
|
1839
2612
|
return wire;
|
|
1840
2613
|
}),
|
|
1841
2614
|
issuesFound,
|
|
2615
|
+
executionCoverage,
|
|
1842
2616
|
nextSteps: params.nextSteps ?? [],
|
|
1843
2617
|
...(changeTable ? { changes: changeTable } : {}),
|
|
1844
2618
|
...(objections ? { objections } : {}),
|
|
@@ -1861,11 +2635,64 @@ export function registerSubmitReportTool(server) {
|
|
|
1861
2635
|
outputFile: reportPath,
|
|
1862
2636
|
elapsedMs: elapsed,
|
|
1863
2637
|
});
|
|
2638
|
+
// The report is on disk, so this call is past every refusal in the handler
|
|
2639
|
+
// — including the one a failed write above would have raised. See
|
|
2640
|
+
// stripDeliveredGateMarkers for why the accepted path is the only one that
|
|
2641
|
+
// may remove a decline.
|
|
2642
|
+
const stripped = await stripDeliveredGateMarkers(stateData.reuseOutcomes, retrofitViews, changedSet);
|
|
2643
|
+
// Only ADDS here. Retirement already happened above, before any refusal
|
|
2644
|
+
// could return, so this call has nothing left to draw down.
|
|
2645
|
+
if (Object.keys(stripped).length > 0) {
|
|
2646
|
+
// Recorded so a later call in this run can read them back — the removal is
|
|
2647
|
+
// for the customer, not for our own gates. A record that cannot be
|
|
2648
|
+
// persisted would make that call refuse a declined fault, so the failure
|
|
2649
|
+
// is logged where the next refusal can be explained by it.
|
|
2650
|
+
try {
|
|
2651
|
+
// Re-read rather than spread the `stateData` this call opened with. The
|
|
2652
|
+
// objections block above has already persisted its own record, and
|
|
2653
|
+
// carryForwardRootFields carries `reportObjections` only when the
|
|
2654
|
+
// incoming value is undefined — so writing the call-start copy back would
|
|
2655
|
+
// revert the submission count and re-raise answers this call resolved.
|
|
2656
|
+
const latest = (await stateManager.readData()) ?? stateData;
|
|
2657
|
+
await stateManager.appendData({
|
|
2658
|
+
...latest,
|
|
2659
|
+
// Merged per file, never replaced: a later submission that strips a
|
|
2660
|
+
// second marker from one file must not forget the first, which is
|
|
2661
|
+
// still a decline this run made.
|
|
2662
|
+
//
|
|
2663
|
+
// A decline this call did NOT consume is retired. The fault it covered
|
|
2664
|
+
// is gone, and permission that covers no fault authorises the next one
|
|
2665
|
+
// instead — which nobody declined. A marker left in a file retires the
|
|
2666
|
+
// same way, through `stale-allow`; this is that rule for the records.
|
|
2667
|
+
strippedDeclines: Object.fromEntries([
|
|
2668
|
+
...new Set([
|
|
2669
|
+
...Object.keys(latest.strippedDeclines ?? {}),
|
|
2670
|
+
...Object.keys(stripped),
|
|
2671
|
+
]),
|
|
2672
|
+
].map((file) => [
|
|
2673
|
+
file,
|
|
2674
|
+
[
|
|
2675
|
+
...(latest.strippedDeclines?.[file] ?? []),
|
|
2676
|
+
...(stripped[file] ?? []),
|
|
2677
|
+
].map((d) => {
|
|
2678
|
+
// The `file` tag is a runtime aid for retirement, not part of
|
|
2679
|
+
// the record: the record is already keyed by file.
|
|
2680
|
+
const { file: _tag, ...rest } = d;
|
|
2681
|
+
return rest;
|
|
2682
|
+
}),
|
|
2683
|
+
])),
|
|
2684
|
+
});
|
|
2685
|
+
}
|
|
2686
|
+
catch (err) {
|
|
2687
|
+
logger.warning("Could not record the declines removed from the delivered files — a further submission may refuse one of them", { error: String(err) });
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
1864
2690
|
// Addressed to the AGENT in the second person, each naming a next move: the
|
|
1865
2691
|
// report is already written when this returns and the tool can be called
|
|
1866
2692
|
// again, so the instruction is actionable. An answered objection is
|
|
1867
2693
|
// published but not listed back, which would read as a second demand.
|
|
1868
|
-
const open = objections?.filter((objection) => objection.stage === "post-execution" &&
|
|
2694
|
+
const open = objections?.filter((objection) => objection.stage === "post-execution" &&
|
|
2695
|
+
objection.answer === undefined) ?? [];
|
|
1869
2696
|
return {
|
|
1870
2697
|
content: [
|
|
1871
2698
|
{
|