@joshuafolkken/kit 1.1453.0 → 1.1471.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/skills/dependency-update/SKILL.md +26 -55
- package/.claude/skills/epic-commands/SKILL.md +87 -148
- package/.claude/skills/epic-commands/epic-bundle.md +45 -77
- package/.claude/skills/epic-commands/execution-waves.md +44 -81
- package/.claude/skills/verify-ui/SKILL.md +25 -50
- package/.claude/skills/workflow-commands/background-commands.md +36 -47
- package/.claude/skills/workflow-commands/backlogrun-child.md +72 -85
- package/.claude/skills/workflow-commands/backlogrun-handoff-report.md +1 -1
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +91 -121
- package/.claude/skills/workflow-commands/backlogrun-park.md +73 -117
- package/.claude/skills/workflow-commands/backlogrun-progress.md +7 -9
- package/.claude/skills/workflow-commands/backlogrun-recovery.md +11 -20
- package/.claude/skills/workflow-commands/backlogrun-steps.md +43 -52
- package/.claude/skills/workflow-commands/chain-rule.md +24 -34
- package/.claude/skills/workflow-commands/followup.md +57 -76
- package/.claude/skills/workflow-commands/halfrun.md +2 -1
- package/.claude/skills/workflow-commands/issue-comments.md +43 -60
- package/.claude/skills/workflow-commands/kickoff.md +65 -74
- package/.claude/skills/workflow-commands/observation-filing.md +62 -107
- package/.claude/skills/workflow-commands/observation-ledger.md +56 -111
- package/.claude/skills/workflow-commands/pre-gate-cut.md +89 -138
- package/.claude/skills/workflow-commands/prerequisite.md +10 -15
- package/.claude/skills/workflow-commands/progress-watcher.md +21 -26
- package/.claude/skills/workflow-commands/prrun.md +2 -1
- package/.claude/skills/workflow-commands/retrospective.md +31 -50
- package/.claude/skills/workflow-commands/split-assessment.md +34 -58
- package/.claude/skills/workflow-commands/working-tree-hold.md +54 -71
- package/CLAUDE.md +7 -6
- package/README.md +2 -9
- package/dist/CLAUDE.md +7 -6
- package/dist/hooks/batch-guard.js +6 -6
- package/dist/hooks/{chunk-Y3GKIKGW.js → chunk-43ZHT22P.js} +31 -15
- package/dist/hooks/{chunk-MBJYQPEK.js → chunk-AS74OU3D.js} +24 -8
- package/dist/hooks/{chunk-3C4XRYG6.js → chunk-E7OIKX5Z.js} +17 -12
- package/dist/hooks/{chunk-Z4XI7CZS.js → chunk-EUMACUK2.js} +18 -12
- package/dist/hooks/{chunk-NO4AALP5.js → chunk-H5HVWYXK.js} +4 -4
- package/dist/hooks/{chunk-5C7L6FRC.js → chunk-IIGFYXKC.js} +346 -241
- package/dist/hooks/{chunk-W5ZKGTSY.js → chunk-J47HU5H5.js} +14 -3
- package/dist/hooks/{chunk-OLMO72ON.js → chunk-K4EOONYM.js} +1 -1
- package/dist/hooks/{chunk-LBFYEOXJ.js → chunk-K4LMBQC2.js} +4 -4
- package/dist/hooks/{chunk-D3NJXN5M.js → chunk-KC5AK2GF.js} +8 -6
- package/dist/hooks/chunk-SHGPANFB.js +56 -0
- package/dist/hooks/{chunk-EF5GQ6WS.js → chunk-YHBZAJSV.js} +3 -3
- package/dist/hooks/{chunk-SHYUQOIS.js → chunk-YYPI7SJZ.js} +5 -5
- package/dist/hooks/codex-hook-adapter.js +13 -13
- package/dist/hooks/duplicate-read-guard.js +7 -7
- package/dist/hooks/format-edited.js +3 -3
- package/dist/hooks/inputs.json +251 -247
- package/dist/hooks/investigation-guard.js +7 -7
- package/dist/hooks/pretool-guard.js +12 -12
- package/dist/hooks/session-lang.js +6 -5
- package/dist/hooks/stop-guard.js +75 -18
- package/dist/josh.js +6 -7
- 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/package.json +3 -3
- package/prompts/coding-standards.md +12 -22
- package/prompts/collaboration-workflow/file-edits.md +26 -44
- package/prompts/collaboration-workflow/gh-rest.md +20 -36
- package/prompts/collaboration-workflow/glossary.md +5 -7
- package/prompts/collaboration-workflow/issue-citation.md +7 -13
- package/prompts/collaboration-workflow/issue-template.md +29 -29
- package/prompts/collaboration-workflow/operating-rules.md +35 -44
- 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 +50 -53
- package/prompts/collaboration-workflow/proposal-request.md +1 -9
- package/prompts/collaboration-workflow/report-format.md +32 -49
- package/prompts/collaboration-workflow/residency.md +10 -10
- package/prompts/collaboration-workflow/rule-delivery.md +9 -9
- package/prompts/collaboration-workflow/shell-body.md +11 -21
- 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 +24 -30
- package/prompts/collaboration-workflow.md +24 -25
- package/prompts/refactoring.md +33 -43
- package/prompts/review-rubric.md +84 -129
- package/prompts/review.md +111 -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-exit-record.ts +23 -1
- package/scripts/agent/codex-agent-argv.ts +7 -2
- package/scripts/backlog/backlog-budget-cli.ts +1 -2
- 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 +2 -4
- package/scripts/backlog/backlog-drive-restore.ts +19 -1
- package/scripts/backlog/backlog-drive-seats.ts +27 -0
- package/scripts/backlog/backlog-drive.ts +11 -6
- package/scripts/backlog/backlog-ready.ts +13 -6
- 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/investigation-guard.ts +1 -1
- package/scripts/delegation/investigation-reads.ts +1 -1
- 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 +27 -27
- package/scripts/document/document-byte-check.ts +1 -2
- package/scripts/document/entry-read-budget.ts +2 -2
- package/scripts/epic/epic-audit-rationale.ts +8 -2
- package/scripts/epic/epic-bundle-cli.ts +1 -2
- package/scripts/epic/epic-chains.ts +6 -2
- package/scripts/epic/epic-close-comment.ts +1 -2
- package/scripts/gate/verification-gate.ts +7 -2
- package/scripts/gh/git-pr-ai-review.ts +12 -1
- package/scripts/gh/git-pr-confirmation.ts +11 -7
- package/scripts/gh/git-pr.ts +6 -2
- 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/hooks/format-edited-file.ts +10 -4
- package/scripts/hooks/pre-push-unit.ts +15 -4
- package/scripts/hooks/pretool-guard.ts +23 -29
- package/scripts/hooks/step-zero-notice.ts +3 -2
- package/scripts/init/hook-command-rewrite.ts +6 -2
- package/scripts/init/init-logic-deploy-vps.ts +4 -2
- package/scripts/issue/behavior-change-lint.ts +12 -6
- package/scripts/issue/issue-scout-cli.ts +3 -2
- package/scripts/josh/file-map-stamp.ts +1 -3
- package/scripts/josh/hook-decision.ts +17 -2
- package/scripts/josh/josh-commands-workflow.ts +1 -1
- package/scripts/lane/lane-await.ts +3 -2
- package/scripts/lane/lane-child-invocation.ts +14 -1
- package/scripts/lane/lane-guard-policy.ts +4 -4
- package/scripts/lane/lane-ledger.ts +9 -0
- package/scripts/lane/lane-load.ts +9 -2
- package/scripts/lane/lane-measure-cli.ts +2 -0
- package/scripts/lane/lane-parked.ts +18 -0
- package/scripts/lane/lane-process-load.ts +125 -0
- package/scripts/lane/lane-process-stats.ts +93 -0
- package/scripts/lane/lane-registry.ts +8 -3
- package/scripts/lane/lane-session-broker.ts +29 -5
- package/scripts/lane/lane-spent.ts +14 -3
- package/scripts/lane/lane-stats.ts +1 -0
- package/scripts/managed-marker/managed-marker-logic.ts +1 -2
- package/scripts/notify/confirmation-options.ts +59 -0
- package/scripts/notify/notify-opt-in.ts +33 -0
- package/scripts/notify/telegram-notify.ts +25 -8
- package/scripts/notify/telegram-test-logic.ts +33 -1
- package/scripts/notify/telegram-test.ts +4 -2
- 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-lint.ts +7 -4
- package/scripts/review/review-record-cli.ts +6 -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 +3 -6
- package/scripts/rules/git-force.ts +5 -9
- package/scripts/rules/implementation-cut.ts +37 -35
- package/scripts/rules/index-guard.ts +4 -7
- package/scripts/rules/issue-comments.ts +5 -15
- 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/oracle-consulted.ts +4 -5
- package/scripts/rules/piped-verification.ts +81 -31
- 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/run-tail-rule.ts +6 -22
- package/scripts/rules/shell-body-trigger.ts +7 -8
- package/scripts/rules/shell-segments.ts +7 -0
- package/scripts/rules/stop-rules.ts +17 -29
- 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-github.ts +11 -3
- package/scripts/run/board/run-board-idle.ts +31 -0
- package/scripts/run/board/run-board-read.ts +9 -12
- package/scripts/run/board/run-board-reason.ts +7 -3
- package/scripts/run/board/run-board-state.ts +4 -0
- package/scripts/run/board/run-board-status.ts +18 -3
- package/scripts/run/board/run-board-thanks.ts +7 -2
- package/scripts/run/board/run-board-tick.ts +32 -7
- 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 +15 -20
- 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 +13 -0
- package/scripts/run/cut/run-cut.ts +10 -1
- package/scripts/run/entry/run-entry-stop.ts +6 -3
- package/scripts/run/hold/run-hold-cli.ts +14 -3
- package/scripts/run/hold/run-hold.ts +6 -3
- package/scripts/run/merge/run-merge-cli.ts +30 -7
- package/scripts/run/merge/run-merge-ship.ts +59 -1
- package/scripts/run/merge/run-merge.ts +3 -2
- package/scripts/run/run-ending.ts +31 -3
- package/scripts/run/run-parent-cut-hook.ts +5 -10
- package/scripts/run/run-preflight.ts +1 -2
- package/scripts/run/run-stop-notify.ts +3 -2
- package/scripts/run/run-watcher-guard.ts +6 -3
- package/scripts/run/ship/run-ship-review-steps.ts +13 -4
- package/scripts/run/ship/run-ship-review.ts +15 -2
- 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/unit-worker-share.ts +3 -2
- 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 +10 -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/dist/hooks/chunk-UT7YC3N2.js +0 -56
|
@@ -53,8 +53,9 @@ const INDEX_ASSERTION_NAME = 'no-direct-git-index-mutation'
|
|
|
53
53
|
// a tool result, or a call to any tool but `Bash`. Read through `json_value.is_record` so a malformed
|
|
54
54
|
// input is treated as absent rather than throwing mid-scan.
|
|
55
55
|
function command_of(block: Block): string | undefined {
|
|
56
|
-
if (block.type !== TOOL_USE || block.name !== BASH_TOOL)
|
|
57
|
-
|
|
56
|
+
if (block.type !== TOOL_USE || block.name !== BASH_TOOL || !json_value.is_record(block.input)) {
|
|
57
|
+
return undefined
|
|
58
|
+
}
|
|
58
59
|
|
|
59
60
|
const command = block.input[COMMAND_KEY]
|
|
60
61
|
|
|
@@ -56,8 +56,10 @@ function measurement_from(
|
|
|
56
56
|
return parsed.success ? [parsed.data.payload.info] : []
|
|
57
57
|
})
|
|
58
58
|
const session = metadata.find((entry) => entry.payload.id === thread_id)
|
|
59
|
-
|
|
60
|
-
if (!same_project(session.payload.cwd, target))
|
|
59
|
+
|
|
60
|
+
if (session === undefined || usages.length === 0 || !same_project(session.payload.cwd, target)) {
|
|
61
|
+
return undefined
|
|
62
|
+
}
|
|
61
63
|
|
|
62
64
|
return { billed_input_per_request: billed_per_request(usages) }
|
|
63
65
|
}
|
|
@@ -56,8 +56,7 @@ interface RunContext {
|
|
|
56
56
|
// `--over=` does not arrive as "hand off after any request" and stop an unattended run after its
|
|
57
57
|
// first child.
|
|
58
58
|
function to_threshold(raw: string | undefined): number | undefined {
|
|
59
|
-
if (raw === undefined) return undefined
|
|
60
|
-
if (raw.trim() === '') return undefined
|
|
59
|
+
if (raw === undefined || raw.trim() === '') return undefined
|
|
61
60
|
|
|
62
61
|
const parsed = Number(raw)
|
|
63
62
|
|
|
@@ -69,7 +69,7 @@ function should_notify(
|
|
|
69
69
|
// A dispatched lane child ends its turn on a denial, so
|
|
70
70
|
// the same guidance is carried without a `permissionDecision`: the read proceeds and the child is nudged
|
|
71
71
|
// to delegate rather than killed. It carries no ⛔, so a person watching reads it as advice.
|
|
72
|
-
const NOTICE = `💡 investigation: files read and not edited, or search turns, since the last delegated unit reached the threshold
|
|
72
|
+
const NOTICE = `💡 investigation: files read and not edited, or search turns, since the last delegated unit reached the threshold; the read proceeds. Run \`pnpm josh delegate investigation\`, then dispatch the \`${investigation_reads.INVESTIGATOR_AGENT}\` agent (\`kit:${investigation_reads.INVESTIGATOR_AGENT}\` in a consumer); it returns \`file:line\` citations, never the file text. Keep main-line reads to files you will edit.`
|
|
73
73
|
|
|
74
74
|
// The notice as it reaches the model: the base guidance with the concrete unedited reads named, so a
|
|
75
75
|
// lane child is shown *which* reads to send to a unit rather than only told to delegate. An empty
|
|
@@ -42,7 +42,7 @@ const SEARCH_LABELS: ReadonlySet<string> = new Set(
|
|
|
42
42
|
const INVESTIGATOR_AGENT = 'investigator'
|
|
43
43
|
|
|
44
44
|
// The deny reason is the only text that reaches the model, so it names the count, command and return shape.
|
|
45
|
-
const REASON = `⛔ investigation: ${String(delegation_policy.INVESTIGATION_FILE_THRESHOLD)} files read and not edited, or ${String(delegation_policy.INVESTIGATION_SEARCH_TURN_THRESHOLD)} search turns, since the last delegated unit
|
|
45
|
+
const REASON = `⛔ investigation: ${String(delegation_policy.INVESTIGATION_FILE_THRESHOLD)} files read and not edited, or ${String(delegation_policy.INVESTIGATION_SEARCH_TURN_THRESHOLD)} search turns, since the last delegated unit. Run \`pnpm josh delegate investigation\`, then dispatch the \`${INVESTIGATOR_AGENT}\` agent (\`kit:${INVESTIGATOR_AGENT}\` in a consumer); it returns \`file:line\` citations, never the file text. Keep main-line reads to files you will edit; every read-only search turn counts. \`.claude/skills/workflow-commands/delegation.md\``
|
|
46
46
|
|
|
47
47
|
interface ReadTally {
|
|
48
48
|
// The files read and not since edited, resolved.
|
|
@@ -138,8 +138,10 @@ function resolve_current_owner(repository_path: string): string | undefined {
|
|
|
138
138
|
// all — the very failure this function exists to close, moved somewhere else.
|
|
139
139
|
function main_worktree(repository_path: string): string {
|
|
140
140
|
const git_directory = resolve_git_directory(repository_path)
|
|
141
|
-
|
|
142
|
-
if (path.basename(git_directory) !== GIT_DIRECTORY)
|
|
141
|
+
|
|
142
|
+
if (git_directory === undefined || path.basename(git_directory) !== GIT_DIRECTORY) {
|
|
143
|
+
return repository_path
|
|
144
|
+
}
|
|
143
145
|
|
|
144
146
|
return path.dirname(git_directory)
|
|
145
147
|
}
|
|
@@ -68,8 +68,7 @@ function parse_scp_like(url: string): RepoIdentity | undefined {
|
|
|
68
68
|
const user_end = url.indexOf(USER_SEPARATOR)
|
|
69
69
|
const authority = user_end === NOT_FOUND ? url : url.slice(user_end + 1)
|
|
70
70
|
const host_end = authority.indexOf(HOST_SEPARATOR)
|
|
71
|
-
if (host_end === NOT_FOUND) return undefined
|
|
72
|
-
if (!is_github_ssh_host(authority.slice(0, host_end))) return undefined
|
|
71
|
+
if (host_end === NOT_FOUND || !is_github_ssh_host(authority.slice(0, host_end))) return undefined
|
|
73
72
|
|
|
74
73
|
return to_identity(authority.slice(host_end + 1))
|
|
75
74
|
}
|
|
@@ -115,8 +114,7 @@ function origin_section_lines(lines: ReadonlyArray<string>): Array<string> {
|
|
|
115
114
|
// the first separator rather than matched, for the reason `parse_scp_like` is split by hand.
|
|
116
115
|
function url_value(line: string): string | undefined {
|
|
117
116
|
const separator = line.indexOf('=')
|
|
118
|
-
if (separator === NOT_FOUND) return undefined
|
|
119
|
-
if (line.slice(0, separator).trim() !== URL_KEY) return undefined
|
|
117
|
+
if (separator === NOT_FOUND || line.slice(0, separator).trim() !== URL_KEY) return undefined
|
|
120
118
|
|
|
121
119
|
return line.slice(separator + 1).trim()
|
|
122
120
|
}
|
|
@@ -69,9 +69,12 @@ function decide_reclaim(
|
|
|
69
69
|
global_josh: string | undefined,
|
|
70
70
|
shim_content: string,
|
|
71
71
|
): ReclaimDecision {
|
|
72
|
-
|
|
73
|
-
//
|
|
74
|
-
if (path_josh === undefined
|
|
72
|
+
// is_shadowed already implies path_josh is defined; the undefined check narrows the type for the
|
|
73
|
+
// return.
|
|
74
|
+
if (path_josh === undefined || !is_shadowed(path_josh, global_josh)) {
|
|
75
|
+
return { action: 'none', reason: NO_SHADOW_REASON }
|
|
76
|
+
}
|
|
77
|
+
|
|
75
78
|
if (!is_kit_shim(shim_content)) return { action: 'none', reason: NOT_KIT_SHIM_REASON }
|
|
76
79
|
|
|
77
80
|
return { action: 'remove', target: path_josh, reason: RECLAIM_REASON }
|
|
@@ -57,52 +57,52 @@ function block_ceiling(size: number): number {
|
|
|
57
57
|
// a stale entry (a file that no longer exists or that an entry now reads) and on an un-budgeted
|
|
58
58
|
// unreached file, so the definition cannot rot as documents move on or off the execution path.
|
|
59
59
|
const DOCUMENT_BYTE_BUDGET: ReadonlyArray<DocumentBudget> = [
|
|
60
|
-
{ path: '.claude/skills/dependency-update/SKILL.md', bytes:
|
|
61
|
-
{ path: '.claude/skills/epic-commands/SKILL.md', bytes:
|
|
62
|
-
{ path: '.claude/skills/epic-commands/epic-bundle.md', bytes:
|
|
63
|
-
{ path: '.claude/skills/epic-commands/execution-waves.md', bytes:
|
|
64
|
-
{ path: '.claude/skills/verify-ui/SKILL.md', bytes:
|
|
60
|
+
{ path: '.claude/skills/dependency-update/SKILL.md', bytes: 4096 },
|
|
61
|
+
{ path: '.claude/skills/epic-commands/SKILL.md', bytes: 12_288 },
|
|
62
|
+
{ path: '.claude/skills/epic-commands/epic-bundle.md', bytes: 4096 },
|
|
63
|
+
{ path: '.claude/skills/epic-commands/execution-waves.md', bytes: 4096 },
|
|
64
|
+
{ path: '.claude/skills/verify-ui/SKILL.md', bytes: 4096 },
|
|
65
65
|
{ path: '.claude/skills/workflow-commands/backlogrun-handoff-report.md', bytes: 4096 },
|
|
66
66
|
{ path: '.claude/skills/workflow-commands/fullrun-steps.md', bytes: 8192 },
|
|
67
67
|
{ path: '.claude/skills/workflow-commands/into-target.md', bytes: 4096 },
|
|
68
68
|
{ path: '.claude/skills/workflow-commands/issue-comments.md', bytes: 8192 },
|
|
69
69
|
{ path: '.claude/skills/workflow-commands/issue-fold-existing.md', bytes: 4096 },
|
|
70
70
|
{ path: '.claude/skills/workflow-commands/needs-human-review.md', bytes: 4096 },
|
|
71
|
-
{ path: '.claude/skills/workflow-commands/observation-filing.md', bytes:
|
|
72
|
-
{ path: '.claude/skills/workflow-commands/observation-ledger.md', bytes:
|
|
71
|
+
{ path: '.claude/skills/workflow-commands/observation-filing.md', bytes: 8192 },
|
|
72
|
+
{ path: '.claude/skills/workflow-commands/observation-ledger.md', bytes: 8192 },
|
|
73
73
|
{ path: '.claude/skills/workflow-commands/prerequisite.md', bytes: 8192 },
|
|
74
74
|
{ path: '.claude/skills/workflow-commands/target-repository.md', bytes: 4096 },
|
|
75
75
|
{ path: '.claude/skills/workflow-commands/working-tree-hold.md', bytes: 8192 },
|
|
76
|
-
{ path: 'docs/josh-commands-automation.md', bytes:
|
|
77
|
-
{ path: 'docs/josh-commands-run.md', bytes:
|
|
78
|
-
{ path: 'docs/josh-commands-backlog.md', bytes:
|
|
79
|
-
{ path: 'docs/josh-commands.md', bytes:
|
|
76
|
+
{ path: 'docs/josh-commands-automation.md', bytes: 45_056 },
|
|
77
|
+
{ path: 'docs/josh-commands-run.md', bytes: 61_440 },
|
|
78
|
+
{ path: 'docs/josh-commands-backlog.md', bytes: 49_152 },
|
|
79
|
+
{ path: 'docs/josh-commands.md', bytes: 45_056 },
|
|
80
80
|
{ path: 'docs/labels-and-run-states.md', bytes: 8192 },
|
|
81
|
-
{ path: 'prompts/coding-standards.md', bytes:
|
|
82
|
-
{ path: 'prompts/collaboration-workflow.md', bytes:
|
|
83
|
-
{ path: 'prompts/collaboration-workflow/file-edits.md', bytes:
|
|
84
|
-
{ path: 'prompts/collaboration-workflow/gh-rest.md', bytes:
|
|
81
|
+
{ path: 'prompts/coding-standards.md', bytes: 4096 },
|
|
82
|
+
{ path: 'prompts/collaboration-workflow.md', bytes: 4096 },
|
|
83
|
+
{ path: 'prompts/collaboration-workflow/file-edits.md', bytes: 8192 },
|
|
84
|
+
{ path: 'prompts/collaboration-workflow/gh-rest.md', bytes: 4096 },
|
|
85
85
|
{ path: 'prompts/collaboration-workflow/glossary.md', bytes: 4096 },
|
|
86
86
|
{ path: 'prompts/collaboration-workflow/issue-citation.md', bytes: 4096 },
|
|
87
|
-
{ path: 'prompts/collaboration-workflow/issue-template.md', bytes:
|
|
88
|
-
{ path: 'prompts/collaboration-workflow/operating-rules.md', bytes:
|
|
87
|
+
{ path: 'prompts/collaboration-workflow/issue-template.md', bytes: 8192 },
|
|
88
|
+
{ path: 'prompts/collaboration-workflow/operating-rules.md', bytes: 8192 },
|
|
89
89
|
{ path: 'prompts/collaboration-workflow/output-bounds.md', bytes: 4096 },
|
|
90
90
|
{ path: 'prompts/collaboration-workflow/overview.md', bytes: 4096 },
|
|
91
|
-
{ path: 'prompts/collaboration-workflow/plan-comment.md', bytes:
|
|
92
|
-
{ path: 'prompts/collaboration-workflow/principles.md', bytes:
|
|
91
|
+
{ path: 'prompts/collaboration-workflow/plan-comment.md', bytes: 4096 },
|
|
92
|
+
{ path: 'prompts/collaboration-workflow/principles.md', bytes: 12_288 },
|
|
93
93
|
{ path: 'prompts/collaboration-workflow/proposal-request.md', bytes: 4096 },
|
|
94
|
-
{ path: 'prompts/collaboration-workflow/report-format.md', bytes:
|
|
94
|
+
{ path: 'prompts/collaboration-workflow/report-format.md', bytes: 12_288 },
|
|
95
95
|
{ path: 'prompts/collaboration-workflow/residency.md', bytes: 8192 },
|
|
96
96
|
{ path: 'prompts/collaboration-workflow/rule-delivery.md', bytes: 4096 },
|
|
97
|
-
{ path: 'prompts/collaboration-workflow/shell-body.md', bytes:
|
|
98
|
-
{ path: 'prompts/collaboration-workflow/turn-batching.md', bytes:
|
|
99
|
-
{ path: 'prompts/collaboration-workflow/upstream-interrupt.md', bytes:
|
|
100
|
-
{ path: 'prompts/collaboration-workflow/wip-cap.md', bytes:
|
|
97
|
+
{ path: 'prompts/collaboration-workflow/shell-body.md', bytes: 4096 },
|
|
98
|
+
{ path: 'prompts/collaboration-workflow/turn-batching.md', bytes: 4096 },
|
|
99
|
+
{ path: 'prompts/collaboration-workflow/upstream-interrupt.md', bytes: 8192 },
|
|
100
|
+
{ path: 'prompts/collaboration-workflow/wip-cap.md', bytes: 8192 },
|
|
101
101
|
{ path: 'prompts/refactoring.md', bytes: 8192 },
|
|
102
|
-
{ path: 'prompts/review-rubric.md', bytes:
|
|
103
|
-
{ path: 'prompts/review.md', bytes:
|
|
102
|
+
{ path: 'prompts/review-rubric.md', bytes: 12_288 },
|
|
103
|
+
{ path: 'prompts/review.md', bytes: 12_288 },
|
|
104
104
|
{ path: 'prompts/sonar-hotspot-handling.md', bytes: 8192 },
|
|
105
|
-
{ path: 'prompts/testing-guide.md', bytes:
|
|
105
|
+
{ path: 'prompts/testing-guide.md', bytes: 12_288 },
|
|
106
106
|
]
|
|
107
107
|
|
|
108
108
|
// The recorded ceiling for one path, or undefined when it carries no budget entry.
|
|
@@ -26,8 +26,7 @@ function ceiling_message(
|
|
|
26
26
|
current_bytes: number,
|
|
27
27
|
recorded_bytes: number | undefined,
|
|
28
28
|
): string | undefined {
|
|
29
|
-
if (recorded_bytes === undefined) return undefined
|
|
30
|
-
if (current_bytes <= recorded_bytes) return undefined
|
|
29
|
+
if (recorded_bytes === undefined || current_bytes <= recorded_bytes) return undefined
|
|
31
30
|
|
|
32
31
|
return document_byte_budget.over_budget_message(relative_path, current_bytes, recorded_bytes)
|
|
33
32
|
}
|
|
@@ -48,8 +48,8 @@ const ENTRY_READ_BUDGET: ReadonlyArray<EntryBudget> = [
|
|
|
48
48
|
{ entry: 'fullrun', bytes: 53_248 },
|
|
49
49
|
{ entry: 'halfrun', bytes: 49_152 },
|
|
50
50
|
{ entry: 'prrun', bytes: 57_344 },
|
|
51
|
-
{ entry: 'backlogrun', bytes:
|
|
52
|
-
{ entry: LANE_CHILD, bytes:
|
|
51
|
+
{ entry: 'backlogrun', bytes: 86_016 },
|
|
52
|
+
{ entry: LANE_CHILD, bytes: 45_056 },
|
|
53
53
|
]
|
|
54
54
|
|
|
55
55
|
function byte_size(root: string, relative_path: string): number {
|
|
@@ -68,9 +68,15 @@ function order_pairs(
|
|
|
68
68
|
return links.flatMap((link) => {
|
|
69
69
|
const blocker = find_child(children, { repo: epic_repo, number: link.blocker })
|
|
70
70
|
const blocked = find_child(children, { repo: epic_repo, number: link.blocked })
|
|
71
|
-
if (blocker === undefined || blocked === undefined) return []
|
|
72
71
|
|
|
73
|
-
if (
|
|
72
|
+
if (
|
|
73
|
+
blocker === undefined ||
|
|
74
|
+
blocked === undefined ||
|
|
75
|
+
!is_open_local(blocker, current_repo) ||
|
|
76
|
+
!is_open_local(blocked, current_repo)
|
|
77
|
+
) {
|
|
78
|
+
return []
|
|
79
|
+
}
|
|
74
80
|
|
|
75
81
|
// eslint-disable-next-line unicorn/no-unnecessary-array-flat-map -- one return type (sonarjs/function-return-type)
|
|
76
82
|
return [{ blocker, blocked }]
|
|
@@ -323,12 +323,11 @@ async function widen_with_referenced(
|
|
|
323
323
|
backlog: FetchedBacklog,
|
|
324
324
|
): Promise<FetchedBacklog> {
|
|
325
325
|
const { context } = backlog
|
|
326
|
-
if (context === undefined) return backlog
|
|
327
326
|
// An issue an epic already tracks has nothing to bundle — `decide_bundle` answers from that alone
|
|
328
327
|
// and never looks at the candidates. Widening first spends a request per reference on a verdict
|
|
329
328
|
// that ignores them, and prints "⚠ Could not read #891." above "Already in an epic", which reads
|
|
330
329
|
// as a gap in an answer the read was never part of.
|
|
331
|
-
if (subject.epic !== undefined) return backlog
|
|
330
|
+
if (context === undefined || subject.epic !== undefined) return backlog
|
|
332
331
|
const known = new Set(backlog.issues.map((issue) => issue.number))
|
|
333
332
|
const found = await epic_bundle_referenced.referenced_candidates(subject, known, context)
|
|
334
333
|
if (found === undefined) return backlog
|
|
@@ -190,8 +190,12 @@ function find_position_ambiguity(
|
|
|
190
190
|
chains: Chains,
|
|
191
191
|
position: InsertPosition | undefined,
|
|
192
192
|
): string | undefined {
|
|
193
|
-
if (
|
|
194
|
-
|
|
193
|
+
if (
|
|
194
|
+
position?.kind !== 'before' ||
|
|
195
|
+
chains_containing(chains, position.target).length < AMBIGUOUS_MATCH_COUNT
|
|
196
|
+
) {
|
|
197
|
+
return undefined
|
|
198
|
+
}
|
|
195
199
|
|
|
196
200
|
return to_ambiguous_position_error(position)
|
|
197
201
|
}
|
|
@@ -66,8 +66,7 @@ function marker_children(line: string): Array<string> | undefined {
|
|
|
66
66
|
if (indent_of(line) >= INDENTED_CODE_SPACES) return undefined
|
|
67
67
|
|
|
68
68
|
const trimmed = line.trim()
|
|
69
|
-
if (!trimmed.startsWith(MARKER_OPEN)) return undefined
|
|
70
|
-
if (!trimmed.endsWith(MARKER_CLOSE)) return undefined
|
|
69
|
+
if (!trimmed.startsWith(MARKER_OPEN) || !trimmed.endsWith(MARKER_CLOSE)) return undefined
|
|
71
70
|
|
|
72
71
|
const listed = trimmed.slice(MARKER_OPEN.length, -MARKER_CLOSE.length)
|
|
73
72
|
|
|
@@ -328,8 +328,13 @@ interface GateOptions {
|
|
|
328
328
|
// `--force` is the caller saying to run regardless. `scoped_green` owns the record and the `JOSH_SCOPED_GREEN`
|
|
329
329
|
// escape hatch, so a person can always get past a record that is wrong about their tree.
|
|
330
330
|
function scoped_precheck_refusal(tree: GateTree, options: GateOptions): string | undefined {
|
|
331
|
-
if (
|
|
332
|
-
|
|
331
|
+
if (
|
|
332
|
+
options.is_scoped_enforced !== true ||
|
|
333
|
+
options.is_forced === true ||
|
|
334
|
+
options.is_unit_included === false
|
|
335
|
+
) {
|
|
336
|
+
return undefined
|
|
337
|
+
}
|
|
333
338
|
|
|
334
339
|
return scoped_green.refusal_for(tree.files, tree.base, options.scoped_sources)
|
|
335
340
|
}
|
|
@@ -89,6 +89,10 @@ async function fetch_review_comments(
|
|
|
89
89
|
return parsed?.map((comment) => to_review_comment(comment))
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
// The other choice both AI-review stops offer: a person who has looked records why and moves on.
|
|
93
|
+
const IGNORE_ALTERNATIVE =
|
|
94
|
+
'Re-run `pnpm josh followup --ai-review-ignore-reason "<reason>"` if the gate does not apply'
|
|
95
|
+
|
|
92
96
|
async function handle_blockers(input: {
|
|
93
97
|
branch_name: string
|
|
94
98
|
ignore_reason: string | undefined
|
|
@@ -105,7 +109,12 @@ async function handle_blockers(input: {
|
|
|
105
109
|
return
|
|
106
110
|
}
|
|
107
111
|
|
|
108
|
-
await git_pr_confirmation.notify_confirmation({
|
|
112
|
+
await git_pr_confirmation.notify_confirmation({
|
|
113
|
+
context: input.context,
|
|
114
|
+
body,
|
|
115
|
+
recommended: 'Fix the findings and re-run `pnpm josh followup`',
|
|
116
|
+
alternatives: [IGNORE_ALTERNATIVE],
|
|
117
|
+
})
|
|
109
118
|
|
|
110
119
|
throw new Error(body)
|
|
111
120
|
}
|
|
@@ -140,6 +149,8 @@ async function handle_unreadable_comments(input: {
|
|
|
140
149
|
await git_pr_confirmation.notify_confirmation({
|
|
141
150
|
context: input.context,
|
|
142
151
|
body: UNREADABLE_COMMENTS_BODY,
|
|
152
|
+
recommended: 'Re-run `pnpm josh followup` once the comments can be read',
|
|
153
|
+
alternatives: [IGNORE_ALTERNATIVE],
|
|
143
154
|
})
|
|
144
155
|
|
|
145
156
|
throw new Error(UNREADABLE_COMMENTS_BODY)
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
confirmation_options,
|
|
3
|
+
type ConfirmationOptions,
|
|
4
|
+
} from '#scripts/notify/confirmation-options'
|
|
1
5
|
import { telegram_notify, type TelegramSendInput } from '#scripts/notify/telegram-notify'
|
|
2
6
|
|
|
3
7
|
// What to type when the confirmation notification itself could not be delivered.
|
|
@@ -5,7 +9,7 @@ import { telegram_notify, type TelegramSendInput } from '#scripts/notify/telegra
|
|
|
5
9
|
// commands and a backtick inside shell double quotes is evaluated before the command runs.
|
|
6
10
|
const CONFIRMATION_RECOVERY =
|
|
7
11
|
'send it by hand — `pnpm josh notify --task-type confirmation --issue-url <url> ' +
|
|
8
|
-
'--body-file <path
|
|
12
|
+
'--body-file <path> --recommended "<choice>"`'
|
|
9
13
|
|
|
10
14
|
interface TelegramContext {
|
|
11
15
|
repo_name: string | undefined
|
|
@@ -14,15 +18,18 @@ interface TelegramContext {
|
|
|
14
18
|
pr_url: string | undefined
|
|
15
19
|
}
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
interface ConfirmationInput extends ConfirmationOptions {
|
|
18
22
|
context: TelegramContext
|
|
19
23
|
body: string
|
|
20
|
-
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function build_confirmation_input(input: ConfirmationInput): TelegramSendInput {
|
|
21
27
|
return {
|
|
22
28
|
task_type: 'confirmation',
|
|
23
29
|
repo_name: input.context.repo_name,
|
|
24
30
|
issue_title: input.context.issue_title,
|
|
25
31
|
body: input.body,
|
|
32
|
+
choices: confirmation_options.block(input),
|
|
26
33
|
issue_url: input.context.issue_url,
|
|
27
34
|
pr_url: input.context.pr_url,
|
|
28
35
|
}
|
|
@@ -34,10 +41,7 @@ function build_confirmation_input(input: {
|
|
|
34
41
|
//
|
|
35
42
|
// Unlike the completion notification, this one *has* a recovery: a `confirmation` is exactly what
|
|
36
43
|
// `josh notify` is for, and the run stops here anyway, so re-sending it by hand loses nothing.
|
|
37
|
-
async function notify_confirmation(input: {
|
|
38
|
-
context: TelegramContext
|
|
39
|
-
body: string
|
|
40
|
-
}): Promise<void> {
|
|
44
|
+
async function notify_confirmation(input: ConfirmationInput): Promise<void> {
|
|
41
45
|
await telegram_notify.send_or_report(build_confirmation_input(input), CONFIRMATION_RECOVERY)
|
|
42
46
|
}
|
|
43
47
|
|
package/scripts/gh/git-pr.ts
CHANGED
|
@@ -173,8 +173,12 @@ function build_body(issue_info: IssueInfo, extra_body?: string): string {
|
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
async function existing_pr_label(branch_name: string): Promise<ReleaseClassification | undefined> {
|
|
176
|
-
if (
|
|
177
|
-
|
|
176
|
+
if (
|
|
177
|
+
!(await git_gh_command.pr_exists(branch_name)) ||
|
|
178
|
+
is_pr_state_merged(await get_pr_state(branch_name))
|
|
179
|
+
) {
|
|
180
|
+
return undefined
|
|
181
|
+
}
|
|
178
182
|
|
|
179
183
|
return await git_gh_command.pr_get_classification(branch_name)
|
|
180
184
|
}
|
|
@@ -88,9 +88,9 @@ function select_linked(found: ReadonlyArray<string>): string | undefined {
|
|
|
88
88
|
const own_directory = found[OWN_GIT_DIRECTORY]
|
|
89
89
|
const common = found[COMMON_GIT_DIRECTORY]
|
|
90
90
|
|
|
91
|
-
return own_directory
|
|
92
|
-
?
|
|
93
|
-
:
|
|
91
|
+
return own_directory === undefined || common === undefined || own_directory === common
|
|
92
|
+
? undefined
|
|
93
|
+
: common
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
function resolve(cwd: string): string | undefined {
|
|
@@ -9,19 +9,24 @@ const REFS_REMOTES_ORIGIN_PREFIX = 'refs/remotes/origin/'
|
|
|
9
9
|
const NAME_ONLY_FLAG = '--name-only'
|
|
10
10
|
const DEFAULT_BRANCH_FALLBACK = 'main'
|
|
11
11
|
|
|
12
|
+
const DEFAULT_BRANCH_ARGUMENTS = ['symbolic-ref', 'refs/remotes/origin/HEAD'] as const
|
|
13
|
+
|
|
14
|
+
// The default branch `DEFAULT_BRANCH_ARGUMENTS` names, or the fallback for a missing or unexpected
|
|
15
|
+
// answer — shared with the synchronous read the implementation-cut guard takes it with.
|
|
16
|
+
function default_branch_of(output: string | undefined): string {
|
|
17
|
+
const trimmed = output?.trim() ?? ''
|
|
18
|
+
|
|
19
|
+
if (!trimmed.startsWith(REFS_REMOTES_ORIGIN_PREFIX)) return DEFAULT_BRANCH_FALLBACK
|
|
20
|
+
|
|
21
|
+
return trimmed.slice(REFS_REMOTES_ORIGIN_PREFIX.length)
|
|
22
|
+
}
|
|
23
|
+
|
|
12
24
|
async function get_default_branch(): Promise<string> {
|
|
13
25
|
try {
|
|
14
|
-
|
|
15
|
-
const trimmed = output.trim()
|
|
16
|
-
|
|
17
|
-
if (trimmed.startsWith(REFS_REMOTES_ORIGIN_PREFIX)) {
|
|
18
|
-
return trimmed.slice(REFS_REMOTES_ORIGIN_PREFIX.length)
|
|
19
|
-
}
|
|
26
|
+
return default_branch_of(await git_spawn.read([...DEFAULT_BRANCH_ARGUMENTS]))
|
|
20
27
|
} catch {
|
|
21
|
-
|
|
28
|
+
return DEFAULT_BRANCH_FALLBACK
|
|
22
29
|
}
|
|
23
|
-
|
|
24
|
-
return DEFAULT_BRANCH_FALLBACK
|
|
25
30
|
}
|
|
26
31
|
|
|
27
32
|
const MERGE_BASE_COMMAND = 'merge-base'
|
|
@@ -210,8 +215,10 @@ async function untracked_names(): Promise<string> {
|
|
|
210
215
|
}
|
|
211
216
|
|
|
212
217
|
const git_diff_reads = {
|
|
218
|
+
DEFAULT_BRANCH_ARGUMENTS,
|
|
213
219
|
change_base,
|
|
214
220
|
change_base_commit,
|
|
221
|
+
default_branch_of,
|
|
215
222
|
default_branch_reference,
|
|
216
223
|
diff_cached_names,
|
|
217
224
|
diff_main,
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
// Extract the exit code from a thrown execa error. Returns the numeric exit code,
|
|
2
2
|
// or undefined when the process was terminated by a signal or failed to spawn.
|
|
3
3
|
function get_exit_code(error: unknown): number | undefined {
|
|
4
|
-
if (typeof error !== 'object' || error === null) return undefined
|
|
5
|
-
if (!('exitCode' in error)) return undefined
|
|
4
|
+
if (typeof error !== 'object' || error === null || !('exitCode' in error)) return undefined
|
|
6
5
|
|
|
7
6
|
const { exitCode: exit_code } = error
|
|
8
7
|
|
|
@@ -251,8 +251,13 @@ function plan_daemon_restart(
|
|
|
251
251
|
): ReadonlyArray<FormatCommand> {
|
|
252
252
|
const relative = relative_to_root(project_root, file_path)
|
|
253
253
|
|
|
254
|
-
if (
|
|
255
|
-
|
|
254
|
+
if (
|
|
255
|
+
relative === undefined ||
|
|
256
|
+
!is_eslint_config_input(relative) ||
|
|
257
|
+
resolve_daemon_cli(ESLINT_DAEMON, project_root) === undefined
|
|
258
|
+
) {
|
|
259
|
+
return []
|
|
260
|
+
}
|
|
256
261
|
|
|
257
262
|
return [{ bin: ESLINT_DAEMON, command_arguments: [DAEMON_RESTART] }]
|
|
258
263
|
}
|
|
@@ -325,8 +330,9 @@ async function run_command(command: FormatCommand, project_root: string): Promis
|
|
|
325
330
|
// something to say. A start failure (non-zero, empty) has already been retried and carries no
|
|
326
331
|
// problems, and prettier's stdout is the list of files it wrote rather than anything to act on.
|
|
327
332
|
function eslint_unfixed(command: FormatCommand, outcome: CommandOutcome): string | undefined {
|
|
328
|
-
if (command.bin !== ESLINT_BIN)
|
|
329
|
-
|
|
333
|
+
if (command.bin !== ESLINT_BIN || outcome.exit_code === 0 || outcome.stdout.length === 0) {
|
|
334
|
+
return undefined
|
|
335
|
+
}
|
|
330
336
|
|
|
331
337
|
return outcome.stdout
|
|
332
338
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { core_budget } from '#scripts/gate/core-budget'
|
|
4
|
+
import { gate_plan } from '#scripts/gate/gate-plan'
|
|
3
5
|
import { gate_skip, type GateReuse } from '#scripts/gate/gate-skip'
|
|
4
6
|
import { gate_tree, type GateTree } from '#scripts/gate/gate-tree'
|
|
5
7
|
import { hook_gate_reuse } from '#scripts/gate/hook-gate-reuse'
|
|
@@ -123,6 +125,18 @@ function format_miss(reason: string): string {
|
|
|
123
125
|
return `↻ running the whole unit suite — the green gate record does not cover this push: ${reason}.`
|
|
124
126
|
}
|
|
125
127
|
|
|
128
|
+
// **The whole suite claims its place in the core budget, the one `josh test:unit` claims.** Without it
|
|
129
|
+
// a push's suite ran beside other lanes' gates off the ledger, so their admission saw its workers only
|
|
130
|
+
// as foreign load (joshuafolkken/kit#3874). Only the miss reserves: a reused record runs nothing, and a
|
|
131
|
+
// claim there could make a push wait out a full ledger for a suite it then skips.
|
|
132
|
+
async function run_reserved_suite(vitest_arguments: ReadonlyArray<string>): Promise<number> {
|
|
133
|
+
return await core_budget.with_command_reservation(
|
|
134
|
+
gate_plan.direct_unit_weight(),
|
|
135
|
+
async () => await test_unit_guard.run_guarded_unit(process.cwd(), vitest_arguments),
|
|
136
|
+
{ memory_mb: gate_plan.direct_unit_memory() },
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
126
140
|
async function run_pre_push_unit(
|
|
127
141
|
extra_arguments: ReadonlyArray<string> = [],
|
|
128
142
|
source?: string,
|
|
@@ -132,10 +146,7 @@ async function run_pre_push_unit(
|
|
|
132
146
|
if ('miss' in reuse) {
|
|
133
147
|
process.stdout.write(`${format_miss(reuse.miss)}\n`)
|
|
134
148
|
|
|
135
|
-
return await
|
|
136
|
-
process.cwd(),
|
|
137
|
-
forwarded_arguments(extra_arguments),
|
|
138
|
-
)
|
|
149
|
+
return await run_reserved_suite(forwarded_arguments(extra_arguments))
|
|
139
150
|
}
|
|
140
151
|
|
|
141
152
|
process.stdout.write(`${format_skip(reuse.stamp.taken_at)}\n`)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { duplicate_read_outcome } from '#scripts/delegation/duplicate-read-guard'
|
|
2
|
-
import {
|
|
2
|
+
import { investigation_outcome } from '#scripts/delegation/investigation-guard'
|
|
3
3
|
import { issue_watch_hook } from '#scripts/issue/issue-watch-hook'
|
|
4
4
|
import { hook_decision, type GuardOutcome } from '#scripts/josh/hook-decision'
|
|
5
5
|
import { lane_phase } from '#scripts/lane/lane-phase'
|
|
@@ -25,35 +25,30 @@ import { step_zero_notice } from './step-zero-notice'
|
|
|
25
25
|
// union matcher (`Bash|Edit|Read|Write|AskUserQuestion`) refuses exactly what the three separate
|
|
26
26
|
// entries did — `AskUserQuestion` is matched for the rule guard's lane-child interactive-ask row,
|
|
27
27
|
// and the batching and investigation guards self-gate away from it. A
|
|
28
|
-
// refusal from any guard wins first; only when none refuses is
|
|
29
|
-
// (the whole-file-write notice) emitted. Each guard still honours its own
|
|
28
|
+
// refusal from any guard wins first; only when none refuses is a guard's non-blocking notice or fault
|
|
29
|
+
// (the whole-file-write notice, a lane child's investigation notice) emitted. Each guard still honours its own
|
|
30
30
|
// switch (`JOSH_BATCH_GUARD` / `JOSH_INVESTIGATION_GUARD` / `JOSH_RULE_GUARD`) internally, so this
|
|
31
31
|
// process needs no switch of its own.
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
// reason is shown first to match the entry order the separate hooks had. When nothing refuses,
|
|
35
|
-
// whatever the batch guard reported (a notice, a fault, or nothing) passes through untouched.
|
|
36
|
-
function combine_outcomes(batch: GuardOutcome, extra_reason: string | undefined): GuardOutcome {
|
|
37
|
-
if (batch.reason !== undefined) return batch
|
|
38
|
-
|
|
39
|
-
if (extra_reason !== undefined) {
|
|
40
|
-
return { reason: extra_reason, notice: undefined, fault: undefined }
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return batch
|
|
44
|
-
}
|
|
33
|
+
const CLEAR: GuardOutcome = { reason: undefined, notice: undefined, fault: undefined }
|
|
45
34
|
|
|
46
35
|
function is_clear(outcome: GuardOutcome): boolean {
|
|
47
36
|
return outcome.reason === undefined && outcome.notice === undefined && outcome.fault === undefined
|
|
48
37
|
}
|
|
49
38
|
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
39
|
+
// Fold the guards' verdicts into one. A refusal wins over everything else, in the entry order the
|
|
40
|
+
// separate hooks had: the outcomes in the order given, then the rule guard's reason. When nothing
|
|
41
|
+
// refuses, the first guard that spoke passes its notice or fault through untouched — so a lane child's
|
|
42
|
+
// `notice`-mode guidance reaches the model rather than only spending its stamp (joshuafolkken/kit#3877).
|
|
43
|
+
function combine_outcomes(
|
|
44
|
+
outcomes: ReadonlyArray<GuardOutcome>,
|
|
45
|
+
rule_reason: string | undefined,
|
|
46
|
+
): GuardOutcome {
|
|
47
|
+
const reason = outcomes.find((outcome) => outcome.reason !== undefined)?.reason ?? rule_reason
|
|
55
48
|
|
|
56
|
-
return { reason: undefined,
|
|
49
|
+
if (reason !== undefined) return { reason, notice: undefined, fault: undefined }
|
|
50
|
+
|
|
51
|
+
return outcomes.find((outcome) => !is_clear(outcome)) ?? CLEAR
|
|
57
52
|
}
|
|
58
53
|
|
|
59
54
|
// The Step 0 reminder is the last word: asked only where every guard stayed
|
|
@@ -70,14 +65,13 @@ function with_step_zero_notice(outcome: GuardOutcome, raw_payload: string): Guar
|
|
|
70
65
|
// once-per-run stamp, so short-circuiting on the first refusal would change which guard is allowed to
|
|
71
66
|
// fire on a later call.
|
|
72
67
|
function guard_outcome(raw_payload: string): GuardOutcome {
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const combined = combine_outcomes(batch, investigation ?? duplicate_refusal ?? rule)
|
|
68
|
+
const outcomes = [
|
|
69
|
+
batch_outcome(raw_payload),
|
|
70
|
+
investigation_outcome(raw_payload),
|
|
71
|
+
duplicate_read_outcome(raw_payload),
|
|
72
|
+
]
|
|
79
73
|
|
|
80
|
-
return
|
|
74
|
+
return combine_outcomes(outcomes, delivered_rules.delivery(raw_payload))
|
|
81
75
|
}
|
|
82
76
|
|
|
83
77
|
// The synchronous verdict the Codex adapter asks for, with the Step 0 reminder as its last layer.
|
|
@@ -191,7 +185,7 @@ function pipefail_envelope(
|
|
|
191
185
|
): string | undefined {
|
|
192
186
|
if (input === undefined || outcome.reason !== undefined) return undefined
|
|
193
187
|
|
|
194
|
-
return hook_decision.
|
|
188
|
+
return hook_decision.flow_rewrite_envelope(
|
|
195
189
|
input,
|
|
196
190
|
rewrite_context(piped_verification.PIPEFAIL_NOTE, outcome),
|
|
197
191
|
)
|