tracegrove 0.0.0-stage → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +588 -2
- package/conformance/coordination-vectors.json +377 -0
- package/dist/adapters/browser/capabilities.d.ts +45 -0
- package/dist/adapters/browser/capabilities.js +14 -0
- package/dist/adapters/browser/capabilities.js.map +1 -0
- package/dist/adapters/browser/driver.d.ts +45 -0
- package/dist/adapters/browser/driver.js +295 -0
- package/dist/adapters/browser/driver.js.map +1 -0
- package/dist/adapters/browser/session.d.ts +21 -0
- package/dist/adapters/browser/session.js +87 -0
- package/dist/adapters/browser/session.js.map +1 -0
- package/dist/adapters/http/capabilities.d.ts +2 -0
- package/dist/adapters/http/capabilities.js +21 -0
- package/dist/adapters/http/capabilities.js.map +1 -0
- package/dist/adapters/http/driver.d.ts +55 -0
- package/dist/adapters/http/driver.js +125 -0
- package/dist/adapters/http/driver.js.map +1 -0
- package/dist/adapters/job/capabilities.d.ts +45 -0
- package/dist/adapters/job/capabilities.js +8 -0
- package/dist/adapters/job/capabilities.js.map +1 -0
- package/dist/adapters/job/driver.d.ts +26 -0
- package/dist/adapters/job/driver.js +110 -0
- package/dist/adapters/job/driver.js.map +1 -0
- package/dist/adapters/job/types.d.ts +9 -0
- package/dist/adapters/job/types.js +2 -0
- package/dist/adapters/job/types.js.map +1 -0
- package/dist/adapters/mcp/capabilities.d.ts +46 -0
- package/dist/adapters/mcp/capabilities.js +17 -0
- package/dist/adapters/mcp/capabilities.js.map +1 -0
- package/dist/adapters/mcp/driver.d.ts +10 -0
- package/dist/adapters/mcp/driver.js +83 -0
- package/dist/adapters/mcp/driver.js.map +1 -0
- package/dist/adapters/poll.d.ts +12 -0
- package/dist/adapters/poll.js +31 -0
- package/dist/adapters/poll.js.map +1 -0
- package/dist/authoring/activity.d.ts +17 -0
- package/dist/authoring/activity.js +18 -0
- package/dist/authoring/activity.js.map +1 -0
- package/dist/authoring/agent.d.ts +70 -0
- package/dist/authoring/agent.js +268 -0
- package/dist/authoring/agent.js.map +1 -0
- package/dist/authoring/appLaunch.d.ts +7 -0
- package/dist/authoring/appLaunch.js +32 -0
- package/dist/authoring/appLaunch.js.map +1 -0
- package/dist/authoring/appLocation.d.ts +2 -0
- package/dist/authoring/appLocation.js +5 -0
- package/dist/authoring/appLocation.js.map +1 -0
- package/dist/authoring/browser.d.ts +15 -0
- package/dist/authoring/browser.js +20 -0
- package/dist/authoring/browser.js.map +1 -0
- package/dist/authoring/compile.d.ts +47 -0
- package/dist/authoring/compile.js +508 -0
- package/dist/authoring/compile.js.map +1 -0
- package/dist/authoring/discover.d.ts +43 -0
- package/dist/authoring/discover.js +149 -0
- package/dist/authoring/discover.js.map +1 -0
- package/dist/authoring/environmentTemplates.d.ts +3 -0
- package/dist/authoring/environmentTemplates.js +24 -0
- package/dist/authoring/environmentTemplates.js.map +1 -0
- package/dist/authoring/heal.d.ts +108 -0
- package/dist/authoring/heal.js +723 -0
- package/dist/authoring/heal.js.map +1 -0
- package/dist/authoring/index.d.ts +14 -0
- package/dist/authoring/index.js +15 -0
- package/dist/authoring/index.js.map +1 -0
- package/dist/authoring/init.d.ts +21 -0
- package/dist/authoring/init.js +141 -0
- package/dist/authoring/init.js.map +1 -0
- package/dist/authoring/localSettings.d.ts +33 -0
- package/dist/authoring/localSettings.js +31 -0
- package/dist/authoring/localSettings.js.map +1 -0
- package/dist/authoring/media.d.ts +10 -0
- package/dist/authoring/media.js +37 -0
- package/dist/authoring/media.js.map +1 -0
- package/dist/authoring/module.d.ts +2 -0
- package/dist/authoring/module.js +38 -0
- package/dist/authoring/module.js.map +1 -0
- package/dist/authoring/run.d.ts +74 -0
- package/dist/authoring/run.js +242 -0
- package/dist/authoring/run.js.map +1 -0
- package/dist/authoring/runSource.d.ts +6 -0
- package/dist/authoring/runSource.js +24 -0
- package/dist/authoring/runSource.js.map +1 -0
- package/dist/authoring/savedRun.d.ts +13 -0
- package/dist/authoring/savedRun.js +94 -0
- package/dist/authoring/savedRun.js.map +1 -0
- package/dist/authoring/schema.d.ts +574 -0
- package/dist/authoring/schema.js +147 -0
- package/dist/authoring/schema.js.map +1 -0
- package/dist/authoring/snapshotSource.d.ts +13 -0
- package/dist/authoring/snapshotSource.js +41 -0
- package/dist/authoring/snapshotSource.js.map +1 -0
- package/dist/authoring/sourceSelection.d.ts +34 -0
- package/dist/authoring/sourceSelection.js +13 -0
- package/dist/authoring/sourceSelection.js.map +1 -0
- package/dist/authoring/sources.d.ts +5 -0
- package/dist/authoring/sources.js +65 -0
- package/dist/authoring/sources.js.map +1 -0
- package/dist/authoring/status.d.ts +48 -0
- package/dist/authoring/status.js +100 -0
- package/dist/authoring/status.js.map +1 -0
- package/dist/authoring/typed.d.ts +6 -0
- package/dist/authoring/typed.js +5 -0
- package/dist/authoring/typed.js.map +1 -0
- package/dist/authoring/ui.d.ts +10 -0
- package/dist/authoring/ui.js +486 -0
- package/dist/authoring/ui.js.map +1 -0
- package/dist/authoring/uiPage.d.ts +1 -0
- package/dist/authoring/uiPage.js +358 -0
- package/dist/authoring/uiPage.js.map +1 -0
- package/dist/authoring/uiSourceControls.d.ts +2 -0
- package/dist/authoring/uiSourceControls.js +41 -0
- package/dist/authoring/uiSourceControls.js.map +1 -0
- package/dist/authoring/verificationSource.d.ts +41 -0
- package/dist/authoring/verificationSource.js +46 -0
- package/dist/authoring/verificationSource.js.map +1 -0
- package/dist/authoring/workspace.d.ts +33 -0
- package/dist/authoring/workspace.js +58 -0
- package/dist/authoring/workspace.js.map +1 -0
- package/dist/cli/arguments.d.ts +288 -0
- package/dist/cli/arguments.js +121 -0
- package/dist/cli/arguments.js.map +1 -0
- package/dist/cli/cleanup.d.ts +18 -0
- package/dist/cli/cleanup.js +40 -0
- package/dist/cli/cleanup.js.map +1 -0
- package/dist/cli/doctor.d.ts +51 -0
- package/dist/cli/doctor.js +118 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/errors.d.ts +2 -0
- package/dist/cli/errors.js +84 -0
- package/dist/cli/errors.js.map +1 -0
- package/dist/cli/jsonFile.d.ts +2 -0
- package/dist/cli/jsonFile.js +15 -0
- package/dist/cli/jsonFile.js.map +1 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +603 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/render.d.ts +22 -0
- package/dist/cli/render.js +150 -0
- package/dist/cli/render.js.map +1 -0
- package/dist/cli/samples/catalog.d.ts +10 -0
- package/dist/cli/samples/catalog.js +3 -0
- package/dist/cli/samples/catalog.js.map +1 -0
- package/dist/cli/samples/write.d.ts +5 -0
- package/dist/cli/samples/write.js +64 -0
- package/dist/cli/samples/write.js.map +1 -0
- package/dist/cli/source.d.ts +41 -0
- package/dist/cli/source.js +59 -0
- package/dist/cli/source.js.map +1 -0
- package/dist/core/activationDecision.d.ts +153 -0
- package/dist/core/activationDecision.js +34 -0
- package/dist/core/activationDecision.js.map +1 -0
- package/dist/core/artifacts.d.ts +18 -0
- package/dist/core/artifacts.js +79 -0
- package/dist/core/artifacts.js.map +1 -0
- package/dist/core/atomicFiles.d.ts +9 -0
- package/dist/core/atomicFiles.js +76 -0
- package/dist/core/atomicFiles.js.map +1 -0
- package/dist/core/boundedFile.d.ts +2 -0
- package/dist/core/boundedFile.js +28 -0
- package/dist/core/boundedFile.js.map +1 -0
- package/dist/core/bundle.d.ts +77 -0
- package/dist/core/bundle.js +375 -0
- package/dist/core/bundle.js.map +1 -0
- package/dist/core/bundleArchive.d.ts +10 -0
- package/dist/core/bundleArchive.js +47 -0
- package/dist/core/bundleArchive.js.map +1 -0
- package/dist/core/bundleEvidence.d.ts +12 -0
- package/dist/core/bundleEvidence.js +169 -0
- package/dist/core/bundleEvidence.js.map +1 -0
- package/dist/core/bundleExport.d.ts +12 -0
- package/dist/core/bundleExport.js +36 -0
- package/dist/core/bundleExport.js.map +1 -0
- package/dist/core/bundleFiles.d.ts +5 -0
- package/dist/core/bundleFiles.js +47 -0
- package/dist/core/bundleFiles.js.map +1 -0
- package/dist/core/bundlePaths.d.ts +2 -0
- package/dist/core/bundlePaths.js +7 -0
- package/dist/core/bundlePaths.js.map +1 -0
- package/dist/core/bundleSnapshot.d.ts +26 -0
- package/dist/core/bundleSnapshot.js +71 -0
- package/dist/core/bundleSnapshot.js.map +1 -0
- package/dist/core/capabilityBoundary.d.ts +1452 -0
- package/dist/core/capabilityBoundary.js +361 -0
- package/dist/core/capabilityBoundary.js.map +1 -0
- package/dist/core/capabilityLifecycle.d.ts +366 -0
- package/dist/core/capabilityLifecycle.js +79 -0
- package/dist/core/capabilityLifecycle.js.map +1 -0
- package/dist/core/cleanup.d.ts +35 -0
- package/dist/core/cleanup.js +71 -0
- package/dist/core/cleanup.js.map +1 -0
- package/dist/core/cleanupActions.d.ts +5 -0
- package/dist/core/cleanupActions.js +15 -0
- package/dist/core/cleanupActions.js.map +1 -0
- package/dist/core/cleanupAttestation.d.ts +23 -0
- package/dist/core/cleanupAttestation.js +57 -0
- package/dist/core/cleanupAttestation.js.map +1 -0
- package/dist/core/clock.d.ts +4 -0
- package/dist/core/clock.js +14 -0
- package/dist/core/clock.js.map +1 -0
- package/dist/core/compiler.d.ts +19 -0
- package/dist/core/compiler.js +200 -0
- package/dist/core/compiler.js.map +1 -0
- package/dist/core/controller.d.ts +7 -0
- package/dist/core/controller.js +132 -0
- package/dist/core/controller.js.map +1 -0
- package/dist/core/coordination.d.ts +72 -0
- package/dist/core/coordination.js +118 -0
- package/dist/core/coordination.js.map +1 -0
- package/dist/core/corpus.d.ts +60 -0
- package/dist/core/corpus.js +216 -0
- package/dist/core/corpus.js.map +1 -0
- package/dist/core/deadline.d.ts +12 -0
- package/dist/core/deadline.js +38 -0
- package/dist/core/deadline.js.map +1 -0
- package/dist/core/deliveryJournal.d.ts +37 -0
- package/dist/core/deliveryJournal.js +85 -0
- package/dist/core/deliveryJournal.js.map +1 -0
- package/dist/core/environmentPreflight.d.ts +14 -0
- package/dist/core/environmentPreflight.js +179 -0
- package/dist/core/environmentPreflight.js.map +1 -0
- package/dist/core/errors.d.ts +22 -0
- package/dist/core/errors.js +29 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/events.d.ts +14 -0
- package/dist/core/events.js +37 -0
- package/dist/core/events.js.map +1 -0
- package/dist/core/executionInspection.d.ts +90 -0
- package/dist/core/executionInspection.js +31 -0
- package/dist/core/executionInspection.js.map +1 -0
- package/dist/core/executionOwnership.d.ts +72 -0
- package/dist/core/executionOwnership.js +82 -0
- package/dist/core/executionOwnership.js.map +1 -0
- package/dist/core/executionPreparation.d.ts +362 -0
- package/dist/core/executionPreparation.js +93 -0
- package/dist/core/executionPreparation.js.map +1 -0
- package/dist/core/executionReadback.d.ts +15 -0
- package/dist/core/executionReadback.js +21 -0
- package/dist/core/executionReadback.js.map +1 -0
- package/dist/core/executor.d.ts +46 -0
- package/dist/core/executor.js +616 -0
- package/dist/core/executor.js.map +1 -0
- package/dist/core/frozenBundle.d.ts +314 -0
- package/dist/core/frozenBundle.js +24 -0
- package/dist/core/frozenBundle.js.map +1 -0
- package/dist/core/fsDurability.d.ts +2 -0
- package/dist/core/fsDurability.js +12 -0
- package/dist/core/fsDurability.js.map +1 -0
- package/dist/core/generators.d.ts +4 -0
- package/dist/core/generators.js +71 -0
- package/dist/core/generators.js.map +1 -0
- package/dist/core/gitSnapshot.d.ts +63 -0
- package/dist/core/gitSnapshot.js +55 -0
- package/dist/core/gitSnapshot.js.map +1 -0
- package/dist/core/gitSources.d.ts +29 -0
- package/dist/core/gitSources.js +97 -0
- package/dist/core/gitSources.js.map +1 -0
- package/dist/core/ledger.d.ts +38 -0
- package/dist/core/ledger.js +169 -0
- package/dist/core/ledger.js.map +1 -0
- package/dist/core/ledgerInspection.d.ts +18 -0
- package/dist/core/ledgerInspection.js +66 -0
- package/dist/core/ledgerInspection.js.map +1 -0
- package/dist/core/ledgerRecords.d.ts +23 -0
- package/dist/core/ledgerRecords.js +29 -0
- package/dist/core/ledgerRecords.js.map +1 -0
- package/dist/core/localActivationState.d.ts +260 -0
- package/dist/core/localActivationState.js +49 -0
- package/dist/core/localActivationState.js.map +1 -0
- package/dist/core/localPromotionState.d.ts +387 -0
- package/dist/core/localPromotionState.js +22 -0
- package/dist/core/localPromotionState.js.map +1 -0
- package/dist/core/localQualificationResults.d.ts +39 -0
- package/dist/core/localQualificationResults.js +160 -0
- package/dist/core/localQualificationResults.js.map +1 -0
- package/dist/core/localReviewStore.d.ts +28 -0
- package/dist/core/localReviewStore.js +167 -0
- package/dist/core/localReviewStore.js.map +1 -0
- package/dist/core/localScenarioActivation.d.ts +6 -0
- package/dist/core/localScenarioActivation.js +70 -0
- package/dist/core/localScenarioActivation.js.map +1 -0
- package/dist/core/localScenarioOwner.d.ts +20 -0
- package/dist/core/localScenarioOwner.js +2 -0
- package/dist/core/localScenarioOwner.js.map +1 -0
- package/dist/core/localScenarioPromotion.d.ts +9 -0
- package/dist/core/localScenarioPromotion.js +49 -0
- package/dist/core/localScenarioPromotion.js.map +1 -0
- package/dist/core/localScenarioState.d.ts +2297 -0
- package/dist/core/localScenarioState.js +117 -0
- package/dist/core/localScenarioState.js.map +1 -0
- package/dist/core/localScenarioStore.d.ts +838 -0
- package/dist/core/localScenarioStore.js +218 -0
- package/dist/core/localScenarioStore.js.map +1 -0
- package/dist/core/localScenarioStructure.d.ts +4 -0
- package/dist/core/localScenarioStructure.js +52 -0
- package/dist/core/localScenarioStructure.js.map +1 -0
- package/dist/core/localStructureState.d.ts +2 -0
- package/dist/core/localStructureState.js +22 -0
- package/dist/core/localStructureState.js.map +1 -0
- package/dist/core/lookup.d.ts +662 -0
- package/dist/core/lookup.js +55 -0
- package/dist/core/lookup.js.map +1 -0
- package/dist/core/ownership.d.ts +3 -0
- package/dist/core/ownership.js +24 -0
- package/dist/core/ownership.js.map +1 -0
- package/dist/core/planInspection.d.ts +108 -0
- package/dist/core/planInspection.js +44 -0
- package/dist/core/planInspection.js.map +1 -0
- package/dist/core/planner.d.ts +14 -0
- package/dist/core/planner.js +261 -0
- package/dist/core/planner.js.map +1 -0
- package/dist/core/qualification.d.ts +26 -0
- package/dist/core/qualification.js +58 -0
- package/dist/core/qualification.js.map +1 -0
- package/dist/core/qualificationBundle.d.ts +6 -0
- package/dist/core/qualificationBundle.js +51 -0
- package/dist/core/qualificationBundle.js.map +1 -0
- package/dist/core/qualificationExecution.d.ts +879 -0
- package/dist/core/qualificationExecution.js +54 -0
- package/dist/core/qualificationExecution.js.map +1 -0
- package/dist/core/recoveryStatus.d.ts +6 -0
- package/dist/core/recoveryStatus.js +123 -0
- package/dist/core/recoveryStatus.js.map +1 -0
- package/dist/core/reducer.d.ts +56 -0
- package/dist/core/reducer.js +355 -0
- package/dist/core/reducer.js.map +1 -0
- package/dist/core/replayAssessment.d.ts +15 -0
- package/dist/core/replayAssessment.js +78 -0
- package/dist/core/replayAssessment.js.map +1 -0
- package/dist/core/replayEvidence.d.ts +7 -0
- package/dist/core/replayEvidence.js +58 -0
- package/dist/core/replayEvidence.js.map +1 -0
- package/dist/core/repositorySelectionEvidence.d.ts +3 -0
- package/dist/core/repositorySelectionEvidence.js +58 -0
- package/dist/core/repositorySelectionEvidence.js.map +1 -0
- package/dist/core/rerun.d.ts +3 -0
- package/dist/core/rerun.js +24 -0
- package/dist/core/rerun.js.map +1 -0
- package/dist/core/resultSink.d.ts +52 -0
- package/dist/core/resultSink.js +120 -0
- package/dist/core/resultSink.js.map +1 -0
- package/dist/core/review.d.ts +22 -0
- package/dist/core/review.js +65 -0
- package/dist/core/review.js.map +1 -0
- package/dist/core/runnerCapabilities.d.ts +22 -0
- package/dist/core/runnerCapabilities.js +44 -0
- package/dist/core/runnerCapabilities.js.map +1 -0
- package/dist/core/scenario.d.ts +32 -0
- package/dist/core/scenario.js +119 -0
- package/dist/core/scenario.js.map +1 -0
- package/dist/core/scenarioActivation.d.ts +16 -0
- package/dist/core/scenarioActivation.js +47 -0
- package/dist/core/scenarioActivation.js.map +1 -0
- package/dist/core/scenarioStructure.d.ts +400 -0
- package/dist/core/scenarioStructure.js +72 -0
- package/dist/core/scenarioStructure.js.map +1 -0
- package/dist/core/snapshotEvidence.d.ts +3 -0
- package/dist/core/snapshotEvidence.js +37 -0
- package/dist/core/snapshotEvidence.js.map +1 -0
- package/dist/core/snapshotRepository.d.ts +48 -0
- package/dist/core/snapshotRepository.js +47 -0
- package/dist/core/snapshotRepository.js.map +1 -0
- package/dist/core/source-analysis.d.ts +18 -0
- package/dist/core/source-analysis.js +99 -0
- package/dist/core/source-analysis.js.map +1 -0
- package/dist/core/source-citation.d.ts +53 -0
- package/dist/core/source-citation.js +59 -0
- package/dist/core/source-citation.js.map +1 -0
- package/dist/core/sourcePromotion.d.ts +929 -0
- package/dist/core/sourcePromotion.js +92 -0
- package/dist/core/sourcePromotion.js.map +1 -0
- package/dist/core/sourceReviewEvidence.d.ts +271 -0
- package/dist/core/sourceReviewEvidence.js +24 -0
- package/dist/core/sourceReviewEvidence.js.map +1 -0
- package/dist/core/targetObservation.d.ts +3 -0
- package/dist/core/targetObservation.js +26 -0
- package/dist/core/targetObservation.js.map +1 -0
- package/dist/hosts/launchTemplates.d.ts +2 -0
- package/dist/hosts/launchTemplates.js +5 -0
- package/dist/hosts/launchTemplates.js.map +1 -0
- package/dist/hosts/local.d.ts +17 -0
- package/dist/hosts/local.js +311 -0
- package/dist/hosts/local.js.map +1 -0
- package/dist/hosts/localSupervisor.d.ts +1 -0
- package/dist/hosts/localSupervisor.js +115 -0
- package/dist/hosts/localSupervisor.js.map +1 -0
- package/dist/hosts/mock.d.ts +4 -0
- package/dist/hosts/mock.js +82 -0
- package/dist/hosts/mock.js.map +1 -0
- package/dist/hosts/pinnedArguments.d.ts +3 -0
- package/dist/hosts/pinnedArguments.js +39 -0
- package/dist/hosts/pinnedArguments.js.map +1 -0
- package/dist/hosts/runtime.d.ts +5 -0
- package/dist/hosts/runtime.js +60 -0
- package/dist/hosts/runtime.js.map +1 -0
- package/dist/hosts/sourceIdentity.d.ts +54 -0
- package/dist/hosts/sourceIdentity.js +27 -0
- package/dist/hosts/sourceIdentity.js.map +1 -0
- package/dist/hosts/types.d.ts +39 -0
- package/dist/hosts/types.js +2 -0
- package/dist/hosts/types.js.map +1 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +52 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/awr/attachment.d.ts +34 -0
- package/dist/integrations/awr/attachment.js +120 -0
- package/dist/integrations/awr/attachment.js.map +1 -0
- package/dist/integrations/awr/attachmentContracts.d.ts +273 -0
- package/dist/integrations/awr/attachmentContracts.js +39 -0
- package/dist/integrations/awr/attachmentContracts.js.map +1 -0
- package/dist/integrations/awr/attachmentHttp.d.ts +9 -0
- package/dist/integrations/awr/attachmentHttp.js +19 -0
- package/dist/integrations/awr/attachmentHttp.js.map +1 -0
- package/dist/integrations/awr/cli.d.ts +7 -0
- package/dist/integrations/awr/cli.js +43 -0
- package/dist/integrations/awr/cli.js.map +1 -0
- package/dist/integrations/awr/contracts.d.ts +54 -0
- package/dist/integrations/awr/contracts.js +33 -0
- package/dist/integrations/awr/contracts.js.map +1 -0
- package/dist/integrations/awr/http.d.ts +8 -0
- package/dist/integrations/awr/http.js +23 -0
- package/dist/integrations/awr/http.js.map +1 -0
- package/dist/integrations/awr/index.d.ts +7 -0
- package/dist/integrations/awr/index.js +8 -0
- package/dist/integrations/awr/index.js.map +1 -0
- package/dist/integrations/awr/jsonHttp.d.ts +15 -0
- package/dist/integrations/awr/jsonHttp.js +69 -0
- package/dist/integrations/awr/jsonHttp.js.map +1 -0
- package/dist/integrations/awr/outbox.d.ts +28 -0
- package/dist/integrations/awr/outbox.js +88 -0
- package/dist/integrations/awr/outbox.js.map +1 -0
- package/dist/integrations/awr/projection.d.ts +4 -0
- package/dist/integrations/awr/projection.js +54 -0
- package/dist/integrations/awr/projection.js.map +1 -0
- package/dist/protocol/canonical.d.ts +41 -0
- package/dist/protocol/canonical.js +114 -0
- package/dist/protocol/canonical.js.map +1 -0
- package/dist/protocol/index.d.ts +9620 -0
- package/dist/protocol/index.js +185 -0
- package/dist/protocol/index.js.map +1 -0
- package/dist/protocol/primitives.d.ts +63 -0
- package/dist/protocol/primitives.js +46 -0
- package/dist/protocol/primitives.js.map +1 -0
- package/dist/protocol/schemas/browser.d.ts +153 -0
- package/dist/protocol/schemas/browser.js +29 -0
- package/dist/protocol/schemas/browser.js.map +1 -0
- package/dist/protocol/schemas/capability.d.ts +2126 -0
- package/dist/protocol/schemas/capability.js +165 -0
- package/dist/protocol/schemas/capability.js.map +1 -0
- package/dist/protocol/schemas/capabilityLifecycle.d.ts +509 -0
- package/dist/protocol/schemas/capabilityLifecycle.js +47 -0
- package/dist/protocol/schemas/capabilityLifecycle.js.map +1 -0
- package/dist/protocol/schemas/cleanup.d.ts +109 -0
- package/dist/protocol/schemas/cleanup.js +59 -0
- package/dist/protocol/schemas/cleanup.js.map +1 -0
- package/dist/protocol/schemas/cli.d.ts +121 -0
- package/dist/protocol/schemas/cli.js +24 -0
- package/dist/protocol/schemas/cli.js.map +1 -0
- package/dist/protocol/schemas/config.d.ts +59 -0
- package/dist/protocol/schemas/config.js +32 -0
- package/dist/protocol/schemas/config.js.map +1 -0
- package/dist/protocol/schemas/corpus.d.ts +36 -0
- package/dist/protocol/schemas/corpus.js +10 -0
- package/dist/protocol/schemas/corpus.js.map +1 -0
- package/dist/protocol/schemas/definitions.d.ts +3409 -0
- package/dist/protocol/schemas/definitions.js +431 -0
- package/dist/protocol/schemas/definitions.js.map +1 -0
- package/dist/protocol/schemas/evidenceProjection.d.ts +146 -0
- package/dist/protocol/schemas/evidenceProjection.js +18 -0
- package/dist/protocol/schemas/evidenceProjection.js.map +1 -0
- package/dist/protocol/schemas/execution.d.ts +3179 -0
- package/dist/protocol/schemas/execution.js +500 -0
- package/dist/protocol/schemas/execution.js.map +1 -0
- package/dist/protocol/schemas/gitSources.d.ts +330 -0
- package/dist/protocol/schemas/gitSources.js +32 -0
- package/dist/protocol/schemas/gitSources.js.map +1 -0
- package/dist/protocol/schemas/mcp.d.ts +24 -0
- package/dist/protocol/schemas/mcp.js +16 -0
- package/dist/protocol/schemas/mcp.js.map +1 -0
- package/dist/protocol/schemas/nativeJob.d.ts +106 -0
- package/dist/protocol/schemas/nativeJob.js +16 -0
- package/dist/protocol/schemas/nativeJob.js.map +1 -0
- package/dist/protocol/schemas/portableEnvironment.d.ts +1256 -0
- package/dist/protocol/schemas/portableEnvironment.js +108 -0
- package/dist/protocol/schemas/portableEnvironment.js.map +1 -0
- package/dist/protocol/schemas/promotion.d.ts +132 -0
- package/dist/protocol/schemas/promotion.js +9 -0
- package/dist/protocol/schemas/promotion.js.map +1 -0
- package/dist/protocol/schemas/protocol.d.ts +146 -0
- package/dist/protocol/schemas/protocol.js +25 -0
- package/dist/protocol/schemas/protocol.js.map +1 -0
- package/dist/protocol/schemas/recoveryStatus.d.ts +88 -0
- package/dist/protocol/schemas/recoveryStatus.js +20 -0
- package/dist/protocol/schemas/recoveryStatus.js.map +1 -0
- package/dist/protocol/schemas/resultSink.d.ts +80 -0
- package/dist/protocol/schemas/resultSink.js +14 -0
- package/dist/protocol/schemas/resultSink.js.map +1 -0
- package/dist/protocol/schemas/review.d.ts +248 -0
- package/dist/protocol/schemas/review.js +45 -0
- package/dist/protocol/schemas/review.js.map +1 -0
- package/dist/protocol/schemas/scenario.d.ts +810 -0
- package/dist/protocol/schemas/scenario.js +53 -0
- package/dist/protocol/schemas/scenario.js.map +1 -0
- package/dist/protocol/schemas/source.d.ts +300 -0
- package/dist/protocol/schemas/source.js +25 -0
- package/dist/protocol/schemas/source.js.map +1 -0
- package/dist/source/git.d.ts +36 -0
- package/dist/source/git.js +159 -0
- package/dist/source/git.js.map +1 -0
- package/dist/source/gitObjectProof.d.ts +12 -0
- package/dist/source/gitObjectProof.js +115 -0
- package/dist/source/gitObjectProof.js.map +1 -0
- package/dist/source/snapshotPaths.d.ts +2 -0
- package/dist/source/snapshotPaths.js +24 -0
- package/dist/source/snapshotPaths.js.map +1 -0
- package/dist/source/snapshotTree.d.ts +15 -0
- package/dist/source/snapshotTree.js +79 -0
- package/dist/source/snapshotTree.js.map +1 -0
- package/docs/architecture.md +126 -0
- package/docs/awr-delivery.md +102 -0
- package/docs/browser-driver.md +148 -0
- package/docs/cleanup-followups.md +74 -0
- package/docs/developer-ux.md +315 -0
- package/docs/examples/portable-environments/alongside_runner.json +199 -0
- package/docs/examples/portable-environments/external_staging.json +183 -0
- package/docs/git-object-proof.md +100 -0
- package/docs/git-source-resolution.md +133 -0
- package/docs/images/ui-run.png +0 -0
- package/docs/local-execution-recovery.md +55 -0
- package/docs/local-review-store.md +13 -0
- package/docs/local-scenario-store.md +91 -0
- package/docs/mcp-driver.md +87 -0
- package/docs/native-jobs.md +96 -0
- package/docs/portable-environments.md +155 -0
- package/docs/qualification-registration.md +58 -0
- package/docs/qualification-results.md +59 -0
- package/docs/replay-assessment.md +17 -0
- package/docs/result-sink.md +87 -0
- package/docs/scenario-activation.md +54 -0
- package/docs/scenario-structure.md +58 -0
- package/docs/source-promotion.md +104 -0
- package/docs/source-review.md +21 -0
- package/docs/verified-bundles.md +175 -0
- package/docs/work-report-attachment.md +127 -0
- package/package.json +45 -4
- package/samples/django-notes/manage.py +8 -0
- package/samples/django-notes/notes/__init__.py +0 -0
- package/samples/django-notes/notes/auth.py +14 -0
- package/samples/django-notes/notes/migrations/0001_initial.py +19 -0
- package/samples/django-notes/notes/migrations/__init__.py +0 -0
- package/samples/django-notes/notes/models.py +13 -0
- package/samples/django-notes/notes/views.py +33 -0
- package/samples/django-notes/notesite/__init__.py +0 -0
- package/samples/django-notes/notesite/settings.py +17 -0
- package/samples/django-notes/notesite/urls.py +9 -0
- package/samples/django-notes/requirements.txt +1 -0
- package/samples/django-notes/tracegrove/notes/crud.flow.yaml +27 -0
- package/samples/django-notes/tracegrove.config.yaml +7 -0
- package/samples/fastapi-notes/app/__init__.py +0 -0
- package/samples/fastapi-notes/app/main.py +69 -0
- package/samples/fastapi-notes/requirements.txt +2 -0
- package/samples/fastapi-notes/tracegrove/notes/crud.flow.yaml +27 -0
- package/samples/fastapi-notes/tracegrove.config.yaml +8 -0
- package/samples/notes-api/app/app.mjs +67 -0
- package/samples/notes-api/app/http.mjs +16 -0
- package/samples/notes-api/tracegrove/fixtures/existing-note.fixture.yaml +12 -0
- package/samples/notes-api/tracegrove/notes/create-read-delete.flow.yaml +35 -0
- package/samples/notes-api/tracegrove/notes/list.flow.yaml +25 -0
- package/samples/notes-api/tracegrove.config.yaml +15 -0
- package/samples/notes-api/verification/actions/notes.registry.yaml +6 -0
- package/samples/notes-api/verification/auth/host-issued-owner.yaml +6 -0
- package/samples/notes-api/verification/cases/notes.contract.yaml +51 -0
- package/samples/notes-api/verification/cases/notes.create.yaml +123 -0
- package/samples/notes-api/verification/cases/notes.suite.yaml +5 -0
- package/samples/notes-api/verification/components/notes-api-stub.profile.yaml +8 -0
- package/samples/notes-api/verification/components/notes-api.profile.yaml +15 -0
- package/samples/notes-api/verification/environments/local-notes.yaml +11 -0
- package/samples/notes-api/verification/environments/mock-notes.yaml +11 -0
- package/samples/notes-api/verification/fidelity/full-live-sandbox.yaml +7 -0
- package/samples/notes-api/verification/fidelity/mock-contract.yaml +6 -0
- package/samples/notes-api/verification/fixtures/existing-note.payload.yaml +15 -0
- package/samples/notes-api/verification/fixtures/existing-note.plan.yaml +9 -0
- package/samples/notes-api/verification/fixtures/existing-note.recipe.yaml +29 -0
- package/samples/notes-api/verification/hosts/local-process.yaml +27 -0
- package/samples/notes-api/verification/hosts/mock-loopback.yaml +14 -0
- package/samples/notes-api/verification/mocks/notes.responses.yaml +5 -0
- package/samples/notes-api/verification/personas/owner.yaml +16 -0
- package/samples/notes-api/verification/policies/no-authoring-inference.yaml +10 -0
- package/samples/notes-api/verification/runners/http.yaml +5 -0
- package/samples/notes-api/verification/surfaces/http-api.binding.yaml +11 -0
- package/samples/notes-api/verification/surfaces/http-api.pack.yaml +7 -0
- package/samples/notes-api/verification/workflow.yaml +27 -0
- package/samples/notes-browser/tracegrove/notes/browser.flow.yaml +19 -0
- package/samples/notes-browser/verification/cases/notes.browser.yaml +63 -0
- package/samples/notes-browser/verification/cases/notes.suite.yaml +4 -0
- package/samples/notes-browser/verification/runners/http.yaml +5 -0
- package/samples/notes-browser/verification/surfaces/http-api.binding.yaml +11 -0
- package/samples/notes-browser/verification/surfaces/http-api.pack.yaml +7 -0
- package/samples/notes-django/README.md +21 -0
- package/samples/notes-django/app/django_app.py +119 -0
- package/samples/notes-django/app/requirements.lock +4 -0
- package/samples/notes-mcp/app/mcp.mjs +47 -0
- package/samples/notes-mcp/tracegrove/notes.flow.yaml +20 -0
- package/samples/notes-mcp/tracegrove.config.yaml +13 -0
- package/samples/report-jobs/README.md +30 -0
- package/samples/report-jobs/app/jobs-api.mjs +65 -0
- package/samples/report-jobs/app/native-bridge.mjs +30 -0
- package/samples/report-jobs/app/store.mjs +74 -0
- package/samples/report-jobs/app/worker.mjs +22 -0
- package/samples/report-jobs/tracegrove/fixtures/failed-job.fixture.yaml +12 -0
- package/samples/report-jobs/tracegrove/fixtures/held-job.fixture.yaml +12 -0
- package/samples/report-jobs/tracegrove/fixtures/success-job.fixture.yaml +12 -0
- package/samples/report-jobs/tracegrove/reports/cancellation.flow.yaml +15 -0
- package/samples/report-jobs/tracegrove/reports/completion.flow.yaml +31 -0
- package/samples/report-jobs/tracegrove/reports/failure.flow.yaml +12 -0
- package/samples/report-jobs/tracegrove.config.yaml +10 -0
- package/samples/report-native/README.md +11 -0
- package/samples/report-native/tracegrove/cancellation.flow.yaml +11 -0
- package/samples/report-native/tracegrove/completion.flow.yaml +22 -0
- package/samples/report-native/tracegrove/failure.flow.yaml +7 -0
- package/samples/report-native/tracegrove.config.yaml +11 -0
- package/schema/tracegrove_cli-error_v1.json +156 -0
- package/schema/tracegrove_config_v1.json +233 -0
- package/schema/tracegrove_fixture_v1.json +297 -0
- package/schema/tracegrove_flow_v1.json +960 -0
- package/schema/tracegrove_recovery-status_v1.json +234 -0
- package/schema/tracegrove_run-video_v1.json +25 -0
- package/schema/workflow_action-registry_v1.json +303 -0
- package/schema/workflow_auth-recipe_v1.json +146 -0
- package/schema/workflow_boundary-observation_v1.json +515 -0
- package/schema/workflow_browser-command_v1.json +292 -0
- package/schema/workflow_browser-locator_v1.json +74 -0
- package/schema/workflow_browser-network-observation_v1.json +48 -0
- package/schema/workflow_browser-observation_v1.json +53 -0
- package/schema/workflow_budget-reservation_v1.json +150 -0
- package/schema/workflow_campaign-result_v1.json +382 -0
- package/schema/workflow_capability-artifact-manifest_v1.json +529 -0
- package/schema/workflow_capability-descriptor_v1.json +351 -0
- package/schema/workflow_capability-lifecycle-checkpoint_v1.json +510 -0
- package/schema/workflow_capability-observation_v1.json +1707 -0
- package/schema/workflow_capability-operation_v1.json +3212 -0
- package/schema/workflow_capability-replay-plan_v1.json +1247 -0
- package/schema/workflow_capability-retry-policy_v1.json +39 -0
- package/schema/workflow_capability-retry-state_v1.json +242 -0
- package/schema/workflow_case_v1.json +4222 -0
- package/schema/workflow_change-model_v1.json +614 -0
- package/schema/workflow_cleanup-expectation_v1.json +108 -0
- package/schema/workflow_cleanup-followup_v1.json +145 -0
- package/schema/workflow_component-profile_v1.json +219 -0
- package/schema/workflow_config_v1.json +180 -0
- package/schema/workflow_corpus_v1.json +172 -0
- package/schema/workflow_cost-observation_v1.json +366 -0
- package/schema/workflow_credential-lease_v1.json +222 -0
- package/schema/workflow_effective-config_v1.json +214 -0
- package/schema/workflow_environment-plan_v1.json +445 -0
- package/schema/workflow_environment_v1.json +1827 -0
- package/schema/workflow_evidence-projection_v1.json +636 -0
- package/schema/workflow_execution_v1.json +4086 -0
- package/schema/workflow_fidelity-plan_v1.json +512 -0
- package/schema/workflow_fidelity-profile_v1.json +396 -0
- package/schema/workflow_fixture-manifest_v1.json +918 -0
- package/schema/workflow_fixture-plan_v1.json +594 -0
- package/schema/workflow_git-source-request_v1.json +124 -0
- package/schema/workflow_git-source-resolution_v1.json +660 -0
- package/schema/workflow_git-source-snapshot_v1.json +287 -0
- package/schema/workflow_host-lease_v1.json +321 -0
- package/schema/workflow_host-observation_v1.json +282 -0
- package/schema/workflow_host-plan_v1.json +719 -0
- package/schema/workflow_identity-attestation_v1.json +412 -0
- package/schema/workflow_intelligence-policy_v1.json +153 -0
- package/schema/workflow_logical-payload_v1.json +206 -0
- package/schema/workflow_mcp-tool-call_v1.json +70 -0
- package/schema/workflow_mcp-tool-observation_v1.json +125 -0
- package/schema/workflow_mock-responses_v1.json +185 -0
- package/schema/workflow_native-job-action_v1.json +123 -0
- package/schema/workflow_native-job-observation_v1.json +138 -0
- package/schema/workflow_native-job-ready_v1.json +31 -0
- package/schema/workflow_native-job-request_v1.json +155 -0
- package/schema/workflow_native-job-response_v1.json +125 -0
- package/schema/workflow_ownership_v1.json +116 -0
- package/schema/workflow_persona_v1.json +499 -0
- package/schema/workflow_plan-edit-projection_v1.json +152 -0
- package/schema/workflow_plan-review_v1.json +246 -0
- package/schema/workflow_portable-environment-intent_v1.json +499 -0
- package/schema/workflow_portable-environment-profile_v1.json +1429 -0
- package/schema/workflow_portable-preflight_v1.json +2281 -0
- package/schema/workflow_qualification-admission_v1.json +467 -0
- package/schema/workflow_qualification-intent_v1.json +98 -0
- package/schema/workflow_qualification-policy_v1.json +109 -0
- package/schema/workflow_qualification-round_v1.json +226 -0
- package/schema/workflow_replay-assessment_v1.json +197 -0
- package/schema/workflow_replay-finding_v1.json +147 -0
- package/schema/workflow_request_v1.json +1567 -0
- package/schema/workflow_result-ingest_v1.json +343 -0
- package/schema/workflow_revisions_v1.json +334 -0
- package/schema/workflow_routing-decision_v1.json +482 -0
- package/schema/workflow_runner-capabilities_v1.json +316 -0
- package/schema/workflow_runner-profile_v1.json +119 -0
- package/schema/workflow_runner-protocol_v1_host.json +382 -0
- package/schema/workflow_runner-protocol_v1_plugin.json +491 -0
- package/schema/workflow_scenario-activation-intent_v1.json +346 -0
- package/schema/workflow_scenario-activation_v1.json +562 -0
- package/schema/workflow_scenario-projection_v1.json +221 -0
- package/schema/workflow_scenario-promotion_v1.json +455 -0
- package/schema/workflow_scenario-replay-assessment_v1.json +696 -0
- package/schema/workflow_scenario-revision-token_v1.json +115 -0
- package/schema/workflow_scenario-structure_v1.json +368 -0
- package/schema/workflow_scenario_v1.json +401 -0
- package/schema/workflow_scoped-resource_v1.json +550 -0
- package/schema/workflow_seed-recipe_v1.json +724 -0
- package/schema/workflow_selection_v1.json +1108 -0
- package/schema/workflow_signed-cleanup-followup_v1.json +175 -0
- package/schema/workflow_source-plan_v1.json +756 -0
- package/schema/workflow_source-selection_v1.json +456 -0
- package/schema/workflow_source-validation_v1.json +366 -0
- package/schema/workflow_step-event_v1.json +228 -0
- package/schema/workflow_stream-transcript_v1.json +1030 -0
- package/schema/workflow_suite_v1.json +64 -0
- package/schema/workflow_surface-binding_v1.json +411 -0
- package/schema/workflow_surface-pack_v1.json +159 -0
|
@@ -0,0 +1,723 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `tracegrove heal`: look at what went wrong, fix what is safe to fix, prove each fix by
|
|
3
|
+
* re-running, and keep only fixes that verify. Setup mistakes (wrong health path, missing
|
|
4
|
+
* interpreter) are fixed deterministically in the config. Failing assertions go to the coding
|
|
5
|
+
* agent, which must decide per case whether the test drifted or the product is wrong; a product
|
|
6
|
+
* bug is reported, never papered over. Guards reject edits that delete steps, drop expectations
|
|
7
|
+
* or flip an expected success into a failure (or back). Every kept change is backed up so
|
|
8
|
+
* `tracegrove heal --undo <id>` restores it.
|
|
9
|
+
*/
|
|
10
|
+
import { spawnSync } from 'node:child_process';
|
|
11
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
12
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import { delimiter, dirname, join, relative, resolve, sep } from 'node:path';
|
|
14
|
+
import { parse as parseYaml, parseDocument } from 'yaml';
|
|
15
|
+
import { Blocked } from "../core/errors.js";
|
|
16
|
+
import { CONFIG_FILE, STATE_DIR, compileProject, loadAuthoringConfig } from "./compile.js";
|
|
17
|
+
import { runTests } from "./run.js";
|
|
18
|
+
import { checkProject, discoverProject } from "./workspace.js";
|
|
19
|
+
import { detectAgents, resolveAgentSettings, runAgent, tracegroveCommand } from "./agent.js";
|
|
20
|
+
import { requireRealDirectory, syncDirectory, writeExclusiveDurable } from "../core/atomicFiles.js";
|
|
21
|
+
export const HEALS_DIR = 'heals';
|
|
22
|
+
const VERDICT_TAG = 'TRACEGROVE_VERDICT';
|
|
23
|
+
const HEALTH_WORDS = /^\/(?:api\/)?(?:health|healthz|healthcheck|ready|readyz|readiness|livez|live|liveness|status|ping|up)(?:\/[\w-]*)?$/;
|
|
24
|
+
function stamp(date) { return date.toISOString().replace(/\.\d{3}Z$/, 'Z').replace(/:/g, '-'); }
|
|
25
|
+
function sha(text) { return createHash('sha256').update(text).digest('hex'); }
|
|
26
|
+
function readText(path) { try {
|
|
27
|
+
return readFileSync(path, 'utf8');
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
} }
|
|
32
|
+
/** Cooperative heal/undo ownership only; not a fence against arbitrary external editors. */
|
|
33
|
+
function acquireMutation(root) {
|
|
34
|
+
const state = join(root, STATE_DIR);
|
|
35
|
+
mkdirSync(state, { recursive: true });
|
|
36
|
+
requireRealDirectory(state);
|
|
37
|
+
const lock = join(state, 'heal-mutation.lock');
|
|
38
|
+
try {
|
|
39
|
+
mkdirSync(lock, { mode: 0o700 });
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (error.code !== 'EEXIST')
|
|
43
|
+
throw error;
|
|
44
|
+
throw new Blocked([{ code: 'heal_mutation_in_progress', detail: lock, action: 'wait for the owning heal/undo; if it exited, preserve the lock and inspect its edits before manual recovery' }]);
|
|
45
|
+
}
|
|
46
|
+
writeFileSync(join(lock, 'owner.json'), JSON.stringify({ pid: process.pid, startedAt: new Date().toISOString() }));
|
|
47
|
+
return () => rmSync(lock, { recursive: true });
|
|
48
|
+
}
|
|
49
|
+
/** A small line diff (LCS) good enough for config and flow files. */
|
|
50
|
+
export function lineDiff(before, after, name) {
|
|
51
|
+
const a = before.split('\n');
|
|
52
|
+
const b = after.split('\n');
|
|
53
|
+
const n = a.length;
|
|
54
|
+
const m = b.length;
|
|
55
|
+
const lcs = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
|
|
56
|
+
for (let i = n - 1; i >= 0; i--)
|
|
57
|
+
for (let j = m - 1; j >= 0; j--)
|
|
58
|
+
lcs[i][j] = a[i] === b[j] ? lcs[i + 1][j + 1] + 1 : Math.max(lcs[i + 1][j], lcs[i][j + 1]);
|
|
59
|
+
const rows = [];
|
|
60
|
+
let i = 0;
|
|
61
|
+
let j = 0;
|
|
62
|
+
while (i < n && j < m) {
|
|
63
|
+
if (a[i] === b[j]) {
|
|
64
|
+
rows.push([' ', a[i]]);
|
|
65
|
+
i++;
|
|
66
|
+
j++;
|
|
67
|
+
}
|
|
68
|
+
else if (lcs[i + 1][j] >= lcs[i][j + 1])
|
|
69
|
+
rows.push(['-', a[i++]]);
|
|
70
|
+
else
|
|
71
|
+
rows.push(['+', b[j++]]);
|
|
72
|
+
}
|
|
73
|
+
while (i < n)
|
|
74
|
+
rows.push(['-', a[i++]]);
|
|
75
|
+
while (j < m)
|
|
76
|
+
rows.push(['+', b[j++]]);
|
|
77
|
+
const keep = rows.map((row, index) => row[0] !== ' ' || rows.slice(Math.max(0, index - 2), index + 3).some((r) => r[0] !== ' '));
|
|
78
|
+
const out = [`--- a/${name}`, `+++ b/${name}`];
|
|
79
|
+
let skipped = false;
|
|
80
|
+
rows.forEach((row, index) => {
|
|
81
|
+
if (keep[index]) {
|
|
82
|
+
out.push(`${row[0]}${row[1]}`);
|
|
83
|
+
skipped = false;
|
|
84
|
+
}
|
|
85
|
+
else if (!skipped) {
|
|
86
|
+
out.push('@@');
|
|
87
|
+
skipped = true;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
return out.join('\n');
|
|
91
|
+
}
|
|
92
|
+
/** Turn a run report (and compile issues) into findings a person or agent can act on. */
|
|
93
|
+
export function classify(report, issues) {
|
|
94
|
+
const findings = [];
|
|
95
|
+
const open = (f) => { findings.push({ ...f, status: 'open' }); };
|
|
96
|
+
for (const issue of issues) {
|
|
97
|
+
const interpreter = issue.code === 'launch_executable_missing' || issue.code === 'application_runtime_unrecognized';
|
|
98
|
+
const file = issue.detail.match(/^(\S+\.(?:flow|fixture)\.yaml)/)?.[1] ?? (interpreter ? CONFIG_FILE : null);
|
|
99
|
+
open({ kind: interpreter ? 'setup' : 'authoring', caseId: null, file, step: null, detail: `${issue.code}: ${issue.detail}`, fix: interpreter ? 'point app.python at an interpreter that has the app\'s dependencies' : issue.action });
|
|
100
|
+
}
|
|
101
|
+
if (!report)
|
|
102
|
+
return findings;
|
|
103
|
+
for (const b of report.blockers)
|
|
104
|
+
open({ kind: 'setup', caseId: null, file: CONFIG_FILE, step: null, detail: `${b.code}: ${b.detail}`, fix: b.action });
|
|
105
|
+
for (const c of report.cases) {
|
|
106
|
+
if (c.outcome === 'passed' || !c.failure)
|
|
107
|
+
continue;
|
|
108
|
+
const ready = c.failure.message.match(/did not become ready at https?:\/\/[^/\s]+(\/\S*) within/);
|
|
109
|
+
if (c.failure.step === '(app startup)') {
|
|
110
|
+
open({ kind: 'setup', caseId: c.caseId, file: CONFIG_FILE, step: null, detail: c.failure.message,
|
|
111
|
+
fix: ready ? `make app.health a GET path that answers 2xx once the app is up (now ${ready[1]})` : 'the app exited or failed to start; run it by hand with the same env to see why' });
|
|
112
|
+
}
|
|
113
|
+
else if (c.outcome === 'assertion_failed') {
|
|
114
|
+
open({ kind: 'assertion', caseId: c.caseId, file: c.file, step: c.failure.step, detail: `${c.failure.message}${c.failure.observed ? `\n${c.failure.observed}` : ''}`, fix: 'decide whether the test drifted or the product regressed' });
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
open({ kind: c.outcome === 'infrastructure_failed' ? 'infrastructure' : 'setup', caseId: c.caseId, file: c.file, step: c.failure.step, detail: c.failure.message, fix: 'rerun; if it repeats, inspect the run folder' });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (report.bundleVerified === false)
|
|
121
|
+
open({ kind: 'evidence', caseId: null, file: null, step: null, detail: 'the evidence bundle failed verification', fix: `read ${relative(report.runDir, join(report.runDir, 'bundle-problems.txt'))} in the run folder; evidence is never repaired, only re-recorded by a fresh run` });
|
|
122
|
+
// One startup finding per distinct message is enough.
|
|
123
|
+
const seen = new Set();
|
|
124
|
+
return findings.filter((f) => { if (f.kind !== 'setup')
|
|
125
|
+
return true; const key = f.detail.replace(/127\.0\.0\.1:\d+/, 'HOST'); if (seen.has(key))
|
|
126
|
+
return false; seen.add(key); return true; });
|
|
127
|
+
}
|
|
128
|
+
function backup(ctx, abs) {
|
|
129
|
+
if (ctx.backups.has(abs))
|
|
130
|
+
return;
|
|
131
|
+
const text = readText(abs);
|
|
132
|
+
if (text !== null)
|
|
133
|
+
ctx.backups.set(abs, text);
|
|
134
|
+
}
|
|
135
|
+
function editConfig(ctx, set) {
|
|
136
|
+
const before = readFileSync(ctx.configPath, 'utf8');
|
|
137
|
+
const doc = parseDocument(before);
|
|
138
|
+
set(doc);
|
|
139
|
+
const after = doc.toString({ flowCollectionPadding: false });
|
|
140
|
+
writeFileSync(ctx.configPath, after);
|
|
141
|
+
return { before, after };
|
|
142
|
+
}
|
|
143
|
+
async function rerun(ctx, grep) {
|
|
144
|
+
try {
|
|
145
|
+
const { report } = await runTests({ configPath: ctx.configPath, ...(grep ? { grep } : {}), ...(ctx.signal ? { signal: ctx.signal } : {}) });
|
|
146
|
+
return { report, issues: [] };
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
if (error instanceof Blocked)
|
|
150
|
+
return { report: null, issues: error.reasons.map((r) => ({ code: r.code, detail: r.detail, action: r.action })) };
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function startupFailed(report, caseId) {
|
|
155
|
+
if (!report)
|
|
156
|
+
return true;
|
|
157
|
+
return report.cases.some((c) => (caseId === null || c.caseId === caseId) && c.failure?.step === '(app startup)');
|
|
158
|
+
}
|
|
159
|
+
function walkFiles(dir, accept, out = [], depth = 0) {
|
|
160
|
+
if (depth > 6 || !existsSync(dir))
|
|
161
|
+
return out;
|
|
162
|
+
for (const name of readdirSync(dir)) {
|
|
163
|
+
if (['node_modules', '.git', STATE_DIR, '.venv', 'venv', '__pycache__', 'dist', 'build'].includes(name))
|
|
164
|
+
continue;
|
|
165
|
+
const path = join(dir, name);
|
|
166
|
+
let st;
|
|
167
|
+
try {
|
|
168
|
+
st = statSync(path);
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (st.isDirectory())
|
|
174
|
+
walkFiles(path, accept, out, depth + 1);
|
|
175
|
+
else if (accept(name) && st.size < 512_000)
|
|
176
|
+
out.push(path);
|
|
177
|
+
}
|
|
178
|
+
return out;
|
|
179
|
+
}
|
|
180
|
+
/** Health paths the app plausibly serves: declared GET routes first, then string literals in its source. */
|
|
181
|
+
export function healthCandidates(root, config, current) {
|
|
182
|
+
const found = [];
|
|
183
|
+
const add = (path) => { if (path !== current && !found.includes(path))
|
|
184
|
+
found.push(path); };
|
|
185
|
+
try {
|
|
186
|
+
for (const r of discoverProject(root, null).routes)
|
|
187
|
+
if (r.method === 'GET' && HEALTH_WORDS.test(r.path))
|
|
188
|
+
add(r.path);
|
|
189
|
+
}
|
|
190
|
+
catch { /* discovery is a hint */ }
|
|
191
|
+
const appRoot = resolve(root, config.app.root);
|
|
192
|
+
for (const file of walkFiles(appRoot, (n) => /\.(?:m?js|cjs|ts|py|go|rb|java|kt|rs)$/.test(n))) {
|
|
193
|
+
const text = readText(file) ?? '';
|
|
194
|
+
for (const m of text.matchAll(/["'`](\/(?:api\/)?[a-z][\w/-]{0,30})["'`]/g))
|
|
195
|
+
if (HEALTH_WORDS.test(m[1]))
|
|
196
|
+
add(m[1]);
|
|
197
|
+
}
|
|
198
|
+
return found.slice(0, 4);
|
|
199
|
+
}
|
|
200
|
+
/** Interpreters worth trying for fastapi/django, in the order a developer would expect. */
|
|
201
|
+
export function pythonCandidates(root, config) {
|
|
202
|
+
const appRoot = resolve(root, config.app.root);
|
|
203
|
+
const local = [appRoot, root].flatMap((dir) => ['.venv', 'venv', 'env', '.env'].map((v) => join(dir, v, 'bin', 'python')));
|
|
204
|
+
const onPath = (process.env.PATH ?? '').split(delimiter).filter(Boolean).flatMap((dir) => ['python3', 'python'].map((n) => join(dir, n)));
|
|
205
|
+
const probe = config.app.adapter === 'django' ? 'import django' : 'import fastapi, uvicorn';
|
|
206
|
+
const seen = new Set();
|
|
207
|
+
return [...local, ...onPath].filter((p) => { if (seen.has(p) || !existsSync(p) || p === config.app.python)
|
|
208
|
+
return false; seen.add(p); return true; })
|
|
209
|
+
.filter((p) => spawnSync(p, ['-c', probe], { timeout: 10_000, stdio: 'ignore' }).status === 0)
|
|
210
|
+
.map((p) => (p.startsWith(`${root}/`) ? relative(resolve(root, config.app.root), p) || p : p))
|
|
211
|
+
.slice(0, 3);
|
|
212
|
+
}
|
|
213
|
+
async function fixHealth(ctx, finding, config, dryRun) {
|
|
214
|
+
const candidates = healthCandidates(ctx.root, config, config.app.health);
|
|
215
|
+
if (candidates.length === 0) {
|
|
216
|
+
finding.fix = `${finding.fix}. No health-like GET route was found in the app source; add one (e.g. GET /health returning 200) or set app.health`;
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
backup(ctx, ctx.configPath);
|
|
220
|
+
for (const path of candidates) {
|
|
221
|
+
ctx.progress('Trying a fix', `app.health: ${config.app.health} → ${path}`);
|
|
222
|
+
const { before, after } = editConfig(ctx, (doc) => doc.setIn(['app', 'health'], path));
|
|
223
|
+
const diff = lineDiff(before, after, CONFIG_FILE);
|
|
224
|
+
if (dryRun) {
|
|
225
|
+
writeFileSync(ctx.configPath, before);
|
|
226
|
+
ctx.changes.push({ file: CONFIG_FILE, reason: `health check ${config.app.health} never answered; ${path} is served by the app`, diff, status: 'reverted', by: 'tracegrove', note: 'dry run: not applied' });
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const { report } = await rerun(ctx, finding.caseId ?? undefined);
|
|
230
|
+
if (!startupFailed(report, finding.caseId)) {
|
|
231
|
+
ctx.changes.push({ file: CONFIG_FILE, reason: `health check ${config.app.health} never answered; the app is ready at ${path}`, diff, status: 'kept', by: 'tracegrove', note: 'verified: the app now starts' });
|
|
232
|
+
finding.status = 'fixed';
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
writeFileSync(ctx.configPath, before);
|
|
236
|
+
ctx.changes.push({ file: CONFIG_FILE, reason: `tried app.health ${path}`, diff, status: 'reverted', by: 'tracegrove', note: 'the app still did not become ready' });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
async function fixPython(ctx, finding, config, dryRun) {
|
|
240
|
+
if (config.app.adapter !== 'fastapi' && config.app.adapter !== 'django')
|
|
241
|
+
return;
|
|
242
|
+
ctx.progress('Looking for a Python interpreter', `with ${config.app.adapter === 'django' ? 'django' : 'fastapi and uvicorn'} installed`);
|
|
243
|
+
const candidates = pythonCandidates(ctx.root, config);
|
|
244
|
+
if (candidates.length === 0) {
|
|
245
|
+
finding.fix = `create a virtualenv with the app's dependencies (python3 -m venv .venv && .venv/bin/pip install -r requirements.txt), then rerun heal`;
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
backup(ctx, ctx.configPath);
|
|
249
|
+
for (const python of candidates) {
|
|
250
|
+
ctx.progress('Trying a fix', `app.python → ${python}`);
|
|
251
|
+
const { before, after } = editConfig(ctx, (doc) => doc.setIn(['app', 'python'], python));
|
|
252
|
+
const diff = lineDiff(before, after, CONFIG_FILE);
|
|
253
|
+
if (dryRun) {
|
|
254
|
+
writeFileSync(ctx.configPath, before);
|
|
255
|
+
ctx.changes.push({ file: CONFIG_FILE, reason: `${config.app.python ?? 'the default interpreter'} is unusable; ${python} imports the app's framework`, diff, status: 'reverted', by: 'tracegrove', note: 'dry run: not applied' });
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const check = checkProject(ctx.root);
|
|
259
|
+
if (!check.issues.some((i) => i.code === 'launch_executable_missing' || i.code === 'application_runtime_unrecognized')) {
|
|
260
|
+
ctx.changes.push({ file: CONFIG_FILE, reason: `${config.app.python ?? 'the default interpreter'} is unusable; ${python} imports the app's framework`, diff, status: 'kept', by: 'tracegrove', note: 'verified: the interpreter is recognized' });
|
|
261
|
+
finding.status = 'fixed';
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
writeFileSync(ctx.configPath, before);
|
|
265
|
+
ctx.changes.push({ file: CONFIG_FILE, reason: `tried app.python ${python}`, diff, status: 'reverted', by: 'tracegrove', note: 'still not recognized' });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function flowShape(text) {
|
|
269
|
+
try {
|
|
270
|
+
const doc = parseYaml(text);
|
|
271
|
+
if (!doc || !Array.isArray(doc.steps))
|
|
272
|
+
return null;
|
|
273
|
+
return { steps: doc.steps.map((s, i) => ({ key: typeof s.name === 'string' ? s.name : `#${i}`, expect: s.expect })) };
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function statusOf(expect) {
|
|
280
|
+
if (typeof expect === 'number')
|
|
281
|
+
return expect;
|
|
282
|
+
if (expect && typeof expect === 'object' && typeof expect.status === 'number')
|
|
283
|
+
return expect.status;
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
function expectWeight(expect) {
|
|
287
|
+
if (expect === undefined || expect === null)
|
|
288
|
+
return 0;
|
|
289
|
+
if (typeof expect === 'number')
|
|
290
|
+
return 1;
|
|
291
|
+
const e = expect;
|
|
292
|
+
return (e.status === undefined ? 0 : 1) + ['json', 'count'].reduce((n, k) => n + Object.keys(e[k] ?? {}).length, 0) + ['present', 'absent'].reduce((n, k) => n + (e[k]?.length ?? 0), 0) + (e.text === undefined ? 0 : 1);
|
|
293
|
+
}
|
|
294
|
+
/** Why an agent's edit to a flow would weaken it, or null when it keeps the test's strength. */
|
|
295
|
+
export function guardFlowEdit(before, after) {
|
|
296
|
+
const a = flowShape(before);
|
|
297
|
+
const b = flowShape(after);
|
|
298
|
+
if (!b)
|
|
299
|
+
return 'the edited flow no longer parses as a flow';
|
|
300
|
+
if (!a)
|
|
301
|
+
return null;
|
|
302
|
+
if (b.steps.length < a.steps.length)
|
|
303
|
+
return `steps were deleted (${a.steps.length} → ${b.steps.length})`;
|
|
304
|
+
const byKey = new Map(b.steps.map((s) => [s.key, s]));
|
|
305
|
+
for (const step of a.steps) {
|
|
306
|
+
const next = byKey.get(step.key);
|
|
307
|
+
if (!next)
|
|
308
|
+
return `step "${step.key}" was removed or renamed`;
|
|
309
|
+
if (expectWeight(next.expect) < expectWeight(step.expect))
|
|
310
|
+
return `step "${step.key}" checks less than before`;
|
|
311
|
+
const was = statusOf(step.expect);
|
|
312
|
+
const now = statusOf(next.expect);
|
|
313
|
+
if (was !== null && now !== null && (was < 400) !== (now < 400))
|
|
314
|
+
return `step "${step.key}" flipped from expecting ${was} to ${now}; that changes what the product is allowed to do`;
|
|
315
|
+
}
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
export function parseVerdicts(output) {
|
|
319
|
+
const line = output.split('\n').reverse().find((l) => l.includes(VERDICT_TAG));
|
|
320
|
+
if (!line)
|
|
321
|
+
return [];
|
|
322
|
+
try {
|
|
323
|
+
const parsed = JSON.parse(line.slice(line.indexOf('{')));
|
|
324
|
+
return (parsed.cases ?? []).filter((c) => typeof c.caseId === 'string' && ['test_drift', 'product_bug', 'unsure'].includes(String(c.verdict)))
|
|
325
|
+
.map((c) => ({ caseId: c.caseId, verdict: c.verdict, reason: typeof c.reason === 'string' ? c.reason.slice(0, 400) : '' }));
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
return [];
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
function editAdmission(output, knownCases) {
|
|
332
|
+
const last = output.trim().split('\n').at(-1) ?? '';
|
|
333
|
+
const reject = () => ({ verdicts: [], problem: 'a final, valid verdict with unique known case IDs is required' });
|
|
334
|
+
if (!last.startsWith(`${VERDICT_TAG} `))
|
|
335
|
+
return reject();
|
|
336
|
+
try {
|
|
337
|
+
const value = JSON.parse(last.slice(VERDICT_TAG.length + 1));
|
|
338
|
+
if (!Array.isArray(value.cases) || value.cases.length === 0)
|
|
339
|
+
return reject();
|
|
340
|
+
const seen = new Set();
|
|
341
|
+
const verdicts = [];
|
|
342
|
+
for (const entry of value.cases) {
|
|
343
|
+
if (!entry || typeof entry !== 'object')
|
|
344
|
+
return reject();
|
|
345
|
+
const candidate = entry;
|
|
346
|
+
if (typeof candidate.caseId !== 'string' || !knownCases.has(candidate.caseId) || seen.has(candidate.caseId)
|
|
347
|
+
|| !['test_drift', 'product_bug', 'unsure'].includes(String(candidate.verdict)) || typeof candidate.reason !== 'string')
|
|
348
|
+
return reject();
|
|
349
|
+
seen.add(candidate.caseId);
|
|
350
|
+
verdicts.push({ caseId: candidate.caseId, verdict: candidate.verdict, reason: candidate.reason.slice(0, 400) });
|
|
351
|
+
}
|
|
352
|
+
return { verdicts, problem: null };
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
return reject();
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
export function buildHealPrompt(input) {
|
|
359
|
+
const tg = (input.tracegrove ?? tracegroveCommand()).join(' ');
|
|
360
|
+
const cases = input.failing.map((c) => {
|
|
361
|
+
const text = readText(join(input.root, c.file)) ?? '(file missing)';
|
|
362
|
+
const steps = c.steps.map((s) => ` ${s.result.padEnd(9)} ${s.name}: ${s.method} ${s.path}${s.status !== null ? ` -> ${s.status}` : ''}`).join('\n');
|
|
363
|
+
return [`### ${c.caseId} (${join(input.root, c.file)})`, `Failed at "${c.failure?.step}": ${c.failure?.message}`, c.failure?.observed ? `Observed: ${c.failure.observed}` : '', 'Steps:', steps, 'Flow file:', '```yaml', text.trim(), '```'].filter(Boolean).join('\n');
|
|
364
|
+
});
|
|
365
|
+
const issues = input.issues.map((i) => `- ${i.code}: ${i.detail} (fix: ${i.action})`);
|
|
366
|
+
return [
|
|
367
|
+
`Project folder: ${input.root}. Work only inside it; every path below is absolute.`,
|
|
368
|
+
'You are repairing tracegrove behaviour tests. tracegrove drives the real app; a failing test means either the TEST drifted from intended behaviour, or the PRODUCT regressed.',
|
|
369
|
+
'Your job is to tell those apart, honestly. Hiding a product bug by editing the test is the worst possible outcome.',
|
|
370
|
+
'',
|
|
371
|
+
...(issues.length ? ['Authoring issues reported by `check` (fix these in the named flow files):', ...issues, ''] : []),
|
|
372
|
+
...(cases.length ? ['Failing cases:', ...cases, ''] : []),
|
|
373
|
+
'Rules:',
|
|
374
|
+
'1. Read the route handlers, recent changes (git log -p, git diff) and docs to learn the INTENDED behaviour before deciding.',
|
|
375
|
+
'2. test_drift: the app\'s current behaviour is clearly intended (renamed field, new status code for the same outcome, changed route, new required field) and the flow is out of date. Edit only that flow file to match. Keep every step and every check; you may update values, paths, request bodies or add checks.',
|
|
376
|
+
'3. product_bug: the app behaves wrongly (a denial now succeeds, data is lost, a 5xx, wrong data returned). Do NOT edit the flow. Explain the bug in one sentence.',
|
|
377
|
+
'4. unsure: you cannot tell. Do not edit.',
|
|
378
|
+
'5. Never create files, and never edit application code, the config, fixtures or flows not listed above. Never delete steps or expectations, never change an expected 2xx/3xx into a 4xx/5xx or back.',
|
|
379
|
+
`6. After editing, run \`${tg} check --json\` until it reports ok. Do not run the tests yourself; tracegrove re-runs them and keeps only edits that pass.`,
|
|
380
|
+
`7. Your LAST line must be exactly: ${VERDICT_TAG} {"cases":[{"caseId":"<id>","verdict":"test_drift|product_bug|unsure","reason":"<one sentence>"}]}`,
|
|
381
|
+
].join('\n');
|
|
382
|
+
}
|
|
383
|
+
function snapshot(files) {
|
|
384
|
+
const map = new Map();
|
|
385
|
+
for (const f of files) {
|
|
386
|
+
const t = readText(f);
|
|
387
|
+
if (t !== null)
|
|
388
|
+
map.set(f, t);
|
|
389
|
+
}
|
|
390
|
+
return map;
|
|
391
|
+
}
|
|
392
|
+
async function healWithAgent(ctx, input) {
|
|
393
|
+
const settings = resolveAgentSettings(ctx.root, input.config, input.overrides);
|
|
394
|
+
const testDir = resolve(ctx.root, input.config?.testDir ?? 'tracegrove');
|
|
395
|
+
const allowed = new Set([...input.failing.map((c) => join(ctx.root, c.file)), ...input.issues.map((i) => i.detail.match(/^(\S+\.(?:flow|fixture)\.yaml)/)?.[1]).filter((f) => !!f).map((f) => join(ctx.root, f))]);
|
|
396
|
+
const appRoot = resolve(ctx.root, input.config?.app.root ?? '.');
|
|
397
|
+
const watched = [...new Set([ctx.configPath, ...walkFiles(testDir, (n) => n.endsWith('.yaml')), ...walkFiles(appRoot, (n) => /\.(?:m?js|cjs|ts|tsx|py|json|ya?ml|html|css)$/.test(n))])];
|
|
398
|
+
const before = snapshot(watched);
|
|
399
|
+
const existed = new Set(walkFiles(ctx.root, () => true));
|
|
400
|
+
ctx.progress('Asking the agent', `${settings.provider}${settings.model ? ` · ${settings.model}` : ''}: ${input.failing.length} failing case(s), ${input.issues.length} authoring issue(s)`);
|
|
401
|
+
let result;
|
|
402
|
+
try {
|
|
403
|
+
result = await runAgent({ root: ctx.root, testDir, settings, prompt: buildHealPrompt({ root: ctx.root, failing: input.failing, issues: input.issues }), ...(input.onOutput ? { onOutput: input.onOutput } : {}), ...(input.onActivity ? { onActivity: input.onActivity } : {}), ...(ctx.signal ? { signal: ctx.signal } : {}) });
|
|
404
|
+
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
// Admission has not succeeded: restore watched edits even when the agent aborts or throws.
|
|
407
|
+
for (const [file, text] of before)
|
|
408
|
+
if (readText(file) !== text) {
|
|
409
|
+
ctx.changes.push({ file: relative(ctx.root, file), reason: 'agent failed before edit admission', diff: lineDiff(text, readText(file) ?? '', relative(ctx.root, file)), status: 'rejected', by: 'agent', note: 'restored' });
|
|
410
|
+
writeFileSync(file, text);
|
|
411
|
+
}
|
|
412
|
+
for (const file of walkFiles(ctx.root, () => true))
|
|
413
|
+
if (!existed.has(file))
|
|
414
|
+
rmSync(file, { force: true });
|
|
415
|
+
return { provider: settings.provider, model: settings.model, exitCode: ctx.signal?.aborted ? 4 : 3, verdicts: [], summary: `agent did not complete; edits restored: ${error.message}` };
|
|
416
|
+
}
|
|
417
|
+
const admission = editAdmission(result.output, new Set(input.failing.map(c => c.caseId)));
|
|
418
|
+
const verdicts = admission.verdicts;
|
|
419
|
+
try {
|
|
420
|
+
const verdictFor = new Map(verdicts.map((v) => [v.caseId, v]));
|
|
421
|
+
for (const f of input.findings) {
|
|
422
|
+
if (f.kind !== 'assertion' || !f.caseId)
|
|
423
|
+
continue;
|
|
424
|
+
const v = verdictFor.get(f.caseId);
|
|
425
|
+
if (v?.verdict === 'product_bug') {
|
|
426
|
+
f.kind = 'product_bug';
|
|
427
|
+
f.status = 'reported';
|
|
428
|
+
f.fix = v.reason || 'the app regressed; fix the product, not the test';
|
|
429
|
+
}
|
|
430
|
+
else if (v?.verdict === 'test_drift') {
|
|
431
|
+
f.kind = 'test_drift';
|
|
432
|
+
f.fix = v.reason || f.fix;
|
|
433
|
+
}
|
|
434
|
+
else if (v)
|
|
435
|
+
f.fix = `agent unsure: ${v.reason}`;
|
|
436
|
+
}
|
|
437
|
+
// Undo anything outside the allowed flow files, then guard what remains.
|
|
438
|
+
const candidates = [];
|
|
439
|
+
for (const [file, text] of before) {
|
|
440
|
+
const now = readText(file);
|
|
441
|
+
if (now === text)
|
|
442
|
+
continue;
|
|
443
|
+
const rel = relative(ctx.root, file);
|
|
444
|
+
if (!allowed.has(file)) {
|
|
445
|
+
writeFileSync(file, text);
|
|
446
|
+
ctx.changes.push({ file: rel, reason: 'the agent edited a file it was not allowed to touch', diff: lineDiff(text, now ?? '', rel), status: 'rejected', by: 'agent', note: 'restored' });
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
const caseId = input.failing.find((c) => join(ctx.root, c.file) === file)?.caseId;
|
|
450
|
+
const v = caseId ? verdictFor.get(caseId) : undefined;
|
|
451
|
+
const matching = verdicts.filter(value => value.caseId === caseId);
|
|
452
|
+
const guard = result.exitCode !== 0 || ctx.signal?.aborted ? 'agent did not complete successfully; edits are not admitted'
|
|
453
|
+
: admission.problem ? admission.problem
|
|
454
|
+
: !caseId || matching.length !== 1 ? 'exactly one matching case verdict is required before an edit can be admitted'
|
|
455
|
+
: v?.verdict !== 'test_drift' ? `the agent did not judge this case to be test drift`
|
|
456
|
+
: guardFlowEdit(text, now ?? '');
|
|
457
|
+
if (guard || input.dryRun) {
|
|
458
|
+
writeFileSync(file, text);
|
|
459
|
+
ctx.changes.push({ file: rel, reason: v?.reason || 'agent repair', diff: lineDiff(text, now ?? '', rel), status: guard ? 'rejected' : 'reverted', by: 'agent', note: guard ?? 'dry run: not applied' });
|
|
460
|
+
if (guard) {
|
|
461
|
+
const f = input.findings.find((x) => x.caseId === caseId);
|
|
462
|
+
if (f && f.kind === 'test_drift') {
|
|
463
|
+
f.kind = 'assertion';
|
|
464
|
+
f.fix = `agent edit rejected: ${guard}`;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
ctx.backups.set(file, ctx.backups.get(file) ?? text);
|
|
470
|
+
candidates.push(file);
|
|
471
|
+
}
|
|
472
|
+
// Files the agent created (scratch scripts, new flows) are outside the brief.
|
|
473
|
+
for (const file of walkFiles(ctx.root, () => true)) {
|
|
474
|
+
if (existed.has(file))
|
|
475
|
+
continue;
|
|
476
|
+
const rel = relative(ctx.root, file);
|
|
477
|
+
ctx.changes.push({ file: rel, reason: 'the agent created a new file during a repair', diff: lineDiff('', readText(file) ?? '', rel), status: 'rejected', by: 'agent', note: 'removed' });
|
|
478
|
+
rmSync(file, { force: true });
|
|
479
|
+
}
|
|
480
|
+
if (candidates.length) {
|
|
481
|
+
ctx.progress('Re-running to prove the repairs', candidates.map((f) => relative(ctx.root, f)).join(', '));
|
|
482
|
+
const check = checkProject(ctx.root);
|
|
483
|
+
const { report } = check.ok ? await rerun(ctx) : { report: null };
|
|
484
|
+
for (const file of candidates) {
|
|
485
|
+
const rel = relative(ctx.root, file);
|
|
486
|
+
const original = before.get(file);
|
|
487
|
+
const edited = readText(file) ?? '';
|
|
488
|
+
const caseId = input.failing.find((c) => join(ctx.root, c.file) === file)?.caseId;
|
|
489
|
+
const passes = check.ok && (caseId ? report?.cases.find((c) => c.caseId === caseId)?.outcome === 'passed' : true);
|
|
490
|
+
const finding = input.findings.find((f) => (caseId ? f.caseId === caseId : f.file === rel));
|
|
491
|
+
if (passes) {
|
|
492
|
+
ctx.changes.push({ file: rel, reason: (caseId && verdictFor.get(caseId)?.reason) || 'repaired authoring issue', diff: lineDiff(original, edited, rel), status: 'kept', by: 'agent', note: caseId ? 'verified: the case now passes' : 'verified: check is clean' });
|
|
493
|
+
if (finding) {
|
|
494
|
+
finding.status = 'fixed';
|
|
495
|
+
if (finding.kind === 'assertion')
|
|
496
|
+
finding.kind = 'test_drift';
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
writeFileSync(file, original);
|
|
501
|
+
ctx.backups.delete(file);
|
|
502
|
+
ctx.changes.push({ file: rel, reason: (caseId && verdictFor.get(caseId)?.reason) || 'agent repair', diff: lineDiff(original, edited, rel), status: 'reverted', by: 'agent', note: check.ok ? 'the case still fails with this edit' : 'the edit does not compile' });
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return { provider: result.provider, model: result.model, exitCode: result.exitCode, verdicts, summary: result.output.split('\n').filter((l) => !l.includes(VERDICT_TAG)).join('\n').trim().slice(-2000) };
|
|
507
|
+
}
|
|
508
|
+
finally {
|
|
509
|
+
// A verification exception cannot leave an unretained candidate edit behind.
|
|
510
|
+
for (const [file, text] of before) {
|
|
511
|
+
const rel = relative(ctx.root, file);
|
|
512
|
+
if (ctx.changes.some(change => change.file === rel && change.status === 'kept') || readText(file) === text)
|
|
513
|
+
continue;
|
|
514
|
+
const edited = readText(file) ?? '';
|
|
515
|
+
writeFileSync(file, text);
|
|
516
|
+
ctx.backups.delete(file);
|
|
517
|
+
ctx.changes.push({ file: rel, reason: 'edit was not retained by verification', diff: lineDiff(text, edited, rel), status: 'reverted', by: 'agent', note: 'restored' });
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
function snap(report, issues) {
|
|
522
|
+
return report ? { outcome: report.outcome, exitCode: report.exitCode, runDir: report.runDir, totals: report.totals, issues: issues.length }
|
|
523
|
+
: { outcome: issues.length ? 'blocked' : 'not_run', exitCode: 2, runDir: null, totals: null, issues: issues.length };
|
|
524
|
+
}
|
|
525
|
+
/** Diagnose, repair what is safe, verify, and write .tracegrove/heals/<id>/report.json. */
|
|
526
|
+
export async function heal(options) {
|
|
527
|
+
const release = acquireMutation(resolve(options.dir));
|
|
528
|
+
try {
|
|
529
|
+
return await healOwned(options);
|
|
530
|
+
}
|
|
531
|
+
finally {
|
|
532
|
+
release();
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
async function healOwned(options) {
|
|
536
|
+
const root = resolve(options.dir);
|
|
537
|
+
const configPath = join(root, CONFIG_FILE);
|
|
538
|
+
if (!existsSync(configPath))
|
|
539
|
+
throw new Blocked([{ code: 'config_not_found', detail: configPath, owner: 'operator', action: 'run tracegrove init first' }]);
|
|
540
|
+
const startedAt = new Date();
|
|
541
|
+
const id = `${stamp(startedAt)}_${randomUUID()}`;
|
|
542
|
+
const healDir = join(root, STATE_DIR, HEALS_DIR, id);
|
|
543
|
+
const progress = options.onProgress ?? (() => { });
|
|
544
|
+
const ctx = { root, configPath, healDir, changes: [], backups: new Map(), progress, signal: options.signal };
|
|
545
|
+
const dryRun = options.dryRun === true;
|
|
546
|
+
progress('Checking flows and config');
|
|
547
|
+
let issues = checkProject(root).issues;
|
|
548
|
+
let report = options.report ?? null;
|
|
549
|
+
if (!report && issues.length === 0) {
|
|
550
|
+
progress('Running the suite to see what fails');
|
|
551
|
+
({ report, issues } = await rerun(ctx));
|
|
552
|
+
}
|
|
553
|
+
const before = snap(report, issues);
|
|
554
|
+
const findings = classify(report, issues);
|
|
555
|
+
let config = null;
|
|
556
|
+
try {
|
|
557
|
+
config = loadAuthoringConfig(configPath);
|
|
558
|
+
}
|
|
559
|
+
catch {
|
|
560
|
+
config = null;
|
|
561
|
+
}
|
|
562
|
+
if (config) {
|
|
563
|
+
for (const f of findings.filter((x) => x.kind === 'setup' && x.status === 'open')) {
|
|
564
|
+
if (/did not become ready at/.test(f.detail))
|
|
565
|
+
await fixHealth(ctx, f, config, dryRun);
|
|
566
|
+
else if (/^(launch_executable_missing|application_runtime_unrecognized)/.test(f.detail))
|
|
567
|
+
await fixPython(ctx, f, config, dryRun);
|
|
568
|
+
try {
|
|
569
|
+
config = loadAuthoringConfig(configPath);
|
|
570
|
+
}
|
|
571
|
+
catch { /* keep previous */ }
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
let after = before;
|
|
575
|
+
// Once setup is repaired the real failures can show; look again before calling the agent.
|
|
576
|
+
if (ctx.changes.some((c) => c.status === 'kept')) {
|
|
577
|
+
progress('Re-running after setup fixes');
|
|
578
|
+
issues = checkProject(root).issues;
|
|
579
|
+
if (issues.length === 0)
|
|
580
|
+
({ report, issues } = await rerun(ctx));
|
|
581
|
+
const fixed = findings.filter((f) => f.status === 'fixed');
|
|
582
|
+
findings.splice(0, findings.length, ...fixed, ...classify(report, issues).filter((f) => !fixed.some((x) => x.detail === f.detail)));
|
|
583
|
+
after = snap(report, issues);
|
|
584
|
+
}
|
|
585
|
+
let agent = null;
|
|
586
|
+
const failing = (report?.cases ?? []).filter((c) => c.outcome === 'assertion_failed');
|
|
587
|
+
const authoring = issues.filter((i) => !['launch_executable_missing', 'application_runtime_unrecognized'].includes(i.code));
|
|
588
|
+
const settings = resolveAgentSettings(root, config, options.overrides ?? {});
|
|
589
|
+
const installed = settings.provider !== 'none' && !!detectAgents()[settings.provider];
|
|
590
|
+
if (options.agent !== false && (failing.length || authoring.length)) {
|
|
591
|
+
if (installed)
|
|
592
|
+
agent = await healWithAgent(ctx, { failing, issues: authoring, config, overrides: options.overrides ?? {}, onOutput: options.onAgentOutput, onActivity: options.onAgentActivity, findings, dryRun });
|
|
593
|
+
else
|
|
594
|
+
for (const f of findings.filter((x) => x.kind === 'assertion'))
|
|
595
|
+
f.fix = `${f.fix}: ${settings.provider === 'none' ? 'enable an agent in tracegrove settings' : `${settings.provider} is not installed`}, or read the failure and edit ${f.file}`;
|
|
596
|
+
}
|
|
597
|
+
const kept = ctx.changes.filter((c) => c.status === 'kept');
|
|
598
|
+
if (kept.some((c) => c.by === 'agent') && !dryRun) {
|
|
599
|
+
progress('Final verification run');
|
|
600
|
+
issues = checkProject(root).issues;
|
|
601
|
+
if (issues.length === 0)
|
|
602
|
+
({ report, issues } = await rerun(ctx));
|
|
603
|
+
after = snap(report, issues);
|
|
604
|
+
}
|
|
605
|
+
const bugs = findings.filter((f) => f.kind === 'product_bug');
|
|
606
|
+
const openOnes = findings.filter((f) => f.status === 'open');
|
|
607
|
+
const outcome = options.signal?.aborted ? 'cancelled' : findings.length === 0 ? 'healthy'
|
|
608
|
+
: bugs.length ? 'product_bugs'
|
|
609
|
+
: openOnes.length === 0 ? 'healed'
|
|
610
|
+
: kept.length ? 'partly_healed' : 'needs_human';
|
|
611
|
+
const next = [];
|
|
612
|
+
if (kept.length)
|
|
613
|
+
next.push({ command: `tracegrove heal --undo ${id}`, why: 'put every kept change back the way it was' });
|
|
614
|
+
if (bugs.length)
|
|
615
|
+
next.push({ command: 'tracegrove show --run latest', why: `${bugs.length} product bug(s) to fix in the app; the tests stay red until then` });
|
|
616
|
+
if (openOnes.some((f) => f.kind === 'assertion') && !installed)
|
|
617
|
+
next.push({ command: 'tracegrove settings --agent claude', why: 'let a coding agent tell drift from regressions' });
|
|
618
|
+
if (outcome === 'healed' || outcome === 'healthy')
|
|
619
|
+
next.push({ command: 'tracegrove test', why: 'everything is green; run it in CI' });
|
|
620
|
+
if (dryRun && ctx.changes.length)
|
|
621
|
+
next.push({ command: 'tracegrove heal', why: 'apply the proposed fixes for real' });
|
|
622
|
+
mkdirSync(healDir, { recursive: true });
|
|
623
|
+
const backupDir = join(healDir, 'backup');
|
|
624
|
+
for (const [abs, text] of ctx.backups) {
|
|
625
|
+
const rel = relative(root, abs);
|
|
626
|
+
if (!kept.some((c) => c.file === rel))
|
|
627
|
+
continue;
|
|
628
|
+
mkdirSync(dirname(join(backupDir, rel)), { recursive: true });
|
|
629
|
+
writeFileSync(join(backupDir, rel), text);
|
|
630
|
+
}
|
|
631
|
+
const result = {
|
|
632
|
+
schemaVersion: 'tracegrove.heal/v1', id, root, startedAt: startedAt.toISOString(), finishedAt: new Date().toISOString(),
|
|
633
|
+
outcome, exitCode: outcome === 'cancelled' ? 4 : outcome === 'healthy' || outcome === 'healed' ? 0 : bugs.length ? 1 : 2,
|
|
634
|
+
before, after, findings, changes: ctx.changes, agent, next, reportPath: join(healDir, 'report.json'),
|
|
635
|
+
fileBindings: [...ctx.backups].filter(([abs]) => kept.some(change => change.file === relative(root, abs)))
|
|
636
|
+
.map(([abs, original]) => ({ file: relative(root, abs), beforeSha256: sha(original), afterSha256: sha(readFileSync(abs, 'utf8')) })),
|
|
637
|
+
};
|
|
638
|
+
writeFileSync(result.reportPath, `${JSON.stringify(result, null, 2)}\n`);
|
|
639
|
+
progress('Done', outcome);
|
|
640
|
+
return result;
|
|
641
|
+
}
|
|
642
|
+
export function listHeals(root) {
|
|
643
|
+
const dir = join(resolve(root), STATE_DIR, HEALS_DIR);
|
|
644
|
+
if (!existsSync(dir))
|
|
645
|
+
return [];
|
|
646
|
+
return readdirSync(dir).flatMap((name) => {
|
|
647
|
+
try {
|
|
648
|
+
return [JSON.parse(readFileSync(join(dir, name, 'report.json'), 'utf8'))];
|
|
649
|
+
}
|
|
650
|
+
catch {
|
|
651
|
+
return [];
|
|
652
|
+
}
|
|
653
|
+
}).sort((a, b) => Date.parse(b.startedAt) - Date.parse(a.startedAt));
|
|
654
|
+
}
|
|
655
|
+
/** Restore every file a heal kept a change to. Refuses when the file changed again since. */
|
|
656
|
+
export function undoHeal(dir, id) {
|
|
657
|
+
const release = acquireMutation(resolve(dir));
|
|
658
|
+
try {
|
|
659
|
+
return undoHealOwned(dir, id);
|
|
660
|
+
}
|
|
661
|
+
finally {
|
|
662
|
+
release();
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
function undoHealOwned(dir, id) {
|
|
666
|
+
const root = resolve(dir);
|
|
667
|
+
if (id !== 'latest' && !/^[A-Za-z0-9_.:-]+$/.test(id))
|
|
668
|
+
throw new Blocked([{ code: 'heal_not_found', detail: id }]);
|
|
669
|
+
const healDir = join(root, STATE_DIR, HEALS_DIR, id === 'latest' ? (listHeals(root)[0]?.id ?? '') : id);
|
|
670
|
+
const path = join(healDir, 'report.json');
|
|
671
|
+
if (!existsSync(path))
|
|
672
|
+
throw new Blocked([{ code: 'heal_not_found', detail: id, owner: 'operator', action: 'run tracegrove heal --json to list heal ids, or pass --undo latest' }]);
|
|
673
|
+
const report = JSON.parse(readFileSync(path, 'utf8'));
|
|
674
|
+
const restored = [];
|
|
675
|
+
const skipped = [];
|
|
676
|
+
const files = [...new Set(report.changes.filter((c) => c.status === 'kept').map((c) => c.file))];
|
|
677
|
+
for (const file of files) {
|
|
678
|
+
const target = resolve(root, file);
|
|
679
|
+
if (!target.startsWith(root + sep)) {
|
|
680
|
+
skipped.push({ file, why: 'file is outside the project' });
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
const bindings = report.fileBindings?.filter(binding => binding.file === file) ?? [];
|
|
684
|
+
if (bindings.length !== 1) {
|
|
685
|
+
skipped.push({ file, why: 'report has no unique complete file binding; undo by hand' });
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
const binding = bindings[0];
|
|
689
|
+
const saved = readText(join(healDir, 'backup', file));
|
|
690
|
+
if (saved === null) {
|
|
691
|
+
skipped.push({ file, why: 'no backup was recorded' });
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
const current = readText(target);
|
|
695
|
+
if (sha(saved) !== binding.beforeSha256 || current === null || sha(current) !== binding.afterSha256) {
|
|
696
|
+
skipped.push({ file, why: 'complete file or backup differs from the recorded heal; undo by hand' });
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
requireRealDirectory(dirname(target));
|
|
700
|
+
if (!lstatSync(target).isFile() || lstatSync(target).isSymbolicLink()) {
|
|
701
|
+
skipped.push({ file, why: 'target is not a regular file' });
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
const temporary = join(dirname(target), `.tracegrove-undo-${randomUUID()}`);
|
|
705
|
+
try {
|
|
706
|
+
writeExclusiveDurable(temporary, Buffer.from(saved));
|
|
707
|
+
// Cooperating heals/undos hold the project lock; arbitrary editors must leave this tree quiescent.
|
|
708
|
+
if (sha(readFileSync(target, 'utf8')) !== binding.afterSha256) {
|
|
709
|
+
skipped.push({ file, why: 'file changed during undo; undo by hand' });
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
renameSync(temporary, target);
|
|
713
|
+
syncDirectory(dirname(target));
|
|
714
|
+
}
|
|
715
|
+
finally {
|
|
716
|
+
rmSync(temporary, { force: true });
|
|
717
|
+
}
|
|
718
|
+
restored.push(file);
|
|
719
|
+
}
|
|
720
|
+
writeFileSync(join(healDir, 'undone.json'), `${JSON.stringify({ at: new Date().toISOString(), restored, skipped, digest: sha(restored.join('\n')) }, null, 2)}\n`);
|
|
721
|
+
return { id: report.id, restored, skipped };
|
|
722
|
+
}
|
|
723
|
+
//# sourceMappingURL=heal.js.map
|