@cleocode/core 2026.10.27 → 2026.11.1-canary.3
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/README.md +2 -0
- package/binaries/README.md +30 -10
- package/dist/.source-stamp.json +4 -0
- package/dist/audit/reconstruct.d.ts.map +1 -1
- package/dist/audit/reconstruct.js +3 -13
- package/dist/audit/reconstruct.js.map +1 -1
- package/dist/build-provenance.json +2 -2
- package/dist/cloud/nexus-cloud.d.ts.map +1 -1
- package/dist/cloud/nexus-cloud.js +3 -3
- package/dist/cloud/nexus-cloud.js.map +1 -1
- package/dist/cloud/nexus-device.d.ts +1 -1
- package/dist/cloud/nexus-link.d.ts +18 -2
- package/dist/cloud/nexus-link.d.ts.map +1 -1
- package/dist/cloud/nexus-link.js +26 -6
- package/dist/cloud/nexus-link.js.map +1 -1
- package/dist/cloud/nexus-vault.d.ts.map +1 -1
- package/dist/cloud/nexus-vault.js +3 -9
- package/dist/cloud/nexus-vault.js.map +1 -1
- package/dist/decide/sites/registry.d.ts +40 -0
- package/dist/decide/sites/registry.d.ts.map +1 -1
- package/dist/decide/sites/registry.js +38 -0
- package/dist/decide/sites/registry.js.map +1 -1
- package/dist/docs/adr-allocator.d.ts +3 -3
- package/dist/docs/adr-allocator.d.ts.map +1 -1
- package/dist/docs/adr-allocator.js +8 -5
- package/dist/docs/adr-allocator.js.map +1 -1
- package/dist/docs/blob-consolidation.d.ts.map +1 -1
- package/dist/docs/blob-consolidation.js +3 -2
- package/dist/docs/blob-consolidation.js.map +1 -1
- package/dist/docs/derive-links.d.ts +12 -0
- package/dist/docs/derive-links.d.ts.map +1 -1
- package/dist/docs/derive-links.js +19 -0
- package/dist/docs/derive-links.js.map +1 -1
- package/dist/docs/doc-name-rule.d.ts +105 -0
- package/dist/docs/doc-name-rule.d.ts.map +1 -0
- package/dist/docs/doc-name-rule.js +212 -0
- package/dist/docs/doc-name-rule.js.map +1 -0
- package/dist/docs/docs-inconsistency-detector.d.ts.map +1 -1
- package/dist/docs/docs-inconsistency-detector.js +4 -3
- package/dist/docs/docs-inconsistency-detector.js.map +1 -1
- package/dist/docs/docs-ops.d.ts +27 -5
- package/dist/docs/docs-ops.d.ts.map +1 -1
- package/dist/docs/docs-ops.js +149 -17
- package/dist/docs/docs-ops.js.map +1 -1
- package/dist/docs/docs-read-model.d.ts +45 -15
- package/dist/docs/docs-read-model.d.ts.map +1 -1
- package/dist/docs/docs-read-model.js +22 -102
- package/dist/docs/docs-read-model.js.map +1 -1
- package/dist/docs/docs-update.d.ts.map +1 -1
- package/dist/docs/docs-update.js +8 -2
- package/dist/docs/docs-update.js.map +1 -1
- package/dist/docs/doctor.d.ts +71 -4
- package/dist/docs/doctor.d.ts.map +1 -1
- package/dist/docs/doctor.js +324 -23
- package/dist/docs/doctor.js.map +1 -1
- package/dist/docs/duplicate-check.d.ts +138 -0
- package/dist/docs/duplicate-check.d.ts.map +1 -0
- package/dist/docs/duplicate-check.js +286 -0
- package/dist/docs/duplicate-check.js.map +1 -0
- package/dist/docs/fetch-result.d.ts.map +1 -1
- package/dist/docs/fetch-result.js +27 -0
- package/dist/docs/fetch-result.js.map +1 -1
- package/dist/docs/import/scanner.d.ts +2 -0
- package/dist/docs/import/scanner.d.ts.map +1 -1
- package/dist/docs/import/scanner.js +3 -1
- package/dist/docs/import/scanner.js.map +1 -1
- package/dist/docs/lineage.d.ts +66 -0
- package/dist/docs/lineage.d.ts.map +1 -0
- package/dist/docs/lineage.js +190 -0
- package/dist/docs/lineage.js.map +1 -0
- package/dist/docs/modernize/apply.d.ts +139 -0
- package/dist/docs/modernize/apply.d.ts.map +1 -0
- package/dist/docs/modernize/apply.js +760 -0
- package/dist/docs/modernize/apply.js.map +1 -0
- package/dist/docs/modernize/guard.d.ts +92 -0
- package/dist/docs/modernize/guard.d.ts.map +1 -0
- package/dist/docs/modernize/guard.js +214 -0
- package/dist/docs/modernize/guard.js.map +1 -0
- package/dist/docs/modernize/index.d.ts +68 -0
- package/dist/docs/modernize/index.d.ts.map +1 -0
- package/dist/docs/modernize/index.js +97 -0
- package/dist/docs/modernize/index.js.map +1 -0
- package/dist/docs/modernize/judge.d.ts +85 -0
- package/dist/docs/modernize/judge.d.ts.map +1 -0
- package/dist/docs/modernize/judge.js +186 -0
- package/dist/docs/modernize/judge.js.map +1 -0
- package/dist/docs/modernize/plan.d.ts +77 -0
- package/dist/docs/modernize/plan.d.ts.map +1 -0
- package/dist/docs/modernize/plan.js +510 -0
- package/dist/docs/modernize/plan.js.map +1 -0
- package/dist/docs/modernize/restore.d.ts +41 -0
- package/dist/docs/modernize/restore.d.ts.map +1 -0
- package/dist/docs/modernize/restore.js +103 -0
- package/dist/docs/modernize/restore.js.map +1 -0
- package/dist/docs/modernize/rules.d.ts +197 -0
- package/dist/docs/modernize/rules.d.ts.map +1 -0
- package/dist/docs/modernize/rules.js +510 -0
- package/dist/docs/modernize/rules.js.map +1 -0
- package/dist/docs/modernize/store.d.ts +64 -0
- package/dist/docs/modernize/store.d.ts.map +1 -0
- package/dist/docs/modernize/store.js +390 -0
- package/dist/docs/modernize/store.js.map +1 -0
- package/dist/docs/publications-store.d.ts +16 -2
- package/dist/docs/publications-store.d.ts.map +1 -1
- package/dist/docs/publications-store.js +23 -2
- package/dist/docs/publications-store.js.map +1 -1
- package/dist/docs/publish-pr.d.ts.map +1 -1
- package/dist/docs/publish-pr.js +12 -1
- package/dist/docs/publish-pr.js.map +1 -1
- package/dist/docs/rot.d.ts +94 -0
- package/dist/docs/rot.d.ts.map +1 -0
- package/dist/docs/rot.js +298 -0
- package/dist/docs/rot.js.map +1 -0
- package/dist/docs/similarity-check.d.ts +2 -0
- package/dist/docs/similarity-check.d.ts.map +1 -1
- package/dist/docs/similarity-check.js +6 -49
- package/dist/docs/similarity-check.js.map +1 -1
- package/dist/docs/slug-rename.d.ts +84 -0
- package/dist/docs/slug-rename.d.ts.map +1 -0
- package/dist/docs/slug-rename.js +166 -0
- package/dist/docs/slug-rename.js.map +1 -0
- package/dist/docs/slug-similarity.d.ts +24 -0
- package/dist/docs/slug-similarity.d.ts.map +1 -0
- package/dist/docs/slug-similarity.js +63 -0
- package/dist/docs/slug-similarity.js.map +1 -0
- package/dist/docs/wikilinks.d.ts +20 -4
- package/dist/docs/wikilinks.d.ts.map +1 -1
- package/dist/docs/wikilinks.js +52 -7
- package/dist/docs/wikilinks.js.map +1 -1
- package/dist/docs/writer-registry.d.ts.map +1 -1
- package/dist/docs/writer-registry.js +33 -0
- package/dist/docs/writer-registry.js.map +1 -1
- package/dist/doctor/type-matrix-repair.d.ts +194 -0
- package/dist/doctor/type-matrix-repair.d.ts.map +1 -0
- package/dist/doctor/type-matrix-repair.js +662 -0
- package/dist/doctor/type-matrix-repair.js.map +1 -0
- package/dist/gateway-client/generated/index.d.ts +2 -2
- package/dist/gateway-client/generated/index.d.ts.map +1 -1
- package/dist/gateway-client/generated/index.js +1 -1
- package/dist/gateway-client/generated/index.js.map +1 -1
- package/dist/gateway-client/generated/namespaces.gen.d.ts +5 -0
- package/dist/gateway-client/generated/namespaces.gen.d.ts.map +1 -1
- package/dist/gateway-client/generated/namespaces.gen.js +6 -1
- package/dist/gateway-client/generated/namespaces.gen.js.map +1 -1
- package/dist/gateway-client/generated/sdk.gen.d.ts +21 -1
- package/dist/gateway-client/generated/sdk.gen.d.ts.map +1 -1
- package/dist/gateway-client/generated/sdk.gen.js +55 -0
- package/dist/gateway-client/generated/sdk.gen.js.map +1 -1
- package/dist/gateway-client/generated/types.gen.d.ts +222 -0
- package/dist/gateway-client/generated/types.gen.d.ts.map +1 -1
- package/dist/gc/gc-subsystem.d.ts +4 -0
- package/dist/gc/gc-subsystem.d.ts.map +1 -1
- package/dist/gc/gc-subsystem.js +16 -0
- package/dist/gc/gc-subsystem.js.map +1 -1
- package/dist/git/work-tree.d.ts +48 -0
- package/dist/git/work-tree.d.ts.map +1 -1
- package/dist/git/work-tree.js +63 -1
- package/dist/git/work-tree.js.map +1 -1
- package/dist/hooks/handlers/session-hooks.d.ts +5 -4
- package/dist/hooks/handlers/session-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/session-hooks.js +5 -21
- package/dist/hooks/handlers/session-hooks.js.map +1 -1
- package/dist/internal.d.ts +5 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +5 -1
- package/dist/internal.js.map +1 -1
- package/dist/memory/brain-doctor.d.ts.map +1 -1
- package/dist/memory/brain-doctor.js +6 -1
- package/dist/memory/brain-doctor.js.map +1 -1
- package/dist/memory/engine-compat.d.ts.map +1 -1
- package/dist/memory/engine-compat.js +6 -0
- package/dist/memory/engine-compat.js.map +1 -1
- package/dist/nexus/analyze-orchestrator.d.ts +24 -0
- package/dist/nexus/analyze-orchestrator.d.ts.map +1 -1
- package/dist/nexus/analyze-orchestrator.js +43 -5
- package/dist/nexus/analyze-orchestrator.js.map +1 -1
- package/dist/nexus/user-profile.d.ts +2 -1
- package/dist/nexus/user-profile.d.ts.map +1 -1
- package/dist/nexus/user-profile.js +13 -5
- package/dist/nexus/user-profile.js.map +1 -1
- package/dist/orchestrate/admission.d.ts +8 -0
- package/dist/orchestrate/admission.d.ts.map +1 -1
- package/dist/orchestrate/admission.js +3 -1
- package/dist/orchestrate/admission.js.map +1 -1
- package/dist/orchestrate/query-ops.d.ts.map +1 -1
- package/dist/orchestrate/query-ops.js +15 -4
- package/dist/orchestrate/query-ops.js.map +1 -1
- package/dist/orchestrate/spawn-ops.d.ts.map +1 -1
- package/dist/orchestrate/spawn-ops.js +43 -2
- package/dist/orchestrate/spawn-ops.js.map +1 -1
- package/dist/orchestration/index.d.ts +6 -0
- package/dist/orchestration/index.d.ts.map +1 -1
- package/dist/orchestration/index.js +3 -0
- package/dist/orchestration/index.js.map +1 -1
- package/dist/orchestration/spawn-prompt.d.ts.map +1 -1
- package/dist/orchestration/spawn-prompt.js +3 -1
- package/dist/orchestration/spawn-prompt.js.map +1 -1
- package/dist/paths.d.ts +43 -1
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +51 -1
- package/dist/paths.js.map +1 -1
- package/dist/project-lifecycle.d.ts.map +1 -1
- package/dist/project-lifecycle.js +31 -0
- package/dist/project-lifecycle.js.map +1 -1
- package/dist/release/authorization.d.ts +156 -0
- package/dist/release/authorization.d.ts.map +1 -0
- package/dist/release/authorization.js +460 -0
- package/dist/release/authorization.js.map +1 -0
- package/dist/release/ci-evidence.d.ts +55 -4
- package/dist/release/ci-evidence.d.ts.map +1 -1
- package/dist/release/ci-evidence.js +251 -22
- package/dist/release/ci-evidence.js.map +1 -1
- package/dist/release/ci-run-evidence.d.ts +107 -0
- package/dist/release/ci-run-evidence.d.ts.map +1 -0
- package/dist/release/ci-run-evidence.js +234 -0
- package/dist/release/ci-run-evidence.js.map +1 -0
- package/dist/release/cut.d.ts +231 -0
- package/dist/release/cut.d.ts.map +1 -0
- package/dist/release/cut.js +775 -0
- package/dist/release/cut.js.map +1 -0
- package/dist/release/deploy-evidence.d.ts +173 -0
- package/dist/release/deploy-evidence.d.ts.map +1 -0
- package/dist/release/deploy-evidence.js +381 -0
- package/dist/release/deploy-evidence.js.map +1 -0
- package/dist/release/engine-ops.d.ts.map +1 -1
- package/dist/release/engine-ops.js +10 -4
- package/dist/release/engine-ops.js.map +1 -1
- package/dist/release/index.d.ts +3 -0
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js +2 -0
- package/dist/release/index.js.map +1 -1
- package/dist/release/invariants/archive-reason-invariant.d.ts.map +1 -1
- package/dist/release/invariants/archive-reason-invariant.js +4 -1
- package/dist/release/invariants/archive-reason-invariant.js.map +1 -1
- package/dist/release/open.d.ts.map +1 -1
- package/dist/release/open.js +2 -6
- package/dist/release/open.js.map +1 -1
- package/dist/release/plan.d.ts +34 -2
- package/dist/release/plan.d.ts.map +1 -1
- package/dist/release/plan.js +30 -6
- package/dist/release/plan.js.map +1 -1
- package/dist/release/preflight-skip.d.ts +4 -19
- package/dist/release/preflight-skip.d.ts.map +1 -1
- package/dist/release/preflight-skip.js +5 -110
- package/dist/release/preflight-skip.js.map +1 -1
- package/dist/release/reconcile-latest.d.ts +29 -0
- package/dist/release/reconcile-latest.d.ts.map +1 -0
- package/dist/release/reconcile-latest.js +71 -0
- package/dist/release/reconcile-latest.js.map +1 -0
- package/dist/release/reconcile.d.ts +3 -1
- package/dist/release/reconcile.d.ts.map +1 -1
- package/dist/release/reconcile.js +18 -6
- package/dist/release/reconcile.js.map +1 -1
- package/dist/release/release-published.d.ts +27 -0
- package/dist/release/release-published.d.ts.map +1 -0
- package/dist/release/release-published.js +35 -0
- package/dist/release/release-published.js.map +1 -0
- package/dist/release/tty-confirm.d.ts +23 -0
- package/dist/release/tty-confirm.d.ts.map +1 -0
- package/dist/release/tty-confirm.js +50 -0
- package/dist/release/tty-confirm.js.map +1 -0
- package/dist/remote/index.js +1 -1
- package/dist/remote/index.js.map +1 -1
- package/dist/resources/admission-ledger.d.ts +103 -1
- package/dist/resources/admission-ledger.d.ts.map +1 -1
- package/dist/resources/admission-ledger.js +200 -17
- package/dist/resources/admission-ledger.js.map +1 -1
- package/dist/resources/agent-lease.d.ts +82 -0
- package/dist/resources/agent-lease.d.ts.map +1 -0
- package/dist/resources/agent-lease.js +167 -0
- package/dist/resources/agent-lease.js.map +1 -0
- package/dist/resources/governor.d.ts +27 -1
- package/dist/resources/governor.d.ts.map +1 -1
- package/dist/resources/governor.js +88 -4
- package/dist/resources/governor.js.map +1 -1
- package/dist/resources/hook-footprint.d.ts +13 -1
- package/dist/resources/hook-footprint.d.ts.map +1 -1
- package/dist/resources/hook-footprint.js +33 -7
- package/dist/resources/hook-footprint.js.map +1 -1
- package/dist/resources/run-governed.d.ts +25 -0
- package/dist/resources/run-governed.d.ts.map +1 -1
- package/dist/resources/run-governed.js +44 -3
- package/dist/resources/run-governed.js.map +1 -1
- package/dist/sagas/reconcile.d.ts.map +1 -1
- package/dist/sagas/reconcile.js +11 -0
- package/dist/sagas/reconcile.js.map +1 -1
- package/dist/scaffold/ensure-dirs.d.ts.map +1 -1
- package/dist/scaffold/ensure-dirs.js +8 -6
- package/dist/scaffold/ensure-dirs.js.map +1 -1
- package/dist/security/override-cap-setting.d.ts +132 -0
- package/dist/security/override-cap-setting.d.ts.map +1 -1
- package/dist/security/override-cap-setting.js +88 -10
- package/dist/security/override-cap-setting.js.map +1 -1
- package/dist/security/owner-override-authority.d.ts.map +1 -1
- package/dist/security/owner-override-authority.js +5 -1
- package/dist/security/owner-override-authority.js.map +1 -1
- package/dist/selfimprove/draft-pr.d.ts.map +1 -1
- package/dist/selfimprove/draft-pr.js +4 -1
- package/dist/selfimprove/draft-pr.js.map +1 -1
- package/dist/session/engine-ops.d.ts.map +1 -1
- package/dist/session/engine-ops.js +4 -16
- package/dist/session/engine-ops.js.map +1 -1
- package/dist/sessions/briefing.d.ts +6 -0
- package/dist/sessions/briefing.d.ts.map +1 -1
- package/dist/sessions/briefing.js +34 -0
- package/dist/sessions/briefing.js.map +1 -1
- package/dist/sessions/session-end-snapshot-entry.js +2 -2
- package/dist/sessions/session-end-snapshot-entry.js.map +1 -1
- package/dist/sessions/session-end-snapshot.js +4 -4
- package/dist/sessions/session-end-snapshot.js.map +1 -1
- package/dist/skills/injection/token.d.ts.map +1 -1
- package/dist/skills/injection/token.js +3 -6
- package/dist/skills/injection/token.js.map +1 -1
- package/dist/skills/orchestrator/spawn.d.ts.map +1 -1
- package/dist/skills/orchestrator/spawn.js +3 -4
- package/dist/skills/orchestrator/spawn.js.map +1 -1
- package/dist/skills/propose-patch.d.ts.map +1 -1
- package/dist/skills/propose-patch.js +4 -1
- package/dist/skills/propose-patch.js.map +1 -1
- package/dist/spawn/branch-integration.d.ts.map +1 -1
- package/dist/spawn/branch-integration.js +5 -2
- package/dist/spawn/branch-integration.js.map +1 -1
- package/dist/store/attachment-repair.d.ts.map +1 -1
- package/dist/store/attachment-repair.js +5 -4
- package/dist/store/attachment-repair.js.map +1 -1
- package/dist/store/attachment-store.d.ts +6 -0
- package/dist/store/attachment-store.d.ts.map +1 -1
- package/dist/store/attachment-store.js +19 -2
- package/dist/store/attachment-store.js.map +1 -1
- package/dist/store/blob-ops.d.ts +1 -1
- package/dist/store/blob-ops.d.ts.map +1 -1
- package/dist/store/blob-ops.js +7 -3
- package/dist/store/blob-ops.js.map +1 -1
- package/dist/store/conduit-doc-history.d.ts +15 -1
- package/dist/store/conduit-doc-history.d.ts.map +1 -1
- package/dist/store/conduit-doc-history.js +16 -10
- package/dist/store/conduit-doc-history.js.map +1 -1
- package/dist/store/db-helpers.d.ts +29 -0
- package/dist/store/db-helpers.d.ts.map +1 -1
- package/dist/store/db-helpers.js +89 -1
- package/dist/store/db-helpers.js.map +1 -1
- package/dist/store/decision-adr-links.d.ts.map +1 -1
- package/dist/store/decision-adr-links.js +5 -2
- package/dist/store/decision-adr-links.js.map +1 -1
- package/dist/store/git-checkpoint.d.ts +11 -1
- package/dist/store/git-checkpoint.d.ts.map +1 -1
- package/dist/store/git-checkpoint.js +14 -3
- package/dist/store/git-checkpoint.js.map +1 -1
- package/dist/store/llmtxt-blob-adapter.d.ts.map +1 -1
- package/dist/store/llmtxt-blob-adapter.js +4 -1
- package/dist/store/llmtxt-blob-adapter.js.map +1 -1
- package/dist/store/schema/attachments.d.ts +7 -5
- package/dist/store/schema/attachments.d.ts.map +1 -1
- package/dist/store/schema/attachments.js +5 -2
- package/dist/store/schema/attachments.js.map +1 -1
- package/dist/store/session-store.d.ts.map +1 -1
- package/dist/store/session-store.js +6 -0
- package/dist/store/session-store.js.map +1 -1
- package/dist/store/sqlite-backup.d.ts +6 -1
- package/dist/store/sqlite-backup.d.ts.map +1 -1
- package/dist/store/sqlite-backup.js +9 -5
- package/dist/store/sqlite-backup.js.map +1 -1
- package/dist/store/sqlite-data-accessor.d.ts.map +1 -1
- package/dist/store/sqlite-data-accessor.js +63 -36
- package/dist/store/sqlite-data-accessor.js.map +1 -1
- package/dist/system/backup.d.ts +29 -0
- package/dist/system/backup.d.ts.map +1 -1
- package/dist/system/backup.js +240 -37
- package/dist/system/backup.js.map +1 -1
- package/dist/system/dependencies.d.ts +22 -0
- package/dist/system/dependencies.d.ts.map +1 -1
- package/dist/system/dependencies.js +49 -2
- package/dist/system/dependencies.js.map +1 -1
- package/dist/system/health.d.ts.map +1 -1
- package/dist/system/health.js +23 -1
- package/dist/system/health.js.map +1 -1
- package/dist/task-work/claims.d.ts.map +1 -1
- package/dist/task-work/claims.js +14 -0
- package/dist/task-work/claims.js.map +1 -1
- package/dist/task-work/index.d.ts.map +1 -1
- package/dist/task-work/index.js +22 -0
- package/dist/task-work/index.js.map +1 -1
- package/dist/tasks/ac-coverage-gate.d.ts +27 -0
- package/dist/tasks/ac-coverage-gate.d.ts.map +1 -1
- package/dist/tasks/ac-coverage-gate.js +63 -28
- package/dist/tasks/ac-coverage-gate.js.map +1 -1
- package/dist/tasks/affected-packages.d.ts +26 -30
- package/dist/tasks/affected-packages.d.ts.map +1 -1
- package/dist/tasks/affected-packages.js +27 -50
- package/dist/tasks/affected-packages.js.map +1 -1
- package/dist/tasks/affected-scope.d.ts +13 -3
- package/dist/tasks/affected-scope.d.ts.map +1 -1
- package/dist/tasks/affected-scope.js +29 -11
- package/dist/tasks/affected-scope.js.map +1 -1
- package/dist/tasks/change-set.d.ts.map +1 -1
- package/dist/tasks/change-set.js +39 -20
- package/dist/tasks/change-set.js.map +1 -1
- package/dist/tasks/command-gate-cmd.d.ts +33 -0
- package/dist/tasks/command-gate-cmd.d.ts.map +1 -0
- package/dist/tasks/command-gate-cmd.js +45 -0
- package/dist/tasks/command-gate-cmd.js.map +1 -0
- package/dist/tasks/complete-ci.d.ts +9 -0
- package/dist/tasks/complete-ci.d.ts.map +1 -1
- package/dist/tasks/complete-ci.js +21 -4
- package/dist/tasks/complete-ci.js.map +1 -1
- package/dist/tasks/complete.d.ts.map +1 -1
- package/dist/tasks/complete.js +14 -0
- package/dist/tasks/complete.js.map +1 -1
- package/dist/tasks/component-pr.d.ts +37 -0
- package/dist/tasks/component-pr.d.ts.map +1 -1
- package/dist/tasks/component-pr.js +106 -14
- package/dist/tasks/component-pr.js.map +1 -1
- package/dist/tasks/done-plan.d.ts +14 -1
- package/dist/tasks/done-plan.d.ts.map +1 -1
- package/dist/tasks/done-plan.js +125 -15
- package/dist/tasks/done-plan.js.map +1 -1
- package/dist/tasks/done-record.d.ts.map +1 -1
- package/dist/tasks/done-record.js +4 -0
- package/dist/tasks/done-record.js.map +1 -1
- package/dist/tasks/engine-converters.d.ts.map +1 -1
- package/dist/tasks/engine-converters.js +2 -0
- package/dist/tasks/engine-converters.js.map +1 -1
- package/dist/tasks/evidence-tree.d.ts +28 -1
- package/dist/tasks/evidence-tree.d.ts.map +1 -1
- package/dist/tasks/evidence-tree.js +66 -2
- package/dist/tasks/evidence-tree.js.map +1 -1
- package/dist/tasks/evidence.d.ts +39 -27
- package/dist/tasks/evidence.d.ts.map +1 -1
- package/dist/tasks/evidence.js +382 -188
- package/dist/tasks/evidence.js.map +1 -1
- package/dist/tasks/gate-notes.d.ts +17 -0
- package/dist/tasks/gate-notes.d.ts.map +1 -1
- package/dist/tasks/gate-notes.js.map +1 -1
- package/dist/tasks/gate-preview.d.ts +7 -1
- package/dist/tasks/gate-preview.d.ts.map +1 -1
- package/dist/tasks/gate-preview.js +30 -4
- package/dist/tasks/gate-preview.js.map +1 -1
- package/dist/tasks/gate-runner.d.ts +42 -0
- package/dist/tasks/gate-runner.d.ts.map +1 -1
- package/dist/tasks/gate-runner.js +234 -94
- package/dist/tasks/gate-runner.js.map +1 -1
- package/dist/tasks/manifest-scope.d.ts +115 -0
- package/dist/tasks/manifest-scope.d.ts.map +1 -0
- package/dist/tasks/manifest-scope.js +352 -0
- package/dist/tasks/manifest-scope.js.map +1 -0
- package/dist/tasks/merge-close.d.ts +376 -0
- package/dist/tasks/merge-close.d.ts.map +1 -0
- package/dist/tasks/merge-close.js +1497 -0
- package/dist/tasks/merge-close.js.map +1 -0
- package/dist/tasks/qa-run-binding.d.ts +5 -0
- package/dist/tasks/qa-run-binding.d.ts.map +1 -1
- package/dist/tasks/qa-run-binding.js +6 -1
- package/dist/tasks/qa-run-binding.js.map +1 -1
- package/dist/tasks/req.d.ts.map +1 -1
- package/dist/tasks/req.js +29 -1
- package/dist/tasks/req.js.map +1 -1
- package/dist/tasks/review-verdict.d.ts +159 -0
- package/dist/tasks/review-verdict.d.ts.map +1 -0
- package/dist/tasks/review-verdict.js +442 -0
- package/dist/tasks/review-verdict.js.map +1 -0
- package/dist/tasks/task-reparent.d.ts.map +1 -1
- package/dist/tasks/task-reparent.js +22 -3
- package/dist/tasks/task-reparent.js.map +1 -1
- package/dist/tasks/test-report-counts.d.ts +274 -0
- package/dist/tasks/test-report-counts.d.ts.map +1 -0
- package/dist/tasks/test-report-counts.js +746 -0
- package/dist/tasks/test-report-counts.js.map +1 -0
- package/dist/tasks/test-run-binding.d.ts +20 -45
- package/dist/tasks/test-run-binding.d.ts.map +1 -1
- package/dist/tasks/test-run-binding.js +34 -119
- package/dist/tasks/test-run-binding.js.map +1 -1
- package/dist/tasks/update.d.ts.map +1 -1
- package/dist/tasks/update.js +42 -0
- package/dist/tasks/update.js.map +1 -1
- package/dist/tasks/verification-policy.d.ts +12 -0
- package/dist/tasks/verification-policy.d.ts.map +1 -1
- package/dist/tasks/verification-policy.js +13 -0
- package/dist/tasks/verification-policy.js.map +1 -1
- package/dist/tasks/workspace-resolution.d.ts +225 -0
- package/dist/tasks/workspace-resolution.d.ts.map +1 -0
- package/dist/tasks/workspace-resolution.js +497 -0
- package/dist/tasks/workspace-resolution.js.map +1 -0
- package/dist/validation/doctor/checks.d.ts +20 -0
- package/dist/validation/doctor/checks.d.ts.map +1 -1
- package/dist/validation/doctor/checks.js +112 -0
- package/dist/validation/doctor/checks.js.map +1 -1
- package/dist/validation/engine-ops.d.ts.map +1 -1
- package/dist/validation/engine-ops.js +37 -3
- package/dist/validation/engine-ops.js.map +1 -1
- package/dist/validation/validate-ops.d.ts +4 -0
- package/dist/validation/validate-ops.d.ts.map +1 -1
- package/dist/validation/validate-ops.js +79 -2
- package/dist/validation/validate-ops.js.map +1 -1
- package/dist/worktree/list.d.ts +4 -21
- package/dist/worktree/list.d.ts.map +1 -1
- package/dist/worktree/list.js +14 -44
- package/dist/worktree/list.js.map +1 -1
- package/dist/worktree/merged-cleanup.d.ts +63 -0
- package/dist/worktree/merged-cleanup.d.ts.map +1 -0
- package/dist/worktree/merged-cleanup.js +571 -0
- package/dist/worktree/merged-cleanup.js.map +1 -0
- package/dist/worktree/prune.d.ts +64 -2
- package/dist/worktree/prune.d.ts.map +1 -1
- package/dist/worktree/prune.js +151 -6
- package/dist/worktree/prune.js.map +1 -1
- package/dist/worktree/sentinel-index.d.ts +14 -0
- package/dist/worktree/sentinel-index.d.ts.map +1 -1
- package/dist/worktree/sentinel-index.js +17 -0
- package/dist/worktree/sentinel-index.js.map +1 -1
- package/dist/worktree/worktree-adopt.d.ts +1 -13
- package/dist/worktree/worktree-adopt.d.ts.map +1 -1
- package/dist/worktree/worktree-adopt.js +2 -17
- package/dist/worktree/worktree-adopt.js.map +1 -1
- package/package.json +12 -12
- package/schemas/project-context.schema.json +33 -0
- package/templates/CLEO-INJECTION.md +6 -16
- package/templates/CLEO-REFERENCE.md +27 -4
- package/templates/git-hooks/legacy-hashes.json +2 -1
- package/templates/git-hooks/pre-commit +43 -11
- package/templates/workflows/release-prepare.yml.tmpl +9 -88
package/dist/tasks/evidence.js
CHANGED
|
@@ -37,11 +37,12 @@ import { HEAVY_HEAP_ENV, HEAVY_WORKERS_ENV, isHeavyTool } from './heavy-tool-env
|
|
|
37
37
|
import { DISABLE_ENV, describeMemoryLimit } from './heavy-tool-limit.js';
|
|
38
38
|
import { qaRunUnionGapReason, typecheckCoverageGap } from './qa-run-binding.js';
|
|
39
39
|
import { computeCommitRevalidationKey, readCommitRevalidationEntry, writeCommitRevalidationEntry, } from './revalidation-cache.js';
|
|
40
|
-
import {
|
|
40
|
+
import { present, readTestReport, TEST_RUN_SHAPE_HINT, } from './test-report-counts.js';
|
|
41
|
+
import { bindTestRunReport, configSchemaCompositionGap, coveredTestFiles, TEST_RUN_MAX_RECORDED_FILES, } from './test-run-binding.js';
|
|
41
42
|
import { captureHead, captureTree, resolveSpawnTimeoutMs, runToolCached, } from './tool-cache.js';
|
|
42
43
|
import { CANONICAL_TOOLS, canonicalToolName, listValidToolNames, resolveToolCommand, } from './tool-resolver.js';
|
|
43
44
|
import { detectStaticVacuity, probeTscFileCount, VACUOUS_TOOL_FIX } from './tool-vacuity.js';
|
|
44
|
-
import { classifyEvidenceTask, declaredEvidenceKind, gateProfileOf, implementedCodeEvidence, isDocumentArtifact, isDocumentEvidencePath, } from './verification-policy.js';
|
|
45
|
+
import { classifyEvidenceTask, declaredEvidenceKind, evidenceProfileOf, gateProfileOf, implementedCodeEvidence, isDocumentArtifact, isDocumentEvidencePath, } from './verification-policy.js';
|
|
45
46
|
/**
|
|
46
47
|
* Valid tool names recognised by the `tool:<name>` evidence atom.
|
|
47
48
|
*
|
|
@@ -142,6 +143,47 @@ export function parseEvidence(raw) {
|
|
|
142
143
|
throw err;
|
|
143
144
|
}
|
|
144
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* The two roots an evidence atom may be about, named so an arm cannot inherit
|
|
148
|
+
* the wrong one.
|
|
149
|
+
*
|
|
150
|
+
* gh#1365 was fixed once by threading a single `executionRoot` parameter, and
|
|
151
|
+
* the fix reached two of the switch's arms. The reason a single parameter could
|
|
152
|
+
* not close it is that **"root" is one word for two things**: the tree the
|
|
153
|
+
* evidence is ABOUT, and the store CLEO keeps its own records in. A parameter
|
|
154
|
+
* called `root` relocates that ambiguity into a signature rather than removing
|
|
155
|
+
* it, so the next arm silently picks whichever the author had in mind.
|
|
156
|
+
*
|
|
157
|
+
* With both fields named, every arm DECLARES what it is about by which field it
|
|
158
|
+
* reads. `decision:` reading {@link storeRoot} is self-documenting rather than
|
|
159
|
+
* an exception needing a comment; `pr:` reading both is obviously correct
|
|
160
|
+
* rather than obviously odd; and a new arm cannot inherit a default, because
|
|
161
|
+
* there is no default.
|
|
162
|
+
*
|
|
163
|
+
* The rule, stated once: **use the root that matches what the atom is about.**
|
|
164
|
+
* Evidence about the repository under test → {@link executionRoot}. Evidence
|
|
165
|
+
* about CLEO's own records → {@link storeRoot}.
|
|
166
|
+
*
|
|
167
|
+
* @task T12238 (gh#1365)
|
|
168
|
+
*/
|
|
169
|
+
/**
|
|
170
|
+
* Repo paths a task's change touches, for judging a `ci-run:` test file's
|
|
171
|
+
* relevance (T14149): its declared files and the files its recorded
|
|
172
|
+
* `implemented` evidence names (`files:` paths and `pr:` changed paths).
|
|
173
|
+
*/
|
|
174
|
+
function taskChangePaths(context) {
|
|
175
|
+
const implemented = context.task.verification?.evidence?.implemented?.atoms ?? [];
|
|
176
|
+
return [
|
|
177
|
+
...new Set([
|
|
178
|
+
...(context.task.files ?? []),
|
|
179
|
+
...implemented.flatMap((atom) => atom.kind === 'files'
|
|
180
|
+
? atom.files.map((file) => file.path)
|
|
181
|
+
: atom.kind === 'pr'
|
|
182
|
+
? (atom.changedPaths ?? [])
|
|
183
|
+
: []),
|
|
184
|
+
]),
|
|
185
|
+
].map(normalizeEvidencePath);
|
|
186
|
+
}
|
|
145
187
|
/**
|
|
146
188
|
* Validate a single parsed atom against the filesystem / git / tools.
|
|
147
189
|
*
|
|
@@ -280,6 +322,41 @@ siblingCommitSha, context) {
|
|
|
280
322
|
? { ok: true, atom: result.atom }
|
|
281
323
|
: { ok: false, reason: result.reason, codeName: result.codeName };
|
|
282
324
|
}
|
|
325
|
+
case 'ci-run': {
|
|
326
|
+
// T14149: a repeat-run.yml run, read from GitHub in the repository. Its
|
|
327
|
+
// test file must be one the task changed or one a linked criterion names.
|
|
328
|
+
const owner = taskId ?? context?.task.id;
|
|
329
|
+
if (owner === undefined || context === undefined)
|
|
330
|
+
return {
|
|
331
|
+
ok: false,
|
|
332
|
+
reason: 'ci-run evidence needs the task context to judge which test file is relevant',
|
|
333
|
+
codeName: 'E_EVIDENCE_INVALID',
|
|
334
|
+
};
|
|
335
|
+
const { resolveCiRunEvidenceAtom } = await import('../release/ci-run-evidence.js');
|
|
336
|
+
const result = await resolveCiRunEvidenceAtom(parsed.runId, roots, {
|
|
337
|
+
taskId: owner,
|
|
338
|
+
changedPaths: taskChangePaths(context),
|
|
339
|
+
criteriaTexts: (context.creditedCriteria ?? []).map((criterion) => criterion.text),
|
|
340
|
+
});
|
|
341
|
+
return result.ok
|
|
342
|
+
? { ok: true, atom: result.atom }
|
|
343
|
+
: { ok: false, reason: result.reason, codeName: result.codeName };
|
|
344
|
+
}
|
|
345
|
+
case 'deploy': {
|
|
346
|
+
// T14083: the project context is CLEO's own record (storeRoot); the
|
|
347
|
+
// deployed commit is resolved in the repository (executionRoot).
|
|
348
|
+
const { resolveDeployEvidenceAtom } = await import('../release/deploy-evidence.js');
|
|
349
|
+
const { loadProjectContext } = await import('../agents/variable-substitution.js');
|
|
350
|
+
const ctx = loadProjectContext(roots.storeRoot);
|
|
351
|
+
const owner = taskId ?? context?.task.id;
|
|
352
|
+
const result = await resolveDeployEvidenceAtom(parsed, roots, {
|
|
353
|
+
projectContext: ctx.loaded ? ctx.context : null,
|
|
354
|
+
...(owner !== undefined ? { taskId: owner } : {}),
|
|
355
|
+
});
|
|
356
|
+
return result.ok
|
|
357
|
+
? { ok: true, atom: result.atom }
|
|
358
|
+
: { ok: false, reason: result.reason, codeName: result.codeName };
|
|
359
|
+
}
|
|
283
360
|
case 'satisfies': {
|
|
284
361
|
// ADR-079-r2: 5-check validator pipeline shipped by T10507.
|
|
285
362
|
// Delegates to the dedicated validator module to keep the dispatch
|
|
@@ -811,8 +888,10 @@ export function extractTaskAcFilesWithProvenance(task) {
|
|
|
811
888
|
/**
|
|
812
889
|
* Return the list of file paths a commit touched (added, modified, or deleted).
|
|
813
890
|
*
|
|
814
|
-
* Uses `git diff-tree --no-commit-id -r --name-only -
|
|
815
|
-
*
|
|
891
|
+
* Uses `git diff-tree --no-commit-id -r --name-only --diff-merges=first-parent`
|
|
892
|
+
* (git 2.31+), which diffs a **merge commit** against its first parent only.
|
|
893
|
+
* `-m --first-parent` listed the diff against EVERY parent, since
|
|
894
|
+
* `--first-parent` limits a revision walk, not diff-tree (T14166).
|
|
816
895
|
* The legacy `git show --name-only` approach returned an empty list for merge
|
|
817
896
|
* commits because show without `--first-parent` produces a combined diff that
|
|
818
897
|
* `--name-only` cannot meaningfully summarise, causing the content-intersect
|
|
@@ -825,6 +904,7 @@ export function extractTaskAcFilesWithProvenance(task) {
|
|
|
825
904
|
* @internal
|
|
826
905
|
* @task T9245
|
|
827
906
|
* @task T11959 (DHQ-083 companion b — merge-commit first-parent diff)
|
|
907
|
+
* @task T14166
|
|
828
908
|
*/
|
|
829
909
|
async function gitShowFiles(sha, projectRoot) {
|
|
830
910
|
return (await commitFilesOrNull(sha, projectRoot)) ?? [];
|
|
@@ -839,9 +919,9 @@ async function gitShowFiles(sha, projectRoot) {
|
|
|
839
919
|
* @task T14001
|
|
840
920
|
*/
|
|
841
921
|
async function commitFilesOrNull(sha, projectRoot) {
|
|
842
|
-
// diff-
|
|
922
|
+
// --diff-merges=first-parent gives a clean single-parent diff for both
|
|
843
923
|
// regular commits and merge commits (compares merge result to first parent,
|
|
844
|
-
// i.e. the branch tip before the PR was merged).
|
|
924
|
+
// i.e. the branch tip before the PR was merged; T14166).
|
|
845
925
|
//
|
|
846
926
|
// --root: Without this flag, git diff-tree returns EMPTY output for the
|
|
847
927
|
// repository's initial (root) commit because there is no parent to diff
|
|
@@ -849,7 +929,15 @@ async function commitFilesOrNull(sha, projectRoot) {
|
|
|
849
929
|
// against the empty tree, correctly listing all files it introduced.
|
|
850
930
|
// This fixes the gate-verify-hint tests (and any real workflow) where the
|
|
851
931
|
// evidence commit happens to be the very first commit in the repo (T11959).
|
|
852
|
-
const r = await runCommand('git', [
|
|
932
|
+
const r = await runCommand('git', [
|
|
933
|
+
'diff-tree',
|
|
934
|
+
'--root',
|
|
935
|
+
'--no-commit-id',
|
|
936
|
+
'-r',
|
|
937
|
+
'--name-only',
|
|
938
|
+
'--diff-merges=first-parent',
|
|
939
|
+
sha,
|
|
940
|
+
], projectRoot);
|
|
853
941
|
if (r.exitCode !== 0)
|
|
854
942
|
return null;
|
|
855
943
|
return r.stdout
|
|
@@ -1267,121 +1355,6 @@ async function validateFiles(paths, roots, taskId, commitShaFor, requireCommitAr
|
|
|
1267
1355
|
}
|
|
1268
1356
|
return { ok: true, atom: { kind: 'files', files } };
|
|
1269
1357
|
}
|
|
1270
|
-
/**
|
|
1271
|
-
* The counter sets a `test-run:` report may carry (gh#1804), tried in order;
|
|
1272
|
-
* the first whose total key is present is read. `failed` and `notRun` sum
|
|
1273
|
-
* their keys, and an absent count is 0.
|
|
1274
|
-
*
|
|
1275
|
-
* | Shape | Total | Passed | Failed | Skipped / todo |
|
|
1276
|
-
* |---|---|---|---|---|
|
|
1277
|
-
* | vitest `--reporter=json`, jest `--json` | `numTotalTests` | `numPassedTests` | `numFailedTests` | `numPendingTests`, `numTodoTests` |
|
|
1278
|
-
* | a runner's summary output (bun test, tsx --test) | `total` | `passed` | `failed` | `skipped`, `todo` |
|
|
1279
|
-
* | node --test summary (`# tests`, `# pass`, ...) | `tests` | `pass` | `fail`, `cancelled` | `skipped`, `todo` |
|
|
1280
|
-
*
|
|
1281
|
-
* Integrity (T13136 review): every count is a non-negative integer;
|
|
1282
|
-
* passed + failed + skipped/todo equals the total; a failure under ANY key
|
|
1283
|
-
* ({@link TEST_RUN_FAILURE_KEYS}) refuses, whichever set supplied the total;
|
|
1284
|
-
* a report's `exit` / `exitCode` must be a number, and a non-zero one is
|
|
1285
|
-
* refused. A `test-run:` atom proves only what its file says; `tool:test` and
|
|
1286
|
-
* `ci:<pr>` are what prove the run. `cleo verify --help` documents the same
|
|
1287
|
-
* sets.
|
|
1288
|
-
*
|
|
1289
|
-
* @task T13136
|
|
1290
|
-
*/
|
|
1291
|
-
const TEST_RUN_COUNTER_SHAPES = [
|
|
1292
|
-
{
|
|
1293
|
-
name: 'vitest/jest',
|
|
1294
|
-
total: 'numTotalTests',
|
|
1295
|
-
passed: 'numPassedTests',
|
|
1296
|
-
failed: ['numFailedTests'],
|
|
1297
|
-
notRun: ['numPendingTests', 'numTodoTests'],
|
|
1298
|
-
},
|
|
1299
|
-
{
|
|
1300
|
-
name: 'summary',
|
|
1301
|
-
total: 'total',
|
|
1302
|
-
passed: 'passed',
|
|
1303
|
-
failed: ['failed'],
|
|
1304
|
-
notRun: ['skipped', 'todo'],
|
|
1305
|
-
},
|
|
1306
|
-
{
|
|
1307
|
-
name: 'node --test',
|
|
1308
|
-
total: 'tests',
|
|
1309
|
-
passed: 'pass',
|
|
1310
|
-
failed: ['fail', 'cancelled'],
|
|
1311
|
-
notRun: ['skipped', 'todo'],
|
|
1312
|
-
},
|
|
1313
|
-
];
|
|
1314
|
-
/** The keys of each counter set, as the refusals name them. */
|
|
1315
|
-
const TEST_RUN_SHAPE_HINT = TEST_RUN_COUNTER_SHAPES.map((s) => `${[s.total, s.passed, ...s.failed, ...s.notRun].join('/')} (${s.name})`).join(', ');
|
|
1316
|
-
/**
|
|
1317
|
-
* Keys that name failures in any counter set, plus jest/vitest suite failures
|
|
1318
|
-
* and the mocha / JUnit spellings: a positive value under any of them refuses
|
|
1319
|
-
* the report, whichever set supplied the total.
|
|
1320
|
-
*/
|
|
1321
|
-
const TEST_RUN_FAILURE_KEYS = [
|
|
1322
|
-
'numFailedTests',
|
|
1323
|
-
'numFailedTestSuites',
|
|
1324
|
-
'numRuntimeErrorTestSuites',
|
|
1325
|
-
'failed',
|
|
1326
|
-
'fail',
|
|
1327
|
-
'cancelled',
|
|
1328
|
-
'failures',
|
|
1329
|
-
'errors',
|
|
1330
|
-
];
|
|
1331
|
-
/** A present, non-null JSON value. */
|
|
1332
|
-
const present = (value) => value !== undefined && value !== null;
|
|
1333
|
-
/** A non-negative integer, or `undefined` when the value is anything else. */
|
|
1334
|
-
const countOf = (value) => typeof value === 'number' && Number.isInteger(value) && value >= 0 ? value : undefined;
|
|
1335
|
-
/**
|
|
1336
|
-
* Read a report's counts from the first counter set it carries, enforcing
|
|
1337
|
-
* their integrity; `null` when it carries none.
|
|
1338
|
-
*/
|
|
1339
|
-
function testRunCounts(report) {
|
|
1340
|
-
const shape = TEST_RUN_COUNTER_SHAPES.find((s) => present(report[s.total]));
|
|
1341
|
-
if (shape === undefined)
|
|
1342
|
-
return null;
|
|
1343
|
-
const invalid = (key) => ({
|
|
1344
|
-
reason: `test-run report's "${key}" is ${JSON.stringify(report[key])}; every count must be a non-negative integer`,
|
|
1345
|
-
codeName: 'E_EVIDENCE_INVALID',
|
|
1346
|
-
});
|
|
1347
|
-
const read = (key) => {
|
|
1348
|
-
if (!present(report[key]))
|
|
1349
|
-
return 0;
|
|
1350
|
-
return countOf(report[key]) ?? invalid(key);
|
|
1351
|
-
};
|
|
1352
|
-
const total = read(shape.total);
|
|
1353
|
-
if (typeof total !== 'number')
|
|
1354
|
-
return total;
|
|
1355
|
-
const keys = [shape.passed, ...shape.failed, ...shape.notRun];
|
|
1356
|
-
const values = [];
|
|
1357
|
-
for (const key of keys) {
|
|
1358
|
-
const v = read(key);
|
|
1359
|
-
if (typeof v !== 'number')
|
|
1360
|
-
return v;
|
|
1361
|
-
values.push(v);
|
|
1362
|
-
}
|
|
1363
|
-
for (const key of TEST_RUN_FAILURE_KEYS) {
|
|
1364
|
-
if (!present(report[key]))
|
|
1365
|
-
continue;
|
|
1366
|
-
const v = countOf(report[key]);
|
|
1367
|
-
if (v === undefined)
|
|
1368
|
-
return invalid(key);
|
|
1369
|
-
if (v > 0) {
|
|
1370
|
-
return { reason: `test-run reports ${key} = ${v}`, codeName: 'E_EVIDENCE_TESTS_FAILED' };
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
|
-
const [passed = 0, ...rest] = values;
|
|
1374
|
-
const failed = rest.slice(0, shape.failed.length).reduce((a, b) => a + b, 0);
|
|
1375
|
-
const notRun = rest.slice(shape.failed.length).reduce((a, b) => a + b, 0);
|
|
1376
|
-
if (passed + failed + notRun !== total) {
|
|
1377
|
-
return {
|
|
1378
|
-
reason: `test-run report's counts do not add up: ${shape.total} is ${total}, but ` +
|
|
1379
|
-
`${shape.passed} + ${[...shape.failed, ...shape.notRun].join(' + ')} = ${passed + failed + notRun}`,
|
|
1380
|
-
codeName: 'E_EVIDENCE_INVALID',
|
|
1381
|
-
};
|
|
1382
|
-
}
|
|
1383
|
-
return { shape, total, passed, failed, notRun };
|
|
1384
|
-
}
|
|
1385
1358
|
/**
|
|
1386
1359
|
* `tool:test-affected` (D11150, T12635): run `testing.affectedCommand` over the
|
|
1387
1360
|
* packages the branch diff touches plus their dependents, through the same
|
|
@@ -1403,6 +1376,9 @@ async function validateAffectedTests(roots, taskId) {
|
|
|
1403
1376
|
*/
|
|
1404
1377
|
async function runAffectedTests(tool, run, roots, taskId) {
|
|
1405
1378
|
const { storeRoot, executionRoot } = roots;
|
|
1379
|
+
const foreign = await workspaceResolutionRefusal(executionRoot);
|
|
1380
|
+
if (foreign !== null)
|
|
1381
|
+
return foreign;
|
|
1406
1382
|
// T13132: status shows what holds the budget — the scope and the task.
|
|
1407
1383
|
const result = await runToolCached(run.command, storeRoot, {
|
|
1408
1384
|
executionRoot,
|
|
@@ -1437,6 +1413,7 @@ async function runAffectedTests(tool, run, roots, taskId) {
|
|
|
1437
1413
|
...(run.testFiles ? { affectedTestFiles: run.testFiles } : {}),
|
|
1438
1414
|
...(run.configPaths ? { configPaths: run.configPaths } : {}),
|
|
1439
1415
|
...(run.untested.length > 0 ? { untestedPackages: run.untested } : {}),
|
|
1416
|
+
...(run.untrackedSetAside ? { untrackedSetAside: run.untrackedSetAside } : {}),
|
|
1440
1417
|
...(await toolRunAtomFields(result)),
|
|
1441
1418
|
},
|
|
1442
1419
|
};
|
|
@@ -1600,16 +1577,74 @@ async function readTestRunReport(path, roots) {
|
|
|
1600
1577
|
codeName: 'E_EVIDENCE_INVALID',
|
|
1601
1578
|
};
|
|
1602
1579
|
}
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1580
|
+
// T14124: one reader for every format, shared with typed gates' minCount.
|
|
1581
|
+
// T14161: a TAP report's file names resolve in the caller's tree.
|
|
1582
|
+
const text = content.toString('utf-8');
|
|
1583
|
+
const read = readTestReport(text, { cwd: executionRoot });
|
|
1584
|
+
if (read === null) {
|
|
1585
|
+
let parsed;
|
|
1586
|
+
try {
|
|
1587
|
+
parsed = JSON.parse(text);
|
|
1588
|
+
}
|
|
1589
|
+
catch (err) {
|
|
1590
|
+
return {
|
|
1591
|
+
ok: false,
|
|
1592
|
+
reason: 'test-run file is not valid JSON, JUnit XML or libtest JSON lines (or node:test TAP): ' +
|
|
1593
|
+
`${err instanceof Error ? err.message : String(err)}`,
|
|
1594
|
+
codeName: 'E_EVIDENCE_INVALID',
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1607
1597
|
return {
|
|
1608
1598
|
ok: false,
|
|
1609
|
-
reason:
|
|
1599
|
+
reason: parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)
|
|
1600
|
+
? `test-run file is not a JSON object. Expected test counts: ${TEST_RUN_SHAPE_HINT}`
|
|
1601
|
+
: `test-run report has no test counts. Expected one of: ${TEST_RUN_SHAPE_HINT}; ` +
|
|
1602
|
+
"e.g. vitest's --reporter=json --outputFile, Playwright's --reporter=json, or " +
|
|
1603
|
+
'{"total":43,"passed":42,"failed":0,"skipped":1}',
|
|
1610
1604
|
codeName: 'E_EVIDENCE_INVALID',
|
|
1611
1605
|
};
|
|
1612
1606
|
}
|
|
1607
|
+
if (!read.ok)
|
|
1608
|
+
return { ok: false, reason: read.reason, codeName: read.codeName };
|
|
1609
|
+
return { ok: true, abs, content, parsed: read.report, counts: read.counts };
|
|
1610
|
+
}
|
|
1611
|
+
/**
|
|
1612
|
+
* T14097: refuse a test run in a checkout whose workspace packages resolve
|
|
1613
|
+
* into another checkout (its tests would run that checkout's build) or whose
|
|
1614
|
+
* dist was built from other source (T14123), and warn when a dependency's
|
|
1615
|
+
* build is borrowed from a checkout with identical source. Null when the run
|
|
1616
|
+
* may proceed.
|
|
1617
|
+
*
|
|
1618
|
+
* @param root - The tree the run is about (any directory inside the checkout).
|
|
1619
|
+
* @param testFiles - Test files the run names, to scope the check; all packages when empty.
|
|
1620
|
+
*/
|
|
1621
|
+
async function workspaceResolutionRefusal(root, testFiles = []) {
|
|
1622
|
+
const { borrowedBuildWarning, checkoutRootOf, checkWorkspaceResolution, packagesNamedByTestArgs, UNSTAMPED_DIST_FIX, unstampedDistWarning, workspaceResolutionError, } = await import('./workspace-resolution.js');
|
|
1623
|
+
const checkout = checkoutRootOf(root) ?? root;
|
|
1624
|
+
const report = checkWorkspaceResolution(checkout, packagesNamedByTestArgs(checkout, checkout, testFiles));
|
|
1625
|
+
const refused = workspaceResolutionError(report);
|
|
1626
|
+
// @sync-invariant none:input-shape a test run against another checkout's build, or a dist built from other source, is refused before anything is written
|
|
1627
|
+
if (refused !== null)
|
|
1628
|
+
return { ok: false, reason: refused.message, codeName: refused.codeName };
|
|
1629
|
+
const borrowed = borrowedBuildWarning(report);
|
|
1630
|
+
if (borrowed !== null)
|
|
1631
|
+
pushWarning({
|
|
1632
|
+
// @sync-invariant none:input-shape a warning only; nothing is refused or written
|
|
1633
|
+
code: 'W_WORKSPACE_BORROWED_BUILD',
|
|
1634
|
+
severity: 'warn',
|
|
1635
|
+
message: borrowed,
|
|
1636
|
+
context: { root: checkout },
|
|
1637
|
+
});
|
|
1638
|
+
const unstamped = unstampedDistWarning(report);
|
|
1639
|
+
if (unstamped !== null)
|
|
1640
|
+
pushWarning({
|
|
1641
|
+
// @sync-invariant none:input-shape a warning only; nothing is refused or written
|
|
1642
|
+
code: 'W_WORKSPACE_UNSTAMPED_DIST',
|
|
1643
|
+
severity: 'warn',
|
|
1644
|
+
message: unstamped,
|
|
1645
|
+
context: { root: checkout, fix: UNSTAMPED_DIST_FIX },
|
|
1646
|
+
});
|
|
1647
|
+
return null;
|
|
1613
1648
|
}
|
|
1614
1649
|
/**
|
|
1615
1650
|
* Validate a `test-run:` report: its counts, then bound to the change.
|
|
@@ -1623,44 +1658,14 @@ async function validateTestRun(path, roots, taskId, siblings = []) {
|
|
|
1623
1658
|
const read = await readTestRunReport(path, roots);
|
|
1624
1659
|
if (!read.ok)
|
|
1625
1660
|
return read;
|
|
1626
|
-
const { abs, content } = read;
|
|
1627
|
-
let parsed = read.parsed;
|
|
1661
|
+
const { abs, content, parsed, counts } = read;
|
|
1628
1662
|
const sha256 = createHash('sha256').update(content).digest('hex');
|
|
1629
|
-
if (
|
|
1663
|
+
if (counts.failure !== undefined)
|
|
1630
1664
|
return {
|
|
1631
1665
|
ok: false,
|
|
1632
|
-
reason: `test-run
|
|
1633
|
-
codeName: '
|
|
1634
|
-
};
|
|
1635
|
-
}
|
|
1636
|
-
// T14031: a Playwright JSON report is read in the vitest shape.
|
|
1637
|
-
const playwright = playwrightTestRun(parsed);
|
|
1638
|
-
if (playwright !== null && 'refusal' in playwright)
|
|
1639
|
-
return { ok: false, reason: playwright.refusal, codeName: playwright.codeName };
|
|
1640
|
-
if (playwright !== null)
|
|
1641
|
-
parsed = playwright;
|
|
1642
|
-
if (present(parsed.testResults) &&
|
|
1643
|
-
(!Array.isArray(parsed.testResults) ||
|
|
1644
|
-
parsed.testResults.some((tr) => tr === null || typeof tr !== 'object' || Array.isArray(tr)))) {
|
|
1645
|
-
return {
|
|
1646
|
-
ok: false,
|
|
1647
|
-
reason: 'test-run report\'s "testResults" must be an array of objects',
|
|
1648
|
-
codeName: 'E_EVIDENCE_INVALID',
|
|
1649
|
-
};
|
|
1650
|
-
}
|
|
1651
|
-
const counts = testRunCounts(parsed);
|
|
1652
|
-
if (counts !== null && !('shape' in counts)) {
|
|
1653
|
-
return { ok: false, reason: counts.reason, codeName: counts.codeName };
|
|
1654
|
-
}
|
|
1655
|
-
if (counts === null) {
|
|
1656
|
-
return {
|
|
1657
|
-
ok: false,
|
|
1658
|
-
reason: `test-run report has no test counts. Expected one of: ${TEST_RUN_SHAPE_HINT}; ` +
|
|
1659
|
-
"e.g. vitest's --reporter=json --outputFile, Playwright's --reporter=json, or " +
|
|
1660
|
-
'{"total":43,"passed":42,"failed":0,"skipped":1}',
|
|
1661
|
-
codeName: 'E_EVIDENCE_INVALID',
|
|
1666
|
+
reason: `test-run reports ${counts.failure}`,
|
|
1667
|
+
codeName: 'E_EVIDENCE_TESTS_FAILED',
|
|
1662
1668
|
};
|
|
1663
|
-
}
|
|
1664
1669
|
const { total, failed, passed, notRun: pending } = counts;
|
|
1665
1670
|
// Both spellings are checked: a report carrying exit 0 and exitCode 1 failed
|
|
1666
1671
|
// (T13136 review).
|
|
@@ -1736,18 +1741,19 @@ async function validateTestRun(path, roots, taskId, siblings = []) {
|
|
|
1736
1741
|
if (broken)
|
|
1737
1742
|
return { ok: false, reason: broken, codeName: E_EVIDENCE_GIT_ROOT };
|
|
1738
1743
|
const testFiles = coveredTestFiles(parsed, bindRoot);
|
|
1744
|
+
// T14097: never record a receipt from a checkout testing another one's build.
|
|
1745
|
+
const foreign = await workspaceResolutionRefusal(bindRoot, testFiles);
|
|
1746
|
+
if (foreign !== null)
|
|
1747
|
+
return foreign;
|
|
1739
1748
|
// T14031: the other reports of this write cover the change with this one.
|
|
1740
1749
|
const siblingFiles = [];
|
|
1741
1750
|
for (const sibling of siblings) {
|
|
1742
1751
|
if (sibling === path)
|
|
1743
1752
|
continue;
|
|
1744
1753
|
const other = await readTestRunReport(sibling, roots);
|
|
1745
|
-
if (!other.ok
|
|
1754
|
+
if (!other.ok)
|
|
1746
1755
|
continue;
|
|
1747
|
-
|
|
1748
|
-
if (adapted !== null && 'refusal' in adapted)
|
|
1749
|
-
continue;
|
|
1750
|
-
siblingFiles.push(...coveredTestFiles(adapted ?? other.parsed, bindRoot));
|
|
1756
|
+
siblingFiles.push(...coveredTestFiles(other.parsed, bindRoot));
|
|
1751
1757
|
}
|
|
1752
1758
|
// The tree hash is the tool cache's (T12958), so a bound test-run and a
|
|
1753
1759
|
// cached tool run of the same content agree.
|
|
@@ -1892,6 +1898,7 @@ async function validateQaRun(path, roots, taskId) {
|
|
|
1892
1898
|
...(gaps?.nonCodeChange ? { nonCodeChange: true } : {}),
|
|
1893
1899
|
...(gaps?.configPaths ? { configPaths: gaps.configPaths } : {}),
|
|
1894
1900
|
...(gaps?.untrackedSetAside ? { untrackedSetAside: gaps.untrackedSetAside } : {}),
|
|
1901
|
+
...(gaps?.owedChecks ? { owedChecks: gaps.owedChecks } : {}),
|
|
1895
1902
|
},
|
|
1896
1903
|
};
|
|
1897
1904
|
}
|
|
@@ -2109,7 +2116,8 @@ async function validateTool(tool, roots, context, siblingCommitSha) {
|
|
|
2109
2116
|
? (await import('./change-set.js')).resolveChangeSetRoot(roots.storeRoot, context.task.id)
|
|
2110
2117
|
.root
|
|
2111
2118
|
: roots.executionRoot;
|
|
2112
|
-
const
|
|
2119
|
+
const scoped = scopedChangedPaths(root);
|
|
2120
|
+
const changed = scoped?.paths ?? [];
|
|
2113
2121
|
const treeHash = isNonCodeOnlyChange(changed) ? (await captureTree(root)).tree : null;
|
|
2114
2122
|
if (treeHash) {
|
|
2115
2123
|
return {
|
|
@@ -2121,6 +2129,11 @@ async function validateTool(tool, roots, context, siblingCommitSha) {
|
|
|
2121
2129
|
notApplicable: true,
|
|
2122
2130
|
nonCodeChange: true,
|
|
2123
2131
|
treeHash,
|
|
2132
|
+
// T14125: a stray untracked code file is not this change; once one
|
|
2133
|
+
// is committed, `cleo complete` refuses this result.
|
|
2134
|
+
...(scoped && scoped.untrackedSetAside.length > 0
|
|
2135
|
+
? { untrackedSetAside: scoped.untrackedSetAside }
|
|
2136
|
+
: {}),
|
|
2124
2137
|
reason: `no test reads this change: it touches only non-code files (${changed.join(', ')})`,
|
|
2125
2138
|
},
|
|
2126
2139
|
};
|
|
@@ -2214,6 +2227,11 @@ async function validateTool(tool, roots, context, siblingCommitSha) {
|
|
|
2214
2227
|
// T13132: a heavy tool here runs the whole project (the affected scope
|
|
2215
2228
|
// returned above); status marks it scope=full with its task.
|
|
2216
2229
|
const taskId = context?.task.id;
|
|
2230
|
+
if (resolution.command.canonical === 'test') {
|
|
2231
|
+
const foreign = await workspaceResolutionRefusal(executionRoot);
|
|
2232
|
+
if (foreign !== null)
|
|
2233
|
+
return foreign;
|
|
2234
|
+
}
|
|
2217
2235
|
const result = await runToolCached(resolution.command, projectRoot, {
|
|
2218
2236
|
executionRoot,
|
|
2219
2237
|
semaphoreOptions: {
|
|
@@ -2714,13 +2732,19 @@ async function validateDecision(decisionId, roots) {
|
|
|
2714
2732
|
* task in its title, body or head branch, or its diff intersects the task's
|
|
2715
2733
|
* declared files — and when files are declared, the diff MUST intersect them.
|
|
2716
2734
|
*
|
|
2735
|
+
* T14094: a task with no declared files may also credit a PR merged under
|
|
2736
|
+
* another task, through the criteria the same write names with
|
|
2737
|
+
* `satisfies:<task>#AC…` ({@link creditRefusal}).
|
|
2738
|
+
*
|
|
2717
2739
|
* @param prNumber - The PR being linked.
|
|
2718
2740
|
* @param pr - The verified PR (title, body, head branch, changed files).
|
|
2719
2741
|
* @param context - The task the evidence is being recorded for.
|
|
2742
|
+
* @param mergedAt - When the PR merged, for the credited-criteria edit check.
|
|
2720
2743
|
* @returns The refusal, or null when the PR is linked to the task.
|
|
2721
2744
|
* @task T12634
|
|
2745
|
+
* @task T14094
|
|
2722
2746
|
*/
|
|
2723
|
-
export function checkPrTaskLinkage(prNumber, pr, context) {
|
|
2747
|
+
export function checkPrTaskLinkage(prNumber, pr, context, mergedAt) {
|
|
2724
2748
|
if (pr.changedFileCount !== pr.changedPaths.length || pr.changedPaths.length === 0) {
|
|
2725
2749
|
return {
|
|
2726
2750
|
ok: false,
|
|
@@ -2732,13 +2756,67 @@ export function checkPrTaskLinkage(prNumber, pr, context) {
|
|
|
2732
2756
|
const taskMention = new RegExp(`(^|[^A-Za-z0-9])${context.task.id}([^A-Za-z0-9]|$)`);
|
|
2733
2757
|
const explicitlyLinked = taskMention.test(`${pr.title}\n${pr.body}\n${pr.headRefName}`);
|
|
2734
2758
|
const scopeIntersects = declaredFiles.length > 0 && diffIntersectsAc(pr.changedPaths, declaredFiles);
|
|
2735
|
-
if (
|
|
2759
|
+
if (declaredFiles.length > 0 && !scopeIntersects) {
|
|
2736
2760
|
return {
|
|
2737
2761
|
ok: false,
|
|
2738
2762
|
codeName: 'E_EVIDENCE_CONTENT_MISMATCH',
|
|
2739
|
-
reason: `PR #${prNumber}
|
|
2763
|
+
reason: `PR #${prNumber} changes none of task ${context.task.id}'s declared files (${declaredFiles.join(', ')}). Changed artifacts: ${pr.changedPaths.join(', ')}.`,
|
|
2740
2764
|
};
|
|
2741
2765
|
}
|
|
2766
|
+
if (explicitlyLinked || scopeIntersects)
|
|
2767
|
+
return null;
|
|
2768
|
+
const refusal = creditRefusal(prNumber, pr.changedPaths, context, mergedAt);
|
|
2769
|
+
return refusal === null
|
|
2770
|
+
? null
|
|
2771
|
+
: { ok: false, codeName: 'E_EVIDENCE_CONTENT_MISMATCH', reason: refusal };
|
|
2772
|
+
}
|
|
2773
|
+
/**
|
|
2774
|
+
* Why a PR that neither cites a task nor declares its files cannot be credited
|
|
2775
|
+
* to it, or null when it can (T14094: work shipped under another task). The
|
|
2776
|
+
* write must name criteria with `satisfies:<task>#AC…`; each must name at
|
|
2777
|
+
* least one repo path, the PR must have changed every one of them, and none
|
|
2778
|
+
* may have been edited after the PR merged, so a criterion cannot be made to
|
|
2779
|
+
* match after the fact. The refusal names that one route, and for a task the
|
|
2780
|
+
* other task fully covers, the duplicate close.
|
|
2781
|
+
*
|
|
2782
|
+
* @param prNumber - The PR being credited.
|
|
2783
|
+
* @param changedPaths - Its complete changed-file list.
|
|
2784
|
+
* @param context - The task, with the criteria the write credits.
|
|
2785
|
+
* @param mergedAt - When the PR merged; unknown refuses any credit.
|
|
2786
|
+
* @returns The refusal, or null.
|
|
2787
|
+
* @task T14094
|
|
2788
|
+
*/
|
|
2789
|
+
function creditRefusal(prNumber, changedPaths, context, mergedAt) {
|
|
2790
|
+
const id = context.task.id;
|
|
2791
|
+
const credited = context.creditedCriteria ?? [];
|
|
2792
|
+
if (credited.length === 0)
|
|
2793
|
+
return (`PR #${prNumber} does not cite task ${id} and ${id} declares no files, so it is not ${id}'s work. ` +
|
|
2794
|
+
`To credit a PR merged under another task, name the criteria it delivers in the same write ` +
|
|
2795
|
+
`(satisfies:${id}#AC<n>); each must name the files the PR changed. If another task fully covers ${id}, ` +
|
|
2796
|
+
`close it with cleo cancel ${id} --reason "covered by T<n>" and cleo relates add ${id} T<n> duplicates "<reason>". ` +
|
|
2797
|
+
`Changed artifacts: ${changedPaths.join(', ')}.`);
|
|
2798
|
+
// Credit is for merged PRs only; without the merge time the edit check below cannot run.
|
|
2799
|
+
// Times here are zoned ISO-8601 (GitHub's mergedAt; criteria normalised to
|
|
2800
|
+
// UTC by the writer), so Date.parse reads them exactly.
|
|
2801
|
+
const mergedMs = mergedAt ? Date.parse(mergedAt) : Number.NaN;
|
|
2802
|
+
if (Number.isNaN(mergedMs))
|
|
2803
|
+
return `PR #${prNumber}'s merge time is unknown, so its credited criteria cannot be checked for later edits.`;
|
|
2804
|
+
const changed = new Set(changedPaths.map(normalizeEvidencePath));
|
|
2805
|
+
for (const criterion of credited) {
|
|
2806
|
+
// An edit gives a criterion a new row, so its creation time counts too.
|
|
2807
|
+
const written = criterion.updatedAt ?? criterion.createdAt;
|
|
2808
|
+
const writtenMs = written ? Date.parse(written) : Number.NaN;
|
|
2809
|
+
if (Number.isNaN(writtenMs))
|
|
2810
|
+
return `${id}#${criterion.alias} has no readable write time, so it cannot be shown to predate PR #${prNumber}'s merge.`;
|
|
2811
|
+
if (writtenMs > mergedMs)
|
|
2812
|
+
return `${id}#${criterion.alias} was written or edited at ${written}, after PR #${prNumber} merged at ${mergedAt}; a criterion changed after the merge cannot be credited by it.`;
|
|
2813
|
+
const paths = extractTaskAcFilesWithProvenance({ acceptance: [criterion.text] }).files ?? [];
|
|
2814
|
+
if (paths.length === 0)
|
|
2815
|
+
return `${id}#${criterion.alias} names no file, so PR #${prNumber} cannot be shown to deliver it; credit only criteria that name the files the PR changed.`;
|
|
2816
|
+
const missing = paths.filter((path) => !changed.has(normalizeEvidencePath(path)));
|
|
2817
|
+
if (missing.length > 0)
|
|
2818
|
+
return `PR #${prNumber} did not change ${missing.join(', ')}, which ${id}#${criterion.alias} names, so it cannot be credited with ${criterion.alias}.`;
|
|
2819
|
+
}
|
|
2742
2820
|
return null;
|
|
2743
2821
|
}
|
|
2744
2822
|
/**
|
|
@@ -2789,10 +2867,10 @@ async function validatePrAtom(prNumber, roots, context, componentPrNumber) {
|
|
|
2789
2867
|
// T12671: `pr:<component>@<integration>` — the integration PR is the
|
|
2790
2868
|
// provenance (merge commit, required workflows); the component PR is the
|
|
2791
2869
|
// task's link and its change. Both are verified, both are recorded.
|
|
2792
|
-
const linked = await linkedPrChange(prNumber, result, roots, componentPrNumber);
|
|
2870
|
+
const linked = await linkedPrChange(prNumber, result, roots, componentPrNumber, undefined, context.task.id);
|
|
2793
2871
|
if (!linked.ok)
|
|
2794
2872
|
return linked;
|
|
2795
|
-
const unlinked = checkPrTaskLinkage(linked.prNumber, linked.pr, context);
|
|
2873
|
+
const unlinked = checkPrTaskLinkage(linked.prNumber, linked.pr, context, result.mergedAt);
|
|
2796
2874
|
if (unlinked)
|
|
2797
2875
|
return unlinked;
|
|
2798
2876
|
const changedPaths = linked.pr.changedPaths;
|
|
@@ -2848,14 +2926,40 @@ async function validatePrAtom(prNumber, roots, context, componentPrNumber) {
|
|
|
2848
2926
|
* @param prNumber - The (integration) PR the atom names.
|
|
2849
2927
|
* @param landing - Its verified resolution.
|
|
2850
2928
|
* @param roots - Evidence roots (git runs in the execution root).
|
|
2929
|
+
* Without a component, a PR that does not name the task itself is linked
|
|
2930
|
+
* through a merge commit on it that names the task (T14164), carrying that
|
|
2931
|
+
* merge's own landed change only, never the whole PR's files.
|
|
2932
|
+
*
|
|
2851
2933
|
* @param componentPrNumber - Component PR, when the atom names one.
|
|
2852
2934
|
* @param view - Component PR reader (tests inject; defaults to `gh pr view`).
|
|
2935
|
+
* @param taskId - The task the evidence is for, to find a merge that names it.
|
|
2853
2936
|
* @returns The linkage subject, or the refusal.
|
|
2854
2937
|
* @task T12671
|
|
2938
|
+
* @task T14164
|
|
2855
2939
|
*/
|
|
2856
|
-
export async function linkedPrChange(prNumber, landing, roots, componentPrNumber, view) {
|
|
2857
|
-
if (componentPrNumber === undefined)
|
|
2858
|
-
|
|
2940
|
+
export async function linkedPrChange(prNumber, landing, roots, componentPrNumber, view, taskId) {
|
|
2941
|
+
if (componentPrNumber === undefined) {
|
|
2942
|
+
if (taskId === undefined ||
|
|
2943
|
+
new RegExp(`(^|[^A-Za-z0-9])${taskId}([^A-Za-z0-9]|$)`).test(`${landing.title}\n${landing.body}\n${landing.headRefName}`))
|
|
2944
|
+
return { ok: true, prNumber, pr: landing };
|
|
2945
|
+
const { taskMergesInLanding } = await import('./component-pr.js');
|
|
2946
|
+
const merged = taskMergesInLanding(roots.executionRoot, taskId, landing);
|
|
2947
|
+
if (merged === null)
|
|
2948
|
+
return { ok: true, prNumber, pr: landing };
|
|
2949
|
+
const changedPaths = [...merged.files, ...merged.deleted];
|
|
2950
|
+
return {
|
|
2951
|
+
ok: true,
|
|
2952
|
+
prNumber,
|
|
2953
|
+
pr: {
|
|
2954
|
+
title: merged.merges.map((m) => m.subject).join('\n'),
|
|
2955
|
+
body: '',
|
|
2956
|
+
headRefName: landing.headRefName,
|
|
2957
|
+
changedPaths,
|
|
2958
|
+
changedFileCount: changedPaths.length,
|
|
2959
|
+
...(merged.deleted.length > 0 ? { deletedPaths: merged.deleted } : {}),
|
|
2960
|
+
},
|
|
2961
|
+
};
|
|
2962
|
+
}
|
|
2859
2963
|
const { resolveComponentPr } = await import('./component-pr.js');
|
|
2860
2964
|
const c = await resolveComponentPr(componentPrNumber, {
|
|
2861
2965
|
prNumber,
|
|
@@ -3034,6 +3138,12 @@ export function checkTaskEvidenceContext(context, gate, atoms) {
|
|
|
3034
3138
|
// T12634: a `ci:` result is bound to the task it was validated for, like `pr:`.
|
|
3035
3139
|
if (atoms.some((atom) => atom.kind === 'ci' && atom.taskId !== context.task.id))
|
|
3036
3140
|
return 'CI evidence lacks verified task linkage; re-verify with current task context.';
|
|
3141
|
+
// T14083: a probed deployment is bound to the task it was verified for.
|
|
3142
|
+
if (atoms.some((atom) => atom.kind === 'deploy' && atom.taskId !== context.task.id))
|
|
3143
|
+
return 'Deploy evidence lacks verified task linkage; re-verify with current task context.';
|
|
3144
|
+
// T14149: a repeat-run is judged relevant to the task it was verified for.
|
|
3145
|
+
if (atoms.some((atom) => atom.kind === 'ci-run' && atom.taskId !== context.task.id))
|
|
3146
|
+
return 'ci-run evidence lacks verified task linkage; re-verify with current task context.';
|
|
3037
3147
|
// T12848: several PRs in one write are unambiguous — each `files:` path is
|
|
3038
3148
|
// pinned to the merge of the PR that changed it (prMergeCommitForPath), and
|
|
3039
3149
|
// every PR must be proven by an artifact it changed that was READ AT ITS OWN
|
|
@@ -3072,6 +3182,7 @@ export function checkTaskEvidenceContext(context, gate, atoms) {
|
|
|
3072
3182
|
!atoms.some((atom) => atom.kind === 'test-run' ||
|
|
3073
3183
|
atom.kind === 'qa-run' ||
|
|
3074
3184
|
atom.kind === 'ci' ||
|
|
3185
|
+
(atom.kind === 'deploy' && atom.satisfiesGates.includes(gate)) ||
|
|
3075
3186
|
(atom.kind === 'tool' && (!atom.notApplicable || atom.nonCodeChange === true)))) {
|
|
3076
3187
|
return `Code task ${context.task.id} requires an actual verification result for ${gate}; absence of a toolchain is not a passing result.`;
|
|
3077
3188
|
}
|
|
@@ -3096,11 +3207,31 @@ export function checkTaskEvidenceContext(context, gate, atoms) {
|
|
|
3096
3207
|
!atoms.some((atom) => atom.kind === 'ci')) {
|
|
3097
3208
|
// T13745: a non-code-only change has no TypeScript to typecheck.
|
|
3098
3209
|
const nonCode = atoms.some((atom) => atom.kind === 'qa-run' && atom.nonCodeChange === true);
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3210
|
+
// T14089: a check is owed only for the changed paths it reads, as the
|
|
3211
|
+
// receipts judged them at bind time. A receipt bound without that
|
|
3212
|
+
// judgement (older, or nothing judged) owes both, as before.
|
|
3213
|
+
const receipts = atoms.flatMap((atom) => (atom.kind === 'qa-run' ? [atom] : []));
|
|
3214
|
+
const owedPaths = (check) => receipts.some((r) => r.owedChecks === undefined)
|
|
3215
|
+
? null
|
|
3216
|
+
: [...new Set(receipts.flatMap((r) => r.owedChecks?.[check] ?? []))];
|
|
3217
|
+
const missing = ['typecheck', 'lint'].filter((check) => {
|
|
3218
|
+
if (nonCode && check === 'typecheck')
|
|
3219
|
+
return false;
|
|
3220
|
+
const paths = owedPaths(check);
|
|
3221
|
+
if (paths !== null && paths.length === 0)
|
|
3222
|
+
return false;
|
|
3223
|
+
return !atoms.some((atom) => (atom.kind === 'qa-run' && atom.check === check) ||
|
|
3224
|
+
(atom.kind === 'tool' && canonicalToolName(atom.tool) === check));
|
|
3225
|
+
});
|
|
3226
|
+
if (missing.length > 0) {
|
|
3227
|
+
const named = missing.map((check) => {
|
|
3228
|
+
const paths = owedPaths(check);
|
|
3229
|
+
return paths === null
|
|
3230
|
+
? check
|
|
3231
|
+
: `${check} (it reads ${paths.slice(0, 5).join(', ')}${paths.length > 5 ? `, … (${paths.length})` : ''})`;
|
|
3232
|
+
});
|
|
3233
|
+
return `qaPassed with qa-run receipts needs a result for each check that reads the changed paths; missing ${named.join(' and ')}: add qa-run:<${missing[0]}-receipt.json> or tool:${missing[0]}.`;
|
|
3234
|
+
}
|
|
3104
3235
|
// T13580: receipts of one kind cover the change together; a check also
|
|
3105
3236
|
// recorded as a whole-project tool: run needs no receipt coverage.
|
|
3106
3237
|
const gap = qaRunUnionGapReason(atoms.filter((atom) => atom.kind === 'qa-run' &&
|
|
@@ -3240,18 +3371,43 @@ export function decisionOnlyRouteNote(gate, implemented) {
|
|
|
3240
3371
|
// ---------------------------------------------------------------------------
|
|
3241
3372
|
// Gate minimum evaluation
|
|
3242
3373
|
// ---------------------------------------------------------------------------
|
|
3374
|
+
/**
|
|
3375
|
+
* Whether a probed `deploy:` atom stands for `gate` (T14083): only for the
|
|
3376
|
+
* gates the project listed in `evidence.deploySatisfies` when it was verified.
|
|
3377
|
+
* Static gate requirements never name `deploy`, so no gate takes one by default.
|
|
3378
|
+
*/
|
|
3379
|
+
function deploySatisfiesGate(gate, atoms) {
|
|
3380
|
+
return atoms.some((atom) => atom.kind === 'deploy' && atom.satisfiesGates.includes(gate));
|
|
3381
|
+
}
|
|
3382
|
+
/**
|
|
3383
|
+
* The profile whose extra combinations apply to these atoms (T14187): the
|
|
3384
|
+
* task's, while every `files:` path is a document. A code path keeps the base
|
|
3385
|
+
* requirements, so a profiled change that touched code still owes a commit
|
|
3386
|
+
* or PR.
|
|
3387
|
+
*/
|
|
3388
|
+
function profileForAtoms(profile, atoms) {
|
|
3389
|
+
if (!profile)
|
|
3390
|
+
return null;
|
|
3391
|
+
const paths = atoms.flatMap((atom) => atom.kind === 'files' ? atom.files.map((f) => f.path) : []);
|
|
3392
|
+
return paths.every(isDocumentEvidencePath) ? profile : null;
|
|
3393
|
+
}
|
|
3243
3394
|
/**
|
|
3244
3395
|
* Check whether a set of validated atoms satisfies the minimum evidence
|
|
3245
3396
|
* required for a given gate.
|
|
3246
3397
|
*
|
|
3247
3398
|
* @param gate - The gate being verified
|
|
3248
3399
|
* @param atoms - Validated atoms
|
|
3400
|
+
* @param profile - The task's gate profile ({@link evidenceProfileOf}); its
|
|
3401
|
+
* extra combinations apply while every `files:` path is a document (T14187).
|
|
3249
3402
|
* @returns null when satisfied; otherwise the reason message
|
|
3250
3403
|
*
|
|
3251
3404
|
* @task T832
|
|
3405
|
+
* @task T14187
|
|
3252
3406
|
* @adr ADR-051 §2.3
|
|
3253
3407
|
*/
|
|
3254
|
-
export function checkGateEvidenceMinimum(gate, atoms) {
|
|
3408
|
+
export function checkGateEvidenceMinimum(gate, atoms, profile) {
|
|
3409
|
+
if (deploySatisfiesGate(gate, atoms))
|
|
3410
|
+
return null;
|
|
3255
3411
|
// Delegate to the SSoT in @cleocode/contracts (T10337). validateEvidenceForGate
|
|
3256
3412
|
// only inspects `.kind`; the post-validation EvidenceAtom union includes the
|
|
3257
3413
|
// `override` kind which is never present here (override-evidence flows skip
|
|
@@ -3267,7 +3423,7 @@ export function checkGateEvidenceMinimum(gate, atoms) {
|
|
|
3267
3423
|
const kind = atom.kind;
|
|
3268
3424
|
projected.push({ kind });
|
|
3269
3425
|
}
|
|
3270
|
-
const result = validateEvidenceForGate(gate, projected);
|
|
3426
|
+
const result = validateEvidenceForGate(gate, projected, profileForAtoms(profile, atoms));
|
|
3271
3427
|
return result.ok ? null : result.message;
|
|
3272
3428
|
}
|
|
3273
3429
|
/**
|
|
@@ -3281,12 +3437,16 @@ export function checkGateEvidenceMinimum(gate, atoms) {
|
|
|
3281
3437
|
*
|
|
3282
3438
|
* @param gate - The gate being verified
|
|
3283
3439
|
* @param atoms - Validated atoms
|
|
3440
|
+
* @param profile - The task's gate profile, as for {@link checkGateEvidenceMinimum} (T14187).
|
|
3284
3441
|
* @returns `null` when satisfied; `{ message, hint }` when not
|
|
3285
3442
|
*
|
|
3286
3443
|
* @task T9949
|
|
3444
|
+
* @task T14187
|
|
3287
3445
|
* @adr ADR-051 §2.3
|
|
3288
3446
|
*/
|
|
3289
|
-
export function checkGateEvidenceMinimumDetailed(gate, atoms) {
|
|
3447
|
+
export function checkGateEvidenceMinimumDetailed(gate, atoms, profile) {
|
|
3448
|
+
if (deploySatisfiesGate(gate, atoms))
|
|
3449
|
+
return null;
|
|
3290
3450
|
const projected = [];
|
|
3291
3451
|
for (const atom of atoms) {
|
|
3292
3452
|
if (atom.kind === 'override')
|
|
@@ -3294,7 +3454,7 @@ export function checkGateEvidenceMinimumDetailed(gate, atoms) {
|
|
|
3294
3454
|
const kind = atom.kind;
|
|
3295
3455
|
projected.push({ kind });
|
|
3296
3456
|
}
|
|
3297
|
-
const result = validateEvidenceForGate(gate, projected);
|
|
3457
|
+
const result = validateEvidenceForGate(gate, projected, profileForAtoms(profile, atoms));
|
|
3298
3458
|
return result.ok ? null : { message: result.message, hint: result.hint };
|
|
3299
3459
|
}
|
|
3300
3460
|
/**
|
|
@@ -3343,7 +3503,8 @@ export function composeGateEvidence(atoms, capturedBy, override, overrideReason,
|
|
|
3343
3503
|
return atom.kind === 'tool' ||
|
|
3344
3504
|
atom.kind === 'test-run' ||
|
|
3345
3505
|
atom.kind === 'qa-run' ||
|
|
3346
|
-
atom.kind === 'ci'
|
|
3506
|
+
atom.kind === 'ci' ||
|
|
3507
|
+
atom.kind === 'ci-run'
|
|
3347
3508
|
? [index]
|
|
3348
3509
|
: [];
|
|
3349
3510
|
});
|
|
@@ -3395,7 +3556,13 @@ export function composeGateEvidence(atoms, capturedBy, override, overrideReason,
|
|
|
3395
3556
|
* @task T13003
|
|
3396
3557
|
*/
|
|
3397
3558
|
export async function boundHeadGapReason(task, atoms, projectRoot) {
|
|
3398
|
-
|
|
3559
|
+
// T14083: a probed deployment is bound to the commit it serves.
|
|
3560
|
+
// T14149: a repeat-run is bound to the commit it tested.
|
|
3561
|
+
const runs = atoms.filter((a) => (a.kind === 'test-run' ||
|
|
3562
|
+
a.kind === 'qa-run' ||
|
|
3563
|
+
a.kind === 'tool' ||
|
|
3564
|
+
a.kind === 'deploy' ||
|
|
3565
|
+
a.kind === 'ci-run') &&
|
|
3399
3566
|
typeof a.headSha === 'string');
|
|
3400
3567
|
if (runs.length === 0)
|
|
3401
3568
|
return null;
|
|
@@ -3468,7 +3635,11 @@ export async function boundHeadGapReason(task, atoms, projectRoot) {
|
|
|
3468
3635
|
continue;
|
|
3469
3636
|
const bound = run.kind === 'tool'
|
|
3470
3637
|
? `tool:${run.tool} ran at ${head.slice(0, 12)}`
|
|
3471
|
-
:
|
|
3638
|
+
: run.kind === 'deploy'
|
|
3639
|
+
? `deploy:${run.provider}/${run.deploymentId} serves ${head.slice(0, 12)}`
|
|
3640
|
+
: run.kind === 'ci-run'
|
|
3641
|
+
? `ci-run:${run.runId} (${run.test} x${run.runs}) tested ${head.slice(0, 12)}`
|
|
3642
|
+
: `${run.kind}:${run.path} was bound at ${head.slice(0, 12)}`;
|
|
3472
3643
|
if (missing !== null) {
|
|
3473
3644
|
return (`${bound}, and git cannot read the commits that relate it to ${task.id}'s implemented ` +
|
|
3474
3645
|
`commit ${shas} (missing object, or a shallow clone: ${missing}). ` +
|
|
@@ -3483,6 +3654,15 @@ export async function boundHeadGapReason(task, atoms, projectRoot) {
|
|
|
3483
3654
|
'the branch was rebased, amended or squash-merged after implemented was recorded. ' +
|
|
3484
3655
|
`Re-record implemented (cleo verify ${task.id} --gate implemented ...), then the run.`);
|
|
3485
3656
|
}
|
|
3657
|
+
if (run.kind === 'ci-run') {
|
|
3658
|
+
return (`${bound}, which does not contain ${task.id}'s implemented commit ${shas}: dispatch ` +
|
|
3659
|
+
'repeat-run.yml on the default branch after the change merged, and record that run.');
|
|
3660
|
+
}
|
|
3661
|
+
if (run.kind === 'deploy') {
|
|
3662
|
+
return (`${bound}, which does not contain ${task.id}'s implemented commit ${shas} (neither an ` +
|
|
3663
|
+
'ancestor nor a patch-equivalent copy): that deployment predates or excludes the change. ' +
|
|
3664
|
+
'Deploy a build that contains it and probe that deployment.');
|
|
3665
|
+
}
|
|
3486
3666
|
return (`${bound}, which never saw ${task.id}'s implemented commit ${shas}: it tested other ` +
|
|
3487
3667
|
"work (another task's checkout, or a commit before the change). Run the tests on this " +
|
|
3488
3668
|
"task's checkout and record that report.");
|
|
@@ -3694,7 +3874,7 @@ export async function revalidateEvidence(evidence, projectRoot, gate, taskId, op
|
|
|
3694
3874
|
!(classifyEvidenceTask(context) !== 'code' &&
|
|
3695
3875
|
isDecisionOnlyImplementation(task.verification?.evidence?.implemented) &&
|
|
3696
3876
|
DECISION_ONLY_RELAXED_GATES.includes(gate))) {
|
|
3697
|
-
reason = checkGateEvidenceMinimum(gate, evidence.atoms);
|
|
3877
|
+
reason = checkGateEvidenceMinimum(gate, evidence.atoms, evidenceProfileOf(task));
|
|
3698
3878
|
}
|
|
3699
3879
|
if (!reason && evidence.scope) {
|
|
3700
3880
|
if (evidence.scope.taskId !== taskId || evidence.scope.gate !== gate)
|
|
@@ -3913,6 +4093,20 @@ export async function revalidateEvidence(evidence, projectRoot, gate, taskId, op
|
|
|
3913
4093
|
failed.push({ atom, reason: recheck.reason });
|
|
3914
4094
|
break;
|
|
3915
4095
|
}
|
|
4096
|
+
case 'ci-run': {
|
|
4097
|
+
// T14149: a re-run of the repeat-run that went red, or a run that moved
|
|
4098
|
+
// to another commit, no longer stands; containment is re-checked above.
|
|
4099
|
+
const { recheckCiRunAtom } = await import('../release/ci-run-evidence.js');
|
|
4100
|
+
const recheck = recheckCiRunAtom(atom, resolveEvidenceExecutionRoot(projectRoot));
|
|
4101
|
+
if (!recheck.ok)
|
|
4102
|
+
failed.push({ atom, reason: recheck.reason });
|
|
4103
|
+
break;
|
|
4104
|
+
}
|
|
4105
|
+
case 'deploy':
|
|
4106
|
+
// T14083: the probe is not re-run (the deployment may have moved on
|
|
4107
|
+
// since); the SHA containment it carries is re-checked above, through
|
|
4108
|
+
// boundHeadGapReason.
|
|
4109
|
+
break;
|
|
3916
4110
|
case 'pr':
|
|
3917
4111
|
// PR atoms capture (prNumber, mergedAt, mergeCommitSha) at verify
|
|
3918
4112
|
// time. A merged PR's mergedAt is immutable, so the atom is trusted
|