@joshuafolkken/kit 1.1005.0 → 1.1035.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/workflow-commands/SKILL.md +8 -23
- package/.claude/skills/workflow-commands/backlogrun-child.md +4 -2
- package/.claude/skills/workflow-commands/backlogrun.md +3 -12
- 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/observation-filing.md +42 -43
- package/.claude/skills/workflow-commands/prerequisite.md +1 -1
- package/.codex/hooks.json +1 -5
- 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 -58
- package/dist/CLAUDE.md +24 -38
- package/dist/hooks/batch-guard.js +4 -4
- package/dist/hooks/{chunk-WMIR5SNX.js → chunk-D67KVGGY.js} +3 -3
- package/dist/hooks/{chunk-EZLHXKJ6.js → chunk-EIFCAJRB.js} +70 -27
- package/dist/hooks/{chunk-KPZN42RT.js → chunk-GJT4BNVE.js} +3 -3
- package/dist/hooks/{chunk-PAXMLGSS.js → chunk-GLBDBJOK.js} +44 -24
- package/dist/hooks/{chunk-GBJUERYK.js → chunk-HOYJU5RM.js} +1 -1
- package/dist/hooks/{chunk-6PSTVOLH.js → chunk-HU66PWWL.js} +33 -57
- package/dist/hooks/{chunk-A2H5I2VD.js → chunk-KG6PZD72.js} +2 -2
- package/dist/hooks/{chunk-AWTFKKFW.js → chunk-XPLP27WA.js} +3 -8
- package/dist/hooks/{chunk-4GDR2YTV.js → chunk-Y27M6A7X.js} +4 -4
- package/dist/hooks/{chunk-56LGMJJV.js → chunk-ZXNE6NY3.js} +2 -2
- package/dist/hooks/codex-hook-adapter.js +10 -10
- package/dist/hooks/duplicate-read-guard.js +5 -5
- package/dist/hooks/format-edited.js +2 -2
- package/dist/hooks/investigation-guard.js +5 -5
- package/dist/hooks/pretool-guard.js +9 -9
- package/dist/hooks/stop-guard.js +11 -15
- package/dist/josh.js +38 -19
- package/dist/version/index.js +93 -41
- package/eslint/base.js +2 -11
- package/package.json +32 -6
- 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/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 +54 -14
- package/prompts/collaboration-workflow/rule-delivery.md +1 -11
- package/prompts/collaboration-workflow/upstream-interrupt.md +1 -1
- package/prompts/collaboration-workflow.md +22 -28
- package/scripts/adopt/adopt-logic.ts +2 -2
- package/scripts/adopt/adopt-toolkits.ts +1 -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/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-offer-cli.ts +6 -5
- package/scripts/backlog/backlog-plan.ts +1 -1
- package/scripts/build/build-bin.ts +2 -1
- package/scripts/build/build-config-merge.ts +2 -1
- package/scripts/build/build-hooks.ts +24 -4
- 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/doctor/doctor-consumer.ts +5 -12
- package/scripts/doctor/doctor-ports.ts +2 -2
- package/scripts/document/document-byte-budget.ts +8 -16
- package/scripts/document/document-section.ts +2 -6
- package/scripts/document/entry-read-budget.ts +1 -1
- 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-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/{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/{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 +1 -1
- package/scripts/git/git-error.ts +2 -5
- 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-pr-followup-wrapup.ts +2 -6
- package/scripts/git/git-preflight.ts +3 -6
- 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/issue-labels.ts +1 -1
- 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-rewrite-notice.ts +2 -8
- package/scripts/init/init-ai-copy.ts +23 -14
- package/scripts/init/init.ts +8 -4
- package/scripts/init/start-steps.ts +2 -1
- package/scripts/init/start.ts +2 -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-commands-ai.ts +2 -2
- package/scripts/josh/josh-commands-backlog.ts +1 -1
- package/scripts/josh/josh-commands-development.ts +1 -1
- package/scripts/josh/josh-commands-workflow.ts +4 -4
- package/scripts/lane/lane-cli.ts +2 -1
- package/scripts/lane/lane-open.ts +3 -2
- package/scripts/lane/lane-registry.ts +3 -6
- 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-publish.ts +6 -7
- package/scripts/propagate/propagate-steps.ts +2 -1
- package/scripts/propagate/propagate.ts +3 -2
- package/scripts/refactor/import-graph.ts +2 -6
- package/scripts/registry-migration/migrate.ts +3 -2
- package/scripts/registry-migration/user-npmrc.ts +2 -2
- package/scripts/release/release-cli.ts +2 -1
- package/scripts/repo/repo-setting.ts +10 -8
- 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/rules/decision-oracle.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-liveness-cli.ts +4 -7
- package/scripts/run/run-progress-args.ts +2 -6
- package/scripts/run/run-progress-cli.ts +2 -1
- 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 +2 -6
- package/scripts/run/run-wake-driver.ts +15 -2
- package/scripts/run/run-wake-session.ts +14 -4
- 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/ui/ui-routes-cli.ts +3 -6
- package/scripts/version/bump-version.ts +2 -1
- package/scripts/version/version-remote.ts +18 -27
- 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
package/scripts/lib/read-file.ts
CHANGED
|
@@ -1,16 +1,40 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs'
|
|
2
2
|
|
|
3
|
+
const ABSENT_CODE = 'ENOENT'
|
|
4
|
+
|
|
5
|
+
function is_absent(error: unknown): boolean {
|
|
6
|
+
return error instanceof Error && 'code' in error && error.code === ABSENT_CODE
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// An absent file is an answer ("nothing declared here"); any other failure — a permission error, a
|
|
10
|
+
// directory where a file was expected — is not, and is thrown so it cannot pass for an empty file.
|
|
11
|
+
function read_optional(path: string): string | undefined {
|
|
12
|
+
try {
|
|
13
|
+
return readFileSync(path, 'utf8')
|
|
14
|
+
} catch (error) {
|
|
15
|
+
if (is_absent(error)) return undefined
|
|
16
|
+
|
|
17
|
+
throw error
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
3
21
|
// Several readers treat an absent file as "nothing declared" rather than an error: a project may
|
|
4
22
|
// carry no lockfile yet, and either overrides location may be missing entirely. Empty content is
|
|
5
23
|
// the shared stand-in, so the callers do not each re-implement the try/catch.
|
|
6
24
|
function read_file_or_empty(path: string): string {
|
|
25
|
+
return read_optional(path) ?? ''
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// For a reader whose contract is to never fail — a hook running after a write already succeeded, a
|
|
29
|
+
// report that states "unreadable" itself — every failure to read, not only absence, is `undefined`.
|
|
30
|
+
function read_if_readable(path: string): string | undefined {
|
|
7
31
|
try {
|
|
8
32
|
return readFileSync(path, 'utf8')
|
|
9
33
|
} catch {
|
|
10
|
-
return
|
|
34
|
+
return undefined
|
|
11
35
|
}
|
|
12
36
|
}
|
|
13
37
|
|
|
14
|
-
const file_reader = { read_file_or_empty }
|
|
38
|
+
const file_reader = { read_optional, read_file_or_empty, read_if_readable }
|
|
15
39
|
|
|
16
40
|
export { file_reader }
|
|
@@ -1,40 +1,105 @@
|
|
|
1
|
-
import { appendFile, mkdir } from 'node:fs/promises'
|
|
1
|
+
import { appendFile, mkdir, readdir, readFile } from 'node:fs/promises'
|
|
2
2
|
import path from 'node:path'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { git_branch } from '#scripts/git/git-branch'
|
|
4
|
+
import { git_spawn } from '#scripts/git/git-spawn'
|
|
5
|
+
import { observation_ledger, OBSERVATION_LEDGER_DIRECTORY } from './observation-ledger'
|
|
5
6
|
import { observation_ledger_migrate } from './observation-ledger-migrate'
|
|
6
7
|
|
|
7
|
-
// Where the observation ledger is read and written, whichever work tree a command runs in
|
|
8
|
-
// (joshuafolkken/kit#2419).
|
|
8
|
+
// Where the observation ledger is read and written, whichever work tree a command runs in.
|
|
9
9
|
//
|
|
10
|
-
// **The ledger lives in the
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// The primary checkout is `repo_discovery.main_worktree`'s answer, reused rather than spelled again;
|
|
18
|
-
// in an ordinary checkout it is the given directory itself, so nothing outside a lane changes.
|
|
10
|
+
// **The ledger lives in the work tree the command runs in — a lane's in a lane** (joshuafolkken/kit#2919).
|
|
11
|
+
// It used to resolve to the primary checkout from every lane (joshuafolkken/kit#2419), so a lane's
|
|
12
|
+
// lines waited there for a batch-end flush; another run's stash of that checkout took them, the flush
|
|
13
|
+
// read `git status` and found nothing, and the lines were lost. Each run now appends to its own
|
|
14
|
+
// issue's file in its own tree, `git-staging.ts` commits it with the run, and `pnpm josh followup`
|
|
15
|
+
// commits whatever was appended after that commit before it merges — so a lane's lines reach the
|
|
16
|
+
// default branch with the lane's own pull request, and the primary checkout holds none of them.
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
return repo_discovery.main_worktree(cwd)
|
|
22
|
-
}
|
|
18
|
+
const DATE_END = 10
|
|
23
19
|
|
|
24
|
-
function
|
|
25
|
-
return
|
|
20
|
+
function ledger_root(cwd: string = process.cwd()): string {
|
|
21
|
+
return cwd
|
|
26
22
|
}
|
|
27
23
|
|
|
28
|
-
// **Resolving the
|
|
29
|
-
// asks here — `review:record --check`,
|
|
30
|
-
// duplicate check — so none of them can read
|
|
31
|
-
// path, which for the review-record check would
|
|
32
|
-
|
|
24
|
+
// **Resolving the directory migrates the single-file ledgers first** (joshuafolkken/kit#2724,
|
|
25
|
+
// joshuafolkken/kit#2919). Every reader and writer asks here — `review:record --check`,
|
|
26
|
+
// `review:findings`, the retrospective, the stash carry's duplicate check — so none of them can read a
|
|
27
|
+
// directory without the lines that still sit at an old path, which for the review-record check would
|
|
28
|
+
// answer `missing` for a round recorded before the move.
|
|
29
|
+
function ledger_directory(cwd: string = process.cwd()): string {
|
|
33
30
|
const root = ledger_root(cwd)
|
|
34
31
|
|
|
35
32
|
observation_ledger_migrate.migrate(root)
|
|
36
33
|
|
|
37
|
-
return path.join(root,
|
|
34
|
+
return path.join(root, OBSERVATION_LEDGER_DIRECTORY)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function file_path(name: number | string, cwd: string): string {
|
|
38
|
+
ledger_directory(cwd)
|
|
39
|
+
|
|
40
|
+
return path.join(ledger_root(cwd), observation_ledger.ledger_file(name))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function issue_path(issue: number, cwd: string = process.cwd()): string {
|
|
44
|
+
return file_path(issue, cwd)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function current_branch(cwd: string): Promise<string> {
|
|
48
|
+
try {
|
|
49
|
+
return await git_spawn.read(['-C', cwd, 'rev-parse', '--abbrev-ref', 'HEAD'])
|
|
50
|
+
} catch {
|
|
51
|
+
return ''
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// **The file a writer that names no issue appends to** — the issue the checked-out branch leads with
|
|
56
|
+
// (`2919-lane`, `2919-store-the-ledger`), so a lane's lines land in its own issue's file; or the date,
|
|
57
|
+
// for a line written on the default branch outside any issue's run, which `pnpm josh
|
|
58
|
+
// observations:flush` commits.
|
|
59
|
+
async function writer_path(now: Date, cwd: string = process.cwd()): Promise<string> {
|
|
60
|
+
const issue = git_branch.issue_from_branch(await current_branch(cwd))
|
|
61
|
+
|
|
62
|
+
return file_path(issue ?? now.toISOString().slice(0, DATE_END), cwd)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// A file whose last line has no newline would otherwise run into the next file's first line.
|
|
66
|
+
async function read_file(target: string): Promise<string> {
|
|
67
|
+
try {
|
|
68
|
+
const content = await readFile(target, 'utf8')
|
|
69
|
+
|
|
70
|
+
return content.length === 0 || content.endsWith('\n') ? content : `${content}\n`
|
|
71
|
+
} catch {
|
|
72
|
+
return ''
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function ledger_files(directory: string): Promise<ReadonlyArray<string> | undefined> {
|
|
77
|
+
try {
|
|
78
|
+
const names = await readdir(directory)
|
|
79
|
+
|
|
80
|
+
return names
|
|
81
|
+
.filter((name) => observation_ledger.is_ledger_file_name(name))
|
|
82
|
+
.toSorted((left, right) => left.localeCompare(right))
|
|
83
|
+
} catch {
|
|
84
|
+
return undefined
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// **Every file in the directory, read as one ledger**, in name order so the answer is stable. Readers
|
|
89
|
+
// count across all of them — a recurrence is a recurrence whichever issue's file each line sits in.
|
|
90
|
+
// `undefined` when there is no directory: a checkout that keeps no ledger, which `review:record
|
|
91
|
+
// --check` turns into `not-required`.
|
|
92
|
+
async function read(cwd: string = process.cwd()): Promise<string | undefined> {
|
|
93
|
+
const directory = ledger_directory(cwd)
|
|
94
|
+
const files = await ledger_files(directory)
|
|
95
|
+
|
|
96
|
+
if (files === undefined) return undefined
|
|
97
|
+
|
|
98
|
+
const contents = await Promise.all(
|
|
99
|
+
files.map(async (name) => await read_file(path.join(directory, name))),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return contents.join('')
|
|
38
103
|
}
|
|
39
104
|
|
|
40
105
|
// The one append every ledger writer goes through. A consumer that does not keep the ledger has no
|
|
@@ -47,6 +112,13 @@ async function append(target: string, lines: ReadonlyArray<string>): Promise<voi
|
|
|
47
112
|
await appendFile(target, `${lines.join('\n')}\n`, 'utf8')
|
|
48
113
|
}
|
|
49
114
|
|
|
50
|
-
const observation_ledger_home = {
|
|
115
|
+
const observation_ledger_home = {
|
|
116
|
+
append,
|
|
117
|
+
issue_path,
|
|
118
|
+
ledger_directory,
|
|
119
|
+
ledger_root,
|
|
120
|
+
read,
|
|
121
|
+
writer_path,
|
|
122
|
+
}
|
|
51
123
|
|
|
52
124
|
export { observation_ledger_home }
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { appendFileSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync } from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
|
+
import { file_reader } from '#scripts/lib/read-file'
|
|
3
4
|
import {
|
|
4
|
-
|
|
5
|
+
LEGACY_LEDGER_FILE,
|
|
6
|
+
LEGACY_OBSERVATION_LEDGER_PATHS,
|
|
5
7
|
MIGRATION_CLAIM_SUFFIX,
|
|
6
|
-
|
|
8
|
+
OBSERVATION_LEDGER_DIRECTORY,
|
|
7
9
|
} from './observation-ledger'
|
|
8
10
|
import { observation_ledger_line } from './observation-ledger-line'
|
|
9
11
|
|
|
10
|
-
// Moves the ledger's lines from the
|
|
11
|
-
// (`observation-ledger.ts` carries why
|
|
12
|
-
//
|
|
13
|
-
// consumer repository's existing ledger
|
|
14
|
-
// migrates first (`observation-ledger-home.ts`), and whatever reached
|
|
15
|
-
//
|
|
12
|
+
// Moves the ledger's lines from the single files it was before joshuafolkken/kit#2919 into the
|
|
13
|
+
// directory it is now, as `legacy.md` (`observation-ledger.ts` carries why the old paths are still
|
|
14
|
+
// recognized). Changing a constant alone splits the appends: a run still on the old code recreates the
|
|
15
|
+
// old file, and a consumer repository's existing ledger stays where nothing reads it. So resolving the
|
|
16
|
+
// ledger migrates first (`observation-ledger-home.ts`), and whatever reached an old path lands in the
|
|
17
|
+
// directory before anything reads or appends.
|
|
16
18
|
//
|
|
17
19
|
// **The old file is claimed with a rename before it is read.** Two sessions resolving the ledger at
|
|
18
20
|
// once would otherwise both read it and both copy its lines — and a repeated line under one key is
|
|
@@ -83,14 +85,6 @@ function terminated(content: string): string {
|
|
|
83
85
|
return content.length === 0 || content.endsWith(NEWLINE) ? content : `${content}${NEWLINE}`
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
function read_or_undefined(file_path: string): string | undefined {
|
|
87
|
-
try {
|
|
88
|
-
return readFileSync(file_path, 'utf8')
|
|
89
|
-
} catch {
|
|
90
|
-
return undefined
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
88
|
// What of the claimed file goes to the ledger's tail: all of it, verbatim, when there is no ledger yet,
|
|
95
89
|
// and otherwise only the lines the ledger does not already hold. **The old file can come back after
|
|
96
90
|
// its lines were moved** — `git restore`, `git reset --hard` or a stash without `-u` restores a
|
|
@@ -108,7 +102,11 @@ function carried(content: string, ledger: string | undefined): string {
|
|
|
108
102
|
// Moves one claimed file's lines to the tail of the ledger, then removes the claim.
|
|
109
103
|
function absorb(claimed: string, target: string): void {
|
|
110
104
|
mkdirSync(path.dirname(target), { recursive: true })
|
|
111
|
-
appendFileSync(
|
|
105
|
+
appendFileSync(
|
|
106
|
+
target,
|
|
107
|
+
carried(readFileSync(claimed, 'utf8'), file_reader.read_if_readable(target)),
|
|
108
|
+
'utf8',
|
|
109
|
+
)
|
|
112
110
|
rmSync(claimed)
|
|
113
111
|
}
|
|
114
112
|
|
|
@@ -127,10 +125,7 @@ function absorb_stale(legacy: string, target: string): number {
|
|
|
127
125
|
return count
|
|
128
126
|
}
|
|
129
127
|
|
|
130
|
-
|
|
131
|
-
function migrate(root: string): boolean {
|
|
132
|
-
const legacy = path.join(root, LEGACY_OBSERVATION_LEDGER_PATH)
|
|
133
|
-
const target = path.join(root, OBSERVATION_LEDGER_PATH)
|
|
128
|
+
function migrate_one(legacy: string, target: string): boolean {
|
|
134
129
|
const recovered = absorb_stale(legacy, target)
|
|
135
130
|
const claimed = claim_path(legacy)
|
|
136
131
|
|
|
@@ -141,6 +136,16 @@ function migrate(root: string): boolean {
|
|
|
141
136
|
return true
|
|
142
137
|
}
|
|
143
138
|
|
|
139
|
+
// Returns whether anything was moved. `root` is the checkout the ledger lives in. Every old path is
|
|
140
|
+
// tried — `map` before `some`, so one that moved does not leave the next one where it was.
|
|
141
|
+
function migrate(root: string): boolean {
|
|
142
|
+
const target = path.join(root, OBSERVATION_LEDGER_DIRECTORY, LEGACY_LEDGER_FILE)
|
|
143
|
+
|
|
144
|
+
return LEGACY_OBSERVATION_LEDGER_PATHS.map((legacy) =>
|
|
145
|
+
migrate_one(path.join(root, legacy), target),
|
|
146
|
+
).some(Boolean)
|
|
147
|
+
}
|
|
148
|
+
|
|
144
149
|
const observation_ledger_migrate = { claim_path, migrate }
|
|
145
150
|
|
|
146
151
|
export { observation_ledger_migrate }
|
|
@@ -1,29 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
import { OBSERVATION_LEDGER_PATH } from './observation-ledger'
|
|
1
|
+
import { observation_ledger_home } from './observation-ledger-home'
|
|
4
2
|
import { observation_ledger_line, type BrokenLine } from './observation-ledger-line'
|
|
5
|
-
import { observation_ledger_migrate } from './observation-ledger-migrate'
|
|
6
3
|
|
|
7
|
-
// **What has to hold before a ledger line is committed, asked by
|
|
8
|
-
// (joshuafolkken/kit#2763): `pnpm josh observations:flush
|
|
9
|
-
// `scripts/git/git-staging.ts
|
|
10
|
-
//
|
|
11
|
-
// the
|
|
4
|
+
// **What has to hold before a ledger line is committed, asked by every path that commits one**
|
|
5
|
+
// (joshuafolkken/kit#2763): `pnpm josh observations:flush`, the run's own commit in
|
|
6
|
+
// `scripts/git/git-staging.ts` and `pnpm josh followup`'s pre-merge ledger commit. Lines still on an
|
|
7
|
+
// old path are moved first (joshuafolkken/kit#2724, joshuafolkken/kit#2919), so the commit carries the
|
|
8
|
+
// move and the lines together, and every entry line is then parsed against the grammar
|
|
9
|
+
// (joshuafolkken/kit#2123) — each caller decides what a broken line costs it.
|
|
12
10
|
//
|
|
13
11
|
// **A ledger that is not there has no line to break** — the change a status saw is then its deletion,
|
|
14
12
|
// and a commit carries that like any other change to it.
|
|
15
|
-
async function read_ledger(root: string): Promise<string> {
|
|
16
|
-
try {
|
|
17
|
-
return await readFile(path.join(root, OBSERVATION_LEDGER_PATH), 'utf8')
|
|
18
|
-
} catch {
|
|
19
|
-
return ''
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
13
|
async function prepare(root: string): Promise<ReadonlyArray<BrokenLine>> {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
14
|
+
return observation_ledger_line.broken_ledger_lines(
|
|
15
|
+
(await observation_ledger_home.read(root)) ?? '',
|
|
16
|
+
)
|
|
27
17
|
}
|
|
28
18
|
|
|
29
19
|
const observation_ledger_prepare = { prepare }
|
|
@@ -1,22 +1,33 @@
|
|
|
1
|
-
// The observation ledger's one
|
|
2
|
-
// (joshuafolkken/kit#1756). `git-staging.ts` stages it with the run's own commit when its grammar
|
|
1
|
+
// The observation ledger's one location, named here rather than inside any of the places that act on
|
|
2
|
+
// it (joshuafolkken/kit#1756). `git-staging.ts` stages it with the run's own commit when its grammar
|
|
3
3
|
// holds (joshuafolkken/kit#2763), `hook-gate-reuse.ts` excludes it from what a hook calls a carried
|
|
4
4
|
// tree, and `observations-flush.ts` commits what no run carried — so a literal in each would be three
|
|
5
5
|
// answers to one question, and a rename reaching only some of them would silently split them.
|
|
6
6
|
//
|
|
7
7
|
// **It sits under `docs/maintainers/`, apart from the reader-facing documents** (joshuafolkken/kit#2724).
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
8
|
+
//
|
|
9
|
+
// **It is a directory of one file per issue, not one file** (joshuafolkken/kit#2919). One file every
|
|
10
|
+
// run appended to made two lanes' pull requests conflict at its tail, so lanes parked their lines in
|
|
11
|
+
// the primary checkout for a batch-end flush — and another run's stash took them from there, and the
|
|
12
|
+
// flush never saw them. A run now appends to `<issue>.md` in its own work tree and merges it with its
|
|
13
|
+
// own pull request: parallel lanes write different files, so nothing conflicts and nothing waits.
|
|
14
|
+
const OBSERVATION_LEDGER_DIRECTORY = 'docs/maintainers/observations'
|
|
15
|
+
|
|
16
|
+
const LEDGER_FILE_EXTENSION = '.md'
|
|
17
|
+
|
|
18
|
+
// What the single-file ledgers held is moved here (`observation-ledger-migrate.ts`), so no line is
|
|
19
|
+
// lost to the split.
|
|
20
|
+
const LEGACY_LEDGER_FILE = `legacy${LEDGER_FILE_EXTENSION}`
|
|
21
|
+
|
|
22
|
+
// Where the ledger lived as one file — `docs/observations.md` before joshuafolkken/kit#2724 and
|
|
23
|
+
// `docs/maintainers/observations.md` until joshuafolkken/kit#2919 — kept as migration sources rather
|
|
24
|
+
// than forgotten: a run still on the old code, a stash cut before the move and a consumer repository's
|
|
25
|
+
// existing ledger all write there, and `observation-ledger-migrate.ts` moves what it finds into the
|
|
26
|
+
// directory. Every place that recognizes the ledger recognizes them, so a line on either path is never
|
|
27
|
+
// staged into an ordinary commit. Remove them once every repository holding a ledger has migrated.
|
|
28
|
+
const LEGACY_OBSERVATION_LEDGER_PATHS: ReadonlyArray<string> = [
|
|
29
|
+
'docs/maintainers/observations.md',
|
|
30
|
+
'docs/observations.md',
|
|
20
31
|
]
|
|
21
32
|
|
|
22
33
|
// `git status --porcelain` prints `XY PATH`, so the path starts at the fourth character.
|
|
@@ -26,29 +37,48 @@ function status_path(line: string): string {
|
|
|
26
37
|
return line.slice(STATUS_PATH_INDEX).trim()
|
|
27
38
|
}
|
|
28
39
|
|
|
29
|
-
// The
|
|
40
|
+
// The ledger file one writer appends to — an issue number, or a date for a line written outside any
|
|
41
|
+
// issue's run (`observation-ledger-home.ts` decides which).
|
|
42
|
+
function ledger_file(name: number | string): string {
|
|
43
|
+
return `${OBSERVATION_LEDGER_DIRECTORY}/${String(name)}${LEDGER_FILE_EXTENSION}`
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function is_ledger_file_name(name: string): boolean {
|
|
47
|
+
return name.endsWith(LEDGER_FILE_EXTENSION)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// The suffix of the file a migration renames an old ledger to while it copies it
|
|
30
51
|
// (`observation-ledger-migrate.ts`): `docs/observations.md.<pid>.migrating`.
|
|
31
52
|
const MIGRATION_CLAIM_SUFFIX = '.migrating'
|
|
32
53
|
|
|
33
54
|
// A claim left by a process killed mid-migration holds ledger lines, so it is the ledger too: never
|
|
34
55
|
// staged by an ordinary commit, and absorbed by the next migration.
|
|
35
56
|
function is_migration_claim(file_path: string): boolean {
|
|
36
|
-
return (
|
|
37
|
-
file_path.startsWith(`${
|
|
38
|
-
file_path.endsWith(MIGRATION_CLAIM_SUFFIX)
|
|
57
|
+
return LEGACY_OBSERVATION_LEDGER_PATHS.some(
|
|
58
|
+
(legacy) => file_path.startsWith(`${legacy}.`) && file_path.endsWith(MIGRATION_CLAIM_SUFFIX),
|
|
39
59
|
)
|
|
40
60
|
}
|
|
41
61
|
|
|
62
|
+
// Porcelain status collapses an untracked directory to `docs/maintainers/observations/`, so the
|
|
63
|
+
// directory itself, trailing slash and all, is the ledger as much as any file inside it.
|
|
64
|
+
function is_in_directory(file_path: string): boolean {
|
|
65
|
+
return file_path.startsWith(`${OBSERVATION_LEDGER_DIRECTORY}/`)
|
|
66
|
+
}
|
|
67
|
+
|
|
42
68
|
function is_ledger_path(file_path: string): boolean {
|
|
43
|
-
return
|
|
69
|
+
return (
|
|
70
|
+
is_in_directory(file_path) ||
|
|
71
|
+
LEGACY_OBSERVATION_LEDGER_PATHS.includes(file_path) ||
|
|
72
|
+
is_migration_claim(file_path)
|
|
73
|
+
)
|
|
44
74
|
}
|
|
45
75
|
|
|
46
76
|
function is_ledger_line(line: string): boolean {
|
|
47
77
|
return is_ledger_path(status_path(line))
|
|
48
78
|
}
|
|
49
79
|
|
|
50
|
-
// The ledger paths a porcelain status names — what a
|
|
51
|
-
//
|
|
80
|
+
// The ledger paths a porcelain status names — what a commit of the ledger stages, which may be a new
|
|
81
|
+
// file together with an old one's deletion.
|
|
52
82
|
function ledger_paths(status_output: string): ReadonlyArray<string> {
|
|
53
83
|
return status_output
|
|
54
84
|
.split('\n')
|
|
@@ -57,7 +87,7 @@ function ledger_paths(status_output: string): ReadonlyArray<string> {
|
|
|
57
87
|
}
|
|
58
88
|
|
|
59
89
|
// **The one place that answers "does this working tree hold a pending observation append?"**
|
|
60
|
-
// `observations-flush.ts`'s `has_ledger_change` and `pnpm josh followup`'s pre-
|
|
90
|
+
// `observations-flush.ts`'s `has_ledger_change` and `pnpm josh followup`'s pre-merge ledger commit
|
|
61
91
|
// both ask it (joshuafolkken/kit#1810), so a literal in each would be the clone the header above
|
|
62
92
|
// warns against. A blank porcelain line carries no ledger path, so `is_ledger_line` answers false for
|
|
63
93
|
// it and no filtering is needed.
|
|
@@ -67,17 +97,20 @@ function has_pending_append(status_output: string): boolean {
|
|
|
67
97
|
|
|
68
98
|
const observation_ledger = {
|
|
69
99
|
has_pending_append,
|
|
100
|
+
is_ledger_file_name,
|
|
70
101
|
is_ledger_line,
|
|
71
102
|
is_ledger_path,
|
|
72
103
|
is_migration_claim,
|
|
104
|
+
ledger_file,
|
|
73
105
|
ledger_paths,
|
|
74
106
|
status_path,
|
|
75
107
|
}
|
|
76
108
|
|
|
77
109
|
export {
|
|
78
|
-
|
|
110
|
+
LEDGER_FILE_EXTENSION,
|
|
111
|
+
LEGACY_LEDGER_FILE,
|
|
112
|
+
LEGACY_OBSERVATION_LEDGER_PATHS,
|
|
79
113
|
MIGRATION_CLAIM_SUFFIX,
|
|
80
|
-
|
|
81
|
-
OBSERVATION_LEDGER_PATHS,
|
|
114
|
+
OBSERVATION_LEDGER_DIRECTORY,
|
|
82
115
|
observation_ledger,
|
|
83
116
|
}
|
|
@@ -1,38 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
|
-
import {
|
|
3
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
4
4
|
import { observations_flush } from './observations-flush'
|
|
5
5
|
|
|
6
6
|
const FAILURE_EXIT_CODE = 1
|
|
7
7
|
|
|
8
|
-
function message_of(error: unknown): string {
|
|
9
|
-
return error instanceof Error ? error.message : String(error)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// **From a lane, the flush acts on the primary checkout** (joshuafolkken/kit#2419). That is where the
|
|
13
|
-
// ledger lives (`observation-ledger-home.ts`), and a lane's own checkout is never the one a flush may
|
|
14
|
-
// switch — so the process moves there first, and every refusal after it is about that checkout, which
|
|
15
|
-
// the prefix names so a "run `pnpm josh ms` first" is read as advice for the primary checkout.
|
|
16
|
-
function enter_ledger_home(): string {
|
|
17
|
-
if (!observation_ledger_home.is_lane()) return ''
|
|
18
|
-
|
|
19
|
-
const root = observation_ledger_home.ledger_root()
|
|
20
|
-
|
|
21
|
-
process.chdir(root)
|
|
22
|
-
|
|
23
|
-
return `The observation ledger lives in the primary checkout, so this flush acts on ${root}: `
|
|
24
|
-
}
|
|
25
|
-
|
|
26
8
|
// The refusals are the command's whole safety story, so they are printed as the message rather than
|
|
27
9
|
// as a stack: "this checkout is on a feature branch" and "the tree holds somebody else's work" are
|
|
28
10
|
// both things the person fixes in one step, and a trace hides the sentence that says which.
|
|
11
|
+
//
|
|
12
|
+
// **It acts on the checkout it runs in** (joshuafolkken/kit#2919). A lane's ledger lines merge with
|
|
13
|
+
// the lane's own pull request, so a lane has nothing to flush and is refused like any feature branch.
|
|
29
14
|
async function main(): Promise<void> {
|
|
30
|
-
const prefix = enter_ledger_home()
|
|
31
|
-
|
|
32
15
|
try {
|
|
33
|
-
console.info(
|
|
16
|
+
console.info(await observations_flush.flush(new Date()))
|
|
34
17
|
} catch (error) {
|
|
35
|
-
console.error(
|
|
18
|
+
console.error(error_text.message_of(error))
|
|
36
19
|
process.exit(FAILURE_EXIT_CODE)
|
|
37
20
|
}
|
|
38
21
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { git_gh_command } from '#scripts/git/git-gh-command'
|
|
2
2
|
import type { OpenPull } from '#scripts/git/git-gh-pr-auto-merge'
|
|
3
3
|
import { git_pr_checks } from '#scripts/git/git-pr-checks'
|
|
4
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
4
5
|
|
|
5
6
|
// **A flush pull request lands whether or not the flush that opened it is still alive**
|
|
6
7
|
// (joshuafolkken/kit#2497). The merge used to happen only inside the flush's own wait, so a session
|
|
@@ -25,7 +26,7 @@ async function request_auto_merge(branch_name: string): Promise<boolean> {
|
|
|
25
26
|
|
|
26
27
|
return true
|
|
27
28
|
} catch (error) {
|
|
28
|
-
console.warn(AUTO_MERGE_WARNING,
|
|
29
|
+
console.warn(AUTO_MERGE_WARNING, error_text.message_of(error))
|
|
29
30
|
|
|
30
31
|
return false
|
|
31
32
|
}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { git_command } from '#scripts/git/git-command'
|
|
2
2
|
import { git_gh_command } from '#scripts/git/git-gh-command'
|
|
3
3
|
import { main_sync } from '#scripts/git/main-sync'
|
|
4
|
-
import {
|
|
4
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
5
|
+
import { observation_ledger, OBSERVATION_LEDGER_DIRECTORY } from './observation-ledger'
|
|
5
6
|
import type { BrokenLine } from './observation-ledger-line'
|
|
6
7
|
import { observation_ledger_prepare } from './observation-ledger-prepare'
|
|
7
8
|
import { observations_flush_landing } from './observations-flush-landing'
|
|
8
9
|
|
|
9
|
-
// The commit path for the ledger lines no run's own commit carried (joshuafolkken/kit#1756).
|
|
10
|
-
//
|
|
11
|
-
// git-staging.ts`, joshuafolkken/kit#2763),
|
|
12
|
-
//
|
|
10
|
+
// The commit path for the ledger lines no run's own commit carried (joshuafolkken/kit#1756). Every run —
|
|
11
|
+
// a lane's included — commits its appended lines with its own pull request (`scripts/git/
|
|
12
|
+
// git-staging.ts`, joshuafolkken/kit#2763, joshuafolkken/kit#2919), and `pnpm josh followup` commits a
|
|
13
|
+
// line appended after that commit before it merges, so what is left here is a line written on the
|
|
14
|
+
// default branch outside any issue's run — the date-named file `observation-ledger-home.ts` resolves.
|
|
13
15
|
//
|
|
14
16
|
// **The shape is `scripts/release/release-publish.ts`'s, deliberately.** Both open a pull request of
|
|
15
17
|
// their own over one path, wait on the same required checks every other pull request waits on, and
|
|
@@ -19,7 +21,7 @@ import { observations_flush_landing } from './observations-flush-landing'
|
|
|
19
21
|
// below does not survive — `observations-flush-landing.ts` carries why.
|
|
20
22
|
|
|
21
23
|
const COMMIT_MESSAGE = 'Record observation ledger entries'
|
|
22
|
-
const CLEAN_MESSAGE = `clean — ${
|
|
24
|
+
const CLEAN_MESSAGE = `clean — ${OBSERVATION_LEDGER_DIRECTORY} matches the commit it sits on, so there is nothing to flush`
|
|
23
25
|
const BRANCH_PREFIX = 'observations/'
|
|
24
26
|
const SUCCESS_EXIT_CODE = 0
|
|
25
27
|
const RETURN_FAILURE_MESSAGE =
|
|
@@ -55,7 +57,7 @@ function branch_name_for(stamp: string): string {
|
|
|
55
57
|
// single issue, and the ledger's own lines say where each one was seen.
|
|
56
58
|
function pull_request_body(): string {
|
|
57
59
|
return [
|
|
58
|
-
`Appended observations from \`${
|
|
60
|
+
`Appended observations from \`${OBSERVATION_LEDGER_DIRECTORY}\` that no run's own commit carried.`,
|
|
59
61
|
'',
|
|
60
62
|
'The ledger is append-only, and a second line under one key is what promotes an observation to an issue — so this pull request adds lines and changes none.',
|
|
61
63
|
'',
|
|
@@ -82,10 +84,6 @@ function other_changed_paths(status_output: string): ReadonlyArray<string> {
|
|
|
82
84
|
)
|
|
83
85
|
}
|
|
84
86
|
|
|
85
|
-
function message_of(error: unknown): string {
|
|
86
|
-
return error instanceof Error ? error.message : String(error)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
87
|
// **The branch a failed flush leaves behind holds the only copy of those lines**, because the working
|
|
90
88
|
// tree no longer shows them once they are committed. Sending a person to `pnpm josh ms` here — which
|
|
91
89
|
// is what the ordinary off-default refusal says — would abandon them without a word, so this branch
|
|
@@ -205,7 +203,9 @@ async function catch_up_default(default_branch: string): Promise<void> {
|
|
|
205
203
|
try {
|
|
206
204
|
await git_command.merge_fast_forward(default_branch)
|
|
207
205
|
} catch (error) {
|
|
208
|
-
throw new Error(behind_default_message(default_branch, message_of(error)), {
|
|
206
|
+
throw new Error(behind_default_message(default_branch, error_text.message_of(error)), {
|
|
207
|
+
cause: error,
|
|
208
|
+
})
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
211
|
|
|
@@ -242,7 +242,9 @@ async function return_to_default_branch(default_branch: string): Promise<void> {
|
|
|
242
242
|
try {
|
|
243
243
|
await git_command.fast_forward_local(default_branch)
|
|
244
244
|
} catch (error) {
|
|
245
|
-
throw new Error(`${FAST_FORWARD_FAILURE_MESSAGE} ${message_of(error)}`, {
|
|
245
|
+
throw new Error(`${FAST_FORWARD_FAILURE_MESSAGE} ${error_text.message_of(error)}`, {
|
|
246
|
+
cause: error,
|
|
247
|
+
})
|
|
246
248
|
}
|
|
247
249
|
|
|
248
250
|
const exit_code = await main_sync.run([])
|
|
@@ -258,7 +260,9 @@ async function push_and_open(branch_name: string): Promise<string> {
|
|
|
258
260
|
|
|
259
261
|
return await git_gh_command.pr_create(COMMIT_MESSAGE, pull_request_body(), 'ignore-for-release')
|
|
260
262
|
} catch (error) {
|
|
261
|
-
throw new Error(stranded_branch_message(branch_name, message_of(error)), {
|
|
263
|
+
throw new Error(stranded_branch_message(branch_name, error_text.message_of(error)), {
|
|
264
|
+
cause: error,
|
|
265
|
+
})
|
|
262
266
|
}
|
|
263
267
|
}
|
|
264
268
|
|
|
@@ -279,7 +283,7 @@ async function roll_back(branch_name: string, default_branch: string): Promise<s
|
|
|
279
283
|
|
|
280
284
|
return undefined
|
|
281
285
|
} catch (error) {
|
|
282
|
-
return message_of(error)
|
|
286
|
+
return error_text.message_of(error)
|
|
283
287
|
}
|
|
284
288
|
}
|
|
285
289
|
|
|
@@ -315,9 +319,12 @@ async function commit_ledger(branch_name: string, default_branch: string): Promi
|
|
|
315
319
|
} catch (error) {
|
|
316
320
|
const roll_back_reason = await roll_back(branch_name, default_branch)
|
|
317
321
|
|
|
318
|
-
throw new Error(
|
|
319
|
-
|
|
320
|
-
|
|
322
|
+
throw new Error(
|
|
323
|
+
rejection_message(branch_name, error_text.message_of(error), roll_back_reason),
|
|
324
|
+
{
|
|
325
|
+
cause: error,
|
|
326
|
+
},
|
|
327
|
+
)
|
|
321
328
|
}
|
|
322
329
|
}
|
|
323
330
|
|
|
@@ -345,7 +352,9 @@ async function land(
|
|
|
345
352
|
try {
|
|
346
353
|
await observations_flush_landing.wait_for_landing(branch_name, is_auto_merge)
|
|
347
354
|
} catch (error) {
|
|
348
|
-
throw new Error(stranded_branch_message(branch_name, message_of(error)), {
|
|
355
|
+
throw new Error(stranded_branch_message(branch_name, error_text.message_of(error)), {
|
|
356
|
+
cause: error,
|
|
357
|
+
})
|
|
349
358
|
}
|
|
350
359
|
|
|
351
360
|
await return_to_default_branch(default_branch)
|
|
@@ -366,7 +375,7 @@ function merged_message(branch_name: string): string {
|
|
|
366
375
|
function broken_lines_message(broken: ReadonlyArray<BrokenLine>): string {
|
|
367
376
|
const rows = broken.map((entry) => ` ${entry.line}\n ↳ ${entry.reason}`).join('\n')
|
|
368
377
|
|
|
369
|
-
return `\`${
|
|
378
|
+
return `\`${OBSERVATION_LEDGER_DIRECTORY}\` has ${String(broken.length)} line(s) that break the ledger grammar, so the ledger commit stops rather than carrying them:\n${rows}`
|
|
370
379
|
}
|
|
371
380
|
|
|
372
381
|
// **A malformed line is refused before a branch is cut, not after.** The ledger path was collected
|