approval-md 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +176 -0
- package/NOTICE +5 -0
- package/README.md +940 -4
- package/SPEC.md +476 -34
- package/cli.js +29 -3
- package/dist/src/adapters/agentmail.d.ts +426 -0
- package/dist/src/adapters/agentmail.js +1200 -0
- package/dist/src/adapters/agentmail.js.map +1 -0
- package/dist/src/adapters/conformance.d.ts +149 -0
- package/dist/src/adapters/conformance.js +461 -0
- package/dist/src/adapters/conformance.js.map +1 -0
- package/dist/src/adapters/contract.d.ts +628 -0
- package/dist/src/adapters/contract.js +1035 -0
- package/dist/src/adapters/contract.js.map +1 -0
- package/dist/src/adapters/email.d.ts +324 -0
- package/dist/src/adapters/email.js +749 -0
- package/dist/src/adapters/email.js.map +1 -0
- package/dist/src/adapters/env-passphrase.d.ts +93 -0
- package/dist/src/adapters/env-passphrase.js +132 -0
- package/dist/src/adapters/env-passphrase.js.map +1 -0
- package/dist/src/adapters/public.d.ts +11 -0
- package/dist/src/adapters/public.js +11 -0
- package/dist/src/adapters/public.js.map +1 -0
- package/dist/src/adapters/registry.d.ts +59 -0
- package/dist/src/adapters/registry.js +77 -0
- package/dist/src/adapters/registry.js.map +1 -0
- package/dist/src/adapters/smtp.d.ts +213 -0
- package/dist/src/adapters/smtp.js +499 -0
- package/dist/src/adapters/smtp.js.map +1 -0
- package/dist/src/adapters/vault-provider.d.ts +114 -0
- package/dist/src/adapters/vault-provider.js +161 -0
- package/dist/src/adapters/vault-provider.js.map +1 -0
- package/dist/src/adapters/zzz.d.ts +66 -0
- package/dist/src/adapters/zzz.js +299 -0
- package/dist/src/adapters/zzz.js.map +1 -0
- package/dist/src/channels/batch.d.ts +109 -0
- package/dist/src/channels/batch.js +121 -0
- package/dist/src/channels/batch.js.map +1 -0
- package/dist/src/channels/cli.d.ts +193 -0
- package/dist/src/channels/cli.js +468 -0
- package/dist/src/channels/cli.js.map +1 -0
- package/dist/src/channels/conformance.d.ts +92 -0
- package/dist/src/channels/conformance.js +445 -0
- package/dist/src/channels/conformance.js.map +1 -0
- package/dist/src/channels/contract.d.ts +623 -0
- package/dist/src/channels/contract.js +494 -0
- package/dist/src/channels/contract.js.map +1 -0
- package/dist/src/channels/payload-view.d.ts +35 -0
- package/dist/src/channels/payload-view.js +43 -0
- package/dist/src/channels/payload-view.js.map +1 -0
- package/dist/src/channels/render-queue.d.ts +149 -0
- package/dist/src/channels/render-queue.js +564 -0
- package/dist/src/channels/render-queue.js.map +1 -0
- package/dist/src/channels/tagging.d.ts +196 -0
- package/dist/src/channels/tagging.js +723 -0
- package/dist/src/channels/tagging.js.map +1 -0
- package/dist/src/channels/telegram.d.ts +1832 -0
- package/dist/src/channels/telegram.js +3190 -0
- package/dist/src/channels/telegram.js.map +1 -0
- package/dist/src/channels/web.d.ts +341 -0
- package/dist/src/channels/web.js +903 -0
- package/dist/src/channels/web.js.map +1 -0
- package/dist/src/cli/adapter.d.ts +90 -0
- package/dist/src/cli/adapter.js +288 -0
- package/dist/src/cli/adapter.js.map +1 -0
- package/dist/src/cli/amend.d.ts +59 -0
- package/dist/src/cli/amend.js +2171 -0
- package/dist/src/cli/amend.js.map +1 -0
- package/dist/src/cli/args.d.ts +43 -0
- package/dist/src/cli/args.js +86 -0
- package/dist/src/cli/args.js.map +1 -0
- package/dist/src/cli/attest.d.ts +41 -0
- package/dist/src/cli/attest.js +307 -0
- package/dist/src/cli/attest.js.map +1 -0
- package/dist/src/cli/audit-card.d.ts +62 -0
- package/dist/src/cli/audit-card.js +201 -0
- package/dist/src/cli/audit-card.js.map +1 -0
- package/dist/src/cli/audit.d.ts +59 -0
- package/dist/src/cli/audit.js +460 -0
- package/dist/src/cli/audit.js.map +1 -0
- package/dist/src/cli/channel-telegram.d.ts +806 -0
- package/dist/src/cli/channel-telegram.js +2063 -0
- package/dist/src/cli/channel-telegram.js.map +1 -0
- package/dist/src/cli/channel-web.d.ts +131 -0
- package/dist/src/cli/channel-web.js +357 -0
- package/dist/src/cli/channel-web.js.map +1 -0
- package/dist/src/cli/channel.d.ts +71 -0
- package/dist/src/cli/channel.js +438 -0
- package/dist/src/cli/channel.js.map +1 -0
- package/dist/src/cli/checkpoint-tap.d.ts +169 -0
- package/dist/src/cli/checkpoint-tap.js +238 -0
- package/dist/src/cli/checkpoint-tap.js.map +1 -0
- package/dist/src/cli/codex.d.ts +2 -0
- package/dist/src/cli/codex.js +172 -0
- package/dist/src/cli/codex.js.map +1 -0
- package/dist/src/cli/coverage.d.ts +61 -0
- package/dist/src/cli/coverage.js +343 -0
- package/dist/src/cli/coverage.js.map +1 -0
- package/dist/src/cli/daemon.d.ts +120 -0
- package/dist/src/cli/daemon.js +631 -0
- package/dist/src/cli/daemon.js.map +1 -0
- package/dist/src/cli/doctor.d.ts +129 -0
- package/dist/src/cli/doctor.js +2762 -0
- package/dist/src/cli/doctor.js.map +1 -0
- package/dist/src/cli/env.d.ts +65 -0
- package/dist/src/cli/env.js +302 -0
- package/dist/src/cli/env.js.map +1 -0
- package/dist/src/cli/execute.d.ts +202 -0
- package/dist/src/cli/execute.js +1682 -0
- package/dist/src/cli/execute.js.map +1 -0
- package/dist/src/cli/exit-codes.d.ts +73 -0
- package/dist/src/cli/exit-codes.js +82 -0
- package/dist/src/cli/exit-codes.js.map +1 -0
- package/dist/src/cli/feedback.d.ts +60 -0
- package/dist/src/cli/feedback.js +205 -0
- package/dist/src/cli/feedback.js.map +1 -0
- package/dist/src/cli/gate-window.d.ts +40 -0
- package/dist/src/cli/gate-window.js +294 -0
- package/dist/src/cli/gate-window.js.map +1 -0
- package/dist/src/cli/gate.d.ts +68 -0
- package/dist/src/cli/gate.js +557 -0
- package/dist/src/cli/gate.js.map +1 -0
- package/dist/src/cli/git-scope.d.ts +190 -0
- package/dist/src/cli/git-scope.js +295 -0
- package/dist/src/cli/git-scope.js.map +1 -0
- package/dist/src/cli/gloss-attach.d.ts +85 -0
- package/dist/src/cli/gloss-attach.js +107 -0
- package/dist/src/cli/gloss-attach.js.map +1 -0
- package/dist/src/cli/gloss-codex-child.d.ts +9 -0
- package/dist/src/cli/gloss-codex-child.js +149 -0
- package/dist/src/cli/gloss-codex-child.js.map +1 -0
- package/dist/src/cli/gloss-codex.d.ts +24 -0
- package/dist/src/cli/gloss-codex.js +255 -0
- package/dist/src/cli/gloss-codex.js.map +1 -0
- package/dist/src/cli/gloss-options.d.ts +42 -0
- package/dist/src/cli/gloss-options.js +79 -0
- package/dist/src/cli/gloss-options.js.map +1 -0
- package/dist/src/cli/gloss.d.ts +265 -0
- package/dist/src/cli/gloss.js +362 -0
- package/dist/src/cli/gloss.js.map +1 -0
- package/dist/src/cli/help.d.ts +103 -0
- package/dist/src/cli/help.js +2339 -0
- package/dist/src/cli/help.js.map +1 -0
- package/dist/src/cli/hook-codex.d.ts +78 -0
- package/dist/src/cli/hook-codex.js +167 -0
- package/dist/src/cli/hook-codex.js.map +1 -0
- package/dist/src/cli/hook.d.ts +331 -0
- package/dist/src/cli/hook.js +2849 -0
- package/dist/src/cli/hook.js.map +1 -0
- package/dist/src/cli/import.d.ts +35 -0
- package/dist/src/cli/import.js +175 -0
- package/dist/src/cli/import.js.map +1 -0
- package/dist/src/cli/init.d.ts +84 -0
- package/dist/src/cli/init.js +336 -0
- package/dist/src/cli/init.js.map +1 -0
- package/dist/src/cli/instructions.d.ts +23 -0
- package/dist/src/cli/instructions.js +262 -0
- package/dist/src/cli/instructions.js.map +1 -0
- package/dist/src/cli/journal.d.ts +41 -0
- package/dist/src/cli/journal.js +238 -0
- package/dist/src/cli/journal.js.map +1 -0
- package/dist/src/cli/log-advance.d.ts +287 -0
- package/dist/src/cli/log-advance.js +840 -0
- package/dist/src/cli/log-advance.js.map +1 -0
- package/dist/src/cli/log-anchor.d.ts +176 -0
- package/dist/src/cli/log-anchor.js +387 -0
- package/dist/src/cli/log-anchor.js.map +1 -0
- package/dist/src/cli/log-checkpoint.d.ts +22 -0
- package/dist/src/cli/log-checkpoint.js +128 -0
- package/dist/src/cli/log-checkpoint.js.map +1 -0
- package/dist/src/cli/log-sync.d.ts +243 -0
- package/dist/src/cli/log-sync.js +849 -0
- package/dist/src/cli/log-sync.js.map +1 -0
- package/dist/src/cli/log-verbs.d.ts +16 -0
- package/dist/src/cli/log-verbs.js +360 -0
- package/dist/src/cli/log-verbs.js.map +1 -0
- package/dist/src/cli/long-help.d.ts +70 -0
- package/dist/src/cli/long-help.js +148 -0
- package/dist/src/cli/long-help.js.map +1 -0
- package/dist/src/cli/main.d.ts +77 -0
- package/dist/src/cli/main.js +1206 -0
- package/dist/src/cli/main.js.map +1 -0
- package/dist/src/cli/mcp.d.ts +52 -0
- package/dist/src/cli/mcp.js +306 -0
- package/dist/src/cli/mcp.js.map +1 -0
- package/dist/src/cli/paths.d.ts +56 -0
- package/dist/src/cli/paths.js +79 -0
- package/dist/src/cli/paths.js.map +1 -0
- package/dist/src/cli/payload.d.ts +58 -0
- package/dist/src/cli/payload.js +253 -0
- package/dist/src/cli/payload.js.map +1 -0
- package/dist/src/cli/policy.d.ts +43 -0
- package/dist/src/cli/policy.js +229 -0
- package/dist/src/cli/policy.js.map +1 -0
- package/dist/src/cli/preflight.d.ts +363 -0
- package/dist/src/cli/preflight.js +1175 -0
- package/dist/src/cli/preflight.js.map +1 -0
- package/dist/src/cli/progress.d.ts +78 -0
- package/dist/src/cli/progress.js +112 -0
- package/dist/src/cli/progress.js.map +1 -0
- package/dist/src/cli/prompt.d.ts +209 -0
- package/dist/src/cli/prompt.js +312 -0
- package/dist/src/cli/prompt.js.map +1 -0
- package/dist/src/cli/quickstart.d.ts +46 -0
- package/dist/src/cli/quickstart.js +297 -0
- package/dist/src/cli/quickstart.js.map +1 -0
- package/dist/src/cli/records.d.ts +34 -0
- package/dist/src/cli/records.js +66 -0
- package/dist/src/cli/records.js.map +1 -0
- package/dist/src/cli/render.d.ts +22 -0
- package/dist/src/cli/render.js +132 -0
- package/dist/src/cli/render.js.map +1 -0
- package/dist/src/cli/sandbox.d.ts +51 -0
- package/dist/src/cli/sandbox.js +150 -0
- package/dist/src/cli/sandbox.js.map +1 -0
- package/dist/src/cli/scaffold.d.ts +79 -0
- package/dist/src/cli/scaffold.js +137 -0
- package/dist/src/cli/scaffold.js.map +1 -0
- package/dist/src/cli/setup-adapter.d.ts +137 -0
- package/dist/src/cli/setup-adapter.js +509 -0
- package/dist/src/cli/setup-adapter.js.map +1 -0
- package/dist/src/cli/setup-channel.d.ts +117 -0
- package/dist/src/cli/setup-channel.js +635 -0
- package/dist/src/cli/setup-channel.js.map +1 -0
- package/dist/src/cli/setup-checkpoint.d.ts +57 -0
- package/dist/src/cli/setup-checkpoint.js +196 -0
- package/dist/src/cli/setup-checkpoint.js.map +1 -0
- package/dist/src/cli/setup-common.d.ts +275 -0
- package/dist/src/cli/setup-common.js +376 -0
- package/dist/src/cli/setup-common.js.map +1 -0
- package/dist/src/cli/setup-flow.d.ts +287 -0
- package/dist/src/cli/setup-flow.js +476 -0
- package/dist/src/cli/setup-flow.js.map +1 -0
- package/dist/src/cli/setup-service.d.ts +96 -0
- package/dist/src/cli/setup-service.js +308 -0
- package/dist/src/cli/setup-service.js.map +1 -0
- package/dist/src/cli/setup.d.ts +202 -0
- package/dist/src/cli/setup.js +473 -0
- package/dist/src/cli/setup.js.map +1 -0
- package/dist/src/cli/style.d.ts +320 -0
- package/dist/src/cli/style.js +469 -0
- package/dist/src/cli/style.js.map +1 -0
- package/dist/src/cli/token.d.ts +39 -0
- package/dist/src/cli/token.js +274 -0
- package/dist/src/cli/token.js.map +1 -0
- package/dist/src/cli/up.d.ts +155 -0
- package/dist/src/cli/up.js +849 -0
- package/dist/src/cli/up.js.map +1 -0
- package/dist/src/cli/usage.d.ts +37 -0
- package/dist/src/cli/usage.js +91 -0
- package/dist/src/cli/usage.js.map +1 -0
- package/dist/src/cli/values.d.ts +40 -0
- package/dist/src/cli/values.js +189 -0
- package/dist/src/cli/values.js.map +1 -0
- package/dist/src/cli/vault.d.ts +59 -0
- package/dist/src/cli/vault.js +362 -0
- package/dist/src/cli/vault.js.map +1 -0
- package/dist/src/cli/verb-registry.d.ts +76 -0
- package/dist/src/cli/verb-registry.js +2341 -0
- package/dist/src/cli/verb-registry.js.map +1 -0
- package/dist/src/cli/wordmark.d.ts +31 -0
- package/dist/src/cli/wordmark.js +52 -0
- package/dist/src/cli/wordmark.js.map +1 -0
- package/dist/src/codex/doctor.d.ts +13 -0
- package/dist/src/codex/doctor.js +41 -0
- package/dist/src/codex/doctor.js.map +1 -0
- package/dist/src/codex/manifest.d.ts +49 -0
- package/dist/src/codex/manifest.js +103 -0
- package/dist/src/codex/manifest.js.map +1 -0
- package/dist/src/codex/templates.d.ts +41 -0
- package/dist/src/codex/templates.js +319 -0
- package/dist/src/codex/templates.js.map +1 -0
- package/dist/src/codex/trust.d.ts +19 -0
- package/dist/src/codex/trust.js +183 -0
- package/dist/src/codex/trust.js.map +1 -0
- package/dist/src/codex/workspace-plan.d.ts +131 -0
- package/dist/src/codex/workspace-plan.js +561 -0
- package/dist/src/codex/workspace-plan.js.map +1 -0
- package/dist/src/core/actor.d.ts +2 -0
- package/dist/src/core/actor.js +5 -0
- package/dist/src/core/actor.js.map +1 -0
- package/dist/src/core/advance-cycle.d.ts +170 -0
- package/dist/src/core/advance-cycle.js +200 -0
- package/dist/src/core/advance-cycle.js.map +1 -0
- package/dist/src/core/agents-md.d.ts +276 -0
- package/dist/src/core/agents-md.js +747 -0
- package/dist/src/core/agents-md.js.map +1 -0
- package/dist/src/core/apply-patch.d.ts +49 -0
- package/dist/src/core/apply-patch.js +266 -0
- package/dist/src/core/apply-patch.js.map +1 -0
- package/dist/src/core/attest.d.ts +420 -0
- package/dist/src/core/attest.js +589 -0
- package/dist/src/core/attest.js.map +1 -0
- package/dist/src/core/audit.d.ts +492 -0
- package/dist/src/core/audit.js +882 -0
- package/dist/src/core/audit.js.map +1 -0
- package/dist/src/core/budgets.d.ts +238 -0
- package/dist/src/core/budgets.js +449 -0
- package/dist/src/core/budgets.js.map +1 -0
- package/dist/src/core/checkpoint.d.ts +500 -0
- package/dist/src/core/checkpoint.js +738 -0
- package/dist/src/core/checkpoint.js.map +1 -0
- package/dist/src/core/child-env.d.ts +88 -0
- package/dist/src/core/child-env.js +86 -0
- package/dist/src/core/child-env.js.map +1 -0
- package/dist/src/core/clock.d.ts +52 -0
- package/dist/src/core/clock.js +43 -0
- package/dist/src/core/clock.js.map +1 -0
- package/dist/src/core/command-class.d.ts +543 -0
- package/dist/src/core/command-class.js +2356 -0
- package/dist/src/core/command-class.js.map +1 -0
- package/dist/src/core/coverage-sources/adapter.d.ts +40 -0
- package/dist/src/core/coverage-sources/adapter.js +71 -0
- package/dist/src/core/coverage-sources/adapter.js.map +1 -0
- package/dist/src/core/coverage-sources/gh.d.ts +48 -0
- package/dist/src/core/coverage-sources/gh.js +136 -0
- package/dist/src/core/coverage-sources/gh.js.map +1 -0
- package/dist/src/core/coverage-sources/git.d.ts +101 -0
- package/dist/src/core/coverage-sources/git.js +269 -0
- package/dist/src/core/coverage-sources/git.js.map +1 -0
- package/dist/src/core/coverage.d.ts +217 -0
- package/dist/src/core/coverage.js +337 -0
- package/dist/src/core/coverage.js.map +1 -0
- package/dist/src/core/credential-spec.d.ts +72 -0
- package/dist/src/core/credential-spec.js +23 -0
- package/dist/src/core/credential-spec.js.map +1 -0
- package/dist/src/core/dark-session.d.ts +331 -0
- package/dist/src/core/dark-session.js +714 -0
- package/dist/src/core/dark-session.js.map +1 -0
- package/dist/src/core/decision-refusal.d.ts +185 -0
- package/dist/src/core/decision-refusal.js +265 -0
- package/dist/src/core/decision-refusal.js.map +1 -0
- package/dist/src/core/env-file.d.ts +450 -0
- package/dist/src/core/env-file.js +837 -0
- package/dist/src/core/env-file.js.map +1 -0
- package/dist/src/core/execute.d.ts +858 -0
- package/dist/src/core/execute.js +1271 -0
- package/dist/src/core/execute.js.map +1 -0
- package/dist/src/core/frontmatter.d.ts +78 -0
- package/dist/src/core/frontmatter.js +100 -0
- package/dist/src/core/frontmatter.js.map +1 -0
- package/dist/src/core/gate-window.d.ts +312 -0
- package/dist/src/core/gate-window.js +506 -0
- package/dist/src/core/gate-window.js.map +1 -0
- package/dist/src/core/gate.d.ts +1364 -0
- package/dist/src/core/gate.js +3002 -0
- package/dist/src/core/gate.js.map +1 -0
- package/dist/src/core/git-run.d.ts +73 -0
- package/dist/src/core/git-run.js +93 -0
- package/dist/src/core/git-run.js.map +1 -0
- package/dist/src/core/harness-version.d.ts +157 -0
- package/dist/src/core/harness-version.js +211 -0
- package/dist/src/core/harness-version.js.map +1 -0
- package/dist/src/core/harness-wait.d.ts +55 -0
- package/dist/src/core/harness-wait.js +58 -0
- package/dist/src/core/harness-wait.js.map +1 -0
- package/dist/src/core/head-retry.d.ts +107 -0
- package/dist/src/core/head-retry.js +121 -0
- package/dist/src/core/head-retry.js.map +1 -0
- package/dist/src/core/instance.d.ts +253 -0
- package/dist/src/core/instance.js +319 -0
- package/dist/src/core/instance.js.map +1 -0
- package/dist/src/core/intake-limits.d.ts +247 -0
- package/dist/src/core/intake-limits.js +350 -0
- package/dist/src/core/intake-limits.js.map +1 -0
- package/dist/src/core/jcs.d.ts +52 -0
- package/dist/src/core/jcs.js +132 -0
- package/dist/src/core/jcs.js.map +1 -0
- package/dist/src/core/journal.d.ts +144 -0
- package/dist/src/core/journal.js +200 -0
- package/dist/src/core/journal.js.map +1 -0
- package/dist/src/core/live-draw.d.ts +436 -0
- package/dist/src/core/live-draw.js +703 -0
- package/dist/src/core/live-draw.js.map +1 -0
- package/dist/src/core/log-reconcile.d.ts +89 -0
- package/dist/src/core/log-reconcile.js +136 -0
- package/dist/src/core/log-reconcile.js.map +1 -0
- package/dist/src/core/log-subscribe.d.ts +36 -0
- package/dist/src/core/log-subscribe.js +162 -0
- package/dist/src/core/log-subscribe.js.map +1 -0
- package/dist/src/core/log.d.ts +278 -0
- package/dist/src/core/log.js +546 -0
- package/dist/src/core/log.js.map +1 -0
- package/dist/src/core/loop.d.ts +274 -0
- package/dist/src/core/loop.js +487 -0
- package/dist/src/core/loop.js.map +1 -0
- package/dist/src/core/md-fence.d.ts +41 -0
- package/dist/src/core/md-fence.js +74 -0
- package/dist/src/core/md-fence.js.map +1 -0
- package/dist/src/core/money.d.ts +147 -0
- package/dist/src/core/money.js +195 -0
- package/dist/src/core/money.js.map +1 -0
- package/dist/src/core/payload-census.d.ts +74 -0
- package/dist/src/core/payload-census.js +146 -0
- package/dist/src/core/payload-census.js.map +1 -0
- package/dist/src/core/payload-store.d.ts +175 -0
- package/dist/src/core/payload-store.js +340 -0
- package/dist/src/core/payload-store.js.map +1 -0
- package/dist/src/core/payload.d.ts +71 -0
- package/dist/src/core/payload.js +80 -0
- package/dist/src/core/payload.js.map +1 -0
- package/dist/src/core/policy-diff.d.ts +292 -0
- package/dist/src/core/policy-diff.js +588 -0
- package/dist/src/core/policy-diff.js.map +1 -0
- package/dist/src/core/policy-expectations.d.ts +199 -0
- package/dist/src/core/policy-expectations.js +394 -0
- package/dist/src/core/policy-expectations.js.map +1 -0
- package/dist/src/core/policy-explain.d.ts +150 -0
- package/dist/src/core/policy-explain.js +258 -0
- package/dist/src/core/policy-explain.js.map +1 -0
- package/dist/src/core/policy-load.d.ts +527 -0
- package/dist/src/core/policy-load.js +536 -0
- package/dist/src/core/policy-load.js.map +1 -0
- package/dist/src/core/policy-match.d.ts +281 -0
- package/dist/src/core/policy-match.js +478 -0
- package/dist/src/core/policy-match.js.map +1 -0
- package/dist/src/core/policy-proposal.d.ts +265 -0
- package/dist/src/core/policy-proposal.js +458 -0
- package/dist/src/core/policy-proposal.js.map +1 -0
- package/dist/src/core/prompt-layout.d.ts +221 -0
- package/dist/src/core/prompt-layout.js +422 -0
- package/dist/src/core/prompt-layout.js.map +1 -0
- package/dist/src/core/protected-path-guard.d.ts +453 -0
- package/dist/src/core/protected-path-guard.js +1566 -0
- package/dist/src/core/protected-path-guard.js.map +1 -0
- package/dist/src/core/registration.d.ts +25 -0
- package/dist/src/core/registration.js +39 -0
- package/dist/src/core/registration.js.map +1 -0
- package/dist/src/core/reindex.d.ts +99 -0
- package/dist/src/core/reindex.js +336 -0
- package/dist/src/core/reindex.js.map +1 -0
- package/dist/src/core/sampler.d.ts +313 -0
- package/dist/src/core/sampler.js +388 -0
- package/dist/src/core/sampler.js.map +1 -0
- package/dist/src/core/sandbox.d.ts +290 -0
- package/dist/src/core/sandbox.js +424 -0
- package/dist/src/core/sandbox.js.map +1 -0
- package/dist/src/core/seal.d.ts +165 -0
- package/dist/src/core/seal.js +290 -0
- package/dist/src/core/seal.js.map +1 -0
- package/dist/src/core/state.d.ts +505 -0
- package/dist/src/core/state.js +1009 -0
- package/dist/src/core/state.js.map +1 -0
- package/dist/src/core/task-file.d.ts +185 -0
- package/dist/src/core/task-file.js +464 -0
- package/dist/src/core/task-file.js.map +1 -0
- package/dist/src/core/telegram-config.d.ts +93 -0
- package/dist/src/core/telegram-config.js +114 -0
- package/dist/src/core/telegram-config.js.map +1 -0
- package/dist/src/core/token.d.ts +409 -0
- package/dist/src/core/token.js +561 -0
- package/dist/src/core/token.js.map +1 -0
- package/dist/src/core/validate.d.ts +138 -0
- package/dist/src/core/validate.js +0 -0
- package/dist/src/core/validate.js.map +1 -0
- package/dist/src/core/values.d.ts +137 -0
- package/dist/src/core/values.js +153 -0
- package/dist/src/core/values.js.map +1 -0
- package/dist/src/core/vault.d.ts +291 -0
- package/dist/src/core/vault.js +612 -0
- package/dist/src/core/vault.js.map +1 -0
- package/dist/src/core/verified-snapshot.d.ts +204 -0
- package/dist/src/core/verified-snapshot.js +506 -0
- package/dist/src/core/verified-snapshot.js.map +1 -0
- package/dist/src/core/verify.d.ts +336 -0
- package/dist/src/core/verify.js +549 -0
- package/dist/src/core/verify.js.map +1 -0
- package/dist/src/core/version.d.ts +8 -0
- package/dist/src/core/version.js +9 -0
- package/dist/src/core/version.js.map +1 -0
- package/dist/src/core/wysiwys.d.ts +370 -0
- package/dist/src/core/wysiwys.js +728 -0
- package/dist/src/core/wysiwys.js.map +1 -0
- package/dist/src/daemon/advance-child.d.ts +39 -0
- package/dist/src/daemon/advance-child.js +78 -0
- package/dist/src/daemon/advance-child.js.map +1 -0
- package/dist/src/daemon/advance.d.ts +466 -0
- package/dist/src/daemon/advance.js +849 -0
- package/dist/src/daemon/advance.js.map +1 -0
- package/dist/src/daemon/audit.d.ts +87 -0
- package/dist/src/daemon/audit.js +90 -0
- package/dist/src/daemon/audit.js.map +1 -0
- package/dist/src/daemon/daemon.d.ts +1180 -0
- package/dist/src/daemon/daemon.js +1988 -0
- package/dist/src/daemon/daemon.js.map +1 -0
- package/dist/src/daemon/dark-session.d.ts +64 -0
- package/dist/src/daemon/dark-session.js +119 -0
- package/dist/src/daemon/dark-session.js.map +1 -0
- package/dist/src/daemon/draw-child.d.ts +36 -0
- package/dist/src/daemon/draw-child.js +132 -0
- package/dist/src/daemon/draw-child.js.map +1 -0
- package/dist/src/daemon/draw.d.ts +154 -0
- package/dist/src/daemon/draw.js +458 -0
- package/dist/src/daemon/draw.js.map +1 -0
- package/dist/src/daemon/git-evidence.d.ts +173 -0
- package/dist/src/daemon/git-evidence.js +345 -0
- package/dist/src/daemon/git-evidence.js.map +1 -0
- package/dist/src/daemon/projection.d.ts +180 -0
- package/dist/src/daemon/projection.js +233 -0
- package/dist/src/daemon/projection.js.map +1 -0
- package/dist/src/daemon/prune.d.ts +207 -0
- package/dist/src/daemon/prune.js +376 -0
- package/dist/src/daemon/prune.js.map +1 -0
- package/dist/src/mcp/http.d.ts +113 -0
- package/dist/src/mcp/http.js +343 -0
- package/dist/src/mcp/http.js.map +1 -0
- package/dist/src/mcp/server.d.ts +265 -0
- package/dist/src/mcp/server.js +602 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/docs/adapter-api.md +106 -0
- package/docs/cli-reference.md +5716 -0
- package/docs/codex-enforced-session.md +30 -0
- package/package.json +53 -4
- package/schema/.gitkeep +0 -0
- package/schema/LICENSE +117 -0
- package/schema/codex-instance.schema.json +82 -0
- package/schema/envelope.schema.json +137 -0
- package/schema/event.schema.json +1811 -0
- package/schema/fixtures/codex-instance/invalid/unpinned-codex-version.json +40 -0
- package/schema/fixtures/codex-instance/valid/canonical.json +40 -0
- package/schema/fixtures/envelope/invalid/action-missing-idempotency-key.json +15 -0
- package/schema/fixtures/envelope/invalid/action-unknown-class-format.json +14 -0
- package/schema/fixtures/envelope/invalid/confidence-out-of-range.json +11 -0
- package/schema/fixtures/envelope/invalid/est-cost-bare-number.json +14 -0
- package/schema/fixtures/envelope/invalid/est-cost-noncanonical-string.json +14 -0
- package/schema/fixtures/envelope/invalid/malformed-assignee.json +10 -0
- package/schema/fixtures/envelope/invalid/malformed-created-by.json +7 -0
- package/schema/fixtures/envelope/invalid/malformed-max-latency.json +11 -0
- package/schema/fixtures/envelope/invalid/malformed-payload-hash.json +14 -0
- package/schema/fixtures/envelope/invalid/max-cost-bare-number.json +11 -0
- package/schema/fixtures/envelope/invalid/missing-origin.json +3 -0
- package/schema/fixtures/envelope/invalid/negative-est-cost.json +14 -0
- package/schema/fixtures/envelope/invalid/unknown-state.json +7 -0
- package/schema/fixtures/envelope/invalid/unknown-top-level-field.json +8 -0
- package/schema/fixtures/envelope/valid/action-payload-hash.json +17 -0
- package/schema/fixtures/envelope/valid/actions-without-budget.json +18 -0
- package/schema/fixtures/envelope/valid/canonical.json +25 -0
- package/schema/fixtures/envelope/valid/minimal.json +7 -0
- package/schema/fixtures/envelope/valid/multi-action-executed.json +30 -0
- package/schema/fixtures/envelope/valid/record-write-stage.json +22 -0
- package/schema/fixtures/event/invalid/approval-granted-agent-actor.json +15 -0
- package/schema/fixtures/event/invalid/approval-granted-empty-batch-delivery-id.json +17 -0
- package/schema/fixtures/event/invalid/approval-granted-fifth-reaction.json +16 -0
- package/schema/fixtures/event/invalid/approval-granted-missing-actor.json +14 -0
- package/schema/fixtures/event/invalid/approval-requested-missing-action-key.json +14 -0
- package/schema/fixtures/event/invalid/approval-withdrawn-agent-policy-drift.json +15 -0
- package/schema/fixtures/event/invalid/approval-withdrawn-missing-reason.json +15 -0
- package/schema/fixtures/event/invalid/approval-withdrawn-system-actor.json +15 -0
- package/schema/fixtures/event/invalid/audit-decision-refused-human-actor.json +17 -0
- package/schema/fixtures/event/invalid/audit-decision-refused-missing-code.json +16 -0
- package/schema/fixtures/event/invalid/audit-reviewed-agent-actor.json +15 -0
- package/schema/fixtures/event/invalid/audit-reviewed-loved-no-note.json +16 -0
- package/schema/fixtures/event/invalid/audit-reviewed-system-actor.json +15 -0
- package/schema/fixtures/event/invalid/bad-actor-prefix.json +15 -0
- package/schema/fixtures/event/invalid/est-cost-bare-number.json +17 -0
- package/schema/fixtures/event/invalid/execution-completed-fabricated-exit-code.json +16 -0
- package/schema/fixtures/event/invalid/execution-completed-provider-ref-empty-id.json +18 -0
- package/schema/fixtures/event/invalid/execution-completed-provider-ref-extra-field.json +19 -0
- package/schema/fixtures/event/invalid/execution-completed-provider-ref-id-not-string.json +18 -0
- package/schema/fixtures/event/invalid/execution-completed-provider-ref-missing-adapter.json +17 -0
- package/schema/fixtures/event/invalid/execution-failed-open-reported-by.json +16 -0
- package/schema/fixtures/event/invalid/execution-indeterminate-open-reason.json +14 -0
- package/schema/fixtures/event/invalid/execution-reconciled-agent-actor.json +17 -0
- package/schema/fixtures/event/invalid/execution-started-negative-env-stripped.json +16 -0
- package/schema/fixtures/event/invalid/gate-bypassed-missing-opened-seq.json +15 -0
- package/schema/fixtures/event/invalid/gate-closed-non-integer-opened-seq.json +13 -0
- package/schema/fixtures/event/invalid/gate-opened-agent-actor.json +16 -0
- package/schema/fixtures/event/invalid/gate-organ-attested-absolute-path.json +14 -0
- package/schema/fixtures/event/invalid/gate-organ-attested-agent-actor.json +14 -0
- package/schema/fixtures/event/invalid/gate-organ-attested-missing-organ-path.json +13 -0
- package/schema/fixtures/event/invalid/harness-unknown-kind.json +18 -0
- package/schema/fixtures/event/invalid/harness-version-multiline.json +16 -0
- package/schema/fixtures/event/invalid/log-checkpoint-agent-actor.json +17 -0
- package/schema/fixtures/event/invalid/log-checkpoint-missing-signature.json +16 -0
- package/schema/fixtures/event/invalid/log-checkpoint-short-signed-hash.json +17 -0
- package/schema/fixtures/event/invalid/log-checkpoint-unknown-signature-alg.json +17 -0
- package/schema/fixtures/event/invalid/malformed-ts.json +15 -0
- package/schema/fixtures/event/invalid/missing-alg.json +14 -0
- package/schema/fixtures/event/invalid/missing-hash.json +14 -0
- package/schema/fixtures/event/invalid/non-integer-seq.json +15 -0
- package/schema/fixtures/event/invalid/payload-pruned-human-actor.json +14 -0
- package/schema/fixtures/event/invalid/payload-pruned-missing-hash.json +14 -0
- package/schema/fixtures/event/invalid/policy-declined-agent-actor.json +15 -0
- package/schema/fixtures/event/invalid/policy-proposed-missing-diff.json +19 -0
- package/schema/fixtures/event/invalid/policy-proposed-system-actor.json +26 -0
- package/schema/fixtures/event/invalid/short-hash.json +15 -0
- package/schema/fixtures/event/invalid/unknown-alg.json +15 -0
- package/schema/fixtures/event/invalid/unknown-event-type.json +15 -0
- package/schema/fixtures/event/invalid/unknown-top-level-field.json +16 -0
- package/schema/fixtures/event/valid/approval-expired.json +15 -0
- package/schema/fixtures/event/valid/approval-granted-batch.json +19 -0
- package/schema/fixtures/event/valid/approval-granted-reaction.json +16 -0
- package/schema/fixtures/event/valid/approval-granted.json +15 -0
- package/schema/fixtures/event/valid/approval-rejected.json +15 -0
- package/schema/fixtures/event/valid/approval-requested.json +19 -0
- package/schema/fixtures/event/valid/approval-revoked.json +15 -0
- package/schema/fixtures/event/valid/approval-withdrawn-policy-drift.json +17 -0
- package/schema/fixtures/event/valid/approval-withdrawn.json +16 -0
- package/schema/fixtures/event/valid/audit-decision-refused.json +20 -0
- package/schema/fixtures/event/valid/audit-reviewed-reaction.json +17 -0
- package/schema/fixtures/event/valid/audit-reviewed.json +15 -0
- package/schema/fixtures/event/valid/audit-sampled.json +14 -0
- package/schema/fixtures/event/valid/budget-exceeded.json +21 -0
- package/schema/fixtures/event/valid/envelope-drift.json +16 -0
- package/schema/fixtures/event/valid/execution-completed-harness-report.json +16 -0
- package/schema/fixtures/event/valid/execution-completed-provider-ref.json +18 -0
- package/schema/fixtures/event/valid/execution-completed.json +15 -0
- package/schema/fixtures/event/valid/execution-failed-harness-report.json +16 -0
- package/schema/fixtures/event/valid/execution-failed.json +15 -0
- package/schema/fixtures/event/valid/execution-indeterminate.json +15 -0
- package/schema/fixtures/event/valid/execution-reconciled.json +17 -0
- package/schema/fixtures/event/valid/execution-started-env-stripped.json +17 -0
- package/schema/fixtures/event/valid/execution-started.json +14 -0
- package/schema/fixtures/event/valid/gate-bypassed-harness-version.json +18 -0
- package/schema/fixtures/event/valid/gate-bypassed.json +19 -0
- package/schema/fixtures/event/valid/gate-closed.json +14 -0
- package/schema/fixtures/event/valid/gate-opened.json +16 -0
- package/schema/fixtures/event/valid/gate-organ-attested.json +14 -0
- package/schema/fixtures/event/valid/genesis-null-prev.json +14 -0
- package/schema/fixtures/event/valid/log-checkpoint.json +17 -0
- package/schema/fixtures/event/valid/payload-pruned-orphan.json +13 -0
- package/schema/fixtures/event/valid/payload-pruned.json +17 -0
- package/schema/fixtures/event/valid/policy-declined.json +16 -0
- package/schema/fixtures/event/valid/policy-proposed.json +35 -0
- package/schema/fixtures/event/valid/policy-updated.json +14 -0
- package/schema/fixtures/event/valid/reconciliation-required.json +18 -0
- package/schema/fixtures/event/valid/reconciliation-satisfied.json +17 -0
- package/schema/fixtures/event/valid/route-accepted.json +15 -0
- package/schema/fixtures/event/valid/route-proposed.json +16 -0
- package/schema/fixtures/event/valid/spec-example.json +15 -0
- package/schema/fixtures/event/valid/task-registered-harness-version.json +23 -0
- package/schema/fixtures/event/valid/task-registered.json +14 -0
- package/schema/fixtures/hash/known-answer-pre-121.json +74 -0
- package/schema/fixtures/hash/known-answer.json +74 -0
- package/schema/fixtures/policy/invalid/bad-approval-ttl.json +7 -0
- package/schema/fixtures/policy/invalid/bad-web-port.json +4 -0
- package/schema/fixtures/policy/invalid/checkpoint-key-not-base64.json +6 -0
- package/schema/fixtures/policy/invalid/class-rule-missing-autonomy.json +9 -0
- package/schema/fixtures/policy/invalid/empty-class-key.json +6 -0
- package/schema/fixtures/policy/invalid/live-rate-on-human-only.json +7 -0
- package/schema/fixtures/policy/invalid/malformed-class-key.json +6 -0
- package/schema/fixtures/policy/invalid/missing-version.json +8 -0
- package/schema/fixtures/policy/invalid/negative-limit.json +9 -0
- package/schema/fixtures/policy/invalid/non-numeric-limit.json +9 -0
- package/schema/fixtures/policy/invalid/non-positive-max-pending.json +9 -0
- package/schema/fixtures/policy/invalid/on-expiry-grant.json +8 -0
- package/schema/fixtures/policy/invalid/payload-retention-bare-number.json +4 -0
- package/schema/fixtures/policy/invalid/payload-retention-compound.json +4 -0
- package/schema/fixtures/policy/invalid/payload-retention-fractional.json +4 -0
- package/schema/fixtures/policy/invalid/payload-retention-zero.json +4 -0
- package/schema/fixtures/policy/invalid/protected-paths-escape.json +4 -0
- package/schema/fixtures/policy/invalid/protected-paths-glob.json +4 -0
- package/schema/fixtures/policy/invalid/retro-rate-on-human-only.json +7 -0
- package/schema/fixtures/policy/invalid/retro-rate-on-manual.json +7 -0
- package/schema/fixtures/policy/invalid/retro-rate-zero.json +7 -0
- package/schema/fixtures/policy/invalid/sample-rate-too-high.json +5 -0
- package/schema/fixtures/policy/invalid/sampling-secret-env-empty.json +7 -0
- package/schema/fixtures/policy/invalid/sampling-secret-env-not-string.json +6 -0
- package/schema/fixtures/policy/invalid/skew-tolerance-compound.json +6 -0
- package/schema/fixtures/policy/invalid/unknown-autonomy.json +7 -0
- package/schema/fixtures/policy/invalid/unknown-class-rule-key.json +6 -0
- package/schema/fixtures/policy/invalid/unknown-top-level-key.json +7 -0
- package/schema/fixtures/policy/invalid/vault-passphrase-env-empty.json +6 -0
- package/schema/fixtures/policy/invalid/vault-passphrase-literal.json +6 -0
- package/schema/fixtures/policy/invalid/version-not-string.json +4 -0
- package/schema/fixtures/policy/valid/canonical.json +47 -0
- package/schema/fixtures/policy/valid/checkpoint-keys.json +18 -0
- package/schema/fixtures/policy/valid/class-approvers-limits.json +25 -0
- package/schema/fixtures/policy/valid/class-retro-rate.json +17 -0
- package/schema/fixtures/policy/valid/global-budgets.json +19 -0
- package/schema/fixtures/policy/valid/human-only.json +9 -0
- package/schema/fixtures/policy/valid/minimal.json +6 -0
- package/schema/fixtures/policy/valid/protected-paths.json +10 -0
- package/schema/fixtures/policy/valid/record-namespace.json +13 -0
- package/schema/fixtures/policy/valid/request-volume-limits.json +26 -0
- package/schema/fixtures/policy/valid/retention-and-sampling-secret.json +16 -0
- package/schema/fixtures/policy/valid/skew-tolerance.json +15 -0
- package/schema/fixtures/policy/valid/vault-passphrase-env.json +14 -0
- package/schema/fixtures/policy/valid/wildcards.json +15 -0
- package/schema/fixtures/policy-md/invalid/alias-bomb.md +15 -0
- package/schema/fixtures/policy-md/invalid/no-fence.md +7 -0
- package/schema/fixtures/policy-md/invalid/protected-route-not-a-subclass.md +16 -0
- package/schema/fixtures/policy-md/invalid/schema-invalid-autonomy.md +16 -0
- package/schema/fixtures/policy-md/invalid/schema-invalid-read-proof.md +17 -0
- package/schema/fixtures/policy-md/invalid/two-fences.md +19 -0
- package/schema/fixtures/policy-md/invalid/unclosed-fence.md +11 -0
- package/schema/fixtures/policy-md/invalid/wrong-info-string.md +11 -0
- package/schema/fixtures/policy-md/invalid/yaml-syntax-error.md +13 -0
- package/schema/fixtures/policy-md/precedence/both/APPROVAL.md +7 -0
- package/schema/fixtures/policy-md/precedence/both/APPROVALS.md +7 -0
- package/schema/fixtures/policy-md/precedence/fallback-only/APPROVALS.md +7 -0
- package/schema/fixtures/policy-md/valid/canonical.md +50 -0
- package/schema/fixtures/policy-md/valid/daemon-read-proof.md +18 -0
- package/schema/fixtures/policy-md/valid/minimal.md +3 -0
- package/schema/fixtures/policy-md/valid/prose-lookalikes.md +54 -0
- package/schema/fixtures/policy-md/valid/routed-protected-paths.md +49 -0
- package/schema/fixtures/policy-md/valid/with-values.md +79 -0
- package/schema/fixtures/sample-record/invalid/bad-date-time.json +4 -0
- package/schema/fixtures/sample-record/invalid/missing-required-field.json +3 -0
- package/schema/fixtures/sample-record/invalid/unknown-top-level-field.json +5 -0
- package/schema/fixtures/sample-record/invalid/wrong-type.json +4 -0
- package/schema/fixtures/sample-record/valid/minimal.json +4 -0
- package/schema/fixtures/sample-record/valid/with-note.json +5 -0
- package/schema/fixtures/values/invalid/class-shaped.json +9 -0
- package/schema/fixtures/values/invalid/duplicate-entry.json +4 -0
- package/schema/fixtures/values/invalid/non-string-item.json +4 -0
- package/schema/fixtures/values/invalid/over-cap.json +26 -0
- package/schema/fixtures/values/invalid/unknown-key.json +5 -0
- package/schema/fixtures/values/invalid/version-string.json +1 -0
- package/schema/fixtures/values/valid/empty-lists.json +7 -0
- package/schema/fixtures/values/valid/full.json +20 -0
- package/schema/fixtures/values/valid/minimal.json +1 -0
- package/schema/fixtures/values-md/invalid/schema-invalid.md +62 -0
- package/schema/fixtures/values-md/invalid/two-blocks.md +69 -0
- package/schema/fixtures/values-md/invalid/unterminated.md +61 -0
- package/schema/fixtures/values-md/invalid/yaml-error.md +63 -0
- package/schema/fixtures/values-md/valid/absent.md +50 -0
- package/schema/fixtures/values-md/valid/with-values.md +79 -0
- package/schema/policy.schema.json +501 -0
- package/schema/sample-record.schema.json +26 -0
- package/schema/values.schema.json +55 -0
- package/templates/codex/README.md +9 -0
|
@@ -0,0 +1,840 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `approval log advance` (APRV-125) — the commit-and-push half of the log
|
|
3
|
+
* ritual, as a verb.
|
|
4
|
+
*
|
|
5
|
+
* The APRV-92 flow, written down: the log's appends the remote does not have
|
|
6
|
+
* yet are gathered into a commit whose message names the seq range they cover,
|
|
7
|
+
* and pushed to a short-lived records branch that exists for it. Main is
|
|
8
|
+
* protected here, so the commit reaches it through a pull request, and the PR
|
|
9
|
+
* step is `--pr`, which runs the ordinary gated `gh` path. Since APRV-284 that
|
|
10
|
+
* step also ARMS the merge (`gh pr merge <branch> --merge --auto`, off with
|
|
11
|
+
* `--no-auto-merge`): a records pull request carries only evidence paths, so
|
|
12
|
+
* the click it was waiting for was never a review. {@link armAutoMerge} carries
|
|
13
|
+
* the reasoning and the guard that keeps it true.
|
|
14
|
+
*
|
|
15
|
+
* ## The three refusals that are the point
|
|
16
|
+
*
|
|
17
|
+
* **`log-advance-dirty-stage`.** The staged set must be EXACTLY the log, the
|
|
18
|
+
* queue projection, and the payload store — nothing else. The failure this
|
|
19
|
+
* prevents is the one CLAUDE.md's rule is written against: a log commit riding
|
|
20
|
+
* a branch that carries other work. A verb that ran `git add -A` and hoped
|
|
21
|
+
* would be a worse version of the hand-ritual it replaces, so anything else
|
|
22
|
+
* already staged is a refusal and not a thing to unstage. Unstaging someone
|
|
23
|
+
* else's work is not this verb's decision to make.
|
|
24
|
+
*
|
|
25
|
+
* **`log-advance-checkout-required`.** This verb never checks out anything. The
|
|
26
|
+
* checkout is the footgun: a branch switch with an uncommitted working log
|
|
27
|
+
* rewinds `events.jsonl` under whatever holds it open, which is fork 2 of
|
|
28
|
+
* 2026-08-20. It builds the commit with a scratch index and pushes THAT commit
|
|
29
|
+
* to a records branch by refspec, which moves no HEAD and touches no file.
|
|
30
|
+
*
|
|
31
|
+
* ## The base is the remote, not the local branch (APRV-203)
|
|
32
|
+
*
|
|
33
|
+
* The verb used to commit on the branch you were standing on, which made the
|
|
34
|
+
* operator responsible for that branch being current. A checkout whose local
|
|
35
|
+
* `main` was behind origin produced a records commit parented on a stale tip,
|
|
36
|
+
* carrying a tree that silently reverted everything main had merged since. So
|
|
37
|
+
* the verb fetches first and bases its commit on the remote's tip: `git
|
|
38
|
+
* read-tree` fills a scratch index from origin's tree, the log, the queue
|
|
39
|
+
* projection and the payload store are laid over it from the working tree, and
|
|
40
|
+
* `commit-tree` parents the result on origin. HEAD, the operator's index and
|
|
41
|
+
* every file in the working tree are exactly as they were found.
|
|
42
|
+
*
|
|
43
|
+
* **`log-advance-not-primary`.** Same rule as sync's: the committed log has one
|
|
44
|
+
* home, and it is not a worktree.
|
|
45
|
+
*
|
|
46
|
+
* ## One records branch, and one pull request, per day (APRV-204)
|
|
47
|
+
*
|
|
48
|
+
* The daemon advances on a cadence, so an advance is no longer a once-a-day
|
|
49
|
+
* ceremony and the day's records branch usually already exists. When it does,
|
|
50
|
+
* and its log is a prefix of the working log, the commit is parented on THAT
|
|
51
|
+
* branch rather than on the trunk: parenting every advance on the trunk makes
|
|
52
|
+
* the second push of the day a non-fast-forward of a branch a pull request is
|
|
53
|
+
* already open on. `--pr` asks whether an open pull request exists for the head
|
|
54
|
+
* branch before creating one, so a day gets a single pull request that later
|
|
55
|
+
* advances grow.
|
|
56
|
+
*
|
|
57
|
+
* A local branch that is AHEAD of origin with commits this verb did not make is
|
|
58
|
+
* not a refusal: the advance is based on origin either way, and those commits
|
|
59
|
+
* are simply not part of it. What IS refused is a working log that origin's log
|
|
60
|
+
* is not a prefix of, in either direction, because an advance is only ever the
|
|
61
|
+
* records origin does not have yet.
|
|
62
|
+
*
|
|
63
|
+
* ## This verb appends no event
|
|
64
|
+
*
|
|
65
|
+
* For the same reason `log sync` appends none: committing the file the log
|
|
66
|
+
* lives in is housekeeping on the container, not a decision about the world.
|
|
67
|
+
* The commit is already the record of itself, in git, where a reader can see
|
|
68
|
+
* exactly which bytes moved (SPEC §10.1, amended APRV-125).
|
|
69
|
+
*/
|
|
70
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
71
|
+
import { tmpdir } from "node:os";
|
|
72
|
+
import { join } from "node:path";
|
|
73
|
+
import { isAdvanceBookkeeping } from "../core/advance-cycle.js";
|
|
74
|
+
import { withAppendLock } from "../core/log.js";
|
|
75
|
+
import { verify } from "../core/verify.js";
|
|
76
|
+
import { commitOnBase, currentBranch, failureText, fetchBase, gh, git, outputLines, primaryCheckout, repoPath, showBlob, } from "./git-scope.js";
|
|
77
|
+
import { anchorRevs, defaultRecordsBranch } from "./log-anchor.js";
|
|
78
|
+
import { compareChains } from "../core/log-reconcile.js";
|
|
79
|
+
import { silentProgress } from "./progress.js";
|
|
80
|
+
import { DEFAULT_LOG_PATH } from "./paths.js";
|
|
81
|
+
import { DEFAULT_QUEUE_PATH } from "./render.js";
|
|
82
|
+
/** SPEC §9: the payload store, the third and last path an advance may carry. */
|
|
83
|
+
const DEFAULT_PAYLOADS_DIR = ".approval/payloads";
|
|
84
|
+
/**
|
|
85
|
+
* The only paths an advance may stage, as git spells them.
|
|
86
|
+
*
|
|
87
|
+
* A closed list, checked in both directions: everything staged must be in it,
|
|
88
|
+
* and nothing outside it may be staged. Widening it is a reviewable diff.
|
|
89
|
+
*/
|
|
90
|
+
export const ADVANCE_PATHS = [
|
|
91
|
+
DEFAULT_LOG_PATH,
|
|
92
|
+
DEFAULT_QUEUE_PATH,
|
|
93
|
+
DEFAULT_PAYLOADS_DIR,
|
|
94
|
+
];
|
|
95
|
+
/** Machine-readable refusal codes. Frozen public API, printed in the help. */
|
|
96
|
+
export const LOG_ADVANCE_REFUSAL_CODES = [
|
|
97
|
+
"log-advance-not-primary",
|
|
98
|
+
"log-advance-no-branch",
|
|
99
|
+
"log-advance-dirty-stage",
|
|
100
|
+
"log-advance-checkout-required",
|
|
101
|
+
"log-advance-unverified",
|
|
102
|
+
"log-advance-locked",
|
|
103
|
+
"log-advance-fetch-failed",
|
|
104
|
+
"log-advance-behind-remote",
|
|
105
|
+
"log-advance-remote-diverged",
|
|
106
|
+
"log-advance-git-failed",
|
|
107
|
+
"log-advance-push-rejected",
|
|
108
|
+
"log-advance-pr-failed",
|
|
109
|
+
"log-advance-co-author-invalid",
|
|
110
|
+
];
|
|
111
|
+
function detail(cause) {
|
|
112
|
+
return cause instanceof Error ? cause.message : String(cause);
|
|
113
|
+
}
|
|
114
|
+
// Re-exported rather than defined here (APRV-219). The anchor check and this
|
|
115
|
+
// verb must name the day's records branch identically, and `cli/log-anchor.ts`
|
|
116
|
+
// is the module that owns which revs a committed copy of the log may live at.
|
|
117
|
+
export { defaultRecordsBranch };
|
|
118
|
+
/**
|
|
119
|
+
* Everything currently staged, as repo-relative paths.
|
|
120
|
+
*
|
|
121
|
+
* `--name-only --cached` is the whole index against HEAD; the `-z` form is used
|
|
122
|
+
* because a path with a space in it is a path, and a split on newlines would
|
|
123
|
+
* quietly mangle it.
|
|
124
|
+
*/
|
|
125
|
+
function stagedPaths(root) {
|
|
126
|
+
const result = git(["diff", "--cached", "--name-only", "-z"], root);
|
|
127
|
+
if (!result.ok)
|
|
128
|
+
return { ok: false, message: failureText(result) };
|
|
129
|
+
const paths = result.stdout.split("\0").filter((entry) => entry.length > 0);
|
|
130
|
+
return { ok: true, paths };
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Does `commit` already carry `candidate` in its history? (APRV-234)
|
|
134
|
+
*
|
|
135
|
+
* The question the day's records branch has to answer before another commit is
|
|
136
|
+
* stacked on it: a branch that no longer contains the trunk cannot be
|
|
137
|
+
* fast-forwarded into it, and everything built on it inherits that. Answered
|
|
138
|
+
* with `merge-base --is-ancestor`, whose exit status IS the answer; anything
|
|
139
|
+
* that is not a clean "yes" reads as "no", which is the direction that rebuilds
|
|
140
|
+
* rather than the direction that stacks on a base nobody checked.
|
|
141
|
+
*/
|
|
142
|
+
function contains(root, commit, candidate) {
|
|
143
|
+
return git(["merge-base", "--is-ancestor", candidate, commit], root).ok;
|
|
144
|
+
}
|
|
145
|
+
/** Is `path` inside one of the three paths an advance is allowed to carry? */
|
|
146
|
+
function isAdvancePath(path) {
|
|
147
|
+
return ADVANCE_PATHS.some((allowed) => path === allowed || path.startsWith(`${allowed}/`));
|
|
148
|
+
}
|
|
149
|
+
/** `approval log advance`, as a function. The CLI wrapper formats its answer. */
|
|
150
|
+
export function logAdvance(options) {
|
|
151
|
+
const coAuthor = validateCoAuthor(options.coAuthor);
|
|
152
|
+
if (!coAuthor.ok) {
|
|
153
|
+
return {
|
|
154
|
+
ok: false,
|
|
155
|
+
code: "log-advance-co-author-invalid",
|
|
156
|
+
message: coAuthor.message,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
const remote = options.remote ?? "origin";
|
|
160
|
+
const dryRun = options.dryRun === true;
|
|
161
|
+
const primary = primaryCheckout(options.cwd);
|
|
162
|
+
if (!primary.ok) {
|
|
163
|
+
return {
|
|
164
|
+
ok: false,
|
|
165
|
+
code: "log-advance-not-primary",
|
|
166
|
+
message: `log advance runs in the PRIMARY checkout only: ${primary.reason}. Log-touching commits are made where the log lives, never from a worktree. Run this in ${primary.primary ?? "the primary checkout"}.`,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
const root = primary.root;
|
|
170
|
+
const logPath = join(root, DEFAULT_LOG_PATH);
|
|
171
|
+
const branch = currentBranch(root);
|
|
172
|
+
if (branch === null) {
|
|
173
|
+
return {
|
|
174
|
+
ok: false,
|
|
175
|
+
code: "log-advance-no-branch",
|
|
176
|
+
message: `HEAD in ${root} is detached, so there is no branch to commit on. This verb never checks anything out; check out the branch the log lives on yourself and run it again.`,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
// The lock covers the READ of the log and nothing after it (APRV-233).
|
|
180
|
+
//
|
|
181
|
+
// It used to wrap this whole function, `git fetch`, `git push` and `gh pr
|
|
182
|
+
// create` included, and on 2026-09-02 that stalled every harness hook on the
|
|
183
|
+
// machine: each one asked for the append lock, waited the full two seconds
|
|
184
|
+
// and refused its command `append-failed`, for as long as each advance's
|
|
185
|
+
// network round-trips took. Nothing in the git half of the verb appends
|
|
186
|
+
// anything, so nothing in it needs the lock. What the lock IS for is the
|
|
187
|
+
// consistency of one read: the chain must verify, the seq range must be
|
|
188
|
+
// measured, and the bytes that get committed must be the bytes that were
|
|
189
|
+
// verified — so the log's exact content is pinned as a git blob HERE, under
|
|
190
|
+
// the lock, and the commit is assembled from that object rather than from a
|
|
191
|
+
// file another writer may have grown in the meantime.
|
|
192
|
+
(options.progress ?? silentProgress).phase("verifying the log chain before anything is committed from it");
|
|
193
|
+
const held = withAppendLock(logPath, () => snapshotUnderLock(root, logPath));
|
|
194
|
+
if (!held.ok) {
|
|
195
|
+
return {
|
|
196
|
+
ok: false,
|
|
197
|
+
code: held.error.code === "lock-timeout" ? "log-advance-locked" : "log-advance-git-failed",
|
|
198
|
+
message: held.error.code === "lock-timeout"
|
|
199
|
+
? `${held.error.message}. Advance holds the append lock while it READS the chain, so the seq range it names is the range it commits. Wait for the append to finish and run this again.`
|
|
200
|
+
: held.error.message,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
if (!held.value.ok)
|
|
204
|
+
return held.value;
|
|
205
|
+
return advanceOnSnapshot({ root, logPath, branch, remote, dryRun, options }, held.value);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Everything the advance must read from the log, read once and pinned.
|
|
209
|
+
*
|
|
210
|
+
* Short by design: a chain verify, an index read and one `git hash-object`, all
|
|
211
|
+
* local. Every writer on the machine waits on this and on nothing else.
|
|
212
|
+
*/
|
|
213
|
+
function snapshotUnderLock(root, logPath) {
|
|
214
|
+
// 1. The chain, verified. A commit of a log that does not verify would be
|
|
215
|
+
// publishing the break.
|
|
216
|
+
const verified = verify(logPath);
|
|
217
|
+
if (verified.status !== "clean") {
|
|
218
|
+
return {
|
|
219
|
+
ok: false,
|
|
220
|
+
code: "log-advance-unverified",
|
|
221
|
+
message: verified.status === "torn-tail"
|
|
222
|
+
? `${logPath} ends without a newline: the final record is truncated. Nothing is committed from a torn log; run \`approval log verify\`.`
|
|
223
|
+
: `${logPath} does not verify (${verified.reason}): ${verified.message}. Nothing is committed from a log that does not verify.`,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
// 2. The staged set: exactly the three paths, and nothing else.
|
|
227
|
+
const staged = stagedPaths(root);
|
|
228
|
+
if (!staged.ok) {
|
|
229
|
+
return {
|
|
230
|
+
ok: false,
|
|
231
|
+
code: "log-advance-git-failed",
|
|
232
|
+
message: `the index could not be read: ${staged.message}`,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
const offending = staged.paths.filter((path) => !isAdvancePath(path));
|
|
236
|
+
if (offending.length > 0) {
|
|
237
|
+
return {
|
|
238
|
+
ok: false,
|
|
239
|
+
code: "log-advance-dirty-stage",
|
|
240
|
+
offending,
|
|
241
|
+
message: `${String(offending.length)} path(s) are staged that an advance may not carry: ${offending.join(", ")}. An advance commits the log, the queue projection and the payload store, and nothing else — a log commit that rides other work is the thing the one-commit rule exists to forbid. Unstage them yourself (\`git restore --staged <path>\`) and run this again; this verb will not unstage anyone's work for them.`,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
// 3. The bytes, pinned. `hash-object -w` writes the blob into the object
|
|
245
|
+
// store and answers its sha, so the commit built after the lock is
|
|
246
|
+
// released carries exactly what was verified above, however much the file
|
|
247
|
+
// has grown by then.
|
|
248
|
+
const text = textOfWorking(logPath);
|
|
249
|
+
const hashed = git(["hash-object", "-w", "--", repoPath(root, logPath)], root);
|
|
250
|
+
const blob = hashed.stdout.trim();
|
|
251
|
+
if (!hashed.ok || blob.length === 0) {
|
|
252
|
+
return {
|
|
253
|
+
ok: false,
|
|
254
|
+
code: "log-advance-git-failed",
|
|
255
|
+
message: `the working log could not be written to the object store: ${failureText(hashed)}; nothing was committed.`,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
return { ok: true, text, blob, staged: staged.paths };
|
|
259
|
+
}
|
|
260
|
+
function advanceOnSnapshot(ctx, snapshot) {
|
|
261
|
+
const { root, logPath, branch, remote, dryRun, options } = ctx;
|
|
262
|
+
const coAuthor = options.coAuthor ?? null;
|
|
263
|
+
const progress = options.progress ?? silentProgress;
|
|
264
|
+
// 4. The base: the remote's tip, fetched by this verb rather than by the
|
|
265
|
+
// operator (APRV-203). Everything after this is measured against it.
|
|
266
|
+
const baseBranch = options.base ?? branch;
|
|
267
|
+
progress.phase(`fetching ${remote}/${baseBranch}: an advance is based on the remote, not on this checkout`);
|
|
268
|
+
const fetched = fetchBase(root, remote, baseBranch);
|
|
269
|
+
if (!fetched.ok) {
|
|
270
|
+
return {
|
|
271
|
+
ok: false,
|
|
272
|
+
code: "log-advance-fetch-failed",
|
|
273
|
+
message: `${fetched.message}. An advance bases its commit on ${remote}/${baseBranch}, so it cannot proceed without knowing where that is. Nothing was committed. Fix the remote (network, credentials, or a branch named something else — pass \`--base <branch>\`) and run this again.`,
|
|
274
|
+
quote: fetched.quote,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
const baseSha = fetched.sha;
|
|
278
|
+
// 5. What is owed: the seq range between the committed log and the working
|
|
279
|
+
// head. Measured against the SNAPSHOT (APRV-233), which is the same bytes
|
|
280
|
+
// the commit will carry, so the range this names is the range it commits
|
|
281
|
+
// even though the lock is no longer held.
|
|
282
|
+
const workingText = snapshot.text;
|
|
283
|
+
const against = (sha, label) => {
|
|
284
|
+
const blob = showBlob(root, sha, repoPath(root, logPath));
|
|
285
|
+
const compared = compareChains({ label: `the working log ${logPath}`, text: workingText }, {
|
|
286
|
+
label: `the committed log ${label}:${repoPath(root, logPath)}`,
|
|
287
|
+
text: blob === null ? "" : blob.toString("utf8"),
|
|
288
|
+
});
|
|
289
|
+
if (!compared.ok) {
|
|
290
|
+
return { ok: false, code: "log-advance-unverified", message: compared.message };
|
|
291
|
+
}
|
|
292
|
+
if (compared.drift.relation === "diverged") {
|
|
293
|
+
return {
|
|
294
|
+
ok: false,
|
|
295
|
+
code: "log-advance-remote-diverged",
|
|
296
|
+
message: `the working log and ${label}'s log part at seq ${String(compared.drift.firstDivergentSeq)}: two chains, not one. Nothing is advanced over a fork, and nothing was committed. Run \`approval doctor\` for the log-drift report; hash chains do not merge, so which of the two is the log is a human decision.`,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
if (compared.drift.relation === "behind") {
|
|
300
|
+
return {
|
|
301
|
+
ok: false,
|
|
302
|
+
code: "log-advance-behind-remote",
|
|
303
|
+
message: `${label} carries records this working log does not (its head is ${String(compared.drift.committedHead?.seq ?? 0)}, the working head is ${String(compared.drift.workingHead?.seq ?? 0)}). An advance publishes records the remote lacks, so there is nothing here to publish and committing would propose an older chain than the one already on the remote. Run \`approval log sync\` first, then run this again. Nothing was committed.`,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
return { ok: true, drift: compared.drift };
|
|
307
|
+
};
|
|
308
|
+
const trunk = against(baseSha, `${remote}/${baseBranch}`);
|
|
309
|
+
if (!("drift" in trunk))
|
|
310
|
+
return trunk;
|
|
311
|
+
const recordsBranch = options.branch ?? defaultRecordsBranch(options.today ?? new Date().toISOString());
|
|
312
|
+
if (recordsBranch === "main" || recordsBranch === "master") {
|
|
313
|
+
return {
|
|
314
|
+
ok: false,
|
|
315
|
+
code: "log-advance-checkout-required",
|
|
316
|
+
message: `--branch ${recordsBranch} would push the advance straight at the trunk. An advance goes to a records branch and reaches the trunk through a pull request; pick another name.`,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
// 4a. One records branch per day, updated rather than re-created (APRV-204).
|
|
320
|
+
//
|
|
321
|
+
// The day's branch usually already exists: the daemon advances on a cadence
|
|
322
|
+
// and every tick after the first would otherwise build ANOTHER commit on the
|
|
323
|
+
// trunk, which the remote rejects as a non-fast-forward of the branch a pull
|
|
324
|
+
// request is already open on. So when the records branch exists and its log
|
|
325
|
+
// is a prefix of the working log, THAT is the parent: the push fast-forwards
|
|
326
|
+
// and the open pull request grows by one commit.
|
|
327
|
+
//
|
|
328
|
+
// A fetch failure here is read as "no such branch", which is what it is
|
|
329
|
+
// whenever the trunk fetch a moment ago succeeded. The alternative — treating
|
|
330
|
+
// it as a network failure and refusing — would refuse every first advance of
|
|
331
|
+
// every day, since the branch genuinely does not exist yet.
|
|
332
|
+
//
|
|
333
|
+
// 4b. Unless the trunk has moved under the branch (APRV-234).
|
|
334
|
+
//
|
|
335
|
+
// Observed 2026-09-02: a ceremony commit landed on main carrying its own copy
|
|
336
|
+
// of the log, so `records-log-2026-09-02` no longer contained the trunk. The
|
|
337
|
+
// daemon went on stacking advance commits on the branch tip — six of them —
|
|
338
|
+
// each built on a base that was missing main's copy, and the pull request
|
|
339
|
+
// went DIRTY and stayed DIRTY. Nothing the cadence could do would clear it;
|
|
340
|
+
// it took a hand `git merge -X ours origin/main` by a person, which is
|
|
341
|
+
// precisely the tap this feature exists to remove.
|
|
342
|
+
//
|
|
343
|
+
// So: stack only while the branch still contains the base. When it does not,
|
|
344
|
+
// REBUILD — the same commit content, parented on the current trunk instead —
|
|
345
|
+
// and push it over the branch. That is sound here for a reason peculiar to
|
|
346
|
+
// this verb: the only paths it carries are the append-only log, its
|
|
347
|
+
// projection and its content-addressed payloads, and the trunk check above
|
|
348
|
+
// has already refused unless the working log is a SUPERSET of the trunk's
|
|
349
|
+
// (`log-advance-behind-remote` in one direction, `log-advance-remote-diverged`
|
|
350
|
+
// in the other). A rebuilt commit is therefore the trunk's own log plus the
|
|
351
|
+
// tail it lacks, never a revert of anything the trunk merged.
|
|
352
|
+
let parent = { ref: `${remote}/${baseBranch}`, sha: baseSha };
|
|
353
|
+
let drift = trunk.drift;
|
|
354
|
+
let reusedRecordsBranch = false;
|
|
355
|
+
let rebuilt = false;
|
|
356
|
+
let rebuiltOn;
|
|
357
|
+
const existing = fetchBase(root, remote, recordsBranch);
|
|
358
|
+
if (existing.ok) {
|
|
359
|
+
if (contains(root, existing.sha, baseSha)) {
|
|
360
|
+
const onBranch = against(existing.sha, `${remote}/${recordsBranch}`);
|
|
361
|
+
if (!("drift" in onBranch))
|
|
362
|
+
return onBranch;
|
|
363
|
+
parent = { ref: `${remote}/${recordsBranch}`, sha: existing.sha };
|
|
364
|
+
drift = onBranch.drift;
|
|
365
|
+
reusedRecordsBranch = true;
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
// The branch exists and does not contain the trunk. Keep `parent` at the
|
|
369
|
+
// trunk and say so: the range is measured against the trunk's log, which
|
|
370
|
+
// is what `trunk.drift` already holds.
|
|
371
|
+
rebuilt = true;
|
|
372
|
+
rebuiltOn = { ref: `${remote}/${baseBranch}`, sha: baseSha };
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
const committedSeq = drift.committedHead?.seq ?? 0;
|
|
376
|
+
// `null` when there is nothing owed. A no-op advance is a SUCCESS, exactly as
|
|
377
|
+
// a no-op `policy amend` is: an operator who runs it on an already-committed
|
|
378
|
+
// log has established what they wanted to establish.
|
|
379
|
+
const range = drift.relation === "ahead"
|
|
380
|
+
? { from: committedSeq + 1, to: drift.workingHead?.seq ?? committedSeq }
|
|
381
|
+
: null;
|
|
382
|
+
const message = range === null ? "" : messageWithCoAuthor(advanceMessage(range, branch), coAuthor);
|
|
383
|
+
const carried = ADVANCE_PATHS.filter((path) => existsSync(join(root, path)));
|
|
384
|
+
const report = (over) => ({
|
|
385
|
+
root,
|
|
386
|
+
branch,
|
|
387
|
+
recordsBranch,
|
|
388
|
+
remote,
|
|
389
|
+
base: { branch: baseBranch, sha: baseSha },
|
|
390
|
+
parent: { ref: parent.ref, sha: parent.sha },
|
|
391
|
+
reusedRecordsBranch,
|
|
392
|
+
rebuilt,
|
|
393
|
+
...(rebuiltOn === undefined ? {} : { rebuiltOn }),
|
|
394
|
+
prCreated: false,
|
|
395
|
+
autoMerge: null,
|
|
396
|
+
autoMergeNote: null,
|
|
397
|
+
range,
|
|
398
|
+
committedHead: drift.committedHead,
|
|
399
|
+
workingHead: drift.workingHead,
|
|
400
|
+
staged: carried,
|
|
401
|
+
message,
|
|
402
|
+
commit: null,
|
|
403
|
+
pushed: false,
|
|
404
|
+
prUrl: null,
|
|
405
|
+
dryRun,
|
|
406
|
+
...over,
|
|
407
|
+
});
|
|
408
|
+
if (range === null || dryRun)
|
|
409
|
+
return { ok: true, report: report({ staged: range === null ? [] : carried }) };
|
|
410
|
+
// 5. Build the commit on the remote's tip, in a scratch index. Exactly those
|
|
411
|
+
// paths, laid over origin's tree; the operator's index is never touched
|
|
412
|
+
// and nothing is checked out.
|
|
413
|
+
progress.phase(`building the records commit on ${parent.ref} ${parent.sha.slice(0, 12)} (nothing is checked out)`);
|
|
414
|
+
const built = commitOnBase(root, {
|
|
415
|
+
base: parent.sha,
|
|
416
|
+
paths: carried,
|
|
417
|
+
message,
|
|
418
|
+
// APRV-233. The log goes in as the blob taken under the append lock, not as
|
|
419
|
+
// whatever the file holds now: the lock was released before the fetch, and
|
|
420
|
+
// a record appended since is a record this commit's message does not claim.
|
|
421
|
+
blobs: carried.includes(DEFAULT_LOG_PATH)
|
|
422
|
+
? [{ path: repoPath(root, logPath), sha: snapshot.blob }]
|
|
423
|
+
: [],
|
|
424
|
+
});
|
|
425
|
+
if (!built.ok) {
|
|
426
|
+
return {
|
|
427
|
+
ok: false,
|
|
428
|
+
code: "log-advance-git-failed",
|
|
429
|
+
message: `${built.message}; nothing was committed and the checkout is untouched.`,
|
|
430
|
+
quote: built.quote,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
if (built.unchanged) {
|
|
434
|
+
// The chains said records were owed and the trees say otherwise, which
|
|
435
|
+
// means the log file on the remote already carries these bytes under a
|
|
436
|
+
// different commit. Nothing to publish, and an empty commit would say
|
|
437
|
+
// otherwise.
|
|
438
|
+
return { ok: true, report: report({ commit: null, staged: [] }) };
|
|
439
|
+
}
|
|
440
|
+
const commit = built.sha;
|
|
441
|
+
// 6. Anchor the commit before pushing, so a rejected push loses nothing. A
|
|
442
|
+
// ref under `refs/approval/` rather than a branch: it keeps the object
|
|
443
|
+
// reachable without adding a branch nobody asked for, and it moves no HEAD.
|
|
444
|
+
const anchor = `refs/approval/advance/${recordsBranch}`;
|
|
445
|
+
git(["update-ref", anchor, commit], root);
|
|
446
|
+
// 7. Push THAT commit to the records branch, by refspec. A sha on the left
|
|
447
|
+
// moves no local ref and checks nothing out: the operator's branch stays
|
|
448
|
+
// exactly where they left it.
|
|
449
|
+
progress.phase(`pushing ${commit.slice(0, 12)} to ${remote} ${recordsBranch}`);
|
|
450
|
+
// A rebuilt commit does not descend from the branch it replaces, so it needs
|
|
451
|
+
// the `+`. It is not a history rewrite in the sense the rule forbids: the
|
|
452
|
+
// branch is a proposal nobody has merged, every record it carried is in the
|
|
453
|
+
// new commit's log, and the alternative is a pull request that cannot land.
|
|
454
|
+
const refspec = `${rebuilt ? "+" : ""}${commit}:refs/heads/${recordsBranch}`;
|
|
455
|
+
let target = recordsBranch;
|
|
456
|
+
let fallbackFrom = null;
|
|
457
|
+
let pushed = git(["push", remote, refspec], root);
|
|
458
|
+
if (!pushed.ok && rebuilt) {
|
|
459
|
+
// The branch cannot be updated in place: a protected-branch ruleset
|
|
460
|
+
// (GH006), or a pull request the merge queue has already taken. The records
|
|
461
|
+
// still have to reach a branch somebody can merge, so they get a fresh one
|
|
462
|
+
// and the report says which (APRV-234).
|
|
463
|
+
const rejection = failureText(pushed);
|
|
464
|
+
for (let n = 2; n <= 9 && !pushed.ok; n += 1) {
|
|
465
|
+
const alternate = `${recordsBranch}-${String(n)}`;
|
|
466
|
+
const attempt = git(["push", remote, `${commit}:refs/heads/${alternate}`], root);
|
|
467
|
+
if (attempt.ok) {
|
|
468
|
+
pushed = attempt;
|
|
469
|
+
fallbackFrom = recordsBranch;
|
|
470
|
+
target = alternate;
|
|
471
|
+
git(["update-ref", `refs/approval/advance/${alternate}`, commit], root);
|
|
472
|
+
progress.phase(`${recordsBranch} would not take the rebuilt commit (${rejection}); opened ${alternate} instead`);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if (!pushed.ok) {
|
|
477
|
+
return {
|
|
478
|
+
ok: false,
|
|
479
|
+
code: "log-advance-push-rejected",
|
|
480
|
+
message: `the advance is built on ${parent.ref} as ${commit.slice(0, 12)} and held at ${anchor}, but \`git push ${remote} ${refspec}\` was REJECTED: ${failureText(pushed)}. The commit exists and nothing was lost; push it when the remote will take it (\`git push ${remote} ${refspec}\`). Your checkout is exactly as you left it, on ${branch}.`,
|
|
481
|
+
quote: outputLines(pushed.stderr, pushed.stdout),
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
if (options.pr !== true) {
|
|
485
|
+
progress.done();
|
|
486
|
+
return {
|
|
487
|
+
ok: true,
|
|
488
|
+
report: report({ commit, pushed: true, recordsBranch: target, fallbackFrom }),
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
progress.phase(`opening or updating the pull request for ${target}`);
|
|
492
|
+
const pr = ghPullRequest(root, target, range, coAuthor);
|
|
493
|
+
if (!pr.ok) {
|
|
494
|
+
progress.done();
|
|
495
|
+
return {
|
|
496
|
+
ok: false,
|
|
497
|
+
code: "log-advance-pr-failed",
|
|
498
|
+
message: `the advance is committed and pushed to ${target}, but \`gh pr ${pr.step}\` failed: ${pr.message}. Open the pull request by hand and merge it with a merge commit.`,
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
// APRV-284. The arm, and never a refusal of the verb: the records are
|
|
502
|
+
// committed, pushed and open as a pull request whatever `gh` says next.
|
|
503
|
+
const arm = options.autoMerge === false
|
|
504
|
+
? { state: "off", note: null }
|
|
505
|
+
: armAutoMerge(root, target, { baseSha, commit });
|
|
506
|
+
if (arm.state === "armed") {
|
|
507
|
+
progress.phase(`auto-merge armed on ${target}`);
|
|
508
|
+
}
|
|
509
|
+
else if (arm.note !== null) {
|
|
510
|
+
progress.phase(`auto-merge was not armed on ${target}: ${arm.note}`);
|
|
511
|
+
}
|
|
512
|
+
progress.done();
|
|
513
|
+
return {
|
|
514
|
+
ok: true,
|
|
515
|
+
report: report({
|
|
516
|
+
commit,
|
|
517
|
+
pushed: true,
|
|
518
|
+
recordsBranch: target,
|
|
519
|
+
fallbackFrom,
|
|
520
|
+
prUrl: pr.url,
|
|
521
|
+
prCreated: pr.created,
|
|
522
|
+
autoMerge: arm.state,
|
|
523
|
+
autoMergeNote: arm.note,
|
|
524
|
+
}),
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* The chain head of one git rev's copy of the log, or `null`.
|
|
529
|
+
*
|
|
530
|
+
* `null` covers three cases that mean the same thing here: no such rev, no such
|
|
531
|
+
* blob, and a copy that is not this chain (a fork, or a chain somehow ahead of
|
|
532
|
+
* the working log). None is evidence that any working record has been
|
|
533
|
+
* published, and treating any of them as evidence would UNDER-count what is
|
|
534
|
+
* owed, which is the direction that loses records.
|
|
535
|
+
*/
|
|
536
|
+
function publishedHeadAt(root, logPath, workingText, rev) {
|
|
537
|
+
const blob = showBlob(root, rev, repoPath(root, logPath));
|
|
538
|
+
if (blob === null)
|
|
539
|
+
return null;
|
|
540
|
+
const compared = compareChains({ label: "the working log", text: workingText }, { label: rev, text: blob.toString("utf8") });
|
|
541
|
+
if (!compared.ok)
|
|
542
|
+
return null;
|
|
543
|
+
if (compared.drift.relation === "diverged" || compared.drift.relation === "behind")
|
|
544
|
+
return null;
|
|
545
|
+
return compared.drift.committedHead?.seq ?? 0;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* How much of the working log is already on a records branch or the trunk.
|
|
549
|
+
*
|
|
550
|
+
* Reads git's object store and NEVER the network: this is asked on every daemon
|
|
551
|
+
* tick and by `approval doctor`, and a status question must not depend on a
|
|
552
|
+
* remote being reachable. The revs consulted are the local advance anchors
|
|
553
|
+
* (`refs/approval/advance/*`, which {@link logAdvance} writes before it pushes),
|
|
554
|
+
* the remote-tracking refs for the base branch and the day's records branch, and
|
|
555
|
+
* `HEAD`. The highest head among the copies that are a PREFIX of this chain
|
|
556
|
+
* wins; anything else is ignored, which can only make a caller advance less
|
|
557
|
+
* eagerly.
|
|
558
|
+
*
|
|
559
|
+
* It lives here rather than beside the daemon's cadence because `approval
|
|
560
|
+
* doctor` reports the same number and a CLI module may not import the daemon.
|
|
561
|
+
*/
|
|
562
|
+
export function publishedState(root, logPath, records, where, today) {
|
|
563
|
+
const workingSeq = records.length === 0 ? 0 : (records[records.length - 1]?.seq ?? 0);
|
|
564
|
+
let workingText;
|
|
565
|
+
try {
|
|
566
|
+
workingText = readFileSync(logPath, "utf8");
|
|
567
|
+
}
|
|
568
|
+
catch {
|
|
569
|
+
workingText = "";
|
|
570
|
+
}
|
|
571
|
+
const revs = anchorRevs(root, { remote: where.remote, base: where.base, today });
|
|
572
|
+
let publishedSeq = 0;
|
|
573
|
+
let publishedRev = null;
|
|
574
|
+
for (const rev of revs) {
|
|
575
|
+
const head = publishedHeadAt(root, logPath, workingText, rev);
|
|
576
|
+
if (head !== null && head > publishedSeq) {
|
|
577
|
+
publishedSeq = head;
|
|
578
|
+
publishedRev = rev;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
const unpublished = records.filter((record) => record.seq > publishedSeq);
|
|
582
|
+
const substantive = unpublished.filter((record) => !isAdvanceBookkeeping(record));
|
|
583
|
+
return {
|
|
584
|
+
publishedSeq,
|
|
585
|
+
workingSeq,
|
|
586
|
+
pending: unpublished.length,
|
|
587
|
+
substantive: substantive.length,
|
|
588
|
+
substantiveSeq: substantive[substantive.length - 1]?.seq ?? publishedSeq,
|
|
589
|
+
publishedRev,
|
|
590
|
+
revs,
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
/** The working log's text, or the empty string when there is no file. */
|
|
594
|
+
function textOfWorking(path) {
|
|
595
|
+
try {
|
|
596
|
+
return readFileSync(path, "utf8");
|
|
597
|
+
}
|
|
598
|
+
catch (cause) {
|
|
599
|
+
if (cause.code === "ENOENT")
|
|
600
|
+
return "";
|
|
601
|
+
throw new Error(`log ${path} could not be read: ${detail(cause)}`);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
/** The canonical commit subject. It names the seq range, so the log is findable. */
|
|
605
|
+
export function advanceMessage(range, branch) {
|
|
606
|
+
const span = range.from === range.to ? `seq ${String(range.from)}` : `seq ${String(range.from)}..${String(range.to)}`;
|
|
607
|
+
return `Log advance: ${span} (${branch})`;
|
|
608
|
+
}
|
|
609
|
+
const CO_AUTHOR_MAX_BYTES = 254;
|
|
610
|
+
const CO_AUTHOR_EMAIL = /^[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$/u;
|
|
611
|
+
/**
|
|
612
|
+
* Validate a display-only Git trailer identity before any mutating step.
|
|
613
|
+
*
|
|
614
|
+
* The accepted form is deliberately one line and one header value. It is never
|
|
615
|
+
* derived from a log actor and never interpreted as gate or GitHub authority.
|
|
616
|
+
*/
|
|
617
|
+
export function validateCoAuthor(input) {
|
|
618
|
+
if (input === undefined)
|
|
619
|
+
return { ok: true, value: null };
|
|
620
|
+
if (input.length === 0 || Buffer.byteLength(input, "utf8") > CO_AUTHOR_MAX_BYTES) {
|
|
621
|
+
return {
|
|
622
|
+
ok: false,
|
|
623
|
+
message: `--co-author expects one Name <email> identity of at most ${String(CO_AUTHOR_MAX_BYTES)} bytes`,
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
if (/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u.test(input) || input.trim() !== input) {
|
|
627
|
+
return {
|
|
628
|
+
ok: false,
|
|
629
|
+
message: "--co-author must be one line with no control or separator characters or surrounding whitespace",
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
const matched = /^([^<>:]+) <([^<>]+)>$/u.exec(input);
|
|
633
|
+
const name = matched?.[1] ?? "";
|
|
634
|
+
const email = matched?.[2] ?? "";
|
|
635
|
+
if (name.length === 0 || name.trim() !== name || !CO_AUTHOR_EMAIL.test(email)) {
|
|
636
|
+
return {
|
|
637
|
+
ok: false,
|
|
638
|
+
message: "--co-author expects exactly Name <email>; it is display credit, not an actor or approval identity",
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
return { ok: true, value: input };
|
|
642
|
+
}
|
|
643
|
+
function coAuthorTrailer(coAuthor) {
|
|
644
|
+
return `Co-authored-by: ${coAuthor}`;
|
|
645
|
+
}
|
|
646
|
+
function messageWithCoAuthor(message, coAuthor) {
|
|
647
|
+
return coAuthor === null ? message : `${message}\n\n${coAuthorTrailer(coAuthor)}`;
|
|
648
|
+
}
|
|
649
|
+
function bodyWithCoAuthor(body, coAuthor) {
|
|
650
|
+
const trailer = coAuthorTrailer(coAuthor);
|
|
651
|
+
const normalized = body.replace(/\r\n/gu, "\n").trimEnd();
|
|
652
|
+
if (normalized === trailer || normalized.endsWith(`\n\n${trailer}`))
|
|
653
|
+
return body;
|
|
654
|
+
return `${body}${body.endsWith("\n") ? "\n" : "\n\n"}${trailer}`;
|
|
655
|
+
}
|
|
656
|
+
function ghWithBodyFile(root, args, body) {
|
|
657
|
+
const scratch = mkdtempSync(join(tmpdir(), "approval-pr-body-"));
|
|
658
|
+
const bodyPath = join(scratch, "body.md");
|
|
659
|
+
try {
|
|
660
|
+
writeFileSync(bodyPath, body, { encoding: "utf8", flag: "wx", mode: 0o600 });
|
|
661
|
+
return gh([...args, "--body-file", bodyPath], root);
|
|
662
|
+
}
|
|
663
|
+
finally {
|
|
664
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
/** The first `http…` line of a `gh` run's output, which is how `gh` names a PR. */
|
|
668
|
+
function urlOf(text) {
|
|
669
|
+
return (text
|
|
670
|
+
.split("\n")
|
|
671
|
+
.map((line) => line.trim())
|
|
672
|
+
.filter((line) => line.startsWith("http"))
|
|
673
|
+
.pop() ?? null);
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* The day's pull request for the records branch: opened once, updated after.
|
|
677
|
+
*
|
|
678
|
+
* ONE PR PER DAY, not one per advance (APRV-204). The daemon advances on a
|
|
679
|
+
* cadence, so `gh pr create` on every run would either fail (a PR for the head
|
|
680
|
+
* branch already exists) or, on a remote that allows it, litter the repository
|
|
681
|
+
* with a pull request per tick. The push has already updated the branch by the
|
|
682
|
+
* time this runs, so an open pull request for that head IS the updated one and
|
|
683
|
+
* this function's whole job is to notice it.
|
|
684
|
+
*
|
|
685
|
+
* `gh pr list` is a read (`read.vcs.remote` in this project's own taxonomy) and
|
|
686
|
+
* asks nothing of the remote's state.
|
|
687
|
+
*/
|
|
688
|
+
function ghPullRequest(root, recordsBranch, range, coAuthor) {
|
|
689
|
+
const listed = gh(["pr", "list", "--head", recordsBranch, "--state", "open", "--json", "url,body"], root);
|
|
690
|
+
if (!listed.ok) {
|
|
691
|
+
return { ok: false, step: "list", message: failureText(listed) };
|
|
692
|
+
}
|
|
693
|
+
const open = parsePrList(listed.stdout);
|
|
694
|
+
if (open.length > 0) {
|
|
695
|
+
const current = open[0];
|
|
696
|
+
if (current === undefined)
|
|
697
|
+
throw new Error("unreachable");
|
|
698
|
+
if (coAuthor !== null) {
|
|
699
|
+
if (current.body === null) {
|
|
700
|
+
return {
|
|
701
|
+
ok: false,
|
|
702
|
+
step: "list",
|
|
703
|
+
message: "the open pull request body was unavailable, so its existing text was not replaced",
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
const body = bodyWithCoAuthor(current.body, coAuthor);
|
|
707
|
+
if (body !== current.body) {
|
|
708
|
+
const edited = ghWithBodyFile(root, ["pr", "edit", recordsBranch], body);
|
|
709
|
+
if (!edited.ok)
|
|
710
|
+
return { ok: false, step: "edit", message: failureText(edited) };
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
return { ok: true, url: current.url, created: false };
|
|
714
|
+
}
|
|
715
|
+
const title = `Log advance: ${range.from === range.to ? `seq ${String(range.from)}` : `seq ${String(range.from)}..${String(range.to)}`}`;
|
|
716
|
+
const body = "This branch carries the append-only log advance, its queue projection, and the payload files the records reference — one commit per advance, and nothing else. " +
|
|
717
|
+
"Merge with a MERGE COMMIT. Nothing else may ride this branch: a log commit alongside other work is what the one-commit rule forbids.";
|
|
718
|
+
const created = coAuthor === null
|
|
719
|
+
? gh(["pr", "create", "--title", title, "--body", body, "--head", recordsBranch], root)
|
|
720
|
+
: ghWithBodyFile(root, ["pr", "create", "--title", title, "--head", recordsBranch], bodyWithCoAuthor(body, coAuthor));
|
|
721
|
+
if (!created.ok) {
|
|
722
|
+
return { ok: false, step: "create", message: failureText(created) };
|
|
723
|
+
}
|
|
724
|
+
return { ok: true, url: urlOf(created.stdout), created: true };
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Arm auto-merge on the day's records pull request (APRV-284).
|
|
728
|
+
*
|
|
729
|
+
* ## Why a records pull request may arm its own merge
|
|
730
|
+
*
|
|
731
|
+
* A records commit carries EXACTLY the log, `QUEUE.md` and
|
|
732
|
+
* `.approval/payloads/` (the `log-advance-dirty-stage` refusal is the other
|
|
733
|
+
* half of that rule), those three are the paths CI's protected-path guard
|
|
734
|
+
* exempts, and there is nothing in them a reviewer reviews: the log is
|
|
735
|
+
* append-only evidence and the queue is a projection of it. So a records pull
|
|
736
|
+
* request is the one shape in this repository that never needs a human to read
|
|
737
|
+
* it, and leaving it at CLEAN until somebody clicks is exactly the failure mode
|
|
738
|
+
* CLAUDE.md's workflow item 7 exists to remove.
|
|
739
|
+
*
|
|
740
|
+
* ## What the arm is, in this project's own taxonomy
|
|
741
|
+
*
|
|
742
|
+
* `gh pr merge <branch> --merge --auto` — the same command with the same class
|
|
743
|
+
* (`vcs.push.main`, `core/command-class.ts`'s `gh-pr-merge` row) a session runs
|
|
744
|
+
* to arm its own pull request. Nothing here mints a class, claims an exemption,
|
|
745
|
+
* or asks a second question: this runs inside whatever authorized the advance,
|
|
746
|
+
* which for the daemon is the `log.advance` grant APRV-204 opened and for a
|
|
747
|
+
* session is the harness hook's answer on the verb.
|
|
748
|
+
*
|
|
749
|
+
* ## The guard, and why the arm is not unconditional
|
|
750
|
+
*
|
|
751
|
+
* The reasoning above holds only while the branch really does carry nothing
|
|
752
|
+
* else. It is a branch on a shared remote, so a commit this verb did not make
|
|
753
|
+
* can be sitting on it, and the advance would fast-forward over that commit and
|
|
754
|
+
* then arm a merge for it. So the arm is preceded by a check of the pushed
|
|
755
|
+
* branch against the base it is measured from, and anything outside
|
|
756
|
+
* {@link ADVANCE_PATHS} WITHHOLDS the arm and names what it saw. Everything
|
|
757
|
+
* ambiguous withholds too: an unreadable diff, a base that could not be
|
|
758
|
+
* resolved. A withheld arm costs a human one click; an unconditional one would
|
|
759
|
+
* land whatever rode the branch.
|
|
760
|
+
*
|
|
761
|
+
* ## An arm `gh` says no to is not a failure
|
|
762
|
+
*
|
|
763
|
+
* Auto-merge disabled on the repository, a merge queue, a pull request already
|
|
764
|
+
* mergeable: `gh` refuses all three, and the advance is committed, pushed and
|
|
765
|
+
* open as a pull request regardless. The refusal is reported and the verb still
|
|
766
|
+
* succeeds — the same rule `cli/amend.ts`'s ceremony arm has followed since
|
|
767
|
+
* APRV-130.
|
|
768
|
+
*/
|
|
769
|
+
function armAutoMerge(root, recordsBranch, span) {
|
|
770
|
+
const carried = branchDiffPaths(root, span.baseSha, span.commit);
|
|
771
|
+
if (carried === null) {
|
|
772
|
+
return {
|
|
773
|
+
state: "withheld",
|
|
774
|
+
note: `\`git diff --name-only ${span.baseSha.slice(0, 12)} ${span.commit.slice(0, 12)}\` could not be read, so what ${recordsBranch} carries is unknown`,
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
const foreign = carried.filter((path) => !isAdvancePath(path));
|
|
778
|
+
if (foreign.length > 0) {
|
|
779
|
+
return {
|
|
780
|
+
state: "withheld",
|
|
781
|
+
note: `${recordsBranch} carries paths an advance does not: ${foreign.join(", ")}. A records pull request arms its own merge because it carries only evidence; this one does not, so it wants a reviewer`,
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
const merged = gh(["pr", "merge", recordsBranch, "--merge", "--auto"], root);
|
|
785
|
+
if (merged.ok)
|
|
786
|
+
return { state: "armed", note: null };
|
|
787
|
+
return {
|
|
788
|
+
state: "refused",
|
|
789
|
+
note: outputLines(merged.stderr, merged.stdout)[0] ?? failureText(merged),
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Every path `from..to` touches, or `null` when git would not say.
|
|
794
|
+
*
|
|
795
|
+
* `null` is not "nothing changed": an empty diff is `[]`, and the two must not
|
|
796
|
+
* collapse, because the caller treats the unknown as a reason to withhold and
|
|
797
|
+
* the empty as a branch carrying nothing foreign.
|
|
798
|
+
*/
|
|
799
|
+
function branchDiffPaths(root, from, to) {
|
|
800
|
+
if (from.length === 0 || to.length === 0)
|
|
801
|
+
return null;
|
|
802
|
+
const diff = git(["diff", "--name-only", `${from}..${to}`], root);
|
|
803
|
+
if (!diff.ok)
|
|
804
|
+
return null;
|
|
805
|
+
return diff.stdout
|
|
806
|
+
.split("\n")
|
|
807
|
+
.map((line) => line.trim())
|
|
808
|
+
.filter((line) => line.length > 0);
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* The urls in `gh pr list --json url` output.
|
|
812
|
+
*
|
|
813
|
+
* Unparseable output answers "no open pull request", which is the conservative
|
|
814
|
+
* reading in exactly one direction: the verb then tries to create one, and a
|
|
815
|
+
* `gh pr create` that finds an existing PR fails loudly with the reason. The
|
|
816
|
+
* opposite default would silently skip opening the day's pull request.
|
|
817
|
+
*/
|
|
818
|
+
function parsePrList(text) {
|
|
819
|
+
let parsed;
|
|
820
|
+
try {
|
|
821
|
+
parsed = JSON.parse(text.trim().length === 0 ? "[]" : text);
|
|
822
|
+
}
|
|
823
|
+
catch {
|
|
824
|
+
return [];
|
|
825
|
+
}
|
|
826
|
+
if (!Array.isArray(parsed))
|
|
827
|
+
return [];
|
|
828
|
+
const pullRequests = [];
|
|
829
|
+
for (const entry of parsed) {
|
|
830
|
+
if (typeof entry !== "object" || entry === null)
|
|
831
|
+
continue;
|
|
832
|
+
const url = entry["url"];
|
|
833
|
+
const body = entry["body"];
|
|
834
|
+
if (typeof url === "string" && url.length > 0) {
|
|
835
|
+
pullRequests.push({ url, body: typeof body === "string" ? body : null });
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
return pullRequests;
|
|
839
|
+
}
|
|
840
|
+
//# sourceMappingURL=log-advance.js.map
|