@joshuafolkken/kit 1.1128.0 → 1.1166.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/agents/investigator.md +17 -0
- package/.claude/settings.json +5 -4
- package/.claude/skills/epic-commands/SKILL.md +27 -239
- package/.claude/skills/epic-commands/epic-bundle.md +115 -0
- package/.claude/skills/epic-commands/execution-waves.md +126 -0
- package/.claude/skills/workflow-commands/SKILL.md +48 -194
- package/.claude/skills/workflow-commands/background-commands.md +1 -1
- package/.claude/skills/workflow-commands/backlogrun-child.md +12 -123
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +11 -60
- package/.claude/skills/workflow-commands/backlogrun-park.md +10 -8
- package/.claude/skills/workflow-commands/backlogrun-progress.md +19 -175
- package/.claude/skills/workflow-commands/backlogrun-recovery.md +155 -0
- package/.claude/skills/workflow-commands/backlogrun-steps.md +64 -144
- package/.claude/skills/workflow-commands/backlogrun.md +5 -4
- package/.claude/skills/workflow-commands/chain-rule.md +3 -2
- package/.claude/skills/workflow-commands/delegation.md +7 -3
- package/.claude/skills/workflow-commands/entry-sequence.md +62 -0
- package/.claude/skills/workflow-commands/followup.md +104 -181
- package/.claude/skills/workflow-commands/fullrun-steps.md +8 -9
- package/.claude/skills/workflow-commands/fullrun.md +16 -73
- package/.claude/skills/workflow-commands/halfrun.md +10 -36
- package/.claude/skills/workflow-commands/into-target.md +4 -4
- package/.claude/skills/workflow-commands/issue-comments.md +7 -8
- package/.claude/skills/workflow-commands/issue-scout.md +6 -6
- package/.claude/skills/workflow-commands/kickoff.md +6 -6
- package/.claude/skills/workflow-commands/needs-human-review.md +6 -6
- package/.claude/skills/workflow-commands/observation-filing.md +21 -172
- package/.claude/skills/workflow-commands/observation-ledger.md +150 -0
- package/.claude/skills/workflow-commands/pre-gate-cut.md +33 -93
- package/.claude/skills/workflow-commands/prerequisite.md +6 -6
- package/.claude/skills/workflow-commands/progress-watcher.md +90 -0
- package/.claude/skills/workflow-commands/prrun.md +5 -5
- package/.claude/skills/workflow-commands/retrospective.md +9 -9
- package/.claude/skills/workflow-commands/split-assessment.md +2 -2
- package/.claude/skills/workflow-commands/target-repository.md +2 -2
- package/.claude/skills/workflow-commands/working-tree-hold.md +7 -5
- package/.codex/hooks.json +4 -4
- package/AGENTS.md +1 -6
- package/CLAUDE.md +58 -120
- package/GEMINI.md +1 -6
- package/SECURITY.md +1 -0
- package/dist/CLAUDE.md +58 -120
- package/dist/hooks/batch-guard.js +4 -4
- package/dist/hooks/{chunk-MKVGBQPA.js → chunk-5KTCMCOT.js} +14 -1
- package/dist/hooks/{chunk-NXYMNPUQ.js → chunk-A6P7PN7P.js} +59 -21
- package/dist/hooks/{chunk-77J7IEQM.js → chunk-ANJ2WGFD.js} +1 -1
- package/dist/hooks/{chunk-4JZDP5X5.js → chunk-DYAVAM6S.js} +572 -127
- package/dist/hooks/{chunk-7HVB7SZG.js → chunk-ENA7P5FB.js} +4 -4
- package/dist/hooks/{chunk-M7QKEKWO.js → chunk-GU5MATVE.js} +214 -85
- package/dist/hooks/chunk-GYNGP7HI.js +56 -0
- package/dist/hooks/{chunk-5SD2YM7Z.js → chunk-URJA6XY4.js} +4 -4
- package/dist/hooks/{chunk-XYUIQOW3.js → chunk-V2BTOZHT.js} +6 -4
- package/dist/hooks/{chunk-AIOIOMPX.js → chunk-ZDZ555YH.js} +225 -78
- package/dist/hooks/codex-hook-adapter.js +10 -10
- package/dist/hooks/duplicate-read-guard.js +5 -5
- package/dist/hooks/format-edited.js +2 -2
- package/dist/hooks/inputs.json +186 -184
- package/dist/hooks/investigation-guard.js +5 -5
- package/dist/hooks/pretool-guard.js +11 -10
- package/dist/hooks/stop-guard.js +5 -5
- package/dist/josh.js +33 -3
- package/dist/version/index.js +5 -1
- package/package.json +7 -4
- package/prompts/collaboration-workflow/file-edits.md +11 -10
- package/prompts/collaboration-workflow/gh-rest.md +2 -4
- package/prompts/collaboration-workflow/glossary.md +1 -1
- package/prompts/collaboration-workflow/issue-citation.md +5 -9
- package/prompts/collaboration-workflow/issue-template.md +40 -115
- package/prompts/collaboration-workflow/operating-rules.md +4 -5
- package/prompts/collaboration-workflow/output-bounds.md +12 -78
- package/prompts/collaboration-workflow/overview.md +7 -13
- package/prompts/collaboration-workflow/plan-comment.md +11 -39
- package/prompts/collaboration-workflow/principles.md +3 -4
- package/prompts/collaboration-workflow/report-format.md +28 -74
- package/prompts/collaboration-workflow/residency.md +12 -109
- package/prompts/collaboration-workflow/rule-delivery.md +98 -97
- package/prompts/collaboration-workflow/shell-body.md +3 -13
- package/prompts/collaboration-workflow/turn-batching.md +14 -88
- package/prompts/collaboration-workflow/upstream-interrupt.md +12 -14
- package/prompts/collaboration-workflow/wip-cap.md +6 -45
- package/prompts/collaboration-workflow.md +1 -13
- package/prompts/review-rubric.md +8 -71
- package/prompts/review.md +25 -55
- package/prompts/testing-guide.md +18 -67
- package/scripts/agent/agent-role-profile.ts +1 -1
- package/scripts/agent/codex-hooks.ts +3 -12
- package/scripts/backlog/backlog-budget.ts +1 -1
- package/scripts/backlog/backlog-drive-cli.ts +4 -3
- package/scripts/backlog/backlog-drive-offer-argv.ts +10 -1
- package/scripts/backlog/backlog-offer.ts +6 -6
- package/scripts/behavior/behavior-rules.ts +11 -4
- package/scripts/delegation/delegation-policy.ts +9 -1
- package/scripts/delegation/investigation-guard.ts +1 -1
- package/scripts/delegation/investigation-reads.ts +109 -23
- package/scripts/discovery/repo-discovery.ts +21 -7
- package/scripts/discovery/repo-origin.ts +26 -0
- package/scripts/discovery/repo-party.ts +42 -1
- package/scripts/document/backlogrun-parent-read-set.ts +14 -15
- package/scripts/document/capped-print.ts +74 -0
- package/scripts/document/document-byte-budget.ts +34 -20
- package/scripts/document/document-section-cli.ts +4 -3
- package/scripts/document/entry-read-budget.ts +21 -9
- package/scripts/document/lane-child-read-set.ts +12 -14
- package/scripts/document/read-set-cli.ts +4 -4
- package/scripts/document/read-set-point-of-use.ts +10 -2
- package/scripts/document/resident-budget.ts +47 -0
- package/scripts/epic/epic-shape.ts +10 -13
- package/scripts/epic/epic-solo.ts +1 -1
- package/scripts/exports/unused-members-cli.ts +7 -3
- package/scripts/gate/scoped-green.ts +25 -14
- package/scripts/gh/git-gh-api-path.ts +7 -0
- package/scripts/hooks/pretool-guard-cli.ts +2 -2
- package/scripts/hooks/pretool-guard.ts +45 -2
- package/scripts/hooks/run-hook.sh +41 -0
- package/scripts/init/claude-kit-only-settings.ts +29 -0
- package/scripts/init/hook-command-rewrite.ts +18 -35
- package/scripts/init/hook-launch.ts +21 -32
- package/scripts/init/init-copy-content.ts +8 -2
- package/scripts/init/initial-commit.ts +24 -0
- package/scripts/init/start-exec.ts +16 -3
- package/scripts/init/start-steps.ts +8 -12
- package/scripts/issue/issue-auto-ok.ts +95 -0
- package/scripts/issue/issue-file-cli.ts +53 -7
- package/scripts/issue/issue-file.ts +28 -5
- package/scripts/issue/issue-labels.ts +12 -12
- package/scripts/issue/issue-rank.ts +1 -1
- package/scripts/issue/issue-read-cli.ts +4 -3
- package/scripts/issue/issue-read.ts +2 -2
- package/scripts/issue/issue-report-failures.ts +6 -2
- package/scripts/issue/issue-scout-cli.ts +2 -2
- package/scripts/issue/issue-state-cli.ts +2 -2
- package/scripts/issue/issue-state.ts +2 -2
- package/scripts/issue/issue-wip.ts +70 -0
- package/scripts/josh/hook-decision.ts +17 -0
- package/scripts/josh/josh-commands-development.ts +10 -0
- package/scripts/josh/josh-commands-issue.ts +2 -2
- package/scripts/josh/josh-commands-lane.ts +1 -1
- package/scripts/josh/josh-commands-project.ts +7 -0
- package/scripts/lane/lane-await.ts +46 -2
- package/scripts/lane/lane-cli.ts +11 -13
- package/scripts/lines/bytes-command.ts +29 -6
- package/scripts/observations/observation-ledger-line.ts +1 -1
- package/scripts/release/github-release-environment.ts +1 -0
- package/scripts/release/github-release.ts +103 -17
- package/scripts/release/release-progress.ts +167 -0
- package/scripts/release/release-publish.ts +19 -8
- package/scripts/release/release-tag.ts +22 -8
- package/scripts/rules/decision-oracle-stage.ts +1 -1
- package/scripts/rules/decision-oracle.ts +6 -6
- package/scripts/rules/delivered-rules.ts +11 -11
- package/scripts/rules/direct-pr-create.ts +110 -0
- package/scripts/rules/file-body.ts +98 -42
- package/scripts/rules/filing-cap.ts +2 -2
- package/scripts/rules/filing-offer.ts +1 -1
- package/scripts/rules/gh-api.ts +18 -1
- package/scripts/rules/implementation-cut.ts +3 -1
- package/scripts/rules/lane-background.ts +67 -10
- package/scripts/rules/permission-guards.ts +10 -2
- package/scripts/rules/stop-rules.ts +4 -4
- package/scripts/rules/test-declared-commit.ts +1 -1
- package/scripts/rules/third-party-write.ts +56 -12
- package/scripts/run/run-carry-cli.ts +3 -1
- package/scripts/run/run-carry-conversation.ts +94 -0
- package/scripts/run/run-carry-reclaim.ts +37 -0
- package/scripts/run/run-carry.ts +21 -5
- package/scripts/run/run-cut-handoff.ts +21 -1
- package/scripts/run/run-entry-cli.ts +11 -3
- package/scripts/run/run-hold-cli.ts +16 -2
- package/scripts/run/run-label.ts +50 -0
- package/scripts/run/run-merge-steps.ts +12 -4
- package/scripts/run/run-merge.ts +1 -1
- package/scripts/run/run-prep-cli.ts +6 -2
- package/scripts/run/run-prep.ts +17 -0
- package/scripts/run/run-ship-preflight.ts +34 -3
- package/scripts/run/run-ship-review-steps.ts +1 -1
- package/scripts/run/run-ship-review.ts +26 -5
- package/scripts/run/run-step-cli.ts +20 -7
- package/scripts/run/run-wake-driver.ts +24 -1
- package/scripts/run/run-wake-session.ts +2 -1
- package/scripts/test/test-declared-logic.ts +1 -1
- package/scripts/test/test-related.ts +1 -6
- package/scripts/test/test-unit-guard.ts +21 -7
- package/scripts/time-runtime/time-batch-guard.ts +52 -16
- package/scripts/time-runtime/time-density-hook.ts +20 -2
- package/scripts/time-runtime/time-density.ts +4 -3
- package/scripts/time-runtime/time-markers.ts +15 -4
- package/scripts/time-runtime/time-spans.ts +4 -0
- package/scripts/version/npm-registry.ts +11 -1
- package/templates/workflows/ci.yml +9 -1
- package/templates/workflows/github-release.yml +13 -25
- package/.claude/skills/workflow-commands/followup-reference.md +0 -118
- package/dist/hooks/chunk-QVLT52OV.js +0 -464
|
@@ -297,10 +297,10 @@ var ISSUE_COMMANDS = {
|
|
|
297
297
|
},
|
|
298
298
|
"issue:file": {
|
|
299
299
|
script: "scripts/issue/issue-file-cli.ts",
|
|
300
|
-
description: "File an Issue with every filing step: lint, Origin, duplicate scout, labels, then epic:bundle",
|
|
300
|
+
description: "File an Issue with every filing step: lint, Origin, WIP cap, duplicate scout, labels, then epic:bundle",
|
|
301
301
|
category: "AI tools",
|
|
302
302
|
reference: [
|
|
303
|
-
"<title> --body-file <path> --depth <0|1|2> [--route <route>] [--label <name>] [--repo <owner/repo>] [--distinct <N,\u2026>]",
|
|
303
|
+
"<title> --body-file <path> --depth <0|1|2> [--route <route>] [--label <name>] [--repo <owner/repo>] [--distinct <N,\u2026>] [--over-cap] [--no-auto-ok]",
|
|
304
304
|
"automation",
|
|
305
305
|
["network"]
|
|
306
306
|
]
|
|
@@ -358,7 +358,7 @@ var LANE_COMMANDS = {
|
|
|
358
358
|
...LANE_ARGUMENTS,
|
|
359
359
|
description: "Block until any of the named in-flight lane children completes; prints which one finished",
|
|
360
360
|
category: "AI tools",
|
|
361
|
-
reference: ["<issue> [<issue>...]", "automation", ["processes"]],
|
|
361
|
+
reference: ["<issue> [<issue>...] [--owner <pid>]", "automation", ["files", "processes"]],
|
|
362
362
|
default_script_arguments: ["await"]
|
|
363
363
|
},
|
|
364
364
|
"lane:launch": {
|
|
@@ -811,6 +811,15 @@ var DEV_COMMANDS = {
|
|
|
811
811
|
// `UserPromptSubmit`, so it must stay eligible for in-process dispatch rather than pay a second
|
|
812
812
|
// tsx start each turn. It calls `process.loadEnvFile` itself through the shared loader.
|
|
813
813
|
},
|
|
814
|
+
"codex:hook-adapter": {
|
|
815
|
+
script: "scripts/hooks/codex-hook-adapter.ts",
|
|
816
|
+
description: "Codex hook: run the pretool or posttool guard on a Codex payload (reads the tool call on stdin)",
|
|
817
|
+
category: "Development",
|
|
818
|
+
reference: ["<pretool|posttool>", "automation", ["none"]]
|
|
819
|
+
// The live-source fallback `scripts/hooks/run-hook.sh` derives from the `codex-hook-adapter`
|
|
820
|
+
// bundle name (joshuafolkken/kit#3184). **No `tsx_arguments`**, the same as `pretool:guard`
|
|
821
|
+
// above: it runs before every guarded Codex call.
|
|
822
|
+
},
|
|
814
823
|
"cspell:dot": {
|
|
815
824
|
script: "scripts/lint/cspell-cached.ts",
|
|
816
825
|
description: "Run spell check including dotfiles",
|
|
@@ -1059,6 +1068,13 @@ var PROJECT_COMMANDS = {
|
|
|
1059
1068
|
["files", "git", "network", "processes"]
|
|
1060
1069
|
]
|
|
1061
1070
|
},
|
|
1071
|
+
"dogfood:commit": {
|
|
1072
|
+
script: "scripts/dogfood/dogfood-commit-cli.ts",
|
|
1073
|
+
description: "Make the first commit of a dogfood run's own kit-test-* project",
|
|
1074
|
+
category: "Project",
|
|
1075
|
+
reference: ["<dir>", "automation", ["files", "git"]],
|
|
1076
|
+
is_kit_only: true
|
|
1077
|
+
},
|
|
1062
1078
|
init: {
|
|
1063
1079
|
script: "scripts/init/init.ts",
|
|
1064
1080
|
description: "Initialize config in a new project",
|
|
@@ -2085,6 +2101,7 @@ var ISSUE_COMMENT_PATTERN = /\bgh\s+issue\s+comment\b/u;
|
|
|
2085
2101
|
var BODY_FLAG_PATTERN = /--body(?:-file)?\b/u;
|
|
2086
2102
|
var ISSUE_LABELS_PATTERN = /repos\/\S+\/issues\/(\d+)\/labels\b(?!\/)/u;
|
|
2087
2103
|
var IN_PROGRESS_FIELD = "labels[]=in-progress";
|
|
2104
|
+
var RUN_ENTRY_PATTERN = /\bjosh\s+run:entry\s+(\d+)\b/u;
|
|
2088
2105
|
var NO_ISSUE = cost_attribute.UNATTRIBUTED_KEY;
|
|
2089
2106
|
function string_field(input, key) {
|
|
2090
2107
|
if (!json_value.is_record(input)) return "";
|
|
@@ -2114,19 +2131,23 @@ function is_issue_comment(command) {
|
|
|
2114
2131
|
function is_in_progress_label(command) {
|
|
2115
2132
|
return ISSUE_LABELS_PATTERN.test(command) && command.includes(IN_PROGRESS_FIELD);
|
|
2116
2133
|
}
|
|
2134
|
+
function is_run_start(command) {
|
|
2135
|
+
return is_in_progress_label(command) || RUN_ENTRY_PATTERN.test(command);
|
|
2136
|
+
}
|
|
2117
2137
|
var COMMAND_SEPARATOR = /&&|\|\||;/u;
|
|
2118
|
-
function
|
|
2119
|
-
const
|
|
2138
|
+
function declared_issue2(part) {
|
|
2139
|
+
const pattern = is_in_progress_label(part) ? ISSUE_LABELS_PATTERN : RUN_ENTRY_PATTERN;
|
|
2140
|
+
const matched = pattern.exec(part);
|
|
2120
2141
|
return matched?.[1] === void 0 ? NO_ISSUE : Number(matched[1]);
|
|
2121
2142
|
}
|
|
2122
2143
|
function bash_issue(command) {
|
|
2123
2144
|
for (const part of command.split(COMMAND_SEPARATOR)) {
|
|
2124
|
-
if (
|
|
2145
|
+
if (is_run_start(part)) return declared_issue2(part);
|
|
2125
2146
|
}
|
|
2126
2147
|
return NO_ISSUE;
|
|
2127
2148
|
}
|
|
2128
2149
|
function bash_marker(command) {
|
|
2129
|
-
if (
|
|
2150
|
+
if (is_run_start(command)) return WORKFLOW_MARKER;
|
|
2130
2151
|
return is_api_body_write(command) || is_issue_comment(command) ? PLAN_MARKER : NO_MARKER;
|
|
2131
2152
|
}
|
|
2132
2153
|
var time_markers = {
|
|
@@ -2573,6 +2594,7 @@ function to_spans(events, finished) {
|
|
|
2573
2594
|
is_bundleable: event.is_bundleable,
|
|
2574
2595
|
targets: event.targets,
|
|
2575
2596
|
is_writing: event.is_writing,
|
|
2597
|
+
may_write: event.may_write,
|
|
2576
2598
|
has_prior_reference: event.has_prior_reference,
|
|
2577
2599
|
writes: event.writes,
|
|
2578
2600
|
message_id: event.message_id,
|
|
@@ -2623,6 +2645,530 @@ var time_spans = {
|
|
|
2623
2645
|
parse_timeline
|
|
2624
2646
|
};
|
|
2625
2647
|
|
|
2648
|
+
// scripts/time-runtime/time-round-trips.ts
|
|
2649
|
+
var CALLS_PER_ROUND_TRIP_FLOOR = 1.5;
|
|
2650
|
+
var DENSITY_DECIMALS = 2;
|
|
2651
|
+
var NONE = 0;
|
|
2652
|
+
function is_tool(span) {
|
|
2653
|
+
return span.category === time_spans.TOOL_CATEGORY;
|
|
2654
|
+
}
|
|
2655
|
+
function is_model(span) {
|
|
2656
|
+
return span.category === time_spans.MODEL_CATEGORY;
|
|
2657
|
+
}
|
|
2658
|
+
function started_ms2(span) {
|
|
2659
|
+
return span.ended_ms - span.duration_ms;
|
|
2660
|
+
}
|
|
2661
|
+
function in_time_order(spans) {
|
|
2662
|
+
return spans.toSorted((left, right) => started_ms2(left) - started_ms2(right));
|
|
2663
|
+
}
|
|
2664
|
+
function is_tool_at(spans, index) {
|
|
2665
|
+
const span = spans[index];
|
|
2666
|
+
return span !== void 0 && is_tool(span);
|
|
2667
|
+
}
|
|
2668
|
+
function is_call(span) {
|
|
2669
|
+
return is_tool(span) && !span.is_continuation;
|
|
2670
|
+
}
|
|
2671
|
+
function count_calls(spans) {
|
|
2672
|
+
return spans.filter((span) => is_call(span)).length;
|
|
2673
|
+
}
|
|
2674
|
+
function previous_break(spans, index) {
|
|
2675
|
+
for (let back = index - 1; back >= 0; back -= 1) {
|
|
2676
|
+
const span = spans[back];
|
|
2677
|
+
if (span === void 0 || !is_model(span)) return span;
|
|
2678
|
+
}
|
|
2679
|
+
return void 0;
|
|
2680
|
+
}
|
|
2681
|
+
function continues_turn(spans, index, message_id) {
|
|
2682
|
+
if (message_id === time_spans.NO_MESSAGE_ID) return false;
|
|
2683
|
+
const earlier = previous_break(spans, index);
|
|
2684
|
+
return earlier !== void 0 && !earlier.is_continuation && earlier.message_id === message_id;
|
|
2685
|
+
}
|
|
2686
|
+
function opens_round_trip(spans, index) {
|
|
2687
|
+
const span = spans[index];
|
|
2688
|
+
if (span === void 0 || !is_tool(span) || span.is_continuation || is_tool_at(spans, index - 1)) {
|
|
2689
|
+
return false;
|
|
2690
|
+
}
|
|
2691
|
+
return !continues_turn(spans, index, span.message_id);
|
|
2692
|
+
}
|
|
2693
|
+
function open_trip(walk) {
|
|
2694
|
+
walk.open = [];
|
|
2695
|
+
walk.trips.push(walk.open);
|
|
2696
|
+
}
|
|
2697
|
+
function place_call(walk, span) {
|
|
2698
|
+
if (is_call(span)) walk.open?.push(span);
|
|
2699
|
+
}
|
|
2700
|
+
function step_trip(walk, span, is_opener) {
|
|
2701
|
+
if (is_opener) open_trip(walk);
|
|
2702
|
+
if (is_tool(span)) place_call(walk, span);
|
|
2703
|
+
else if (!is_model(span)) walk.open = void 0;
|
|
2704
|
+
}
|
|
2705
|
+
function group_round_trips(spans) {
|
|
2706
|
+
const ordered = in_time_order(spans);
|
|
2707
|
+
const walk = { trips: [], open: void 0 };
|
|
2708
|
+
for (const [index, span] of ordered.entries()) {
|
|
2709
|
+
step_trip(walk, span, opens_round_trip(ordered, index));
|
|
2710
|
+
}
|
|
2711
|
+
return walk.trips;
|
|
2712
|
+
}
|
|
2713
|
+
function count_round_trips(spans) {
|
|
2714
|
+
return group_round_trips(spans).length;
|
|
2715
|
+
}
|
|
2716
|
+
function turn_key(span, index) {
|
|
2717
|
+
return span.message_id === time_spans.NO_MESSAGE_ID ? `#${String(index)}` : span.message_id;
|
|
2718
|
+
}
|
|
2719
|
+
function count_turns(spans) {
|
|
2720
|
+
const keys = spans.filter((span) => is_model(span)).map((span, index) => turn_key(span, index));
|
|
2721
|
+
return new Set(keys).size;
|
|
2722
|
+
}
|
|
2723
|
+
function per_round_trip(total, round_trip_count) {
|
|
2724
|
+
if (round_trip_count === NONE) return NONE;
|
|
2725
|
+
return total / round_trip_count;
|
|
2726
|
+
}
|
|
2727
|
+
function format_density(density) {
|
|
2728
|
+
return density.toFixed(DENSITY_DECIMALS);
|
|
2729
|
+
}
|
|
2730
|
+
function is_below_floor(density) {
|
|
2731
|
+
return density > NONE && density < CALLS_PER_ROUND_TRIP_FLOOR;
|
|
2732
|
+
}
|
|
2733
|
+
var time_round_trips = {
|
|
2734
|
+
CALLS_PER_ROUND_TRIP_FLOOR,
|
|
2735
|
+
count_calls,
|
|
2736
|
+
count_round_trips,
|
|
2737
|
+
// Exported since joshuafolkken/kit#1406, so a turn is defined in the one module that already
|
|
2738
|
+
// defines a round trip rather than as a span filter in the report next door.
|
|
2739
|
+
count_turns,
|
|
2740
|
+
format_density,
|
|
2741
|
+
// Exported since joshuafolkken/kit#1385, so the per-tool counts are read off the very groups this
|
|
2742
|
+
// module counts rather than off a second grouping beside them.
|
|
2743
|
+
group_round_trips,
|
|
2744
|
+
// Exported since joshuafolkken/kit#1309, so the failure aggregation orders a run's spans through
|
|
2745
|
+
// the one function that already knows why they need it — a second `toSorted` beside it would be
|
|
2746
|
+
// the clone `CLAUDE.md` prohibits, and the seam it guards (a delegated unit's spans appended
|
|
2747
|
+
// after the parent's) is not one a second copy would keep remembering.
|
|
2748
|
+
in_time_order,
|
|
2749
|
+
is_below_floor,
|
|
2750
|
+
// The four below are exported since joshuafolkken/kit#1406, when the model-gap walk moved to
|
|
2751
|
+
// `time-model-gaps.ts` and needed the same predicates and the same opener test this walk uses. A
|
|
2752
|
+
// private copy there would be the clone `CLAUDE.md` prohibits, in the one place a drift would have
|
|
2753
|
+
// the distribution and the count disagreeing about what a round trip is.
|
|
2754
|
+
per_round_trip
|
|
2755
|
+
};
|
|
2756
|
+
|
|
2757
|
+
// scripts/time-runtime/time-agent-bundles.ts
|
|
2758
|
+
var NONE2 = 0;
|
|
2759
|
+
var ONE = 1;
|
|
2760
|
+
var MIN_GROUP = 2;
|
|
2761
|
+
function is_launch2(span) {
|
|
2762
|
+
return span.category === time_spans.TOOL_CATEGORY && !span.is_continuation && time_bundle_call.is_launch_tool(span.label);
|
|
2763
|
+
}
|
|
2764
|
+
function new_walk() {
|
|
2765
|
+
return { closed: [], open: NONE2, wrote_since: false, turn_id: time_spans.NO_MESSAGE_ID };
|
|
2766
|
+
}
|
|
2767
|
+
function close_group(walk) {
|
|
2768
|
+
if (walk.open >= MIN_GROUP) walk.closed.push(walk.open);
|
|
2769
|
+
walk.open = NONE2;
|
|
2770
|
+
}
|
|
2771
|
+
function is_same_turn(walk, span) {
|
|
2772
|
+
return span.message_id !== time_spans.NO_MESSAGE_ID && span.message_id === walk.turn_id;
|
|
2773
|
+
}
|
|
2774
|
+
function open_turn(walk, span) {
|
|
2775
|
+
if (walk.wrote_since || span.has_prior_reference) close_group(walk);
|
|
2776
|
+
walk.open += ONE;
|
|
2777
|
+
walk.turn_id = span.message_id;
|
|
2778
|
+
walk.wrote_since = false;
|
|
2779
|
+
}
|
|
2780
|
+
function step(walk, span) {
|
|
2781
|
+
if (span.is_writing) {
|
|
2782
|
+
walk.wrote_since = true;
|
|
2783
|
+
return;
|
|
2784
|
+
}
|
|
2785
|
+
if (!is_launch2(span) || is_same_turn(walk, span)) return;
|
|
2786
|
+
open_turn(walk, span);
|
|
2787
|
+
}
|
|
2788
|
+
function agent_bundles(spans) {
|
|
2789
|
+
const walk = new_walk();
|
|
2790
|
+
for (const span of time_round_trips.in_time_order(spans)) step(walk, span);
|
|
2791
|
+
close_group(walk);
|
|
2792
|
+
return walk.closed;
|
|
2793
|
+
}
|
|
2794
|
+
var time_agent_bundles = { agent_bundles };
|
|
2795
|
+
|
|
2796
|
+
// scripts/time-runtime/time-bundles.ts
|
|
2797
|
+
var { unmeasured_row: unmeasured_row2 } = time_format;
|
|
2798
|
+
var HEADING = "Bundling:";
|
|
2799
|
+
var SEQUENCE_LABEL = "bundleable sequences";
|
|
2800
|
+
var RECOVERABLE_LABEL = "recoverable round trips";
|
|
2801
|
+
var SAVING_LABEL = "recoverable wait";
|
|
2802
|
+
var BY_TOOL_LABEL = "recoverable by tool";
|
|
2803
|
+
var AGENT_ROW_LABEL = "Agent";
|
|
2804
|
+
var ROW_INDENT = " ";
|
|
2805
|
+
var MIN_SEQUENCE = 2;
|
|
2806
|
+
var ONE_CALL = 1;
|
|
2807
|
+
var NONE3 = 0;
|
|
2808
|
+
var PATH_SEPARATOR2 = "/";
|
|
2809
|
+
var FIRST_CALL = 1;
|
|
2810
|
+
var NO_LABEL = "";
|
|
2811
|
+
var NO_BUNDLES = {
|
|
2812
|
+
sequence_count: 0,
|
|
2813
|
+
longest_sequence: 0,
|
|
2814
|
+
recoverable_round_trips: 0,
|
|
2815
|
+
by_tool: [],
|
|
2816
|
+
unattributed_round_trips: 0,
|
|
2817
|
+
is_measured: false
|
|
2818
|
+
};
|
|
2819
|
+
function contains_segments(whole, part) {
|
|
2820
|
+
if (whole.startsWith(`${part}${PATH_SEPARATOR2}`)) return true;
|
|
2821
|
+
return whole.endsWith(`${PATH_SEPARATOR2}${part}`) || whole.includes(`${PATH_SEPARATOR2}${part}${PATH_SEPARATOR2}`);
|
|
2822
|
+
}
|
|
2823
|
+
function is_related(left, right) {
|
|
2824
|
+
if (left === right) return true;
|
|
2825
|
+
return contains_segments(left, right) || contains_segments(right, left);
|
|
2826
|
+
}
|
|
2827
|
+
function shares_target(left, right) {
|
|
2828
|
+
return left.some((one) => right.some((other) => is_related(one, other)));
|
|
2829
|
+
}
|
|
2830
|
+
function is_dependent(earlier, later) {
|
|
2831
|
+
if (earlier.is_writing && later.is_writing) return false;
|
|
2832
|
+
return shares_target(earlier.targets, later.targets);
|
|
2833
|
+
}
|
|
2834
|
+
function conflicts(span, sequence) {
|
|
2835
|
+
return sequence.some((earlier) => is_dependent(earlier, span));
|
|
2836
|
+
}
|
|
2837
|
+
function is_call2(span) {
|
|
2838
|
+
return span.category === time_spans.TOOL_CATEGORY && !span.is_continuation;
|
|
2839
|
+
}
|
|
2840
|
+
function is_turn_boundary(span) {
|
|
2841
|
+
return span.category === time_spans.MODEL_CATEGORY;
|
|
2842
|
+
}
|
|
2843
|
+
function new_walk2() {
|
|
2844
|
+
return { pending: [], sequence: [], closed: [] };
|
|
2845
|
+
}
|
|
2846
|
+
function flush(walk) {
|
|
2847
|
+
if (walk.sequence.length >= MIN_SEQUENCE) walk.closed.push(walk.sequence);
|
|
2848
|
+
walk.sequence = [];
|
|
2849
|
+
}
|
|
2850
|
+
function extend(walk, span) {
|
|
2851
|
+
if (conflicts(span, walk.sequence)) flush(walk);
|
|
2852
|
+
walk.sequence.push(span);
|
|
2853
|
+
}
|
|
2854
|
+
function close_trip(walk) {
|
|
2855
|
+
const [only] = walk.pending;
|
|
2856
|
+
if (walk.pending.length === ONE_CALL && only?.is_bundleable === true) extend(walk, only);
|
|
2857
|
+
else if (walk.pending.length > NONE3) flush(walk);
|
|
2858
|
+
walk.pending = [];
|
|
2859
|
+
}
|
|
2860
|
+
function is_same_turn2(span, pending) {
|
|
2861
|
+
const [first] = pending;
|
|
2862
|
+
if (first === void 0 || span.is_continuation || span.message_id === time_spans.NO_MESSAGE_ID) {
|
|
2863
|
+
return false;
|
|
2864
|
+
}
|
|
2865
|
+
return span.message_id === first.message_id;
|
|
2866
|
+
}
|
|
2867
|
+
function step2(walk, span) {
|
|
2868
|
+
if (is_call2(span)) {
|
|
2869
|
+
walk.pending.push(span);
|
|
2870
|
+
return;
|
|
2871
|
+
}
|
|
2872
|
+
if (is_same_turn2(span, walk.pending)) return;
|
|
2873
|
+
close_trip(walk);
|
|
2874
|
+
if (!is_turn_boundary(span)) flush(walk);
|
|
2875
|
+
}
|
|
2876
|
+
function longest_of(sizes) {
|
|
2877
|
+
return sizes.length === NONE3 ? NONE3 : Math.max(...sizes);
|
|
2878
|
+
}
|
|
2879
|
+
function recoverable_of(sizes) {
|
|
2880
|
+
return sizes.reduce((sum, size) => sum + size - ONE_CALL, NONE3);
|
|
2881
|
+
}
|
|
2882
|
+
function sizes_of(closed) {
|
|
2883
|
+
return closed.map((sequence) => sequence.length);
|
|
2884
|
+
}
|
|
2885
|
+
function tally_for(tallies, label) {
|
|
2886
|
+
const found = tallies.get(label);
|
|
2887
|
+
if (found !== void 0) return found;
|
|
2888
|
+
const created = { sequence_count: NONE3, recoverable_round_trips: NONE3 };
|
|
2889
|
+
tallies.set(label, created);
|
|
2890
|
+
return created;
|
|
2891
|
+
}
|
|
2892
|
+
function count_trip(tallies, counted, label) {
|
|
2893
|
+
const tally2 = tally_for(tallies, label);
|
|
2894
|
+
tally2.recoverable_round_trips += ONE_CALL;
|
|
2895
|
+
if (!counted.has(label)) tally2.sequence_count += ONE_CALL;
|
|
2896
|
+
counted.add(label);
|
|
2897
|
+
}
|
|
2898
|
+
function tally_sequence(tallies, sequence) {
|
|
2899
|
+
const counted = /* @__PURE__ */ new Set();
|
|
2900
|
+
for (const span of sequence.slice(FIRST_CALL)) {
|
|
2901
|
+
if (span.label !== NO_LABEL) count_trip(tallies, counted, span.label);
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
function by_weight(left, right) {
|
|
2905
|
+
if (left.recoverable_round_trips !== right.recoverable_round_trips) {
|
|
2906
|
+
return right.recoverable_round_trips - left.recoverable_round_trips;
|
|
2907
|
+
}
|
|
2908
|
+
return left.label.localeCompare(right.label);
|
|
2909
|
+
}
|
|
2910
|
+
function tool_rows(closed) {
|
|
2911
|
+
const tallies = /* @__PURE__ */ new Map();
|
|
2912
|
+
for (const sequence of closed) tally_sequence(tallies, sequence);
|
|
2913
|
+
return [...tallies].map(([label, tally2]) => ({ label, ...tally2 })).toSorted(by_weight);
|
|
2914
|
+
}
|
|
2915
|
+
function attributed_of(rows) {
|
|
2916
|
+
return rows.reduce((sum, row) => sum + row.recoverable_round_trips, NONE3);
|
|
2917
|
+
}
|
|
2918
|
+
function with_agent_row(rows, agent_sizes) {
|
|
2919
|
+
const recoverable_round_trips = recoverable_of(agent_sizes);
|
|
2920
|
+
if (recoverable_round_trips === NONE3) return [...rows];
|
|
2921
|
+
const row = {
|
|
2922
|
+
label: AGENT_ROW_LABEL,
|
|
2923
|
+
sequence_count: agent_sizes.length,
|
|
2924
|
+
recoverable_round_trips
|
|
2925
|
+
};
|
|
2926
|
+
return [...rows, row].toSorted(by_weight);
|
|
2927
|
+
}
|
|
2928
|
+
function build_bundles(spans) {
|
|
2929
|
+
const walk = new_walk2();
|
|
2930
|
+
for (const span of time_round_trips.in_time_order(spans)) step2(walk, span);
|
|
2931
|
+
close_trip(walk);
|
|
2932
|
+
flush(walk);
|
|
2933
|
+
const sizes = sizes_of(walk.closed);
|
|
2934
|
+
const agent_sizes = time_agent_bundles.agent_bundles(spans);
|
|
2935
|
+
const by_tool = with_agent_row(tool_rows(walk.closed), agent_sizes);
|
|
2936
|
+
const recoverable_round_trips = recoverable_of(sizes) + recoverable_of(agent_sizes);
|
|
2937
|
+
return {
|
|
2938
|
+
sequence_count: sizes.length + agent_sizes.length,
|
|
2939
|
+
longest_sequence: Math.max(longest_of(sizes), longest_of(agent_sizes)),
|
|
2940
|
+
recoverable_round_trips,
|
|
2941
|
+
by_tool,
|
|
2942
|
+
unattributed_round_trips: recoverable_round_trips - attributed_of(by_tool),
|
|
2943
|
+
is_measured: time_spans.has_transcript_data(spans.length)
|
|
2944
|
+
};
|
|
2945
|
+
}
|
|
2946
|
+
function open_sequence(spans) {
|
|
2947
|
+
const walk = new_walk2();
|
|
2948
|
+
for (const span of time_round_trips.in_time_order(spans)) step2(walk, span);
|
|
2949
|
+
return walk.sequence;
|
|
2950
|
+
}
|
|
2951
|
+
function sequence_suffix(totals) {
|
|
2952
|
+
return `longest ${String(totals.longest_sequence)} turn(s)`;
|
|
2953
|
+
}
|
|
2954
|
+
function recoverable_suffix(totals, round_trip_count) {
|
|
2955
|
+
const share = time_format.format_share(totals.recoverable_round_trips, round_trip_count);
|
|
2956
|
+
return `${share} of ${String(round_trip_count)} round trip(s)`;
|
|
2957
|
+
}
|
|
2958
|
+
function saving_line(totals, model_ms_per_round_trip) {
|
|
2959
|
+
const saved_ms = totals.recoverable_round_trips * model_ms_per_round_trip;
|
|
2960
|
+
const rate = `at ${time_format.format_seconds(model_ms_per_round_trip)} model time per round trip`;
|
|
2961
|
+
return time_format.format_row(SAVING_LABEL, saved_ms, rate);
|
|
2962
|
+
}
|
|
2963
|
+
function by_tool_suffix(totals) {
|
|
2964
|
+
const attributed = totals.recoverable_round_trips - totals.unattributed_round_trips;
|
|
2965
|
+
return `${String(attributed)} of ${String(totals.recoverable_round_trips)} attributed`;
|
|
2966
|
+
}
|
|
2967
|
+
function tool_row(row) {
|
|
2968
|
+
return time_format.format_columns(
|
|
2969
|
+
`${ROW_INDENT}${row.label}`,
|
|
2970
|
+
String(row.recoverable_round_trips),
|
|
2971
|
+
`in ${String(row.sequence_count)} sequence(s)`
|
|
2972
|
+
);
|
|
2973
|
+
}
|
|
2974
|
+
function by_tool_lines(totals) {
|
|
2975
|
+
const heading = time_format.format_columns(
|
|
2976
|
+
BY_TOOL_LABEL,
|
|
2977
|
+
String(totals.by_tool.length),
|
|
2978
|
+
by_tool_suffix(totals)
|
|
2979
|
+
);
|
|
2980
|
+
const rows = totals.by_tool.slice(NONE3, time_format.MAX_ROWS).map((row) => tool_row(row));
|
|
2981
|
+
return [heading, ...rows, ...time_format.overflow_line(totals.by_tool.length)];
|
|
2982
|
+
}
|
|
2983
|
+
function measured_lines(totals, price) {
|
|
2984
|
+
return [
|
|
2985
|
+
time_format.format_columns(
|
|
2986
|
+
SEQUENCE_LABEL,
|
|
2987
|
+
String(totals.sequence_count),
|
|
2988
|
+
sequence_suffix(totals)
|
|
2989
|
+
),
|
|
2990
|
+
time_format.format_columns(
|
|
2991
|
+
RECOVERABLE_LABEL,
|
|
2992
|
+
String(totals.recoverable_round_trips),
|
|
2993
|
+
recoverable_suffix(totals, price.round_trip_count)
|
|
2994
|
+
),
|
|
2995
|
+
saving_line(totals, price.model_ms_per_round_trip),
|
|
2996
|
+
...by_tool_lines(totals)
|
|
2997
|
+
];
|
|
2998
|
+
}
|
|
2999
|
+
var LABELS = [SEQUENCE_LABEL, RECOVERABLE_LABEL, SAVING_LABEL, BY_TOOL_LABEL];
|
|
3000
|
+
function no_divisor_lines() {
|
|
3001
|
+
return LABELS.map((label) => time_format.format_columns(label, "", time_format.NO_CALLS));
|
|
3002
|
+
}
|
|
3003
|
+
function bundle_lines(totals, price) {
|
|
3004
|
+
const heading = ["", HEADING];
|
|
3005
|
+
if (!totals.is_measured) return [...heading, ...LABELS.map((label) => unmeasured_row2(label))];
|
|
3006
|
+
if (price.round_trip_count === NONE3) return [...heading, ...no_divisor_lines()];
|
|
3007
|
+
return [...heading, ...measured_lines(totals, price)];
|
|
3008
|
+
}
|
|
3009
|
+
var time_bundles = {
|
|
3010
|
+
HEADING,
|
|
3011
|
+
BY_TOOL_LABEL,
|
|
3012
|
+
NO_BUNDLES,
|
|
3013
|
+
build_bundles,
|
|
3014
|
+
bundle_lines,
|
|
3015
|
+
is_dependent,
|
|
3016
|
+
open_sequence,
|
|
3017
|
+
shares_target
|
|
3018
|
+
};
|
|
3019
|
+
|
|
3020
|
+
// scripts/time-runtime/time-command-key.ts
|
|
3021
|
+
var UNNAMED_KEY = "";
|
|
3022
|
+
function command_key(span) {
|
|
3023
|
+
if (span.label === time_spans.UNKNOWN_TOOL) return UNNAMED_KEY;
|
|
3024
|
+
return span.josh_command === "" ? span.label : span.josh_command;
|
|
3025
|
+
}
|
|
3026
|
+
var time_command_key = { UNNAMED_KEY, command_key };
|
|
3027
|
+
|
|
3028
|
+
// scripts/time-runtime/time-call-identity.ts
|
|
3029
|
+
var IDENTITY_SEPARATOR = "\0";
|
|
3030
|
+
function identity_of(call) {
|
|
3031
|
+
return [
|
|
3032
|
+
time_command_key.command_key(call),
|
|
3033
|
+
call.check_key,
|
|
3034
|
+
[...call.targets].toSorted((left, right) => left.localeCompare(right)).join(",")
|
|
3035
|
+
].join(IDENTITY_SEPARATOR);
|
|
3036
|
+
}
|
|
3037
|
+
var time_call_identity = { identity_of };
|
|
3038
|
+
|
|
3039
|
+
// scripts/time-runtime/time-batch-guard.ts
|
|
3040
|
+
var CONSECUTIVE_LIMIT = 3;
|
|
3041
|
+
var REFIRE_EVERY = CONSECUTIVE_LIMIT;
|
|
3042
|
+
var ONE_TURN = 1;
|
|
3043
|
+
var NONE4 = 0;
|
|
3044
|
+
var NOTICE_REFIRE_EVERY = ONE_TURN;
|
|
3045
|
+
var NAMED_CANDIDATE_COUNT = 3;
|
|
3046
|
+
var SEQUENCE_BEFORE_LIMIT = CONSECUTIVE_LIMIT - ONE_TURN;
|
|
3047
|
+
var REASON = `\u26D4 batching: the last ${String(SEQUENCE_BEFORE_LIMIT)} turns each issued a single tool call, so this one would make ${String(CONSECUTIVE_LIMIT)} in a row. Reissue it in one turn together with the calls meant to follow it that do not need its result. **The criterion is whether this call's input needs another call's result, not what kind of call it is** \u2014 edits are covered exactly as reads are, and it never authorizes weakening a verification gate or a review: fewer turns, never less work. The rule, and where its measured cost and rejected mechanisms are, is in \`prompts/collaboration-workflow/turn-batching.md\`. If this turn was already batching, or the call genuinely has nothing to go beside it, reissue it as it was: this fires once per run of single-call turns and cannot repeat on the call in hand.`;
|
|
3048
|
+
var NOTICE = `\u{1F4A1} batching: the last ${String(SEQUENCE_BEFORE_LIMIT)} turns each issued a single tool call, so this Write makes ${String(CONSECUTIVE_LIMIT)} in a row. This is a notice, not a refusal \u2014 the write proceeds. Where the writes meant to follow it do not need its result, issue them in one turn together. **The criterion is whether a call's input needs another call's result, not what kind of call it is** \u2014 it never authorizes weakening a verification gate or a review: fewer turns, never less work. The rule, and where its measured cost and rejected mechanisms are, is in \`prompts/collaboration-workflow/turn-batching.md\`. If this run was already batching, or the write genuinely has nothing to go beside it, carry on: this recurs every ${String(NOTICE_REFIRE_EVERY)} further single-call turn(s).`;
|
|
3049
|
+
var LANE_NOTICE = `\u{1F4A1} batching: the last ${String(SEQUENCE_BEFORE_LIMIT)} turns each issued a single tool call, so this one makes ${String(CONSECUTIVE_LIMIT)} in a row. This is a notice, not a refusal \u2014 the call proceeds, because a dispatched lane child ends its turn on a denial. Where the calls meant to follow it do not need its result, issue them in one turn together. **The criterion is whether a call's input needs another call's result, not what kind of call it is** \u2014 it never authorizes weakening a verification gate or a review: fewer turns, never less work. The rule, and where its measured cost and rejected mechanisms are, is in \`prompts/collaboration-workflow/turn-batching.md\`. If this run was already batching, or the call genuinely has nothing to go beside it, carry on: this recurs every ${String(NOTICE_REFIRE_EVERY)} further single-call turn(s).`;
|
|
3050
|
+
var WHOLE_FILE_WRITE_TOOL = "Write";
|
|
3051
|
+
var EDIT_TOOL = "Edit";
|
|
3052
|
+
function is_guarded_call(call) {
|
|
3053
|
+
if (call.name === WHOLE_FILE_WRITE_TOOL) return false;
|
|
3054
|
+
return time_bundle_call.call_facts(call.name, call.input).is_bundleable;
|
|
3055
|
+
}
|
|
3056
|
+
function is_notice_call(call) {
|
|
3057
|
+
return call.name === WHOLE_FILE_WRITE_TOOL;
|
|
3058
|
+
}
|
|
3059
|
+
function is_read_only_call(call) {
|
|
3060
|
+
const facts = time_bundle_call.call_facts(call.name, call.input);
|
|
3061
|
+
return facts.is_bundleable && !facts.may_write;
|
|
3062
|
+
}
|
|
3063
|
+
function depends_on_sequence(sequence, facts) {
|
|
3064
|
+
return sequence.some((span) => time_bundles.shares_target(span.targets, facts.targets));
|
|
3065
|
+
}
|
|
3066
|
+
function sequence_started_ms(sequence) {
|
|
3067
|
+
return sequence[0]?.ended_ms ?? NONE4;
|
|
3068
|
+
}
|
|
3069
|
+
function turns_since(sequence, last_fired_ms) {
|
|
3070
|
+
return sequence.filter((span) => span.ended_ms > last_fired_ms).length;
|
|
3071
|
+
}
|
|
3072
|
+
function is_sequence_at_limit(sequence, facts, last_fired_ms, refire_every) {
|
|
3073
|
+
if (sequence.length < SEQUENCE_BEFORE_LIMIT || depends_on_sequence(sequence, facts)) return false;
|
|
3074
|
+
if (last_fired_ms < sequence_started_ms(sequence)) return true;
|
|
3075
|
+
return turns_since(sequence, last_fired_ms) >= refire_every;
|
|
3076
|
+
}
|
|
3077
|
+
function is_at_limit(spans, call, last_fired_ms, refire_every) {
|
|
3078
|
+
return is_sequence_at_limit(
|
|
3079
|
+
time_bundles.open_sequence(spans),
|
|
3080
|
+
time_bundle_call.call_facts(call.name, call.input),
|
|
3081
|
+
last_fired_ms,
|
|
3082
|
+
refire_every
|
|
3083
|
+
);
|
|
3084
|
+
}
|
|
3085
|
+
var GUARD_LABEL = time_transcript_line.guard_from_refusal(REASON);
|
|
3086
|
+
function is_reissued_refusal(spans, call) {
|
|
3087
|
+
const identity = time_call_identity.identity_of(
|
|
3088
|
+
time_spans.to_tool_call(call.name, call.input, time_spans.NO_MESSAGE_ID)
|
|
3089
|
+
);
|
|
3090
|
+
return spans.some(
|
|
3091
|
+
(span) => span.refusal_guard === GUARD_LABEL && time_call_identity.identity_of(span) === identity
|
|
3092
|
+
);
|
|
3093
|
+
}
|
|
3094
|
+
function should_block(text, call, refused_at_ms, refire_every = REFIRE_EVERY) {
|
|
3095
|
+
if (!is_guarded_call(call)) return false;
|
|
3096
|
+
const { spans } = time_spans.parse_timeline(text);
|
|
3097
|
+
if (is_reissued_refusal(spans, call)) return false;
|
|
3098
|
+
return is_at_limit(spans, call, refused_at_ms, refire_every);
|
|
3099
|
+
}
|
|
3100
|
+
function should_notify(text, call, notified_at_ms) {
|
|
3101
|
+
if (!is_notice_call(call)) return false;
|
|
3102
|
+
const { spans } = time_spans.parse_timeline(text);
|
|
3103
|
+
return is_at_limit(spans, call, notified_at_ms, NOTICE_REFIRE_EVERY);
|
|
3104
|
+
}
|
|
3105
|
+
function describe_span(span) {
|
|
3106
|
+
const target = span.targets[0] ?? "";
|
|
3107
|
+
if (target === "" || span.label.includes(target)) return span.label;
|
|
3108
|
+
return `${span.label} ${target}`;
|
|
3109
|
+
}
|
|
3110
|
+
var FOLD_MINIMUM = 2;
|
|
3111
|
+
function read_targets(sequence) {
|
|
3112
|
+
const paths = sequence.filter((span) => span.is_bundleable && !span.is_writing).map((span) => span.targets[0] ?? "").filter((path6) => path6 !== "" && time_bundle_call.has_extension(path6));
|
|
3113
|
+
return [...new Set(paths)];
|
|
3114
|
+
}
|
|
3115
|
+
function read_fold_directive(sequence) {
|
|
3116
|
+
const paths = read_targets(sequence);
|
|
3117
|
+
if (paths.length < FOLD_MINIMUM) return "";
|
|
3118
|
+
return ` Fold them into one call: \`pnpm josh read:files ${paths.join(" ")}\`.`;
|
|
3119
|
+
}
|
|
3120
|
+
function write_targets(sequence) {
|
|
3121
|
+
const paths = sequence.filter((span) => span.label === EDIT_TOOL).map((span) => span.targets[0] ?? "").filter((path6) => path6 !== "" && time_bundle_call.has_extension(path6));
|
|
3122
|
+
return [...new Set(paths)];
|
|
3123
|
+
}
|
|
3124
|
+
function write_fold_directive(sequence) {
|
|
3125
|
+
const paths = write_targets(sequence);
|
|
3126
|
+
if (paths.length < FOLD_MINIMUM) return "";
|
|
3127
|
+
return ` Fold them into one \`pnpm josh edit:files\` call with a plan over: ${paths.join(" ")}.`;
|
|
3128
|
+
}
|
|
3129
|
+
function name_candidates(calls) {
|
|
3130
|
+
if (calls.length === NONE4) return "";
|
|
3131
|
+
const named = calls.map((call) => describe_span(call)).join(", ");
|
|
3132
|
+
const folds = `${read_fold_directive(calls)}${write_fold_directive(calls)}`;
|
|
3133
|
+
return ` Just issued one per turn, so at least these could have shared a turn: ${named}.${folds}`;
|
|
3134
|
+
}
|
|
3135
|
+
function recent_candidates(tail) {
|
|
3136
|
+
return name_candidates(
|
|
3137
|
+
time_bundles.open_sequence(time_spans.parse_timeline(tail).spans).slice(-NAMED_CANDIDATE_COUNT)
|
|
3138
|
+
);
|
|
3139
|
+
}
|
|
3140
|
+
function pairing_candidates(tail, call) {
|
|
3141
|
+
const sequence = time_bundles.open_sequence(time_spans.parse_timeline(tail).spans);
|
|
3142
|
+
if (call === void 0 || sequence.length === NONE4) {
|
|
3143
|
+
return name_candidates(sequence.slice(-NAMED_CANDIDATE_COUNT));
|
|
3144
|
+
}
|
|
3145
|
+
const facts = time_bundle_call.call_facts(call.name, call.input);
|
|
3146
|
+
if (!facts.is_bundleable || depends_on_sequence(sequence, facts)) return "";
|
|
3147
|
+
const current = time_spans.to_tool_call(call.name, call.input, time_spans.NO_MESSAGE_ID);
|
|
3148
|
+
return name_candidates([...sequence.slice(ONE_TURN - NAMED_CANDIDATE_COUNT), current]);
|
|
3149
|
+
}
|
|
3150
|
+
var STAMP_PREFIX = "josh-batch-guard-";
|
|
3151
|
+
var NOTICE_STAMP_PREFIX = "josh-batch-guard-notice-";
|
|
3152
|
+
var time_batch_guard = {
|
|
3153
|
+
CONSECUTIVE_LIMIT,
|
|
3154
|
+
GUARD_LABEL,
|
|
3155
|
+
LANE_NOTICE,
|
|
3156
|
+
NOTICE,
|
|
3157
|
+
NOTICE_REFIRE_EVERY,
|
|
3158
|
+
NOTICE_STAMP_PREFIX,
|
|
3159
|
+
REASON,
|
|
3160
|
+
REFIRE_EVERY,
|
|
3161
|
+
SEQUENCE_BEFORE_LIMIT,
|
|
3162
|
+
STAMP_PREFIX,
|
|
3163
|
+
is_guarded_call,
|
|
3164
|
+
is_notice_call,
|
|
3165
|
+
is_read_only_call,
|
|
3166
|
+
pairing_candidates,
|
|
3167
|
+
recent_candidates,
|
|
3168
|
+
should_block,
|
|
3169
|
+
should_notify
|
|
3170
|
+
};
|
|
3171
|
+
|
|
2626
3172
|
// scripts/time-runtime/time-hook-transcript.ts
|
|
2627
3173
|
import path3 from "node:path";
|
|
2628
3174
|
|
|
@@ -3198,120 +3744,11 @@ var stamp_file = {
|
|
|
3198
3744
|
// scripts/time-runtime/time-density-hook.ts
|
|
3199
3745
|
import { z as z5 } from "zod";
|
|
3200
3746
|
|
|
3201
|
-
// scripts/time-runtime/time-round-trips.ts
|
|
3202
|
-
var CALLS_PER_ROUND_TRIP_FLOOR = 1.5;
|
|
3203
|
-
var DENSITY_DECIMALS = 2;
|
|
3204
|
-
var NONE = 0;
|
|
3205
|
-
function is_tool(span) {
|
|
3206
|
-
return span.category === time_spans.TOOL_CATEGORY;
|
|
3207
|
-
}
|
|
3208
|
-
function is_model(span) {
|
|
3209
|
-
return span.category === time_spans.MODEL_CATEGORY;
|
|
3210
|
-
}
|
|
3211
|
-
function started_ms2(span) {
|
|
3212
|
-
return span.ended_ms - span.duration_ms;
|
|
3213
|
-
}
|
|
3214
|
-
function in_time_order(spans) {
|
|
3215
|
-
return spans.toSorted((left, right) => started_ms2(left) - started_ms2(right));
|
|
3216
|
-
}
|
|
3217
|
-
function is_tool_at(spans, index) {
|
|
3218
|
-
const span = spans[index];
|
|
3219
|
-
return span !== void 0 && is_tool(span);
|
|
3220
|
-
}
|
|
3221
|
-
function is_call(span) {
|
|
3222
|
-
return is_tool(span) && !span.is_continuation;
|
|
3223
|
-
}
|
|
3224
|
-
function count_calls(spans) {
|
|
3225
|
-
return spans.filter((span) => is_call(span)).length;
|
|
3226
|
-
}
|
|
3227
|
-
function previous_break(spans, index) {
|
|
3228
|
-
for (let back = index - 1; back >= 0; back -= 1) {
|
|
3229
|
-
const span = spans[back];
|
|
3230
|
-
if (span === void 0 || !is_model(span)) return span;
|
|
3231
|
-
}
|
|
3232
|
-
return void 0;
|
|
3233
|
-
}
|
|
3234
|
-
function continues_turn(spans, index, message_id) {
|
|
3235
|
-
if (message_id === time_spans.NO_MESSAGE_ID) return false;
|
|
3236
|
-
const earlier = previous_break(spans, index);
|
|
3237
|
-
return earlier !== void 0 && !earlier.is_continuation && earlier.message_id === message_id;
|
|
3238
|
-
}
|
|
3239
|
-
function opens_round_trip(spans, index) {
|
|
3240
|
-
const span = spans[index];
|
|
3241
|
-
if (span === void 0 || !is_tool(span) || span.is_continuation || is_tool_at(spans, index - 1)) {
|
|
3242
|
-
return false;
|
|
3243
|
-
}
|
|
3244
|
-
return !continues_turn(spans, index, span.message_id);
|
|
3245
|
-
}
|
|
3246
|
-
function open_trip(walk) {
|
|
3247
|
-
walk.open = [];
|
|
3248
|
-
walk.trips.push(walk.open);
|
|
3249
|
-
}
|
|
3250
|
-
function place_call(walk, span) {
|
|
3251
|
-
if (is_call(span)) walk.open?.push(span);
|
|
3252
|
-
}
|
|
3253
|
-
function step_trip(walk, span, is_opener) {
|
|
3254
|
-
if (is_opener) open_trip(walk);
|
|
3255
|
-
if (is_tool(span)) place_call(walk, span);
|
|
3256
|
-
else if (!is_model(span)) walk.open = void 0;
|
|
3257
|
-
}
|
|
3258
|
-
function group_round_trips(spans) {
|
|
3259
|
-
const ordered = in_time_order(spans);
|
|
3260
|
-
const walk = { trips: [], open: void 0 };
|
|
3261
|
-
for (const [index, span] of ordered.entries()) {
|
|
3262
|
-
step_trip(walk, span, opens_round_trip(ordered, index));
|
|
3263
|
-
}
|
|
3264
|
-
return walk.trips;
|
|
3265
|
-
}
|
|
3266
|
-
function count_round_trips(spans) {
|
|
3267
|
-
return group_round_trips(spans).length;
|
|
3268
|
-
}
|
|
3269
|
-
function turn_key(span, index) {
|
|
3270
|
-
return span.message_id === time_spans.NO_MESSAGE_ID ? `#${String(index)}` : span.message_id;
|
|
3271
|
-
}
|
|
3272
|
-
function count_turns(spans) {
|
|
3273
|
-
const keys = spans.filter((span) => is_model(span)).map((span, index) => turn_key(span, index));
|
|
3274
|
-
return new Set(keys).size;
|
|
3275
|
-
}
|
|
3276
|
-
function per_round_trip(total, round_trip_count) {
|
|
3277
|
-
if (round_trip_count === NONE) return NONE;
|
|
3278
|
-
return total / round_trip_count;
|
|
3279
|
-
}
|
|
3280
|
-
function format_density(density) {
|
|
3281
|
-
return density.toFixed(DENSITY_DECIMALS);
|
|
3282
|
-
}
|
|
3283
|
-
function is_below_floor(density) {
|
|
3284
|
-
return density > NONE && density < CALLS_PER_ROUND_TRIP_FLOOR;
|
|
3285
|
-
}
|
|
3286
|
-
var time_round_trips = {
|
|
3287
|
-
CALLS_PER_ROUND_TRIP_FLOOR,
|
|
3288
|
-
count_calls,
|
|
3289
|
-
count_round_trips,
|
|
3290
|
-
// Exported since joshuafolkken/kit#1406, so a turn is defined in the one module that already
|
|
3291
|
-
// defines a round trip rather than as a span filter in the report next door.
|
|
3292
|
-
count_turns,
|
|
3293
|
-
format_density,
|
|
3294
|
-
// Exported since joshuafolkken/kit#1385, so the per-tool counts are read off the very groups this
|
|
3295
|
-
// module counts rather than off a second grouping beside them.
|
|
3296
|
-
group_round_trips,
|
|
3297
|
-
// Exported since joshuafolkken/kit#1309, so the failure aggregation orders a run's spans through
|
|
3298
|
-
// the one function that already knows why they need it — a second `toSorted` beside it would be
|
|
3299
|
-
// the clone `CLAUDE.md` prohibits, and the seam it guards (a delegated unit's spans appended
|
|
3300
|
-
// after the parent's) is not one a second copy would keep remembering.
|
|
3301
|
-
in_time_order,
|
|
3302
|
-
is_below_floor,
|
|
3303
|
-
// The four below are exported since joshuafolkken/kit#1406, when the model-gap walk moved to
|
|
3304
|
-
// `time-model-gaps.ts` and needed the same predicates and the same opener test this walk uses. A
|
|
3305
|
-
// private copy there would be the clone `CLAUDE.md` prohibits, in the one place a drift would have
|
|
3306
|
-
// the distribution and the count disagreeing about what a round trip is.
|
|
3307
|
-
per_round_trip
|
|
3308
|
-
};
|
|
3309
|
-
|
|
3310
3747
|
// scripts/time-runtime/time-density.ts
|
|
3311
3748
|
var MIN_ROUND_TRIPS = 10;
|
|
3312
3749
|
var NOTICE_INTERVAL_MS = 3e5;
|
|
3313
|
-
var
|
|
3314
|
-
var
|
|
3750
|
+
var ONE_CALL2 = 1;
|
|
3751
|
+
var NONE5 = 0;
|
|
3315
3752
|
function has_whole_tail(text) {
|
|
3316
3753
|
const last = text.split("\n").findLast((line) => line.trim() !== "");
|
|
3317
3754
|
return last !== void 0 && time_spans.parse_line(last) !== void 0;
|
|
@@ -3320,10 +3757,10 @@ function assistant_lines(text) {
|
|
|
3320
3757
|
return text.split("\n").map((line) => time_spans.parse_line(line)).filter((line) => line?.type === time_spans.ASSISTANT_TYPE);
|
|
3321
3758
|
}
|
|
3322
3759
|
function last_turn_calls(text) {
|
|
3323
|
-
if (!has_whole_tail(text)) return
|
|
3760
|
+
if (!has_whole_tail(text)) return NONE5;
|
|
3324
3761
|
const lines = assistant_lines(text);
|
|
3325
3762
|
const message_id = lines.at(-1)?.message_id;
|
|
3326
|
-
if (message_id === void 0 || message_id === time_spans.NO_MESSAGE_ID) return
|
|
3763
|
+
if (message_id === void 0 || message_id === time_spans.NO_MESSAGE_ID) return NONE5;
|
|
3327
3764
|
return lines.filter((line) => line.message_id === message_id).flatMap((line) => line.blocks).filter((block) => block.type === cost_blocks.TOOL_USE_TYPE).length;
|
|
3328
3765
|
}
|
|
3329
3766
|
function read_window(text) {
|
|
@@ -3337,15 +3774,15 @@ function read_window(text) {
|
|
|
3337
3774
|
};
|
|
3338
3775
|
}
|
|
3339
3776
|
function is_due(reading, since_last_ms) {
|
|
3340
|
-
if (reading.round_trip_count < MIN_ROUND_TRIPS || reading.turn_calls !==
|
|
3777
|
+
if (reading.round_trip_count < MIN_ROUND_TRIPS || reading.turn_calls !== ONE_CALL2 || !time_round_trips.is_below_floor(reading.density)) {
|
|
3341
3778
|
return false;
|
|
3342
3779
|
}
|
|
3343
3780
|
return since_last_ms >= NOTICE_INTERVAL_MS;
|
|
3344
3781
|
}
|
|
3345
|
-
function format_notice(reading) {
|
|
3782
|
+
function format_notice(reading, candidates = "") {
|
|
3346
3783
|
const density = time_round_trips.format_density(reading.density);
|
|
3347
3784
|
const floor = time_round_trips.format_density(time_round_trips.CALLS_PER_ROUND_TRIP_FLOOR);
|
|
3348
|
-
return `\u26A0 batching: ${density} calls per round trip over the last ${String(reading.round_trip_count)}, under the ${floor} floor, and this turn issued one call. Independent reads, greps, gh queries and edits go out together; the criterion is whether this call's input needs another call's result, not what kind of call it is \u2014 prompts/collaboration-workflow/turn-batching.md
|
|
3785
|
+
return `\u26A0 batching: ${density} calls per round trip over the last ${String(reading.round_trip_count)}, under the ${floor} floor, and this turn issued one call. Independent reads, greps, gh queries and edits go out together; the criterion is whether this call's input needs another call's result, not what kind of call it is \u2014 prompts/collaboration-workflow/turn-batching.md.${candidates}`;
|
|
3349
3786
|
}
|
|
3350
3787
|
var time_density = {
|
|
3351
3788
|
MIN_ROUND_TRIPS,
|
|
@@ -3369,11 +3806,17 @@ var payload_schema = z5.object({
|
|
|
3369
3806
|
agent_id: z5.string().nullish()
|
|
3370
3807
|
});
|
|
3371
3808
|
var notice_schema = z5.object({ notified_at_ms: z5.number() });
|
|
3809
|
+
var call_schema = z5.object({ tool_name: z5.string().min(1), tool_input: z5.unknown() });
|
|
3372
3810
|
function parse_transcript_path(raw_payload) {
|
|
3373
3811
|
const parsed = payload_schema.safeParse(JSON.parse(raw_payload));
|
|
3374
3812
|
if (!parsed.success || parsed.data.transcript_path === void 0) return void 0;
|
|
3375
3813
|
return time_hook_transcript.transcript_of(parsed.data.transcript_path, parsed.data.agent_id);
|
|
3376
3814
|
}
|
|
3815
|
+
function parse_call(raw_payload) {
|
|
3816
|
+
const parsed = call_schema.safeParse(JSON.parse(raw_payload));
|
|
3817
|
+
if (!parsed.success) return void 0;
|
|
3818
|
+
return { name: parsed.data.tool_name, input: parsed.data.tool_input };
|
|
3819
|
+
}
|
|
3377
3820
|
function read_tail(file_path, max_bytes = TAIL_BYTES) {
|
|
3378
3821
|
const descriptor = openSync(file_path, "r");
|
|
3379
3822
|
try {
|
|
@@ -3407,11 +3850,14 @@ function arm_throttle(target, now_ms) {
|
|
|
3407
3850
|
function notice_for(raw_payload, now_ms) {
|
|
3408
3851
|
const transcript_path = parse_transcript_path(raw_payload);
|
|
3409
3852
|
if (transcript_path === void 0) return void 0;
|
|
3410
|
-
const
|
|
3853
|
+
const tail = read_tail(transcript_path);
|
|
3854
|
+
const reading = time_density.read_window(tail);
|
|
3411
3855
|
const target = notice_path(transcript_path);
|
|
3412
3856
|
if (!time_density.is_due(reading, now_ms - last_notice_ms(target))) return void 0;
|
|
3857
|
+
const candidates = time_batch_guard.pairing_candidates(tail, parse_call(raw_payload));
|
|
3858
|
+
if (candidates === "") return void 0;
|
|
3413
3859
|
arm_throttle(target, now_ms);
|
|
3414
|
-
return time_density.format_notice(reading);
|
|
3860
|
+
return time_density.format_notice(reading, candidates);
|
|
3415
3861
|
}
|
|
3416
3862
|
function density_notice(raw_payload, now_ms = Date.now()) {
|
|
3417
3863
|
try {
|
|
@@ -3435,12 +3881,11 @@ export {
|
|
|
3435
3881
|
ESLINT_EDIT_CACHE_FLAGS,
|
|
3436
3882
|
time_shell,
|
|
3437
3883
|
time_bundle_call,
|
|
3438
|
-
time_format,
|
|
3439
3884
|
cost_attribute,
|
|
3440
3885
|
time_reported_failure,
|
|
3441
3886
|
time_transcript_line,
|
|
3442
3887
|
time_spans,
|
|
3443
|
-
|
|
3888
|
+
time_batch_guard,
|
|
3444
3889
|
lane_paths,
|
|
3445
3890
|
file_reader,
|
|
3446
3891
|
cost_usage,
|