@joshuafolkken/kit 1.1010.0 → 1.1050.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/.claude/settings.json +1 -5
- package/.claude/skills/dependency-update/SKILL.md +6 -3
- package/.claude/skills/epic-commands/SKILL.md +46 -17
- package/.claude/skills/verify-ui/SKILL.md +1 -1
- package/.claude/skills/workflow-commands/SKILL.md +10 -25
- package/.claude/skills/workflow-commands/background-commands.md +1 -1
- package/.claude/skills/workflow-commands/backlogrun-child.md +6 -4
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +1 -1
- package/.claude/skills/workflow-commands/backlogrun-park.md +6 -0
- package/.claude/skills/workflow-commands/backlogrun-progress.md +30 -36
- package/.claude/skills/workflow-commands/backlogrun-steps.md +16 -16
- package/.claude/skills/workflow-commands/backlogrun.md +3 -12
- package/.claude/skills/workflow-commands/chain-rule.md +2 -2
- package/.claude/skills/workflow-commands/followup-reference.md +2 -2
- package/.claude/skills/workflow-commands/fullrun-steps.md +4 -3
- package/.claude/skills/workflow-commands/fullrun.md +1 -1
- package/.claude/skills/workflow-commands/halfrun.md +2 -1
- package/.claude/skills/workflow-commands/into-target.md +1 -1
- package/.claude/skills/workflow-commands/issue-comments.md +1 -1
- package/.claude/skills/workflow-commands/observation-filing.md +80 -126
- package/.claude/skills/workflow-commands/pre-gate-cut.md +33 -36
- package/.claude/skills/workflow-commands/prerequisite.md +1 -1
- package/.claude/skills/workflow-commands/working-tree-hold.md +2 -2
- package/.codex/hooks.json +1 -5
- package/.github/workflows/auto-tag.yml +21 -0
- package/.github/workflows/publish.yml +9 -10
- package/.github/workflows/sonar-qube.yml +1 -1
- package/AGENTS.md +4 -25
- package/CLAUDE.md +24 -38
- package/GEMINI.md +4 -24
- package/README.md +22 -56
- package/dist/CLAUDE.md +24 -38
- package/dist/hooks/batch-guard.js +4 -4
- package/dist/hooks/{chunk-6PSTVOLH.js → chunk-5BK3K7V4.js} +114 -90
- package/dist/hooks/{chunk-PAXMLGSS.js → chunk-7VPDUBRN.js} +93 -175
- package/dist/hooks/{chunk-KPZN42RT.js → chunk-DMQN45FN.js} +3 -3
- package/dist/hooks/{chunk-WMIR5SNX.js → chunk-EJDX2TOB.js} +13 -3
- package/dist/hooks/{chunk-A2H5I2VD.js → chunk-GQ7KP45M.js} +2 -2
- package/dist/hooks/{chunk-4GDR2YTV.js → chunk-HJIISEAC.js} +4 -4
- package/dist/hooks/{chunk-AWTFKKFW.js → chunk-NAS4C7GP.js} +6 -33
- package/dist/hooks/{chunk-56LGMJJV.js → chunk-NJQOBE6L.js} +2 -2
- package/dist/hooks/{chunk-EZLHXKJ6.js → chunk-RLDX5SOC.js} +1222 -941
- package/dist/hooks/{chunk-GBJUERYK.js → chunk-WQ4OSBSS.js} +10 -3
- package/dist/hooks/codex-hook-adapter.js +10 -10
- package/dist/hooks/duplicate-read-guard.js +5 -5
- package/dist/hooks/format-edited.js +15 -34
- package/dist/hooks/investigation-guard.js +5 -5
- package/dist/hooks/pretool-guard.js +23 -16
- package/dist/hooks/stop-guard.js +21 -200
- package/dist/josh.js +42 -125
- package/dist/version/index.js +466 -42
- package/eslint/base.js +2 -11
- package/package.json +36 -10
- package/prompts/coding-standards.md +15 -290
- package/prompts/collaboration-workflow/cross-repo-epic.md +2 -51
- package/prompts/collaboration-workflow/epic-audit.md +2 -53
- package/prompts/collaboration-workflow/epic-bundle.md +2 -106
- package/prompts/collaboration-workflow/file-edits.md +6 -53
- package/prompts/collaboration-workflow/issue-template.md +4 -4
- package/prompts/collaboration-workflow/operating-rules.md +18 -75
- package/prompts/collaboration-workflow/principles.md +74 -0
- package/prompts/collaboration-workflow/residency.md +55 -15
- package/prompts/collaboration-workflow/rule-delivery.md +5 -46
- package/prompts/collaboration-workflow/shell-body.md +7 -9
- package/prompts/collaboration-workflow/turn-batching.md +21 -37
- package/prompts/collaboration-workflow/upstream-interrupt.md +1 -1
- package/prompts/collaboration-workflow/wip-cap.md +15 -18
- package/prompts/collaboration-workflow.md +22 -28
- package/prompts/sonar-hotspot-handling.md +1 -1
- package/scripts/adopt/adopt-logic.ts +2 -2
- package/scripts/adopt/adopt-toolkits.ts +3 -1
- package/scripts/adopt/adopt.ts +2 -2
- package/scripts/agent/agent-event.ts +2 -9
- package/scripts/agent/agent-exit-record.ts +2 -9
- package/scripts/auto-ok/auto-ok-cli.ts +9 -9
- package/scripts/backlog/backlog-budget-cli.ts +2 -6
- package/scripts/backlog/backlog-drive-cli.ts +20 -10
- package/scripts/backlog/backlog-drive.ts +50 -16
- package/scripts/backlog/backlog-next.ts +24 -8
- package/scripts/backlog/backlog-offer-cli.ts +6 -5
- package/scripts/backlog/backlog-plan-cli.ts +5 -1
- package/scripts/backlog/backlog-plan.ts +1 -1
- package/scripts/backlog/backlog-pool.ts +31 -11
- package/scripts/backlog/backlog-rank.ts +106 -0
- package/scripts/backlog/backlog-waves.ts +42 -29
- package/scripts/build/build-bin.ts +2 -1
- package/scripts/build/build-config-merge.ts +2 -1
- package/scripts/build/build-hooks.ts +31 -6
- package/scripts/build/build-managed-marker.ts +2 -1
- package/scripts/build/build-self-sync-guard.ts +2 -1
- package/scripts/build/build-version.ts +2 -1
- package/scripts/cases/cases-logic.ts +2 -6
- package/scripts/ci/pr-classification.ts +13 -11
- package/scripts/cost-runtime/cost-transcript.ts +4 -11
- package/scripts/delegation/delegation-policy.ts +11 -0
- package/scripts/doctor/doctor-consumer.ts +5 -12
- package/scripts/doctor/doctor-ports.ts +2 -2
- package/scripts/document/document-byte-budget.ts +13 -21
- package/scripts/document/document-section.ts +2 -6
- package/scripts/document/entry-read-budget.ts +6 -5
- package/scripts/{git/git-epic-add-body.ts → epic/epic-add-body.ts} +23 -23
- package/scripts/{git/git-epic-add-plan.ts → epic/epic-add-plan.ts} +23 -26
- package/scripts/{git/git-epic-add.ts → epic/epic-add.ts} +17 -17
- package/scripts/epic/epic-audit-cli.ts +4 -4
- package/scripts/epic/epic-audit-orphans.ts +2 -2
- package/scripts/epic/epic-audit-rationale.ts +1 -1
- package/scripts/epic/epic-audit.ts +1 -1
- package/scripts/{git/git-epic-body.ts → epic/epic-body.ts} +6 -6
- package/scripts/epic/epic-bundle-cli.ts +1 -1
- package/scripts/epic/epic-bundle.ts +2 -2
- package/scripts/epic/epic-candidate-confirm.ts +2 -1
- package/scripts/{git/git-epic-chains.ts → epic/epic-chains.ts} +4 -4
- package/{scripts-ai → scripts/epic}/epic-check.ts +5 -4
- package/{scripts-ai → scripts/epic}/epic-cli.ts +4 -4
- package/scripts/{git/git-epic-close-comment.ts → epic/epic-close-comment.ts} +9 -9
- package/scripts/{git/git-epic-close.ts → epic/epic-close.ts} +23 -19
- package/scripts/epic/epic-cross-repo.ts +21 -38
- package/scripts/{git/git-epic-decision.ts → epic/epic-decision.ts} +12 -14
- package/scripts/epic/epic-fetch.ts +3 -3
- package/scripts/epic/epic-graph.ts +3 -3
- package/scripts/epic/epic-index.ts +2 -2
- package/scripts/epic/epic-issue.ts +1 -1
- package/scripts/epic/epic-lane-offer.ts +11 -22
- package/scripts/epic/epic-next.ts +3 -3
- package/scripts/{git/git-epic-parse.ts → epic/epic-parse.ts} +3 -3
- package/scripts/{git/git-epic-promote.ts → epic/epic-promote.ts} +10 -10
- package/scripts/epic/epic-rank.ts +37 -0
- package/scripts/{git/git-epic-read.ts → epic/epic-read.ts} +9 -9
- package/scripts/{git/git-epic-reconcile.ts → epic/epic-reconcile.ts} +18 -20
- package/scripts/{git/git-epic-reference.ts → epic/epic-reference.ts} +4 -23
- package/scripts/epic/epic-relation-recheck.ts +4 -3
- package/scripts/{git/git-epic-relations.ts → epic/epic-relations.ts} +5 -5
- package/scripts/{git/git-epic-remove-plan.ts → epic/epic-remove-plan.ts} +17 -19
- package/scripts/{git/git-epic-remove.ts → epic/epic-remove.ts} +13 -13
- package/scripts/{git/git-epic-run.ts → epic/epic-run.ts} +27 -27
- package/scripts/{git/git-epic-sections.ts → epic/epic-sections.ts} +6 -6
- package/scripts/{git/git-epic-shape.ts → epic/epic-shape.ts} +7 -7
- package/scripts/epic/epic-solo.ts +29 -28
- package/scripts/{git/git-epic-validate.ts → epic/epic-validate.ts} +13 -13
- package/{scripts-ai → scripts/epic}/epic.ts +17 -16
- package/scripts/gh/fix-gh-packages.ts +2 -1
- package/scripts/gh/gh-spawn.ts +12 -10
- package/scripts/git/constants.ts +1 -1
- package/scripts/git/git-branch.ts +10 -1
- package/scripts/git/git-command.ts +2 -2
- package/scripts/git/git-error.ts +2 -5
- package/scripts/git/git-execa-error.ts +6 -1
- package/{scripts-ai → scripts/git}/git-followup-finish.ts +7 -7
- package/scripts/git/git-followup-flush.ts +74 -48
- package/scripts/git/git-followup-issue-close.ts +2 -2
- package/scripts/git/git-followup-label.ts +1 -1
- package/scripts/git/git-followup-pending.ts +1 -1
- package/{scripts-ai → scripts/git}/git-followup-workflow.ts +23 -11
- package/scripts/git/git-gh-exec.ts +85 -26
- package/scripts/git/git-gh-issue-read.ts +2 -2
- package/scripts/git/git-gh-issue-rest.ts +2 -2
- package/scripts/git/git-gh-issue-write.ts +6 -6
- package/scripts/git/git-gh-pr-read.ts +0 -21
- package/scripts/git/git-gh-pr.ts +2 -23
- package/scripts/git/git-next-issues.ts +34 -5
- package/scripts/git/git-pr-followup-wrapup.ts +2 -6
- package/scripts/git/git-preflight.ts +25 -10
- package/scripts/git/git-push-transport.ts +12 -69
- package/scripts/git/git-spawn.ts +79 -3
- package/scripts/git/git-ssh-keepalive.ts +74 -0
- package/scripts/git/git-staging.ts +21 -9
- package/{scripts-ai → scripts/git}/git-workflow.ts +11 -11
- package/scripts/git/github-issue-url.ts +1 -1
- package/scripts/git/gone-branch.ts +3 -2
- package/scripts/git/issue-labels.ts +13 -1
- package/scripts/git/issue-rank.ts +89 -0
- package/scripts/git/issue-reference.ts +19 -0
- package/scripts/git/main-merge.ts +2 -1
- package/scripts/git/main-sync.ts +3 -2
- package/scripts/git/schemas.ts +1 -1
- package/scripts/git/stash-pop-args.ts +7 -13
- package/scripts/git/stash-pop-cli.ts +2 -1
- package/{scripts-ai → scripts/git}/telegram-test-logic.ts +2 -2
- package/{scripts-ai → scripts/git}/telegram-test.ts +7 -7
- package/scripts/hooks/format-edited-cli.ts +15 -0
- package/scripts/hooks/format-edited-file.ts +2 -9
- package/scripts/hooks/format-rewrite-notice.ts +2 -8
- package/scripts/hooks/pretool-guard-cli.ts +21 -0
- package/scripts/hooks/pretool-guard.ts +14 -15
- package/scripts/init/init-ai-copy.ts +2 -5
- package/scripts/init/init.ts +8 -4
- package/scripts/init/start-exec.ts +7 -1
- package/scripts/init/start-plan.ts +18 -5
- package/scripts/init/start-setup-pr.ts +46 -19
- package/scripts/init/start-steps.ts +43 -10
- package/scripts/init/start.ts +42 -11
- package/scripts/issue/issue-cite.ts +19 -1
- package/scripts/issue/issue-comment-cli.ts +2 -1
- package/scripts/issue/issue-file-cli.ts +2 -1
- package/scripts/issue/issue-fold-cli.ts +9 -16
- package/scripts/issue/issue-scout-cli.ts +8 -13
- package/scripts/issue/measure-rerun-cli.ts +7 -6
- package/scripts/josh/josh-command-map.ts +0 -102
- package/scripts/josh/josh-commands-ai.ts +2 -2
- package/scripts/josh/josh-commands-backlog.ts +1 -1
- package/scripts/josh/josh-commands-development.ts +3 -3
- package/scripts/josh/josh-commands-project.ts +1 -1
- package/scripts/josh/josh-commands-workflow.ts +4 -4
- package/scripts/lane/lane-child-marker.ts +16 -2
- package/scripts/lane/lane-cli.ts +34 -15
- package/scripts/lane/lane-open.ts +3 -2
- package/scripts/lane/lane-registry.ts +3 -6
- package/scripts/lane/lane-report.ts +10 -4
- package/scripts/lib/cli-flags.ts +20 -2
- package/scripts/lib/error-message.ts +9 -0
- package/scripts/lib/read-file.ts +26 -2
- package/scripts/observations/observation-ledger-home.ts +98 -26
- package/scripts/observations/observation-ledger-migrate.ts +26 -21
- package/scripts/observations/observation-ledger-prepare.ts +10 -20
- package/scripts/observations/observation-ledger.ts +58 -25
- package/scripts/observations/observations-flush-cli.ts +6 -23
- package/scripts/observations/observations-flush-landing.ts +2 -1
- package/scripts/observations/observations-flush.ts +29 -20
- package/scripts/package/package-scout-cli.ts +8 -16
- package/scripts/ports/port-command.ts +2 -1
- package/scripts/propagate/propagate-plan.ts +185 -0
- package/scripts/propagate/propagate-publish.ts +6 -7
- package/scripts/propagate/propagate-run.ts +5 -1
- package/scripts/propagate/propagate-steps.ts +18 -2
- package/scripts/propagate/propagate-targets.ts +13 -2
- package/scripts/propagate/propagate.ts +63 -44
- package/scripts/refactor/import-graph.ts +2 -6
- package/scripts/registry-migration/migrate-logic.ts +1 -0
- package/scripts/registry-migration/migrate.ts +3 -2
- package/scripts/registry-migration/user-npmrc.ts +2 -2
- package/scripts/release/github-release.ts +1 -1
- package/scripts/release/release-cli.ts +2 -1
- package/scripts/repo/repo-setting.ts +10 -8
- package/scripts/review/review-attest.ts +44 -0
- package/scripts/review/review-brief-cli.ts +49 -2
- package/scripts/review/review-brief.ts +46 -1
- package/scripts/review/review-finding-ledger.ts +3 -3
- package/scripts/review/review-findings-cli.ts +4 -11
- package/scripts/review/review-record-cli.ts +14 -11
- package/scripts/review/review-record.ts +6 -16
- package/scripts/review/review-stamps.ts +7 -0
- package/scripts/rules/decision-oracle.ts +2 -2
- package/scripts/rules/implementation-cut.ts +1 -1
- package/scripts/rules/issue-fold.ts +1 -2
- package/scripts/rules/lane-carry-conflict.ts +1 -2
- package/scripts/rules/lane-park.ts +1 -2
- package/scripts/rules/pre-gate-cut.ts +2 -2
- package/scripts/rules/rule-body-guard.ts +2 -2
- package/scripts/rules/rule-value-cache.ts +99 -0
- package/scripts/rules/rule-value-cli.ts +10 -9
- package/scripts/rules/rule-value.ts +1 -1
- package/scripts/run/run-carry-args.ts +2 -6
- package/scripts/run/run-carry-cli.ts +5 -7
- package/scripts/run/run-cut-args.ts +7 -13
- package/scripts/run/run-ending-cli.ts +4 -7
- package/scripts/run/run-event-stream.ts +2 -5
- package/scripts/run/run-headless.ts +22 -6
- package/scripts/run/run-liveness-cli.ts +4 -7
- package/scripts/run/run-parent-cut-hook.ts +144 -0
- package/scripts/run/run-progress-args.ts +2 -6
- package/scripts/run/run-progress-cli.ts +2 -1
- package/scripts/run/run-progress-read.ts +2 -1
- package/scripts/run/run-progress.ts +21 -8
- package/scripts/run/run-ship-cli.ts +6 -74
- package/scripts/run/run-ship-preflight.ts +87 -0
- package/scripts/run/run-ship-review-steps.ts +5 -26
- package/scripts/run/run-ship-review.ts +9 -0
- package/scripts/run/run-ship-scoped.ts +44 -0
- package/scripts/run/run-ship-stage.ts +5 -1
- package/scripts/run/run-ship-steps.ts +105 -0
- package/scripts/run/run-ship.ts +2 -0
- package/scripts/run/run-tail-cli.ts +3 -3
- package/scripts/run/run-tidy-stashes.ts +7 -14
- package/scripts/run/run-wake-cli.ts +4 -8
- package/scripts/run/run-wake-describe.ts +19 -1
- package/scripts/run/run-wake-driver.ts +15 -2
- package/scripts/run/run-wake-session.ts +14 -4
- package/scripts/run/run-watcher-hook.ts +1 -1
- package/scripts/safe-chain/project-config.ts +3 -6
- package/scripts/security/security-audit-provision.ts +6 -7
- package/scripts/sonar/sonar-hotspots-cli.ts +2 -1
- package/scripts/sync/directory-copy-guard.ts +2 -1
- package/scripts/sync/sync-ai-files.ts +3 -3
- package/scripts/sync/template-source-logic.ts +3 -4
- package/scripts/test/e2e-retry-check.ts +3 -6
- package/scripts/time-runtime/time-bundle-call.ts +2 -2
- package/scripts/ui/ui-routes-cli.ts +3 -6
- package/scripts/version/bump-version.ts +2 -1
- package/scripts/version/npm-registry.ts +97 -0
- package/scripts/version/version-commands.ts +5 -4
- package/scripts/version/version-remote.ts +38 -33
- package/scripts/version/version-targets.ts +3 -3
- package/.claude/skills/workflow-commands/rule-residency.md +0 -267
- package/evals/scenarios/consult-not-execute.json +0 -20
- package/evals/scenarios/no-implicit-workflow.json +0 -26
- package/evals/scenarios/overrides-both-locations.json +0 -29
- package/evals/scenarios/upstream-interrupt.json +0 -23
- package/evals/scenarios/workflow-skill-loaded.json +0 -16
- package/prompts/agent-lint-protocol.json +0 -79
- package/prompts/agent-rules.md +0 -63
- package/prompts/collaboration-workflow/consultation-vs-execution.md +0 -8
- package/prompts/collaboration-workflow/distributed-docs.md +0 -7
- package/prompts/collaboration-workflow/durable-rules.md +0 -24
- package/prompts/collaboration-workflow/latest-first.md +0 -14
- package/prompts/collaboration-workflow/no-clones.md +0 -11
- package/prompts/collaboration-workflow/simplicity-first.md +0 -9
- package/prompts/collaboration-workflow/single-source-rules.md +0 -13
- package/scripts/build/shim-shell.ts +0 -25
- package/scripts/claude/skill-meta.ts +0 -37
- package/scripts/document/document-scan.ts +0 -108
- package/scripts/document/generate-catalog.ts +0 -69
- package/scripts/document/retired-phrases.ts +0 -143
- package/scripts/document/review-agent-type.ts +0 -46
- package/scripts/document/single-source.ts +0 -73
- package/scripts/git/git-fixture-workspace.ts +0 -88
- package/scripts/hooks/format-edited-file-fixtures.ts +0 -109
- package/scripts/hooks/prompt-hooks.ts +0 -38
- package/scripts/init/hook-command-bootstrap.ts +0 -83
- package/scripts/issue/issue-logic.ts +0 -50
- package/scripts/josh/__snapshots__/josh-command-map.test.ts.snap +0 -1227
- package/scripts/josh/josh-script-reader.ts +0 -11
- package/scripts/package/production-dependency-graph.ts +0 -68
- package/scripts/retrospective/retrospective-cli.ts +0 -97
- package/scripts/retrospective/retrospective.ts +0 -127
- package/scripts/rules/delivered-rules-harness.ts +0 -72
- package/scripts/rules/top-level-scripts-guard.ts +0 -47
- package/scripts/run/run-carry-flush.ts +0 -58
- package/scripts/run/run-transcript.golden.txt +0 -57
- package/scripts/run/run-wake-work.ts +0 -108
- package/scripts/sync/dependabot-pin-logic.ts +0 -135
- package/scripts/test/classify-isolation.ts +0 -149
- package/scripts/test/http-fault-injection.ts +0 -79
- package/scripts/test/josh-harness-environment.ts +0 -260
- package/scripts/test/josh-harness.ts +0 -96
- package/scripts/test/pilot-files.ts +0 -452
- package/scripts/test/pilot-measure.ts +0 -78
- package/scripts/test/test-network-guard.ts +0 -453
- package/scripts/test/test-repository-guard.ts +0 -317
- package/scripts/test/test-state-guard.ts +0 -85
- package/scripts/test/test-stdout-guard.ts +0 -54
- package/scripts/test/test-telegram-guard.ts +0 -90
- package/scripts/test/unit-guard-environment.ts +0 -12
- package/scripts/test/unit-projects.ts +0 -139
- package/scripts/test/vitest-include-globs.ts +0 -12
- package/scripts/time-runtime/time-batch-guard-fixtures.ts +0 -66
- package/scripts-ai/completion-gate.ts +0 -13
- package/scripts-ai/environment-loader.ts +0 -9
- /package/{scripts-ai → scripts/epic}/epic-cli-argv.ts +0 -0
- /package/{scripts-ai → scripts/epic}/epic-cli-remove.ts +0 -0
- /package/{scripts-ai → scripts/git}/followup-issue-number.ts +0 -0
|
@@ -65,13 +65,17 @@ const SKIP_REASONS: Readonly<Record<string, string>> = {
|
|
|
65
65
|
missing_checkout: 'no local checkout at the mapped path — reported, not cloned',
|
|
66
66
|
unreadable: 'package.json could not be read',
|
|
67
67
|
not_selected: 'not the repository --target named',
|
|
68
|
+
carried_above: 'delivered by the propagation of a toolkit above this one',
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
// The reason text for a candidate that is not going to be processed. A missing local checkout is
|
|
71
72
|
// reported rather than cloned: propagation writes to a working tree, and creating one nobody asked
|
|
72
73
|
// for is not a step this command may take on its own.
|
|
73
74
|
function skip_reason(target: PropagateTarget): string {
|
|
74
|
-
|
|
75
|
+
const reason = SKIP_REASONS[target.state] ?? 'not eligible'
|
|
76
|
+
if (target.carriers === undefined) return reason
|
|
77
|
+
|
|
78
|
+
return `${reason}: ${target.carriers.join(', ')}`
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
const LEFTOVER_NOTE = 'upgrade/sync changes left uncommitted'
|
|
@@ -3,6 +3,7 @@ import { git_gh_exec } from '#scripts/git/git-gh-exec'
|
|
|
3
3
|
import { git_gh_issue_write } from '#scripts/git/git-gh-issue-write'
|
|
4
4
|
import { OTHER_CHANGE_LABEL } from '#scripts/git/issue-labels'
|
|
5
5
|
import { GATE_COMMAND } from '#scripts/josh/josh-command-types'
|
|
6
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
6
7
|
import { build_upgrade_shell_command } from '#scripts/version/upgrade-shell-command'
|
|
7
8
|
import { create_version_command_config } from '#scripts/version/version-command-config'
|
|
8
9
|
import { execaSync } from 'execa'
|
|
@@ -87,13 +88,28 @@ function release_names(releases: ReadonlyArray<Release>): string {
|
|
|
87
88
|
return `${names.slice(0, -1).join(NAME_SEPARATOR)}${LAST_SEPARATOR}${last}`
|
|
88
89
|
}
|
|
89
90
|
|
|
91
|
+
// Each release with its own version, for a plan whose releases disagree on one — a toolkit's
|
|
92
|
+
// propagation carries the base packages at the versions that toolkit was verified with
|
|
93
|
+
// (joshuafolkken/kit#2879), so one shared "to <version>" would name a version most of them are not.
|
|
94
|
+
function versioned_names(releases: ReadonlyArray<Release>): string {
|
|
95
|
+
const named = releases.map((release) => ({
|
|
96
|
+
...release,
|
|
97
|
+
package_name: `${release.package_name} to ${release.version}`,
|
|
98
|
+
}))
|
|
99
|
+
|
|
100
|
+
return `Upgrade ${release_names(named)}`
|
|
101
|
+
}
|
|
102
|
+
|
|
90
103
|
// One release reproduces `issue_title` exactly, which is what keeps a `propagate` run's issue title
|
|
91
104
|
// unchanged by the generalization.
|
|
92
105
|
function plan_title(releases: ReadonlyArray<Release>): string {
|
|
93
106
|
const [first] = releases
|
|
94
107
|
if (first === undefined) return EMPTY_PLAN_TITLE
|
|
108
|
+
const is_one_version = releases.every((release) => release.version === first.version)
|
|
95
109
|
|
|
96
|
-
return
|
|
110
|
+
return is_one_version
|
|
111
|
+
? issue_title(release_names(releases), first.version)
|
|
112
|
+
: versioned_names(releases)
|
|
97
113
|
}
|
|
98
114
|
|
|
99
115
|
function plan_body(plan: ReleasePlan): string {
|
|
@@ -235,7 +251,7 @@ function precheck_step(target: PropagateTarget, step: string): StepResult {
|
|
|
235
251
|
// multi-line detail would print raw JSON as an unindented second line and glue that warning to the
|
|
236
252
|
// end of it, where nobody reading the report would see it.
|
|
237
253
|
function to_issue_detail(error: unknown): string {
|
|
238
|
-
const message =
|
|
254
|
+
const message = error_text.message_of(error)
|
|
239
255
|
|
|
240
256
|
return message.replaceAll('\n', ' ').trim()
|
|
241
257
|
}
|
|
@@ -20,9 +20,17 @@ const MANIFEST_NAME = 'package.json'
|
|
|
20
20
|
const NODE_MODULES = 'node_modules'
|
|
21
21
|
|
|
22
22
|
// Why a candidate is not going to be processed, or that it is. `not_selected` is the one state set
|
|
23
|
-
// after classification: a candidate `--target` left out (joshuafolkken/kit#2755).
|
|
23
|
+
// after classification: a candidate `--target` left out (joshuafolkken/kit#2755). `carried_above` is
|
|
24
|
+
// the other: a consumer that installs a toolkit above the supplier, which that toolkit's own
|
|
25
|
+
// propagation delivers instead (joshuafolkken/kit#2879).
|
|
24
26
|
type TargetState =
|
|
25
|
-
|
|
27
|
+
| 'ready'
|
|
28
|
+
| 'up_to_date'
|
|
29
|
+
| 'not_downstream'
|
|
30
|
+
| 'missing_checkout'
|
|
31
|
+
| 'unreadable'
|
|
32
|
+
| 'not_selected'
|
|
33
|
+
| 'carried_above'
|
|
26
34
|
|
|
27
35
|
interface PropagateTarget {
|
|
28
36
|
repo: string
|
|
@@ -31,6 +39,9 @@ interface PropagateTarget {
|
|
|
31
39
|
// The range the consumer currently declares, when it declares one at all. Reported so a skip can
|
|
32
40
|
// say what it skipped rather than only that it did.
|
|
33
41
|
declared_range?: string
|
|
42
|
+
// The toolkits above the supplier this consumer installs, when it is `carried_above` — named in
|
|
43
|
+
// the skip so the report says which propagation will deliver it.
|
|
44
|
+
carriers?: ReadonlyArray<string>
|
|
34
45
|
}
|
|
35
46
|
|
|
36
47
|
// Only the fields propagation reads. `passthrough` is deliberate — a consumer manifest carries far
|
|
@@ -2,17 +2,16 @@
|
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
3
|
import { repo_discovery } from '#scripts/discovery/repo-discovery'
|
|
4
4
|
import { PROJECT_ROOT } from '#scripts/init/init-paths'
|
|
5
|
-
import {
|
|
5
|
+
import { cli_flags } from '#scripts/lib/cli-flags'
|
|
6
6
|
import { status_icons } from '#scripts/lib/status-icons'
|
|
7
|
-
import { self_sync_guard } from '#scripts/self-sync-guard/self-sync-guard-logic'
|
|
8
|
-
import { KIT_PACKAGE_NAME } from '#scripts/version/kit-descriptor'
|
|
9
7
|
import { derive_versions_endpoint } from '#scripts/version/version-command-config'
|
|
10
8
|
import { version_targets } from '#scripts/version/version-targets'
|
|
11
9
|
import { propagate_git } from './propagate-git'
|
|
10
|
+
import { propagate_plan } from './propagate-plan'
|
|
12
11
|
import { propagate_publish } from './propagate-publish'
|
|
13
12
|
import { propagate_run } from './propagate-run'
|
|
14
13
|
import { propagate_select, type TargetSelection } from './propagate-select'
|
|
15
|
-
import { propagate_steps } from './propagate-steps'
|
|
14
|
+
import { propagate_steps, type ReleasePlan } from './propagate-steps'
|
|
16
15
|
import { propagate_targets, type PropagateTarget } from './propagate-targets'
|
|
17
16
|
|
|
18
17
|
// `josh propagate` — carry the release this repository just published into every consumer checked
|
|
@@ -72,12 +71,14 @@ function parse_options(argv: ReadonlyArray<string>): RunOptions {
|
|
|
72
71
|
...(target !== undefined && { target }),
|
|
73
72
|
}
|
|
74
73
|
const usage =
|
|
75
|
-
target_usage ??
|
|
74
|
+
target_usage ??
|
|
75
|
+
cli_flags.refuse_unknown_flags(rest, [...KNOWN_FLAGS, TARGET_USAGE], 'propagate')
|
|
76
76
|
|
|
77
77
|
return usage === undefined ? options : { ...options, usage }
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
// Propagation runs from the supplier's own repository, and only there
|
|
80
|
+
// Propagation runs from the supplier's own repository, and only there — kit's, or a toolkit's built
|
|
81
|
+
// on it (joshuafolkken/kit#2879).
|
|
81
82
|
//
|
|
82
83
|
// This is also what decides who propagates when several sessions are running: in the
|
|
83
84
|
// per-repository concurrency model there is one session per checkout, so the session standing in
|
|
@@ -85,13 +86,7 @@ function parse_options(argv: ReadonlyArray<string>): RunOptions {
|
|
|
85
86
|
// convention enforced at the boundary, not a lock — two checkouts of the supplier would both pass,
|
|
86
87
|
// which is why each consumer is additionally refused unless its working tree is clean.
|
|
87
88
|
function refuse_outside_source_repository(project_root: string): string | undefined {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return [
|
|
91
|
-
`Refusing to propagate: this is not ${KIT_PACKAGE_NAME}'s own repository.`,
|
|
92
|
-
'Propagation carries a published release outward, so it runs from the package that published it.',
|
|
93
|
-
`Run it from the ${KIT_PACKAGE_NAME} checkout instead.`,
|
|
94
|
-
].join('\n')
|
|
89
|
+
return propagate_plan.resolve_supplier(project_root).refusal
|
|
95
90
|
}
|
|
96
91
|
|
|
97
92
|
// The version to carry: this repository's own declared version, which is what the merge published.
|
|
@@ -143,10 +138,14 @@ function resolve_run_version(project_root: string, is_dry_run = false): RunVersi
|
|
|
143
138
|
return resolve_supplier_version(project_root, is_dry_run)
|
|
144
139
|
}
|
|
145
140
|
|
|
146
|
-
async function await_publish(
|
|
141
|
+
async function await_publish(
|
|
142
|
+
package_name: string,
|
|
143
|
+
target_version: string,
|
|
144
|
+
is_skipped: boolean,
|
|
145
|
+
): Promise<boolean> {
|
|
147
146
|
if (is_skipped) return true
|
|
148
|
-
console.info(`Waiting for ${
|
|
149
|
-
const endpoint = derive_versions_endpoint(
|
|
147
|
+
console.info(`Waiting for ${package_name}@${target_version} to appear in the registry…`)
|
|
148
|
+
const endpoint = derive_versions_endpoint(package_name)
|
|
150
149
|
const result = await propagate_publish.wait_for_publish(endpoint, target_version)
|
|
151
150
|
|
|
152
151
|
if (result.state === 'published') {
|
|
@@ -178,49 +177,65 @@ function announce_run_version(resolved: RunVersion): string | undefined {
|
|
|
178
177
|
|
|
179
178
|
interface RunPlan {
|
|
180
179
|
version: string
|
|
180
|
+
releases: ReleasePlan
|
|
181
181
|
targets: ReadonlyArray<PropagateTarget>
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
// The consumers this run will consider, narrowed by `--target`. Resolved before the publish wait so
|
|
185
|
-
// a mistyped name fails at once instead of after minutes of polling the registry.
|
|
186
|
-
|
|
185
|
+
// a mistyped name fails at once instead of after minutes of polling the registry. A consumer that
|
|
186
|
+
// installs a toolkit above the supplier is left to that toolkit's propagation (joshuafolkken/kit#2879).
|
|
187
|
+
function resolve_run_targets(
|
|
188
|
+
releases: ReleasePlan,
|
|
189
|
+
version: string,
|
|
190
|
+
target: string | undefined,
|
|
191
|
+
): TargetSelection {
|
|
187
192
|
const map = repo_discovery.discover_repositories(PROJECT_ROOT)
|
|
188
|
-
const
|
|
193
|
+
const supplier = propagate_plan.supplier_name(releases)
|
|
194
|
+
const targets = propagate_targets
|
|
195
|
+
.resolve_targets(map, supplier, version)
|
|
196
|
+
.map((candidate) => propagate_plan.mark_carried(candidate, releases))
|
|
189
197
|
|
|
190
198
|
return propagate_select.select_target(targets, target)
|
|
191
199
|
}
|
|
192
200
|
|
|
193
|
-
//
|
|
201
|
+
// Print why the run stopped before any consumer was touched.
|
|
202
|
+
function refuse_run(refusal: string): void {
|
|
203
|
+
console.error(refusal)
|
|
204
|
+
console.error(UNTOUCHED_NOTE)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
interface PlanOutcome {
|
|
208
|
+
plan?: RunPlan
|
|
209
|
+
refusal?: string
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// The releases and the consumers for a version already accepted, or the reason there are none.
|
|
213
|
+
function plan_consumers(version: string, options: RunOptions): PlanOutcome {
|
|
214
|
+
const { plan: releases, refusal } = propagate_plan.build_plan(PROJECT_ROOT, version)
|
|
215
|
+
if (releases === undefined) return { refusal: refusal ?? UNTOUCHED_NOTE }
|
|
216
|
+
const selection = resolve_run_targets(releases, version, options.target)
|
|
217
|
+
if (selection.refusal !== undefined) return { refusal: selection.refusal }
|
|
218
|
+
|
|
219
|
+
return { plan: { version, releases, targets: selection.targets } }
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// The version, the releases and the consumers, or nothing once whatever stopped the run has been
|
|
223
|
+
// printed.
|
|
194
224
|
function plan_run(options: RunOptions): RunPlan | undefined {
|
|
195
225
|
const version = announce_run_version(resolve_run_version(PROJECT_ROOT, options.is_dry_run))
|
|
196
226
|
if (version === undefined) return undefined
|
|
197
|
-
const
|
|
227
|
+
const { plan, refusal } = plan_consumers(version, options)
|
|
228
|
+
if (refusal !== undefined) refuse_run(refusal)
|
|
198
229
|
|
|
199
|
-
|
|
200
|
-
console.error(selection.refusal)
|
|
201
|
-
console.error(UNTOUCHED_NOTE)
|
|
202
|
-
|
|
203
|
-
return undefined
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return { version, targets: selection.targets }
|
|
230
|
+
return plan
|
|
207
231
|
}
|
|
208
232
|
|
|
209
233
|
// Everything after the publish wait: run each consumer, print one report.
|
|
210
234
|
function propagate_to_consumers(plan: RunPlan, is_dry_run: boolean): number {
|
|
211
|
-
const {
|
|
235
|
+
const { releases, targets } = plan
|
|
212
236
|
const step = is_dry_run
|
|
213
|
-
?
|
|
214
|
-
: propagate_steps.create_step_runner(
|
|
215
|
-
releases: [
|
|
216
|
-
{
|
|
217
|
-
package_name: KIT_PACKAGE_NAME,
|
|
218
|
-
version,
|
|
219
|
-
bin_name: propagate_steps.JOSH_BIN,
|
|
220
|
-
},
|
|
221
|
-
],
|
|
222
|
-
origin: propagate_steps.PROPAGATE_ORIGIN,
|
|
223
|
-
})
|
|
237
|
+
? propagate_plan.create_step_describer(releases)
|
|
238
|
+
: propagate_steps.create_step_runner(releases)
|
|
224
239
|
const results = propagate_run.run_targets(
|
|
225
240
|
targets,
|
|
226
241
|
step,
|
|
@@ -235,8 +250,12 @@ function propagate_to_consumers(plan: RunPlan, is_dry_run: boolean): number {
|
|
|
235
250
|
|
|
236
251
|
// A dry run never waits: the whole reason to ask for one is to see the target list, and a wait that
|
|
237
252
|
// times out would end the run before the list was ever printed.
|
|
238
|
-
async function resolve_publish(
|
|
239
|
-
return await await_publish(
|
|
253
|
+
async function resolve_publish(plan: RunPlan, options: RunOptions): Promise<boolean> {
|
|
254
|
+
return await await_publish(
|
|
255
|
+
propagate_plan.supplier_name(plan.releases),
|
|
256
|
+
plan.version,
|
|
257
|
+
options.is_dry_run || options.is_publish_wait_skipped,
|
|
258
|
+
)
|
|
240
259
|
}
|
|
241
260
|
|
|
242
261
|
async function run(argv: ReadonlyArray<string>): Promise<number> {
|
|
@@ -250,7 +269,7 @@ async function run(argv: ReadonlyArray<string>): Promise<number> {
|
|
|
250
269
|
|
|
251
270
|
const plan = plan_run(options)
|
|
252
271
|
if (plan === undefined) return FAILURE_EXIT_CODE
|
|
253
|
-
if (!(await resolve_publish(plan
|
|
272
|
+
if (!(await resolve_publish(plan, options))) return FAILURE_EXIT_CODE
|
|
254
273
|
|
|
255
274
|
return propagate_to_consumers(plan, options.is_dry_run)
|
|
256
275
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs'
|
|
2
1
|
import path from 'node:path'
|
|
2
|
+
import { file_reader } from '#scripts/lib/read-file'
|
|
3
3
|
|
|
4
4
|
// The scope expansion `prompts/refactoring.md` §4.3 used to describe in prose (joshuafolkken/kit#2180):
|
|
5
5
|
// from the seed files, follow imports both ways — the files a seed imports and the files that import a
|
|
@@ -67,11 +67,7 @@ function resolve_spec(
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
function read_source(file: string): string {
|
|
70
|
-
|
|
71
|
-
return readFileSync(file, 'utf8')
|
|
72
|
-
} catch {
|
|
73
|
-
return ''
|
|
74
|
-
}
|
|
70
|
+
return file_reader.read_if_readable(file) ?? ''
|
|
75
71
|
}
|
|
76
72
|
|
|
77
73
|
function file_imports(file: string, root: string, known: ReadonlySet<string>): ReadonlySet<string> {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { existsSync,
|
|
1
|
+
import { existsSync, rmSync, writeFileSync } from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { file_reader } from '#scripts/lib/read-file'
|
|
4
5
|
import { execa } from 'execa'
|
|
5
6
|
import { z } from 'zod'
|
|
6
7
|
import { migrate_logic, type MigrationPlan } from './migrate-logic'
|
|
@@ -70,7 +71,7 @@ interface MigrationContext {
|
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
function read_file(file_path: string): string {
|
|
73
|
-
return
|
|
74
|
+
return file_reader.read_file_or_empty(file_path)
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
function has_registry_override(environment: NodeJS.ProcessEnv): boolean {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'node:fs'
|
|
2
1
|
import os from 'node:os'
|
|
3
2
|
import path from 'node:path'
|
|
3
|
+
import { file_reader } from '#scripts/lib/read-file'
|
|
4
4
|
|
|
5
5
|
const NPMRC = '.npmrc'
|
|
6
6
|
|
|
7
7
|
function read(home: string = os.homedir()): string {
|
|
8
8
|
const file = path.join(home, NPMRC)
|
|
9
9
|
|
|
10
|
-
return
|
|
10
|
+
return file_reader.read_file_or_empty(file)
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const user_npmrc = { read }
|
|
@@ -4,7 +4,7 @@ import { z } from 'zod'
|
|
|
4
4
|
|
|
5
5
|
const API_URL = 'https://api.github.com/repos/joshuafolkken/kit/releases'
|
|
6
6
|
const WORKFLOW_RUNS_URL =
|
|
7
|
-
'https://api.github.com/repos/joshuafolkken/kit/actions/workflows/publish.yml/runs?
|
|
7
|
+
'https://api.github.com/repos/joshuafolkken/kit/actions/workflows/publish.yml/runs?per_page=100'
|
|
8
8
|
const WORKFLOW_JOBS_URL = 'https://api.github.com/repos/joshuafolkken/kit/actions/runs'
|
|
9
9
|
const TAG_PATTERN = /^v\d+\.\d+\.\d+(?:[.-][0-9A-Za-z.-]+)?$/u
|
|
10
10
|
const REQUEST_TIMEOUT_MS = 30_000
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
3
|
import { git_command } from '#scripts/git/git-command'
|
|
4
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
4
5
|
import { release_history } from './release-history'
|
|
5
6
|
import { release_plan, type ReleasePlan } from './release-plan'
|
|
6
7
|
import { release_publish } from './release-publish'
|
|
@@ -76,7 +77,7 @@ async function main(): Promise<void> {
|
|
|
76
77
|
try {
|
|
77
78
|
process.exit(await run(is_dry_run_requested(process.argv)))
|
|
78
79
|
} catch (error) {
|
|
79
|
-
console.error(
|
|
80
|
+
console.error(error_text.message_of(error))
|
|
80
81
|
process.exit(FAILURE_EXIT_CODE)
|
|
81
82
|
}
|
|
82
83
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { git_gh_exec } from '#scripts/git/git-gh-exec'
|
|
2
2
|
import { PROJECT_ROOT } from '#scripts/init/init-paths'
|
|
3
|
-
import { read_spawn_stdout } from '#scripts/lib/spawn-exit'
|
|
4
|
-
import { execaSync } from 'execa'
|
|
5
3
|
|
|
6
4
|
// The shared half of every "report a GitHub repository setting kit cannot write" check.
|
|
7
5
|
//
|
|
@@ -27,17 +25,21 @@ interface RepoApiResult {
|
|
|
27
25
|
stdout: string
|
|
28
26
|
}
|
|
29
27
|
|
|
28
|
+
const FAILED_EXIT_CODE = 1
|
|
29
|
+
|
|
30
30
|
// One `gh api` call. Never throws and never propagates a non-zero exit: an unreadable answer is a
|
|
31
|
-
// reported status, not a command failure.
|
|
31
|
+
// reported status, not a command failure. Every failure reads as `FAILED_EXIT_CODE` with no output,
|
|
32
|
+
// which `parse_payload` treats as unreadable whatever gh's own exit code was.
|
|
32
33
|
function query_repo_api(api_path: string): RepoApiResult {
|
|
33
|
-
const
|
|
34
|
-
|
|
34
|
+
const stdout = git_gh_exec.read_gh_api_sync({
|
|
35
|
+
path: api_path,
|
|
35
36
|
cwd: PROJECT_ROOT,
|
|
36
|
-
|
|
37
|
-
timeout: GH_TIMEOUT_MS,
|
|
37
|
+
timeout_ms: GH_TIMEOUT_MS,
|
|
38
38
|
})
|
|
39
39
|
|
|
40
|
-
return
|
|
40
|
+
return stdout === undefined
|
|
41
|
+
? { exit_code: FAILED_EXIT_CODE, stdout: '' }
|
|
42
|
+
: { exit_code: 0, stdout }
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
// The response body as an object, or nothing. A non-zero exit covers every case where the answer
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto'
|
|
2
|
+
import { lstatSync } from 'node:fs'
|
|
2
3
|
import { git_command } from '#scripts/git/git-command'
|
|
3
4
|
import { PROJECT_ROOT } from '#scripts/init/init-paths'
|
|
4
5
|
import { stamp_file } from '#scripts/josh/stamp-file'
|
|
@@ -240,6 +241,47 @@ function check(now: number = Date.now(), root?: string): AttestVerdict {
|
|
|
240
241
|
return verdict
|
|
241
242
|
}
|
|
242
243
|
|
|
244
|
+
function is_written_since(source: string, taken_at: string): boolean {
|
|
245
|
+
try {
|
|
246
|
+
const stats = lstatSync(source)
|
|
247
|
+
|
|
248
|
+
return stats.isFile() && stats.mtimeMs >= Date.parse(taken_at)
|
|
249
|
+
} catch {
|
|
250
|
+
return false
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// **Whether the last briefed review of this branch finished** (joshuafolkken/kit#2945). A ship resumed
|
|
255
|
+
// after a stop re-issues round 1, and without this answer it briefed the whole change again — the
|
|
256
|
+
// files the finished review had just read, re-read at full cost. It is asked before `record_target`
|
|
257
|
+
// mints the next nonce, since that call drops the record it reads. The branch must match: the pointer
|
|
258
|
+
// is keyed on the root, and a lane root outlives the branch a previous run reviewed there.
|
|
259
|
+
//
|
|
260
|
+
// **An attestation alone is not a finished review.** The reviewer may attest and then die before it
|
|
261
|
+
// reports — a timeout, a usage limit — and narrowing on that would call files nobody read "already
|
|
262
|
+
// read". So the findings file, written only once the review has its verdict, must postdate the brief
|
|
263
|
+
// this pointer names; one left from an earlier review is older than that brief and does not count.
|
|
264
|
+
function is_reviewed_on(checkout: ReviewCheckout, findings_path: string, root?: string): boolean {
|
|
265
|
+
const pointer = read_pointer(root)
|
|
266
|
+
|
|
267
|
+
if (pointer === undefined) return false
|
|
268
|
+
|
|
269
|
+
const verdict = verdict_for(pointer.nonce)
|
|
270
|
+
|
|
271
|
+
if (verdict.status !== 'ok' || verdict.expected?.branch !== checkout.branch) return false
|
|
272
|
+
|
|
273
|
+
return is_written_since(findings_path, pointer.taken_at)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Whether a record written at `written_at` belongs to the brief the pointer names rather than an earlier
|
|
277
|
+
// one (joshuafolkken/kit#2945): that brief writes it right after minting the pointer. ISO timestamps
|
|
278
|
+
// order as strings.
|
|
279
|
+
function is_briefed_since(written_at: string, root?: string): boolean {
|
|
280
|
+
const pointer = read_pointer(root)
|
|
281
|
+
|
|
282
|
+
return pointer !== undefined && written_at >= pointer.taken_at
|
|
283
|
+
}
|
|
284
|
+
|
|
243
285
|
// The pointer is keyed on the repository root rather than on `process.cwd()`, which is what
|
|
244
286
|
// `PROJECT_ROOT` is. `josh review:brief` records against the root it just asked git for, so the check
|
|
245
287
|
// has to ask the same question: run the two from different directories — one from `scripts/`, one
|
|
@@ -303,6 +345,8 @@ const review_attest = {
|
|
|
303
345
|
clear_here,
|
|
304
346
|
EXPECT_PREFIX,
|
|
305
347
|
expect_path,
|
|
348
|
+
is_briefed_since,
|
|
349
|
+
is_reviewed_on,
|
|
306
350
|
MISMATCH_VERDICT,
|
|
307
351
|
MISSING_REASON,
|
|
308
352
|
POINTER_PREFIX,
|
|
@@ -6,8 +6,13 @@ import { scoped_green } from '#scripts/gate/scoped-green'
|
|
|
6
6
|
import { change_base } from '#scripts/git/change-base'
|
|
7
7
|
import { changed_paths } from '#scripts/git/changed-paths'
|
|
8
8
|
import { git_command } from '#scripts/git/git-command'
|
|
9
|
+
import { PROJECT_ROOT } from '#scripts/init/init-paths'
|
|
10
|
+
import type { FileMapStamp } from '#scripts/josh/file-map-stamp'
|
|
9
11
|
import { find_package_directory } from '#scripts/josh/josh-logic'
|
|
10
12
|
import { path_decision } from '#scripts/josh/path-decision'
|
|
13
|
+
import { stamp_file } from '#scripts/josh/stamp-file'
|
|
14
|
+
import { openai_review_broker } from '#scripts/lane/openai-review-broker'
|
|
15
|
+
import { run_ship_review } from '#scripts/run/run-ship-review'
|
|
11
16
|
import { review_attest } from './review-attest'
|
|
12
17
|
import { review_brief } from './review-brief'
|
|
13
18
|
import { review_checkout, type ReviewCheckout } from './review-checkout'
|
|
@@ -134,13 +139,54 @@ function record_round_one(
|
|
|
134
139
|
// failed to record would hand the review a command that cannot succeed and the run a check that
|
|
135
140
|
// answers `not-required` — the guard gone, silently, which is the shape this whole record exists to
|
|
136
141
|
// remove.
|
|
137
|
-
|
|
142
|
+
//
|
|
143
|
+
// **Whether this is a resume is read first** (joshuafolkken/kit#2945): `record_target` drops the
|
|
144
|
+
// previous nonce's records, so asked afterwards the finished review would always read as absent. The
|
|
145
|
+
// findings file is the ship's, keyed on `PROJECT_ROOT` as the ship keys it; a review run outside the
|
|
146
|
+
// ship writes none, so it never reads as a resume and keeps the whole change.
|
|
147
|
+
//
|
|
148
|
+
// **The narrowing is measured from the tree that review was briefed on**, and only when that record
|
|
149
|
+
// was written by the brief the pointer names: one left by an earlier brief is not the tree the
|
|
150
|
+
// finished review read, so the resume reads the whole change instead.
|
|
151
|
+
function attested_tree(checkout: ReviewCheckout): FileMapStamp | undefined {
|
|
152
|
+
const { findings } = openai_review_broker.review_paths(PROJECT_ROOT)
|
|
153
|
+
const briefed = review_stamps.briefed_stamp.read()
|
|
154
|
+
|
|
155
|
+
if (briefed === undefined || !review_attest.is_briefed_since(briefed.taken_at, checkout.root)) {
|
|
156
|
+
return undefined
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const is_clean =
|
|
160
|
+
review_attest.is_reviewed_on(checkout, findings, checkout.root) &&
|
|
161
|
+
run_ship_review.is_clean_round_one(stamp_file.read_stamp_text(findings))
|
|
162
|
+
|
|
163
|
+
return is_clean ? briefed : undefined
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function open_contract(): Promise<{
|
|
167
|
+
checkout: ReviewCheckout
|
|
168
|
+
nonce: string
|
|
169
|
+
resumed_from: FileMapStamp | undefined
|
|
170
|
+
}> {
|
|
138
171
|
const checkout = await review_checkout.read_checkout()
|
|
172
|
+
const resumed_from = attested_tree(checkout)
|
|
139
173
|
|
|
140
174
|
// Keyed on the root git just answered with, never on `process.cwd()`: the check made before the
|
|
141
175
|
// merge asks git the same question, and the two hash different keys the moment one of them runs
|
|
142
176
|
// from a subdirectory — a mismatch that would drop the guard with nothing printed.
|
|
143
|
-
return { checkout, nonce: review_attest.record_target(checkout, checkout.root) }
|
|
177
|
+
return { checkout, nonce: review_attest.record_target(checkout, checkout.root), resumed_from }
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Retaken on every round-1 brief, after the pointer it pairs with (joshuafolkken/kit#2945). Swallowed:
|
|
181
|
+
// a record that failed to write predates the next pointer, so the resume widens rather than narrows.
|
|
182
|
+
function record_briefed(round: number, tree: Record<string, string>, base?: string): void {
|
|
183
|
+
if (round !== FIRST_ROUND) return
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
review_stamps.briefed_stamp.write(tree, undefined, base)
|
|
187
|
+
} catch {
|
|
188
|
+
/* no record widens the resume rather than narrowing it */
|
|
189
|
+
}
|
|
144
190
|
}
|
|
145
191
|
|
|
146
192
|
interface ComposeRequest {
|
|
@@ -252,6 +298,7 @@ async function run_review(round: number, reading: ChangeReading): Promise<number
|
|
|
252
298
|
if (resolved.kind === 'rejected') return report_error(resolved.note)
|
|
253
299
|
console.info(await compose_brief({ round, paths, tree, base, profile: resolved.profile }))
|
|
254
300
|
record_round_one(round, tree, commit)
|
|
301
|
+
record_briefed(round, tree, commit)
|
|
255
302
|
|
|
256
303
|
return 0
|
|
257
304
|
}
|
|
@@ -357,6 +357,39 @@ function round_two_block(
|
|
|
357
357
|
return narrow_block(scope.recorded_at, scope, root, base)
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
+
// **A resumed round 1 reads what changed since the attested review, reusing round 2's reconciliation**
|
|
361
|
+
// (joshuafolkken/kit#2945). A ship that stopped and was re-issued briefed the whole change again, so
|
|
362
|
+
// the reviewer re-read every file the attested review had just read. The scope is the comparison round
|
|
363
|
+
// 2 makes, measured from the tree that review was briefed on. Every reading that cannot narrow falls back to the whole
|
|
364
|
+
// change: no record, a moved base, or an empty target. An empty target is included because a resumed
|
|
365
|
+
// review that read nothing would report a clean verdict over the one already recorded.
|
|
366
|
+
const RESUMED_HEADING =
|
|
367
|
+
'Resumed round 1 — a review of this branch was already attested, so this pass reads only what changed since the tree it read.'
|
|
368
|
+
|
|
369
|
+
const RESUMED_QUESTION =
|
|
370
|
+
'Review these files in full against the rubric — they are the only code this pass has not already read.'
|
|
371
|
+
|
|
372
|
+
function resumed_target(
|
|
373
|
+
snapshot: FileMapStamp | undefined,
|
|
374
|
+
tree: Record<string, string>,
|
|
375
|
+
root: string,
|
|
376
|
+
base: string,
|
|
377
|
+
): string {
|
|
378
|
+
const scope = round_two_scope(snapshot, tree, base)
|
|
379
|
+
|
|
380
|
+
if (scope.recorded_at === undefined || scope.target.length === 0) {
|
|
381
|
+
return whole_change_target(root, base)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return [
|
|
385
|
+
RESUMED_HEADING,
|
|
386
|
+
`The attested review read the tree recorded at ${scope.recorded_at}.`,
|
|
387
|
+
`Target: only these files, which changed since that tree:\n${format_paths(root, scope.target)}`,
|
|
388
|
+
...reconciliation(root, base, scope),
|
|
389
|
+
RESUMED_QUESTION,
|
|
390
|
+
].join('\n')
|
|
391
|
+
}
|
|
392
|
+
|
|
360
393
|
interface BriefStamps {
|
|
361
394
|
gate: FileMapStamp | undefined
|
|
362
395
|
in_flight: FileMapStamp | undefined
|
|
@@ -377,12 +410,23 @@ interface BriefInput {
|
|
|
377
410
|
// The rubric's absolute path, resolved by the caller against the kit package rather than the review
|
|
378
411
|
// checkout, so it resolves in a consumer that ships no `prompts/` of its own (joshuafolkken/kit#2402).
|
|
379
412
|
rubric_path: string
|
|
413
|
+
// The tree a finished, clean review of this branch was briefed on, so round 1 is a resume
|
|
414
|
+
// (joshuafolkken/kit#2945). Absent, round 1 reads the whole change.
|
|
415
|
+
resumed_from?: FileMapStamp | undefined
|
|
380
416
|
}
|
|
381
417
|
|
|
382
418
|
const SECOND_ROUND = 2
|
|
383
419
|
|
|
420
|
+
function round_one_target(input: BriefInput): string {
|
|
421
|
+
const { root } = input.checkout
|
|
422
|
+
|
|
423
|
+
if (input.resumed_from === undefined) return whole_change_target(root, input.base)
|
|
424
|
+
|
|
425
|
+
return resumed_target(input.resumed_from, input.tree, root, input.base)
|
|
426
|
+
}
|
|
427
|
+
|
|
384
428
|
function target_block(input: BriefInput): string {
|
|
385
|
-
if (input.round < SECOND_ROUND) return
|
|
429
|
+
if (input.round < SECOND_ROUND) return round_one_target(input)
|
|
386
430
|
|
|
387
431
|
return round_two_block(input.stamps.round_one, input.tree, input.checkout.root, input.base)
|
|
388
432
|
}
|
|
@@ -422,6 +466,7 @@ const review_brief = {
|
|
|
422
466
|
green_stamp,
|
|
423
467
|
in_flight_line,
|
|
424
468
|
live_marker,
|
|
469
|
+
RESUMED_HEADING,
|
|
425
470
|
matching_stamp,
|
|
426
471
|
no_snapshot_line,
|
|
427
472
|
NOTHING_LEFT_LINE,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// The review-finding line's grammar, its category vocabulary and the aggregation over it — the single
|
|
2
2
|
// place all three are defined (joshuafolkken/kit#2325). A `/code-review` round's findings evaporated
|
|
3
3
|
// the moment they were fixed, so no one could say which category recurred. They now land in the same
|
|
4
|
-
// append-only ledger the observation lines use (`docs/maintainers/observations
|
|
5
|
-
// prefix so `observation-ledger-line.ts`'s `- k:` grammar never treats them as its own — one ledger
|
|
6
|
-
//
|
|
4
|
+
// append-only ledger the observation lines use (`docs/maintainers/observations/`), under a distinct `- rf:`
|
|
5
|
+
// prefix so `observation-ledger-line.ts`'s `- k:` grammar never treats them as its own — one ledger,
|
|
6
|
+
// one commit path, a second line *type* rather than a second ledger.
|
|
7
7
|
|
|
8
8
|
const FINDING_PREFIX = '- rf:'
|
|
9
9
|
const FIELD_SEPARATOR = ' | '
|