@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
|
@@ -27,7 +27,7 @@ const AI_COMMANDS: Record<string, CommandEntry> = {
|
|
|
27
27
|
reference: ['<message>', 'automation', ['git']],
|
|
28
28
|
},
|
|
29
29
|
epic: {
|
|
30
|
-
script: 'scripts
|
|
30
|
+
script: 'scripts/epic/epic.ts',
|
|
31
31
|
description: 'Create an epic issue from its child issue numbers',
|
|
32
32
|
category: 'AI tools',
|
|
33
33
|
reference: ['<title> <issue...> [--ordered]', 'automation', ['network']],
|
|
@@ -51,7 +51,7 @@ const AI_COMMANDS: Record<string, CommandEntry> = {
|
|
|
51
51
|
reference: ['<epic>', 'automation', ['network']],
|
|
52
52
|
},
|
|
53
53
|
'epic:check': {
|
|
54
|
-
script: 'scripts
|
|
54
|
+
script: 'scripts/epic/epic-check.ts',
|
|
55
55
|
description: 'Check an epic issue against the tracking requirements',
|
|
56
56
|
category: 'AI tools',
|
|
57
57
|
reference: ['<epic>', 'automation', ['network']],
|
|
@@ -19,7 +19,7 @@ const BACKLOG_COMMANDS: Record<string, CommandEntry> = {
|
|
|
19
19
|
description:
|
|
20
20
|
'Collapse a backlogrun loop-head event into one call: read backlog:next, ask backlog:budget, return the verdict and any issues to start',
|
|
21
21
|
category: 'AI tools',
|
|
22
|
-
reference: ['[options]', 'automation', ['network']],
|
|
22
|
+
reference: ['[options]', 'automation', ['files', 'network', 'processes']],
|
|
23
23
|
},
|
|
24
24
|
'backlog:drive': {
|
|
25
25
|
script: 'scripts/backlog/backlog-drive-cli.ts',
|
|
@@ -57,7 +57,7 @@ const DEV_COMMANDS: Record<string, CommandEntry> = {
|
|
|
57
57
|
description:
|
|
58
58
|
"Print each delivered rule's unaided compliance — runs reached, kept rate, refusals",
|
|
59
59
|
category: 'AI tools',
|
|
60
|
-
reference: ['', 'developer', ['
|
|
60
|
+
reference: ['[--refresh]', 'developer', ['files']],
|
|
61
61
|
},
|
|
62
62
|
format: {
|
|
63
63
|
script: 'scripts/lint/format.ts',
|
|
@@ -66,7 +66,7 @@ const DEV_COMMANDS: Record<string, CommandEntry> = {
|
|
|
66
66
|
reference: ['', 'developer', ['files', 'processes']],
|
|
67
67
|
},
|
|
68
68
|
'format:edited': {
|
|
69
|
-
script: 'scripts/hooks/format-edited-
|
|
69
|
+
script: 'scripts/hooks/format-edited-cli.ts',
|
|
70
70
|
description: 'Claude Code hook: format the file just edited (reads the tool call on stdin)',
|
|
71
71
|
category: 'Development',
|
|
72
72
|
reference: ['', 'automation', ['files', 'processes']],
|
|
@@ -85,7 +85,7 @@ const DEV_COMMANDS: Record<string, CommandEntry> = {
|
|
|
85
85
|
// node's own `--env-file` parser with node's own precedence.
|
|
86
86
|
},
|
|
87
87
|
'pretool:guard': {
|
|
88
|
-
script: 'scripts/hooks/pretool-guard.ts',
|
|
88
|
+
script: 'scripts/hooks/pretool-guard-cli.ts',
|
|
89
89
|
description:
|
|
90
90
|
'Claude Code hook: the batch, investigation and rule guards in one process (reads the tool call on stdin)',
|
|
91
91
|
category: 'Development',
|
|
@@ -17,7 +17,7 @@ const PROJECT_COMMANDS: Record<string, CommandEntry> = {
|
|
|
17
17
|
description: 'Set a project up for the GitHub Issue workflow, from git init to the setup PR',
|
|
18
18
|
category: 'Project',
|
|
19
19
|
reference: [
|
|
20
|
-
`${PROFILE_ARGUMENTS} [--yes] [--github] [--public]`,
|
|
20
|
+
`${PROFILE_ARGUMENTS} [--yes] [--github] [--public] [--init-command <command>]`,
|
|
21
21
|
'developer',
|
|
22
22
|
['files', 'git', 'network', 'processes'],
|
|
23
23
|
],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { OPTIONAL_ENV_FILE_FLAGS, type CommandEntry } from './josh-command-types'
|
|
2
2
|
|
|
3
|
-
const GIT_WORKFLOW_SCRIPT = 'scripts
|
|
3
|
+
const GIT_WORKFLOW_SCRIPT = 'scripts/git/git-workflow.ts'
|
|
4
4
|
const GIT_MESSAGE_ARGUMENTS = '[-y] <message>'
|
|
5
5
|
|
|
6
6
|
/* eslint-disable @typescript-eslint/naming-convention */
|
|
@@ -27,7 +27,7 @@ const WORKFLOW_COMMANDS: Record<string, CommandEntry> = {
|
|
|
27
27
|
// session, and `followup` with them the merge step of every `fullrun` there. `doctor` was moved to
|
|
28
28
|
// this form for the same reason in joshuafolkken/kit#869.
|
|
29
29
|
//
|
|
30
|
-
// Nothing is lost by relaxing it: both scripts already
|
|
30
|
+
// Nothing is lost by relaxing it: both scripts already load `.env` through `josh_environment_file`
|
|
31
31
|
// themselves, so the file is read either way and node's precedence — an existing environment
|
|
32
32
|
// variable wins over the file — is node's own in both spellings.
|
|
33
33
|
//
|
|
@@ -35,7 +35,7 @@ const WORKFLOW_COMMANDS: Record<string, CommandEntry> = {
|
|
|
35
35
|
// than dropped: `telegram_notify.send` now throws on missing credentials and on a failed send, so
|
|
36
36
|
// `josh notify` exits non-zero instead of warning and answering 0.
|
|
37
37
|
followup: {
|
|
38
|
-
script: 'scripts
|
|
38
|
+
script: 'scripts/git/git-followup-workflow.ts',
|
|
39
39
|
description: 'Follow-up git workflow',
|
|
40
40
|
category: 'Workflow',
|
|
41
41
|
reference: [
|
|
@@ -46,7 +46,7 @@ const WORKFLOW_COMMANDS: Record<string, CommandEntry> = {
|
|
|
46
46
|
tsx_arguments: OPTIONAL_ENV_FILE_FLAGS,
|
|
47
47
|
},
|
|
48
48
|
notify: {
|
|
49
|
-
script: 'scripts
|
|
49
|
+
script: 'scripts/git/telegram-test.ts',
|
|
50
50
|
description: 'Send Telegram notification',
|
|
51
51
|
category: 'Workflow',
|
|
52
52
|
reference: ['--task-type <type> --body <text>', 'automation', ['notifications']],
|
|
@@ -19,6 +19,15 @@ import { lane_paths } from './lane-paths'
|
|
|
19
19
|
// `.claude/skills/workflow-commands/pre-gate-cut.md`.
|
|
20
20
|
const KEY = 'JOSH_LANE_CHILD'
|
|
21
21
|
|
|
22
|
+
// **A dispatched child also asks for the five-minute prompt-cache TTL** (joshuafolkken/kit#2944). A
|
|
23
|
+
// headless `claude -p` session on a subscription defaults to the one-hour TTL, whose writes bill at 2x
|
|
24
|
+
// the input rate against 1.25x for five minutes. A replay of 1,953 lane transcripts (about 57,000
|
|
25
|
+
// requests) found 0.3% of request gaps over five minutes, so the shorter TTL — re-writing the prefix on
|
|
26
|
+
// those few gaps — still costs 12-13% less. Every launcher of a child composes its environment here,
|
|
27
|
+
// so this is the one place the choice is made.
|
|
28
|
+
const CACHE_TTL_KEY = 'CLAUDE_CODE_PROMPT_CACHE_TTL'
|
|
29
|
+
const CACHE_TTL = '5m'
|
|
30
|
+
|
|
22
31
|
type MarkerSource = Readonly<Record<string, string | undefined>>
|
|
23
32
|
|
|
24
33
|
// The environment fragment that marks a child dispatched for `#<issue>`. Composed from a digits-only
|
|
@@ -27,7 +36,7 @@ type MarkerSource = Readonly<Record<string, string | undefined>>
|
|
|
27
36
|
function environment_for(issue: string): Record<string, string> {
|
|
28
37
|
run_issue_number.require_issue_number(issue)
|
|
29
38
|
|
|
30
|
-
return { [KEY]: issue }
|
|
39
|
+
return { [KEY]: issue, [CACHE_TTL_KEY]: CACHE_TTL }
|
|
31
40
|
}
|
|
32
41
|
|
|
33
42
|
// The issue a child was dispatched for, or `undefined` when the mark is absent or malformed. A blank
|
|
@@ -52,7 +61,12 @@ function is_child_of(directory: string, source: MarkerSource = process.env): boo
|
|
|
52
61
|
return marked !== undefined && marked === lane_paths.lane_issue_of(directory, { ...source })
|
|
53
62
|
}
|
|
54
63
|
|
|
55
|
-
const lane_child_marker = {
|
|
64
|
+
const lane_child_marker = {
|
|
65
|
+
KEY,
|
|
66
|
+
env_for: environment_for,
|
|
67
|
+
is_child_of,
|
|
68
|
+
marked_issue,
|
|
69
|
+
}
|
|
56
70
|
|
|
57
71
|
export type { MarkerSource }
|
|
58
72
|
export { lane_child_marker }
|
package/scripts/lane/lane-cli.ts
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
3
|
import { epic_busy } from '#scripts/epic/epic-busy'
|
|
4
4
|
import { git_gh_command } from '#scripts/git/git-gh-command'
|
|
5
|
+
import type { OpenIssueData } from '#scripts/git/schemas'
|
|
6
|
+
import { issue_cite, type IssueCiter } from '#scripts/issue/issue-cite'
|
|
7
|
+
import { error_text } from '#scripts/lib/error-message'
|
|
5
8
|
import { run_carry } from '#scripts/run/run-carry'
|
|
6
9
|
import { lane_await } from './lane-await'
|
|
7
10
|
import { lane_close, type CloseOutcome, type SweepOutcome } from './lane-close'
|
|
@@ -149,10 +152,10 @@ function report_sweep(outcome: SweepOutcome): number {
|
|
|
149
152
|
|
|
150
153
|
// `none` on standard output for the empty case as well, so `lane:list` keeps the contract the rest
|
|
151
154
|
// of the commands follow — the prose belongs on standard error with every other explanation.
|
|
152
|
-
function report_lanes(lanes: ReadonlyArray<LaneInfo
|
|
155
|
+
function report_lanes(lanes: ReadonlyArray<LaneInfo>, cite: IssueCiter): number {
|
|
153
156
|
if (lanes.length === 0) console.error(lane_report.NO_LANES)
|
|
154
157
|
|
|
155
|
-
console.info(lanes.length === 0 ? NONE_TOKEN : lane_report.describe_lanes(lanes))
|
|
158
|
+
console.info(lanes.length === 0 ? NONE_TOKEN : lane_report.describe_lanes(lanes, cite))
|
|
156
159
|
|
|
157
160
|
return SUCCESS_EXIT_CODE
|
|
158
161
|
}
|
|
@@ -191,23 +194,39 @@ const OCCUPANCY_UNREADABLE =
|
|
|
191
194
|
// The `in-progress` issues this repository shows running, read the way `epic:next` reads lane
|
|
192
195
|
// occupancy — never rebuilt (joshuafolkken/kit#2235). A read that could not see the whole listing is
|
|
193
196
|
// `undefined`, so the difference is skipped rather than computed against a set known to be partial.
|
|
194
|
-
async function
|
|
197
|
+
async function to_holders(repo: string): Promise<ReadonlyArray<OpenIssueData> | undefined> {
|
|
195
198
|
const read = await epic_busy.read_repository(repo)
|
|
196
199
|
if (read.kind === 'idle') return []
|
|
197
200
|
|
|
198
|
-
return read.kind === 'busy' ? read.issues
|
|
201
|
+
return read.kind === 'busy' ? read.issues : undefined
|
|
199
202
|
}
|
|
200
203
|
|
|
201
|
-
|
|
204
|
+
// The repository and its running issues, read once: the listing cites each lane from the titles here
|
|
205
|
+
// (joshuafolkken/kit#2943) and the occupancy check compares against the same set.
|
|
206
|
+
interface InProgress {
|
|
207
|
+
repo: string | undefined
|
|
208
|
+
holders: ReadonlyArray<OpenIssueData> | undefined
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function read_in_progress(): Promise<InProgress> {
|
|
202
212
|
try {
|
|
203
213
|
const repo = await git_gh_command.repo_get_name_with_owner()
|
|
204
214
|
|
|
205
|
-
return repo === undefined ? undefined : await
|
|
215
|
+
return { repo, holders: repo === undefined ? undefined : await to_holders(repo) }
|
|
206
216
|
} catch {
|
|
207
|
-
return undefined
|
|
217
|
+
return { repo: undefined, holders: undefined }
|
|
208
218
|
}
|
|
209
219
|
}
|
|
210
220
|
|
|
221
|
+
function citer_of(in_progress: InProgress): IssueCiter {
|
|
222
|
+
const holders = in_progress.holders ?? []
|
|
223
|
+
|
|
224
|
+
return issue_cite.citer(
|
|
225
|
+
in_progress.repo,
|
|
226
|
+
new Map(holders.map((issue) => [String(issue.number), issue.title])),
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
|
|
211
230
|
// A stranded lane's work tree is gone, so it holds no issue: it counts as no lane rather than a live
|
|
212
231
|
// one, which is what turns its surviving `in-progress` label into a `stopped` anomaly.
|
|
213
232
|
function live_lane_issues(lanes: ReadonlyArray<LaneInfo>): Array<number> {
|
|
@@ -216,16 +235,15 @@ function live_lane_issues(lanes: ReadonlyArray<LaneInfo>): Array<number> {
|
|
|
216
235
|
|
|
217
236
|
// The lane/label difference, printed to standard error beside the listing. Wrapped so a GitHub read
|
|
218
237
|
// that fails skips only this section — `lane:list`'s job is to list the lanes it already read.
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (in_progress === undefined) {
|
|
238
|
+
function report_occupancy(lanes: ReadonlyArray<LaneInfo>, in_progress: InProgress): void {
|
|
239
|
+
if (in_progress.holders === undefined) {
|
|
223
240
|
console.error(OCCUPANCY_UNREADABLE)
|
|
224
241
|
|
|
225
242
|
return
|
|
226
243
|
}
|
|
227
244
|
|
|
228
|
-
const
|
|
245
|
+
const holders = in_progress.holders.map((issue) => issue.number)
|
|
246
|
+
const report = lane_occupancy.classify(holders, {
|
|
229
247
|
kind: 'lanes',
|
|
230
248
|
issues: live_lane_issues(lanes),
|
|
231
249
|
})
|
|
@@ -238,9 +256,10 @@ async function list_command(rest: ReadonlyArray<string>): Promise<number> {
|
|
|
238
256
|
if (rest.length > 0) return report_usage()
|
|
239
257
|
|
|
240
258
|
const lanes = await lane_registry.list_lanes()
|
|
241
|
-
const
|
|
259
|
+
const in_progress = await read_in_progress()
|
|
260
|
+
const code = report_lanes(lanes, citer_of(in_progress))
|
|
242
261
|
|
|
243
|
-
|
|
262
|
+
report_occupancy(lanes, in_progress)
|
|
244
263
|
|
|
245
264
|
return code
|
|
246
265
|
}
|
|
@@ -384,7 +403,7 @@ async function run(argv: ReadonlyArray<string>): Promise<number> {
|
|
|
384
403
|
try {
|
|
385
404
|
return await dispatch(argv)
|
|
386
405
|
} catch (error) {
|
|
387
|
-
console.error(
|
|
406
|
+
console.error(error_text.message_of(error))
|
|
388
407
|
|
|
389
408
|
return FAILURE_EXIT_CODE
|
|
390
409
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdirSync, readdirSync, rmSync, writeFileSync } from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { ENV_FILE_NAME } from '#ports'
|
|
4
4
|
import { git_worktree } from '#scripts/git/git-worktree'
|
|
5
|
+
import { file_reader } from '#scripts/lib/read-file'
|
|
5
6
|
import { lane_cache } from './lane-cache'
|
|
6
7
|
import { lane_environment } from './lane-environment'
|
|
7
8
|
import { lane_install, type InstallResult } from './lane-install'
|
|
@@ -52,7 +53,7 @@ const SEAT_LOCK_PREFIX = 'seat-'
|
|
|
52
53
|
function read_root_environment(repository_root: string): string {
|
|
53
54
|
const file = path.join(repository_root, ENV_FILE_NAME)
|
|
54
55
|
|
|
55
|
-
return
|
|
56
|
+
return file_reader.read_file_or_empty(file)
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
function build_lane(
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { existsSync
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { ENV_FILE_NAME } from '#ports'
|
|
4
4
|
import type { AgentProfile } from '#scripts/agent/agent-role-profile'
|
|
5
5
|
import { repo_discovery } from '#scripts/discovery/repo-discovery'
|
|
6
6
|
import { git_command } from '#scripts/git/git-command'
|
|
7
7
|
import { git_worktree } from '#scripts/git/git-worktree'
|
|
8
|
+
import { file_reader } from '#scripts/lib/read-file'
|
|
8
9
|
import { lane_environment } from './lane-environment'
|
|
9
10
|
import { lane_paths } from './lane-paths'
|
|
10
11
|
|
|
@@ -105,11 +106,7 @@ function held_seat(content: string | undefined): SeatPorts {
|
|
|
105
106
|
* a state the report shows rather than a state anything falls back from.
|
|
106
107
|
*/
|
|
107
108
|
function read_environment(directory: string): string | undefined {
|
|
108
|
-
|
|
109
|
-
return readFileSync(path.join(directory, ENV_FILE_NAME), 'utf8')
|
|
110
|
-
} catch {
|
|
111
|
-
return undefined
|
|
112
|
-
}
|
|
109
|
+
return file_reader.read_if_readable(path.join(directory, ENV_FILE_NAME))
|
|
113
110
|
}
|
|
114
111
|
|
|
115
112
|
// A lane is identified by its branch rather than by where it sits, so nothing here compares two
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { agent_role_profile } from '#scripts/agent/agent-role-profile'
|
|
2
|
+
import { issue_cite, type IssueCiter } from '#scripts/issue/issue-cite'
|
|
2
3
|
import { run_liveness } from '#scripts/run/run-liveness'
|
|
3
4
|
import type { LaneInfo } from './lane-registry'
|
|
4
5
|
|
|
@@ -48,9 +49,14 @@ function event_column(lane: LaneInfo): string {
|
|
|
48
49
|
return state ?? 'agent -'
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
// The plain `#N`, for a caller that could not read the repository the issue would link into.
|
|
53
|
+
const PLAIN_CITE: IssueCiter = issue_cite.citer(undefined, new Map())
|
|
54
|
+
|
|
55
|
+
// The issue leads as a citation (joshuafolkken/kit#2943): a lane status report copies this row, and a
|
|
56
|
+
// bare `#N` copied into it is what the Stop guard sends back.
|
|
57
|
+
function describe_lane(lane: LaneInfo, cite: IssueCiter = PLAIN_CITE): string {
|
|
52
58
|
return [
|
|
53
|
-
|
|
59
|
+
cite(lane.issue),
|
|
54
60
|
`seat ${value_or_unknown(lane.seat)}`,
|
|
55
61
|
`dev ${value_or_unknown(lane.development_port)}`,
|
|
56
62
|
`preview ${value_or_unknown(lane.preview_port)}`,
|
|
@@ -63,10 +69,10 @@ function describe_lane(lane: LaneInfo): string {
|
|
|
63
69
|
].join(COLUMN_SEPARATOR)
|
|
64
70
|
}
|
|
65
71
|
|
|
66
|
-
function describe_lanes(lanes: ReadonlyArray<LaneInfo
|
|
72
|
+
function describe_lanes(lanes: ReadonlyArray<LaneInfo>, cite: IssueCiter = PLAIN_CITE): string {
|
|
67
73
|
if (lanes.length === 0) return NO_LANES
|
|
68
74
|
|
|
69
|
-
return lanes.map((lane) => describe_lane(lane)).join('\n')
|
|
75
|
+
return lanes.map((lane) => describe_lane(lane, cite)).join('\n')
|
|
70
76
|
}
|
|
71
77
|
|
|
72
78
|
function describe_ports(lane: LaneInfo): string {
|
package/scripts/lib/cli-flags.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import { parseArgs, type ParseArgsConfig } from 'node:util'
|
|
2
|
+
|
|
3
|
+
// Argument reading for the commands under `scripts/`.
|
|
2
4
|
//
|
|
3
5
|
// Reject anything not on the list rather than ignoring it: a misspelled `--dryrun` that fell through
|
|
4
6
|
// would run the real write path. `josh propagate` and `josh adopt` both write into working trees, so
|
|
@@ -16,4 +18,20 @@ function refuse_unknown_flags(
|
|
|
16
18
|
return `Unknown argument(s): ${unknown.join(' ')}\n${usage}`
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
// `parseArgs`, answering `undefined` where it would throw — an unknown flag, a missing option value,
|
|
22
|
+
// or a positional the config does not allow. Each caller turns that into its usage line and a failure
|
|
23
|
+
// exit rather than a stack trace, on commands whose output a workflow reads; eleven of them had begun
|
|
24
|
+
// to carry a copy of the same try/catch each (joshuafolkken/kit#2902), and this is the one copy.
|
|
25
|
+
function parse_or_undefined<T extends ParseArgsConfig>(
|
|
26
|
+
config: T,
|
|
27
|
+
): ReturnType<typeof parseArgs<T>> | undefined {
|
|
28
|
+
try {
|
|
29
|
+
return parseArgs(config)
|
|
30
|
+
} catch {
|
|
31
|
+
return undefined
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const cli_flags = { refuse_unknown_flags, parse_or_undefined }
|
|
36
|
+
|
|
37
|
+
export { cli_flags }
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// The one place a caught value becomes the text a report shows: an `Error` contributes its own message,
|
|
2
|
+
// anything else thrown is stringified as it stands.
|
|
3
|
+
function message_of(error: unknown): string {
|
|
4
|
+
return error instanceof Error ? error.message : String(error)
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const error_text = { message_of }
|
|
8
|
+
|
|
9
|
+
export { error_text }
|
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 }
|