@joshuafolkken/kit 1.872.0 → 1.881.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/.github/workflows/publish.yml +39 -2
- package/README.md +3 -3
- package/dist/hooks/batch-guard.js +4 -4
- package/dist/hooks/{chunk-KABG6AEW.js → chunk-264QY6KS.js} +7 -0
- package/dist/hooks/{chunk-I4D3NVUD.js → chunk-36UE53LS.js} +2 -2
- package/dist/hooks/{chunk-WCYPBPJC.js → chunk-4OXFU3MK.js} +8 -8
- package/dist/hooks/{chunk-ABVEVV2M.js → chunk-6MQCXEEA.js} +1 -1
- package/dist/hooks/{chunk-46EEEHC3.js → chunk-FNFOLFFV.js} +1 -1
- package/dist/hooks/{chunk-5U5EZYG6.js → chunk-FRJKVSK3.js} +2 -2
- package/dist/hooks/{chunk-2RUPMGGV.js → chunk-PRDCNGA6.js} +4 -4
- package/dist/hooks/{chunk-WMN4TSVF.js → chunk-VGYSA2NT.js} +2 -2
- package/dist/hooks/{chunk-A5YJ3H7E.js → chunk-Y5OYCU2F.js} +3 -3
- package/dist/hooks/{chunk-4QHBNAI4.js → chunk-ZBK4XRAS.js} +3 -3
- package/dist/hooks/codex-hook-adapter.js +10 -10
- package/dist/hooks/duplicate-read-guard.js +5 -5
- package/dist/hooks/format-edited.js +2 -2
- package/dist/hooks/investigation-guard.js +5 -5
- package/dist/hooks/pretool-guard.js +9 -9
- package/dist/hooks/stop-guard.js +3 -3
- package/dist/josh.js +7 -0
- package/package.json +6 -2
- package/scripts/init/distributed-paths.ts +14 -11
- package/scripts/init/hook-command-bootstrap.ts +51 -0
- package/scripts/init/hook-command-rewrite.ts +9 -1
- package/scripts/init/init-logic.ts +2 -8
- package/scripts/josh/__snapshots__/josh-command-map.test.ts.snap +10 -0
- package/scripts/josh/josh-command-map.ts +1 -0
- package/scripts/josh/josh-commands-project.ts +6 -0
- package/scripts/registry-migration/migrate-logic.ts +229 -0
- package/scripts/registry-migration/migrate.ts +280 -0
- package/scripts/registry-migration/user-npmrc.ts +15 -0
- package/templates/workflows/ci.yml +6 -2
- package/dist/hooks/chunk-25JL6FMS.js +0 -4970
- package/dist/hooks/chunk-2ANGT3XM.js +0 -375
- package/dist/hooks/chunk-2EI5GBSW.js +0 -42
- package/dist/hooks/chunk-2OT5XE2N.js +0 -5014
- package/dist/hooks/chunk-2XIGZQMQ.js +0 -3299
- package/dist/hooks/chunk-33ELOKEV.js +0 -910
- package/dist/hooks/chunk-33YRTKM7.js +0 -230
- package/dist/hooks/chunk-36JQU5NN.js +0 -77
- package/dist/hooks/chunk-3EJBTWVS.js +0 -1769
- package/dist/hooks/chunk-3FK5SFAC.js +0 -432
- package/dist/hooks/chunk-3PWPYDD2.js +0 -240
- package/dist/hooks/chunk-3TQU7TDH.js +0 -375
- package/dist/hooks/chunk-3TVLSFZ6.js +0 -432
- package/dist/hooks/chunk-3VXYCB4E.js +0 -77
- package/dist/hooks/chunk-44M6JXGV.js +0 -3398
- package/dist/hooks/chunk-4A7UVP2P.js +0 -364
- package/dist/hooks/chunk-4LFQEZS4.js +0 -137
- package/dist/hooks/chunk-4OCX3V2T.js +0 -469
- package/dist/hooks/chunk-4OHA5DE7.js +0 -49
- package/dist/hooks/chunk-4PMDW3YD.js +0 -68
- package/dist/hooks/chunk-4SSB3FEE.js +0 -3273
- package/dist/hooks/chunk-5A2WQ2CQ.js +0 -5390
- package/dist/hooks/chunk-5EVA2JQB.js +0 -5390
- package/dist/hooks/chunk-5P7VF5VE.js +0 -3230
- package/dist/hooks/chunk-5QWJGGZP.js +0 -49
- package/dist/hooks/chunk-5VUUF3ZV.js +0 -240
- package/dist/hooks/chunk-5WA7UBT3.js +0 -374
- package/dist/hooks/chunk-6EBGRQ3Q.js +0 -301
- package/dist/hooks/chunk-6TVHWMD3.js +0 -432
- package/dist/hooks/chunk-6VAGLEMB.js +0 -68
- package/dist/hooks/chunk-75BTXKFJ.js +0 -3424
- package/dist/hooks/chunk-75RHN67S.js +0 -137
- package/dist/hooks/chunk-76KC3BDD.js +0 -3414
- package/dist/hooks/chunk-76SLS7CZ.js +0 -898
- package/dist/hooks/chunk-7DHZ4DWT.js +0 -5015
- package/dist/hooks/chunk-7INSJDGE.js +0 -458
- package/dist/hooks/chunk-7VJ4FTDX.js +0 -268
- package/dist/hooks/chunk-7XCYNHQK.js +0 -77
- package/dist/hooks/chunk-AAEXXUUL.js +0 -268
- package/dist/hooks/chunk-ACN3YXFL.js +0 -68
- package/dist/hooks/chunk-AD3XE3QZ.js +0 -397
- package/dist/hooks/chunk-AENFZPZH.js +0 -230
- package/dist/hooks/chunk-AFKRFQ6W.js +0 -68
- package/dist/hooks/chunk-AHD5IPWI.js +0 -375
- package/dist/hooks/chunk-AMK5JGV7.js +0 -77
- package/dist/hooks/chunk-ARN7NCQI.js +0 -425
- package/dist/hooks/chunk-AUFY653N.js +0 -3425
- package/dist/hooks/chunk-AXQBNFEF.js +0 -375
- package/dist/hooks/chunk-AYC7R6U4.js +0 -3316
- package/dist/hooks/chunk-AYEA2FJ7.js +0 -5015
- package/dist/hooks/chunk-B2LIXYFL.js +0 -77
- package/dist/hooks/chunk-B4HPNY2I.js +0 -375
- package/dist/hooks/chunk-B4YNCUGB.js +0 -469
- package/dist/hooks/chunk-B57S3VWT.js +0 -922
- package/dist/hooks/chunk-BINZ3PLI.js +0 -364
- package/dist/hooks/chunk-BIWLABFK.js +0 -432
- package/dist/hooks/chunk-BODUSXKK.js +0 -410
- package/dist/hooks/chunk-BQBAT2YK.js +0 -375
- package/dist/hooks/chunk-BUJEBW75.js +0 -391
- package/dist/hooks/chunk-BVNWORKA.js +0 -72
- package/dist/hooks/chunk-BWI47LQS.js +0 -5205
- package/dist/hooks/chunk-C25HRMJX.js +0 -137
- package/dist/hooks/chunk-CBFZNC74.js +0 -375
- package/dist/hooks/chunk-CECYXS4Y.js +0 -3424
- package/dist/hooks/chunk-CELGUP5F.js +0 -49
- package/dist/hooks/chunk-CMHANU3F.js +0 -3442
- package/dist/hooks/chunk-CMN372LI.js +0 -68
- package/dist/hooks/chunk-CPGZLMMK.js +0 -3299
- package/dist/hooks/chunk-CTQKXDC7.js +0 -268
- package/dist/hooks/chunk-CYAT4RIT.js +0 -68
- package/dist/hooks/chunk-CYTWYCH2.js +0 -5014
- package/dist/hooks/chunk-CZSOLRBA.js +0 -77
- package/dist/hooks/chunk-D2PIDYJR.js +0 -3438
- package/dist/hooks/chunk-DE57HI6T.js +0 -137
- package/dist/hooks/chunk-DGXDGAPG.js +0 -5306
- package/dist/hooks/chunk-DNYRSYQG.js +0 -5014
- package/dist/hooks/chunk-DTAXH2A2.js +0 -240
- package/dist/hooks/chunk-E2IZUWI5.js +0 -375
- package/dist/hooks/chunk-E4L2K2BL.js +0 -397
- package/dist/hooks/chunk-E55NKX5F.js +0 -68
- package/dist/hooks/chunk-ECXVCCVD.js +0 -3079
- package/dist/hooks/chunk-EPHO443D.js +0 -68
- package/dist/hooks/chunk-EUFK2BI2.js +0 -375
- package/dist/hooks/chunk-EYCD5ZAV.js +0 -137
- package/dist/hooks/chunk-F2SDYJU4.js +0 -240
- package/dist/hooks/chunk-F4B5D5TF.js +0 -68
- package/dist/hooks/chunk-FGEO3NDX.js +0 -432
- package/dist/hooks/chunk-FJYI52RE.js +0 -137
- package/dist/hooks/chunk-FYTZHIY4.js +0 -3085
- package/dist/hooks/chunk-G7JL2A5K.js +0 -375
- package/dist/hooks/chunk-GA3ITDBW.js +0 -42
- package/dist/hooks/chunk-GDR4H4SV.js +0 -469
- package/dist/hooks/chunk-GFKDXPSM.js +0 -2855
- package/dist/hooks/chunk-GIMRTKFU.js +0 -566
- package/dist/hooks/chunk-GOPHRLGO.js +0 -240
- package/dist/hooks/chunk-GX4HO7SJ.js +0 -230
- package/dist/hooks/chunk-GXKKK23T.js +0 -240
- package/dist/hooks/chunk-GZ6FUVIK.js +0 -566
- package/dist/hooks/chunk-H5HPY5M5.js +0 -2869
- package/dist/hooks/chunk-HCF3OGEE.js +0 -1829
- package/dist/hooks/chunk-HDEXAOUM.js +0 -469
- package/dist/hooks/chunk-HM2TY3TN.js +0 -469
- package/dist/hooks/chunk-HOA7NXA2.js +0 -55
- package/dist/hooks/chunk-HOFEWH6H.js +0 -240
- package/dist/hooks/chunk-HUUI6S5J.js +0 -2669
- package/dist/hooks/chunk-I4Q6WAJE.js +0 -68
- package/dist/hooks/chunk-IB6N4CR2.js +0 -432
- package/dist/hooks/chunk-IFLDYJMD.js +0 -469
- package/dist/hooks/chunk-IGU466YD.js +0 -77
- package/dist/hooks/chunk-IJCQUHKY.js +0 -375
- package/dist/hooks/chunk-IKUMBDJB.js +0 -230
- package/dist/hooks/chunk-IM3BDIBJ.js +0 -5005
- package/dist/hooks/chunk-IMYMAODK.js +0 -3431
- package/dist/hooks/chunk-IN2ZVGXA.js +0 -5005
- package/dist/hooks/chunk-ITVJ2MKQ.js +0 -18
- package/dist/hooks/chunk-J5RIJYTM.js +0 -2669
- package/dist/hooks/chunk-JFLMH4K5.js +0 -3460
- package/dist/hooks/chunk-JR25IFML.js +0 -77
- package/dist/hooks/chunk-JT5AUXFI.js +0 -432
- package/dist/hooks/chunk-JY5YVN6N.js +0 -3234
- package/dist/hooks/chunk-K5Q4ETVI.js +0 -391
- package/dist/hooks/chunk-K67V2F24.js +0 -137
- package/dist/hooks/chunk-KATZESEC.js +0 -3273
- package/dist/hooks/chunk-KFH4H7W7.js +0 -469
- package/dist/hooks/chunk-KMUEI6RX.js +0 -910
- package/dist/hooks/chunk-KMV4OY77.js +0 -3438
- package/dist/hooks/chunk-KQAZNWEY.js +0 -137
- package/dist/hooks/chunk-KZZR6JER.js +0 -5015
- package/dist/hooks/chunk-LAR6DREP.js +0 -77
- package/dist/hooks/chunk-LNCJWEZD.js +0 -5015
- package/dist/hooks/chunk-LRYD2GLM.js +0 -6
- package/dist/hooks/chunk-LXBKYHDV.js +0 -77
- package/dist/hooks/chunk-LYLKIUNE.js +0 -5024
- package/dist/hooks/chunk-LZKCFOVC.js +0 -432
- package/dist/hooks/chunk-M4FCIYRI.js +0 -77
- package/dist/hooks/chunk-M5M7327Q.js +0 -3425
- package/dist/hooks/chunk-MF72MHSX.js +0 -3455
- package/dist/hooks/chunk-MK56LKDO.js +0 -397
- package/dist/hooks/chunk-N3MJADY7.js +0 -240
- package/dist/hooks/chunk-NFQSUDBN.js +0 -458
- package/dist/hooks/chunk-NHREKY45.js +0 -137
- package/dist/hooks/chunk-NP5TTAAW.js +0 -375
- package/dist/hooks/chunk-NQTLTKTJ.js +0 -2993
- package/dist/hooks/chunk-NVRCZPBZ.js +0 -391
- package/dist/hooks/chunk-O46HMMLJ.js +0 -469
- package/dist/hooks/chunk-OFJGH2L4.js +0 -240
- package/dist/hooks/chunk-OLFMCZH5.js +0 -240
- package/dist/hooks/chunk-OMCZS46I.js +0 -49
- package/dist/hooks/chunk-OQZEZW5G.js +0 -469
- package/dist/hooks/chunk-P2MDAT2Y.js +0 -1897
- package/dist/hooks/chunk-P3M2JJIF.js +0 -68
- package/dist/hooks/chunk-PDVHQITQ.js +0 -3453
- package/dist/hooks/chunk-PNT2AMDH.js +0 -391
- package/dist/hooks/chunk-PR2LVSY6.js +0 -3445
- package/dist/hooks/chunk-PRR5R4JK.js +0 -137
- package/dist/hooks/chunk-PXT44QVN.js +0 -566
- package/dist/hooks/chunk-PZLWJLAP.js +0 -469
- package/dist/hooks/chunk-Q2FEAZJ2.js +0 -77
- package/dist/hooks/chunk-Q32WETDO.js +0 -3265
- package/dist/hooks/chunk-Q45DUBLE.js +0 -3600
- package/dist/hooks/chunk-Q7NZSWQ5.js +0 -240
- package/dist/hooks/chunk-QH2JNAMK.js +0 -3085
- package/dist/hooks/chunk-QNNWYQ2R.js +0 -375
- package/dist/hooks/chunk-QPU5H2OS.js +0 -68
- package/dist/hooks/chunk-QRAHZ5TO.js +0 -1829
- package/dist/hooks/chunk-QUZ5HPIE.js +0 -469
- package/dist/hooks/chunk-QVCRG34O.js +0 -2893
- package/dist/hooks/chunk-R3MY4E3S.js +0 -1675
- package/dist/hooks/chunk-R6TXJJXP.js +0 -77
- package/dist/hooks/chunk-REJU6VT7.js +0 -1829
- package/dist/hooks/chunk-REQAQLRC.js +0 -2993
- package/dist/hooks/chunk-RFCXPAHH.js +0 -391
- package/dist/hooks/chunk-RHI6Y22X.js +0 -137
- package/dist/hooks/chunk-RKZVOBBS.js +0 -469
- package/dist/hooks/chunk-RL57TT2Y.js +0 -566
- package/dist/hooks/chunk-RORDOD4S.js +0 -3321
- package/dist/hooks/chunk-RZKYW5P2.js +0 -77
- package/dist/hooks/chunk-SJPJ6VSI.js +0 -391
- package/dist/hooks/chunk-SKSIJQQQ.js +0 -432
- package/dist/hooks/chunk-SSQDZKXG.js +0 -268
- package/dist/hooks/chunk-SYHFKE4C.js +0 -77
- package/dist/hooks/chunk-TCGNI2ZY.js +0 -42
- package/dist/hooks/chunk-TG3WXJF5.js +0 -137
- package/dist/hooks/chunk-TH7IJAY5.js +0 -77
- package/dist/hooks/chunk-TLZ27PCY.js +0 -240
- package/dist/hooks/chunk-U652FXTN.js +0 -566
- package/dist/hooks/chunk-UEK47YUF.js +0 -268
- package/dist/hooks/chunk-UIO45H3O.js +0 -432
- package/dist/hooks/chunk-UNLZEEVH.js +0 -5005
- package/dist/hooks/chunk-UNRVF3SZ.js +0 -432
- package/dist/hooks/chunk-USLFAFOI.js +0 -3453
- package/dist/hooks/chunk-VAEELMLL.js +0 -442
- package/dist/hooks/chunk-VALSWLDQ.js +0 -3453
- package/dist/hooks/chunk-VC5JEPNY.js +0 -3398
- package/dist/hooks/chunk-VEZVK6C7.js +0 -230
- package/dist/hooks/chunk-VK5JKFAR.js +0 -49
- package/dist/hooks/chunk-VNF2KP7Z.js +0 -469
- package/dist/hooks/chunk-VWWCUMW6.js +0 -2669
- package/dist/hooks/chunk-W4JARUQV.js +0 -137
- package/dist/hooks/chunk-WEOBMNJG.js +0 -2993
- package/dist/hooks/chunk-WKJU3J5Y.js +0 -5390
- package/dist/hooks/chunk-WNON54PT.js +0 -3469
- package/dist/hooks/chunk-WOVKBPPX.js +0 -137
- package/dist/hooks/chunk-WRVFORYJ.js +0 -432
- package/dist/hooks/chunk-WUBVYGQZ.js +0 -240
- package/dist/hooks/chunk-WWULTUOM.js +0 -68
- package/dist/hooks/chunk-WXHKKB45.js +0 -4962
- package/dist/hooks/chunk-XAI3YTHJ.js +0 -469
- package/dist/hooks/chunk-XFZH6JZJ.js +0 -77
- package/dist/hooks/chunk-XGPWX6IA.js +0 -137
- package/dist/hooks/chunk-XIXXIVGY.js +0 -566
- package/dist/hooks/chunk-XKDOYJGA.js +0 -922
- package/dist/hooks/chunk-YALTVBHM.js +0 -432
- package/dist/hooks/chunk-YEBEPBFK.js +0 -5014
- package/dist/hooks/chunk-YI2BQ3DK.js +0 -3441
- package/dist/hooks/chunk-YVNBCFGG.js +0 -1829
- package/dist/hooks/chunk-YXBN3RGG.js +0 -432
- package/dist/hooks/chunk-Z2OOJSW3.js +0 -3163
- package/dist/hooks/chunk-ZA3HVBOY.js +0 -2644
- package/dist/hooks/chunk-ZKID46U3.js +0 -137
- package/dist/hooks/chunk-ZPMU2JV6.js +0 -49
- package/dist/hooks/chunk-ZRKY4W7O.js +0 -432
- package/dist/hooks/chunk-ZTMRB7LJ.js +0 -268
- package/dist/hooks/chunk-ZVPPBQ3L.js +0 -3413
|
@@ -1,1829 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
batch_outcome
|
|
3
|
-
} from "./chunk-VK5JKFAR.js";
|
|
4
|
-
import {
|
|
5
|
-
investigation_reads,
|
|
6
|
-
investigation_refusal
|
|
7
|
-
} from "./chunk-SJPJ6VSI.js";
|
|
8
|
-
import {
|
|
9
|
-
hook_decision,
|
|
10
|
-
time_batch_guard
|
|
11
|
-
} from "./chunk-U652FXTN.js";
|
|
12
|
-
import {
|
|
13
|
-
cost_blocks,
|
|
14
|
-
json_value,
|
|
15
|
-
lane_paths,
|
|
16
|
-
stamp_file,
|
|
17
|
-
time_shell,
|
|
18
|
-
time_transcript_line
|
|
19
|
-
} from "./chunk-REQAQLRC.js";
|
|
20
|
-
|
|
21
|
-
// scripts/hooks/pretool-guard.ts
|
|
22
|
-
import { text } from "node:stream/consumers";
|
|
23
|
-
import { fileURLToPath } from "node:url";
|
|
24
|
-
|
|
25
|
-
// scripts/run/run-progress.ts
|
|
26
|
-
var DEFAULT_INTERVAL_MINUTES = 20;
|
|
27
|
-
var INTERVAL_KEY = "JOSH_PROGRESS_INTERVAL_MINUTES";
|
|
28
|
-
var MS_PER_MINUTE = 6e4;
|
|
29
|
-
var MS_PER_SECOND = 1e3;
|
|
30
|
-
var DEFAULT_INTERVAL_MS = DEFAULT_INTERVAL_MINUTES * MS_PER_MINUTE;
|
|
31
|
-
var NO_LANES = "none";
|
|
32
|
-
var NO_CHILD_YET = "no in-progress child yet";
|
|
33
|
-
var NO_NEXT = "none";
|
|
34
|
-
var QUIET_MARKER = "\u23F3";
|
|
35
|
-
var STAMP_END = 16;
|
|
36
|
-
var PAD_WIDTH = 2;
|
|
37
|
-
var MINUTES_PER_HOUR = 60;
|
|
38
|
-
function to_minutes(elapsed_ms) {
|
|
39
|
-
return Math.floor(elapsed_ms / MS_PER_MINUTE);
|
|
40
|
-
}
|
|
41
|
-
function format_minutes(elapsed_ms) {
|
|
42
|
-
return `${String(to_minutes(elapsed_ms))}m`;
|
|
43
|
-
}
|
|
44
|
-
function pad(value) {
|
|
45
|
-
return String(value).padStart(PAD_WIDTH, "0");
|
|
46
|
-
}
|
|
47
|
-
function offset_minutes(date) {
|
|
48
|
-
return Math.round(-date.getTimezoneOffset());
|
|
49
|
-
}
|
|
50
|
-
function format_offset(total_minutes) {
|
|
51
|
-
const sign = total_minutes < 0 ? "-" : "+";
|
|
52
|
-
const absolute = Math.abs(total_minutes);
|
|
53
|
-
return `${sign}${pad(Math.floor(absolute / MINUTES_PER_HOUR))}:${pad(absolute % MINUTES_PER_HOUR)}`;
|
|
54
|
-
}
|
|
55
|
-
function format_observed_at(now_ms) {
|
|
56
|
-
const minutes = offset_minutes(new Date(now_ms));
|
|
57
|
-
const local = new Date(now_ms + minutes * MS_PER_MINUTE);
|
|
58
|
-
const day = `${String(local.getUTCFullYear())}-${pad(local.getUTCMonth() + 1)}-${pad(local.getUTCDate())}`;
|
|
59
|
-
const clock = `${pad(local.getUTCHours())}:${pad(local.getUTCMinutes())}${format_offset(minutes)}`;
|
|
60
|
-
return `${day} ${clock} / ${new Date(now_ms).toISOString().slice(0, STAMP_END)}Z`;
|
|
61
|
-
}
|
|
62
|
-
function observation_key(observations) {
|
|
63
|
-
return JSON.stringify({ children: observations.children, lanes: observations.lanes });
|
|
64
|
-
}
|
|
65
|
-
function next_state(previous, key, now_ms) {
|
|
66
|
-
if (previous?.key === key) return previous;
|
|
67
|
-
return { key, unchanged_since_ms: now_ms };
|
|
68
|
-
}
|
|
69
|
-
function format_child(child) {
|
|
70
|
-
const labels = child.labels.length > 0 ? child.labels.join(",") : "(no labels)";
|
|
71
|
-
return `#${child.issue} ${labels} PR:${child.pr_state}`;
|
|
72
|
-
}
|
|
73
|
-
function format_children(children) {
|
|
74
|
-
if (children.length === 0) return NO_CHILD_YET;
|
|
75
|
-
return children.map((child) => format_child(child)).join(" \xB7 ");
|
|
76
|
-
}
|
|
77
|
-
function format_lanes(lanes) {
|
|
78
|
-
if (lanes.length === 0) return NO_LANES;
|
|
79
|
-
return lanes.map((lane) => `${lane.issue}:${lane.state}`).join(" ");
|
|
80
|
-
}
|
|
81
|
-
function format_record(record_age_ms) {
|
|
82
|
-
return record_age_ms === void 0 ? "unread" : `+${format_minutes(record_age_ms)}`;
|
|
83
|
-
}
|
|
84
|
-
function format_next_report(now_ms, interval_ms) {
|
|
85
|
-
return format_observed_at(now_ms + interval_ms);
|
|
86
|
-
}
|
|
87
|
-
function format_next_line(next) {
|
|
88
|
-
return `next ${next ?? NO_NEXT}`;
|
|
89
|
-
}
|
|
90
|
-
function format_report(fields) {
|
|
91
|
-
return [
|
|
92
|
-
`${QUIET_MARKER} at ${fields.observed_at}`,
|
|
93
|
-
`quiet ${fields.quiet} \xB7 unchanged ${fields.unchanged}`,
|
|
94
|
-
`children ${fields.children}`,
|
|
95
|
-
`lanes ${fields.lanes} \xB7 load ${fields.load} \xB7 record ${fields.record}`,
|
|
96
|
-
format_next_line(fields.next)
|
|
97
|
-
].join("\n");
|
|
98
|
-
}
|
|
99
|
-
function format_line(observations, timing) {
|
|
100
|
-
return format_report({
|
|
101
|
-
observed_at: format_observed_at(timing.now_ms),
|
|
102
|
-
quiet: format_minutes(timing.now_ms - timing.quiet_since_ms),
|
|
103
|
-
unchanged: format_minutes(timing.now_ms - timing.unchanged_since_ms),
|
|
104
|
-
children: format_children(observations.children),
|
|
105
|
-
lanes: format_lanes(observations.lanes),
|
|
106
|
-
load: observations.load_average.toFixed(1),
|
|
107
|
-
record: format_record(observations.record_age_ms),
|
|
108
|
-
next: format_next_report(timing.now_ms, timing.interval_ms)
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
function is_due(last_report_ms, now_ms, interval_ms) {
|
|
112
|
-
return now_ms - last_report_ms >= interval_ms;
|
|
113
|
-
}
|
|
114
|
-
function minutes_from(raw) {
|
|
115
|
-
const minutes = Number(raw);
|
|
116
|
-
if (raw === void 0 || raw.trim() === "" || !Number.isFinite(minutes) || minutes <= 0) {
|
|
117
|
-
return void 0;
|
|
118
|
-
}
|
|
119
|
-
return minutes;
|
|
120
|
-
}
|
|
121
|
-
var run_progress = {
|
|
122
|
-
DEFAULT_INTERVAL_MINUTES,
|
|
123
|
-
DEFAULT_INTERVAL_MS,
|
|
124
|
-
INTERVAL_KEY,
|
|
125
|
-
MS_PER_MINUTE,
|
|
126
|
-
MS_PER_SECOND,
|
|
127
|
-
NO_CHILD_YET,
|
|
128
|
-
NO_NEXT,
|
|
129
|
-
format_child,
|
|
130
|
-
format_children,
|
|
131
|
-
format_lanes,
|
|
132
|
-
format_line,
|
|
133
|
-
format_next_line,
|
|
134
|
-
format_next_report,
|
|
135
|
-
format_record,
|
|
136
|
-
format_report,
|
|
137
|
-
is_due,
|
|
138
|
-
minutes_from,
|
|
139
|
-
next_state,
|
|
140
|
-
observation_key
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
// scripts/git/constants.ts
|
|
144
|
-
import { platform } from "node:os";
|
|
145
|
-
var GIT_COMMAND_UNIX = "/usr/bin/git";
|
|
146
|
-
var PORCELAIN_FLAG = "--porcelain";
|
|
147
|
-
function get_git_command() {
|
|
148
|
-
if (platform() === "win32") {
|
|
149
|
-
return String.raw`"C:\Program Files\Git\cmd\git.exe"`;
|
|
150
|
-
}
|
|
151
|
-
return GIT_COMMAND_UNIX;
|
|
152
|
-
}
|
|
153
|
-
function get_git_command_for_spawn() {
|
|
154
|
-
if (platform() === "win32") {
|
|
155
|
-
return String.raw`C:\Program Files\Git\cmd\git.exe`;
|
|
156
|
-
}
|
|
157
|
-
return GIT_COMMAND_UNIX;
|
|
158
|
-
}
|
|
159
|
-
var git_utilities = {
|
|
160
|
-
get_git_command,
|
|
161
|
-
get_git_command_for_spawn
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
// scripts/run/run-progress-clock.ts
|
|
165
|
-
import { execaSync } from "execa";
|
|
166
|
-
import { z } from "zod";
|
|
167
|
-
var PROGRESS_PREFIX = "josh-run-progress-";
|
|
168
|
-
var LIFE_PREFIX = "josh-run-progress-life-";
|
|
169
|
-
var GIT_TIMEOUT_MS = 5e3;
|
|
170
|
-
var GIT_DIRECTORY_ARGUMENTS = ["rev-parse", "--absolute-git-dir"];
|
|
171
|
-
var report_stamp_schema = z.object({ reported_at: z.string(), line: z.string().optional() });
|
|
172
|
-
function parse_stamp(raw) {
|
|
173
|
-
const parsed = report_stamp_schema.safeParse(JSON.parse(raw));
|
|
174
|
-
if (!parsed.success) return void 0;
|
|
175
|
-
const reported_at = Date.parse(parsed.data.reported_at);
|
|
176
|
-
return Number.isNaN(reported_at) ? void 0 : reported_at;
|
|
177
|
-
}
|
|
178
|
-
function read_last_line(target) {
|
|
179
|
-
const raw = stamp_file.read_stamp_text(target);
|
|
180
|
-
if (raw === void 0) return void 0;
|
|
181
|
-
try {
|
|
182
|
-
return report_stamp_schema.safeParse(JSON.parse(raw)).data?.line;
|
|
183
|
-
} catch {
|
|
184
|
-
return void 0;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
function read_last_report(target) {
|
|
188
|
-
const raw = stamp_file.read_stamp_text(target);
|
|
189
|
-
if (raw === void 0) return void 0;
|
|
190
|
-
try {
|
|
191
|
-
return parse_stamp(raw);
|
|
192
|
-
} catch {
|
|
193
|
-
return void 0;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
function mark(target, now_ms, line) {
|
|
197
|
-
const reported_at = new Date(now_ms).toISOString();
|
|
198
|
-
const kept = line ?? read_last_line(target);
|
|
199
|
-
stamp_file.write_stamp(target, kept === void 0 ? { reported_at } : { reported_at, line: kept });
|
|
200
|
-
}
|
|
201
|
-
function stamp_target_of(git_directory) {
|
|
202
|
-
return stamp_file.stamp_path(PROGRESS_PREFIX, git_directory);
|
|
203
|
-
}
|
|
204
|
-
function life_target_of(git_directory) {
|
|
205
|
-
return stamp_file.stamp_path(LIFE_PREFIX, git_directory);
|
|
206
|
-
}
|
|
207
|
-
function begin_life(target) {
|
|
208
|
-
stamp_file.write_stamp(target, { alive: true });
|
|
209
|
-
}
|
|
210
|
-
function is_life_ended(target) {
|
|
211
|
-
return stamp_file.read_stamp_text(target) === void 0;
|
|
212
|
-
}
|
|
213
|
-
function end_life(target) {
|
|
214
|
-
stamp_file.remove_stamp(target);
|
|
215
|
-
}
|
|
216
|
-
function git_directory_sync() {
|
|
217
|
-
const git_binary = git_utilities.get_git_command_for_spawn();
|
|
218
|
-
const result = execaSync(git_binary, GIT_DIRECTORY_ARGUMENTS, {
|
|
219
|
-
reject: false,
|
|
220
|
-
timeout: GIT_TIMEOUT_MS
|
|
221
|
-
});
|
|
222
|
-
if (result.exitCode !== 0) return void 0;
|
|
223
|
-
const output = result.stdout.trim();
|
|
224
|
-
return output === "" ? void 0 : output;
|
|
225
|
-
}
|
|
226
|
-
function stamp_target_sync() {
|
|
227
|
-
const directory = git_directory_sync();
|
|
228
|
-
return directory === void 0 ? void 0 : stamp_target_of(directory);
|
|
229
|
-
}
|
|
230
|
-
function read_last_report_sync() {
|
|
231
|
-
const target = stamp_target_sync();
|
|
232
|
-
return target === void 0 ? void 0 : read_last_report(target);
|
|
233
|
-
}
|
|
234
|
-
var run_progress_clock = {
|
|
235
|
-
LIFE_PREFIX,
|
|
236
|
-
PROGRESS_PREFIX,
|
|
237
|
-
begin_life,
|
|
238
|
-
end_life,
|
|
239
|
-
is_life_ended,
|
|
240
|
-
life_target_of,
|
|
241
|
-
mark,
|
|
242
|
-
parse_stamp,
|
|
243
|
-
read_last_line,
|
|
244
|
-
read_last_report,
|
|
245
|
-
read_last_report_sync,
|
|
246
|
-
stamp_target_of,
|
|
247
|
-
stamp_target_sync
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
// scripts/run/run-progress-config.ts
|
|
251
|
-
import { readFileSync } from "node:fs";
|
|
252
|
-
import path from "node:path";
|
|
253
|
-
import { z as z2 } from "zod";
|
|
254
|
-
var PACKAGE_FILE = "package.json";
|
|
255
|
-
var CONFIG_FIELD = "josh";
|
|
256
|
-
var INTERVAL_FIELD = "progress_interval_minutes";
|
|
257
|
-
var MAX_DEPTH = 10;
|
|
258
|
-
var package_schema = z2.object({
|
|
259
|
-
josh: z2.object({ progress_interval_minutes: z2.number().positive() }).optional()
|
|
260
|
-
});
|
|
261
|
-
function ancestors_of(start) {
|
|
262
|
-
const directories = [];
|
|
263
|
-
let current = path.resolve(start);
|
|
264
|
-
while (directories.length < MAX_DEPTH && !directories.includes(current)) {
|
|
265
|
-
directories.push(current);
|
|
266
|
-
current = path.dirname(current);
|
|
267
|
-
}
|
|
268
|
-
return directories;
|
|
269
|
-
}
|
|
270
|
-
function read_declared_minutes(directory) {
|
|
271
|
-
try {
|
|
272
|
-
const raw = readFileSync(path.join(directory, PACKAGE_FILE), "utf8");
|
|
273
|
-
const parsed = package_schema.safeParse(JSON.parse(raw));
|
|
274
|
-
return parsed.success ? parsed.data.josh?.progress_interval_minutes : void 0;
|
|
275
|
-
} catch {
|
|
276
|
-
return void 0;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
function read_package_minutes(start) {
|
|
280
|
-
for (const directory of ancestors_of(start)) {
|
|
281
|
-
const minutes = read_declared_minutes(directory);
|
|
282
|
-
if (minutes !== void 0) return minutes;
|
|
283
|
-
}
|
|
284
|
-
return void 0;
|
|
285
|
-
}
|
|
286
|
-
function to_ms(minutes) {
|
|
287
|
-
return minutes === void 0 ? void 0 : minutes * run_progress.MS_PER_MINUTE;
|
|
288
|
-
}
|
|
289
|
-
function resolve_interval_ms(raw, directory = process.cwd()) {
|
|
290
|
-
return to_ms(run_progress.minutes_from(raw)) ?? to_ms(run_progress.minutes_from(process.env[run_progress.INTERVAL_KEY])) ?? to_ms(read_package_minutes(directory)) ?? run_progress.DEFAULT_INTERVAL_MS;
|
|
291
|
-
}
|
|
292
|
-
function configured_interval_ms(directory = process.cwd()) {
|
|
293
|
-
return resolve_interval_ms(void 0, directory);
|
|
294
|
-
}
|
|
295
|
-
var run_progress_config = {
|
|
296
|
-
CONFIG_FIELD,
|
|
297
|
-
INTERVAL_FIELD,
|
|
298
|
-
PACKAGE_FILE,
|
|
299
|
-
configured_interval_ms,
|
|
300
|
-
read_package_minutes,
|
|
301
|
-
resolve_interval_ms
|
|
302
|
-
};
|
|
303
|
-
|
|
304
|
-
// scripts/rules/shell-segments.ts
|
|
305
|
-
var SEGMENT_SEPARATOR = /&&|\|\||;|\n/u;
|
|
306
|
-
function segments_of(command) {
|
|
307
|
-
return command.split(SEGMENT_SEPARATOR).map((segment) => segment.trim());
|
|
308
|
-
}
|
|
309
|
-
function is_josh_command(segment, names) {
|
|
310
|
-
const named = time_shell.josh_command_of(segment);
|
|
311
|
-
if (!named.startsWith(time_shell.JOSH_PREFIX)) return false;
|
|
312
|
-
return names.has(named.slice(time_shell.JOSH_PREFIX.length));
|
|
313
|
-
}
|
|
314
|
-
var shell_segments = {
|
|
315
|
-
SEGMENT_SEPARATOR,
|
|
316
|
-
is_josh_command,
|
|
317
|
-
segments_of
|
|
318
|
-
};
|
|
319
|
-
|
|
320
|
-
// scripts/rules/early-heartbeat.ts
|
|
321
|
-
var ARM_PREFIX = "josh-heartbeat-arm-";
|
|
322
|
-
var SEGMENT_SEPARATOR2 = /&&|\|\||;|&|\n/u;
|
|
323
|
-
var SLEEP_SEGMENT = /^sleep\s+(\d+(?:\.\d+)?)([smhd]?)$/u;
|
|
324
|
-
var INERT_SEGMENT = /^(?::|true|(?:echo|printf|date)\b.*)$/u;
|
|
325
|
-
var LOOP_KEYWORD = /\b(?:while|until|for|do|done)\b/u;
|
|
326
|
-
var SECONDS_PER_UNIT = { d: 86400, h: 3600, m: 60, s: 1 };
|
|
327
|
-
var BARE_NUMBER_SECONDS = 1;
|
|
328
|
-
var AMOUNT_GROUP = 1;
|
|
329
|
-
var UNIT_GROUP = 2;
|
|
330
|
-
var NO_SECONDS = 0;
|
|
331
|
-
var ARM_RECORD = hook_decision.create_refusal_stamp(ARM_PREFIX);
|
|
332
|
-
function segments_of2(command) {
|
|
333
|
-
return command.split(SEGMENT_SEPARATOR2).map((segment) => segment.trim()).filter((segment) => segment !== "");
|
|
334
|
-
}
|
|
335
|
-
function sleep_seconds(segment) {
|
|
336
|
-
const matched = SLEEP_SEGMENT.exec(segment);
|
|
337
|
-
if (matched === null) return void 0;
|
|
338
|
-
const unit = matched[UNIT_GROUP] ?? "";
|
|
339
|
-
return Number(matched[AMOUNT_GROUP]) * (SECONDS_PER_UNIT[unit] ?? BARE_NUMBER_SECONDS);
|
|
340
|
-
}
|
|
341
|
-
function is_wait_or_inert(segment) {
|
|
342
|
-
return sleep_seconds(segment) !== void 0 || INERT_SEGMENT.test(segment);
|
|
343
|
-
}
|
|
344
|
-
function is_wait_timer(command) {
|
|
345
|
-
if (LOOP_KEYWORD.test(command)) return false;
|
|
346
|
-
const segments = segments_of2(command);
|
|
347
|
-
if (segments.every((segment) => sleep_seconds(segment) === void 0)) return false;
|
|
348
|
-
return segments.every((segment) => is_wait_or_inert(segment));
|
|
349
|
-
}
|
|
350
|
-
function wait_duration_ms(command) {
|
|
351
|
-
const seconds = segments_of2(command).reduce(
|
|
352
|
-
(total, segment) => total + (sleep_seconds(segment) ?? NO_SECONDS),
|
|
353
|
-
NO_SECONDS
|
|
354
|
-
);
|
|
355
|
-
return seconds * run_progress.MS_PER_SECOND;
|
|
356
|
-
}
|
|
357
|
-
function is_armed(target, now_ms) {
|
|
358
|
-
return now_ms < ARM_RECORD.last_ms(target);
|
|
359
|
-
}
|
|
360
|
-
function landing_ms(command, now_ms, last_report_ms) {
|
|
361
|
-
const interval_ms = run_progress_config.configured_interval_ms();
|
|
362
|
-
const duration_ms = wait_duration_ms(command);
|
|
363
|
-
if (duration_ms > interval_ms) return void 0;
|
|
364
|
-
const lands_ms = now_ms + duration_ms;
|
|
365
|
-
return run_progress.is_due(last_report_ms, lands_ms, interval_ms) ? lands_ms : void 0;
|
|
366
|
-
}
|
|
367
|
-
function decide(call, run, can_record) {
|
|
368
|
-
const last_report_ms = run_progress_clock.read_last_report_sync();
|
|
369
|
-
if (last_report_ms === void 0) return false;
|
|
370
|
-
const target = ARM_RECORD.path(run.transcript);
|
|
371
|
-
if (is_armed(target, run.now_ms)) return true;
|
|
372
|
-
const lands_ms = landing_ms(time_shell.bash_command(call.input), run.now_ms, last_report_ms);
|
|
373
|
-
if (lands_ms === void 0) return true;
|
|
374
|
-
if (can_record) ARM_RECORD.record(target, lands_ms);
|
|
375
|
-
return false;
|
|
376
|
-
}
|
|
377
|
-
var EARLY_HEARTBEAT_REASON = '\u26D4 early heartbeat: this call arms a wait timer of its own, and the progress clock is not yours to keep. `pnpm josh run:progress --wait` waits the interval out for you (`JOSH_PROGRESS_INTERVAL_MINUTES`, or `josh.progress_interval_minutes` in `package.json`, default 20 minutes), prints the five labelled lines and exits \u2014 start it in the background, relay the lines it printed when it exits, and start the next one. Two timers armed at once is what produced reports 3\u20135 minutes apart on a 15-minute setting (joshuafolkken/kit#1570): a new one was armed on the turn a timer fired and again on the turn a child\'s completion woke the run, and `--mark` recorded each report without anything refusing the early ones. **An explicit ask is not a heartbeat**: if the person asked for progress now, run `pnpm josh run:progress --once`, which prints the five labelled lines whatever the clock says. If you are waiting on something rather than on a clock, wait on the thing itself \u2014 `pnpm josh followup` waits for CI. The procedure is `.claude/skills/workflow-commands/backlogrun.md` \u2192 "Progress while the run is quiet". **This rule fires on every early arm, not once per run**, so reissuing the same sleep will be refused again.';
|
|
378
|
-
var PROGRESS_NAMES = /* @__PURE__ */ new Set(["run:progress"]);
|
|
379
|
-
var WATCH_FLAG = /(?:^|\s)--(?:wait|once)(?=\s|$)/u;
|
|
380
|
-
function is_progress_watch_segment(segment) {
|
|
381
|
-
return shell_segments.is_josh_command(segment, PROGRESS_NAMES) && WATCH_FLAG.test(segment);
|
|
382
|
-
}
|
|
383
|
-
function is_progress_watch(command) {
|
|
384
|
-
return segments_of2(time_shell.unquoted(command)).some(
|
|
385
|
-
(segment) => is_progress_watch_segment(segment)
|
|
386
|
-
);
|
|
387
|
-
}
|
|
388
|
-
function waits_for_progress(command) {
|
|
389
|
-
return is_wait_timer(command) || is_progress_watch(command);
|
|
390
|
-
}
|
|
391
|
-
var early_heartbeat = {
|
|
392
|
-
ARM_PREFIX,
|
|
393
|
-
EARLY_HEARTBEAT_REASON,
|
|
394
|
-
decide,
|
|
395
|
-
is_progress_watch,
|
|
396
|
-
is_wait_timer,
|
|
397
|
-
wait_duration_ms,
|
|
398
|
-
waits_for_progress
|
|
399
|
-
};
|
|
400
|
-
|
|
401
|
-
// scripts/git/issue-labels.ts
|
|
402
|
-
var NEEDS_DECISION_LABEL = "needs-decision";
|
|
403
|
-
|
|
404
|
-
// scripts/run/run-issue-number.ts
|
|
405
|
-
var ISSUE_NUMBER_PATTERN = /^[1-9]\d*$/u;
|
|
406
|
-
var BAD_ISSUE_MESSAGE = "Not an issue number: ";
|
|
407
|
-
function require_issue_number(issue) {
|
|
408
|
-
if (!ISSUE_NUMBER_PATTERN.test(issue)) throw new Error(`${BAD_ISSUE_MESSAGE}${issue}`);
|
|
409
|
-
}
|
|
410
|
-
var run_issue_number = { BAD_ISSUE_MESSAGE, ISSUE_NUMBER_PATTERN, require_issue_number };
|
|
411
|
-
|
|
412
|
-
// scripts/lane/lane-child-marker.ts
|
|
413
|
-
var KEY = "JOSH_LANE_CHILD";
|
|
414
|
-
function environment_for(issue) {
|
|
415
|
-
run_issue_number.require_issue_number(issue);
|
|
416
|
-
return { [KEY]: issue };
|
|
417
|
-
}
|
|
418
|
-
function marked_issue(source = process.env) {
|
|
419
|
-
const value = source[KEY];
|
|
420
|
-
if (value === void 0) return void 0;
|
|
421
|
-
return run_issue_number.ISSUE_NUMBER_PATTERN.test(value) ? value : void 0;
|
|
422
|
-
}
|
|
423
|
-
function is_child_of(directory, source = process.env) {
|
|
424
|
-
const marked = marked_issue(source);
|
|
425
|
-
return marked !== void 0 && marked === lane_paths.lane_issue_of(directory, { ...source });
|
|
426
|
-
}
|
|
427
|
-
var lane_child_marker = { KEY, env_for: environment_for, is_child_of, marked_issue };
|
|
428
|
-
|
|
429
|
-
// scripts/rules/lane-park.ts
|
|
430
|
-
var NOTIFY_COMMANDS = /* @__PURE__ */ new Set(["notify"]);
|
|
431
|
-
var CONFIRMATION_TASK_TYPE = /(?:^|\s)--task-type[=\s]+["']?confirmation\b/u;
|
|
432
|
-
function is_confirmation_notify(command) {
|
|
433
|
-
return shell_segments.segments_of(command).some(
|
|
434
|
-
(segment) => shell_segments.is_josh_command(segment, NOTIFY_COMMANDS) && CONFIRMATION_TASK_TYPE.test(segment)
|
|
435
|
-
);
|
|
436
|
-
}
|
|
437
|
-
function is_unparked_stop(command, is_lane_child = lane_child_marker.is_child_of(process.cwd())) {
|
|
438
|
-
if (!is_confirmation_notify(command)) return false;
|
|
439
|
-
return is_lane_child;
|
|
440
|
-
}
|
|
441
|
-
var APPLIES_NEEDS_DECISION = new RegExp(
|
|
442
|
-
String.raw`(?:labels\[\]=|--add-label[= ]'?)${NEEDS_DECISION_LABEL}\b`,
|
|
443
|
-
"u"
|
|
444
|
-
);
|
|
445
|
-
function records_the_park(command) {
|
|
446
|
-
return APPLIES_NEEDS_DECISION.test(command);
|
|
447
|
-
}
|
|
448
|
-
var LANE_PARK_REASON = '\u26D4 lane child park: this session is a dispatched lane child, and a stop that waits on a person must leave the question on the Issue before it sends this Telegram \u2014 otherwise the parent finds an OPEN Issue with `in-progress` and no question, and a person cannot tell what to answer (joshuafolkken/kit#2034 measured this twice in one run). The child is the only process that knows the question, the options and whether it stashed, so it records the park itself rather than leaving the parent to guess from a log. Before you notify and stop: apply `needs-decision` and post a comment carrying the question, the options, and whether work was stashed \u2014 `gh api repos/{owner}/{repo}/issues/<N>/labels -f \'labels[]=needs-decision\'` then `gh api repos/{owner}/{repo}/issues/<N>/comments --field body=@<path>`. If this stop is a `needs-human-review` or `already-done` one, that label is already on the Issue and there is nothing to add. Then reissue this notify \u2014 it fires once per run and cannot repeat on the call in hand. The procedure is `.claude/skills/workflow-commands/pre-gate-cut.md` \u2192 "A lane child records its park before it stops", and the park itself is `backlogrun-park.md` \u2192 "park and continue".';
|
|
449
|
-
var lane_park = {
|
|
450
|
-
LANE_PARK_REASON,
|
|
451
|
-
is_confirmation_notify,
|
|
452
|
-
is_unparked_stop,
|
|
453
|
-
records_the_park
|
|
454
|
-
};
|
|
455
|
-
|
|
456
|
-
// scripts/rules/piped-verification.ts
|
|
457
|
-
var VERIFICATION_COMMANDS = /* @__PURE__ */ new Set([
|
|
458
|
-
"check",
|
|
459
|
-
"cspell",
|
|
460
|
-
"cspell:dot",
|
|
461
|
-
"eval",
|
|
462
|
-
"gate",
|
|
463
|
-
"lint",
|
|
464
|
-
"lint:eslint",
|
|
465
|
-
"lint:prettier",
|
|
466
|
-
"lint:related",
|
|
467
|
-
"overrides",
|
|
468
|
-
"ranges",
|
|
469
|
-
"test",
|
|
470
|
-
"test:e2e",
|
|
471
|
-
"test:related",
|
|
472
|
-
"test:unit"
|
|
473
|
-
]);
|
|
474
|
-
function is_verification_command(segment) {
|
|
475
|
-
return shell_segments.is_josh_command(segment, VERIFICATION_COMMANDS);
|
|
476
|
-
}
|
|
477
|
-
function is_masked_verification(command) {
|
|
478
|
-
return time_shell.discarded_commands(command).some((segment) => is_verification_command(segment));
|
|
479
|
-
}
|
|
480
|
-
var PIPED_VERIFICATION_REASON = "\u26D4 piped verification: a pipeline exits with its last command's status, so `pnpm josh gate | tail` reports success on a gate that failed, and the verdict is discarded before anything reads it. Run the check without the pipe \u2014 josh prints its verdict line last, so the harness output cap keeps it even when the middle is elided. Where the output genuinely has to be narrowed, redirect it to a file and read ranges from that file, or prefix `set -o pipefail` so the pipeline carries the check's status. Read the printed verdict either way, never the exit code alone. Read-only listings are untouched \u2014 this fires only on a command whose result means pass or fail. The rule is in `prompts/collaboration-workflow/output-bounds.md`. Reissue this call with no pipe \u2014 it fires once per run and cannot repeat on the call in hand.";
|
|
481
|
-
function command_pieces(command) {
|
|
482
|
-
return shell_segments.segments_of(time_shell.unquoted(command)).flatMap((segment) => segment.split("|")).map((piece) => piece.trim());
|
|
483
|
-
}
|
|
484
|
-
function runs_verification(command) {
|
|
485
|
-
return command_pieces(command).some((piece) => is_verification_command(piece));
|
|
486
|
-
}
|
|
487
|
-
function keeps_verdict_intact(command) {
|
|
488
|
-
return runs_verification(command) && !is_masked_verification(command);
|
|
489
|
-
}
|
|
490
|
-
var piped_verification = {
|
|
491
|
-
PIPED_VERIFICATION_REASON,
|
|
492
|
-
VERIFICATION_COMMANDS,
|
|
493
|
-
is_masked_verification,
|
|
494
|
-
keeps_verdict_intact,
|
|
495
|
-
runs_verification
|
|
496
|
-
};
|
|
497
|
-
|
|
498
|
-
// scripts/run/run-cut.ts
|
|
499
|
-
import { execFileSync } from "node:child_process";
|
|
500
|
-
|
|
501
|
-
// scripts/backlog/backlog-budget.ts
|
|
502
|
-
var MS_PER_MINUTE2 = 6e4;
|
|
503
|
-
var MINUTES_PER_HOUR2 = 60;
|
|
504
|
-
var WHOLE_RUN_BUDGET_HOURS = 8;
|
|
505
|
-
var WHOLE_RUN_BUDGET_MINUTES = WHOLE_RUN_BUDGET_HOURS * MINUTES_PER_HOUR2;
|
|
506
|
-
var WHOLE_RUN_BUDGET_MS = WHOLE_RUN_BUDGET_MINUTES * MS_PER_MINUTE2;
|
|
507
|
-
var DEFAULT_IDLE_MINUTES = 30;
|
|
508
|
-
var DEFAULT_IDLE_MS = DEFAULT_IDLE_MINUTES * MS_PER_MINUTE2;
|
|
509
|
-
var IDLE_POLL_MINUTES = 5;
|
|
510
|
-
var RUN_VERDICT = "run";
|
|
511
|
-
var WATCH_VERDICT = "watch";
|
|
512
|
-
var STOP_VERDICT = "stop";
|
|
513
|
-
var ANSWERS = ["blocked", "candidates", "exhausted", "parked", "unreadable"];
|
|
514
|
-
var WHOLE_RUN_REASON = `The whole-run bound of ${String(WHOLE_RUN_BUDGET_HOURS)} hours has passed. An unattended run that has not finished overnight needs a person, not more waiting.`;
|
|
515
|
-
var PARKED_REASON = "Nothing opted in can proceed without a person. Report the parked issues and finish.";
|
|
516
|
-
var UNREADABLE_REASON = "The backlog listing could not be read, so nothing was offered. Report what the command printed and finish \u2014 it is not an empty backlog.";
|
|
517
|
-
var BLOCKED_REASON = "Everything opted in is blocked or already running, so waiting can still change the answer. Poll again at the polling interval.";
|
|
518
|
-
var NO_IDLE_WATCH_REASON = "The backlog is empty and the idle watch was turned off with `--idle 0`, so the run finishes here.";
|
|
519
|
-
function to_minutes2(duration_ms) {
|
|
520
|
-
return String(Math.ceil(duration_ms / MS_PER_MINUTE2));
|
|
521
|
-
}
|
|
522
|
-
function max_reached_reason(merged, max_issues) {
|
|
523
|
-
return `This run's maximum of ${String(max_issues)} is reached, with ${String(merged)} merged. Report what was done, including how many were picked up during an idle watch, and finish.`;
|
|
524
|
-
}
|
|
525
|
-
function draining_reason(running, reason) {
|
|
526
|
-
return `Start nothing more and poll until the running children merge (running: ${String(running)}). Then report and finish \u2014 ${reason}`;
|
|
527
|
-
}
|
|
528
|
-
function idle_expired_reason(idle_budget_ms) {
|
|
529
|
-
return `The backlog stayed empty for the whole ${to_minutes2(idle_budget_ms)}-minute idle watch. Report what was done, including how many were picked up during the watch, and finish.`;
|
|
530
|
-
}
|
|
531
|
-
function next_ask_minutes(left_ms) {
|
|
532
|
-
const left_minutes = Number(to_minutes2(left_ms));
|
|
533
|
-
return String(Math.min(IDLE_POLL_MINUTES, left_minutes));
|
|
534
|
-
}
|
|
535
|
-
function idle_watch_reason(left_ms) {
|
|
536
|
-
return `The backlog is empty and the idle watch has about ${to_minutes2(left_ms)} minutes left. Release the working tree, ask again in ${next_ask_minutes(left_ms)} minutes, and restart the watch the moment a candidate appears.`;
|
|
537
|
-
}
|
|
538
|
-
function running_watch_reason(running, left_ms) {
|
|
539
|
-
return `The backlog is empty and the idle watch has about ${to_minutes2(left_ms)} minutes left, with ${String(running)} still running. Keep the working tree until they merge and poll at the polling interval, not the idle poll \u2014 the watch is running down meanwhile.`;
|
|
540
|
-
}
|
|
541
|
-
function watching_reason(input, left_ms) {
|
|
542
|
-
if (input.running === 0) return idle_watch_reason(left_ms);
|
|
543
|
-
return running_watch_reason(input.running, left_ms);
|
|
544
|
-
}
|
|
545
|
-
function taken_by(input) {
|
|
546
|
-
return input.merged + input.running;
|
|
547
|
-
}
|
|
548
|
-
function run_reason(input) {
|
|
549
|
-
const { max_issues } = input;
|
|
550
|
-
if (max_issues === void 0) return "No maximum was given, so start every issue offered.";
|
|
551
|
-
return `Start at most ${String(max_issues - taken_by(input))} more: this run's maximum is ${String(max_issues)}, with ${String(input.merged)} merged and ${String(input.running)} running.`;
|
|
552
|
-
}
|
|
553
|
-
var ANSWER_STOP_REASONS = {
|
|
554
|
-
parked: PARKED_REASON,
|
|
555
|
-
unreadable: UNREADABLE_REASON
|
|
556
|
-
};
|
|
557
|
-
function whole_run_reason(input) {
|
|
558
|
-
if (input.now_ms - input.started_at_ms < WHOLE_RUN_BUDGET_MS) return void 0;
|
|
559
|
-
return WHOLE_RUN_REASON;
|
|
560
|
-
}
|
|
561
|
-
function stop_reason(input) {
|
|
562
|
-
return ANSWER_STOP_REASONS[input.answer] ?? whole_run_reason(input);
|
|
563
|
-
}
|
|
564
|
-
function stopping(input, reason) {
|
|
565
|
-
if (input.running === 0) return { verdict: STOP_VERDICT, reason };
|
|
566
|
-
return { verdict: WATCH_VERDICT, reason: draining_reason(input.running, reason) };
|
|
567
|
-
}
|
|
568
|
-
function max_decision(input) {
|
|
569
|
-
const { max_issues } = input;
|
|
570
|
-
if (max_issues === void 0 || taken_by(input) < max_issues) return void 0;
|
|
571
|
-
return stopping(input, max_reached_reason(input.merged, max_issues));
|
|
572
|
-
}
|
|
573
|
-
function idle_decision(input) {
|
|
574
|
-
const { idle_budget_ms } = input;
|
|
575
|
-
if (idle_budget_ms === void 0) return stopping(input, NO_IDLE_WATCH_REASON);
|
|
576
|
-
const left_ms = idle_budget_ms - (input.now_ms - input.active_at_ms);
|
|
577
|
-
if (left_ms <= 0) return stopping(input, idle_expired_reason(idle_budget_ms));
|
|
578
|
-
return { verdict: WATCH_VERDICT, reason: watching_reason(input, left_ms) };
|
|
579
|
-
}
|
|
580
|
-
function decide2(input) {
|
|
581
|
-
const reason = stop_reason(input);
|
|
582
|
-
if (reason !== void 0) return stopping(input, reason);
|
|
583
|
-
const capped = max_decision(input);
|
|
584
|
-
if (capped !== void 0) return capped;
|
|
585
|
-
if (input.answer === "candidates") return { verdict: RUN_VERDICT, reason: run_reason(input) };
|
|
586
|
-
if (input.answer === "blocked") return { verdict: WATCH_VERDICT, reason: BLOCKED_REASON };
|
|
587
|
-
return idle_decision(input);
|
|
588
|
-
}
|
|
589
|
-
var backlog_budget = {
|
|
590
|
-
ANSWERS,
|
|
591
|
-
ANSWER_STOP_REASONS,
|
|
592
|
-
BLOCKED_REASON,
|
|
593
|
-
DEFAULT_IDLE_MINUTES,
|
|
594
|
-
DEFAULT_IDLE_MS,
|
|
595
|
-
IDLE_POLL_MINUTES,
|
|
596
|
-
MS_PER_MINUTE: MS_PER_MINUTE2,
|
|
597
|
-
NO_IDLE_WATCH_REASON,
|
|
598
|
-
PARKED_REASON,
|
|
599
|
-
RUN_VERDICT,
|
|
600
|
-
STOP_VERDICT,
|
|
601
|
-
UNREADABLE_REASON,
|
|
602
|
-
WATCH_VERDICT,
|
|
603
|
-
WHOLE_RUN_BUDGET_HOURS,
|
|
604
|
-
WHOLE_RUN_BUDGET_MINUTES,
|
|
605
|
-
WHOLE_RUN_BUDGET_MS,
|
|
606
|
-
WHOLE_RUN_REASON,
|
|
607
|
-
decide: decide2,
|
|
608
|
-
draining_reason,
|
|
609
|
-
idle_expired_reason,
|
|
610
|
-
idle_watch_reason,
|
|
611
|
-
max_reached_reason,
|
|
612
|
-
run_reason,
|
|
613
|
-
running_watch_reason,
|
|
614
|
-
taken_by
|
|
615
|
-
};
|
|
616
|
-
|
|
617
|
-
// scripts/cost-runtime/context-cut-threshold.ts
|
|
618
|
-
var CONTEXT_CUT_THRESHOLD = 15e4;
|
|
619
|
-
|
|
620
|
-
// scripts/git/git-push-transport.ts
|
|
621
|
-
import { execa } from "execa";
|
|
622
|
-
|
|
623
|
-
// scripts/git/git-execa-error.ts
|
|
624
|
-
function get_exit_code(error) {
|
|
625
|
-
if (typeof error !== "object" || error === null) return void 0;
|
|
626
|
-
if (!("exitCode" in error)) return void 0;
|
|
627
|
-
const { exitCode: exit_code } = error;
|
|
628
|
-
return typeof exit_code === "number" ? exit_code : void 0;
|
|
629
|
-
}
|
|
630
|
-
function has_timed_out(error) {
|
|
631
|
-
if (typeof error !== "object" || error === null) return false;
|
|
632
|
-
if (!("timedOut" in error)) return false;
|
|
633
|
-
return error.timedOut === true;
|
|
634
|
-
}
|
|
635
|
-
function create_spawn_error(command, exit_code) {
|
|
636
|
-
const exit_code_string = exit_code === void 0 ? "unknown" : String(exit_code);
|
|
637
|
-
const error_message = `git ${command} exited with code ${exit_code_string}`;
|
|
638
|
-
return new Error(error_message, { cause: { exit_code: exit_code_string } });
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
// scripts/git/git-push-transport.ts
|
|
642
|
-
var MS_PER_SECOND2 = 1e3;
|
|
643
|
-
var PUSH_TIMEOUT_SECONDS = 120;
|
|
644
|
-
var PUSH_TIMEOUT_MS = PUSH_TIMEOUT_SECONDS * MS_PER_SECOND2;
|
|
645
|
-
var PUSH_TIMEOUT_LABEL = `${String(PUSH_TIMEOUT_SECONDS)}s`;
|
|
646
|
-
var PUSH_TIMEOUT_MESSAGE = "git push timed out";
|
|
647
|
-
var PUSH_TIMEOUT_EXIT_CODE = "timeout";
|
|
648
|
-
var SSH_KEEPALIVE_OPTIONS = "-o ServerAliveInterval=15 -o ServerAliveCountMax=3";
|
|
649
|
-
var SSH_COMMAND_VARIABLE = "GIT_SSH_COMMAND";
|
|
650
|
-
var SSH_LEGACY_VARIABLE = "GIT_SSH";
|
|
651
|
-
var KEEPALIVE_SSH_COMMAND = `ssh ${SSH_KEEPALIVE_OPTIONS}`;
|
|
652
|
-
var SSH_COMMAND_CONFIG_KEY = "core.sshCommand";
|
|
653
|
-
var PUSH_SUBCOMMAND = "push";
|
|
654
|
-
function to_environment_value(name) {
|
|
655
|
-
return process.env[name]?.trim() ?? "";
|
|
656
|
-
}
|
|
657
|
-
function to_configured_ssh_command() {
|
|
658
|
-
const from_command = to_environment_value(SSH_COMMAND_VARIABLE);
|
|
659
|
-
return from_command === "" ? to_environment_value(SSH_LEGACY_VARIABLE) : from_command;
|
|
660
|
-
}
|
|
661
|
-
async function has_configured_ssh_command() {
|
|
662
|
-
if (to_configured_ssh_command() !== "") return true;
|
|
663
|
-
try {
|
|
664
|
-
const git_command_bin = git_utilities.get_git_command_for_spawn();
|
|
665
|
-
const { stdout } = await execa(git_command_bin, ["config", "--get", SSH_COMMAND_CONFIG_KEY]);
|
|
666
|
-
return stdout.trim() !== "";
|
|
667
|
-
} catch {
|
|
668
|
-
return false;
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
async function to_push_options() {
|
|
672
|
-
const should_keep_alive = !await has_configured_ssh_command();
|
|
673
|
-
return {
|
|
674
|
-
stdio: "inherit",
|
|
675
|
-
timeout: PUSH_TIMEOUT_MS,
|
|
676
|
-
env: should_keep_alive ? { [SSH_COMMAND_VARIABLE]: KEEPALIVE_SSH_COMMAND } : {}
|
|
677
|
-
};
|
|
678
|
-
}
|
|
679
|
-
async function did_push_time_out(arguments_list) {
|
|
680
|
-
const git_command_bin = git_utilities.get_git_command_for_spawn();
|
|
681
|
-
const options = await to_push_options();
|
|
682
|
-
try {
|
|
683
|
-
await execa(git_command_bin, [PUSH_SUBCOMMAND, ...arguments_list], options);
|
|
684
|
-
return false;
|
|
685
|
-
} catch (error) {
|
|
686
|
-
if (has_timed_out(error)) return true;
|
|
687
|
-
throw create_spawn_error(PUSH_SUBCOMMAND, get_exit_code(error));
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
function to_manual_command(arguments_list) {
|
|
691
|
-
return ["git", PUSH_SUBCOMMAND, ...arguments_list].join(" ");
|
|
692
|
-
}
|
|
693
|
-
function to_retry_notice(arguments_list) {
|
|
694
|
-
return `${PUSH_TIMEOUT_MESSAGE} after ${PUSH_TIMEOUT_LABEL} \u2014 retrying once: ${to_manual_command(arguments_list)}`;
|
|
695
|
-
}
|
|
696
|
-
function create_timeout_error(arguments_list) {
|
|
697
|
-
const message = `${PUSH_TIMEOUT_MESSAGE} twice (${PUSH_TIMEOUT_LABEL} each). Re-run it by hand: ${to_manual_command(arguments_list)}`;
|
|
698
|
-
return new Error(message, { cause: { exit_code: PUSH_TIMEOUT_EXIT_CODE } });
|
|
699
|
-
}
|
|
700
|
-
async function push(arguments_list) {
|
|
701
|
-
if (!await did_push_time_out(arguments_list)) return;
|
|
702
|
-
console.warn(to_retry_notice(arguments_list));
|
|
703
|
-
if (!await did_push_time_out(arguments_list)) return;
|
|
704
|
-
throw create_timeout_error(arguments_list);
|
|
705
|
-
}
|
|
706
|
-
var git_push_transport = {
|
|
707
|
-
push
|
|
708
|
-
};
|
|
709
|
-
|
|
710
|
-
// scripts/git/git-spawn.ts
|
|
711
|
-
import { execa as execa2 } from "execa";
|
|
712
|
-
async function read(arguments_) {
|
|
713
|
-
const git_cmd = git_utilities.get_git_command_for_spawn();
|
|
714
|
-
const { stdout } = await execa2(git_cmd, arguments_);
|
|
715
|
-
return stdout.trimEnd();
|
|
716
|
-
}
|
|
717
|
-
async function with_output(command, arguments_list) {
|
|
718
|
-
const git_command_bin = git_utilities.get_git_command_for_spawn();
|
|
719
|
-
try {
|
|
720
|
-
await execa2(git_command_bin, [command, ...arguments_list], { stdio: "inherit" });
|
|
721
|
-
} catch (error) {
|
|
722
|
-
throw create_spawn_error(command, get_exit_code(error));
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
var git_spawn = {
|
|
726
|
-
read,
|
|
727
|
-
with_output
|
|
728
|
-
};
|
|
729
|
-
|
|
730
|
-
// scripts/git/git-command.ts
|
|
731
|
-
async function branch() {
|
|
732
|
-
return await git_spawn.read(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
733
|
-
}
|
|
734
|
-
async function status() {
|
|
735
|
-
return await git_spawn.read(["status", PORCELAIN_FLAG, "--untracked-files=normal"]);
|
|
736
|
-
}
|
|
737
|
-
async function repository_root() {
|
|
738
|
-
return await git_spawn.read(["rev-parse", "--show-toplevel"]);
|
|
739
|
-
}
|
|
740
|
-
async function head_commit() {
|
|
741
|
-
return await git_spawn.read(["rev-parse", "HEAD"]);
|
|
742
|
-
}
|
|
743
|
-
var GIT_DIRECTORY_ARGUMENTS2 = [
|
|
744
|
-
"rev-parse",
|
|
745
|
-
"--absolute-git-dir",
|
|
746
|
-
"--path-format=absolute",
|
|
747
|
-
"--git-common-dir"
|
|
748
|
-
];
|
|
749
|
-
async function git_directories() {
|
|
750
|
-
const output = await git_spawn.read([...GIT_DIRECTORY_ARGUMENTS2]);
|
|
751
|
-
return output.split("\n").filter((line) => line !== "");
|
|
752
|
-
}
|
|
753
|
-
async function diff_cached(file_path) {
|
|
754
|
-
return await git_spawn.read(["diff", "--cached", file_path]);
|
|
755
|
-
}
|
|
756
|
-
var REFS_REMOTES_ORIGIN_PREFIX = "refs/remotes/origin/";
|
|
757
|
-
var NAME_ONLY_FLAG = "--name-only";
|
|
758
|
-
var DEFAULT_BRANCH_FALLBACK = "main";
|
|
759
|
-
async function get_default_branch() {
|
|
760
|
-
try {
|
|
761
|
-
const output = await git_spawn.read(["symbolic-ref", "refs/remotes/origin/HEAD"]);
|
|
762
|
-
const trimmed = output.trim();
|
|
763
|
-
if (trimmed.startsWith(REFS_REMOTES_ORIGIN_PREFIX)) {
|
|
764
|
-
return trimmed.slice(REFS_REMOTES_ORIGIN_PREFIX.length);
|
|
765
|
-
}
|
|
766
|
-
} catch {
|
|
767
|
-
}
|
|
768
|
-
return DEFAULT_BRANCH_FALLBACK;
|
|
769
|
-
}
|
|
770
|
-
var MERGE_BASE_COMMAND = "merge-base";
|
|
771
|
-
async function reference_exists(reference) {
|
|
772
|
-
try {
|
|
773
|
-
await git_spawn.read(["rev-parse", "--verify", "--quiet", reference]);
|
|
774
|
-
return true;
|
|
775
|
-
} catch {
|
|
776
|
-
return false;
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
async function default_branch_reference() {
|
|
780
|
-
const default_branch = await get_default_branch();
|
|
781
|
-
const remote_reference = `${REFS_REMOTES_ORIGIN_PREFIX}${default_branch}`;
|
|
782
|
-
return await reference_exists(remote_reference) ? remote_reference : default_branch;
|
|
783
|
-
}
|
|
784
|
-
async function change_base() {
|
|
785
|
-
const default_branch = await default_branch_reference();
|
|
786
|
-
try {
|
|
787
|
-
return await git_spawn.read([MERGE_BASE_COMMAND, default_branch, "HEAD"]);
|
|
788
|
-
} catch {
|
|
789
|
-
return default_branch;
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
async function diff_main(file_path) {
|
|
793
|
-
return await git_spawn.read(["diff", await change_base(), "--", file_path]);
|
|
794
|
-
}
|
|
795
|
-
var NO_PATH_QUOTING = ["-c", "core.quotePath=false"];
|
|
796
|
-
var NO_RELATIVE_PATHS = "--no-relative";
|
|
797
|
-
var NO_RENAME_DETECTION = "--no-renames";
|
|
798
|
-
async function change_base_commit() {
|
|
799
|
-
return await git_spawn.read(["rev-parse", await change_base()]);
|
|
800
|
-
}
|
|
801
|
-
async function diff_main_names() {
|
|
802
|
-
return await git_spawn.read([
|
|
803
|
-
...NO_PATH_QUOTING,
|
|
804
|
-
"diff",
|
|
805
|
-
NAME_ONLY_FLAG,
|
|
806
|
-
NO_RELATIVE_PATHS,
|
|
807
|
-
NO_RENAME_DETECTION,
|
|
808
|
-
await change_base(),
|
|
809
|
-
"--"
|
|
810
|
-
]);
|
|
811
|
-
}
|
|
812
|
-
async function diff_cached_names() {
|
|
813
|
-
return await git_spawn.read([
|
|
814
|
-
...NO_PATH_QUOTING,
|
|
815
|
-
"diff",
|
|
816
|
-
"--cached",
|
|
817
|
-
NAME_ONLY_FLAG,
|
|
818
|
-
NO_RELATIVE_PATHS,
|
|
819
|
-
NO_RENAME_DETECTION
|
|
820
|
-
]);
|
|
821
|
-
}
|
|
822
|
-
var WHOLE_TREE_PATHSPEC = ":/";
|
|
823
|
-
async function untracked_names() {
|
|
824
|
-
return await git_spawn.read([
|
|
825
|
-
...NO_PATH_QUOTING,
|
|
826
|
-
"ls-files",
|
|
827
|
-
"--others",
|
|
828
|
-
"--exclude-standard",
|
|
829
|
-
"--full-name",
|
|
830
|
-
WHOLE_TREE_PATHSPEC
|
|
831
|
-
]);
|
|
832
|
-
}
|
|
833
|
-
async function fetch_branch(branch_name) {
|
|
834
|
-
const refspec = `+refs/heads/${branch_name}:refs/remotes/origin/${branch_name}`;
|
|
835
|
-
return await git_spawn.read(["fetch", "origin", refspec]);
|
|
836
|
-
}
|
|
837
|
-
async function merge_fast_forward(branch_name) {
|
|
838
|
-
return await git_spawn.read(["merge", "--ff-only", `origin/${branch_name}`]);
|
|
839
|
-
}
|
|
840
|
-
async function merge_branch(branch_name) {
|
|
841
|
-
await git_spawn.with_output("merge", [`origin/${branch_name}`]);
|
|
842
|
-
}
|
|
843
|
-
async function checkout_b(branch_name) {
|
|
844
|
-
return await git_spawn.read(["checkout", "-b", branch_name]);
|
|
845
|
-
}
|
|
846
|
-
async function checkout(branch_name) {
|
|
847
|
-
return await git_spawn.read(["checkout", branch_name]);
|
|
848
|
-
}
|
|
849
|
-
async function commit(message) {
|
|
850
|
-
await git_spawn.with_output("commit", ["-m", message]);
|
|
851
|
-
}
|
|
852
|
-
async function delete_branch(branch_name) {
|
|
853
|
-
await git_spawn.read(["branch", "-d", branch_name]);
|
|
854
|
-
}
|
|
855
|
-
async function commit_count_beyond(base, tip) {
|
|
856
|
-
const range = `${base}..${tip}`;
|
|
857
|
-
const output = await git_spawn.read(["rev-list", "--count", range]);
|
|
858
|
-
const parsed = Number(output);
|
|
859
|
-
if (output.length > 0 && Number.isFinite(parsed)) return parsed;
|
|
860
|
-
throw new Error(`\`git rev-list --count ${range}\` printed \`${output}\` rather than a count`);
|
|
861
|
-
}
|
|
862
|
-
function is_exit_code_128(cause) {
|
|
863
|
-
return typeof cause === "object" && cause !== null && "exit_code" in cause && cause.exit_code === "128";
|
|
864
|
-
}
|
|
865
|
-
function is_upstream_not_set_error(error) {
|
|
866
|
-
if (!(error instanceof Error)) return false;
|
|
867
|
-
const { cause } = error;
|
|
868
|
-
return cause !== void 0 && is_exit_code_128(cause);
|
|
869
|
-
}
|
|
870
|
-
async function push_with_upstream(branch_name) {
|
|
871
|
-
await git_push_transport.push(["--set-upstream", "origin", branch_name]);
|
|
872
|
-
}
|
|
873
|
-
async function push2() {
|
|
874
|
-
try {
|
|
875
|
-
await git_push_transport.push([]);
|
|
876
|
-
} catch (error) {
|
|
877
|
-
if (is_upstream_not_set_error(error)) {
|
|
878
|
-
const current_branch = await branch();
|
|
879
|
-
await push_with_upstream(current_branch);
|
|
880
|
-
return;
|
|
881
|
-
}
|
|
882
|
-
throw error;
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
async function pull_fast_forward() {
|
|
886
|
-
await git_spawn.with_output("pull", ["--ff-only"]);
|
|
887
|
-
}
|
|
888
|
-
var SHORT_NAME_FORMAT = "--format=%(refname:short)";
|
|
889
|
-
var REMOTES_FLAG = "--remotes";
|
|
890
|
-
async function list_branches(flags, pattern) {
|
|
891
|
-
try {
|
|
892
|
-
const output = await git_spawn.read([
|
|
893
|
-
"branch",
|
|
894
|
-
"--list",
|
|
895
|
-
SHORT_NAME_FORMAT,
|
|
896
|
-
...flags,
|
|
897
|
-
pattern
|
|
898
|
-
]);
|
|
899
|
-
return output.split("\n").filter((line) => line.trim() !== "");
|
|
900
|
-
} catch {
|
|
901
|
-
return [];
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
async function branch_names(pattern) {
|
|
905
|
-
return await list_branches([], pattern);
|
|
906
|
-
}
|
|
907
|
-
async function branch_names_remote(pattern) {
|
|
908
|
-
return await list_branches([REMOTES_FLAG], pattern);
|
|
909
|
-
}
|
|
910
|
-
async function branch_exists(branch_name) {
|
|
911
|
-
const names = await branch_names(branch_name);
|
|
912
|
-
return names.length > 0;
|
|
913
|
-
}
|
|
914
|
-
var ALL_PATHS_PATHSPEC = ":/";
|
|
915
|
-
function exclude_pathspec(file_path) {
|
|
916
|
-
return `:(exclude,top)${file_path}`;
|
|
917
|
-
}
|
|
918
|
-
async function add_tracked(excluded_paths) {
|
|
919
|
-
const exclusions = excluded_paths.map((file_path) => exclude_pathspec(file_path));
|
|
920
|
-
await git_spawn.read(["add", "-u", "--", ALL_PATHS_PATHSPEC, ...exclusions]);
|
|
921
|
-
}
|
|
922
|
-
async function add_path(file_path) {
|
|
923
|
-
await git_spawn.with_output("add", ["--", file_path]);
|
|
924
|
-
}
|
|
925
|
-
var FIRST_PARENT_FLAG = "--first-parent";
|
|
926
|
-
async function log_first_parent(limit, file_path, tip = "HEAD") {
|
|
927
|
-
const output = await git_spawn.read([
|
|
928
|
-
"log",
|
|
929
|
-
FIRST_PARENT_FLAG,
|
|
930
|
-
"--format=%H",
|
|
931
|
-
`-n`,
|
|
932
|
-
String(limit),
|
|
933
|
-
tip,
|
|
934
|
-
"--",
|
|
935
|
-
file_path
|
|
936
|
-
]);
|
|
937
|
-
return output.split("\n").filter((line) => line.length > 0);
|
|
938
|
-
}
|
|
939
|
-
async function show_file(spec) {
|
|
940
|
-
return await git_spawn.read(["show", spec]);
|
|
941
|
-
}
|
|
942
|
-
var MERGE_COUNT_ARGUMENTS = [
|
|
943
|
-
"rev-list",
|
|
944
|
-
"--count",
|
|
945
|
-
"--merges",
|
|
946
|
-
FIRST_PARENT_FLAG
|
|
947
|
-
];
|
|
948
|
-
function merge_count_arguments(range) {
|
|
949
|
-
return [...MERGE_COUNT_ARGUMENTS, range];
|
|
950
|
-
}
|
|
951
|
-
async function count_merges(range) {
|
|
952
|
-
const output = await git_spawn.read(merge_count_arguments(range));
|
|
953
|
-
const parsed = Number(output.trim());
|
|
954
|
-
return Number.isFinite(parsed) ? parsed : 0;
|
|
955
|
-
}
|
|
956
|
-
var git_command = {
|
|
957
|
-
GIT_DIRECTORY_ARGUMENTS: GIT_DIRECTORY_ARGUMENTS2,
|
|
958
|
-
branch,
|
|
959
|
-
status,
|
|
960
|
-
repository_root,
|
|
961
|
-
head_commit,
|
|
962
|
-
git_directories,
|
|
963
|
-
diff_cached,
|
|
964
|
-
diff_cached_names,
|
|
965
|
-
diff_main,
|
|
966
|
-
change_base,
|
|
967
|
-
change_base_commit,
|
|
968
|
-
diff_main_names,
|
|
969
|
-
untracked_names,
|
|
970
|
-
get_default_branch,
|
|
971
|
-
default_branch_reference,
|
|
972
|
-
fetch_branch,
|
|
973
|
-
merge_fast_forward,
|
|
974
|
-
merge_branch,
|
|
975
|
-
checkout_b,
|
|
976
|
-
checkout,
|
|
977
|
-
commit,
|
|
978
|
-
commit_count_beyond,
|
|
979
|
-
delete_branch,
|
|
980
|
-
push: push2,
|
|
981
|
-
pull_fast_forward,
|
|
982
|
-
branch_exists,
|
|
983
|
-
branch_names,
|
|
984
|
-
branch_names_remote,
|
|
985
|
-
add_tracked,
|
|
986
|
-
add_path,
|
|
987
|
-
log_first_parent,
|
|
988
|
-
show_file,
|
|
989
|
-
count_merges,
|
|
990
|
-
merge_count_arguments,
|
|
991
|
-
is_upstream_not_set_error
|
|
992
|
-
};
|
|
993
|
-
|
|
994
|
-
// scripts/lane/lane-child-invocation.ts
|
|
995
|
-
var CHILD_INVOCATION = "fullrun";
|
|
996
|
-
var RESUME_GUIDE = ".claude/skills/workflow-commands/pre-gate-cut.md";
|
|
997
|
-
function child_invocation(issue) {
|
|
998
|
-
run_issue_number.require_issue_number(issue);
|
|
999
|
-
return `${CHILD_INVOCATION} #${issue}`;
|
|
1000
|
-
}
|
|
1001
|
-
function resume_invocation(issue) {
|
|
1002
|
-
const preamble = `Resuming the lane child for issue #${issue} \u2014 do not re-read the workflow-commands entry documents (SKILL.md, fullrun.md). Run \`pnpm josh run:cut --resume ${issue}\` before anything else and follow the matching verdict in ${RESUME_GUIDE}: \`resume\` goes to the gate, \`resume-impl\` continues implementation. Only on \`fresh\` proceed as an ordinary`;
|
|
1003
|
-
return `${preamble} ${child_invocation(issue)}`;
|
|
1004
|
-
}
|
|
1005
|
-
var lane_child_invocation = { CHILD_INVOCATION, child_invocation, resume_invocation };
|
|
1006
|
-
|
|
1007
|
-
// scripts/run/run-cut.ts
|
|
1008
|
-
import { z as z4 } from "zod";
|
|
1009
|
-
|
|
1010
|
-
// scripts/run/run-hold.ts
|
|
1011
|
-
import { z as z3 } from "zod";
|
|
1012
|
-
var HOLD_PREFIX = "josh-run-hold-";
|
|
1013
|
-
var UNNUMBERED_ISSUE = "new";
|
|
1014
|
-
var HOLD_MAX_AGE_HOURS = 8;
|
|
1015
|
-
var MILLISECONDS_PER_HOUR = 36e5;
|
|
1016
|
-
var HOLD_MAX_AGE_MS = HOLD_MAX_AGE_HOURS * MILLISECONDS_PER_HOUR;
|
|
1017
|
-
var RELEASE_COMMAND = "pnpm josh run:release";
|
|
1018
|
-
var FORCE_RELEASE_COMMAND = `${RELEASE_COMMAND} --force`;
|
|
1019
|
-
var NOT_IDLE = 'that is not "nothing is running here"';
|
|
1020
|
-
var FREE_READ = { kind: "free" };
|
|
1021
|
-
var UNREADABLE_READ = { kind: "unreadable" };
|
|
1022
|
-
function hold_path(git_directory) {
|
|
1023
|
-
return stamp_file.stamp_path(HOLD_PREFIX, git_directory);
|
|
1024
|
-
}
|
|
1025
|
-
async function worktree_directory() {
|
|
1026
|
-
const directories = await git_command.git_directories();
|
|
1027
|
-
return directories[0];
|
|
1028
|
-
}
|
|
1029
|
-
async function is_linked_worktree() {
|
|
1030
|
-
const directories = await git_command.git_directories();
|
|
1031
|
-
return directories[0] !== void 0 && directories[0] !== directories[1];
|
|
1032
|
-
}
|
|
1033
|
-
var run_hold_schema = z3.object({
|
|
1034
|
-
issue: z3.string(),
|
|
1035
|
-
taken_at: z3.string(),
|
|
1036
|
-
pid: z3.number()
|
|
1037
|
-
});
|
|
1038
|
-
function parse_hold(raw) {
|
|
1039
|
-
try {
|
|
1040
|
-
const parsed = run_hold_schema.safeParse(JSON.parse(raw));
|
|
1041
|
-
return parsed.success ? parsed.data : void 0;
|
|
1042
|
-
} catch {
|
|
1043
|
-
return void 0;
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
function is_stale(hold, now) {
|
|
1047
|
-
const taken = Date.parse(hold.taken_at);
|
|
1048
|
-
if (Number.isNaN(taken)) return true;
|
|
1049
|
-
return now.getTime() - taken > HOLD_MAX_AGE_MS;
|
|
1050
|
-
}
|
|
1051
|
-
function classify(raw, now) {
|
|
1052
|
-
if (raw === void 0) return FREE_READ;
|
|
1053
|
-
const hold = parse_hold(raw);
|
|
1054
|
-
if (hold === void 0) return UNREADABLE_READ;
|
|
1055
|
-
return is_stale(hold, now) ? { kind: "stale", hold } : { kind: "held", hold };
|
|
1056
|
-
}
|
|
1057
|
-
function read_hold(target, now = /* @__PURE__ */ new Date()) {
|
|
1058
|
-
return classify(stamp_file.read_stamp_text(target), now);
|
|
1059
|
-
}
|
|
1060
|
-
function build_hold(issue, now) {
|
|
1061
|
-
return { issue, taken_at: now.toISOString(), pid: process.pid };
|
|
1062
|
-
}
|
|
1063
|
-
function write_hold(target, issue, now = /* @__PURE__ */ new Date()) {
|
|
1064
|
-
const hold = build_hold(issue, now);
|
|
1065
|
-
stamp_file.write_stamp(target, hold);
|
|
1066
|
-
return hold;
|
|
1067
|
-
}
|
|
1068
|
-
function create_hold(target, issue, now = /* @__PURE__ */ new Date()) {
|
|
1069
|
-
return stamp_file.create_stamp(target, build_hold(issue, now));
|
|
1070
|
-
}
|
|
1071
|
-
async function is_tree_dirty() {
|
|
1072
|
-
try {
|
|
1073
|
-
const status2 = await git_command.status();
|
|
1074
|
-
return status2.trim() !== "";
|
|
1075
|
-
} catch {
|
|
1076
|
-
return true;
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
function release_hold(target) {
|
|
1080
|
-
stamp_file.remove_stamp(target);
|
|
1081
|
-
}
|
|
1082
|
-
function describe_holder(hold) {
|
|
1083
|
-
const holder = hold.issue === UNNUMBERED_ISSUE ? "an unnumbered run" : `#${hold.issue}`;
|
|
1084
|
-
return `${holder}, recorded ${hold.taken_at} (pid ${String(hold.pid)})`;
|
|
1085
|
-
}
|
|
1086
|
-
function own_release_command(issue) {
|
|
1087
|
-
return issue === UNNUMBERED_ISSUE ? RELEASE_COMMAND : `${RELEASE_COMMAND} ${issue}`;
|
|
1088
|
-
}
|
|
1089
|
-
function is_own_hold(hold, claimant) {
|
|
1090
|
-
return hold.issue === claimant;
|
|
1091
|
-
}
|
|
1092
|
-
function held_message(hold) {
|
|
1093
|
-
return `This working tree is already held by ${describe_holder(hold)}. One run at a time per working tree: a second run here commits its files onto the other run's branch. If that record is stale, run \`${FORCE_RELEASE_COMMAND}\` in this working tree \u2014 it removes a record this run did not write, so be sure the run that did has ended \u2014 and ask again.`;
|
|
1094
|
-
}
|
|
1095
|
-
function foreign_release_message(hold) {
|
|
1096
|
-
return `This working tree is held by ${describe_holder(hold)}, and this release did not claim it \u2014 nothing was removed. Release it as that run with \`${own_release_command(hold.issue)}\`, or, once you are sure it has ended, remove the record with \`${FORCE_RELEASE_COMMAND}\`.`;
|
|
1097
|
-
}
|
|
1098
|
-
function forced_release_message(hold) {
|
|
1099
|
-
return `Forced: removed the run record held by ${describe_holder(hold)}.`;
|
|
1100
|
-
}
|
|
1101
|
-
function unreadable_message() {
|
|
1102
|
-
return `This working tree's run record could not be read \u2014 ${NOT_IDLE}. Run \`${FORCE_RELEASE_COMMAND}\` in this working tree to clear it, then ask again.`;
|
|
1103
|
-
}
|
|
1104
|
-
function stale_message(hold) {
|
|
1105
|
-
return `Replaced a stale run record held by ${describe_holder(hold)}: older than ${String(HOLD_MAX_AGE_HOURS)} hours, so the run that wrote it has ended without releasing it.`;
|
|
1106
|
-
}
|
|
1107
|
-
function uncommitted_message(hold) {
|
|
1108
|
-
return `This working tree still has uncommitted changes, and its run record \u2014 held by ${describe_holder(hold)} \u2014 has expired. That is a run which stopped for a person rather than one that finished, so the tree is not free: commit or stash the work, or run \`${own_release_command(hold.issue)}\` once you are done with it, then ask again.`;
|
|
1109
|
-
}
|
|
1110
|
-
function race_message(read2, target) {
|
|
1111
|
-
if (read2.kind === "held") return held_message(read2.hold);
|
|
1112
|
-
return `A record already exists at ${target} and this run did not write it \u2014 ${NOT_IDLE}. Either another run claimed the tree at the same moment, or the record belongs to another account. Run \`${FORCE_RELEASE_COMMAND}\`, and remove that file by hand if it is still there.`;
|
|
1113
|
-
}
|
|
1114
|
-
function unknown_message() {
|
|
1115
|
-
return `Could not read this working tree's git directory \u2014 ${NOT_IDLE}. Run this from inside the checkout the run will edit.`;
|
|
1116
|
-
}
|
|
1117
|
-
var run_hold = {
|
|
1118
|
-
FORCE_RELEASE_COMMAND,
|
|
1119
|
-
HOLD_MAX_AGE_HOURS,
|
|
1120
|
-
HOLD_MAX_AGE_MS,
|
|
1121
|
-
RELEASE_COMMAND,
|
|
1122
|
-
UNNUMBERED_ISSUE,
|
|
1123
|
-
classify,
|
|
1124
|
-
create_hold,
|
|
1125
|
-
describe_holder,
|
|
1126
|
-
foreign_release_message,
|
|
1127
|
-
forced_release_message,
|
|
1128
|
-
held_message,
|
|
1129
|
-
hold_path,
|
|
1130
|
-
is_linked_worktree,
|
|
1131
|
-
is_own_hold,
|
|
1132
|
-
is_tree_dirty,
|
|
1133
|
-
own_release_command,
|
|
1134
|
-
parse_hold,
|
|
1135
|
-
race_message,
|
|
1136
|
-
read_hold,
|
|
1137
|
-
release_hold,
|
|
1138
|
-
stale_message,
|
|
1139
|
-
uncommitted_message,
|
|
1140
|
-
unknown_message,
|
|
1141
|
-
unreadable_message,
|
|
1142
|
-
worktree_directory,
|
|
1143
|
-
write_hold
|
|
1144
|
-
};
|
|
1145
|
-
|
|
1146
|
-
// scripts/run/run-cut.ts
|
|
1147
|
-
var CUT_PREFIX = "josh-run-cut-";
|
|
1148
|
-
var PRE_GATE_PHASE = "pre-gate";
|
|
1149
|
-
var IMPLEMENTATION_PHASE = "implementation";
|
|
1150
|
-
var IMPLEMENTATION_CONTEXT_THRESHOLD = CONTEXT_CUT_THRESHOLD;
|
|
1151
|
-
var CUT_MAX_AGE_MS = backlog_budget.WHOLE_RUN_BUDGET_MS;
|
|
1152
|
-
var CUT_MAX_AGE_HOURS = backlog_budget.WHOLE_RUN_BUDGET_HOURS;
|
|
1153
|
-
var GIT_READ_TIMEOUT_MS = 5e3;
|
|
1154
|
-
var END_COMMAND = "pnpm josh run:cut --end";
|
|
1155
|
-
var READ_COMMAND = "pnpm josh run:cut --json";
|
|
1156
|
-
var NONE_READ = { kind: "none" };
|
|
1157
|
-
var UNREADABLE_READ2 = { kind: "unreadable" };
|
|
1158
|
-
var run_cut_schema = z4.object({
|
|
1159
|
-
invocation: z4.string(),
|
|
1160
|
-
issue: z4.string(),
|
|
1161
|
-
branch: z4.string(),
|
|
1162
|
-
phase: z4.string(),
|
|
1163
|
-
cut_at: z4.string(),
|
|
1164
|
-
is_handed_off: z4.boolean().optional()
|
|
1165
|
-
});
|
|
1166
|
-
function cut_path(git_directory) {
|
|
1167
|
-
return stamp_file.stamp_path(CUT_PREFIX, git_directory);
|
|
1168
|
-
}
|
|
1169
|
-
var { worktree_directory: worktree_directory2 } = run_hold;
|
|
1170
|
-
function invocation_for(issue) {
|
|
1171
|
-
return lane_child_invocation.child_invocation(issue);
|
|
1172
|
-
}
|
|
1173
|
-
function parse_cut(raw) {
|
|
1174
|
-
try {
|
|
1175
|
-
const parsed = run_cut_schema.safeParse(JSON.parse(raw));
|
|
1176
|
-
return parsed.success ? parsed.data : void 0;
|
|
1177
|
-
} catch {
|
|
1178
|
-
return void 0;
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
function is_expired(cut, now) {
|
|
1182
|
-
const started = Date.parse(cut.cut_at);
|
|
1183
|
-
if (Number.isNaN(started)) return true;
|
|
1184
|
-
return now.getTime() - started > CUT_MAX_AGE_MS;
|
|
1185
|
-
}
|
|
1186
|
-
function classify2(raw, now) {
|
|
1187
|
-
if (raw === void 0) return NONE_READ;
|
|
1188
|
-
const cut = parse_cut(raw);
|
|
1189
|
-
if (cut === void 0) return UNREADABLE_READ2;
|
|
1190
|
-
return is_expired(cut, now) ? { kind: "expired", cut } : { kind: "carried", cut };
|
|
1191
|
-
}
|
|
1192
|
-
function read_cut(target, now = /* @__PURE__ */ new Date()) {
|
|
1193
|
-
return classify2(stamp_file.read_stamp_text(target), now);
|
|
1194
|
-
}
|
|
1195
|
-
function worktree_git_directory_sync() {
|
|
1196
|
-
try {
|
|
1197
|
-
const output = execFileSync(
|
|
1198
|
-
git_utilities.get_git_command_for_spawn(),
|
|
1199
|
-
[...git_command.GIT_DIRECTORY_ARGUMENTS],
|
|
1200
|
-
// git's own diagnostics are discarded: a checkout it cannot describe is already the
|
|
1201
|
-
// `undefined` below, and a hook that let `fatal: not a git repository` through would print it
|
|
1202
|
-
// in front of a tool call that is about to be allowed.
|
|
1203
|
-
// A `PreToolUse` hook holds the tool call while it runs, so the read is bounded rather than
|
|
1204
|
-
// left to whatever git does.
|
|
1205
|
-
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: GIT_READ_TIMEOUT_MS }
|
|
1206
|
-
);
|
|
1207
|
-
return output.split("\n").find((line) => line !== "");
|
|
1208
|
-
} catch {
|
|
1209
|
-
return void 0;
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
function carried_cut_sync(now = /* @__PURE__ */ new Date(), directory = worktree_git_directory_sync()) {
|
|
1213
|
-
if (directory === void 0) return void 0;
|
|
1214
|
-
const read2 = read_cut(cut_path(directory), now);
|
|
1215
|
-
return read2.kind === "carried" ? read2.cut : void 0;
|
|
1216
|
-
}
|
|
1217
|
-
function fresh_cut(spec, now) {
|
|
1218
|
-
return {
|
|
1219
|
-
invocation: invocation_for(spec.issue),
|
|
1220
|
-
issue: spec.issue,
|
|
1221
|
-
branch: spec.branch,
|
|
1222
|
-
phase: spec.phase,
|
|
1223
|
-
cut_at: now.toISOString(),
|
|
1224
|
-
is_handed_off: true
|
|
1225
|
-
};
|
|
1226
|
-
}
|
|
1227
|
-
function begin_cut(target, spec, now = /* @__PURE__ */ new Date()) {
|
|
1228
|
-
const cut = fresh_cut(spec, now);
|
|
1229
|
-
return stamp_file.create_stamp(target, cut) ? cut : void 0;
|
|
1230
|
-
}
|
|
1231
|
-
function is_declared_cut(cut, issue) {
|
|
1232
|
-
return cut.is_handed_off === true && cut.invocation === invocation_for(issue);
|
|
1233
|
-
}
|
|
1234
|
-
function matches_state(cut, request) {
|
|
1235
|
-
return cut.branch === request.current_branch && cut.issue === request.issue && request.is_dirty && request.is_held;
|
|
1236
|
-
}
|
|
1237
|
-
function is_adopted_cut(cut, issue) {
|
|
1238
|
-
return cut.is_handed_off === false && cut.invocation === invocation_for(issue);
|
|
1239
|
-
}
|
|
1240
|
-
function classify_resume(cut, request) {
|
|
1241
|
-
if (is_adopted_cut(cut, request.issue)) return "handed-off";
|
|
1242
|
-
if (!is_declared_cut(cut, request.issue)) return "stale";
|
|
1243
|
-
return matches_state(cut, request) ? "resume" : "stale";
|
|
1244
|
-
}
|
|
1245
|
-
function adopt_cut(target, cut) {
|
|
1246
|
-
const next = { ...cut, is_handed_off: false };
|
|
1247
|
-
stamp_file.remove_stamp(target);
|
|
1248
|
-
return stamp_file.create_stamp(target, next) ? next : void 0;
|
|
1249
|
-
}
|
|
1250
|
-
function end_cut(target) {
|
|
1251
|
-
stamp_file.remove_stamp(target);
|
|
1252
|
-
}
|
|
1253
|
-
function tree_is_held(directory) {
|
|
1254
|
-
if (directory === void 0) return false;
|
|
1255
|
-
const read2 = run_hold.read_hold(run_hold.hold_path(directory));
|
|
1256
|
-
return read2.kind === "held" || read2.kind === "stale";
|
|
1257
|
-
}
|
|
1258
|
-
async function current_state() {
|
|
1259
|
-
const branch2 = await git_command.branch();
|
|
1260
|
-
const is_dirty = await run_hold.is_tree_dirty();
|
|
1261
|
-
const directory = await worktree_directory2();
|
|
1262
|
-
return { branch: branch2, is_dirty, is_held: tree_is_held(directory) };
|
|
1263
|
-
}
|
|
1264
|
-
function describe_cut(cut) {
|
|
1265
|
-
return `${cut.invocation} cut at ${cut.cut_at} on ${cut.branch} (${cut.phase})`;
|
|
1266
|
-
}
|
|
1267
|
-
function busy_message(cut) {
|
|
1268
|
-
return `Another process already resumed this cut \u2014 ${describe_cut(cut)}. Nothing was resumed here; a double launch was detected.`;
|
|
1269
|
-
}
|
|
1270
|
-
function handed_off_message(cut) {
|
|
1271
|
-
return `This cut was already handed off to a successor \u2014 ${describe_cut(cut)}. Nothing was resumed here; the run is being carried on by the process that adopted it, so this one has nothing to do.`;
|
|
1272
|
-
}
|
|
1273
|
-
function stale_message2(cut) {
|
|
1274
|
-
return `This cut does not match the current tree \u2014 ${describe_cut(cut)}. Resume failed; verify the branch and the uncommitted work, then clear it with \`${END_COMMAND}\` if the run is over.`;
|
|
1275
|
-
}
|
|
1276
|
-
function unreadable_message2() {
|
|
1277
|
-
return `A cut record is here but could not be read; clear it with \`${END_COMMAND}\` once you know no run is using it. Read it with \`${READ_COMMAND}\`.`;
|
|
1278
|
-
}
|
|
1279
|
-
function unknown_message2() {
|
|
1280
|
-
return "This working tree\u2019s git directory could not be read, so no cut record was acted on.";
|
|
1281
|
-
}
|
|
1282
|
-
var run_cut = {
|
|
1283
|
-
CUT_MAX_AGE_HOURS,
|
|
1284
|
-
CUT_MAX_AGE_MS,
|
|
1285
|
-
END_COMMAND,
|
|
1286
|
-
IMPLEMENTATION_CONTEXT_THRESHOLD,
|
|
1287
|
-
IMPLEMENTATION_PHASE,
|
|
1288
|
-
PRE_GATE_PHASE,
|
|
1289
|
-
adopt_cut,
|
|
1290
|
-
begin_cut,
|
|
1291
|
-
busy_message,
|
|
1292
|
-
carried_cut_sync,
|
|
1293
|
-
classify: classify2,
|
|
1294
|
-
classify_resume,
|
|
1295
|
-
current_state,
|
|
1296
|
-
cut_path,
|
|
1297
|
-
describe_cut,
|
|
1298
|
-
end_cut,
|
|
1299
|
-
fresh_cut,
|
|
1300
|
-
handed_off_message,
|
|
1301
|
-
invocation_for,
|
|
1302
|
-
is_expired,
|
|
1303
|
-
read_cut,
|
|
1304
|
-
stale_message: stale_message2,
|
|
1305
|
-
unknown_message: unknown_message2,
|
|
1306
|
-
unreadable_message: unreadable_message2,
|
|
1307
|
-
worktree_directory: worktree_directory2,
|
|
1308
|
-
worktree_git_directory_sync
|
|
1309
|
-
};
|
|
1310
|
-
|
|
1311
|
-
// scripts/rules/pre-gate-cut.ts
|
|
1312
|
-
var GATE_COMMANDS = /* @__PURE__ */ new Set(["gate"]);
|
|
1313
|
-
var CUT_COMMANDS = /* @__PURE__ */ new Set(["run:cut"]);
|
|
1314
|
-
var CUT_MODE_FLAG = /(?:^|\s)--(?:resume|end|json)(?:[=\s]|$)/u;
|
|
1315
|
-
var HOLD_COMMANDS = /* @__PURE__ */ new Set(["run:hold"]);
|
|
1316
|
-
var HOLD_MODE_FLAG = /(?:^|\s)--release(?:[=\s]|$)/u;
|
|
1317
|
-
function invokes_josh(command, names, mode_flag) {
|
|
1318
|
-
return shell_segments.segments_of(command).some(
|
|
1319
|
-
(segment) => shell_segments.is_josh_command(segment, names) && mode_flag?.test(segment) !== true
|
|
1320
|
-
);
|
|
1321
|
-
}
|
|
1322
|
-
function runs_the_gate(command) {
|
|
1323
|
-
return invokes_josh(command, GATE_COMMANDS);
|
|
1324
|
-
}
|
|
1325
|
-
function takes_the_cut(command) {
|
|
1326
|
-
return invokes_josh(command, CUT_COMMANDS, CUT_MODE_FLAG);
|
|
1327
|
-
}
|
|
1328
|
-
function claims_the_hold(command) {
|
|
1329
|
-
return invokes_josh(command, HOLD_COMMANDS, HOLD_MODE_FLAG);
|
|
1330
|
-
}
|
|
1331
|
-
function asks_about_the_cut(command) {
|
|
1332
|
-
return invokes_josh(command, CUT_COMMANDS);
|
|
1333
|
-
}
|
|
1334
|
-
function current_state2() {
|
|
1335
|
-
return {
|
|
1336
|
-
directory: process.cwd(),
|
|
1337
|
-
carried: run_cut.carried_cut_sync,
|
|
1338
|
-
marked_issue: lane_child_marker.marked_issue()
|
|
1339
|
-
};
|
|
1340
|
-
}
|
|
1341
|
-
function uncut_lane_issue(state) {
|
|
1342
|
-
const issue = lane_paths.lane_issue_of(state.directory);
|
|
1343
|
-
if (issue === void 0) return void 0;
|
|
1344
|
-
if (state.marked_issue !== issue) return void 0;
|
|
1345
|
-
return state.carried()?.issue === issue ? void 0 : issue;
|
|
1346
|
-
}
|
|
1347
|
-
function is_uncut_gate(command, state = current_state2()) {
|
|
1348
|
-
if (!runs_the_gate(command)) return false;
|
|
1349
|
-
return uncut_lane_issue(state) !== void 0;
|
|
1350
|
-
}
|
|
1351
|
-
var PRE_GATE_CUT_REASON = "\u26D4 pre-gate cut: this checkout is a lane dispatched for this issue and the gate is the boundary to cut at, so take the cut before running it. `pnpm josh run:cut <N>` ends this process and relaunches a fresh one that resumes from the gate, dropping the thinking built up while implementing rather than carrying it on every later request \u2014 joshuafolkken/kit#1839 measured 176K of 204K output riding across that boundary, and joshuafolkken/kit#1864 measured the step itself firing 0 times in 6 lane children while it was carried as prose. Issue `pnpm josh run:cut <N>` now, and read the verdict: on `cut`, **end the turn immediately** \u2014 the fresh process owns the run from there and must not be waited for; on `not-a-lane`, `unready`, `busy`, `failed` or `unknown`, this process carries the run on and the gate is simply the next call. Never relaunch a second process after `busy`. This fired because the dispatch mark names this lane; a person working here carries no mark and sees no refusal, so there is no human-or-child judgement left to make. The procedure is `.claude/skills/workflow-commands/pre-gate-cut.md`. Reissue the gate once the cut has answered \u2014 this fires once per run, so it cannot repeat on the call in hand.";
|
|
1352
|
-
var pre_gate_cut = {
|
|
1353
|
-
PRE_GATE_CUT_REASON,
|
|
1354
|
-
asks_about_the_cut,
|
|
1355
|
-
claims_the_hold,
|
|
1356
|
-
is_uncut_gate,
|
|
1357
|
-
runs_the_gate,
|
|
1358
|
-
takes_the_cut,
|
|
1359
|
-
uncut_lane_issue
|
|
1360
|
-
};
|
|
1361
|
-
|
|
1362
|
-
// scripts/rules/prior-comment-read.ts
|
|
1363
|
-
var ISSUE_PATH_NUMBER = /issues\/(\d+)/gu;
|
|
1364
|
-
var ISSUE_VIEW_NUMBER = /\bissue\s+view\s+(?:-{1,2}\S+\s+)*(\d+)/u;
|
|
1365
|
-
var ISSUE_READ_ARGUMENTS = /\bissue:read\b([\s\d]*)/u;
|
|
1366
|
-
var A_NUMBER = /^\d+$/u;
|
|
1367
|
-
var COMMENTS_ENDPOINT = /issues\/\d+\/comments\b/u;
|
|
1368
|
-
var VIEW_WITH_COMMENTS = /\bissue\s+view\b[^\n]*--comments\b/u;
|
|
1369
|
-
var ISSUE_READ_COMMAND = /\bissue:read\b/u;
|
|
1370
|
-
function add_issue_read_numbers(command, numbers) {
|
|
1371
|
-
const match = ISSUE_READ_ARGUMENTS.exec(command);
|
|
1372
|
-
if (match === null) return;
|
|
1373
|
-
const tokens = (match[1] ?? "").split(/\s+/u);
|
|
1374
|
-
for (const token of tokens) {
|
|
1375
|
-
if (A_NUMBER.test(token)) numbers.add(Number(token));
|
|
1376
|
-
}
|
|
1377
|
-
}
|
|
1378
|
-
function issue_numbers_of(command) {
|
|
1379
|
-
const numbers = /* @__PURE__ */ new Set();
|
|
1380
|
-
for (const match of command.matchAll(ISSUE_PATH_NUMBER)) numbers.add(Number(match[1]));
|
|
1381
|
-
const view = ISSUE_VIEW_NUMBER.exec(command);
|
|
1382
|
-
if (view !== null) numbers.add(Number(view[1]));
|
|
1383
|
-
add_issue_read_numbers(command, numbers);
|
|
1384
|
-
return numbers;
|
|
1385
|
-
}
|
|
1386
|
-
function fetches_comments(command) {
|
|
1387
|
-
return COMMENTS_ENDPOINT.test(command) || VIEW_WITH_COMMENTS.test(command) || ISSUE_READ_COMMAND.test(command);
|
|
1388
|
-
}
|
|
1389
|
-
function reads_target_comments(command, target) {
|
|
1390
|
-
if (!fetches_comments(command)) return false;
|
|
1391
|
-
for (const number of issue_numbers_of(command)) {
|
|
1392
|
-
if (target.has(number)) return true;
|
|
1393
|
-
}
|
|
1394
|
-
return false;
|
|
1395
|
-
}
|
|
1396
|
-
function is_bash_call(block) {
|
|
1397
|
-
return block.type === cost_blocks.TOOL_USE_TYPE && block.name === cost_blocks.BASH_TOOL;
|
|
1398
|
-
}
|
|
1399
|
-
function bash_commands_of(line) {
|
|
1400
|
-
const parsed = time_transcript_line.parse_line(line);
|
|
1401
|
-
if (parsed === void 0) return [];
|
|
1402
|
-
return parsed.blocks.filter((block) => is_bash_call(block)).map((block) => time_shell.bash_command(block.input));
|
|
1403
|
-
}
|
|
1404
|
-
function prior_commands(tail) {
|
|
1405
|
-
return tail.split("\n").flatMap((line) => bash_commands_of(line));
|
|
1406
|
-
}
|
|
1407
|
-
function already_read_comments_for(tail, command) {
|
|
1408
|
-
const target = issue_numbers_of(command);
|
|
1409
|
-
if (target.size === 0) return false;
|
|
1410
|
-
return prior_commands(tail).some((prior) => reads_target_comments(prior, target));
|
|
1411
|
-
}
|
|
1412
|
-
function already_read_for_call(tail, call) {
|
|
1413
|
-
if (call.name !== cost_blocks.BASH_TOOL) return false;
|
|
1414
|
-
return already_read_comments_for(tail, time_shell.bash_command(call.input));
|
|
1415
|
-
}
|
|
1416
|
-
var prior_comment_read = {
|
|
1417
|
-
already_read_comments_for,
|
|
1418
|
-
already_read_for_call,
|
|
1419
|
-
fetches_comments,
|
|
1420
|
-
issue_numbers_of
|
|
1421
|
-
};
|
|
1422
|
-
|
|
1423
|
-
// scripts/rules/run-tail.ts
|
|
1424
|
-
var SEGMENT_PREFIX = String.raw`(?:\(\s*)?(?:[A-Za-z_][A-Za-z0-9_]*=\S*\s+)*(?:npx\s+|pnpm\s+(?:exec\s+|run\s+)?)?`;
|
|
1425
|
-
var WORD_END = String.raw`(?=\s|$|\))`;
|
|
1426
|
-
var PUSH_STEP_SEGMENT = new RegExp(String.raw`^${SEGMENT_PREFIX}josh\s+(?:git|g)${WORD_END}`, "u");
|
|
1427
|
-
var CONFIRMED_FLAG = new RegExp(String.raw`(?:^|\s)(?:-y|--yes)${WORD_END}`, "u");
|
|
1428
|
-
var SKIPS_THE_PUSH = new RegExp(String.raw`(?:^|\s)--skip-push${WORD_END}`, "u");
|
|
1429
|
-
var BACKGROUND_KEY = "run_in_background";
|
|
1430
|
-
var QUOTED_SPAN = /"[^"]*"|'[^']*'/gu;
|
|
1431
|
-
function flags_of(segment) {
|
|
1432
|
-
return segment.replaceAll(QUOTED_SPAN, " ");
|
|
1433
|
-
}
|
|
1434
|
-
function is_push_step_segment(segment) {
|
|
1435
|
-
if (!PUSH_STEP_SEGMENT.test(segment)) return false;
|
|
1436
|
-
const flags = flags_of(segment);
|
|
1437
|
-
if (SKIPS_THE_PUSH.test(flags)) return false;
|
|
1438
|
-
return CONFIRMED_FLAG.test(flags);
|
|
1439
|
-
}
|
|
1440
|
-
function is_push_step(command) {
|
|
1441
|
-
return shell_segments.segments_of(command).some((segment) => is_push_step_segment(segment));
|
|
1442
|
-
}
|
|
1443
|
-
function is_backgrounded(input) {
|
|
1444
|
-
if (!json_value.is_record(input)) return false;
|
|
1445
|
-
return input[BACKGROUND_KEY] === true;
|
|
1446
|
-
}
|
|
1447
|
-
function is_push_step_call(call) {
|
|
1448
|
-
if (call.name !== cost_blocks.BASH_TOOL) return false;
|
|
1449
|
-
return is_push_step(time_shell.bash_command(call.input));
|
|
1450
|
-
}
|
|
1451
|
-
function is_backgrounded_push_step(call) {
|
|
1452
|
-
if (!is_backgrounded(call.input)) return false;
|
|
1453
|
-
return is_push_step_call(call);
|
|
1454
|
-
}
|
|
1455
|
-
function is_foreground_push_step(call) {
|
|
1456
|
-
if (is_backgrounded(call.input)) return false;
|
|
1457
|
-
return is_push_step_call(call);
|
|
1458
|
-
}
|
|
1459
|
-
function decide3() {
|
|
1460
|
-
return true;
|
|
1461
|
-
}
|
|
1462
|
-
var RUN_TAIL_REASON = '\u26D4 run tail: this issues the commit-push-PR step in the foreground, where the harness decides when to stop waiting. Reissue it with `run_in_background` set. In `fullrun #1501` this call was given a 900-second timeout, the harness detached it at its own 600-second cap, and nothing read the output for a further 6m28s \u2014 45m03s of wall clock on about 17 minutes of work (joshuafolkken/kit#1510). **Never give a foreground call a timeout above the harness cap**: the cap decides, not the number. A backgrounded call re-invokes you when it exits, so the completion notification is what resumes the run. **When it arrives, the turn that reads it goes straight through any branch-2 filing and `pnpm josh epic:bundle` to `pnpm josh followup` \u2014 the turn never ends at the push.** CI went green 6m06s before the merge started in that run, and it started only because the person asked; joshuafolkken/kit#1333 had already settled that the merge is issued in the turn the review closes. While the push runs, do the work that writes nothing \u2014 drafting the completion body to a file, deciding the branch-2 disposition. `pnpm josh followup` itself stays in the **foreground** \u2014 nearly every step after it reads its result. But a tail does follow the merge, measured at 3.0 min and 5.9% of a run (joshuafolkken/kit#1462), so empty it beforehand: the steps that read the merge result stay after `followup`, and the rest are composed in the turn that issues it, bar the one exception background-commands.md names. The procedure is `.claude/skills/workflow-commands/background-commands.md`, "A command that can take minutes is issued in the background". **This rule fires on every foreground push, not once per run**, so reissuing the same call in the foreground will be refused again.';
|
|
1463
|
-
var run_tail = {
|
|
1464
|
-
RUN_TAIL_REASON,
|
|
1465
|
-
decide: decide3,
|
|
1466
|
-
is_backgrounded_push_step,
|
|
1467
|
-
is_foreground_push_step,
|
|
1468
|
-
is_push_step,
|
|
1469
|
-
is_push_step_call
|
|
1470
|
-
};
|
|
1471
|
-
|
|
1472
|
-
// scripts/rules/shell-body-trigger.ts
|
|
1473
|
-
var FIELD_FLAGS = "(?:-f|-F|--field|--raw-field)";
|
|
1474
|
-
var MESSAGE_FLAGS = "(?:--body|--notify-message)";
|
|
1475
|
-
var FIELD_OPENING = String.raw`${FIELD_FLAGS}[\s=]*(?:'?body="|"body=)`;
|
|
1476
|
-
var MESSAGE_OPENING = String.raw`${MESSAGE_FLAGS}[\s=]+"`;
|
|
1477
|
-
var NESTED_SUBSTITUTION = String.raw`\$\([^()]*\)`;
|
|
1478
|
-
var SUBSTITUTION = String.raw`\$\((?:[^()$]|\$(?!\()|${NESTED_SUBSTITUTION})*\)`;
|
|
1479
|
-
var QUOTED_VALUE = String.raw`(?:[^"\\$]|\\.|\$(?!\()|${SUBSTITUTION})*`;
|
|
1480
|
-
var INLINE_BODY_VALUE = new RegExp(
|
|
1481
|
-
`(?:${FIELD_OPENING}|${MESSAGE_OPENING})(${QUOTED_VALUE})"`,
|
|
1482
|
-
"gu"
|
|
1483
|
-
);
|
|
1484
|
-
var SHELL_EVALUATED = /[`$]/u;
|
|
1485
|
-
var WHOLE_VALUE_SUBSTITUTION = new RegExp(`^${SUBSTITUTION}$`, "u");
|
|
1486
|
-
var BACKTICK = "`";
|
|
1487
|
-
var WHOLE_VALUE_FILE_REFERENCE = /^@\S+$/u;
|
|
1488
|
-
var ESCAPED_PAIR = /\\./gu;
|
|
1489
|
-
function is_whole_value_substitution(literal) {
|
|
1490
|
-
return WHOLE_VALUE_SUBSTITUTION.test(literal) && !literal.includes(BACKTICK);
|
|
1491
|
-
}
|
|
1492
|
-
function is_body_already_in_a_file(literal) {
|
|
1493
|
-
return WHOLE_VALUE_FILE_REFERENCE.test(literal) || is_whole_value_substitution(literal);
|
|
1494
|
-
}
|
|
1495
|
-
function is_evaluated_value(raw_value) {
|
|
1496
|
-
const literal = raw_value.replaceAll(ESCAPED_PAIR, "");
|
|
1497
|
-
if (is_body_already_in_a_file(literal)) return false;
|
|
1498
|
-
return SHELL_EVALUATED.test(literal);
|
|
1499
|
-
}
|
|
1500
|
-
function is_shell_evaluated_body(command) {
|
|
1501
|
-
for (const match of command.matchAll(INLINE_BODY_VALUE)) {
|
|
1502
|
-
if (is_evaluated_value(match[1] ?? "")) return true;
|
|
1503
|
-
}
|
|
1504
|
-
return false;
|
|
1505
|
-
}
|
|
1506
|
-
var FILE_FLAG = /(?:--body-file|--notify-message-file)(?:[\s=]|$)/u;
|
|
1507
|
-
var FIELD_FILE_REFERENCE = new RegExp(String.raw`${FIELD_FLAGS}[\s=]*body=@\S`, "u");
|
|
1508
|
-
var ANSI_C_BODY = /(?:(?:--body|--notify-message)[\s=]+|body=)\$'/u;
|
|
1509
|
-
var DOUBLE_QUOTED_SPAN = /"[^"]*"/gu;
|
|
1510
|
-
var BODY_FLAG = new RegExp(
|
|
1511
|
-
String.raw`${FIELD_FLAGS}[\s=]*'?"?body=|${MESSAGE_FLAGS}[\s=]|--body-file[\s=]|--notify-message-file[\s=]`,
|
|
1512
|
-
"u"
|
|
1513
|
-
);
|
|
1514
|
-
var QUOTED_SPAN2 = /"[^"]*"|'[^']*'/gu;
|
|
1515
|
-
function inline_body_literals(command) {
|
|
1516
|
-
return Array.from(
|
|
1517
|
-
command.matchAll(INLINE_BODY_VALUE),
|
|
1518
|
-
(match) => (match[1] ?? "").replaceAll(ESCAPED_PAIR, "")
|
|
1519
|
-
);
|
|
1520
|
-
}
|
|
1521
|
-
function is_safe_body_form(command) {
|
|
1522
|
-
if (ANSI_C_BODY.test(command.replaceAll(DOUBLE_QUOTED_SPAN, " "))) return true;
|
|
1523
|
-
const flags = command.replaceAll(QUOTED_SPAN2, " ");
|
|
1524
|
-
if (FILE_FLAG.test(flags) || FIELD_FILE_REFERENCE.test(flags)) return true;
|
|
1525
|
-
return inline_body_literals(command).some((literal) => is_body_already_in_a_file(literal));
|
|
1526
|
-
}
|
|
1527
|
-
function keeps_body_safe(command) {
|
|
1528
|
-
return is_safe_body_form(command) && !is_shell_evaluated_body(command);
|
|
1529
|
-
}
|
|
1530
|
-
function carries_a_body(command) {
|
|
1531
|
-
if (BODY_FLAG.test(command.replaceAll(QUOTED_SPAN2, " "))) return true;
|
|
1532
|
-
return inline_body_literals(command).length > 0;
|
|
1533
|
-
}
|
|
1534
|
-
var shell_body_trigger = {
|
|
1535
|
-
carries_a_body,
|
|
1536
|
-
is_safe_body_form,
|
|
1537
|
-
is_shell_evaluated_body,
|
|
1538
|
-
keeps_body_safe
|
|
1539
|
-
};
|
|
1540
|
-
|
|
1541
|
-
// scripts/rules/delivered-rules.ts
|
|
1542
|
-
var SWITCH_ENV_KEY = "JOSH_RULE_GUARD";
|
|
1543
|
-
var STAMP_PREFIX = "josh-rule-guard-";
|
|
1544
|
-
var ISSUE_CREATE_COMMAND = /\bgh\s+(?:\S+\s+)*?issue\s+create\b/u;
|
|
1545
|
-
var ISSUES_ENDPOINT = /repos\/[^\s'"]*\/issues(?=$|["'\s])/u;
|
|
1546
|
-
var TITLE_FIELD = /(?:-f|-F|--field|--raw-field)\s*'?title=/u;
|
|
1547
|
-
function is_issue_filing(command) {
|
|
1548
|
-
if (ISSUE_CREATE_COMMAND.test(command)) return true;
|
|
1549
|
-
return ISSUES_ENDPOINT.test(command) && TITLE_FIELD.test(command);
|
|
1550
|
-
}
|
|
1551
|
-
function on_bash_command(is_match) {
|
|
1552
|
-
return function is_trigger(call) {
|
|
1553
|
-
if (call.name !== cost_blocks.BASH_TOOL) return false;
|
|
1554
|
-
return is_match(time_shell.bash_command(call.input));
|
|
1555
|
-
};
|
|
1556
|
-
}
|
|
1557
|
-
var WIP_CAP_REASON = "\u26D4 backlog WIP cap: count the target repository's open Issues before filing. With more than 30 open, close one first; nothing honestly closable means do not file. Two filings are exempt and proceed while stating the overage \u2014 one the run is blocked by, and an interrupt, decided by three tests rather than judgement: a verification answers wrongly, a documented workflow cannot complete, or data is lost or written outside the repository. Meeting none of the three, the finding is discretionary and waits. The count command and both procedures are in `prompts/collaboration-workflow/wip-cap.md`. Reissue this call once you have counted \u2014 it fires once per run and cannot repeat on the call in hand.";
|
|
1558
|
-
var GH_FLAGS = String.raw`(?:-{1,2}[\w-]+(?:[= ][^\s]+)?\s+)*`;
|
|
1559
|
-
var ISSUE_VIEW_COMMAND = new RegExp(String.raw`^gh\s+${GH_FLAGS}issue\s+view\s`, "u");
|
|
1560
|
-
var GH_API_COMMAND = new RegExp(String.raw`^gh\s+${GH_FLAGS}api\s`, "u");
|
|
1561
|
-
var ISSUE_BODY_PATH = /repos\/[^\s'"]*\/issues\/\d+(?=$|["'\s])/u;
|
|
1562
|
-
var API_METHOD = /(?:^|\s)(?:--method|-X)[= ]([A-Za-z]+)/u;
|
|
1563
|
-
var API_FIELD = /(?:^|\s)(?:--raw-field|--field|--input|-F|-f)(?:[= ]|$)/u;
|
|
1564
|
-
var FETCHES_COMMENTS = /--comments\b|--json\s[\w,]*\bcomments\b|\/comments\b/u;
|
|
1565
|
-
var ISSUES_PATH = /repos\/[^\s'"]*\/issues\//u;
|
|
1566
|
-
function is_api_read(segment) {
|
|
1567
|
-
const method = API_METHOD.exec(segment)?.[1];
|
|
1568
|
-
if (method !== void 0) return method.toUpperCase() === "GET";
|
|
1569
|
-
return !API_FIELD.test(segment);
|
|
1570
|
-
}
|
|
1571
|
-
var FIELD_PROJECTION = /(?:--jq|--json)[= ]\s*('[^']*'|"[^"]*"|\S+)/u;
|
|
1572
|
-
var NAMES_THE_BODY = /\bbody\b/u;
|
|
1573
|
-
function projects_away_body(segment) {
|
|
1574
|
-
const projection = FIELD_PROJECTION.exec(segment);
|
|
1575
|
-
if (projection === null) return false;
|
|
1576
|
-
return !NAMES_THE_BODY.test(projection[1] ?? "");
|
|
1577
|
-
}
|
|
1578
|
-
function is_body_read_segment(segment) {
|
|
1579
|
-
if (projects_away_body(segment)) return false;
|
|
1580
|
-
if (ISSUE_VIEW_COMMAND.test(segment)) return true;
|
|
1581
|
-
return GH_API_COMMAND.test(segment) && ISSUE_BODY_PATH.test(segment) && is_api_read(segment);
|
|
1582
|
-
}
|
|
1583
|
-
function fetches_issue_comments(segment) {
|
|
1584
|
-
if (!FETCHES_COMMENTS.test(segment)) return false;
|
|
1585
|
-
return ISSUE_VIEW_COMMAND.test(segment) || GH_API_COMMAND.test(segment) && ISSUES_PATH.test(segment);
|
|
1586
|
-
}
|
|
1587
|
-
function is_body_only_issue_read(command) {
|
|
1588
|
-
const segments = shell_segments.segments_of(command);
|
|
1589
|
-
if (segments.some((segment) => fetches_issue_comments(segment))) return false;
|
|
1590
|
-
return segments.some((segment) => is_body_read_segment(segment));
|
|
1591
|
-
}
|
|
1592
|
-
var ISSUE_COMMENTS_REASON = "\u26D4 an Issue's comments are part of the Issue: read them before implementing, not only the body. A decision recorded after the body was written lives only in a comment \u2014 a corrected diagnosis (joshuafolkken/kit#1537), a changed default and an added acceptance criterion (joshuafolkken/kit#1520), a scope handed to another Issue (joshuafolkken/kit#1304) \u2014 and nothing in the body says it was superseded, so a body-only reader builds the wrong thing and sees no contradiction. Reissue this read with the comments included: `gh api repos/{owner}/{repo}/issues/<N>/comments --jq '.[] | {user: .user.login, created_at, body}'` beside the body read, or `gh issue view <N> --comments` where GraphQL is reachable. Then the later text is the agreement in force \u2014 a comment supersedes the body it contradicts \u2014 except for two answers that are not the run's to make: work a comment reassigns to another Issue is out of scope and is not implemented, and a comment saying the Issue no longer has a reason to exist stops the run with a `confirmation` Telegram. The procedure is `.claude/skills/workflow-commands/SKILL.md` \u2192 \"An Issue's comments are part of the Issue\". It fires once per run and cannot repeat on the call in hand.";
|
|
1593
|
-
var { carries_a_body: carries_a_body2, is_shell_evaluated_body: is_shell_evaluated_body2, keeps_body_safe: keeps_body_safe2 } = shell_body_trigger;
|
|
1594
|
-
var SHELL_BODY_REASON = "\u26D4 shell-evaluated body: this command carries a body inline in double quotes, and that body contains a backtick or a `$`. The shell evaluates both before the command runs, so the text is executed rather than merely mangled \u2014 joshuafolkken/kit#1198 recorded a PR comment whose own words ran as git commands and switched a lane's work tree onto main. Write the body to a file and pass it by path: `gh api repos/{owner}/{repo}/issues/<N>/comments --field body=@<path>` (a PR comment is an issue comment), `pnpm josh followup --notify-message-file <path>`, `pnpm josh notify --body-file <path>`, and `--body-file <path>` wherever a command offers it. `$'\u2026'` quoting is the other safe form and is unchanged. The rule and what the trigger cannot see are in `prompts/collaboration-workflow/shell-body.md`. Reissue this call once the body is in a file \u2014 it fires once per run and cannot repeat on the call in hand.";
|
|
1595
|
-
var ISSUE_LISTING = /^gh\s+(?:-{1,2}[\w-]+(?:[= ]\S+)?\s+)*issue\s+list\b/u;
|
|
1596
|
-
var ISSUES_QUERY = /repos\/[^\s'"]*\/issues\?[^\s'"]*state=open/u;
|
|
1597
|
-
var OPEN_STATE = /--state[= ]open|state=open/u;
|
|
1598
|
-
var LABEL_FILTER = /--label\b|[?&]labels=/u;
|
|
1599
|
-
function counts_open_issues(command) {
|
|
1600
|
-
return shell_segments.segments_of(command).some((segment) => {
|
|
1601
|
-
if (LABEL_FILTER.test(segment)) return false;
|
|
1602
|
-
return ISSUE_LISTING.test(segment) && OPEN_STATE.test(segment) || ISSUES_QUERY.test(segment);
|
|
1603
|
-
});
|
|
1604
|
-
}
|
|
1605
|
-
function reads_issue_comments(command) {
|
|
1606
|
-
return shell_segments.segments_of(command).some((segment) => fetches_issue_comments(segment));
|
|
1607
|
-
}
|
|
1608
|
-
var ASKS_ABOUT_THE_CUT = on_bash_command(pre_gate_cut.asks_about_the_cut);
|
|
1609
|
-
var CLAIMS_THE_HOLD = on_bash_command(pre_gate_cut.claims_the_hold);
|
|
1610
|
-
function reaches_the_pre_gate_boundary(call, _turn, run) {
|
|
1611
|
-
if (!ASKS_ABOUT_THE_CUT(call)) return false;
|
|
1612
|
-
return run.some((issued) => CLAIMS_THE_HOLD(issued));
|
|
1613
|
-
}
|
|
1614
|
-
var DELIVERED_RULES = [
|
|
1615
|
-
{
|
|
1616
|
-
id: "wip-cap",
|
|
1617
|
-
is_trigger: on_bash_command(is_issue_filing),
|
|
1618
|
-
reason: WIP_CAP_REASON,
|
|
1619
|
-
keeps: on_bash_command(counts_open_issues)
|
|
1620
|
-
},
|
|
1621
|
-
{
|
|
1622
|
-
id: "issue-comments",
|
|
1623
|
-
is_trigger: on_bash_command(is_body_only_issue_read),
|
|
1624
|
-
reason: ISSUE_COMMENTS_REASON,
|
|
1625
|
-
already_satisfied: prior_comment_read.already_read_for_call,
|
|
1626
|
-
keeps: on_bash_command(reads_issue_comments)
|
|
1627
|
-
},
|
|
1628
|
-
{
|
|
1629
|
-
id: "shell-body",
|
|
1630
|
-
is_trigger: on_bash_command(is_shell_evaluated_body2),
|
|
1631
|
-
reason: SHELL_BODY_REASON,
|
|
1632
|
-
keeps: on_bash_command(keeps_body_safe2),
|
|
1633
|
-
reaches: on_bash_command(carries_a_body2)
|
|
1634
|
-
},
|
|
1635
|
-
{
|
|
1636
|
-
id: "piped-verification",
|
|
1637
|
-
is_trigger: on_bash_command(piped_verification.is_masked_verification),
|
|
1638
|
-
reason: piped_verification.PIPED_VERIFICATION_REASON,
|
|
1639
|
-
keeps: on_bash_command(piped_verification.keeps_verdict_intact),
|
|
1640
|
-
reaches: on_bash_command(piped_verification.runs_verification)
|
|
1641
|
-
},
|
|
1642
|
-
{
|
|
1643
|
-
id: "early-heartbeat",
|
|
1644
|
-
is_trigger: on_bash_command(early_heartbeat.is_wait_timer),
|
|
1645
|
-
reason: early_heartbeat.EARLY_HEARTBEAT_REASON,
|
|
1646
|
-
decide: early_heartbeat.decide,
|
|
1647
|
-
keeps: on_bash_command(early_heartbeat.is_progress_watch),
|
|
1648
|
-
reaches: on_bash_command(early_heartbeat.waits_for_progress)
|
|
1649
|
-
},
|
|
1650
|
-
// **The one row whose trigger reads a field of the input beside the command**, so it supplies its
|
|
1651
|
-
// own tool-name check rather than going through `on_bash_command`: a push step already issued with
|
|
1652
|
-
// `run_in_background` is the rule obeyed, and refusing it would charge a run for doing the right
|
|
1653
|
-
// thing. It supplies `decide` because a push is a recurring act — one per child in a batch, and a
|
|
1654
|
-
// second inside one `fullrun` when round 2 fixes a finding in place.
|
|
1655
|
-
{
|
|
1656
|
-
id: "run-tail",
|
|
1657
|
-
is_trigger: run_tail.is_foreground_push_step,
|
|
1658
|
-
reason: run_tail.RUN_TAIL_REASON,
|
|
1659
|
-
decide: run_tail.decide,
|
|
1660
|
-
keeps: run_tail.is_backgrounded_push_step,
|
|
1661
|
-
reaches: run_tail.is_push_step_call
|
|
1662
|
-
},
|
|
1663
|
-
// **The one row whose trigger consults the world beside the command**, because "is this a lane that
|
|
1664
|
-
// has not cut" is not readable from the call: it is the working directory's own name and the cut
|
|
1665
|
-
// record on disk. Both reads are synchronous, and the command match runs first, so a run that never
|
|
1666
|
-
// types `pnpm josh gate` pays nothing for it.
|
|
1667
|
-
//
|
|
1668
|
-
// **Once per run rather than `decide`, and the direction of the error is why.** Taking the cut ends
|
|
1669
|
-
// the process, so this is not the recurring act joshuafolkken/kit#1570 wrote `decide` for — and the
|
|
1670
|
-
// five verdicts that legitimately leave a run at the gate (`not-a-lane`, `unready`, `busy`,
|
|
1671
|
-
// `failed`, `unknown`) all need the reissue to go through. A row that refused every time would wedge exactly
|
|
1672
|
-
// those runs; a row that refuses once cannot.
|
|
1673
|
-
//
|
|
1674
|
-
// **`pnpm josh rule:value` read this row with a ceiling of about 50%, and joshuafolkken/kit#1867 is
|
|
1675
|
-
// what removed it** (the ceiling was recorded here by joshuafolkken/kit#1864's review round 2). A
|
|
1676
|
-
// cut relaunches a *new session*, which the measurement groups as a run of its own, and that
|
|
1677
|
-
// resumed run issues the entry check — so scored on the asking alone it joined the denominator
|
|
1678
|
-
// while never being able to satisfy `keeps`, which only the cutting run can, and ten perfectly
|
|
1679
|
-
// obedient children read as ten kept out of twenty. **The two halves are separable, just not from
|
|
1680
|
-
// one call**: both processes issue byte-identical `--resume` strings, but the `fresh` verdict sends
|
|
1681
|
-
// a run on to claim the working-tree hold while the `resume` verdict tells its counterpart to skip
|
|
1682
|
-
// that claim. `reaches_the_pre_gate_boundary` is that conjunction, and it is why `CallTest` is
|
|
1683
|
-
// handed the run's calls beside the turn's rather than a second predicate slot of its own.
|
|
1684
|
-
// Dropping `reaches` altogether is still strictly worse — the trigger evaluates against the
|
|
1685
|
-
// *measuring* process's working directory, so it is always false from the main checkout and the row
|
|
1686
|
-
// would read as no runs at all.
|
|
1687
|
-
//
|
|
1688
|
-
// **It overlaps `piped-verification` on a piped gate call, and the order is safe in both
|
|
1689
|
-
// directions.** `pnpm josh gate | tail` in an uncut lane is a real instance of both defects; the
|
|
1690
|
-
// earlier row speaks, its own reason says to reissue, and this one delivers on the reissue — the
|
|
1691
|
-
// reading "the losing rule's delivery is still correct one call later" that the comment below
|
|
1692
|
-
// requires of any admissible overlap.
|
|
1693
|
-
{
|
|
1694
|
-
id: "pre-gate-cut",
|
|
1695
|
-
is_trigger: on_bash_command(pre_gate_cut.is_uncut_gate),
|
|
1696
|
-
reason: pre_gate_cut.PRE_GATE_CUT_REASON,
|
|
1697
|
-
keeps: on_bash_command(pre_gate_cut.takes_the_cut),
|
|
1698
|
-
reaches: reaches_the_pre_gate_boundary
|
|
1699
|
-
},
|
|
1700
|
-
// **The second row whose trigger consults the world beside the command** (joshuafolkken/kit#2034):
|
|
1701
|
-
// "is this a dispatched lane child" is the dispatch mark against this checkout's own issue, read
|
|
1702
|
-
// synchronously by `lane_child_marker.is_child_of`, and the command match runs first so an ordinary
|
|
1703
|
-
// run pays nothing for it.
|
|
1704
|
-
//
|
|
1705
|
-
// **Once per run rather than `decide`, and no `already_satisfied`.** The stop ends the turn, so it
|
|
1706
|
-
// is not the recurring act joshuafolkken/kit#1570 wrote `decide` for. It carries no
|
|
1707
|
-
// `already_satisfied` because the park it asks for is a GitHub label a synchronous guard cannot
|
|
1708
|
-
// read, and the transcript-tail alternative would read a child that merely *read*
|
|
1709
|
-
// `backlogrun-park.md` — whose prose carries the `labels[]=needs-decision` command — as compliant
|
|
1710
|
-
// and fall silent on a real violation. `lane-park.ts` states why one wasted reissue on the compliant
|
|
1711
|
-
// path is the safe direction. `keeps` reads an actual label-application `Bash` command, never the
|
|
1712
|
-
// tail, so it is not fooled the same way; `reaches` stays `is_trigger`, since a compliant run issues
|
|
1713
|
-
// the same confirmation notify and belongs in the denominator.
|
|
1714
|
-
{
|
|
1715
|
-
id: "lane-park",
|
|
1716
|
-
is_trigger: on_bash_command(lane_park.is_unparked_stop),
|
|
1717
|
-
reason: lane_park.LANE_PARK_REASON,
|
|
1718
|
-
keeps: on_bash_command(lane_park.records_the_park)
|
|
1719
|
-
}
|
|
1720
|
-
];
|
|
1721
|
-
var ALONE_IN_TURN = 1;
|
|
1722
|
-
function batches_the_turn(call, turn) {
|
|
1723
|
-
return time_batch_guard.is_guarded_call(call) && turn.length > ALONE_IN_TURN;
|
|
1724
|
-
}
|
|
1725
|
-
var BATCHING_RULE = {
|
|
1726
|
-
id: "batching",
|
|
1727
|
-
reason: time_batch_guard.REASON,
|
|
1728
|
-
keeps: batches_the_turn,
|
|
1729
|
-
reaches: time_batch_guard.is_guarded_call
|
|
1730
|
-
};
|
|
1731
|
-
var INVESTIGATION_RULE = {
|
|
1732
|
-
id: "investigation",
|
|
1733
|
-
reason: investigation_reads.REASON,
|
|
1734
|
-
reaches: investigation_reads.is_refusable_call
|
|
1735
|
-
};
|
|
1736
|
-
var MEASURED_RULES = [
|
|
1737
|
-
...DELIVERED_RULES,
|
|
1738
|
-
BATCHING_RULE,
|
|
1739
|
-
INVESTIGATION_RULE
|
|
1740
|
-
];
|
|
1741
|
-
var BATCH_STAMP = hook_decision.create_refusal_stamp(time_batch_guard.STAMP_PREFIX);
|
|
1742
|
-
var BATCH_REFUSAL_WINDOW_MS = 1e4;
|
|
1743
|
-
function will_batch_guard_refuse(tail, call, run) {
|
|
1744
|
-
const refused_at_ms = BATCH_STAMP.last_ms(BATCH_STAMP.path(run.transcript));
|
|
1745
|
-
if (run.now_ms - refused_at_ms < BATCH_REFUSAL_WINDOW_MS) return true;
|
|
1746
|
-
return time_batch_guard.should_block(tail, call, refused_at_ms);
|
|
1747
|
-
}
|
|
1748
|
-
function is_first_delivery(tail, call, delivered_at_ms, run) {
|
|
1749
|
-
if (delivered_at_ms !== hook_decision.NEVER_MS) return false;
|
|
1750
|
-
return !will_batch_guard_refuse(tail, call, run);
|
|
1751
|
-
}
|
|
1752
|
-
function delivery_decision(rule) {
|
|
1753
|
-
const { decide: decide4, already_satisfied } = rule;
|
|
1754
|
-
if (decide4 === void 0 && already_satisfied === void 0) return is_first_delivery;
|
|
1755
|
-
return function should_block(tail, call, delivered_at_ms, run) {
|
|
1756
|
-
if (already_satisfied?.(tail, call) === true) return false;
|
|
1757
|
-
if (decide4 === void 0) return is_first_delivery(tail, call, delivered_at_ms, run);
|
|
1758
|
-
return decide4(call, run, !will_batch_guard_refuse(tail, call, run));
|
|
1759
|
-
};
|
|
1760
|
-
}
|
|
1761
|
-
function guard_of(rule) {
|
|
1762
|
-
return hook_decision.create_transcript_guard({
|
|
1763
|
-
prefix: `${STAMP_PREFIX}${rule.id}-`,
|
|
1764
|
-
switch_key: SWITCH_ENV_KEY,
|
|
1765
|
-
is_candidate: rule.is_trigger,
|
|
1766
|
-
should_block: delivery_decision(rule),
|
|
1767
|
-
reason: rule.reason
|
|
1768
|
-
});
|
|
1769
|
-
}
|
|
1770
|
-
var GUARDS = new Map(DELIVERED_RULES.map((rule) => [rule.id, guard_of(rule)]));
|
|
1771
|
-
function delivery_path(rule_id, transcript_path) {
|
|
1772
|
-
const guard = GUARDS.get(rule_id);
|
|
1773
|
-
return guard === void 0 ? "" : guard.refusal_path(transcript_path);
|
|
1774
|
-
}
|
|
1775
|
-
function delivery(raw_payload, now_ms = Date.now()) {
|
|
1776
|
-
for (const guard of GUARDS.values()) {
|
|
1777
|
-
const reason = guard.refusal(raw_payload, now_ms);
|
|
1778
|
-
if (reason !== void 0) return reason;
|
|
1779
|
-
}
|
|
1780
|
-
return void 0;
|
|
1781
|
-
}
|
|
1782
|
-
function is_enabled() {
|
|
1783
|
-
return hook_decision.is_switch_enabled(SWITCH_ENV_KEY);
|
|
1784
|
-
}
|
|
1785
|
-
var delivered_rules = {
|
|
1786
|
-
DELIVERED_RULES,
|
|
1787
|
-
EARLY_HEARTBEAT_REASON: early_heartbeat.EARLY_HEARTBEAT_REASON,
|
|
1788
|
-
ISSUE_COMMENTS_REASON,
|
|
1789
|
-
LANE_PARK_REASON: lane_park.LANE_PARK_REASON,
|
|
1790
|
-
MEASURED_RULES,
|
|
1791
|
-
PIPED_VERIFICATION_REASON: piped_verification.PIPED_VERIFICATION_REASON,
|
|
1792
|
-
PRE_GATE_CUT_REASON: pre_gate_cut.PRE_GATE_CUT_REASON,
|
|
1793
|
-
RUN_TAIL_REASON: run_tail.RUN_TAIL_REASON,
|
|
1794
|
-
SHELL_BODY_REASON,
|
|
1795
|
-
SWITCH_ENV_KEY,
|
|
1796
|
-
WIP_CAP_REASON,
|
|
1797
|
-
delivery,
|
|
1798
|
-
delivery_path,
|
|
1799
|
-
is_body_only_issue_read,
|
|
1800
|
-
is_enabled,
|
|
1801
|
-
is_issue_filing,
|
|
1802
|
-
is_masked_verification: piped_verification.is_masked_verification,
|
|
1803
|
-
is_wait_timer: early_heartbeat.is_wait_timer
|
|
1804
|
-
};
|
|
1805
|
-
|
|
1806
|
-
// scripts/hooks/pretool-guard.ts
|
|
1807
|
-
function combine_outcomes(batch, extra_reason) {
|
|
1808
|
-
if (batch.reason !== void 0) return batch;
|
|
1809
|
-
if (extra_reason !== void 0) {
|
|
1810
|
-
return { reason: extra_reason, notice: void 0, fault: void 0 };
|
|
1811
|
-
}
|
|
1812
|
-
return batch;
|
|
1813
|
-
}
|
|
1814
|
-
function pretool_outcome(raw_payload) {
|
|
1815
|
-
const batch = batch_outcome(raw_payload);
|
|
1816
|
-
const investigation = investigation_refusal(raw_payload);
|
|
1817
|
-
const rule = delivered_rules.delivery(raw_payload);
|
|
1818
|
-
return combine_outcomes(batch, investigation ?? rule);
|
|
1819
|
-
}
|
|
1820
|
-
var pretool_guard = { combine_outcomes, pretool_outcome };
|
|
1821
|
-
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
1822
|
-
if (process.stdin.isTTY) hook_decision.report_no_payload("pretool:guard");
|
|
1823
|
-
else hook_decision.write_outcome(await text(process.stdin), pretool_outcome);
|
|
1824
|
-
}
|
|
1825
|
-
|
|
1826
|
-
export {
|
|
1827
|
-
pretool_outcome,
|
|
1828
|
-
pretool_guard
|
|
1829
|
-
};
|