@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
package/prompts/testing-guide.md
CHANGED
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
## 0. Per-Requirement Test Coverage (MANDATORY)
|
|
4
4
|
|
|
5
|
-
Before
|
|
5
|
+
Before any implementation code, **list every requirement from the user's request and assign a test to each one**.
|
|
6
6
|
|
|
7
7
|
### Planning template
|
|
8
8
|
|
|
9
|
-
For a user request with N requirements, enumerate each one before touching any source files:
|
|
10
|
-
|
|
11
9
|
```text
|
|
12
10
|
Req 1: "Label chip shown immediately"
|
|
13
11
|
→ E2E: open editor, type new label, press Enter → chip visible without blurring
|
|
@@ -21,19 +19,17 @@ Req N: ...
|
|
|
21
19
|
|
|
22
20
|
### Rules
|
|
23
21
|
|
|
24
|
-
-
|
|
25
|
-
-
|
|
22
|
+
- User-facing behavior change (UI interaction, keyboard shortcut, visible state) → an E2E test.
|
|
23
|
+
- Logic/utility change (pure functions, filters, transforms) → a unit test. Bug fix → a regression test; refactor → tests pinning the existing behavior BEFORE the change (`prompts/refactoring.md`).
|
|
26
24
|
- **Non-runtime updates (pre-approved manual-only exception):** a change touching no executable runtime code path — docs, non-executable config, editor/IDE files, cosmetic asset swaps with no selector/path change — may be verified by hand; declare it in Step 0 with the reason. The mechanically exempt (`*.md`, `.editorconfig`, `.idea/**`, `.vscode/**`, `prompts/**`) need no declaration. A runtime change whose test is infeasible needs the reason and user approval.
|
|
27
|
-
- **basic で kit がテストできない言語だけの変更:** `package.json` に `josh.profile: basic`(改名前の `static`
|
|
28
|
-
-
|
|
29
|
-
-
|
|
25
|
+
- **basic で kit がテストできない言語だけの変更:** `package.json` に `josh.profile: basic`(改名前の `static` も同じ)があり、変更が HTML/CSS や Lua などのソースだけなら自動テストは不要。HTML/CSS はブラウザーで対象ページを開き、想定する画面幅でレイアウト・リンク・HTML 内の操作を確認する。そのほかのソースは変更した処理を実際に動かす。どちらも結果を記録する。JavaScript または TypeScript のファイルを含む変更は自動テストを行う。
|
|
26
|
+
- Test infeasible (e.g., a native OS date-picker Playwright cannot drive) → comment why in the test file and test the closest observable behavior (e.g., the editor stays open after `change`).
|
|
27
|
+
- A requirement without its test is not done.
|
|
30
28
|
|
|
31
29
|
### Verification checklist (add to the completion gate)
|
|
32
30
|
|
|
33
|
-
- [ ]
|
|
34
|
-
- [ ]
|
|
35
|
-
- [ ] The two counts match (or each gap is documented as infeasible with a comment).
|
|
36
|
-
- [ ] **UI changes only:** a screenshot of the affected screen was captured and visually confirmed before reporting completion (passing tests are not proof the UI looks correct — see the **UI verification (screenshot)** rule in the completion gate). If a screenshot is impossible in this environment, that is stated and the user is asked to verify visually.
|
|
31
|
+
- [ ] Requirement count equals the count of tests added/updated (each gap documented as infeasible).
|
|
32
|
+
- [ ] UI changes: a screenshot of the affected screen was checked (`CLAUDE.md` → UI verification); if impossible here, say so and ask the user to check.
|
|
37
33
|
|
|
38
34
|
---
|
|
39
35
|
|
|
@@ -44,21 +40,19 @@ Req N: ...
|
|
|
44
40
|
| `src/routes/` pages, UI components with user interaction | E2E (Playwright) |
|
|
45
41
|
| `.ts`/`.js` utilities, `src/lib/server/`, display-only components | Unit/Integration (Vitest) |
|
|
46
42
|
|
|
47
|
-
**The type is decided from the path, not by asking
|
|
43
|
+
**The type is decided from the path, not by asking**: under `src/routes/` → E2E; every other runtime path → Unit (an interactive component is reached through its route). `pnpm josh test:declared` prints the type per untested runtime file on its `required` line.
|
|
48
44
|
|
|
49
45
|
### Test file naming & placement (one unambiguous rule)
|
|
50
46
|
|
|
51
|
-
There is exactly **one** convention for every test file — never choose between two. The rules below are mandatory; some are config-enforced (a violating file runs under the wrong vitest project or not at all), others are enforced by convention/review — each rule states which.
|
|
52
|
-
|
|
53
47
|
| Test kind | Required filename | Routed to |
|
|
54
48
|
| ------------------- | ------------------------ | -------------------------------- |
|
|
55
49
|
| Unit / integration | `*.test.ts` | node/unit vitest project |
|
|
56
50
|
| Component / browser | `*.svelte.test.ts` | browser/component vitest project |
|
|
57
51
|
| E2E | `src/routes/**/*.e2e.ts` | Playwright |
|
|
58
52
|
|
|
59
|
-
- **
|
|
60
|
-
- **
|
|
61
|
-
- **Colocate
|
|
53
|
+
- **Never `*.spec.ts`** — lint-enforced by `eslint/rules/test-filename.js` (wired into `eslint/base.js`) for every JS/TS extension.
|
|
54
|
+
- **Keep the `.svelte.` infix** on component/browser tests: `*.svelte.test.ts` matches the browser project (`include: src/**/*.svelte.{test,spec}.{js,ts}`), plain `*.test.ts` the node project (`include: src/**/*.{test,spec}.{js,ts}`); dropping it silently moves the file to the wrong project.
|
|
55
|
+
- **Colocate** each test beside its code (`foo.test.ts` next to `foo.ts`); no top-level `tests/` (lint-enforced by the same rule). E2E specs go under `src/routes/**` — enforced by review only, since `playwright.config.ts` `testMatch: '**/*.e2e.{ts,js}'` runs them anywhere.
|
|
62
56
|
|
|
63
57
|
---
|
|
64
58
|
|
|
@@ -72,156 +66,111 @@ There is exactly **one** convention for every test file — never choose between
|
|
|
72
66
|
### Test Functions
|
|
73
67
|
|
|
74
68
|
- Outside `describe`: `test`; inside `describe`: `it`
|
|
75
|
-
-
|
|
69
|
+
- `describe` only when grouping multiple tests
|
|
76
70
|
|
|
77
71
|
### Parameterized Tests
|
|
78
72
|
|
|
79
73
|
- Playwright: `for` loop; Vitest: `test.each` / `it.each`
|
|
80
|
-
-
|
|
74
|
+
- Cases: a `cases` or `*_cases` array at file top, outside test functions
|
|
81
75
|
|
|
82
76
|
### Constants
|
|
83
77
|
|
|
84
|
-
-
|
|
85
|
-
- Place after imports at file top
|
|
78
|
+
- Magic numbers/strings except `0`, `1`, `-1` → `UPPER_SNAKE_CASE` constants after the imports
|
|
86
79
|
|
|
87
80
|
### Imports
|
|
88
81
|
|
|
89
|
-
- Always
|
|
82
|
+
- Always the `.js` extension: `import { foo } from './bar.js'`
|
|
90
83
|
- Type imports: `import { type Foo } from '...'`
|
|
91
84
|
|
|
92
85
|
### Playwright
|
|
93
86
|
|
|
94
|
-
-
|
|
87
|
+
- Select with `page.getByTestId('id')`; add `data-testid` to the implementation
|
|
95
88
|
- Test functions always `async`
|
|
96
|
-
-
|
|
97
|
-
-
|
|
98
|
-
-
|
|
89
|
+
- `playwright.config.ts` projects, each depending on the previous: `e2e-guest` (no auth: `src/routes/**/dash-guest.e2e.ts`, `src/routes/**/demo.e2e.ts`) → `e2e-main` → `e2e-leak-check`. Authenticated ones restore `storageState` from `src/routes/.auth/user.json` when it exists — no UI login per test.
|
|
90
|
+
- `workers: 1` — authenticated E2E shares one storage state and DB user.
|
|
91
|
+
- Guest/main/leak projects set per-project test timeouts so tab switches and dash actions are not cut off.
|
|
99
92
|
|
|
100
93
|
### Regression fix workflow (E2E data / cleanup bugs)
|
|
101
94
|
|
|
102
|
-
When
|
|
95
|
+
When tests leave persistent state behind:
|
|
103
96
|
|
|
104
|
-
1.
|
|
105
|
-
2.
|
|
106
|
-
3.
|
|
107
|
-
4.
|
|
97
|
+
1. Add or extend a guard and confirm it fails (e.g. `dash_leak_guard.scrub_then_assert_clean` in `src/routes/dash/dash-leak-check.e2e.ts` scrubs `E2E_`-prefixed tasks, then asserts none remain).
|
|
98
|
+
2. Fix production code or teardown — prefer `data-testid` over locale-specific `aria-label`; do not swallow cleanup errors unless intended.
|
|
99
|
+
3. Confirm green with full `pnpm test:e2e` (or a targeted Playwright run).
|
|
100
|
+
4. Document any new invariant here or in the E2E helper module.
|
|
108
101
|
|
|
109
102
|
### Error Messages
|
|
110
103
|
|
|
111
|
-
-
|
|
104
|
+
- State expected and actual values
|
|
112
105
|
- Wrap `number` in template literals with `String()`
|
|
113
|
-
-
|
|
106
|
+
- No `?? ''` unless the value can be `undefined`
|
|
114
107
|
|
|
115
108
|
### Type Safety
|
|
116
109
|
|
|
117
|
-
- No `any`;
|
|
118
|
-
-
|
|
110
|
+
- No `any`; use every loop variable
|
|
111
|
+
- `@ts-expect-error`, not `@ts-ignore`
|
|
119
112
|
|
|
120
113
|
### Statement Limit
|
|
121
114
|
|
|
122
|
-
- Max 10 statements per test function; split
|
|
115
|
+
- Max 10 statements per test function; split beyond that
|
|
123
116
|
|
|
124
117
|
### No live network
|
|
125
118
|
|
|
126
|
-
- **A unit test must not reach the network
|
|
127
|
-
- **Mock the whole read
|
|
128
|
-
-
|
|
119
|
+
- **A unit test must not reach the network** — mock every read leaving the process (`gh` call, HTTP request, CLI subprocess that makes one).
|
|
120
|
+
- **Mock the whole read**: a stub of one call leaving a second on its default still passes, slowly and against the remote (joshuafolkken/kit#1353: a 10-second CI timeout).
|
|
121
|
+
- kit's `vitest.config.ts` fails such a run, listing the `gh` invocations (`docs/josh-commands.md` → `josh test:unit`); elsewhere the rule holds unguarded.
|
|
129
122
|
|
|
130
123
|
### A josh command that writes state runs through the integration harness (kit only)
|
|
131
124
|
|
|
132
|
-
-
|
|
125
|
+
- A new or changed state-writing josh command needs a test in all three environments of `scripts/test/josh-harness.ts` — kit, a consumer without `docs/`, a lane worktree (joshuafolkken/kit#2402). Packed suite: `vitest.harness.config.ts`
|
|
133
126
|
|
|
134
127
|
---
|
|
135
128
|
|
|
136
129
|
## 3. Checklist
|
|
137
130
|
|
|
138
|
-
- [ ]
|
|
139
|
-
- [ ]
|
|
140
|
-
- [ ] `.js`
|
|
141
|
-
- [ ] Playwright: `data-testid` selectors only
|
|
142
|
-
- [ ] `async`/`await` used correctly
|
|
143
|
-
- [ ] No `any`; all loop variables used
|
|
144
|
-
- [ ] Max 10 statements per test function
|
|
145
|
-
- [ ] `read_lints` run; 0 errors before reporting done
|
|
146
|
-
- [ ] No unnecessary `?? ''`; `String()` for numbers in template literals
|
|
131
|
+
- [ ] `pnpm josh gate`, then E2E per § 6: the CI E2E job where a pull request is open; where there is none, run `pnpm josh test:e2e` with `CI` unset (DEV: `pnpm run dev` on the port `playwright.config.ts` derives from `PORT_SEED`); no `pnpm build` or `CI=true` unless asked. Playwright cannot run here → report the E2E result missing and the gate not closed
|
|
132
|
+
- [ ] `read_lints`: 0 errors on touched files; Playwright: no failures and no flaky runs
|
|
133
|
+
- [ ] § 2 holds: constants, `.js` imports, `data-testid` selectors, correct `async`/`await`, no `any`, ≤10 statements, `String()` / no needless `?? ''`
|
|
147
134
|
|
|
148
135
|
---
|
|
149
136
|
|
|
150
137
|
## 4. Reference Files
|
|
151
138
|
|
|
152
|
-
|
|
153
|
-
**Unit:** `src/lib/data/phrases/phrases.test.ts`, `src/lib/data/praise-audio.test.ts`
|
|
154
|
-
**ESLint:** `eslint.config.js`
|
|
139
|
+
E2E: `src/routes/**/page.e2e.ts`, `src/routes/**/praise.e2e.ts` · Unit: `src/lib/data/phrases/phrases.test.ts`, `src/lib/data/praise-audio.test.ts` · ESLint: `eslint.config.js`
|
|
155
140
|
|
|
156
141
|
---
|
|
157
142
|
|
|
158
143
|
## 5. Unit Test Example — Stateful Functions
|
|
159
144
|
|
|
160
|
-
|
|
161
|
-
import { expect, test } from 'vitest'
|
|
162
|
-
import { get_praise_audio_file, reset_praise_audio_index } from './praise-audio.js'
|
|
163
|
-
|
|
164
|
-
test('returns expected values in sequence', () => {
|
|
165
|
-
reset_praise_audio_index() // reset state at the start of each test
|
|
166
|
-
|
|
167
|
-
const first_result = get_praise_audio_file()
|
|
168
|
-
expect(first_result).toBe('expected-value-1')
|
|
169
|
-
|
|
170
|
-
const second_result = get_praise_audio_file()
|
|
171
|
-
expect(second_result).toBe('expected-value-2')
|
|
172
|
-
})
|
|
173
|
-
```
|
|
145
|
+
Reset module state at the start of each test (`reset_praise_audio_index()` in `src/lib/data/praise-audio.test.ts`), then assert each call of the sequence in order.
|
|
174
146
|
|
|
175
147
|
---
|
|
176
148
|
|
|
177
149
|
## 6. Closing the E2E gate without a human run
|
|
178
150
|
|
|
179
|
-
No human E2E run closes the gate, and nothing a red local run stopped is let through
|
|
180
|
-
(`docs/maintainers/testing-guide-history.md`).
|
|
151
|
+
No human E2E run closes the gate, and nothing a red local run stopped is let through.
|
|
181
152
|
|
|
182
153
|
### Which result closes the gate
|
|
183
154
|
|
|
184
|
-
Decided by whether a pull request is open
|
|
155
|
+
Decided by whether a pull request is open:
|
|
185
156
|
|
|
186
157
|
| Situation | What closes the gate |
|
|
187
158
|
| -------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
188
159
|
| A pull request is open — `fullrun`, `prrun`, `backlogrun` | The CI E2E job. `pnpm josh followup` waits for the checks and refuses to merge while any of them is non-passing. |
|
|
189
160
|
| No pull request — `halfrun`'s stop before commit, a completion reported outside any workflow | `pnpm josh test:e2e`, run by **you**, output read by you. |
|
|
190
161
|
|
|
191
|
-
**Never ask the user to run it
|
|
192
|
-
somebody being at the keyboard; that dependency is what made a `fullrun` chain stall at its last
|
|
193
|
-
step. And never report completion on an E2E result nobody read — an unread result is not a pass.
|
|
162
|
+
**Never ask the user to run it**, in either row, and never report completion on an unread E2E result — unread is not a pass.
|
|
194
163
|
|
|
195
|
-
|
|
196
|
-
for the second row: `@joshuafolkken/app-kit` ships `pnpm josh-app verify`, which builds once and
|
|
197
|
-
boots once to run the E2E suite and the DAST scan against that one server — and which delegates E2E
|
|
198
|
-
to `josh test:e2e`, so the skip below still applies. **Decide by the command list the installed
|
|
199
|
-
toolkit prints, not by which toolkit is installed and not by a version number written here**: the
|
|
200
|
-
same rule the UI gate uses, and the only one that survives a release.
|
|
164
|
+
Second row: prefer an integrated gate when the installed toolkit's printed command list has one (not by toolkit or version) — `@joshuafolkken/app-kit`'s `pnpm josh-app verify` builds and boots once for E2E and the DAST scan, delegating E2E to `josh test:e2e` (so the skip below applies).
|
|
201
165
|
|
|
202
166
|
### A project with no E2E suite
|
|
203
167
|
|
|
204
|
-
`pnpm josh test:e2e` runs through `scripts/test/test-e2e-guard.ts`, which
|
|
205
|
-
the reason, when `@playwright/test` is not installed or no `*.e2e.{ts,js}` file exists. That skip is
|
|
206
|
-
a **defined** outcome, not an accident: a project with no E2E suite has nothing for the gate to
|
|
207
|
-
read, and the run says so out loud. **A skip you did not see printed is not one** — it is an unread
|
|
208
|
-
result, and it falls under the previous paragraph.
|
|
168
|
+
`pnpm josh test:e2e` runs through `scripts/test/test-e2e-guard.ts`, which skips and exits 0, naming the reason, when `@playwright/test` is not installed or no `*.e2e.{ts,js}` file exists. That is a defined outcome; **a skip you did not see printed is an unread result**.
|
|
209
169
|
|
|
210
|
-
|
|
211
|
-
`templates/workflows/ci.yml` enables the `e2e` job when any `*.e2e.{ts,js}` exists outside
|
|
212
|
-
`node_modules` — the guard's own glob — whatever the config's filename;
|
|
213
|
-
`scripts/ci/ci-yml-e2e-detect.test.ts` pins the agreement. CI enables the job on the spec alone, even
|
|
214
|
-
where the guard skips for a missing `@playwright/test`, so a spec that cannot run ends red rather than
|
|
215
|
-
silently green.
|
|
170
|
+
CI uses the same rule: the `e2e-detect` job in `templates/workflows/ci.yml` enables the `e2e` job when any `*.e2e.{ts,js}` exists outside `node_modules` (the guard's glob), whatever the config's filename — on the spec alone, so a spec that cannot run ends red, not silently green. `scripts/ci/ci-yml-e2e-detect.test.ts` pins the agreement.
|
|
216
171
|
|
|
217
172
|
### The gate is not weakened
|
|
218
173
|
|
|
219
|
-
- **A non-passing check keeps the merge closed.** `evaluate_pr_state` returns `success` only when
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
ends the wait on the poll that sees it, by name (`PR checks failed (failed checks: E2E).`).
|
|
223
|
-
- **Filtering, narrowing or reinterpreting E2E output to get past it is a workaround** —
|
|
224
|
-
`CLAUDE.md` → "Cross-package problems"; reporting the filtered result honestly does not make it
|
|
225
|
-
compliant.
|
|
226
|
-
- **`E2E` stays off the default required-check list**; a project opts in through
|
|
227
|
-
`JOSH_REQUIRED_CHECKS`. `scripts/gh/git-pr-checks-e2e-gate.test.ts` pins both properties.
|
|
174
|
+
- **A non-passing check keeps the merge closed.** `evaluate_pr_state` returns `success` only when GitHub reports the pull request `CLEAN` and every required check passes; the only exception is a non-passing set made entirely of CodeRabbit checks (the temporary kit#753 policy). A failing check ends the wait on the poll that sees it, by name (`PR checks failed (failed checks: E2E).`).
|
|
175
|
+
- **Filtering, narrowing or reinterpreting E2E output to get past it is a workaround** (`CLAUDE.md` → "Cross-package problems"), however honestly reported.
|
|
176
|
+
- `E2E` stays off the default required-check list; a project opts in through `JOSH_REQUIRED_CHECKS`. `scripts/gh/git-pr-checks-e2e-gate.test.ts` pins both properties.
|
|
@@ -65,8 +65,7 @@ function declared_toolkits(project_root: string): ReadonlyArray<string> {
|
|
|
65
65
|
// declares one, so a toolkit shipping several CLIs still resolves to one deterministically.
|
|
66
66
|
function pick_bin_name(package_name: string, bin: string | Record<string, string>): string {
|
|
67
67
|
const unscoped = path.basename(package_name)
|
|
68
|
-
if (typeof bin === 'string') return unscoped
|
|
69
|
-
if (bin[unscoped] !== undefined) return unscoped
|
|
68
|
+
if (typeof bin === 'string' || bin[unscoped] !== undefined) return unscoped
|
|
70
69
|
|
|
71
70
|
return Object.keys(bin)[0] ?? unscoped
|
|
72
71
|
}
|
|
@@ -99,8 +98,7 @@ function has_runnable_cli(project_root: string, bin_name: string): boolean {
|
|
|
99
98
|
// spawning it would fail the sync step rather than say what is wrong.
|
|
100
99
|
function resolve_toolkit(project_root: string, package_name: string): Release | undefined {
|
|
101
100
|
const bin_name = read_bin_name(project_root, package_name)
|
|
102
|
-
if (bin_name === undefined) return undefined
|
|
103
|
-
if (!has_runnable_cli(project_root, bin_name)) return undefined
|
|
101
|
+
if (bin_name === undefined || !has_runnable_cli(project_root, bin_name)) return undefined
|
|
104
102
|
|
|
105
103
|
return { package_name, version: LATEST_VERSION, bin_name }
|
|
106
104
|
}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
type AgentEnvironment,
|
|
5
5
|
type AgentProfile,
|
|
6
6
|
type AgentRole,
|
|
7
|
+
type ProfileResult,
|
|
7
8
|
} from './agent-role-profile'
|
|
8
9
|
import { claude_agent_argv } from './claude-agent-argv'
|
|
9
10
|
import { codex_agent_argv } from './codex-agent-argv'
|
|
@@ -11,6 +12,8 @@ import { codex_agent_argv } from './codex-agent-argv'
|
|
|
11
12
|
interface AgentArgv {
|
|
12
13
|
command: string
|
|
13
14
|
args: ReadonlyArray<string>
|
|
15
|
+
// The invocation is the last element, so `detached-launch.ts` lets that one break lines.
|
|
16
|
+
ends_with_prompt: true
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
type AgentArgvResult =
|
|
@@ -112,22 +115,35 @@ function resume_argv(
|
|
|
112
115
|
: with_profile_in(invocation, resolved.profile, cwd)
|
|
113
116
|
}
|
|
114
117
|
|
|
118
|
+
function profiled_in(invocation: string, resolved: ProfileResult, cwd: string): AgentArgvResult {
|
|
119
|
+
return resolved.kind === 'rejected'
|
|
120
|
+
? resolved
|
|
121
|
+
: with_profile_in(invocation, resolved.profile, cwd)
|
|
122
|
+
}
|
|
123
|
+
|
|
115
124
|
function resolve_in(
|
|
116
125
|
invocation: string,
|
|
117
126
|
role: AgentRole,
|
|
118
127
|
cwd: string,
|
|
119
128
|
environment: AgentEnvironment = process.env,
|
|
120
129
|
): AgentArgvResult {
|
|
121
|
-
|
|
130
|
+
return profiled_in(invocation, agent_role_profile.resolve(role, environment), cwd)
|
|
131
|
+
}
|
|
122
132
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
133
|
+
// The reviewer for one review round, so a verification pass runs on its own profile.
|
|
134
|
+
function review_in(
|
|
135
|
+
invocation: string,
|
|
136
|
+
round: number,
|
|
137
|
+
cwd: string,
|
|
138
|
+
environment: AgentEnvironment = process.env,
|
|
139
|
+
): AgentArgvResult {
|
|
140
|
+
return profiled_in(invocation, agent_role_profile.resolve_review(round, environment), cwd)
|
|
126
141
|
}
|
|
127
142
|
|
|
128
143
|
const agent_argv = {
|
|
129
144
|
resolve,
|
|
130
145
|
resolve_in,
|
|
146
|
+
review_in,
|
|
131
147
|
resume_argv,
|
|
132
148
|
with_profile,
|
|
133
149
|
with_profile_in,
|
|
@@ -20,6 +20,28 @@ function read_exit_record(transcript: string): ClaudeResultEvent | undefined {
|
|
|
20
20
|
.findLast((record) => record !== undefined)
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
function event_type(line: string): unknown {
|
|
24
|
+
const value = json_value.parse_or_undefined(line)
|
|
25
|
+
|
|
26
|
+
return typeof value === 'object' && value !== null && 'type' in value ? value.type : undefined
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// The Claude stream events a turn is made of — a Codex transcript's own (`turn.completed`, `error`)
|
|
30
|
+
// are none of these, so one never reads as killed.
|
|
31
|
+
const TURN_TYPES: ReadonlySet<unknown> = new Set(['system', 'assistant', 'user', 'stream_event'])
|
|
32
|
+
|
|
33
|
+
// Whether a Claude session was killed inside a turn: its last event is a turn's, not a `result`. A
|
|
34
|
+
// finished turn ends on a result; a background task can wake the session for another turn, and a
|
|
35
|
+
// process killed before that turn's result leaves other events last.
|
|
36
|
+
function is_mid_turn(transcript: string): boolean {
|
|
37
|
+
const last = transcript
|
|
38
|
+
.split('\n')
|
|
39
|
+
.map((line) => event_type(line))
|
|
40
|
+
.findLast((type) => type !== undefined)
|
|
41
|
+
|
|
42
|
+
return TURN_TYPES.has(last)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const agent_exit_record = { is_mid_turn, read_exit_record }
|
|
24
46
|
|
|
25
47
|
export { agent_exit_record }
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { agent_session_environment } from '#scripts/josh/agent-session-environment'
|
|
2
2
|
import { z } from 'zod'
|
|
3
|
+
import { agent_safe_text } from './agent-safe-text'
|
|
3
4
|
|
|
4
|
-
const FIRST_PRINTABLE_CODE = 0x20
|
|
5
|
-
const DELETE_CODE = 0x7f
|
|
6
|
-
const FIRST_CODE_POINT = 0
|
|
7
5
|
const EMPTY_LENGTH = 0
|
|
8
|
-
const MAX_VALUE_LENGTH = 4096
|
|
9
6
|
|
|
10
7
|
const ROLE_SCHEMA = z.enum(['scheduler', 'worker', 'reviewer'])
|
|
11
8
|
const PROVIDER_SCHEMA = z.enum(['anthropic', 'openai'])
|
|
@@ -32,6 +29,18 @@ type ProfileResult = { kind: 'profile'; profile: AgentProfile } | { kind: 'rejec
|
|
|
32
29
|
type Rejected = Extract<ProfileResult, { kind: 'rejected' }>
|
|
33
30
|
type EffortResult = Rejected | { kind: 'effort'; effort: AgentEffort }
|
|
34
31
|
type ProviderResult = Rejected | { kind: 'provider'; provider: AgentProvider; is_default?: true }
|
|
32
|
+
type OverrideField = 'model' | 'effort'
|
|
33
|
+
|
|
34
|
+
interface Override {
|
|
35
|
+
key: string
|
|
36
|
+
value: string | undefined
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// What a resolution is keyed on beyond the role: the run phase (effort) and the review round (profile).
|
|
40
|
+
interface AgentStage {
|
|
41
|
+
phase?: string | undefined
|
|
42
|
+
round?: number
|
|
43
|
+
}
|
|
35
44
|
|
|
36
45
|
const SCHEDULER: AgentRole = 'scheduler'
|
|
37
46
|
const WORKER: AgentRole = 'worker'
|
|
@@ -72,6 +81,22 @@ const OPENAI_PROFILES: Readonly<Record<AgentRole, AgentProfile>> = {
|
|
|
72
81
|
|
|
73
82
|
const PROVIDER_PROFILES = { anthropic: DEFAULT_PROFILES, openai: OPENAI_PROFILES }
|
|
74
83
|
|
|
84
|
+
// **The verification pass resolves a profile of its own** (joshuafolkken/kit#3926). A round after the
|
|
85
|
+
// first only checks that the first round's findings were fixed, so its model and effort are set apart:
|
|
86
|
+
// its own keys win, then the reviewer's, then its own default.
|
|
87
|
+
const FIRST_REVIEW_ROUND = 1
|
|
88
|
+
// Sonnet holds the verification pass by the eval criterion: on every `verification-fix-*` scenario its
|
|
89
|
+
// pass/fail equals Opus's (100% agreement), and Opus passes all 4.
|
|
90
|
+
const VERIFICATION_MODEL = 'claude-sonnet-5-5'
|
|
91
|
+
const VERIFICATION_PROFILES: Readonly<Record<AgentProvider, AgentProfile>> = {
|
|
92
|
+
anthropic: { ...DEFAULT_PROFILES.reviewer, model: VERIFICATION_MODEL },
|
|
93
|
+
openai: OPENAI_PROFILES.reviewer,
|
|
94
|
+
}
|
|
95
|
+
const VERIFICATION_KEYS = {
|
|
96
|
+
model: 'JOSH_REVIEWER_VERIFICATION_MODEL',
|
|
97
|
+
effort: 'JOSH_REVIEWER_VERIFICATION_EFFORT',
|
|
98
|
+
}
|
|
99
|
+
|
|
75
100
|
// **Effort as a function of the run phase, not the role alone**. For the first
|
|
76
101
|
// merge only the mechanical ship/bookkeeping region is lowered: the pre-gate resume drives the gate,
|
|
77
102
|
// commit, PR and merge, applying fixes the gate has already named — work delegation.md calls the opposite
|
|
@@ -93,19 +118,7 @@ const ENV_KEYS: Readonly<Record<AgentRole, { model: string; effort: string }>> =
|
|
|
93
118
|
|
|
94
119
|
const LEGACY_WORKER_KEYS = { model: 'JOSH_LANE_MODEL', effort: 'JOSH_LANE_EFFORT' }
|
|
95
120
|
|
|
96
|
-
|
|
97
|
-
const code = character.codePointAt(FIRST_CODE_POINT) ?? FIRST_PRINTABLE_CODE
|
|
98
|
-
|
|
99
|
-
return code < FIRST_PRINTABLE_CODE || code === DELETE_CODE
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function is_safe_value(value: string): boolean {
|
|
103
|
-
if (value.length === EMPTY_LENGTH || value.length > MAX_VALUE_LENGTH) return false
|
|
104
|
-
|
|
105
|
-
for (const character of value) if (is_control_character(character)) return false
|
|
106
|
-
|
|
107
|
-
return true
|
|
108
|
-
}
|
|
121
|
+
const { MAX_VALUE_LENGTH, is_safe_prompt, is_safe_value, printable } = agent_safe_text
|
|
109
122
|
|
|
110
123
|
function trimmed(value: string | undefined): string | undefined {
|
|
111
124
|
const result = value?.trim()
|
|
@@ -113,44 +126,53 @@ function trimmed(value: string | undefined): string | undefined {
|
|
|
113
126
|
return result === undefined || result.length === EMPTY_LENGTH ? undefined : result
|
|
114
127
|
}
|
|
115
128
|
|
|
116
|
-
function
|
|
129
|
+
function is_verification(role: AgentRole, round: number | undefined): boolean {
|
|
130
|
+
return role === REVIEWER && round !== undefined && round > FIRST_REVIEW_ROUND
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// The keys an override is read from, first match wins: a worker falls back to its legacy lane keys, and
|
|
134
|
+
// a verification round reads its own keys ahead of the reviewer's.
|
|
135
|
+
function override_keys(
|
|
117
136
|
role: AgentRole,
|
|
118
|
-
field:
|
|
119
|
-
|
|
120
|
-
):
|
|
137
|
+
field: OverrideField,
|
|
138
|
+
round: number | undefined,
|
|
139
|
+
): readonly [string, ...Array<string>] {
|
|
121
140
|
const key = ENV_KEYS[role][field]
|
|
122
|
-
|
|
123
|
-
if (value !== undefined) return { key, value }
|
|
124
|
-
if (role !== WORKER) return { key, value: undefined }
|
|
141
|
+
if (role === WORKER) return [key, LEGACY_WORKER_KEYS[field]]
|
|
125
142
|
|
|
126
|
-
|
|
127
|
-
|
|
143
|
+
return is_verification(role, round) ? [VERIFICATION_KEYS[field], key] : [key]
|
|
144
|
+
}
|
|
128
145
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
146
|
+
function override_value(
|
|
147
|
+
role: AgentRole,
|
|
148
|
+
field: OverrideField,
|
|
149
|
+
environment: AgentEnvironment,
|
|
150
|
+
round?: number,
|
|
151
|
+
): Override {
|
|
152
|
+
const keys = override_keys(role, field, round)
|
|
153
|
+
const key = keys.find((candidate) => trimmed(environment[candidate]) !== undefined)
|
|
154
|
+
|
|
155
|
+
return key === undefined
|
|
156
|
+
? { key: keys[0], value: undefined }
|
|
157
|
+
: { key, value: trimmed(environment[key]) }
|
|
132
158
|
}
|
|
133
159
|
|
|
134
160
|
function model_override(
|
|
135
161
|
role: AgentRole,
|
|
136
162
|
environment: AgentEnvironment,
|
|
137
163
|
provider: AgentProvider,
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
164
|
+
round: number | undefined,
|
|
165
|
+
): Override {
|
|
141
166
|
return provider === OPENAI_PROVIDER
|
|
142
|
-
? { key, value: undefined }
|
|
143
|
-
: override_value(role, 'model', environment)
|
|
167
|
+
? { key: ENV_KEYS[role].model, value: undefined }
|
|
168
|
+
: override_value(role, 'model', environment, round)
|
|
144
169
|
}
|
|
145
170
|
|
|
146
171
|
function rejection(key: string, value: string, expected: string): Rejected {
|
|
147
172
|
return { kind: 'rejected', note: `${key}=${value} ${expected}, so the agent was not started` }
|
|
148
173
|
}
|
|
149
174
|
|
|
150
|
-
function resolved_effort(
|
|
151
|
-
override: { key: string; value: string | undefined },
|
|
152
|
-
fallback: AgentEffort,
|
|
153
|
-
): EffortResult {
|
|
175
|
+
function resolved_effort(override: Override, fallback: AgentEffort): EffortResult {
|
|
154
176
|
const parsed = EFFORT_SCHEMA.safeParse(override.value ?? fallback)
|
|
155
177
|
|
|
156
178
|
return parsed.success
|
|
@@ -290,17 +312,27 @@ function with_phase_effort(
|
|
|
290
312
|
return { ...profile, effort: overridden_effort(profile, phase, environment) }
|
|
291
313
|
}
|
|
292
314
|
|
|
293
|
-
function
|
|
315
|
+
function stage_defaults(
|
|
316
|
+
provider: AgentProvider,
|
|
294
317
|
role: AgentRole,
|
|
295
|
-
|
|
296
|
-
|
|
318
|
+
round: number | undefined,
|
|
319
|
+
): AgentProfile {
|
|
320
|
+
return is_verification(role, round)
|
|
321
|
+
? VERIFICATION_PROFILES[provider]
|
|
322
|
+
: PROVIDER_PROFILES[provider][role]
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function resolve_stage(
|
|
326
|
+
role: AgentRole,
|
|
327
|
+
environment: AgentEnvironment,
|
|
328
|
+
stage: AgentStage,
|
|
297
329
|
): ProfileResult {
|
|
298
330
|
const selected = resolve_provider(environment)
|
|
299
331
|
if (selected.kind === 'rejected') return selected
|
|
300
|
-
const defaults =
|
|
301
|
-
const model = model_override(role, environment, selected.provider)
|
|
302
|
-
const effort = override_value(role, 'effort', environment)
|
|
303
|
-
const resolved = resolved_effort(effort, phase_effort(role, phase, defaults.effort))
|
|
332
|
+
const defaults = stage_defaults(selected.provider, role, stage.round)
|
|
333
|
+
const model = model_override(role, environment, selected.provider, stage.round)
|
|
334
|
+
const effort = override_value(role, 'effort', environment, stage.round)
|
|
335
|
+
const resolved = resolved_effort(effort, phase_effort(role, stage.phase, defaults.effort))
|
|
304
336
|
|
|
305
337
|
if (resolved.kind === 'rejected') return resolved
|
|
306
338
|
|
|
@@ -314,10 +346,29 @@ function resolve(
|
|
|
314
346
|
return validate(profile, model.key)
|
|
315
347
|
}
|
|
316
348
|
|
|
349
|
+
function resolve(
|
|
350
|
+
role: AgentRole,
|
|
351
|
+
environment: AgentEnvironment = process.env,
|
|
352
|
+
phase?: string,
|
|
353
|
+
): ProfileResult {
|
|
354
|
+
return resolve_stage(role, environment, { phase })
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// The reviewer for one review round: round 1 as `resolve(REVIEWER)` does, any later round the
|
|
358
|
+
// verification pass's own profile.
|
|
359
|
+
function resolve_review(round: number, environment: AgentEnvironment = process.env): ProfileResult {
|
|
360
|
+
return resolve_stage(REVIEWER, environment, { round })
|
|
361
|
+
}
|
|
362
|
+
|
|
317
363
|
function describe(profile: AgentProfile): string {
|
|
318
364
|
return `provider=${profile.provider} role=${profile.role} model=${profile.model} effort=${profile.effort}`
|
|
319
365
|
}
|
|
320
366
|
|
|
367
|
+
// The run-log line a review launch writes, so each round's model is on record beside its round.
|
|
368
|
+
function describe_review(round: number, profile: AgentProfile): string {
|
|
369
|
+
return `review round ${String(round)} · ${describe(profile)}`
|
|
370
|
+
}
|
|
371
|
+
|
|
321
372
|
function parse(value: unknown): AgentProfile | undefined {
|
|
322
373
|
const parsed = PROFILE_SCHEMA.safeParse(value)
|
|
323
374
|
|
|
@@ -335,15 +386,20 @@ const agent_role_profile = {
|
|
|
335
386
|
PROFILE_SCHEMA,
|
|
336
387
|
REVIEWER,
|
|
337
388
|
SCHEDULER,
|
|
389
|
+
VERIFICATION_PROFILES,
|
|
338
390
|
WORKER,
|
|
339
391
|
default_notice,
|
|
340
392
|
describe,
|
|
393
|
+
describe_review,
|
|
341
394
|
handoff_environment,
|
|
342
395
|
has_completion_callback,
|
|
396
|
+
is_safe_prompt,
|
|
343
397
|
is_safe_value,
|
|
344
398
|
parse,
|
|
399
|
+
printable,
|
|
345
400
|
resolve,
|
|
346
401
|
resolve_provider,
|
|
402
|
+
resolve_review,
|
|
347
403
|
warn_of_default,
|
|
348
404
|
with_phase_effort,
|
|
349
405
|
}
|