@joshuafolkken/kit 1.1455.0 → 1.1514.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 +6 -6
- package/.claude/skills/dependency-update/SKILL.md +26 -55
- package/.claude/skills/epic-commands/SKILL.md +83 -156
- package/.claude/skills/epic-commands/epic-bundle.md +51 -79
- package/.claude/skills/epic-commands/execution-waves.md +43 -81
- package/.claude/skills/verify-ui/SKILL.md +24 -50
- package/.claude/skills/workflow-commands/SKILL.md +7 -9
- package/.claude/skills/workflow-commands/background-commands.md +31 -46
- package/.claude/skills/workflow-commands/backlogrun-child.md +65 -85
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +86 -125
- package/.claude/skills/workflow-commands/backlogrun-park.md +68 -118
- package/.claude/skills/workflow-commands/backlogrun-progress.md +17 -28
- package/.claude/skills/workflow-commands/backlogrun-recovery.md +17 -26
- package/.claude/skills/workflow-commands/backlogrun-steps.md +35 -52
- package/.claude/skills/workflow-commands/backlogrun.md +31 -50
- package/.claude/skills/workflow-commands/chain-rule.md +31 -40
- package/.claude/skills/workflow-commands/delegation.md +12 -8
- package/.claude/skills/workflow-commands/entry-sequence.md +1 -3
- package/.claude/skills/workflow-commands/followup.md +55 -77
- package/.claude/skills/workflow-commands/fullrun-steps.md +3 -5
- package/.claude/skills/workflow-commands/fullrun.md +4 -6
- package/.claude/skills/workflow-commands/halfrun.md +3 -7
- package/.claude/skills/workflow-commands/into-target.md +1 -2
- package/.claude/skills/workflow-commands/issue-comments.md +43 -60
- package/.claude/skills/workflow-commands/issue-scout.md +3 -2
- package/.claude/skills/workflow-commands/kickoff.md +65 -74
- package/.claude/skills/workflow-commands/latest-gate.md +2 -6
- package/.claude/skills/workflow-commands/needs-human-review.md +2 -5
- package/.claude/skills/workflow-commands/observation-filing.md +59 -118
- package/.claude/skills/workflow-commands/observation-ledger.md +52 -112
- package/.claude/skills/workflow-commands/pre-gate-cut.md +88 -142
- package/.claude/skills/workflow-commands/prerequisite.md +11 -17
- package/.claude/skills/workflow-commands/progress-watcher.md +23 -33
- package/.claude/skills/workflow-commands/prrun.md +3 -5
- package/.claude/skills/workflow-commands/retrospective.md +35 -54
- package/.claude/skills/workflow-commands/split-assessment.md +32 -58
- package/.claude/skills/workflow-commands/target-repository.md +1 -2
- package/.claude/skills/workflow-commands/working-tree-hold.md +54 -73
- package/.codex/hooks.json +6 -6
- package/.github/actions/setup-node/action.yml +1 -1
- package/.github/workflows/ci.yml +1 -1
- package/.github/workflows/publish.yml +2 -2
- package/CLAUDE.md +7 -6
- package/cspell/index.yaml +7 -0
- package/dist/CLAUDE.md +7 -6
- package/dist/commands/document-section-cli.js +67 -34
- package/dist/hooks/batch-guard.js +6 -6
- package/dist/hooks/chunk-4KKD2UTK.js +66 -0
- package/dist/hooks/{chunk-GPLGZ6IC.js → chunk-6TS2JD6D.js} +14 -3
- package/dist/hooks/chunk-7DSBJVFH.js +50 -0
- package/dist/hooks/{chunk-5H4SSDSD.js → chunk-A65Y4YOR.js} +3 -3
- package/dist/hooks/{chunk-3C4XRYG6.js → chunk-APUO6U4J.js} +102 -19
- package/dist/hooks/{chunk-IX6TTTI6.js → chunk-EFXE6C37.js} +8 -22
- package/dist/hooks/chunk-GP4IEC4O.js +20 -0
- package/dist/hooks/chunk-JP45SOCJ.js +56 -0
- package/dist/hooks/{chunk-OLMO72ON.js → chunk-LAOV7NLM.js} +1 -1
- package/dist/hooks/chunk-M4HGZV7U.js +83 -0
- package/dist/hooks/{chunk-TBJFTSUY.js → chunk-O6GDRNJB.js} +9 -7
- package/dist/hooks/{chunk-HFGWYGUV.js → chunk-PF2NZL2A.js} +5 -5
- package/dist/hooks/{chunk-WNLVGT7E.js → chunk-PTICX4OL.js} +4 -5
- package/dist/hooks/{chunk-HHI6FO3R.js → chunk-RT27QRJW.js} +763 -1515
- package/dist/hooks/{chunk-7WQBPLUN.js → chunk-SYOTDYCI.js} +1149 -1095
- package/dist/hooks/{chunk-LIAFCXL7.js → chunk-TNUMY6NL.js} +1519 -1332
- package/dist/hooks/chunk-UPUZF3AN.js +279 -0
- package/dist/hooks/{chunk-Z4XI7CZS.js → chunk-WK6SMBIC.js} +1932 -784
- package/dist/hooks/codex-hook-adapter.js +17 -14
- package/dist/hooks/duplicate-read-guard.js +7 -7
- package/dist/hooks/format-edited.js +5 -4
- package/dist/hooks/inputs.json +302 -247
- package/dist/hooks/investigation-guard.js +8 -11
- package/dist/hooks/pretool-guard.js +16 -13
- package/dist/hooks/session-lang.js +372 -26
- package/dist/hooks/stop-guard.js +2593 -189
- package/dist/josh.js +2128 -282
- package/dist/managed-marker/index.js +1 -2
- package/dist/version/index.js +15 -12
- package/eslint/base.js +2 -1
- package/eslint/rules/unicorn.js +16 -0
- package/lefthook/base.yml +5 -3
- package/package.json +14 -12
- package/prompts/coding-standards.md +12 -22
- package/prompts/collaboration-workflow/file-edits.md +25 -45
- package/prompts/collaboration-workflow/gh-rest.md +19 -36
- package/prompts/collaboration-workflow/glossary.md +5 -7
- package/prompts/collaboration-workflow/issue-citation.md +6 -14
- package/prompts/collaboration-workflow/issue-template.md +40 -32
- package/prompts/collaboration-workflow/lane-instructions.md +35 -0
- package/prompts/collaboration-workflow/operating-rules.md +33 -46
- package/prompts/collaboration-workflow/output-bounds.md +15 -15
- package/prompts/collaboration-workflow/overview.md +14 -19
- package/prompts/collaboration-workflow/plan-comment.md +13 -13
- package/prompts/collaboration-workflow/principles.md +57 -54
- package/prompts/collaboration-workflow/proposal-request.md +1 -9
- package/prompts/collaboration-workflow/report-format.md +41 -49
- package/prompts/collaboration-workflow/residency.md +15 -11
- package/prompts/collaboration-workflow/rule-delivery.md +7 -15
- package/prompts/collaboration-workflow/shell-body.md +10 -20
- package/prompts/collaboration-workflow/turn-batching.md +8 -14
- package/prompts/collaboration-workflow/upstream-interrupt.md +26 -34
- package/prompts/collaboration-workflow/wip-cap.md +26 -32
- package/prompts/collaboration-workflow.md +25 -25
- package/prompts/refactoring.md +33 -43
- package/prompts/review-rubric.md +84 -129
- package/prompts/review.md +110 -141
- package/prompts/sonar-hotspot-handling.md +22 -50
- package/prompts/testing-guide.md +48 -99
- package/scripts/adopt/adopt-toolkits.ts +2 -4
- package/scripts/agent/agent-argv.ts +20 -4
- package/scripts/agent/agent-exit-record.ts +23 -1
- package/scripts/agent/agent-role-profile.ts +101 -45
- package/scripts/agent/agent-safe-text.ts +60 -0
- package/scripts/agent/claude-agent-argv.ts +14 -0
- package/scripts/agent/codex-agent-argv.ts +9 -2
- package/scripts/agent/lane-instructions.ts +144 -0
- package/scripts/backlog/backlog-budget-cli.ts +1 -2
- package/scripts/backlog/backlog-budget.ts +4 -4
- package/scripts/backlog/backlog-drive-carried.ts +26 -0
- package/scripts/backlog/backlog-drive-cli.ts +10 -5
- package/scripts/backlog/backlog-drive-named.ts +3 -5
- package/scripts/backlog/backlog-drive-restore.ts +28 -2
- package/scripts/backlog/backlog-drive-seats.ts +27 -0
- package/scripts/backlog/backlog-drive.ts +11 -6
- package/scripts/backlog/backlog-occupied.ts +56 -0
- package/scripts/backlog/backlog-ready.ts +59 -15
- package/scripts/behavior/behavior-rules.ts +3 -2
- package/scripts/cost-runtime/codex-usage.ts +4 -2
- package/scripts/cost-runtime/cost-cli.ts +1 -2
- package/scripts/delegation/duplicate-read-guard.ts +2 -2
- package/scripts/delegation/investigation-guard.ts +15 -65
- package/scripts/delegation/investigation-reads.ts +1 -2
- package/scripts/discovery/repo-discovery.ts +4 -2
- package/scripts/discovery/repo-origin.ts +2 -4
- package/scripts/doctor/doctor-logic.ts +6 -3
- package/scripts/document/document-byte-budget.ts +28 -27
- package/scripts/document/document-byte-check.ts +1 -2
- package/scripts/document/document-section-cli.ts +17 -5
- package/scripts/document/entry-read-budget.ts +3 -3
- package/scripts/document/read-set-cli.ts +4 -1
- package/scripts/epic/epic-audit-orphans.ts +5 -8
- package/scripts/epic/epic-audit-rationale.ts +8 -2
- package/scripts/epic/epic-audit.ts +52 -1
- package/scripts/epic/epic-bundle-cli.ts +1 -2
- package/scripts/epic/epic-bundle-evidence.ts +49 -26
- package/scripts/epic/epic-bundle-referenced.ts +14 -3
- package/scripts/epic/epic-bundle.ts +17 -9
- package/scripts/epic/epic-chains.ts +6 -2
- package/scripts/epic/epic-close-comment.ts +1 -2
- package/scripts/followup/git-followup-workflow.ts +19 -1
- package/scripts/gate/check-inputs.ts +129 -0
- package/scripts/gate/check-reuse.ts +162 -0
- package/scripts/gate/core-budget.ts +93 -44
- package/scripts/gate/core-wait.ts +130 -0
- package/scripts/gate/free-cores.ts +49 -0
- package/scripts/gate/gate-ledger.ts +17 -3
- package/scripts/gate/gate-plan.ts +23 -1
- package/scripts/gate/gate-report.ts +15 -2
- package/scripts/gate/gate-run-steps.ts +150 -0
- package/scripts/gate/held-mark.ts +55 -0
- package/scripts/gate/machine-capacity.ts +70 -11
- package/scripts/gate/pre-commit-type-check.ts +14 -1
- package/scripts/gate/verification-gate.ts +65 -127
- package/scripts/gh/git-gh-issue-rest.ts +1 -0
- package/scripts/gh/git-gh-issue-write.ts +24 -2
- package/scripts/gh/git-pr.ts +19 -3
- package/scripts/git/git-common-directory.ts +3 -3
- package/scripts/git/git-diff-reads.ts +16 -9
- package/scripts/git/git-execa-error.ts +1 -2
- package/scripts/git/main-merge-regenerate.ts +78 -0
- package/scripts/git/main-merge.ts +14 -5
- package/scripts/hooks/batch-guard.ts +5 -8
- package/scripts/hooks/format-edited-file.ts +10 -4
- package/scripts/hooks/pre-commit-lint.ts +91 -0
- package/scripts/hooks/pre-push-unit.ts +15 -4
- package/scripts/hooks/pretool-guard.ts +48 -79
- package/scripts/hooks/reserved-run.ts +1 -0
- package/scripts/hooks/run-hook.sh +10 -12
- package/scripts/hooks/step-zero-notice.ts +3 -2
- package/scripts/hooks/stop-actions.ts +204 -0
- package/scripts/hooks/stop-guard.ts +62 -21
- package/scripts/init/hook-command-rewrite.ts +6 -2
- package/scripts/init/hook-launch.ts +12 -7
- package/scripts/init/init-logic-deploy-vps.ts +4 -2
- package/scripts/issue/behavior-change-lint.ts +12 -6
- package/scripts/issue/expected-effect-lint.ts +59 -0
- package/scripts/issue/issue-cite-cli.ts +35 -15
- package/scripts/issue/issue-classification.ts +1 -1
- package/scripts/issue/issue-lint-cli.ts +5 -3
- package/scripts/issue/issue-scout-cli.ts +22 -16
- package/scripts/issue/markdown-section.ts +19 -5
- package/scripts/josh/command-usage.ts +110 -0
- package/scripts/josh/file-map-stamp.ts +1 -3
- package/scripts/josh/hook-decision.ts +17 -2
- package/scripts/josh/josh-command-map.ts +2 -0
- package/scripts/josh/josh-command-types.ts +55 -9
- package/scripts/josh/josh-commands-ai.ts +13 -0
- package/scripts/josh/josh-commands-development.ts +1 -1
- package/scripts/josh/josh-commands-hooks.ts +6 -0
- package/scripts/josh/josh-commands-lane.ts +10 -1
- package/scripts/josh/josh-commands-workflow.ts +5 -0
- package/scripts/josh/josh-logic.ts +6 -15
- package/scripts/josh/josh-usage-automation.ts +121 -0
- package/scripts/josh/josh-usage-backlog.ts +180 -0
- package/scripts/josh/josh-usage-developer.ts +230 -0
- package/scripts/josh/josh-usage-hooks.ts +38 -0
- package/scripts/josh/josh-usage-issue.ts +205 -0
- package/scripts/josh/josh-usage-review.ts +101 -0
- package/scripts/josh/josh-usage-run.ts +252 -0
- package/scripts/josh/josh-usage-session.ts +238 -0
- package/scripts/josh/josh-usage-workflow.ts +212 -0
- package/scripts/josh/josh.ts +14 -5
- package/scripts/josh/package-directory.ts +20 -0
- package/scripts/lane/lane-await.ts +3 -2
- package/scripts/lane/lane-capacity.ts +8 -2
- package/scripts/lane/lane-check-stats.ts +56 -0
- package/scripts/lane/lane-child-invocation.ts +49 -5
- package/scripts/lane/lane-dispatch.ts +3 -3
- package/scripts/lane/lane-guard-policy.ts +5 -5
- package/scripts/lane/lane-handoff.ts +8 -1
- package/scripts/lane/lane-install.ts +12 -4
- package/scripts/lane/lane-ledger.ts +52 -1
- package/scripts/lane/lane-limit-cli.ts +8 -9
- package/scripts/lane/lane-load.ts +9 -2
- package/scripts/lane/lane-measure-cli.ts +4 -0
- package/scripts/lane/lane-parked.ts +18 -0
- package/scripts/lane/lane-preserve.ts +29 -1
- package/scripts/lane/lane-process-load.ts +125 -0
- package/scripts/lane/lane-process-stats.ts +93 -0
- package/scripts/lane/lane-registry.ts +17 -10
- package/scripts/lane/lane-restart.ts +5 -26
- package/scripts/lane/lane-session-action.ts +146 -0
- package/scripts/lane/lane-session-broker.ts +68 -125
- package/scripts/lane/lane-spent.ts +14 -3
- package/scripts/lane/lane-stats.ts +1 -0
- package/scripts/lane/lane-withdraw-cli.ts +71 -0
- package/scripts/lane/lane-withdraw.ts +182 -0
- package/scripts/lint/format.ts +4 -4
- package/scripts/lint/lint-parallel.ts +10 -3
- package/scripts/lint/lint-related-scope.ts +4 -1
- package/scripts/managed-marker/managed-marker-logic.ts +1 -2
- package/scripts/notify/telegram-test.ts +10 -5
- package/scripts/overrides/lockfile-overrides.ts +1 -3
- package/scripts/propagate/propagate.ts +1 -2
- package/scripts/release/release-progress.ts +3 -2
- package/scripts/report/report-format-reference.ts +2 -0
- package/scripts/report/report-lint.ts +7 -4
- package/scripts/review/acceptance-map-check.ts +61 -0
- package/scripts/review/acceptance-map.ts +205 -0
- package/scripts/review/review-brief-cli.ts +1 -1
- package/scripts/review/review-record-cli.ts +6 -2
- package/scripts/rules/agent-model.ts +59 -0
- package/scripts/rules/delivered-rules.ts +11 -2
- package/scripts/rules/destructive-command.ts +6 -9
- package/scripts/rules/direct-filing.ts +3 -7
- package/scripts/rules/early-heartbeat.ts +7 -18
- package/scripts/rules/file-body.ts +6 -13
- package/scripts/rules/filing-cap.ts +4 -6
- package/scripts/rules/firing-point.ts +6 -5
- package/scripts/rules/git-force.ts +5 -9
- package/scripts/rules/guard-remedy.ts +117 -0
- package/scripts/rules/implementation-cut.ts +37 -35
- package/scripts/rules/index-guard.ts +4 -7
- package/scripts/rules/issue-comments.ts +67 -16
- package/scripts/rules/josh-git-bare.ts +3 -6
- package/scripts/rules/lane-background.ts +10 -21
- package/scripts/rules/lane-carry-conflict.ts +7 -17
- package/scripts/rules/lane-interactive-ask.ts +6 -11
- package/scripts/rules/lane-park.ts +5 -13
- package/scripts/rules/lane-split-park.ts +2 -7
- package/scripts/rules/lane-switch-main.ts +9 -12
- package/scripts/rules/last-prompt.ts +50 -5
- package/scripts/rules/oracle-consulted.ts +4 -5
- package/scripts/rules/oracle-list-cli.ts +7 -0
- package/scripts/rules/piped-verification.ts +84 -32
- package/scripts/rules/poll-loop.ts +7 -17
- package/scripts/rules/pre-gate-cut.ts +7 -21
- package/scripts/rules/prior-comment-read.ts +7 -5
- package/scripts/rules/protected-files.ts +5 -9
- package/scripts/rules/raw-field-body.ts +3 -7
- package/scripts/rules/rule-body-guard.ts +4 -11
- package/scripts/rules/rule-list.ts +4 -1
- package/scripts/rules/rule-registry-rows.ts +10 -0
- package/scripts/rules/rule-registry.ts +7 -4
- package/scripts/rules/rule-rewrite.ts +114 -0
- package/scripts/rules/rule-value.ts +7 -1
- package/scripts/rules/run-tail-rule.ts +6 -22
- package/scripts/rules/shell-body-trigger.ts +4 -6
- package/scripts/rules/shell-segments.ts +22 -0
- package/scripts/rules/stop-decision.ts +77 -0
- package/scripts/rules/stop-rules.ts +164 -77
- package/scripts/rules/tail-commands.ts +15 -1
- package/scripts/rules/test-declared-commit.ts +4 -10
- package/scripts/rules/third-party-write.ts +5 -10
- package/scripts/rules/worktree-guard.ts +5 -12
- package/scripts/run/board/run-board-cli.ts +2 -0
- package/scripts/run/board/run-board-closed.ts +8 -2
- package/scripts/run/board/run-board-github.ts +19 -3
- package/scripts/run/board/run-board-header.ts +4 -1
- package/scripts/run/board/run-board-idle.ts +31 -0
- package/scripts/run/board/run-board-labels.ts +2 -0
- package/scripts/run/board/run-board-layout.ts +1 -1
- package/scripts/run/board/run-board-read.ts +25 -12
- package/scripts/run/board/run-board-reason.ts +17 -4
- package/scripts/run/board/run-board-render-legend.ts +1 -0
- package/scripts/run/board/run-board-render-slot.ts +1 -1
- package/scripts/run/board/run-board-render-waits.ts +41 -0
- package/scripts/run/board/run-board-render.ts +6 -1
- package/scripts/run/board/run-board-row-yield.ts +1 -0
- package/scripts/run/board/run-board-state.ts +9 -0
- package/scripts/run/board/run-board-status.ts +89 -23
- package/scripts/run/board/run-board-thanks.ts +7 -2
- package/scripts/run/board/run-board-tick.ts +37 -8
- package/scripts/run/carry/run-carry-args.ts +1 -3
- package/scripts/run/carry/run-carry-conversation.ts +7 -2
- package/scripts/run/cut/run-cut-cli.ts +18 -22
- package/scripts/run/cut/run-cut-decline.ts +72 -0
- package/scripts/run/cut/run-cut-ready.ts +74 -0
- package/scripts/run/cut/run-cut-report.ts +29 -0
- package/scripts/run/cut/run-cut.ts +10 -1
- package/scripts/run/detached-launch.ts +23 -4
- package/scripts/run/entry/run-entry-cli.ts +8 -1
- package/scripts/run/event/run-event-render.ts +1 -0
- package/scripts/run/event/run-event-scope.ts +19 -1
- package/scripts/run/event/run-event-stream-emit.ts +13 -6
- package/scripts/run/event/run-event-stream.ts +4 -0
- package/scripts/run/hold/run-hold-cli.ts +14 -3
- package/scripts/run/hold/run-hold-session.ts +46 -0
- package/scripts/run/hold/run-hold.ts +13 -4
- package/scripts/run/launch-priority.ts +32 -0
- package/scripts/run/merge/run-merge-cli.ts +16 -42
- package/scripts/run/merge/run-merge-ship.ts +59 -1
- package/scripts/run/merge/run-merge-signals.ts +99 -0
- package/scripts/run/merge/run-merge-steps.ts +11 -1
- package/scripts/run/merge/run-merge.ts +22 -9
- package/scripts/run/run-ending.ts +31 -3
- package/scripts/run/run-liveness.ts +1 -1
- package/scripts/run/run-parent-cut-hook.ts +5 -10
- package/scripts/run/run-poll-hook.ts +157 -0
- package/scripts/run/run-preflight.ts +1 -2
- package/scripts/run/run-step-cli.ts +22 -15
- package/scripts/run/run-step-section.ts +85 -0
- package/scripts/run/run-stop-notify.ts +1 -2
- package/scripts/run/run-watcher-guard.ts +6 -3
- package/scripts/run/ship/run-ship-cli.ts +5 -12
- package/scripts/run/ship/run-ship-detach.ts +9 -0
- package/scripts/run/ship/run-ship-next.ts +10 -2
- package/scripts/run/ship/run-ship-overlap.ts +195 -0
- package/scripts/run/ship/run-ship-pre-detach.ts +33 -15
- package/scripts/run/ship/run-ship-preflight.ts +28 -2
- package/scripts/run/ship/run-ship-return.ts +31 -7
- package/scripts/run/ship/run-ship-review-steps.ts +25 -14
- package/scripts/run/ship/run-ship-review.ts +15 -2
- package/scripts/run/ship/run-ship-steps.ts +1 -1
- package/scripts/run/ship/run-ship-stop-context.ts +206 -0
- package/scripts/run/ship/run-ship-sync.ts +14 -6
- package/scripts/run/tidy/run-tidy-cli.ts +3 -3
- package/scripts/run/tidy/run-tidy-lanes.ts +3 -2
- package/scripts/safe-chain/project-config.ts +7 -2
- package/scripts/sync/sync-hook-safety.ts +8 -2
- package/scripts/test/test-e2e-guard.ts +30 -5
- package/scripts/test/test-unit-guard.ts +16 -1
- package/scripts/test/unit-worker-share.ts +26 -3
- package/scripts/time-runtime/time-batch-guard.ts +3 -3
- package/scripts/time-runtime/time-hook-transcript.ts +3 -2
- package/scripts/time-runtime/time-shell.ts +39 -4
- package/scripts/time-runtime/time-spans.ts +1 -0
- package/scripts/time-runtime/time-tool-call.ts +7 -0
- package/scripts/version/effective-upstream.ts +3 -4
- package/scripts/version/kit-version-config.ts +8 -2
- package/scripts/version/latest-corepack.ts +1 -2
- package/scripts/version/release-hold.ts +11 -4
- package/scripts/version/version-commands.ts +12 -4
- package/templates/gitignore +6 -2
- package/dist/hooks/chunk-D5QBTOCZ.js +0 -68
- package/dist/hooks/chunk-UT7YC3N2.js +0 -56
|
@@ -5,6 +5,7 @@ import { agent_headless } from '#scripts/agent/agent-headless'
|
|
|
5
5
|
import { backlog_ready } from '#scripts/backlog/backlog-ready'
|
|
6
6
|
import { backlog_stalled_detect } from '#scripts/backlog/backlog-stalled-detect'
|
|
7
7
|
import { repo_party } from '#scripts/discovery/repo-party'
|
|
8
|
+
import { stop_actions } from '#scripts/hooks/stop-actions'
|
|
8
9
|
import { hook_decision } from '#scripts/josh/hook-decision'
|
|
9
10
|
import { session_language } from '#scripts/josh/session-language'
|
|
10
11
|
import { lane_child_marker } from '#scripts/lane/lane-child-marker'
|
|
@@ -13,37 +14,64 @@ import { filing_cap } from '#scripts/rules/filing-cap'
|
|
|
13
14
|
import { lane_background } from '#scripts/rules/lane-background'
|
|
14
15
|
import { lane_park } from '#scripts/rules/lane-park'
|
|
15
16
|
import { last_prompt } from '#scripts/rules/last-prompt'
|
|
17
|
+
import { pre_gate_cut } from '#scripts/rules/pre-gate-cut'
|
|
18
|
+
import { stop_decision } from '#scripts/rules/stop-decision'
|
|
16
19
|
import { stop_rules, type StopContext, type StopOutcome } from '#scripts/rules/stop-rules'
|
|
20
|
+
import { tail_commands } from '#scripts/rules/tail-commands'
|
|
17
21
|
import { run_carry } from '#scripts/run/carry/run-carry'
|
|
18
22
|
import { run_cut } from '#scripts/run/cut/run-cut'
|
|
19
|
-
import {
|
|
23
|
+
import { run_cut_report } from '#scripts/run/cut/run-cut-report'
|
|
24
|
+
import { run_hold, type RunHold } from '#scripts/run/hold/run-hold'
|
|
25
|
+
import { run_hold_session } from '#scripts/run/hold/run-hold-session'
|
|
20
26
|
import { run_halfrun_resume } from '#scripts/run/run-halfrun-resume'
|
|
21
27
|
import { run_headless } from '#scripts/run/run-headless'
|
|
22
28
|
import { run_stranded_detect } from '#scripts/run/run-stranded-detect'
|
|
23
29
|
import { time_density_hook } from '#scripts/time-runtime/time-density-hook'
|
|
24
30
|
import { time_hook_transcript } from '#scripts/time-runtime/time-hook-transcript'
|
|
25
31
|
|
|
26
|
-
// The `Stop` hook entry: one process that delivers the
|
|
32
|
+
// The `Stop` hook entry: one process that delivers the stop-time rules, running the fixed ones itself
|
|
33
|
+
// (`stop-actions.ts`).
|
|
27
34
|
// It is the stop-time counterpart of `pretool-guard.ts` — a second entry on the same `hook_decision`
|
|
28
35
|
// foundation, not a second copy of it.
|
|
29
36
|
//
|
|
30
37
|
// **Every failure allows the stop.** A missing transcript, an unreadable hold record, a git status
|
|
31
38
|
// that will not read — each ends as "no block", for the same reason the PreToolUse guard fails open:
|
|
32
|
-
// a hook that failed closed would wedge a run over its own plumbing. The
|
|
33
|
-
//
|
|
39
|
+
// a hook that failed closed would wedge a run over its own plumbing. The hook runs the fixed
|
|
40
|
+
// operations itself (send the notify, release the hold) and `stop_hook_active` is the backstop, so failing
|
|
34
41
|
// open here costs at most a missed nudge, never a stuck run.
|
|
35
42
|
|
|
36
|
-
// The hold record for this working tree
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
async function hold_facts(): Promise<Pick<StopContext, 'hold_present' | 'prrun_stopped'>> {
|
|
43
|
+
// The hold record for this working tree. `unreadable` reads as absent: the safe direction for a stop
|
|
44
|
+
// guard is not to act, and an unreadable record is not proof a run is holding.
|
|
45
|
+
async function held_record(): Promise<RunHold | undefined> {
|
|
40
46
|
const directory = await run_hold.worktree_directory()
|
|
41
|
-
|
|
42
|
-
directory === undefined
|
|
43
|
-
? undefined
|
|
44
|
-
: run_halfrun_resume.hold_of(run_hold.read_hold(run_hold.hold_path(directory)))
|
|
47
|
+
if (directory === undefined) return undefined
|
|
45
48
|
|
|
46
|
-
return
|
|
49
|
+
return run_halfrun_resume.hold_of(run_hold.read_hold(run_hold.hold_path(directory)))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// The pause was announced: by a `josh notify` on the tail, or by this hook's own send, recorded on the
|
|
53
|
+
// hold since the pause began (joshuafolkken/kit#3961).
|
|
54
|
+
function was_notified(hold: RunHold | undefined, tail: string): boolean {
|
|
55
|
+
if (lane_park.mentions_confirmation_notify(tail)) return true
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
hold !== undefined && run_hold_session.is_notified_since(hold, last_prompt.pause_start(tail))
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// The Issue the hold names, whether it is a `prrun` stop's and this session's own, and whether its
|
|
63
|
+
// pause was already announced.
|
|
64
|
+
async function hold_facts(
|
|
65
|
+
tail: string,
|
|
66
|
+
): Promise<Pick<StopContext, 'hold_issue' | 'hold_owned' | 'notified' | 'prrun_stopped'>> {
|
|
67
|
+
const hold = await held_record()
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
hold_issue: hold?.issue,
|
|
71
|
+
hold_owned: hold === undefined || run_hold_session.is_session_hold(hold),
|
|
72
|
+
notified: was_notified(hold, tail),
|
|
73
|
+
prrun_stopped: hold?.prrun_stop_head !== undefined,
|
|
74
|
+
}
|
|
47
75
|
}
|
|
48
76
|
|
|
49
77
|
// A `confirmation` notify sits on this run's transcript tail. The tail is derived exactly as the
|
|
@@ -60,6 +88,19 @@ function was_filed(tail: string): boolean {
|
|
|
60
88
|
return filing_cap.turn_filing_count(tail) > 0
|
|
61
89
|
}
|
|
62
90
|
|
|
91
|
+
// This turn ended at a cut — a record still `carried`, or this turn's `run:cut` relaunching a successor.
|
|
92
|
+
// The second covers the successor's resume clearing the record before this stop (joshuafolkken/kit#3936);
|
|
93
|
+
// the held cut outside a lane prints no marker, so its person is still notified.
|
|
94
|
+
function is_cut_stop(tail: string): boolean {
|
|
95
|
+
if (run_cut.carried_cut_sync() !== undefined) return true
|
|
96
|
+
|
|
97
|
+
return tail_commands.answered(
|
|
98
|
+
filing_cap.current_turn(tail),
|
|
99
|
+
pre_gate_cut.takes_the_cut,
|
|
100
|
+
run_cut_report.RELAUNCHED_CUT_MARKER,
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
63
104
|
// The session language the refusal is written for — `write_stop_decision` has already loaded the `.env`.
|
|
64
105
|
function session_lang(): string {
|
|
65
106
|
return session_language.resolve_session_lang().lang
|
|
@@ -85,13 +126,13 @@ async function build_context(
|
|
|
85
126
|
const tail = transcript_tail(transcript_path)
|
|
86
127
|
|
|
87
128
|
return {
|
|
88
|
-
...(await hold_facts()),
|
|
129
|
+
...(await hold_facts(tail)),
|
|
89
130
|
tree_clean: !(await run_hold.is_tree_dirty()),
|
|
90
|
-
notified: lane_park.mentions_confirmation_notify(tail),
|
|
91
131
|
message: payload_tail.message,
|
|
92
132
|
prompt: last_prompt.prompt_text(tail),
|
|
133
|
+
answers_question: stop_decision.is_question(last_prompt.turn_prompt(tail)),
|
|
93
134
|
stop_hook_active: payload_tail.stop_hook_active,
|
|
94
|
-
cut_pending:
|
|
135
|
+
cut_pending: is_cut_stop(tail),
|
|
95
136
|
filed: was_filed(tail),
|
|
96
137
|
session_owner: repo_party.current_owner(),
|
|
97
138
|
...(await session_role_facts()),
|
|
@@ -167,17 +208,17 @@ async function run_report_checks(): Promise<void> {
|
|
|
167
208
|
await run_stranded_detect.run_stranded_check()
|
|
168
209
|
}
|
|
169
210
|
|
|
170
|
-
// Nothing reaches stdout on an ordinary stop, so what the harness parses stays empty unless the stop
|
|
171
|
-
//
|
|
211
|
+
// Nothing reaches stdout on an ordinary stop, so what the harness parses stays empty unless the stop is
|
|
212
|
+
// held or the hook acted on it (`stop-actions.ts`).
|
|
172
213
|
async function write_stop_decision(raw_payload: string): Promise<void> {
|
|
173
214
|
hook_decision.load_environment_file()
|
|
174
215
|
await run_report_checks()
|
|
175
216
|
|
|
176
|
-
const
|
|
217
|
+
const envelope = await stop_actions.envelope_of(await outcome_of(raw_payload), session_lang())
|
|
177
218
|
|
|
178
|
-
if (
|
|
219
|
+
if (envelope === undefined) return
|
|
179
220
|
|
|
180
|
-
process.stdout.write(`${
|
|
221
|
+
process.stdout.write(`${envelope}\n`)
|
|
181
222
|
}
|
|
182
223
|
|
|
183
224
|
const stop_guard = { outcome_of, stop_outcome_for_payload }
|
|
@@ -65,8 +65,12 @@ function rewrite_command_value(value: string): string {
|
|
|
65
65
|
.split(PNPM_JOSH_PREFIX)
|
|
66
66
|
.join(BUNDLE_INVOCATION)
|
|
67
67
|
|
|
68
|
-
if (
|
|
69
|
-
|
|
68
|
+
if (
|
|
69
|
+
!rewritten.includes(claude_plugin_config.MARKETPLACE_PATH) ||
|
|
70
|
+
rewritten.includes(MISSING_INSTALL_GUARD)
|
|
71
|
+
) {
|
|
72
|
+
return rewritten
|
|
73
|
+
}
|
|
70
74
|
|
|
71
75
|
return `${MISSING_INSTALL_GUARD}${rewritten}`
|
|
72
76
|
}
|
|
@@ -9,18 +9,23 @@ import { GIT_LOCATION_VARIABLES } from '#scripts/git/git-location-environment'
|
|
|
9
9
|
// Every path after the prefix is relative, so a hook fired from a
|
|
10
10
|
// subdirectory would otherwise miss its script. The root is git's, resolved with the location
|
|
11
11
|
// variables a git hook exports cleared first, and resolved with them only when that fails — git
|
|
12
|
-
// metadata outside the work tree.
|
|
13
|
-
// so
|
|
12
|
+
// metadata outside the work tree. When the cleared lookup succeeds the variables are cleared for the
|
|
13
|
+
// hook too, so its own git calls act on the checkout it stands in. The consumer rewrite
|
|
14
|
+
// (`hook-command-rewrite.ts`) keeps this prefix, so both sides stay one mechanism for Claude Code and
|
|
15
|
+
// Codex alike.
|
|
14
16
|
//
|
|
15
|
-
// **
|
|
16
|
-
//
|
|
17
|
-
//
|
|
17
|
+
// **A hook runs on every tool call, so its launch spawns as little as it can** (joshuafolkken/kit#3946):
|
|
18
|
+
// the shell's own `unset` clears the variables rather than an `env` process, and that one lookup
|
|
19
|
+
// both finds the root and decides whether the variables stay — `git` starts once.
|
|
20
|
+
//
|
|
21
|
+
// **Everything after the root is one script** (`scripts/hooks/run-hook.sh`): the bundle-ready gate and
|
|
22
|
+
// the live-source fallback live there once, so each hook command names only its hook.
|
|
18
23
|
|
|
19
24
|
const HOOK_DIST_DIR = 'dist/hooks'
|
|
20
25
|
const RUN_HOOK_SCRIPT = 'scripts/hooks/run-hook.sh'
|
|
21
|
-
const
|
|
26
|
+
const LOCATION_VARIABLES = GIT_LOCATION_VARIABLES.join(' ')
|
|
22
27
|
const ROOT_LOOKUP = 'git rev-parse --show-toplevel'
|
|
23
|
-
const PROJECT_ROOT_PREFIX = `project_root="$(
|
|
28
|
+
const PROJECT_ROOT_PREFIX = `if project_root="$(unset ${LOCATION_VARIABLES}; ${ROOT_LOOKUP} 2>/dev/null)"; then unset ${LOCATION_VARIABLES}; else project_root="$(${ROOT_LOOKUP})"; fi && cd "$project_root" && `
|
|
24
29
|
|
|
25
30
|
// `name` is the hook's bundle under `dist/hooks` without `.js` (e.g. `pretool-guard`), followed by any
|
|
26
31
|
// arguments it takes (e.g. `codex-hook-adapter pretool`).
|
|
@@ -30,8 +30,10 @@ function patch_deploy_vps_pnpm(content: string): string {
|
|
|
30
30
|
const with_pnpm12 = content.replaceAll(PNPM_INSTALL_PATTERN, (command) =>
|
|
31
31
|
replace_outdated_install(command),
|
|
32
32
|
)
|
|
33
|
-
|
|
34
|
-
if (!OLD_PNPM_CHECK_PATTERN.test(with_pnpm12))
|
|
33
|
+
|
|
34
|
+
if (with_pnpm12.includes(VERSION_CHECK_MARKER) || !OLD_PNPM_CHECK_PATTERN.test(with_pnpm12)) {
|
|
35
|
+
return with_pnpm12
|
|
36
|
+
}
|
|
35
37
|
|
|
36
38
|
return with_pnpm12
|
|
37
39
|
.replace(OLD_MAJOR_LINE_PATTERN, '')
|
|
@@ -76,9 +76,12 @@ function firing_point_problem(body: string): string | undefined {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
function baseline_problem(body: string): string | undefined {
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
if (
|
|
80
|
+
!markdown_section.has_line(body, BASELINE_HEADING) ||
|
|
81
|
+
baseline_measure.has_command_value(body)
|
|
82
|
+
) {
|
|
83
|
+
return undefined
|
|
84
|
+
}
|
|
82
85
|
|
|
83
86
|
return 'baseline must be written as `command → value`, not prose (it has to be re-runnable)'
|
|
84
87
|
}
|
|
@@ -86,9 +89,12 @@ function baseline_problem(body: string): string | undefined {
|
|
|
86
89
|
// The reproduction section is checked for format only where the heading is present — a missing heading
|
|
87
90
|
// is already reported by `heading_problems`, so reporting it here too would double the message.
|
|
88
91
|
function reproduction_problem(body: string): string | undefined {
|
|
89
|
-
if (
|
|
90
|
-
|
|
91
|
-
|
|
92
|
+
if (
|
|
93
|
+
!markdown_section.has_line(body, REPRODUCTION_HEADING) ||
|
|
94
|
+
reproduction_measure.has_command_output(body)
|
|
95
|
+
) {
|
|
96
|
+
return undefined
|
|
97
|
+
}
|
|
92
98
|
|
|
93
99
|
return 'reproduction must be a command and its actual output (a fenced block), not prose ("確認した" is not re-runnable)'
|
|
94
100
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { markdown_section } from './markdown-section'
|
|
2
|
+
|
|
3
|
+
// joshuafolkken/kit#3909: a run-time-shortening proposal was filed and implemented on a criterion that
|
|
4
|
+
// never weighed how often the speed-up applies against what it costs when it does not. An Issue that
|
|
5
|
+
// declares that aim must therefore state its expected effect — the hit rate, the gain per hit, the loss
|
|
6
|
+
// per miss and where each number came from — before `issue:lint` and `issue:file` accept it. The
|
|
7
|
+
// declaration, the heading and the item labels are the template's, pinned by the document test.
|
|
8
|
+
|
|
9
|
+
const DECLARATION_LINE = '- 目的: 時間短縮'
|
|
10
|
+
const BACKGROUND_HEADING = '## 背景'
|
|
11
|
+
const HEADING = '## 期待効果'
|
|
12
|
+
const ITEM_LABELS: ReadonlyArray<string> = ['効く率', '1 回あたりの得', '外れたときの損', '出典']
|
|
13
|
+
// A template placeholder value — the `<...>` guidance a filed Issue replaces. It is not an estimate, but a
|
|
14
|
+
// value that only opens with `<` (an upper bound such as `<1 min`) is one, so the placeholder must close with `>` too.
|
|
15
|
+
const PLACEHOLDER = /^<.*>$/u
|
|
16
|
+
|
|
17
|
+
// Whether the body declares a run-time-shortening aim under its background heading.
|
|
18
|
+
function is_target(body: string): boolean {
|
|
19
|
+
const background = markdown_section.section_lines(body, BACKGROUND_HEADING).join('\n')
|
|
20
|
+
|
|
21
|
+
return markdown_section.has_unfenced_line(background, DECLARATION_LINE)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function item_value(line: string, label: string): string | undefined {
|
|
25
|
+
const prefix = `- ${label}:`
|
|
26
|
+
|
|
27
|
+
if (!line.startsWith(prefix)) return undefined
|
|
28
|
+
|
|
29
|
+
return line.slice(prefix.length).trim()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function has_item(lines: ReadonlyArray<string>, label: string): boolean {
|
|
33
|
+
return lines.some((line) => {
|
|
34
|
+
const value = item_value(line.trim(), label)
|
|
35
|
+
|
|
36
|
+
return value !== undefined && value.length > 0 && !PLACEHOLDER.test(value)
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Every expected-effect problem a body has, or an empty array. A body without the declaration has none.
|
|
41
|
+
function problems(body: string): ReadonlyArray<string> {
|
|
42
|
+
if (!is_target(body)) return []
|
|
43
|
+
if (!markdown_section.has_line(body, HEADING)) return [`missing heading: ${HEADING}`]
|
|
44
|
+
const section = markdown_section.section_lines(body, HEADING).join('\n')
|
|
45
|
+
const lines = markdown_section.unfenced_lines(section)
|
|
46
|
+
|
|
47
|
+
return ITEM_LABELS.filter((label) => !has_item(lines, label)).map(
|
|
48
|
+
(label) => `missing expected-effect item: - ${label}: <value>`,
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const expected_effect_lint = {
|
|
53
|
+
DECLARATION_LINE,
|
|
54
|
+
HEADING,
|
|
55
|
+
ITEM_LABELS,
|
|
56
|
+
problems,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { expected_effect_lint }
|
|
@@ -101,16 +101,26 @@ async function local_slug_for(targets: ReadonlyArray<CiteTarget>): Promise<strin
|
|
|
101
101
|
return has_bare_target ? await git_gh_command.repo_get_name_with_owner() : undefined
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
// The citation lines and the failure lines of one invocation, apart.
|
|
105
|
+
interface CiteReport {
|
|
106
|
+
lines: ReadonlyArray<string>
|
|
107
|
+
failures: ReadonlyArray<string>
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function split_results(results: ReadonlyArray<LineResult>): CiteReport {
|
|
111
|
+
return {
|
|
112
|
+
lines: results.filter((result) => result.kind === 'ok').map((result) => result.line),
|
|
113
|
+
failures: results.filter((result) => result.kind === 'fail').map((result) => result.line),
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
104
117
|
// Citations to stdout so the block stays paste-ready; failures to stderr so each unanswered number is
|
|
105
118
|
// named without landing in what gets pasted. A failure sets a non-zero exit.
|
|
106
|
-
function report(
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
if (lines.length > 0) console.info(lines.join(LINE_SEPARATOR))
|
|
111
|
-
for (const failure of failures) console.error(failure)
|
|
119
|
+
function report(cited: CiteReport): number {
|
|
120
|
+
if (cited.lines.length > 0) console.info(cited.lines.join(LINE_SEPARATOR))
|
|
121
|
+
for (const failure of cited.failures) console.error(failure)
|
|
112
122
|
|
|
113
|
-
return failures.length === 0 ? SUCCESS_EXIT_CODE : FAILURE_EXIT_CODE
|
|
123
|
+
return cited.failures.length === 0 ? SUCCESS_EXIT_CODE : FAILURE_EXIT_CODE
|
|
114
124
|
}
|
|
115
125
|
|
|
116
126
|
// The targets the command line names, or `undefined` for a line nobody can read — an unknown flag and
|
|
@@ -122,14 +132,11 @@ function read_targets(args: ReadonlyArray<string>): ReadonlyArray<CiteTarget> |
|
|
|
122
132
|
return parse_targets(parsed.positionals, parsed.values.repo)
|
|
123
133
|
}
|
|
124
134
|
|
|
125
|
-
|
|
135
|
+
// The lines `issue:cite` prints for `args`, without printing them — the `Stop` hook shows them itself
|
|
136
|
+
// (`stop-actions.ts`). `undefined` for a line nobody can read.
|
|
137
|
+
async function cite(args: ReadonlyArray<string>): Promise<CiteReport | undefined> {
|
|
126
138
|
const targets = read_targets(args)
|
|
127
|
-
|
|
128
|
-
if (targets === undefined) {
|
|
129
|
-
console.error(USAGE)
|
|
130
|
-
|
|
131
|
-
return FAILURE_EXIT_CODE
|
|
132
|
-
}
|
|
139
|
+
if (targets === undefined) return undefined
|
|
133
140
|
|
|
134
141
|
const local_slug = await local_slug_for(targets)
|
|
135
142
|
const results = await bounded_pool.bounded_map(
|
|
@@ -138,7 +145,19 @@ async function run(args: ReadonlyArray<string>): Promise<number> {
|
|
|
138
145
|
async (target) => await cite_target(target, local_slug),
|
|
139
146
|
)
|
|
140
147
|
|
|
141
|
-
return
|
|
148
|
+
return split_results(results)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function run(args: ReadonlyArray<string>): Promise<number> {
|
|
152
|
+
const cited = await cite(args)
|
|
153
|
+
|
|
154
|
+
if (cited === undefined) {
|
|
155
|
+
console.error(USAGE)
|
|
156
|
+
|
|
157
|
+
return FAILURE_EXIT_CODE
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return report(cited)
|
|
142
161
|
}
|
|
143
162
|
|
|
144
163
|
async function main(args: ReadonlyArray<string>): Promise<void> {
|
|
@@ -147,6 +166,7 @@ async function main(args: ReadonlyArray<string>): Promise<void> {
|
|
|
147
166
|
|
|
148
167
|
const issue_cite_cli = {
|
|
149
168
|
USAGE,
|
|
169
|
+
cite,
|
|
150
170
|
parse_targets,
|
|
151
171
|
run,
|
|
152
172
|
}
|
|
@@ -2,7 +2,7 @@ import { issue_bug_label } from './issue-bug-label'
|
|
|
2
2
|
import { BREAKING_CHANGE_LABEL, BUG_LABEL, ENHANCEMENT_LABEL, has_label_name } from './issue-labels'
|
|
3
3
|
import { markdown_section } from './markdown-section'
|
|
4
4
|
|
|
5
|
-
const FEATURE_DECLARATIONS = ['- 目的: 機能追加', '- 目的: 機能改善']
|
|
5
|
+
const FEATURE_DECLARATIONS = ['- 目的: 機能追加', '- 目的: 機能改善', '- 目的: 時間短縮']
|
|
6
6
|
const BREAKING_DECLARATION = '- 互換性: 破壊的変更'
|
|
7
7
|
const NON_BUG_DECLARATION = '- 種別: 非不具合'
|
|
8
8
|
const CLASSIFICATION_LABELS = [BUG_LABEL, ENHANCEMENT_LABEL, BREAKING_CHANGE_LABEL]
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { readFile } from 'node:fs/promises'
|
|
3
3
|
import { fileURLToPath } from 'node:url'
|
|
4
4
|
import { behavior_change_lint } from './behavior-change-lint'
|
|
5
|
+
import { expected_effect_lint } from './expected-effect-lint'
|
|
5
6
|
import { issue_classification } from './issue-classification'
|
|
6
7
|
import { issue_lint } from './issue-lint'
|
|
7
8
|
|
|
@@ -9,7 +10,7 @@ import { issue_lint } from './issue-lint'
|
|
|
9
10
|
// one per line, exiting non-zero. It reads a path rather than stdin so it can
|
|
10
11
|
// lint a body written to a file before the `gh api … issues` call that files it. A body that declares
|
|
11
12
|
// itself a behavior-change Issue is additionally held to the firing-point, baseline and reproduction
|
|
12
|
-
// rules.
|
|
13
|
+
// rules, and one that declares a run-time-shortening aim to the expected-effect estimate.
|
|
13
14
|
|
|
14
15
|
const SUCCESS_EXIT_CODE = 0
|
|
15
16
|
const FAILURE_EXIT_CODE = 1
|
|
@@ -17,14 +18,15 @@ const OK_MESSAGE = 'ok'
|
|
|
17
18
|
const NO_LABELS = 'none'
|
|
18
19
|
const USAGE = 'Usage: josh issue:lint <path-to-issue-body>'
|
|
19
20
|
|
|
20
|
-
// Every problem a body has: the four base template headings it lacks, then the behavior-change
|
|
21
|
-
// problems (empty for an Issue that does not declare itself one).
|
|
21
|
+
// Every problem a body has: the four base template headings it lacks, then the behavior-change and
|
|
22
|
+
// expected-effect problems (each empty for an Issue that does not declare itself one).
|
|
22
23
|
function problems(body: string): ReadonlyArray<string> {
|
|
23
24
|
const missing = issue_lint.missing_headings(body).map((heading) => `missing heading: ${heading}`)
|
|
24
25
|
|
|
25
26
|
return [
|
|
26
27
|
...missing,
|
|
27
28
|
...behavior_change_lint.problems(body),
|
|
29
|
+
...expected_effect_lint.problems(body),
|
|
28
30
|
...issue_classification.problems(body),
|
|
29
31
|
]
|
|
30
32
|
}
|
|
@@ -73,8 +73,9 @@ function read_arguments(argv: ReadonlyArray<string>): ScoutArguments | undefined
|
|
|
73
73
|
const { values, positionals } = parsed
|
|
74
74
|
const [title] = positionals
|
|
75
75
|
|
|
76
|
-
if (has_invalid_arguments(title, values.body, values['body-file']))
|
|
77
|
-
|
|
76
|
+
if (title === undefined || has_invalid_arguments(title, values.body, values['body-file'])) {
|
|
77
|
+
return undefined
|
|
78
|
+
}
|
|
78
79
|
|
|
79
80
|
return { title, body: values.body ?? '', body_file: values['body-file'] }
|
|
80
81
|
}
|
|
@@ -102,7 +103,7 @@ function draft_of(args: ScoutArguments, repo: string): BacklogIssue {
|
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
// The epic beside a candidate is the placement answer for a draft that cites nothing: the epic half
|
|
105
|
-
// below decides from
|
|
106
|
+
// below decides from signal references, and a title-only draft has none to give it.
|
|
106
107
|
function format_duplicate(candidate: DuplicateCandidate): string {
|
|
107
108
|
const score = candidate.is_referenced === true ? 'ref' : candidate.score.toFixed(SCORE_DIGITS)
|
|
108
109
|
const epic = candidate.epic === undefined ? '' : ` (epic ${session_cite.issue(candidate.epic)})`
|
|
@@ -161,12 +162,13 @@ function format_epic(decision: BundleDecision): Array<string> {
|
|
|
161
162
|
return lines
|
|
162
163
|
}
|
|
163
164
|
|
|
164
|
-
// A draft cites nothing, so the epic half has nothing to decide from: its two
|
|
165
|
-
//
|
|
166
|
-
//
|
|
165
|
+
// A draft cites nothing where a citation counts, so the epic half has nothing to decide from: its two
|
|
166
|
+
// signals are references in the acceptance criteria or the `parent: #N` line and recorded
|
|
167
|
+
// dependencies, and a title or a background mention carries neither. Saying "file it standalone"
|
|
168
|
+
// there reports a scan that found nothing when none was possible — the confident wrong answer the gap
|
|
167
169
|
// warnings exist to prevent everywhere else in this command.
|
|
168
170
|
const NO_REFERENCE_LINE =
|
|
169
|
-
'Epic: not asked — no issue number in the summary. Pass --body "
|
|
171
|
+
'Epic: not asked — no issue number in the summary\'s acceptance criteria or parent line. Pass --body "parent: #<N>", or take the epic printed beside a duplicate above.'
|
|
170
172
|
|
|
171
173
|
// Every placing verdict above asserts that no epic already tracks the draft's relatives, and a cut
|
|
172
174
|
// epic listing cannot support that: `epic:bundle` withholds them for it,
|
|
@@ -194,18 +196,22 @@ function format_epic_decision(
|
|
|
194
196
|
return format_epic(decision).join('\n')
|
|
195
197
|
}
|
|
196
198
|
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
|
|
199
|
+
// The issue numbers the summary names where `decide_bundle` counts a reference — its acceptance
|
|
200
|
+
// criteria and its `parent: #N` line — read through the same parser the decision itself uses: a
|
|
201
|
+
// second reading of "does this body cite anything" could disagree with the one that matters, and an
|
|
202
|
+
// issue cited only as the background of the filing is not one the draft belongs with.
|
|
203
|
+
function signal_numbers(draft: BacklogIssue): Array<number> {
|
|
204
|
+
return epic_audit_logic
|
|
205
|
+
.signal_references(draft.body, draft.repo)
|
|
206
|
+
.map((reference) => reference.number)
|
|
201
207
|
}
|
|
202
208
|
|
|
203
209
|
// The open epics the summary names outright. `decide_bundle` excludes an epic from the candidate pool
|
|
204
|
-
// — a container is not a sibling — so a draft
|
|
205
|
-
// workflow documents themselves suggest, otherwise reaches `none` and is told to file
|
|
206
|
-
// the epic it named never mentioned.
|
|
210
|
+
// — a container is not a sibling — so a draft whose `parent: #1153` line names the epic, which is the
|
|
211
|
+
// phrasing the workflow documents themselves suggest, otherwise reaches `none` and is told to file
|
|
212
|
+
// standalone with the epic it named never mentioned.
|
|
207
213
|
function named_epics(draft: BacklogIssue, issues: ReadonlyArray<BacklogIssue>): Array<number> {
|
|
208
|
-
const named = new Set(
|
|
214
|
+
const named = new Set(signal_numbers(draft))
|
|
209
215
|
|
|
210
216
|
return issues
|
|
211
217
|
.filter((issue) => issue.is_epic === true && named.has(issue.number))
|
|
@@ -240,7 +246,7 @@ function format_epic_answer(
|
|
|
240
246
|
|
|
241
247
|
if (named.length > 0) return format_named_epics(named, decision).join('\n')
|
|
242
248
|
|
|
243
|
-
return format_epic_decision(decision,
|
|
249
|
+
return format_epic_decision(decision, signal_numbers(draft).length > 0, is_membership_established)
|
|
244
250
|
}
|
|
245
251
|
|
|
246
252
|
// The closed rows reach the duplicate half only. `decide_bundle` places a draft among issues that
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
// way, so the slicing lives in one place rather than being written twice.
|
|
4
4
|
|
|
5
5
|
const HEADING_PREFIX = '## '
|
|
6
|
+
// A body saved from GitHub's web editor ends its lines with CRLF; a kept `\r` would end every line's
|
|
7
|
+
// text with it and defeat each `$`-anchored match downstream.
|
|
8
|
+
const LINE_BREAK_PATTERN = /\r?\n/u
|
|
6
9
|
|
|
7
10
|
function is_heading(line: string): boolean {
|
|
8
11
|
return line.trimStart().startsWith(HEADING_PREFIX)
|
|
@@ -29,21 +32,31 @@ function next_fence(line: string, fence: string): string | undefined {
|
|
|
29
32
|
return undefined
|
|
30
33
|
}
|
|
31
34
|
|
|
32
|
-
function
|
|
35
|
+
function lines_of(body: string): ReadonlyArray<string> {
|
|
36
|
+
return body.split(LINE_BREAK_PATTERN)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// The body's lines with every fenced line, fences included, blanked. Indentation is kept, so a list
|
|
40
|
+
// item's indented continuation survives.
|
|
41
|
+
function fence_free_lines(body: string): ReadonlyArray<string> {
|
|
33
42
|
let fence = ''
|
|
34
43
|
const visible: Array<string> = []
|
|
35
44
|
|
|
36
|
-
for (const line of body
|
|
45
|
+
for (const line of lines_of(body)) {
|
|
37
46
|
const next = next_fence(line, fence)
|
|
38
|
-
const visible_line = line.replace(/^(?: {4}|\t).*/u, '')
|
|
39
47
|
|
|
40
|
-
visible.push(next === undefined && fence === '' ?
|
|
48
|
+
visible.push(next === undefined && fence === '' ? line : '')
|
|
41
49
|
fence = next ?? fence
|
|
42
50
|
}
|
|
43
51
|
|
|
44
52
|
return visible
|
|
45
53
|
}
|
|
46
54
|
|
|
55
|
+
// As `fence_free_lines`, with an indented code line (4 spaces or a tab) blanked too.
|
|
56
|
+
function unfenced_lines(body: string): ReadonlyArray<string> {
|
|
57
|
+
return fence_free_lines(body).map((line) => line.replace(/^(?: {4}|\t).*/u, ''))
|
|
58
|
+
}
|
|
59
|
+
|
|
47
60
|
function has_unfenced_line(body: string, text: string): boolean {
|
|
48
61
|
return unfenced_lines(body).some((line) => line.trim() === text)
|
|
49
62
|
}
|
|
@@ -59,7 +72,7 @@ function section_lines_matching(
|
|
|
59
72
|
body: string,
|
|
60
73
|
is_target: (line: string) => boolean,
|
|
61
74
|
): ReadonlyArray<string> {
|
|
62
|
-
const lines = body
|
|
75
|
+
const lines = lines_of(body)
|
|
63
76
|
const visible = unfenced_lines(body)
|
|
64
77
|
const start = visible.findIndex((line) => is_target(line))
|
|
65
78
|
|
|
@@ -80,6 +93,7 @@ function section_lines(body: string, heading: string): ReadonlyArray<string> {
|
|
|
80
93
|
const markdown_section = {
|
|
81
94
|
section_lines,
|
|
82
95
|
section_lines_matching,
|
|
96
|
+
fence_free_lines,
|
|
83
97
|
unfenced_lines,
|
|
84
98
|
has_line,
|
|
85
99
|
has_unfenced_line,
|