@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
|
@@ -0,0 +1,775 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cleo release cut <version>` — one-command release from the default branch's
|
|
3
|
+
* newest commit with green CI (T14102 · epic T14099 · owner decision D11209).
|
|
4
|
+
*
|
|
5
|
+
* Replaces plan → open → bump-PR → hand-pushed tag. The cut:
|
|
6
|
+
* 1. validates the CalVer version (strictly above every release tag and the
|
|
7
|
+
* default branch's package.json), then picks the newest first-parent
|
|
8
|
+
* commit of `origin/<default>` whose required checks
|
|
9
|
+
* (`release.prRequiredWorkflows`, app-pinned) all concluded `success` on
|
|
10
|
+
* its push run (an empty conclusion is not green), or verifies a pinned
|
|
11
|
+
* `--sha` the same way;
|
|
12
|
+
* 2. renders the notes from the changesets added since the previous release
|
|
13
|
+
* tag, refusing a changeset no merged pull request brought in;
|
|
14
|
+
* 3. reports the packages, new migrations and each named task's status
|
|
15
|
+
* (reported, never gated: green CI on the sha is the gate);
|
|
16
|
+
* 4. creates the annotated tag `v<version>` on that sha through the GitHub
|
|
17
|
+
* API. Its message carries the hand-written lead (`--lead`). The tag push
|
|
18
|
+
* starts `release.yml`, which writes the CHANGELOG section
|
|
19
|
+
* ({@link releaseChangelog}), publishes, and opens the CHANGELOG + version
|
|
20
|
+
* PR. No test suite reruns: CI already passed that tree.
|
|
21
|
+
*
|
|
22
|
+
* @task T14102
|
|
23
|
+
* @epic T14099
|
|
24
|
+
*/
|
|
25
|
+
import { execFileSync } from 'node:child_process';
|
|
26
|
+
import { appendFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync, } from 'node:fs';
|
|
27
|
+
import { tmpdir } from 'node:os';
|
|
28
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
29
|
+
import { engineError, engineSuccess } from '@cleocode/contracts/engine-result.js';
|
|
30
|
+
import { E_CHANGESET_YAML_INVALID, E_DOCS_ROT, E_GH_NOT_AUTHENTICATED, E_INVALID_STATE, E_RELEASE_NO_GREEN_COMMIT, E_RELEASE_NOT_AUTHORIZED, E_RELEASE_TAG_FAILED, E_RELEASE_UNMERGED_CHANGESET, ExitCode, } from '@cleocode/contracts/exit-codes.js';
|
|
31
|
+
import { parseChangesetDir } from '../changesets/index.js';
|
|
32
|
+
import { getConfigValue } from '../config/registry.js';
|
|
33
|
+
import { scanDocsRot } from '../docs/rot.js';
|
|
34
|
+
import { getTaskAccessor } from '../store/data-accessor.js';
|
|
35
|
+
import { sweepMergedClosures } from '../tasks/merge-close.js';
|
|
36
|
+
import { cleanupMergedWork } from '../worktree/merged-cleanup.js';
|
|
37
|
+
import { AUTHORIZED_BY_TRAILER, releaseAuthorizationFor } from './authorization.js';
|
|
38
|
+
import { aggregateChangesetsForRelease } from './changesets-aggregator.js';
|
|
39
|
+
import { newestReachableReleaseTag, writeChangelogSection } from './plan.js';
|
|
40
|
+
import { readRequiredCheckPins, resolveRequiredWorkflows } from './pr-evidence.js';
|
|
41
|
+
import { githubReleaseState } from './release-published.js';
|
|
42
|
+
import { readTtyConfirmation } from './tty-confirm.js';
|
|
43
|
+
import { normalizeVersion } from './version.js';
|
|
44
|
+
/** How many first-parent commits of the default branch the cut inspects for green CI. */
|
|
45
|
+
export const CUT_MAX_CANDIDATES = 30;
|
|
46
|
+
/** Per-cut audit log (ADR-099 §Consequences), relative to the project root. */
|
|
47
|
+
export const RELEASE_CUT_AUDIT_FILE = '.cleo/audit/release-cut.jsonl';
|
|
48
|
+
/** CalVer release version: `vYYYY.M.N`, optional `-suffix` (release.yml's Validate CalVer). */
|
|
49
|
+
const CALVER = /^v(\d{4})\.(\d{1,2})\.(\d+)(?:-([0-9A-Za-z.-]+))?$/;
|
|
50
|
+
const SUBPROCESS_TIMEOUT_MS = 60_000;
|
|
51
|
+
function defaultRunner() {
|
|
52
|
+
const run = (bin) => (args, cwd) => execFileSync(bin, [...args], {
|
|
53
|
+
cwd,
|
|
54
|
+
encoding: 'utf-8',
|
|
55
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
56
|
+
timeout: SUBPROCESS_TIMEOUT_MS,
|
|
57
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
58
|
+
}).trim();
|
|
59
|
+
return { runGh: run('gh'), runGit: run('git') };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The checks `release.prRequiredWorkflows` requires, with their pins, read from
|
|
63
|
+
* the tracked `.cleo/project-context.json` at `ref`: the tree being released
|
|
64
|
+
* decides what it must pass, not the caller's checkout.
|
|
65
|
+
*/
|
|
66
|
+
function requiredChecks(runner, cwd, ref) {
|
|
67
|
+
let context = null;
|
|
68
|
+
try {
|
|
69
|
+
context = JSON.parse(runner.runGit(['show', `${ref}:.cleo/project-context.json`], cwd));
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
context = null;
|
|
73
|
+
}
|
|
74
|
+
const pins = readRequiredCheckPins(context);
|
|
75
|
+
return resolveRequiredWorkflows(process.env, context).map((name) => ({ name, ...pins[name] }));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* `success` when every required check concluded `success` on `sha`'s push run
|
|
79
|
+
* of the default branch, else the first that did not (`<name>: <state>`). A
|
|
80
|
+
* workflow-pinned check is read from that workflow's `push` run only, so a pull
|
|
81
|
+
* request's run never counts; an unpinned one from the newest check run of
|
|
82
|
+
* that name posted by its pinned app (default `github-actions`).
|
|
83
|
+
*/
|
|
84
|
+
function requiredCiState(runner, cwd, sha, branch, checks) {
|
|
85
|
+
const jobsByWorkflow = new Map();
|
|
86
|
+
for (const check of checks) {
|
|
87
|
+
const app = check.app ?? 'github-actions';
|
|
88
|
+
const actions = app === 'github-actions' || app === 15368;
|
|
89
|
+
if (!check.workflow) {
|
|
90
|
+
const raw = runner.runGh([
|
|
91
|
+
'api',
|
|
92
|
+
`repos/{owner}/{repo}/commits/${sha}/check-runs?check_name=${encodeURIComponent(check.name)}&per_page=100`,
|
|
93
|
+
], cwd);
|
|
94
|
+
const listed = JSON.parse(raw);
|
|
95
|
+
const newest = (listed.check_runs ?? [])
|
|
96
|
+
.filter((r) => r.app?.slug === app || r.app?.id === app)
|
|
97
|
+
.sort((a, b) => b.id - a.id)[0];
|
|
98
|
+
const state = newest === undefined
|
|
99
|
+
? 'none'
|
|
100
|
+
: newest.status !== 'completed'
|
|
101
|
+
? newest.status
|
|
102
|
+
: (newest.conclusion ?? 'none');
|
|
103
|
+
if (state !== 'success')
|
|
104
|
+
return `${check.name}: ${state}`;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (!actions)
|
|
108
|
+
return `${check.name}: workflow check pinned to app ${app}`;
|
|
109
|
+
let jobs = jobsByWorkflow.get(check.workflow);
|
|
110
|
+
if (jobs === undefined) {
|
|
111
|
+
jobs = pushRunJobs(runner, cwd, sha, branch, check.workflow);
|
|
112
|
+
jobsByWorkflow.set(check.workflow, jobs);
|
|
113
|
+
}
|
|
114
|
+
if (typeof jobs === 'string')
|
|
115
|
+
return `${check.name}: ${jobs}`;
|
|
116
|
+
const state = jobs.get(check.name) ?? 'missing';
|
|
117
|
+
if (state !== 'success')
|
|
118
|
+
return `${check.name}: ${state}`;
|
|
119
|
+
}
|
|
120
|
+
return 'success';
|
|
121
|
+
}
|
|
122
|
+
/** Job name → state of the newest attempt of `workflow`'s push run on `sha`, or the run's own state. */
|
|
123
|
+
function pushRunJobs(runner, cwd, sha, branch, workflow) {
|
|
124
|
+
const file = basename(workflow);
|
|
125
|
+
const raw = runner.runGh([
|
|
126
|
+
'api',
|
|
127
|
+
`repos/{owner}/{repo}/actions/workflows/${file}/runs?head_sha=${sha}&event=push&branch=${branch}&per_page=10`,
|
|
128
|
+
], cwd);
|
|
129
|
+
const listed = JSON.parse(raw);
|
|
130
|
+
const run = (listed.workflow_runs ?? []).sort((a, b) => b.id - a.id)[0];
|
|
131
|
+
if (run === undefined)
|
|
132
|
+
return `no ${file} push run`;
|
|
133
|
+
if (run.status !== 'completed')
|
|
134
|
+
return run.status;
|
|
135
|
+
const jobs = new Map();
|
|
136
|
+
for (let page = 1; page <= 5; page++) {
|
|
137
|
+
const body = JSON.parse(runner.runGh([
|
|
138
|
+
'api',
|
|
139
|
+
`repos/{owner}/{repo}/actions/runs/${run.id}/attempts/${run.run_attempt ?? 1}/jobs?per_page=100&page=${page}`,
|
|
140
|
+
], cwd));
|
|
141
|
+
for (const job of body.jobs ?? []) {
|
|
142
|
+
jobs.set(job.name, job.status !== 'completed' ? job.status : (job.conclusion ?? 'none'));
|
|
143
|
+
}
|
|
144
|
+
if ((body.jobs ?? []).length < 100)
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
return jobs;
|
|
148
|
+
}
|
|
149
|
+
/** True when `sha` is a first-parent commit of `origin/<branch>` (ADR-099 §2.2). */
|
|
150
|
+
function isFirstParent(runner, cwd, sha, branch) {
|
|
151
|
+
return runner
|
|
152
|
+
.runGit(['rev-list', '--first-parent', `origin/${branch}`], cwd)
|
|
153
|
+
.split('\n')
|
|
154
|
+
.includes(sha);
|
|
155
|
+
}
|
|
156
|
+
/** Order two `v`-prefixed CalVer versions; a stable version sorts above its own prereleases. */
|
|
157
|
+
function compareCalVer(a, b) {
|
|
158
|
+
const pa = CALVER.exec(a);
|
|
159
|
+
const pb = CALVER.exec(b);
|
|
160
|
+
if (!pa || !pb)
|
|
161
|
+
return a.localeCompare(b);
|
|
162
|
+
for (const k of [1, 2, 3]) {
|
|
163
|
+
const d = Number(pa[k]) - Number(pb[k]);
|
|
164
|
+
if (d !== 0)
|
|
165
|
+
return d;
|
|
166
|
+
}
|
|
167
|
+
if (pa[4] === undefined || pb[4] === undefined)
|
|
168
|
+
return pa[4] === pb[4] ? 0 : pa[4] === undefined ? 1 : -1;
|
|
169
|
+
return pa[4].localeCompare(pb[4], undefined, { numeric: true });
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Why `version` cannot be released, or null: not CalVer, outside release.yml's
|
|
173
|
+
* month window, not above every release tag, or not above the default
|
|
174
|
+
* branch's package.json version.
|
|
175
|
+
*/
|
|
176
|
+
function versionProblem(runner, cwd, version, ref) {
|
|
177
|
+
const m = CALVER.exec(version);
|
|
178
|
+
if (!m)
|
|
179
|
+
return `${version} is not CalVer vYYYY.M.N[-suffix]`;
|
|
180
|
+
const now = new Date();
|
|
181
|
+
const year = now.getUTCFullYear();
|
|
182
|
+
const month = now.getUTCMonth() + 1;
|
|
183
|
+
const next = month === 12 ? { year: year + 1, month: 1 } : { year, month: month + 1 };
|
|
184
|
+
const [y, mo] = [Number(m[1]), Number(m[2])];
|
|
185
|
+
const current = y === year && mo === month;
|
|
186
|
+
if (m[4] === undefined ? !current : !current && !(y === next.year && mo === next.month)) {
|
|
187
|
+
return `${version} is outside release.yml's CalVer window (${year}.${month}${m[4] === undefined ? '' : ` or ${next.year}.${next.month} for a prerelease`})`;
|
|
188
|
+
}
|
|
189
|
+
const newest = runner
|
|
190
|
+
.runGit(['tag', '--list', 'v*'], cwd)
|
|
191
|
+
.split('\n')
|
|
192
|
+
.map((t) => t.trim())
|
|
193
|
+
.filter((t) => CALVER.test(t))
|
|
194
|
+
.sort(compareCalVer)
|
|
195
|
+
.at(-1);
|
|
196
|
+
if (newest !== undefined && compareCalVer(version, newest) <= 0) {
|
|
197
|
+
return `${version} is not above the newest release tag ${newest}`;
|
|
198
|
+
}
|
|
199
|
+
let manifest = '';
|
|
200
|
+
try {
|
|
201
|
+
manifest = runner.runGit(['show', `${ref}:package.json`], cwd);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
const base = JSON.parse(manifest);
|
|
207
|
+
if (base.version && compareCalVer(version, normalizeVersion(base.version)) <= 0) {
|
|
208
|
+
return `${version} is not above package.json's ${base.version} on the default branch`;
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
/** `release.agentCut` levels, narrowest first: a level covers its own scope and every narrower one (D11221). */
|
|
213
|
+
const AGENT_CUT_LEVELS = ['canary', 'prerelease', 'stable'];
|
|
214
|
+
/**
|
|
215
|
+
* `release.agentCut` and the config file it came from, project before global,
|
|
216
|
+
* or null when unset, `off` or invalid. A project value (even `off`) shadows the global one.
|
|
217
|
+
*/
|
|
218
|
+
async function agentCutSetting(projectRoot) {
|
|
219
|
+
for (const scope of ['project', 'global']) {
|
|
220
|
+
const raw = await getConfigValue('release.agentCut', { scope, projectRoot });
|
|
221
|
+
if (raw === undefined)
|
|
222
|
+
continue;
|
|
223
|
+
const level = AGENT_CUT_LEVELS.find((l) => l === raw);
|
|
224
|
+
return level ? { level, source: scope } : null;
|
|
225
|
+
}
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
/** Append one row to {@link RELEASE_CUT_AUDIT_FILE}; an audit failure never changes the result. */
|
|
229
|
+
function auditCut(cwd, row) {
|
|
230
|
+
try {
|
|
231
|
+
const path = join(cwd, RELEASE_CUT_AUDIT_FILE);
|
|
232
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
233
|
+
appendFileSync(path, `${JSON.stringify({ at: new Date().toISOString(), ...row })}\n`, 'utf-8');
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
// non-fatal: the GitHub tag is the release's own record
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/** Changeset paths added in `from..to` (top-level `.changeset/*.md`, README excluded). */
|
|
240
|
+
function changesetPathsInRange(runner, cwd, from, to) {
|
|
241
|
+
const args = from
|
|
242
|
+
? ['diff', '--name-only', '--diff-filter=A', from, to, '--', '.changeset']
|
|
243
|
+
: ['ls-tree', '-r', '--name-only', to, '--', '.changeset'];
|
|
244
|
+
return runner
|
|
245
|
+
.runGit(args, cwd)
|
|
246
|
+
.split('\n')
|
|
247
|
+
.map((p) => p.trim())
|
|
248
|
+
.filter((p) => /^\.changeset\/[^/]+\.md$/.test(p) && basename(p) !== 'README.md')
|
|
249
|
+
.sort();
|
|
250
|
+
}
|
|
251
|
+
/** Parse the changesets as they are at `ref`, without touching the working tree. */
|
|
252
|
+
function parseChangesetsAt(runner, cwd, ref, paths) {
|
|
253
|
+
if (paths.length === 0)
|
|
254
|
+
return [];
|
|
255
|
+
const dir = mkdtempSync(join(tmpdir(), 'cleo-cut-changesets-'));
|
|
256
|
+
try {
|
|
257
|
+
for (const path of paths) {
|
|
258
|
+
writeFileSync(join(dir, basename(path)), runner.runGit(['show', `${ref}:${path}`], cwd));
|
|
259
|
+
}
|
|
260
|
+
return parseChangesetDir(dir);
|
|
261
|
+
}
|
|
262
|
+
finally {
|
|
263
|
+
rmSync(dir, { recursive: true, force: true });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/** Notes markdown: the lead, then the aggregated sections without their version header. */
|
|
267
|
+
function renderNotes(entries, version, date, lead) {
|
|
268
|
+
const body = aggregateChangesetsForRelease({ entries, version, date }).markdown.replace(/^\s*## [^\n]*\n+/, '');
|
|
269
|
+
return [lead.trim(), body.trim()].filter((part) => part.length > 0).join('\n\n');
|
|
270
|
+
}
|
|
271
|
+
function channelOf(version) {
|
|
272
|
+
return version.includes('-') ? 'prerelease' : 'latest';
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* The newest reachable release tag below `version` whose GitHub Release
|
|
276
|
+
* published (T14173), or `unknown` naming the first tag `gh` could not judge.
|
|
277
|
+
*/
|
|
278
|
+
function previousPublishedTag(runner, cwd, version, sha) {
|
|
279
|
+
let unknown = null;
|
|
280
|
+
const tag = newestReachableReleaseTag(cwd, version, channelOf(version), sha, (t) => {
|
|
281
|
+
const state = githubReleaseState(t, (args) => runner.runGh(args, cwd));
|
|
282
|
+
if (state === null)
|
|
283
|
+
unknown ??= t;
|
|
284
|
+
return state === true;
|
|
285
|
+
});
|
|
286
|
+
return unknown ? { unknown } : { tag };
|
|
287
|
+
}
|
|
288
|
+
/** The refusal when the previous release's publication cannot be read. */
|
|
289
|
+
function previousTagUnknown(tag) {
|
|
290
|
+
return engineError(E_INVALID_STATE, `Could not read the GitHub Release of ${tag}`, {
|
|
291
|
+
exitCode: ExitCode.GENERAL_ERROR,
|
|
292
|
+
fix: `Check \`gh release view ${tag}\` works (auth, network, remote), then re-run`,
|
|
293
|
+
details: { tag },
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
/** Packages `release.yml` publishes (`publish_pkg <dir> [npmName]` lines, gate 9 SSoT). */
|
|
297
|
+
function publishedPackages(runner, cwd, ref, version) {
|
|
298
|
+
const workflow = runner.runGit(['show', `${ref}:.github/workflows/release.yml`], cwd);
|
|
299
|
+
const packages = [];
|
|
300
|
+
for (const match of workflow.matchAll(/^\s*publish_pkg\s+([\w.-]+)(?:\s+(\S+))?\s*$/gm)) {
|
|
301
|
+
const dir = match[1] ?? '';
|
|
302
|
+
const manifest = JSON.parse(runner.runGit(['show', `${ref}:packages/${dir}/package.json`], cwd));
|
|
303
|
+
packages.push({ dir, name: match[2] ?? manifest.name ?? dir, version });
|
|
304
|
+
}
|
|
305
|
+
return packages;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Cut a release: tag the newest green default-branch commit and let
|
|
309
|
+
* `release.yml` publish it. `dryRun` reports without tagging.
|
|
310
|
+
*
|
|
311
|
+
* @param opts - See {@link ReleaseCutOptions}.
|
|
312
|
+
* @returns The chosen commit, notes, packages, migrations and task report.
|
|
313
|
+
*
|
|
314
|
+
* @example
|
|
315
|
+
* ```ts
|
|
316
|
+
* const preview = await releaseCut({ version: '2026.10.28', projectRoot, dryRun: true });
|
|
317
|
+
* if (preview.success) console.log(preview.data.sha, preview.data.notes);
|
|
318
|
+
* ```
|
|
319
|
+
*
|
|
320
|
+
* @task T14102
|
|
321
|
+
*/
|
|
322
|
+
export async function releaseCut(opts) {
|
|
323
|
+
const runner = opts.runner ?? defaultRunner();
|
|
324
|
+
// T14160: git work happens only in the invoking checkout; the store, audit
|
|
325
|
+
// and grants belong to the project.
|
|
326
|
+
const cwd = opts.gitRoot ?? opts.projectRoot;
|
|
327
|
+
const storeRoot = opts.projectRoot;
|
|
328
|
+
const version = normalizeVersion(opts.version);
|
|
329
|
+
const bare = version.slice(1);
|
|
330
|
+
const branch = opts.branch ?? 'main';
|
|
331
|
+
// D11212: nothing is tagged without --yes; without it the cut is a preview.
|
|
332
|
+
const dryRun = opts.dryRun === true || opts.yes !== true;
|
|
333
|
+
try {
|
|
334
|
+
runner.runGh(['auth', 'status'], cwd);
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
return engineError(E_GH_NOT_AUTHENTICATED, 'gh is not installed or not authenticated', {
|
|
338
|
+
exitCode: ExitCode.GENERAL_ERROR,
|
|
339
|
+
fix: 'gh auth login',
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
runner.runGit(['fetch', '--quiet', '--tags', 'origin', branch], cwd);
|
|
343
|
+
const remoteTag = runner.runGit(['ls-remote', '--tags', 'origin', `refs/tags/${version}`], cwd);
|
|
344
|
+
if (remoteTag.length > 0) {
|
|
345
|
+
return engineError(E_INVALID_STATE, `Tag ${version} already exists on origin`, {
|
|
346
|
+
exitCode: ExitCode.VALIDATION_ERROR,
|
|
347
|
+
fix: `Pick the next version, or re-run the release workflow for ${version}: gh workflow run release.yml -f version=${bare}`,
|
|
348
|
+
details: { tag: version },
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
const badVersion = versionProblem(runner, cwd, version, `origin/${branch}`);
|
|
352
|
+
if (badVersion !== null) {
|
|
353
|
+
return engineError(E_INVALID_STATE, badVersion, {
|
|
354
|
+
exitCode: ExitCode.VALIDATION_ERROR,
|
|
355
|
+
fix: 'Pick a CalVer version for this month above the newest release tag',
|
|
356
|
+
details: { version },
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
// T14101: close what just merged, so the task report below is current.
|
|
360
|
+
// The sweep runs gh and git in the project checkout; from another checkout
|
|
361
|
+
// it is skipped and named, never run there (T14160).
|
|
362
|
+
const sweep = cwd === storeRoot
|
|
363
|
+
? await (opts.sweep ?? sweepMergedClosures)(storeRoot, { dryRun, force: true })
|
|
364
|
+
: {
|
|
365
|
+
swept: false,
|
|
366
|
+
skippedReason: `cut runs in ${cwd}, not the project checkout ${storeRoot}; the merge sweep runs from the project checkout`,
|
|
367
|
+
dryRun,
|
|
368
|
+
prs: [],
|
|
369
|
+
closed: [],
|
|
370
|
+
bound: [],
|
|
371
|
+
refused: [],
|
|
372
|
+
waiting: 0,
|
|
373
|
+
reran: 0,
|
|
374
|
+
};
|
|
375
|
+
const checks = requiredChecks(runner, cwd, `origin/${branch}`);
|
|
376
|
+
const candidates = [];
|
|
377
|
+
let sha = null;
|
|
378
|
+
const pool = opts.sha
|
|
379
|
+
? [runner.runGit(['rev-parse', '--verify', `${opts.sha}^{commit}`], cwd).trim()]
|
|
380
|
+
: runner
|
|
381
|
+
.runGit(['rev-list', '--first-parent', `--max-count=${CUT_MAX_CANDIDATES}`, `origin/${branch}`], cwd)
|
|
382
|
+
.split('\n')
|
|
383
|
+
.filter((s) => s.length > 0);
|
|
384
|
+
if (opts.sha && !isFirstParent(runner, cwd, pool[0] ?? '', branch)) {
|
|
385
|
+
return engineError(E_INVALID_STATE, `${opts.sha} is not a first-parent commit of origin/${branch}`, {
|
|
386
|
+
exitCode: ExitCode.VALIDATION_ERROR,
|
|
387
|
+
fix: `Pin a merge commit on origin/${branch} (git log --first-parent origin/${branch}), or omit --sha to take the newest green one`,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
for (const candidate of pool) {
|
|
391
|
+
const ci = requiredCiState(runner, cwd, candidate, branch, checks);
|
|
392
|
+
candidates.push({ sha: candidate, ci });
|
|
393
|
+
if (ci === 'success') {
|
|
394
|
+
sha = candidate;
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (sha === null) {
|
|
399
|
+
return engineError(E_RELEASE_NO_GREEN_COMMIT, opts.sha
|
|
400
|
+
? `${opts.sha} is not green: ${candidates[0]?.ci ?? 'no checks'}`
|
|
401
|
+
: `None of the last ${pool.length} first-parent commits of origin/${branch} has every required check green (${checks.map((c) => c.name).join(', ')})`, {
|
|
402
|
+
exitCode: ExitCode.VALIDATION_ERROR,
|
|
403
|
+
fix: `Wait for the required checks to finish green on origin/${branch}, then re-run`,
|
|
404
|
+
details: { candidates },
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
const previous = previousPublishedTag(runner, cwd, version, sha);
|
|
408
|
+
if ('unknown' in previous)
|
|
409
|
+
return previousTagUnknown(previous.unknown);
|
|
410
|
+
const previousTag = previous.tag;
|
|
411
|
+
const paths = changesetPathsInRange(runner, cwd, previousTag, sha);
|
|
412
|
+
// A changeset must arrive through a merged pull request: that is what makes
|
|
413
|
+
// "green CI on the sha" the whole gate (D11209).
|
|
414
|
+
const unmerged = [];
|
|
415
|
+
for (const path of paths) {
|
|
416
|
+
const commit = runner
|
|
417
|
+
.runGit([
|
|
418
|
+
'log',
|
|
419
|
+
'--diff-filter=A',
|
|
420
|
+
'--format=%H',
|
|
421
|
+
'-1',
|
|
422
|
+
previousTag ? `${previousTag}..${sha}` : sha,
|
|
423
|
+
'--',
|
|
424
|
+
path,
|
|
425
|
+
], cwd)
|
|
426
|
+
.trim();
|
|
427
|
+
const pulls = JSON.parse(runner.runGh(['api', `repos/{owner}/{repo}/commits/${commit}/pulls`], cwd));
|
|
428
|
+
if (!pulls.some((pr) => pr.merged_at !== null))
|
|
429
|
+
unmerged.push({ path, commit });
|
|
430
|
+
}
|
|
431
|
+
if (unmerged.length > 0) {
|
|
432
|
+
return engineError(E_RELEASE_UNMERGED_CHANGESET, `${unmerged.length} changeset(s) did not arrive through a merged pull request: ${unmerged.map((u) => u.path).join(', ')}`, {
|
|
433
|
+
exitCode: ExitCode.VALIDATION_ERROR,
|
|
434
|
+
fix: 'Land each changeset through a reviewed pull request, then re-run',
|
|
435
|
+
details: { changesets: unmerged },
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
let entries;
|
|
439
|
+
try {
|
|
440
|
+
entries = parseChangesetsAt(runner, cwd, sha, paths);
|
|
441
|
+
}
|
|
442
|
+
catch (err) {
|
|
443
|
+
return engineError(E_CHANGESET_YAML_INVALID, err instanceof Error ? err.message : String(err), {
|
|
444
|
+
exitCode: ExitCode.VALIDATION_ERROR,
|
|
445
|
+
fix: 'Run `node scripts/lint-changesets.mjs`, fix the entry on the default branch, then re-run',
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
const lead = opts.leadFile ? readFileSync(opts.leadFile, 'utf-8') : '';
|
|
449
|
+
const notes = renderNotes(entries, version, new Date().toISOString().slice(0, 10), lead);
|
|
450
|
+
const migrations = runner
|
|
451
|
+
.runGit(previousTag
|
|
452
|
+
? [
|
|
453
|
+
'diff',
|
|
454
|
+
'--name-only',
|
|
455
|
+
'--diff-filter=A',
|
|
456
|
+
previousTag,
|
|
457
|
+
sha,
|
|
458
|
+
'--',
|
|
459
|
+
'packages/core/migrations',
|
|
460
|
+
]
|
|
461
|
+
: ['ls-tree', '-r', '--name-only', sha, '--', 'packages/core/migrations'], cwd)
|
|
462
|
+
.split('\n')
|
|
463
|
+
.filter((p) => p.length > 0);
|
|
464
|
+
const taskIds = [...new Set(entries.flatMap((entry) => entry.tasks))].sort();
|
|
465
|
+
const tasks = [];
|
|
466
|
+
const accessor = await getTaskAccessor(storeRoot);
|
|
467
|
+
try {
|
|
468
|
+
for (const id of taskIds) {
|
|
469
|
+
const task = await accessor.loadSingleTask(id);
|
|
470
|
+
tasks.push({ id, status: task?.status ?? 'missing' });
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
finally {
|
|
474
|
+
await accessor.close();
|
|
475
|
+
}
|
|
476
|
+
// T14111: a release does not ship stale, duplicate or orphaned docs.
|
|
477
|
+
// T14142: never fails the cut; an empty task set is skipped, because no
|
|
478
|
+
// ids would widen the cleanup to every done task.
|
|
479
|
+
const cleanupTasks = async (dry) => {
|
|
480
|
+
if (taskIds.length === 0)
|
|
481
|
+
return null;
|
|
482
|
+
// The cleanup judges against the project checkout; from another checkout
|
|
483
|
+
// it would run git there, so it is skipped and named instead (T14160).
|
|
484
|
+
if (cwd !== storeRoot) {
|
|
485
|
+
return {
|
|
486
|
+
dryRun: dry,
|
|
487
|
+
base: '',
|
|
488
|
+
worktrees: [],
|
|
489
|
+
localBranches: [],
|
|
490
|
+
remoteBranches: [],
|
|
491
|
+
nestedCopies: [],
|
|
492
|
+
bytesReclaimed: 0,
|
|
493
|
+
skippedReason: `cut runs in ${cwd}, not the project checkout ${storeRoot}; the merged-work cleanup runs from the project checkout`,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
try {
|
|
497
|
+
return await (opts.cleanup ?? cleanupMergedWork)({ projectRoot: cwd, taskIds, dryRun: dry });
|
|
498
|
+
}
|
|
499
|
+
catch (err) {
|
|
500
|
+
return {
|
|
501
|
+
dryRun: dry,
|
|
502
|
+
base: '',
|
|
503
|
+
worktrees: [],
|
|
504
|
+
localBranches: [],
|
|
505
|
+
remoteBranches: [],
|
|
506
|
+
nestedCopies: [],
|
|
507
|
+
bytesReclaimed: 0,
|
|
508
|
+
skippedReason: err instanceof Error ? err.message : String(err),
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
const docsRot = scanDocsRot({ projectRoot: cwd });
|
|
513
|
+
const rotFindings = docsRot.stale.length + docsRot.duplicates.length + docsRot.orphans.length;
|
|
514
|
+
const checkoutAtMain = runner.runGit(['rev-parse', 'HEAD'], cwd).trim() ===
|
|
515
|
+
runner.runGit(['rev-parse', `origin/${branch}`], cwd).trim();
|
|
516
|
+
const result = {
|
|
517
|
+
version,
|
|
518
|
+
tag: version,
|
|
519
|
+
sha,
|
|
520
|
+
previousTag,
|
|
521
|
+
candidates,
|
|
522
|
+
changesets: paths,
|
|
523
|
+
notes,
|
|
524
|
+
packages: publishedPackages(runner, cwd, sha, bare),
|
|
525
|
+
migrations,
|
|
526
|
+
tasks,
|
|
527
|
+
sweep,
|
|
528
|
+
cleanup: dryRun ? await cleanupTasks(true) : null,
|
|
529
|
+
docsRot,
|
|
530
|
+
warnings: rotFindings > 0 && channelOf(version) !== 'latest'
|
|
531
|
+
? [`Docs rot (does not block a prerelease): ${docsRot.summary}`]
|
|
532
|
+
: [],
|
|
533
|
+
checkoutAtMain,
|
|
534
|
+
confirmCommand: [
|
|
535
|
+
'cleo release cut',
|
|
536
|
+
version,
|
|
537
|
+
`--sha ${sha}`,
|
|
538
|
+
...(opts.leadFile ? [`--lead ${resolve(opts.leadFile)}`] : []),
|
|
539
|
+
'--yes',
|
|
540
|
+
].join(' '),
|
|
541
|
+
dryRun,
|
|
542
|
+
tagCreated: false,
|
|
543
|
+
allowedBy: null,
|
|
544
|
+
};
|
|
545
|
+
if (dryRun)
|
|
546
|
+
return engineSuccess(result);
|
|
547
|
+
const refuse = (code, message, fix, details) => {
|
|
548
|
+
auditCut(storeRoot, { version, sha, outcome: 'refused', code, reason: message });
|
|
549
|
+
return engineError(code, message, {
|
|
550
|
+
exitCode: ExitCode.VALIDATION_ERROR,
|
|
551
|
+
fix,
|
|
552
|
+
details,
|
|
553
|
+
});
|
|
554
|
+
};
|
|
555
|
+
if (!checkoutAtMain) {
|
|
556
|
+
return refuse(E_INVALID_STATE, `The local checkout is not at origin/${branch}, and the docs-rot gate scans it`, `git switch ${branch} && git pull --ff-only, then re-run`, { branch });
|
|
557
|
+
}
|
|
558
|
+
// A prerelease is a test release: docs rot warns there and blocks only stable.
|
|
559
|
+
if (rotFindings > 0 && channelOf(version) === 'latest') {
|
|
560
|
+
return refuse(E_DOCS_ROT, `Docs rot blocks the release: ${docsRot.summary}`, 'cleo docs rot (update the stale docs, merge the duplicates, fix or link the orphans), then re-run', { docsRot });
|
|
561
|
+
}
|
|
562
|
+
// ADR-099 §2.5: CI is judged again right before the tag, never from an old read.
|
|
563
|
+
const ciNow = requiredCiState(runner, cwd, sha, branch, checks);
|
|
564
|
+
if (ciNow !== 'success') {
|
|
565
|
+
return refuse(E_RELEASE_NO_GREEN_COMMIT, `${sha} is no longer green: ${ciNow}`, 'Wait for the required checks on the default branch, then re-run', { sha, ci: ciNow });
|
|
566
|
+
}
|
|
567
|
+
// D11212: a standing authorization, the `release.agentCut` setting (D11221),
|
|
568
|
+
// or the version typed on the controlling terminal.
|
|
569
|
+
const neededScope = channelOf(version) === 'latest'
|
|
570
|
+
? 'stable'
|
|
571
|
+
: /-canary\.\d+$/.test(version)
|
|
572
|
+
? 'canary'
|
|
573
|
+
: 'prerelease';
|
|
574
|
+
const authorization = await releaseAuthorizationFor(storeRoot, version, cwd);
|
|
575
|
+
const agentCut = authorization ? null : await agentCutSetting(storeRoot);
|
|
576
|
+
const settingCovers = agentCut !== null &&
|
|
577
|
+
AGENT_CUT_LEVELS.indexOf(agentCut.level) >= AGENT_CUT_LEVELS.indexOf(neededScope);
|
|
578
|
+
let allowedBy = authorization
|
|
579
|
+
? `authorization:${authorization.id}`
|
|
580
|
+
: settingCovers
|
|
581
|
+
? `setting:release.agentCut=${agentCut.level}@${agentCut.source}`
|
|
582
|
+
: '';
|
|
583
|
+
if (!allowedBy) {
|
|
584
|
+
const typed = readTtyConfirmation(`Tag ${version} on ${sha}? Type ${version} to confirm: `);
|
|
585
|
+
if (typed === null) {
|
|
586
|
+
const current = agentCut ? `${agentCut.level} (${agentCut.source})` : 'off';
|
|
587
|
+
return refuse(E_RELEASE_NOT_AUTHORIZED, `No terminal to confirm ${version}; release.agentCut is ${current}, which does not cover scope ${neededScope}, and no standing release authorization covers it`, `Allow agent cuts of ${neededScope} versions: cleo config set release.agentCut ${neededScope} --scope global`, { version, sha, scope: neededScope, agentCut: agentCut?.level ?? 'off' });
|
|
588
|
+
}
|
|
589
|
+
if (normalizeVersion(typed) !== version) {
|
|
590
|
+
return refuse(E_RELEASE_NOT_AUTHORIZED, `The confirmation "${typed}" does not match ${version}`, `Re-run \`${result.confirmCommand}\` and type ${version} exactly`, { version, sha });
|
|
591
|
+
}
|
|
592
|
+
allowedBy = 'tty-confirm';
|
|
593
|
+
}
|
|
594
|
+
// Created with the caller's token, the tag push triggers release.yml
|
|
595
|
+
// (a GITHUB_TOKEN-created tag would not).
|
|
596
|
+
let tagObject = '';
|
|
597
|
+
try {
|
|
598
|
+
const created = JSON.parse(runner.runGh([
|
|
599
|
+
'api',
|
|
600
|
+
'-X',
|
|
601
|
+
'POST',
|
|
602
|
+
'repos/{owner}/{repo}/git/tags',
|
|
603
|
+
'-f',
|
|
604
|
+
`tag=${version}`,
|
|
605
|
+
'-f',
|
|
606
|
+
`message=${[
|
|
607
|
+
lead.trim() || `Release ${version}`,
|
|
608
|
+
...(authorization ? [`${AUTHORIZED_BY_TRAILER}: ${authorization.id}`] : []),
|
|
609
|
+
].join('\n\n')}`,
|
|
610
|
+
'-f',
|
|
611
|
+
`object=${sha}`,
|
|
612
|
+
'-f',
|
|
613
|
+
'type=commit',
|
|
614
|
+
], cwd));
|
|
615
|
+
tagObject = created.sha;
|
|
616
|
+
runner.runGh([
|
|
617
|
+
'api',
|
|
618
|
+
'-X',
|
|
619
|
+
'POST',
|
|
620
|
+
'repos/{owner}/{repo}/git/refs',
|
|
621
|
+
'-f',
|
|
622
|
+
`ref=refs/tags/${version}`,
|
|
623
|
+
'-f',
|
|
624
|
+
`sha=${tagObject}`,
|
|
625
|
+
], cwd);
|
|
626
|
+
}
|
|
627
|
+
catch (err) {
|
|
628
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
629
|
+
auditCut(storeRoot, { version, sha, outcome: 'failed', tagObject: tagObject || null, reason });
|
|
630
|
+
return engineError(E_RELEASE_TAG_FAILED, `Creating tag ${version} failed: ${reason}`, {
|
|
631
|
+
exitCode: ExitCode.GENERAL_ERROR,
|
|
632
|
+
fix: tagObject
|
|
633
|
+
? `No refs/tags/${version} was created (the unreferenced tag object ${tagObject} is harmless); re-run the cut`
|
|
634
|
+
: 're-run the cut',
|
|
635
|
+
details: { sha, tagObject: tagObject || null },
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
auditCut(storeRoot, {
|
|
639
|
+
version,
|
|
640
|
+
sha,
|
|
641
|
+
outcome: 'tagged',
|
|
642
|
+
tagObject,
|
|
643
|
+
allowedBy,
|
|
644
|
+
scope: neededScope,
|
|
645
|
+
settingSource: settingCovers ? agentCut.source : null,
|
|
646
|
+
});
|
|
647
|
+
return engineSuccess({
|
|
648
|
+
...result,
|
|
649
|
+
cleanup: await cleanupTasks(false),
|
|
650
|
+
dryRun: false,
|
|
651
|
+
confirmCommand: null,
|
|
652
|
+
tagCreated: true,
|
|
653
|
+
allowedBy,
|
|
654
|
+
warnings: settingCovers
|
|
655
|
+
? [
|
|
656
|
+
...result.warnings,
|
|
657
|
+
`Tagged on the authority of release.agentCut=${agentCut.level} (${agentCut.source} config)`,
|
|
658
|
+
]
|
|
659
|
+
: result.warnings,
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Refuse to publish a tag whose commit is not a first-parent commit of
|
|
664
|
+
* `origin/<default>` with every required check green on its push run. Run by
|
|
665
|
+
* `release.yml` before anything ships, so a hand-pushed tag or a dispatched
|
|
666
|
+
* run never publishes an untested tree.
|
|
667
|
+
*
|
|
668
|
+
* @param opts - Version, project root, default branch, test seam.
|
|
669
|
+
* @returns The tag, its commit and the checks found green.
|
|
670
|
+
*
|
|
671
|
+
* @example
|
|
672
|
+
* ```ts
|
|
673
|
+
* const ok = await releaseVerifyTag({ version: '2026.10.28', projectRoot: process.cwd() });
|
|
674
|
+
* if (!ok.success) process.exit(1);
|
|
675
|
+
* ```
|
|
676
|
+
*
|
|
677
|
+
* @task T14102
|
|
678
|
+
*/
|
|
679
|
+
export async function releaseVerifyTag(opts) {
|
|
680
|
+
const runner = opts.runner ?? defaultRunner();
|
|
681
|
+
const cwd = opts.projectRoot;
|
|
682
|
+
const branch = opts.branch ?? 'main';
|
|
683
|
+
const tag = normalizeVersion(opts.version);
|
|
684
|
+
const sha = runner.runGit(['rev-parse', '--verify', `${tag}^{commit}`], cwd).trim();
|
|
685
|
+
if (!isFirstParent(runner, cwd, sha, branch)) {
|
|
686
|
+
return engineError(E_INVALID_STATE, `${tag} (${sha}) is not a first-parent commit of origin/${branch}`, {
|
|
687
|
+
exitCode: ExitCode.VALIDATION_ERROR,
|
|
688
|
+
fix: `Delete the tag (git push origin :refs/tags/${tag}) and cut from origin/${branch}: cleo release cut ${tag}`,
|
|
689
|
+
details: { tag, sha },
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
const checks = requiredChecks(runner, cwd, sha);
|
|
693
|
+
const ci = requiredCiState(runner, cwd, sha, branch, checks);
|
|
694
|
+
if (ci !== 'success') {
|
|
695
|
+
return engineError(E_RELEASE_NO_GREEN_COMMIT, `${tag} (${sha}) is not green: ${ci}`, {
|
|
696
|
+
exitCode: ExitCode.VALIDATION_ERROR,
|
|
697
|
+
fix: `Wait for the required checks on ${sha}, then re-run this workflow; or delete the tag (git push origin :refs/tags/${tag})`,
|
|
698
|
+
details: { tag, sha, ci },
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
return engineSuccess({ tag, sha, checks: checks.map((c) => c.name) });
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Write the `## [<version>]` CHANGELOG section from the changesets added since
|
|
705
|
+
* the previous release tag, led by the tag's annotation (the `--lead` the cut
|
|
706
|
+
* recorded). Runs in `release.yml` on the tagged checkout. A section that
|
|
707
|
+
* already exists (a plan-PR release) is left untouched.
|
|
708
|
+
*
|
|
709
|
+
* @param opts - See {@link ReleaseChangelogOptions}.
|
|
710
|
+
* @returns Whether the section was written, and from what range.
|
|
711
|
+
*
|
|
712
|
+
* @example
|
|
713
|
+
* ```ts
|
|
714
|
+
* await releaseChangelog({ version: '2026.10.28', projectRoot: process.cwd() });
|
|
715
|
+
* ```
|
|
716
|
+
*
|
|
717
|
+
* @task T14102
|
|
718
|
+
*/
|
|
719
|
+
export async function releaseChangelog(opts) {
|
|
720
|
+
const runner = opts.runner ?? defaultRunner();
|
|
721
|
+
const cwd = opts.projectRoot;
|
|
722
|
+
const version = normalizeVersion(opts.version);
|
|
723
|
+
const changelogPath = join(cwd, 'CHANGELOG.md');
|
|
724
|
+
const tagRef = `refs/tags/${version}`;
|
|
725
|
+
const tagged = runner.runGit(['tag', '--list', version], cwd).trim() === version;
|
|
726
|
+
const ref = opts.ref ?? (tagged ? version : 'HEAD');
|
|
727
|
+
const sha = runner.runGit(['rev-parse', '--verify', `${ref}^{commit}`], cwd).trim();
|
|
728
|
+
const previous = previousPublishedTag(runner, cwd, version, sha);
|
|
729
|
+
if ('unknown' in previous)
|
|
730
|
+
return previousTagUnknown(previous.unknown);
|
|
731
|
+
const previousTag = previous.tag;
|
|
732
|
+
let existing = '';
|
|
733
|
+
try {
|
|
734
|
+
existing = readFileSync(changelogPath, 'utf-8');
|
|
735
|
+
}
|
|
736
|
+
catch {
|
|
737
|
+
existing = '';
|
|
738
|
+
}
|
|
739
|
+
const headerRe = new RegExp(`^## \\[${version.slice(1).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\]`, 'm');
|
|
740
|
+
if (headerRe.test(existing)) {
|
|
741
|
+
return engineSuccess({ changelogPath, written: false, previousTag, changesetCount: 0 });
|
|
742
|
+
}
|
|
743
|
+
const paths = changesetPathsInRange(runner, cwd, previousTag, sha);
|
|
744
|
+
let entries;
|
|
745
|
+
try {
|
|
746
|
+
entries = parseChangesetsAt(runner, cwd, sha, paths);
|
|
747
|
+
}
|
|
748
|
+
catch (err) {
|
|
749
|
+
return engineError(E_CHANGESET_YAML_INVALID, err instanceof Error ? err.message : String(err), {
|
|
750
|
+
exitCode: ExitCode.VALIDATION_ERROR,
|
|
751
|
+
fix: 'Run `node scripts/lint-changesets.mjs`',
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
// The tag message is the lead plus, for an authorized cut, its trailer (T14141).
|
|
755
|
+
const lead = tagged
|
|
756
|
+
? runner
|
|
757
|
+
.runGit(['tag', '--list', '--format=%(contents)', version], cwd)
|
|
758
|
+
.replace(new RegExp(`^${AUTHORIZED_BY_TRAILER}: \\S+$`, 'm'), '')
|
|
759
|
+
: '';
|
|
760
|
+
// T14172: the UTC day, as release.yml's CalVer window counts it, not the tagger's zone.
|
|
761
|
+
const date = new Date(tagged
|
|
762
|
+
? Number(runner.runGit(['for-each-ref', '--format=%(creatordate:unix)', tagRef], cwd)) * 1000
|
|
763
|
+
: Date.now())
|
|
764
|
+
.toISOString()
|
|
765
|
+
.slice(0, 10);
|
|
766
|
+
const notes = renderNotes(entries, version, date, lead.trim() === `Release ${version}` ? '' : lead);
|
|
767
|
+
const { written } = await writeChangelogSection({
|
|
768
|
+
changelogPath,
|
|
769
|
+
version,
|
|
770
|
+
date,
|
|
771
|
+
notesMarkdown: notes,
|
|
772
|
+
});
|
|
773
|
+
return engineSuccess({ changelogPath, written, previousTag, changesetCount: entries.length });
|
|
774
|
+
}
|
|
775
|
+
//# sourceMappingURL=cut.js.map
|