@joshuafolkken/kit 1.972.0 → 1.983.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/epic-commands/SKILL.md +6 -5
- package/.claude/skills/workflow-commands/SKILL.md +9 -9
- package/.claude/skills/workflow-commands/background-commands.md +1 -1
- package/.claude/skills/workflow-commands/fullrun-steps.md +8 -8
- package/.claude/skills/workflow-commands/fullrun.md +1 -0
- package/.claude/skills/workflow-commands/halfrun.md +16 -12
- package/.claude/skills/workflow-commands/issue-comments.md +1 -1
- package/.claude/skills/workflow-commands/issue-scout.md +21 -22
- package/.claude/skills/workflow-commands/kickoff.md +8 -9
- package/.claude/skills/workflow-commands/observation-filing.md +9 -10
- package/.claude/skills/workflow-commands/prerequisite.md +7 -6
- package/.claude/skills/workflow-commands/retrospective.md +5 -5
- package/.claude/skills/workflow-commands/rule-residency.md +2 -2
- package/.claude/skills/workflow-commands/split-assessment.md +4 -3
- package/.claude/skills/workflow-commands/target-repository.md +7 -6
- package/.claude/skills/workflow-commands/working-tree-hold.md +16 -1
- package/README.md +24 -20
- package/dist/hooks/batch-guard.js +4 -4
- package/dist/hooks/{chunk-ZPLXLJ7O.js → chunk-47SPMGOD.js} +16 -1
- package/dist/hooks/{chunk-BVMSPG3M.js → chunk-5JX6HXDX.js} +3 -3
- package/dist/hooks/{chunk-KQG4776P.js → chunk-APVOACVT.js} +1 -1
- package/dist/hooks/{chunk-2K52BRWV.js → chunk-ASUZGZFT.js} +2 -2
- package/dist/hooks/{chunk-CRGJTD6E.js → chunk-ERXMJJOV.js} +1 -1
- package/dist/hooks/{chunk-WWTMHHZU.js → chunk-IM3KM25Y.js} +61 -30
- package/dist/hooks/{chunk-BNWIOJFU.js → chunk-LBXQHXMC.js} +138 -358
- package/dist/hooks/{chunk-NXL3SBXY.js → chunk-P3UX7SFN.js} +2 -2
- package/dist/hooks/{chunk-F6OM6SIG.js → chunk-RFPHKU7C.js} +12 -12
- package/dist/hooks/{chunk-ZYYSBNR6.js → chunk-ZT65LR3U.js} +4 -4
- 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 +4 -4
- package/dist/josh.js +16 -1
- package/package.json +2 -2
- package/prompts/collaboration-workflow/epic-bundle.md +8 -8
- package/prompts/collaboration-workflow/issue-template.md +5 -5
- package/prompts/collaboration-workflow/rule-delivery.md +43 -30
- package/prompts/collaboration-workflow/upstream-interrupt.md +1 -1
- package/prompts/collaboration-workflow/wip-cap.md +3 -3
- package/prompts/review.md +11 -9
- package/scripts/ci/pr-classification.ts +13 -12
- package/scripts/delegation/delegation-policy.ts +9 -9
- package/scripts/document/document-byte-budget.ts +1 -1
- package/scripts/git/issue-labels.ts +52 -5
- package/scripts/init/init-bootstrap.ts +92 -0
- package/scripts/init/init-install.ts +4 -1
- package/scripts/init/init-logic-workspace.ts +1 -1
- package/scripts/init/init.ts +23 -5
- package/scripts/init/start-plan.ts +1 -1
- package/scripts/init/start-steps.ts +5 -28
- package/scripts/issue/issue-file-cli.ts +188 -0
- package/scripts/issue/issue-file.ts +192 -0
- package/scripts/issue/issue-lint-cli.ts +1 -1
- package/scripts/issue/issue-scout-cli.ts +41 -97
- package/scripts/issue/issue-scout-closed.ts +102 -0
- package/scripts/josh/__snapshots__/josh-command-map.test.ts.snap +9 -1
- package/scripts/josh/josh-command-map.ts +1 -0
- package/scripts/josh/josh-commands-issue.ts +11 -0
- package/scripts/josh/josh-commands-project.ts +5 -1
- package/scripts/propagate/propagate-run.ts +1 -0
- package/scripts/propagate/propagate-select.ts +72 -0
- package/scripts/propagate/propagate-targets.ts +5 -3
- package/scripts/propagate/propagate.ts +86 -24
- package/scripts/repo/repository-labels.ts +91 -0
- package/scripts/rules/bash-triggers.ts +19 -6
- package/scripts/rules/delivered-rules.ts +28 -32
- package/scripts/rules/direct-filing.ts +38 -0
- package/scripts/rules/filing-cap.ts +11 -8
- package/scripts/rules/issue-fold.ts +4 -4
- package/scripts/rules/oracle-consulted.ts +4 -4
- package/scripts/rules/oracle-firing.ts +13 -16
- package/scripts/rules/rule-body-guard.ts +6 -9
- package/scripts/rules/stop-rules.ts +3 -2
- package/scripts/run/run-entry-cli.ts +34 -1
- package/scripts/run/run-halfrun-resume.ts +104 -0
- package/scripts/run/run-hold-cli.ts +27 -4
- package/scripts/run/run-hold.ts +11 -0
- package/scripts/security/security-audit.ts +12 -1
- package/scripts/sync/sync.ts +6 -0
- package/scripts/test/pilot-files.ts +0 -1
- package/scripts/version/fresh-metadata-cache.ts +36 -0
- package/scripts/version/latest-update.ts +2 -1
- package/scripts/rules/issue-bug-label.ts +0 -85
- package/scripts/rules/issue-filing-args.ts +0 -50
- package/scripts/rules/issue-scout.ts +0 -64
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ALREADY_DONE_LABEL,
|
|
3
3
|
BREAKING_CHANGE_LABEL,
|
|
4
|
-
|
|
4
|
+
BUGFIX_LABEL,
|
|
5
5
|
CONTEXT_CUT_THRESHOLD,
|
|
6
6
|
ENHANCEMENT_LABEL,
|
|
7
7
|
FULL_PAGE_QUERY,
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
NEEDS_DECISION_LABEL,
|
|
10
10
|
NEEDS_HUMAN_REVIEW_LABEL,
|
|
11
11
|
RECENT_REQUEST_WINDOW,
|
|
12
|
+
RELEASE_CLASSIFICATION_NAMES,
|
|
12
13
|
agent_role_profile,
|
|
13
14
|
agent_session_environment,
|
|
14
15
|
bash_triggers,
|
|
@@ -18,7 +19,6 @@ import {
|
|
|
18
19
|
git_gh_api_path,
|
|
19
20
|
git_gh_exec,
|
|
20
21
|
has_any_label,
|
|
21
|
-
has_label_name,
|
|
22
22
|
has_stderr_field,
|
|
23
23
|
lane_background,
|
|
24
24
|
lane_park,
|
|
@@ -35,31 +35,31 @@ import {
|
|
|
35
35
|
run_watcher_guard,
|
|
36
36
|
shell_segments,
|
|
37
37
|
tail_commands
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-IM3KM25Y.js";
|
|
39
39
|
import {
|
|
40
40
|
find_local_bin_upwards
|
|
41
41
|
} from "./chunk-3V37IID6.js";
|
|
42
42
|
import {
|
|
43
43
|
batch_outcome
|
|
44
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-P3UX7SFN.js";
|
|
45
45
|
import {
|
|
46
46
|
investigation_refusal
|
|
47
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-5JX6HXDX.js";
|
|
48
48
|
import {
|
|
49
49
|
duplicate_read_outcome
|
|
50
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-ZT65LR3U.js";
|
|
51
51
|
import {
|
|
52
52
|
investigation_reads
|
|
53
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-RFPHKU7C.js";
|
|
54
54
|
import {
|
|
55
55
|
lane_guard_policy,
|
|
56
56
|
time_batch_guard
|
|
57
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-ASUZGZFT.js";
|
|
58
58
|
import {
|
|
59
59
|
hook_decision,
|
|
60
60
|
lane_child_marker,
|
|
61
61
|
run_issue_number
|
|
62
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-APVOACVT.js";
|
|
63
63
|
import {
|
|
64
64
|
PLATFORM_TEMP_ROOT,
|
|
65
65
|
cost_attribute,
|
|
@@ -69,14 +69,28 @@ import {
|
|
|
69
69
|
json_value,
|
|
70
70
|
lane_paths,
|
|
71
71
|
stamp_file,
|
|
72
|
-
time_density_hook,
|
|
73
72
|
time_shell
|
|
74
|
-
} from "./chunk-
|
|
73
|
+
} from "./chunk-47SPMGOD.js";
|
|
75
74
|
|
|
76
75
|
// scripts/hooks/pretool-guard.ts
|
|
77
76
|
import { text } from "node:stream/consumers";
|
|
78
77
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
79
78
|
|
|
79
|
+
// scripts/rules/direct-filing.ts
|
|
80
|
+
var DIRECT_FILING_REASON = '\u26D4 direct filing: an Issue is filed with `pnpm josh issue:file "<title>" --body-file <path> --depth <0|1|2> [--route <route>] [--label <name>] [--repo <owner/repo>]`, never with `gh api \u2026/issues` or `gh issue create`. The command runs every filing step in order \u2014 the duplicate scout, the body lint and its classification labels, the depth and `route:` labels, the `## Origin` check when filing into another repository, and `epic:bundle` after the create \u2014 so none of them is a step to remember. When the scout prints candidates it holds the filing: read each, then reissue with `--distinct <N,\u2026>` for the ones that are separate deliverables. Reference: `docs/josh-commands.md` \u2192 "`josh issue:file`". **This rule fires on every occurrence, not once per run.**';
|
|
81
|
+
function is_any_filing(command) {
|
|
82
|
+
return bash_triggers.is_direct_filing(command) || bash_triggers.is_issue_filing(command);
|
|
83
|
+
}
|
|
84
|
+
var ROW = {
|
|
85
|
+
id: "direct-filing",
|
|
86
|
+
is_trigger: bash_triggers.on_bash_command(bash_triggers.is_direct_filing),
|
|
87
|
+
reason: DIRECT_FILING_REASON,
|
|
88
|
+
decide: () => true,
|
|
89
|
+
keeps: bash_triggers.on_bash_command(bash_triggers.is_issue_filing),
|
|
90
|
+
reaches: bash_triggers.on_bash_command(is_any_filing)
|
|
91
|
+
};
|
|
92
|
+
var direct_filing = { DIRECT_FILING_REASON, ROW };
|
|
93
|
+
|
|
80
94
|
// scripts/run/run-progress.ts
|
|
81
95
|
var DEFAULT_INTERVAL_MINUTES = 20;
|
|
82
96
|
var INTERVAL_KEY = "JOSH_PROGRESS_INTERVAL_MINUTES";
|
|
@@ -380,7 +394,7 @@ function carries_a_file_body(command, has_file = existsSync) {
|
|
|
380
394
|
return writes_existing_file(command, has_file) || has_body_interpreter(command);
|
|
381
395
|
}
|
|
382
396
|
var FILE_BODY_REASON = "\u26D4 file body in a shell command: this carries a file's new text inline \u2014 a heredoc rewriting an existing file, an interpreter (`python3 - <<`, `node -e`) whose body writes, or an in-place `perl -0pi -e`. `CLAUDE.md` forbids it because the command body is re-read on every later request of the run, so an editing script written early is billed for the rest of it (`file-edits.md` measured one run at ~2m45s spent retyping existing lines). Edit a region with the Edit tool (old / new), or pass a body by path (`--field body=@<path>`, `--body-file <path>`). A new-file creation (`cat > <new-file> <<EOF`), a read-only heredoc (`cat <<EOF`), and a short `sed -i` stay allowed. **This rule fires on every occurrence, not once per run.**";
|
|
383
|
-
var
|
|
397
|
+
var ROW2 = {
|
|
384
398
|
id: "file-body",
|
|
385
399
|
is_trigger: bash_triggers.on_bash_command((command) => carries_a_file_body(command)),
|
|
386
400
|
reason: FILE_BODY_REASON,
|
|
@@ -388,7 +402,7 @@ var ROW = {
|
|
|
388
402
|
};
|
|
389
403
|
var file_body = {
|
|
390
404
|
FILE_BODY_REASON,
|
|
391
|
-
ROW,
|
|
405
|
+
ROW: ROW2,
|
|
392
406
|
carries_a_file_body,
|
|
393
407
|
is_in_place_perl,
|
|
394
408
|
is_interpreter_write,
|
|
@@ -516,7 +530,7 @@ function is_force_or_delete(command) {
|
|
|
516
530
|
return shell_segments.segments_of(command).some((segment) => is_destructive_segment(segment));
|
|
517
531
|
}
|
|
518
532
|
var GIT_FORCE_REASON = "\u26D4 force push or branch delete: this rewrites or removes shared history, which is Tier C and needs explicit current-turn user instruction \u2014 never an AI decision, even to fix a problem you caused (`prompts/collaboration-workflow/operating-rules.md`). The `.claude/settings.json` deny list matches globs, so it misses this spelling: a combined short cluster (`git push -uf`), a `git -C <path>` prefix, and the colon-form delete (`git push origin :branch`) all pass it. This row reads the argv instead, so the spelling does not matter. For an ordinary push use `pnpm josh git`; a force push, a `git push --delete` / `:branch`, or a `git branch -d` / `-D` is the user's to run in their own terminal. **This rule fires on every occurrence, not once per run.**";
|
|
519
|
-
var
|
|
533
|
+
var ROW3 = {
|
|
520
534
|
id: "git-force",
|
|
521
535
|
is_trigger: bash_triggers.on_bash_command(is_force_or_delete),
|
|
522
536
|
reason: GIT_FORCE_REASON,
|
|
@@ -524,7 +538,7 @@ var ROW2 = {
|
|
|
524
538
|
};
|
|
525
539
|
var git_force = {
|
|
526
540
|
GIT_FORCE_REASON,
|
|
527
|
-
ROW:
|
|
541
|
+
ROW: ROW3,
|
|
528
542
|
is_branch_delete,
|
|
529
543
|
is_force_or_delete,
|
|
530
544
|
is_force_or_delete_push
|
|
@@ -813,8 +827,8 @@ function selected_threshold(values) {
|
|
|
813
827
|
function threshold_option(over) {
|
|
814
828
|
return over === void 0 ? {} : { over };
|
|
815
829
|
}
|
|
816
|
-
function path_option(
|
|
817
|
-
return
|
|
830
|
+
function path_option(path6) {
|
|
831
|
+
return path6 === void 0 ? {} : { path: path6 };
|
|
818
832
|
}
|
|
819
833
|
function to_options(values) {
|
|
820
834
|
if (values.cut === true && values.over !== void 0) return void 0;
|
|
@@ -1054,7 +1068,7 @@ function is_reissued_refusal(now_ms, delivered_at_ms) {
|
|
|
1054
1068
|
function decide2(_call, run2, _can_record, delivered_at_ms) {
|
|
1055
1069
|
return !is_reissued_refusal(run2.now_ms, delivered_at_ms);
|
|
1056
1070
|
}
|
|
1057
|
-
var
|
|
1071
|
+
var ROW4 = {
|
|
1058
1072
|
id: "implementation-cut",
|
|
1059
1073
|
is_trigger: is_over_threshold_edit,
|
|
1060
1074
|
reason: IMPLEMENTATION_CUT_REASON,
|
|
@@ -1064,7 +1078,7 @@ var ROW3 = {
|
|
|
1064
1078
|
var implementation_cut = {
|
|
1065
1079
|
IMPLEMENTATION_CUT_REASON,
|
|
1066
1080
|
REISSUE_WINDOW_MS,
|
|
1067
|
-
ROW:
|
|
1081
|
+
ROW: ROW4,
|
|
1068
1082
|
decide: decide2,
|
|
1069
1083
|
is_edit_tool,
|
|
1070
1084
|
is_over_threshold_edit,
|
|
@@ -1073,201 +1087,6 @@ var implementation_cut = {
|
|
|
1073
1087
|
uncut_run_issue
|
|
1074
1088
|
};
|
|
1075
1089
|
|
|
1076
|
-
// scripts/rules/issue-bug-label.ts
|
|
1077
|
-
import { readFileSync as readFileSync3 } from "node:fs";
|
|
1078
|
-
import path4 from "node:path";
|
|
1079
|
-
|
|
1080
|
-
// scripts/issue/markdown-section.ts
|
|
1081
|
-
var HEADING_PREFIX = "## ";
|
|
1082
|
-
function is_heading(line) {
|
|
1083
|
-
return line.trimStart().startsWith(HEADING_PREFIX);
|
|
1084
|
-
}
|
|
1085
|
-
function is_the_heading(line, heading) {
|
|
1086
|
-
return line.trim() === heading;
|
|
1087
|
-
}
|
|
1088
|
-
function next_fence(line, fence) {
|
|
1089
|
-
const marker = /^ {0,3}(`{3,}|~{3,})/u.exec(line)?.[1];
|
|
1090
|
-
if (fence === "") return marker;
|
|
1091
|
-
const closing = new RegExp(
|
|
1092
|
-
String.raw`^ {0,3}${fence.charAt(0)}{${String(fence.length)},}\s*$`,
|
|
1093
|
-
"u"
|
|
1094
|
-
);
|
|
1095
|
-
if (closing.test(line)) return "";
|
|
1096
|
-
return void 0;
|
|
1097
|
-
}
|
|
1098
|
-
function unfenced_lines(body) {
|
|
1099
|
-
let fence = "";
|
|
1100
|
-
const visible = [];
|
|
1101
|
-
for (const line of body.split("\n")) {
|
|
1102
|
-
const next = next_fence(line, fence);
|
|
1103
|
-
const visible_line = line.replace(/^(?: {4}|\t).*/u, "");
|
|
1104
|
-
visible.push(next === void 0 && fence === "" ? visible_line : "");
|
|
1105
|
-
fence = next ?? fence;
|
|
1106
|
-
}
|
|
1107
|
-
return visible;
|
|
1108
|
-
}
|
|
1109
|
-
function has_unfenced_line(body, text2) {
|
|
1110
|
-
return unfenced_lines(body).some((line) => line.trim() === text2);
|
|
1111
|
-
}
|
|
1112
|
-
function has_line(body, text2) {
|
|
1113
|
-
return has_unfenced_line(body, text2);
|
|
1114
|
-
}
|
|
1115
|
-
function section_lines(body, heading) {
|
|
1116
|
-
const lines = body.split("\n");
|
|
1117
|
-
const visible = unfenced_lines(body);
|
|
1118
|
-
const start = visible.findIndex((line) => is_the_heading(line, heading));
|
|
1119
|
-
if (start === -1) return [];
|
|
1120
|
-
const rest = lines.slice(start + 1);
|
|
1121
|
-
const end = visible.slice(start + 1).findIndex((line) => is_heading(line));
|
|
1122
|
-
return end === -1 ? rest : rest.slice(0, end);
|
|
1123
|
-
}
|
|
1124
|
-
var markdown_section = { section_lines, is_heading, has_line, has_unfenced_line };
|
|
1125
|
-
|
|
1126
|
-
// scripts/issue/issue-bug-label.ts
|
|
1127
|
-
var BUG_DECLARATION_LINE = "- \u7A2E\u5225: \u4E0D\u5177\u5408";
|
|
1128
|
-
function is_bug_fix(body) {
|
|
1129
|
-
const background = markdown_section.section_lines(body, "## \u80CC\u666F").join("\n");
|
|
1130
|
-
return markdown_section.has_unfenced_line(background, BUG_DECLARATION_LINE);
|
|
1131
|
-
}
|
|
1132
|
-
var issue_bug_label = { BUG_DECLARATION_LINE, is_bug_fix };
|
|
1133
|
-
|
|
1134
|
-
// scripts/issue/issue-classification.ts
|
|
1135
|
-
var FEATURE_DECLARATIONS = ["- \u76EE\u7684: \u6A5F\u80FD\u8FFD\u52A0", "- \u76EE\u7684: \u6A5F\u80FD\u6539\u5584"];
|
|
1136
|
-
var BREAKING_DECLARATION = "- \u4E92\u63DB\u6027: \u7834\u58CA\u7684\u5909\u66F4";
|
|
1137
|
-
var NON_BUG_DECLARATION = "- \u7A2E\u5225: \u975E\u4E0D\u5177\u5408";
|
|
1138
|
-
var CLASSIFICATION_LABELS = [BUG_LABEL, ENHANCEMENT_LABEL, BREAKING_CHANGE_LABEL];
|
|
1139
|
-
var CONFLICT = "bug and enhancement declarations cannot be combined";
|
|
1140
|
-
var MISSING_BUG_CLASSIFICATION = "missing bug classification";
|
|
1141
|
-
var CONFLICTING_BUG_CLASSIFICATIONS = "conflicting bug classifications";
|
|
1142
|
-
function required_labels(body) {
|
|
1143
|
-
const labels = [];
|
|
1144
|
-
const background = markdown_section.section_lines(body, "## \u80CC\u666F").join("\n");
|
|
1145
|
-
if (issue_bug_label.is_bug_fix(body)) labels.push(BUG_LABEL);
|
|
1146
|
-
if (FEATURE_DECLARATIONS.some((line) => markdown_section.has_unfenced_line(background, line))) {
|
|
1147
|
-
labels.push(ENHANCEMENT_LABEL);
|
|
1148
|
-
}
|
|
1149
|
-
if (markdown_section.has_unfenced_line(background, BREAKING_DECLARATION)) {
|
|
1150
|
-
labels.push(BREAKING_CHANGE_LABEL);
|
|
1151
|
-
}
|
|
1152
|
-
return labels;
|
|
1153
|
-
}
|
|
1154
|
-
function labels_for(body, existing) {
|
|
1155
|
-
const missing = required_labels(body).filter((label) => !has_label_name(existing, label));
|
|
1156
|
-
return [...existing, ...missing];
|
|
1157
|
-
}
|
|
1158
|
-
function bug_classification_problem(body) {
|
|
1159
|
-
const background = markdown_section.section_lines(body, "## \u80CC\u666F").join("\n");
|
|
1160
|
-
const has_bug = issue_bug_label.is_bug_fix(body);
|
|
1161
|
-
const has_non_bug = markdown_section.has_unfenced_line(background, NON_BUG_DECLARATION);
|
|
1162
|
-
if (!has_bug && !has_non_bug) return MISSING_BUG_CLASSIFICATION;
|
|
1163
|
-
if (has_bug && has_non_bug) return CONFLICTING_BUG_CLASSIFICATIONS;
|
|
1164
|
-
return void 0;
|
|
1165
|
-
}
|
|
1166
|
-
function problems(body) {
|
|
1167
|
-
const classification_problem = bug_classification_problem(body);
|
|
1168
|
-
const found = [];
|
|
1169
|
-
if (classification_problem !== void 0) found.push(classification_problem);
|
|
1170
|
-
const labels = required_labels(body);
|
|
1171
|
-
if (labels.includes(BUG_LABEL) && labels.includes(ENHANCEMENT_LABEL)) found.push(CONFLICT);
|
|
1172
|
-
return found;
|
|
1173
|
-
}
|
|
1174
|
-
var issue_classification = {
|
|
1175
|
-
BREAKING_DECLARATION,
|
|
1176
|
-
CLASSIFICATION_LABELS,
|
|
1177
|
-
FEATURE_DECLARATIONS,
|
|
1178
|
-
NON_BUG_DECLARATION,
|
|
1179
|
-
labels_for,
|
|
1180
|
-
problems,
|
|
1181
|
-
required_labels
|
|
1182
|
-
};
|
|
1183
|
-
|
|
1184
|
-
// scripts/rules/issue-filing-args.ts
|
|
1185
|
-
var SHELL_WORD = /(?:[^\s'"]|'[^']*'|"[^"]*")+/gu;
|
|
1186
|
-
var QUOTE = /['"]/gu;
|
|
1187
|
-
var RAW_FIELDS = /* @__PURE__ */ new Set(["-f", "--raw-field"]);
|
|
1188
|
-
var FILE_FIELDS = /* @__PURE__ */ new Set(["-F", "--field"]);
|
|
1189
|
-
var LABEL_OPTIONS = /* @__PURE__ */ new Set(["-l", "--label"]);
|
|
1190
|
-
var BODY_FILE_OPTIONS = /* @__PURE__ */ new Set(["--body-file"]);
|
|
1191
|
-
var INLINE_BODY_OPTIONS = /* @__PURE__ */ new Set(["-b", "--body"]);
|
|
1192
|
-
var BODY_FILE_PREFIX = "body=@";
|
|
1193
|
-
function words_of2(command) {
|
|
1194
|
-
return [...command.matchAll(SHELL_WORD)].map((match) => match[0].replaceAll(QUOTE, ""));
|
|
1195
|
-
}
|
|
1196
|
-
function values_after(words, flags) {
|
|
1197
|
-
return words.flatMap((word, index) => {
|
|
1198
|
-
const value = words[index + 1];
|
|
1199
|
-
return value !== void 0 && flags.has(word) ? [value] : [];
|
|
1200
|
-
});
|
|
1201
|
-
}
|
|
1202
|
-
function has_inline_body(words) {
|
|
1203
|
-
const has_raw = values_after(words, RAW_FIELDS).some((value) => value.startsWith("body="));
|
|
1204
|
-
return has_raw || values_after(words, INLINE_BODY_OPTIONS).length > 0;
|
|
1205
|
-
}
|
|
1206
|
-
function body_file(command) {
|
|
1207
|
-
const words = words_of2(command);
|
|
1208
|
-
const fields = values_after(words, FILE_FIELDS).filter((value) => value.startsWith("body="));
|
|
1209
|
-
const options = values_after(words, BODY_FILE_OPTIONS);
|
|
1210
|
-
if (has_inline_body(words) || fields.length + options.length !== 1) return void 0;
|
|
1211
|
-
return fields[0]?.startsWith(BODY_FILE_PREFIX) ? fields[0].slice(BODY_FILE_PREFIX.length) : options[0];
|
|
1212
|
-
}
|
|
1213
|
-
function has_label(command, label) {
|
|
1214
|
-
const words = words_of2(command);
|
|
1215
|
-
const fields = [...values_after(words, RAW_FIELDS), ...values_after(words, FILE_FIELDS)];
|
|
1216
|
-
const options = values_after(words, LABEL_OPTIONS).flatMap((value) => value.split(","));
|
|
1217
|
-
return fields.includes(`labels[]=${label}`) || options.includes(label);
|
|
1218
|
-
}
|
|
1219
|
-
var issue_filing_args = { body_file, has_label, words_of: words_of2 };
|
|
1220
|
-
|
|
1221
|
-
// scripts/rules/issue-bug-label.ts
|
|
1222
|
-
var LINT_NAMES = /* @__PURE__ */ new Set(["issue:lint"]);
|
|
1223
|
-
var LINT_PATH = /\b(?:issue:lint|iln)\s+(?:'([^']+)'|"([^"]+)"|(\S+))/u;
|
|
1224
|
-
function lint_argument(segment) {
|
|
1225
|
-
const [, single_quoted, double_quoted, bare] = LINT_PATH.exec(segment) ?? [];
|
|
1226
|
-
return single_quoted ?? double_quoted ?? bare;
|
|
1227
|
-
}
|
|
1228
|
-
function latest_lint_path(tail) {
|
|
1229
|
-
const segments = tail_commands.prior_bash_commands(tail).flatMap((command) => shell_segments.segments_of(command));
|
|
1230
|
-
const lint = segments.findLast((segment) => shell_segments.is_josh_command(segment, LINT_NAMES));
|
|
1231
|
-
return lint === void 0 ? void 0 : lint_argument(lint);
|
|
1232
|
-
}
|
|
1233
|
-
function linted_body(body_path) {
|
|
1234
|
-
try {
|
|
1235
|
-
return readFileSync3(body_path, "utf8");
|
|
1236
|
-
} catch {
|
|
1237
|
-
return void 0;
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
|
-
function matches_linted_file(command, lint_path) {
|
|
1241
|
-
const filing_path = issue_filing_args.body_file(command);
|
|
1242
|
-
return filing_path !== void 0 && path4.resolve(filing_path) === path4.resolve(lint_path);
|
|
1243
|
-
}
|
|
1244
|
-
function needs_classification_labels(command, tail) {
|
|
1245
|
-
const lint_path = latest_lint_path(tail);
|
|
1246
|
-
if (lint_path === void 0) return false;
|
|
1247
|
-
const body = linted_body(lint_path);
|
|
1248
|
-
if (body === void 0) return false;
|
|
1249
|
-
if (!matches_linted_file(command, lint_path) || issue_classification.problems(body).length > 0) {
|
|
1250
|
-
return true;
|
|
1251
|
-
}
|
|
1252
|
-
const expected = issue_classification.required_labels(body);
|
|
1253
|
-
return issue_classification.CLASSIFICATION_LABELS.some(
|
|
1254
|
-
(label) => expected.includes(label) !== issue_filing_args.has_label(command, label)
|
|
1255
|
-
);
|
|
1256
|
-
}
|
|
1257
|
-
function decide3(call, run2) {
|
|
1258
|
-
const command = time_shell.bash_command(call.input);
|
|
1259
|
-
const tail = time_density_hook.read_tail(run2.transcript);
|
|
1260
|
-
return needs_classification_labels(command, tail);
|
|
1261
|
-
}
|
|
1262
|
-
var ISSUE_CLASSIFICATION_REASON = "\u26D4 issue classification mismatch: use the exact body file checked by `pnpm josh issue:lint` in this creation call (`-F body=@<path>` or `gh issue create --body-file <path>`) and apply exactly the `labels:` it reports (`bug`, `enhancement`, `breaking-change`). See `prompts/collaboration-workflow/issue-template.md`.";
|
|
1263
|
-
var ROW4 = {
|
|
1264
|
-
id: "issue-bug-label",
|
|
1265
|
-
is_trigger: bash_triggers.on_bash_command(bash_triggers.is_issue_filing),
|
|
1266
|
-
reason: ISSUE_CLASSIFICATION_REASON,
|
|
1267
|
-
decide: decide3
|
|
1268
|
-
};
|
|
1269
|
-
var issue_bug_label_rule = { ROW: ROW4, latest_lint_path, needs_classification_labels };
|
|
1270
|
-
|
|
1271
1090
|
// scripts/rules/issue-fold.ts
|
|
1272
1091
|
var FOLD_NAMES = /* @__PURE__ */ new Set(["issue:fold"]);
|
|
1273
1092
|
function runs_the_fold(command) {
|
|
@@ -1277,7 +1096,7 @@ function already_folded(tail, _call) {
|
|
|
1277
1096
|
if (filing_cap.prior_filing_count(tail) === 0) return true;
|
|
1278
1097
|
return tail_commands.prior_bash_commands(tail).some((command) => runs_the_fold(command));
|
|
1279
1098
|
}
|
|
1280
|
-
var ISSUE_FOLD_REASON = '\u26D4 issue fold: this run has filed an Issue before and is filing another with no `pnpm josh issue:fold` between them. Several findings from one session fold into one Issue by default \u2014 the filing-time counterpart to the split assessment, whose two questions it reuses: are the findings separable, and does the whole clearly exceed one verification gate (the size half is `split:assess`, called not recomputed). Reissue after folding: `pnpm josh issue:fold "<title>" "<title>" \u2026` \u2014 `fold` means file one Issue covering them, `separate` means file them apart (separable findings whose combined size clears the split guide), and the default is `fold`. Add `--not-separable` when they are really one deliverable. The procedure is `.claude/skills/workflow-commands/split-assessment.md` \u2192 "The question".
|
|
1099
|
+
var ISSUE_FOLD_REASON = '\u26D4 issue fold: this run has filed an Issue before and is filing another with no `pnpm josh issue:fold` between them. Several findings from one session fold into one Issue by default \u2014 the filing-time counterpart to the split assessment, whose two questions it reuses: are the findings separable, and does the whole clearly exceed one verification gate (the size half is `split:assess`, called not recomputed). Reissue after folding: `pnpm josh issue:fold "<title>" "<title>" \u2026` \u2014 `fold` means file one Issue covering them, `separate` means file them apart (separable findings whose combined size clears the split guide), and the default is `fold`. Add `--not-separable` when they are really one deliverable. The procedure is `.claude/skills/workflow-commands/split-assessment.md` \u2192 "The question". Every later filing is refused until the fold is on the transcript.';
|
|
1281
1100
|
var ROW5 = {
|
|
1282
1101
|
id: "issue-fold",
|
|
1283
1102
|
is_trigger: bash_triggers.on_bash_command(bash_triggers.is_issue_filing),
|
|
@@ -1287,24 +1106,6 @@ var ROW5 = {
|
|
|
1287
1106
|
};
|
|
1288
1107
|
var issue_fold = { ISSUE_FOLD_REASON, ROW: ROW5, already_folded, runs_the_fold };
|
|
1289
1108
|
|
|
1290
|
-
// scripts/rules/issue-scout.ts
|
|
1291
|
-
var SCOUT_NAMES = /* @__PURE__ */ new Set(["issue:scout"]);
|
|
1292
|
-
function runs_the_scout(command) {
|
|
1293
|
-
return shell_segments.segments_of(command).some((segment) => shell_segments.is_josh_command(segment, SCOUT_NAMES));
|
|
1294
|
-
}
|
|
1295
|
-
function already_scouted(tail, _call) {
|
|
1296
|
-
return tail_commands.prior_bash_commands(tail).some((command) => runs_the_scout(command));
|
|
1297
|
-
}
|
|
1298
|
-
var ISSUE_SCOUT_REASON = '\u26D4 issue scout: this files an Issue with no `pnpm josh issue:scout` earlier in the run. A duplicate is likeliest for the work that just finished, which is exactly where self-restraint is weakest. Reissue after scouting: `pnpm josh issue:scout "<title>"` (add `--body "<one-line summary, citing #N where the work follows one>"` so the epic half can answer), then read its `Duplicates:` \u2014 an **open** candidate covering the same work stops the run rather than filing a second Issue, and a `(closed)` one means the work is already done \u2014 before reissuing this filing. The procedure is `.claude/skills/workflow-commands/SKILL.md` \u2192 \xA72e. It fires once per run and cannot repeat on the call in hand.';
|
|
1299
|
-
var ROW6 = {
|
|
1300
|
-
id: "issue-scout",
|
|
1301
|
-
is_trigger: bash_triggers.on_bash_command(bash_triggers.is_issue_filing),
|
|
1302
|
-
reason: ISSUE_SCOUT_REASON,
|
|
1303
|
-
already_satisfied: already_scouted,
|
|
1304
|
-
keeps: bash_triggers.on_bash_command(runs_the_scout)
|
|
1305
|
-
};
|
|
1306
|
-
var issue_scout = { ISSUE_SCOUT_REASON, ROW: ROW6, already_scouted, runs_the_scout };
|
|
1307
|
-
|
|
1308
1109
|
// scripts/rules/josh-git-bare.ts
|
|
1309
1110
|
var GIT_COMMAND2 = /* @__PURE__ */ new Set(["git"]);
|
|
1310
1111
|
var CONFIRM_FLAGS = /* @__PURE__ */ new Set(["-y", "--yes"]);
|
|
@@ -1319,7 +1120,7 @@ function is_bare_josh_git(command) {
|
|
|
1319
1120
|
return shell_segments.segments_of(command).some((segment) => is_bare_segment(segment));
|
|
1320
1121
|
}
|
|
1321
1122
|
var JOSH_GIT_BARE_REASON = '\u26D4 bare `pnpm josh git`: this prompts to confirm the staging, and with no TTY the prompt falls to its cancel branch \u2014 `Checking unstaged files... Found` then `Operation cancelled.` \u2014 so the call throws away the time it takes to fail (about 31 seconds in one measured lane) and you reissue it with `-y` anyway (joshuafolkken/kit#2297). Run it unattended from the start: `pnpm josh git -y "<title> #<N>"`. The recovery form that opens the PR for a push that already landed is `pnpm josh git -y --skip-commit --skip-push`. This rule fires on every occurrence, not once per run.';
|
|
1322
|
-
var
|
|
1123
|
+
var ROW6 = {
|
|
1323
1124
|
id: "josh-git-bare",
|
|
1324
1125
|
is_trigger: bash_triggers.on_bash_command(is_bare_josh_git),
|
|
1325
1126
|
reason: JOSH_GIT_BARE_REASON,
|
|
@@ -1327,7 +1128,7 @@ var ROW7 = {
|
|
|
1327
1128
|
};
|
|
1328
1129
|
var josh_git_bare = {
|
|
1329
1130
|
JOSH_GIT_BARE_REASON,
|
|
1330
|
-
ROW:
|
|
1131
|
+
ROW: ROW6,
|
|
1331
1132
|
is_bare_josh_git
|
|
1332
1133
|
};
|
|
1333
1134
|
|
|
@@ -1341,7 +1142,7 @@ function is_carry_conflict(command, is_lane_child = lane_child_marker.is_child_o
|
|
|
1341
1142
|
return is_lane_child;
|
|
1342
1143
|
}
|
|
1343
1144
|
var LANE_CARRY_CONFLICT_REASON = '\u26D4 lane child carry conflict: this session is a dispatched lane child, and `pnpm josh run:merge` / `pnpm josh run:carry` are the parent backlogrun budget commands \u2014 a child must never run them. The live carry record here belongs to the parent that launched this child, not to a competing run: the parent holds the one run budget for the repository, and every lane shares the common git directory it is keyed on, so a live owner a lane child reads is always its parent (joshuafolkken/kit#2267, measured 2026-09-21 in backlogrun #2252 \u2014 a child read run:merge `busy` as a competitor and parked #2258 unimplemented). Do not run this command, and do not treat it as a conflict: no `needs-decision`, no `confirmation` Telegram, no question to a person \u2014 a dispatched child cannot ask, and there is nothing to decide. Continue implementing this Issue; the parent owns the budget and counts the outcome at the return. The procedure is `.claude/skills/workflow-commands/pre-gate-cut.md` and `backlogrun-progress.md` \u2192 "The hand-off". This rule fires on every occurrence, not once per run.';
|
|
1344
|
-
var
|
|
1145
|
+
var ROW7 = {
|
|
1345
1146
|
id: "lane-carry-conflict",
|
|
1346
1147
|
is_trigger: bash_triggers.on_bash_command(is_carry_conflict),
|
|
1347
1148
|
reason: LANE_CARRY_CONFLICT_REASON,
|
|
@@ -1349,7 +1150,7 @@ var ROW8 = {
|
|
|
1349
1150
|
};
|
|
1350
1151
|
var lane_carry_conflict = {
|
|
1351
1152
|
LANE_CARRY_CONFLICT_REASON,
|
|
1352
|
-
ROW:
|
|
1153
|
+
ROW: ROW7,
|
|
1353
1154
|
invokes_budget_command,
|
|
1354
1155
|
is_carry_conflict
|
|
1355
1156
|
};
|
|
@@ -1402,13 +1203,13 @@ function is_interactive_ask(call, is_lane_child = lane_child_marker.is_child_of(
|
|
|
1402
1203
|
return is_lane_child;
|
|
1403
1204
|
}
|
|
1404
1205
|
var LANE_INTERACTIVE_ASK_REASON = '\u26D4 lane child interactive ask: this session is a dispatched lane child and cannot ask a person, so this call is refused. Left to run it would end the turn with nothing on the Issue \u2014 the parent then finds an OPEN Issue with `in-progress` and no question (joshuafolkken/kit#2201; joshuafolkken/kit#2034 placed the same rule one tool-call too late). Do not ask: park the decision instead. 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 `pnpm josh issue:comment <N> --body-file <path>` \u2014 then send the `confirmation` Telegram and stop. 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". This rule fires on every occurrence, not once per run.';
|
|
1405
|
-
var
|
|
1206
|
+
var ROW8 = {
|
|
1406
1207
|
id: "lane-interactive-ask",
|
|
1407
1208
|
is_trigger: is_interactive_ask,
|
|
1408
1209
|
reason: LANE_INTERACTIVE_ASK_REASON,
|
|
1409
1210
|
decide: () => true
|
|
1410
1211
|
};
|
|
1411
|
-
var lane_interactive_ask = { LANE_INTERACTIVE_ASK_REASON, ROW:
|
|
1212
|
+
var lane_interactive_ask = { LANE_INTERACTIVE_ASK_REASON, ROW: ROW8, is_interactive_ask };
|
|
1412
1213
|
|
|
1413
1214
|
// scripts/rules/lane-switch-main.ts
|
|
1414
1215
|
var SWITCH_SUBCOMMAND = "switch";
|
|
@@ -1439,7 +1240,7 @@ function is_switch_away_from_lane(command, is_lane_child = lane_child_marker.is_
|
|
|
1439
1240
|
return switch_targets(command).some((target) => target !== own_branch);
|
|
1440
1241
|
}
|
|
1441
1242
|
var LANE_SWITCH_MAIN_REASON = "\u26D4 lane child on `git switch main`: this session is a dispatched lane child, and switching to the default branch here always fails \u2014 a lane is a linked work tree, the primary checkout holds the default branch, and git allows one branch in one work tree at a time (`fatal: main is already used by worktree at <the primary checkout>`, measured 11 times in the 2026-09-21 backlogrun). Do not switch: the lane is already on its own issue branch, branched from a fresh default before it opened, and the latest default is brought in during the gate by `pnpm josh main:merge`. So skip this step and keep implementing on the current branch \u2014 no `git switch`, no `git pull`. `pnpm josh main:sync` (`josh ms`) is refused in a lane for the same reason, and a lane is finished with `pnpm josh lane:close <issue-number>`. The parent runs `git switch main && git pull` in the primary checkout before each lane opens, never the child \u2014 `backlogrun-lanes.md` and `backlogrun-child.md` are the single sources. This rule fires on every occurrence, not once per run.";
|
|
1442
|
-
var
|
|
1243
|
+
var ROW9 = {
|
|
1443
1244
|
id: "lane-switch-main",
|
|
1444
1245
|
is_trigger: bash_triggers.on_bash_command(is_switch_away_from_lane),
|
|
1445
1246
|
reason: LANE_SWITCH_MAIN_REASON,
|
|
@@ -1447,7 +1248,7 @@ var ROW10 = {
|
|
|
1447
1248
|
};
|
|
1448
1249
|
var lane_switch_main = {
|
|
1449
1250
|
LANE_SWITCH_MAIN_REASON,
|
|
1450
|
-
ROW:
|
|
1251
|
+
ROW: ROW9,
|
|
1451
1252
|
is_switch_away_from_lane,
|
|
1452
1253
|
switch_target,
|
|
1453
1254
|
switch_targets
|
|
@@ -1750,20 +1551,13 @@ var FIRING = /* @__PURE__ */ new Map([
|
|
|
1750
1551
|
describes: "a package-add command (`pnpm add \u2026`, `npm install <pkg>`)",
|
|
1751
1552
|
governs: is_package_add
|
|
1752
1553
|
}
|
|
1753
|
-
],
|
|
1754
|
-
[
|
|
1755
|
-
"issue:lint",
|
|
1756
|
-
{
|
|
1757
|
-
describes: "an Issue-filing call (`gh api \u2026/issues`, `gh issue create`)",
|
|
1758
|
-
governs: bash_triggers.is_issue_filing
|
|
1759
|
-
}
|
|
1760
1554
|
]
|
|
1761
1555
|
]);
|
|
1762
1556
|
var PHASE = "the act it governs is a phase of a run, not a single shell call a trigger can anchor on";
|
|
1763
1557
|
var READ = "it answers a status question and governs no action of its own to gate before";
|
|
1764
1558
|
var SELF = "the command is itself the governed act; there is no earlier call to gate before it";
|
|
1765
1559
|
var LATEST_SCOPE_REASON = "the run it governs (`pnpm josh latest`) is already gated at its point of use by the latest-gate; a firing here would double-gate it";
|
|
1766
|
-
var
|
|
1560
|
+
var FILING_STEP_REASON = "`pnpm josh issue:file` runs it as one of its filing steps, and a direct filing is refused by the `direct-filing` delivered rule; there is no separate call left to gate";
|
|
1767
1561
|
var RELEASE_SCOPE_REASON = "it reads the release owed *after* `pnpm josh followup` has merged (`git_followup_pending`), so it trails the merge rather than gating it; a firing on `followup` would gate a check that must run once the merge is done, reading a pre-merge state that has not yet arrived";
|
|
1768
1562
|
var NOT_NAMED = /* @__PURE__ */ new Map([
|
|
1769
1563
|
["release:scope", RELEASE_SCOPE_REASON],
|
|
@@ -1775,8 +1569,9 @@ var NOT_NAMED = /* @__PURE__ */ new Map([
|
|
|
1775
1569
|
["latest:scope", LATEST_SCOPE_REASON],
|
|
1776
1570
|
["run:hold", SELF],
|
|
1777
1571
|
["cost:cut", READ],
|
|
1778
|
-
["epic:bundle",
|
|
1779
|
-
["issue:scout",
|
|
1572
|
+
["epic:bundle", FILING_STEP_REASON],
|
|
1573
|
+
["issue:scout", FILING_STEP_REASON],
|
|
1574
|
+
["issue:lint", FILING_STEP_REASON],
|
|
1780
1575
|
[
|
|
1781
1576
|
"issue:fold",
|
|
1782
1577
|
"the second filing it governs is already gated by the `issue-fold` delivered rule"
|
|
@@ -1832,7 +1627,7 @@ function command_line(oracle) {
|
|
|
1832
1627
|
return oracle.args ? `pnpm josh ${command} ${oracle.args}` : `pnpm josh ${command}`;
|
|
1833
1628
|
}
|
|
1834
1629
|
function reason_for(oracle, firing_point) {
|
|
1835
|
-
return `\u26D4 decision oracle not consulted: this is ${firing_point.describes}, but the run has not run \`${command_line(oracle)}\` first \u2014 the oracle that decides ${oracle.decision.toLowerCase()}. It answers ${oracle.vocabulary.join(" | ")}; single source ${oracle.single_source}. Reissue after running it and reading its verdict.
|
|
1630
|
+
return `\u26D4 decision oracle not consulted: this is ${firing_point.describes}, but the run has not run \`${command_line(oracle)}\` first \u2014 the oracle that decides ${oracle.decision.toLowerCase()}. It answers ${oracle.vocabulary.join(" | ")}; single source ${oracle.single_source}. Reissue after running it and reading its verdict. Every such call is refused until that command is on the transcript.`;
|
|
1836
1631
|
}
|
|
1837
1632
|
function row_for(oracle, firing_point) {
|
|
1838
1633
|
const names = command_names(oracle);
|
|
@@ -1915,7 +1710,7 @@ function is_poll_loop(command) {
|
|
|
1915
1710
|
return LOOP_KEYWORD2.test(plain) && SLEEP_COMMAND.test(plain);
|
|
1916
1711
|
}
|
|
1917
1712
|
var POLL_LOOP_REASON = '\u26D4 output poll loop: this hand-writes a `while`/`until` wait loop over a command\'s output, a marker file or a process \u2014 with a `sleep` or, worse, without one \u2014 and the loop does not know when the command finished. A regex that never matches waits to the harness block limit; one that matches a mid-run line exits too early \u2014 either way the command exit is not what ends the wait, and each pass spends a full-context round trip (joshuafolkken/kit#2371 measured about 45 minutes, 13% of six lanes, lost to exactly this). Do not poll the output file. A command known to take minutes \u2014 `pnpm josh gate`, `pnpm josh followup`, `pnpm josh format` \u2014 is started detached from the first with `run_in_background` set, and the harness re-invokes you when it exits: its completion notification is what ends the wait, on the task exit rather than a regex or the block limit. So issue the long command in the background and end the turn \u2014 do not arm a loop; the wake-up brings you back. To glance at interim output, `Read` the output file once, never in a sleep loop. If you are waiting on CI, `pnpm josh followup` waits on it for you. 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 occurrence, not once per run.**';
|
|
1918
|
-
var
|
|
1713
|
+
var ROW10 = {
|
|
1919
1714
|
id: "poll-loop",
|
|
1920
1715
|
is_trigger: bash_triggers.on_bash_command(is_poll_loop),
|
|
1921
1716
|
reason: POLL_LOOP_REASON,
|
|
@@ -1923,7 +1718,7 @@ var ROW11 = {
|
|
|
1923
1718
|
};
|
|
1924
1719
|
var poll_loop = {
|
|
1925
1720
|
POLL_LOOP_REASON,
|
|
1926
|
-
ROW:
|
|
1721
|
+
ROW: ROW10,
|
|
1927
1722
|
is_poll_loop
|
|
1928
1723
|
};
|
|
1929
1724
|
|
|
@@ -2043,16 +1838,16 @@ function posts_at_path_literally(command) {
|
|
|
2043
1838
|
return RAW_FIELD_BODY_AT.test(command);
|
|
2044
1839
|
}
|
|
2045
1840
|
var RAW_FIELD_BODY_REASON = "\u26D4 literal @path body: this passes `body=@<path>` to `gh api` with a raw field flag (`-f` / `--raw-field`), which sends the value verbatim \u2014 the string `@<path>` is posted as the comment, not the file it points at. `-F` / `--field` is the flag that reads the file, and the two spellings differ by one character while `gh` exits 0 and returns a comment URL either way, so the break is invisible until a person reads the issue (joshuafolkken/kit#2304 lost two park comments this way). Post an issue or PR comment with `pnpm josh issue:comment <N> --body-file <path>` \u2014 the one spelling, which cannot take the wrong flag. The rule and the safe forms are in `prompts/collaboration-workflow/shell-body.md`. This rule fires on every occurrence, not once per run.";
|
|
2046
|
-
var
|
|
1841
|
+
var ROW11 = {
|
|
2047
1842
|
id: "raw-field-body",
|
|
2048
1843
|
is_trigger: bash_triggers.on_bash_command(posts_at_path_literally),
|
|
2049
1844
|
reason: RAW_FIELD_BODY_REASON,
|
|
2050
1845
|
decide: () => true
|
|
2051
1846
|
};
|
|
2052
|
-
var raw_field_body = { RAW_FIELD_BODY_REASON, ROW:
|
|
1847
|
+
var raw_field_body = { RAW_FIELD_BODY_REASON, ROW: ROW11, posts_at_path_literally };
|
|
2053
1848
|
|
|
2054
1849
|
// scripts/rules/rule-body-guard.ts
|
|
2055
|
-
import { existsSync as existsSync2, readFileSync as
|
|
1850
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
|
|
2056
1851
|
var EDIT_TOOL = "Edit";
|
|
2057
1852
|
var WRITE_TOOL = "Write";
|
|
2058
1853
|
var CLAUDE_DOCUMENT = /(?:^|\/)CLAUDE\.md$/u;
|
|
@@ -2075,7 +1870,7 @@ function string_field(input, key) {
|
|
|
2075
1870
|
return typeof value === "string" ? value : "";
|
|
2076
1871
|
}
|
|
2077
1872
|
function read_existing(file_path) {
|
|
2078
|
-
return existsSync2(file_path) ?
|
|
1873
|
+
return existsSync2(file_path) ? readFileSync3(file_path, "utf8") : "";
|
|
2079
1874
|
}
|
|
2080
1875
|
function texts_of(name, input, read2) {
|
|
2081
1876
|
if (name === WRITE_TOOL) {
|
|
@@ -2102,7 +1897,7 @@ function writes_rule_prose(call, read2 = read_existing) {
|
|
|
2102
1897
|
const { old_text, new_text } = texts_of(call.name, input, read2);
|
|
2103
1898
|
return adds_rule_prose(old_text, new_text);
|
|
2104
1899
|
}
|
|
2105
|
-
var RULE_BODY_REASON = "\u26D4 rule written into prose: this edit adds rule text to CLAUDE.md, a `prompts/**` doc or a skill body. Before it lands, run the residency questions the addition skips. A rule whose answer is computable from mechanically readable inputs should be a decision oracle, not prose \u2014 question 0; `pnpm josh oracle:list` lists the existing oracles a new one joins (single source `scripts/rules/decision-oracle.ts`). A rule that decides *when* or *in what order* to act belongs in the run driver, not prose \u2014 the ordering question; `pnpm josh run:step` is the state machine a sequencing rule moves into. Only a rule that is neither \u2014 not computable, not ordering \u2014 stays prose, and then as a trigger-plus-pointer line rather than a second copy of its procedure. The criterion is `prompts/collaboration-workflow/residency.md` (question 0 and the ordering question); the delivery enumeration is `prompts/collaboration-workflow/rule-delivery.md`.
|
|
1900
|
+
var RULE_BODY_REASON = "\u26D4 rule written into prose: this edit adds rule text to CLAUDE.md, a `prompts/**` doc or a skill body. Before it lands, run the residency questions the addition skips. A rule whose answer is computable from mechanically readable inputs should be a decision oracle, not prose \u2014 question 0; `pnpm josh oracle:list` lists the existing oracles a new one joins (single source `scripts/rules/decision-oracle.ts`). A rule that decides *when* or *in what order* to act belongs in the run driver, not prose \u2014 the ordering question; `pnpm josh run:step` is the state machine a sequencing rule moves into. Only a rule that is neither \u2014 not computable, not ordering \u2014 stays prose, and then as a trigger-plus-pointer line rather than a second copy of its procedure. The criterion is `prompts/collaboration-workflow/residency.md` (question 0 and the ordering question); the delivery enumeration is `prompts/collaboration-workflow/rule-delivery.md`. Every such edit is refused until both commands are on the transcript \u2014 run them, confirm the rule can be neither an oracle nor an ordering step, then reissue this edit.";
|
|
2106
1901
|
var PLACEMENT_ORACLES = [
|
|
2107
1902
|
/* @__PURE__ */ new Set(["oracle:list"]),
|
|
2108
1903
|
/* @__PURE__ */ new Set(["run:step", "run:next"])
|
|
@@ -2121,7 +1916,7 @@ function runs_placement_oracle(command) {
|
|
|
2121
1916
|
(names) => shell_segments.segments_of(command).some((segment) => shell_segments.is_josh_command(segment, names))
|
|
2122
1917
|
);
|
|
2123
1918
|
}
|
|
2124
|
-
var
|
|
1919
|
+
var ROW12 = {
|
|
2125
1920
|
id: "rule-body",
|
|
2126
1921
|
is_trigger: writes_rule_prose,
|
|
2127
1922
|
reason: RULE_BODY_REASON,
|
|
@@ -2130,7 +1925,7 @@ var ROW13 = {
|
|
|
2130
1925
|
};
|
|
2131
1926
|
var rule_body_guard = {
|
|
2132
1927
|
RULE_BODY_REASON,
|
|
2133
|
-
ROW:
|
|
1928
|
+
ROW: ROW12,
|
|
2134
1929
|
adds_rule_prose,
|
|
2135
1930
|
answered_placement,
|
|
2136
1931
|
is_rule_document,
|
|
@@ -2214,8 +2009,8 @@ var shell_body_trigger = {
|
|
|
2214
2009
|
import { execFileSync } from "node:child_process";
|
|
2215
2010
|
|
|
2216
2011
|
// scripts/gate/project-checks.ts
|
|
2217
|
-
import { existsSync as existsSync3, globSync as globSync2, readFileSync as
|
|
2218
|
-
import
|
|
2012
|
+
import { existsSync as existsSync3, globSync as globSync2, readFileSync as readFileSync4 } from "node:fs";
|
|
2013
|
+
import path4 from "node:path";
|
|
2219
2014
|
|
|
2220
2015
|
// scripts/test/skip-marker.ts
|
|
2221
2016
|
var SKIP_MARKER = "\u2014 skipping";
|
|
@@ -2259,8 +2054,8 @@ var DEPENDENCY_FIELDS = ["dependencies", "devDependencies"];
|
|
|
2259
2054
|
var object_schema = z5.record(z5.string(), z5.unknown());
|
|
2260
2055
|
function project_root(directory) {
|
|
2261
2056
|
let current = directory;
|
|
2262
|
-
while (!existsSync3(
|
|
2263
|
-
const parent =
|
|
2057
|
+
while (!existsSync3(path4.join(current, PACKAGE_JSON))) {
|
|
2058
|
+
const parent = path4.dirname(current);
|
|
2264
2059
|
if (parent === current) return directory;
|
|
2265
2060
|
current = parent;
|
|
2266
2061
|
}
|
|
@@ -2273,7 +2068,7 @@ function has_files(directory, pattern) {
|
|
|
2273
2068
|
return globSync2(pattern, { cwd: project_root(directory), exclude: is_ignored }).length > 0;
|
|
2274
2069
|
}
|
|
2275
2070
|
function has_config(directory, names) {
|
|
2276
|
-
return names.some((name) => existsSync3(
|
|
2071
|
+
return names.some((name) => existsSync3(path4.join(project_root(directory), name)));
|
|
2277
2072
|
}
|
|
2278
2073
|
function has_bin(directory, bin_name) {
|
|
2279
2074
|
return find_local_bin_upwards(directory, bin_name) !== void 0;
|
|
@@ -2289,8 +2084,8 @@ function is_static_manifest(manifest) {
|
|
|
2289
2084
|
}
|
|
2290
2085
|
function read_manifest(directory) {
|
|
2291
2086
|
try {
|
|
2292
|
-
const manifest_path =
|
|
2293
|
-
return JSON.parse(
|
|
2087
|
+
const manifest_path = path4.join(project_root(directory), PACKAGE_JSON);
|
|
2088
|
+
return JSON.parse(readFileSync4(manifest_path, "utf8"));
|
|
2294
2089
|
} catch {
|
|
2295
2090
|
return void 0;
|
|
2296
2091
|
}
|
|
@@ -2355,32 +2150,32 @@ var EXEMPT_PREFIXES = [".idea/", ".vscode/", "prompts/"];
|
|
|
2355
2150
|
var EXEMPT_SUFFIXES = [".md"];
|
|
2356
2151
|
var STATIC_VISUAL_SUFFIXES = [".html", ".css"];
|
|
2357
2152
|
function normalize(paths) {
|
|
2358
|
-
return paths.map((
|
|
2153
|
+
return paths.map((path6) => path6.trim()).filter((path6) => path6 !== "");
|
|
2359
2154
|
}
|
|
2360
|
-
function is_test_file(
|
|
2361
|
-
return TEST_SUFFIXES.some((suffix) =>
|
|
2155
|
+
function is_test_file(path6) {
|
|
2156
|
+
return TEST_SUFFIXES.some((suffix) => path6.endsWith(suffix));
|
|
2362
2157
|
}
|
|
2363
|
-
function is_static_visual(
|
|
2364
|
-
return is_static2 && STATIC_VISUAL_SUFFIXES.some((suffix) =>
|
|
2158
|
+
function is_static_visual(path6, is_static2) {
|
|
2159
|
+
return is_static2 && STATIC_VISUAL_SUFFIXES.some((suffix) => path6.endsWith(suffix));
|
|
2365
2160
|
}
|
|
2366
|
-
function is_exempt(
|
|
2367
|
-
if (EXEMPT_PATHS.includes(
|
|
2368
|
-
return EXEMPT_SUFFIXES.some((suffix) =>
|
|
2161
|
+
function is_exempt(path6, is_static2 = false) {
|
|
2162
|
+
if (EXEMPT_PATHS.includes(path6) || is_static_visual(path6, is_static2)) return true;
|
|
2163
|
+
return EXEMPT_SUFFIXES.some((suffix) => path6.endsWith(suffix)) || EXEMPT_PREFIXES.some((prefix) => path6.startsWith(prefix));
|
|
2369
2164
|
}
|
|
2370
|
-
function is_runtime(
|
|
2371
|
-
return !is_test_file(
|
|
2165
|
+
function is_runtime(path6, is_static2 = false) {
|
|
2166
|
+
return !is_test_file(path6) && !is_exempt(path6, is_static2);
|
|
2372
2167
|
}
|
|
2373
2168
|
function verdict_for(paths, is_static2 = false) {
|
|
2374
2169
|
const changed = normalize(paths);
|
|
2375
|
-
if (changed.some((
|
|
2376
|
-
if (changed.some((
|
|
2170
|
+
if (changed.some((path6) => is_test_file(path6))) return "satisfied";
|
|
2171
|
+
if (changed.some((path6) => is_runtime(path6, is_static2))) return "required";
|
|
2377
2172
|
return "exempt";
|
|
2378
2173
|
}
|
|
2379
2174
|
function runtime_files(paths, is_static2 = false) {
|
|
2380
|
-
return normalize(paths).filter((
|
|
2175
|
+
return normalize(paths).filter((path6) => is_runtime(path6, is_static2));
|
|
2381
2176
|
}
|
|
2382
2177
|
function exempt_files(paths, is_static2 = false) {
|
|
2383
|
-
return normalize(paths).filter((
|
|
2178
|
+
return normalize(paths).filter((path6) => is_exempt(path6, is_static2));
|
|
2384
2179
|
}
|
|
2385
2180
|
var test_declared_logic = {
|
|
2386
2181
|
EXEMPT_PATHS,
|
|
@@ -2445,8 +2240,8 @@ function is_untested_commit(call) {
|
|
|
2445
2240
|
return test_declared_changed.current_verdict() === "required";
|
|
2446
2241
|
}
|
|
2447
2242
|
var REASON = '\u26D4 test declared: this commits a runtime code change with no test beside it \u2014 `pnpm josh test:declared` reads the working tree as `required` (a changed file that is neither a test \u2014 `*.test.ts` / `*.e2e.ts` \u2014 nor a mechanically-exempt non-runtime path). `CLAUDE.md` \u2192 Code Change Rules requires a test for ALL code changes: a bug fix gets a regression test, a refactor gets tests pinning behavior first, logic gets a unit test, an observable UI/timing change gets an E2E. Add the declared test and reissue this commit. **If the change genuinely touches no executable runtime path** it is the pre-approved non-runtime exception \u2014 declare it in your Step 0 work summary with the reason it is exempt (non-executable config, a cosmetic asset swap) and reissue; the exception is a presentation, not a stop. Run `pnpm josh test:declared` to see the files it names. The procedure is `prompts/collaboration-workflow/rule-delivery.md` \u2192 "\u914D\u9001\u3055\u308C\u3066\u3044\u308B\u898F\u5247". This fires once per run, so reissuing will not repeat on the call in hand.';
|
|
2448
|
-
var
|
|
2449
|
-
var test_declared_commit = { REASON, ROW:
|
|
2243
|
+
var ROW13 = { id: "test-declared", is_trigger: is_untested_commit, reason: REASON };
|
|
2244
|
+
var test_declared_commit = { REASON, ROW: ROW13, is_untested_commit };
|
|
2450
2245
|
|
|
2451
2246
|
// scripts/rules/third-party-write.ts
|
|
2452
2247
|
function is_third_party_write_segment(segment, session_owner) {
|
|
@@ -2462,14 +2257,14 @@ function writes_third_party(command, resolve_owner = repo_party.current_owner) {
|
|
|
2462
2257
|
return targets.some((segment) => is_third_party_write_segment(segment, session_owner));
|
|
2463
2258
|
}
|
|
2464
2259
|
var THIRD_PARTY_WRITE_REASON = '\u26D4 third-party write: this `gh api` call writes to a repository whose owner is not this session \u2014 a tracker we do not own is Tier C, and every write there (Issue, comment, PR, Discussion, review) needs explicit current-turn user instruction; typing the target, or holding a correct diagnosis, is not that instruction (`CLAUDE.md` \u2192 "Third-party repositories are Tier C", `prompts/collaboration-workflow/upstream-interrupt.md`). Confirm the party with `pnpm josh repo:party <owner/repo>` \u2014 owner equality, computed not judged. A read (`-X GET`, or no field flag) passes untouched; only a write (`-f`/`-F`/`--field`/`--raw-field`/`--input`, or `-X POST|PATCH|PUT|DELETE`) is refused. Record the finding on our own Issue and draft the artifact for a person to post \u2014 do not publish it yourself. **This rule fires on every occurrence, not once per run.**';
|
|
2465
|
-
var
|
|
2260
|
+
var ROW14 = {
|
|
2466
2261
|
id: "third-party-write",
|
|
2467
2262
|
is_trigger: bash_triggers.on_bash_command((command) => writes_third_party(command)),
|
|
2468
2263
|
reason: THIRD_PARTY_WRITE_REASON,
|
|
2469
2264
|
decide: () => true
|
|
2470
2265
|
};
|
|
2471
2266
|
var third_party_write = {
|
|
2472
|
-
ROW:
|
|
2267
|
+
ROW: ROW14,
|
|
2473
2268
|
THIRD_PARTY_WRITE_REASON,
|
|
2474
2269
|
is_third_party_write_segment,
|
|
2475
2270
|
writes_third_party
|
|
@@ -2523,14 +2318,14 @@ function is_unauthorized_worktree_change(command) {
|
|
|
2523
2318
|
return shell_segments.segments_of(command).some((segment) => is_unauthorized_segment(segment));
|
|
2524
2319
|
}
|
|
2525
2320
|
var WORKTREE_MUTATION_REASON = '\u26D4 unauthorized working-tree change: `git checkout -- <path>`, `git restore <path>` and an unauthorized `git stash` mutate the working tree or the repository-wide stash every work tree shares, and none is on the `.claude/settings.json` deny list (`operating-rules.md`). Do not run them on your own judgement. A commit goes through `pnpm josh git`; a stash pop goes through `pnpm josh stash:pop "<message>"`, never a positional `git stash pop` a shared stack lets another lane divert. A stash push is authorized only inside a documented flow and only with a findable message \u2014 `git stash push -u -m "<message>"` \u2014 which this row allows; a bare `git stash`, a message-less push, or `pop` / `apply` / `drop` is refused. **This rule fires on every occurrence, not once per run.**';
|
|
2526
|
-
var
|
|
2321
|
+
var ROW15 = {
|
|
2527
2322
|
id: "worktree-mutation",
|
|
2528
2323
|
is_trigger: bash_triggers.on_bash_command(is_unauthorized_worktree_change),
|
|
2529
2324
|
reason: WORKTREE_MUTATION_REASON,
|
|
2530
2325
|
decide: () => true
|
|
2531
2326
|
};
|
|
2532
2327
|
var worktree_guard = {
|
|
2533
|
-
ROW:
|
|
2328
|
+
ROW: ROW15,
|
|
2534
2329
|
WORKTREE_MUTATION_REASON,
|
|
2535
2330
|
is_unauthorized_stash,
|
|
2536
2331
|
is_unauthorized_worktree_change,
|
|
@@ -2567,7 +2362,7 @@ function is_body_only_issue_read(command) {
|
|
|
2567
2362
|
if (segments.some((segment) => fetches_issue_comments(segment))) return false;
|
|
2568
2363
|
return segments.some((segment) => is_body_read_segment(segment));
|
|
2569
2364
|
}
|
|
2570
|
-
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\".
|
|
2365
|
+
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\". Every body-only read is refused until the comments are read.";
|
|
2571
2366
|
var { SHELL_BODY_REASON: SHELL_BODY_REASON2, carries_a_body: carries_a_body2, is_shell_evaluated_body: is_shell_evaluated_body2, keeps_body_safe: keeps_body_safe2 } = shell_body_trigger;
|
|
2572
2367
|
var ISSUE_LISTING = /^gh\s+(?:-{1,2}[\w-]+(?:[= ]\S+)?\s+)*issue\s+list\b/u;
|
|
2573
2368
|
var ISSUES_QUERY = /repos\/[^\s'"]*\/issues\?[^\s'"]*state=open/u;
|
|
@@ -2591,26 +2386,24 @@ var DELIVERED_RULES = [
|
|
|
2591
2386
|
// before any of those speak; on a first-party filing it is silent (the owners match), so `wip-cap`
|
|
2592
2387
|
// still speaks first, exactly as before. It fires on every occurrence (`git-force.ts`).
|
|
2593
2388
|
third_party_write.ROW,
|
|
2389
|
+
// **A hand-built filing is refused on every occurrence** (joshuafolkken/kit#2808) and pointed at
|
|
2390
|
+
// `josh issue:file`, which runs the scout, the lint, the labels, the `## Origin` check and
|
|
2391
|
+
// `epic:bundle` itself. It claims the direct call only, so the rows below — which trigger on the
|
|
2392
|
+
// `issue:file` call — never overlap it.
|
|
2393
|
+
direct_filing.ROW,
|
|
2594
2394
|
{
|
|
2595
2395
|
id: "wip-cap",
|
|
2596
2396
|
is_trigger: on_bash_command(is_issue_filing),
|
|
2597
2397
|
reason: WIP_CAP_REASON,
|
|
2598
2398
|
keeps: on_bash_command(counts_open_issues)
|
|
2599
2399
|
},
|
|
2600
|
-
// **
|
|
2601
|
-
// backlog count still speaks first: `
|
|
2602
|
-
// run,
|
|
2603
|
-
//
|
|
2604
|
-
//
|
|
2605
|
-
//
|
|
2606
|
-
issue_scout.ROW,
|
|
2400
|
+
// **Two more rows share the filing trigger** (joshuafolkken/kit#2119, joshuafolkken/kit#2213), listed
|
|
2401
|
+
// after `wip-cap` so the backlog count still speaks first: `filing-cap` refuses every filing past
|
|
2402
|
+
// the per-run ceiling, and `issue-fold` refuses a *second* filing the run has not folded. Each is a
|
|
2403
|
+
// real instance of the admissible overlap the comment on `delivery` describes — the losing rule's
|
|
2404
|
+
// delivery is still correct one reissue later — so an unfolded, over-cap second filing is delivered
|
|
2405
|
+
// `wip-cap`, then `filing-cap`, then `issue-fold` across its reissues.
|
|
2607
2406
|
filing_cap.ROW,
|
|
2608
|
-
// **`issue-fold` shares the same filing trigger** (joshuafolkken/kit#2213), listed after
|
|
2609
|
-
// `filing-cap`: it refuses a *second* filing the run has not folded, once per run, stood down off
|
|
2610
|
-
// the tail when the run has already folded or has filed nothing earlier. It is another instance of
|
|
2611
|
-
// the admissible overlap — the losing rule's delivery is still correct one reissue later — so a
|
|
2612
|
-
// scout-less, unfolded, over-cap second filing is delivered `wip-cap`, then `issue-scout`, then
|
|
2613
|
-
// `filing-cap`, then `issue-fold` across its reissues.
|
|
2614
2407
|
issue_fold.ROW,
|
|
2615
2408
|
{
|
|
2616
2409
|
id: "issue-comments",
|
|
@@ -2837,13 +2630,9 @@ var DELIVERED_RULES = [
|
|
|
2837
2630
|
rule_body_guard.ROW,
|
|
2838
2631
|
// **The generic oracle-consulted rows, one per oracle that declared a firing point**
|
|
2839
2632
|
// (joshuafolkken/kit#2324). Each refuses the action the oracle governs when the run has not run that
|
|
2840
|
-
// oracle's command first, stood down once it has
|
|
2841
|
-
//
|
|
2842
|
-
|
|
2843
|
-
// `issue-fold` have each had their turn. The non-overlapping firing point (`pkg:scout` on a package
|
|
2844
|
-
// add) claims a command no row above matches.
|
|
2845
|
-
...oracle_consulted.ROWS,
|
|
2846
|
-
issue_bug_label_rule.ROW
|
|
2633
|
+
// oracle's command first, stood down once it has. Listed last; the one firing point left
|
|
2634
|
+
// (`pkg:scout` on a package add) claims a command no row above matches.
|
|
2635
|
+
...oracle_consulted.ROWS
|
|
2847
2636
|
];
|
|
2848
2637
|
var ALONE_IN_TURN = 1;
|
|
2849
2638
|
function batches_the_turn(call, turn) {
|
|
@@ -2877,12 +2666,12 @@ function is_first_delivery(tail, call, delivered_at_ms, run2) {
|
|
|
2877
2666
|
return delivered_at_ms === hook_decision.NEVER_MS && !will_batch_guard_refuse(tail, call, run2);
|
|
2878
2667
|
}
|
|
2879
2668
|
function delivery_decision(rule) {
|
|
2880
|
-
const { decide:
|
|
2881
|
-
if (
|
|
2669
|
+
const { decide: decide5, already_satisfied } = rule;
|
|
2670
|
+
if (decide5 === void 0 && already_satisfied === void 0) return is_first_delivery;
|
|
2882
2671
|
return function should_block(tail, call, delivered_at_ms, run2) {
|
|
2883
2672
|
if (already_satisfied?.(tail, call) === true) return false;
|
|
2884
|
-
if (
|
|
2885
|
-
return
|
|
2673
|
+
if (decide5 === void 0) return true;
|
|
2674
|
+
return decide5(call, run2, !will_batch_guard_refuse(tail, call, run2), delivered_at_ms);
|
|
2886
2675
|
};
|
|
2887
2676
|
}
|
|
2888
2677
|
function guard_of(rule) {
|
|
@@ -2912,11 +2701,11 @@ var delivered_rules = {
|
|
|
2912
2701
|
DELIVERED_RULES,
|
|
2913
2702
|
EARLY_HEARTBEAT_REASON: early_heartbeat.EARLY_HEARTBEAT_REASON,
|
|
2914
2703
|
FILE_BODY_REASON: file_body.FILE_BODY_REASON,
|
|
2704
|
+
DIRECT_FILING_REASON: direct_filing.DIRECT_FILING_REASON,
|
|
2915
2705
|
FILING_CAP_REASON: filing_cap.FILING_CAP_REASON,
|
|
2916
2706
|
GIT_FORCE_REASON: git_force.GIT_FORCE_REASON,
|
|
2917
2707
|
ISSUE_COMMENTS_REASON,
|
|
2918
2708
|
ISSUE_FOLD_REASON: issue_fold.ISSUE_FOLD_REASON,
|
|
2919
|
-
ISSUE_SCOUT_REASON: issue_scout.ISSUE_SCOUT_REASON,
|
|
2920
2709
|
LANE_INTERACTIVE_ASK_REASON: lane_interactive_ask.LANE_INTERACTIVE_ASK_REASON,
|
|
2921
2710
|
LANE_PARK_REASON: lane_park.LANE_PARK_REASON,
|
|
2922
2711
|
MEASURED_RULES,
|
|
@@ -3267,9 +3056,9 @@ async function issue_get_labels_and_body(issue_number) {
|
|
|
3267
3056
|
return await issue_view_json(issue_number, "number,state,labels,body");
|
|
3268
3057
|
}
|
|
3269
3058
|
async function issue_list_comments(issue_number) {
|
|
3270
|
-
const
|
|
3059
|
+
const path6 = `${git_gh_api_path.issue_comments_api_path(issue_number)}${git_gh_api_path.FULL_PAGE_QUERY}`;
|
|
3271
3060
|
try {
|
|
3272
|
-
return await git_gh_exec.exec_gh_api({ path:
|
|
3061
|
+
return await git_gh_exec.exec_gh_api({ path: path6, should_paginate: true });
|
|
3273
3062
|
} catch {
|
|
3274
3063
|
return void 0;
|
|
3275
3064
|
}
|
|
@@ -3344,8 +3133,8 @@ function is_selected(rest, request) {
|
|
|
3344
3133
|
return !git_gh_issue_rest.is_pull_request(rest) && matches_body(rest, request.body_term);
|
|
3345
3134
|
}
|
|
3346
3135
|
async function fetch_page(request, page) {
|
|
3347
|
-
const
|
|
3348
|
-
const rows = git_gh_issue_rest.parse_rest_issues(await git_gh_exec.exec_gh_api({ path:
|
|
3136
|
+
const path6 = listing_path(request, page);
|
|
3137
|
+
const rows = git_gh_issue_rest.parse_rest_issues(await git_gh_exec.exec_gh_api({ path: path6 }));
|
|
3349
3138
|
return {
|
|
3350
3139
|
selected: rows.filter((row) => is_selected(row, request)),
|
|
3351
3140
|
is_full: rows.length >= PER_PAGE
|
|
@@ -3586,20 +3375,11 @@ var git_gh_issue = {
|
|
|
3586
3375
|
};
|
|
3587
3376
|
|
|
3588
3377
|
// scripts/ci/pr-classification.ts
|
|
3589
|
-
import { readFileSync as
|
|
3378
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
3590
3379
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3591
3380
|
import { execaSync } from "execa";
|
|
3592
3381
|
import { z as z8 } from "zod";
|
|
3593
|
-
var
|
|
3594
|
-
var BREAKING = "breaking-change";
|
|
3595
|
-
var CLASSIFICATION_LABELS2 = [
|
|
3596
|
-
BREAKING,
|
|
3597
|
-
"enhancement",
|
|
3598
|
-
BUGFIX,
|
|
3599
|
-
"other-change",
|
|
3600
|
-
"ignore-for-release"
|
|
3601
|
-
];
|
|
3602
|
-
var CLASSIFICATION_SET = new Set(CLASSIFICATION_LABELS2);
|
|
3382
|
+
var CLASSIFICATION_SET = new Set(RELEASE_CLASSIFICATION_NAMES);
|
|
3603
3383
|
var ISSUE_BUG_LABEL = "bug";
|
|
3604
3384
|
var DECLARATION = /^- リリース分類: (.+)$/gmu;
|
|
3605
3385
|
var LEGACY_LABELS = /* @__PURE__ */ new Set(["semver-major", "semver-minor"]);
|
|
@@ -3618,14 +3398,14 @@ function classification_error(labels, author) {
|
|
|
3618
3398
|
return `Choose exactly one release classification; found: ${selected.join(", ")}`;
|
|
3619
3399
|
}
|
|
3620
3400
|
if (selected.length === 1 || author.endsWith("[bot]")) return void 0;
|
|
3621
|
-
return `Choose one release classification: ${
|
|
3401
|
+
return `Choose one release classification: ${RELEASE_CLASSIFICATION_NAMES.join(", ")}`;
|
|
3622
3402
|
}
|
|
3623
3403
|
function is_classification(value) {
|
|
3624
3404
|
return CLASSIFICATION_SET.has(value);
|
|
3625
3405
|
}
|
|
3626
3406
|
function release_candidates(candidates) {
|
|
3627
|
-
if (!candidates.includes(
|
|
3628
|
-
return candidates.filter((candidate) => candidate !==
|
|
3407
|
+
if (!candidates.includes(BREAKING_CHANGE_LABEL)) return candidates;
|
|
3408
|
+
return candidates.filter((candidate) => candidate !== ENHANCEMENT_LABEL);
|
|
3629
3409
|
}
|
|
3630
3410
|
function issue_candidates(issue_json) {
|
|
3631
3411
|
const issue = z8.object({ labels: z8.array(LABEL), body: z8.string().nullable() }).parse(JSON.parse(issue_json));
|
|
@@ -3634,8 +3414,8 @@ function issue_candidates(issue_json) {
|
|
|
3634
3414
|
const candidates = [...release_candidates([...names, ...declared].filter(is_classification))];
|
|
3635
3415
|
const unknown = declared.filter((value) => !is_classification(value));
|
|
3636
3416
|
if (unknown.length > 0) throw new Error(`Unknown release classification: ${unknown.join(", ")}`);
|
|
3637
|
-
if (names.includes(ISSUE_BUG_LABEL) && !candidates.includes(
|
|
3638
|
-
candidates.push(
|
|
3417
|
+
if (names.includes(ISSUE_BUG_LABEL) && !candidates.includes(BREAKING_CHANGE_LABEL)) {
|
|
3418
|
+
candidates.push(BUGFIX_LABEL);
|
|
3639
3419
|
}
|
|
3640
3420
|
return candidates;
|
|
3641
3421
|
}
|
|
@@ -3645,7 +3425,7 @@ function select_issue_classification(issue_json) {
|
|
|
3645
3425
|
const [label] = selected;
|
|
3646
3426
|
if (label === void 0 || selected.length > 1) {
|
|
3647
3427
|
throw new Error(
|
|
3648
|
-
`Choose one release classification before opening the PR: ${
|
|
3428
|
+
`Choose one release classification before opening the PR: ${RELEASE_CLASSIFICATION_NAMES.join(", ")}; found: ${selected.join(", ") || "none"}. Add "- \u30EA\u30EA\u30FC\u30B9\u5206\u985E: <label>" to the Issue body.`
|
|
3649
3429
|
);
|
|
3650
3430
|
}
|
|
3651
3431
|
return label;
|
|
@@ -3662,7 +3442,7 @@ function fetch_current_labels(repository, pull_number) {
|
|
|
3662
3442
|
return result.stdout.split("\n").filter((name) => name !== "");
|
|
3663
3443
|
}
|
|
3664
3444
|
function check_event(event_path, read_labels = fetch_current_labels) {
|
|
3665
|
-
const event = EVENT.parse(JSON.parse(
|
|
3445
|
+
const event = EVENT.parse(JSON.parse(readFileSync5(event_path, "utf8")));
|
|
3666
3446
|
const labels = read_labels(event.repository.full_name, event.pull_request.number);
|
|
3667
3447
|
return classification_error(labels, event.pull_request.user.login);
|
|
3668
3448
|
}
|
|
@@ -3895,9 +3675,9 @@ async function pr_head_reference(pr_number) {
|
|
|
3895
3675
|
async function read_comments(branch_name, to_path, to_json) {
|
|
3896
3676
|
const pr_number = await resolve_pr_number(branch_name);
|
|
3897
3677
|
if (pr_number === void 0) return void 0;
|
|
3898
|
-
const
|
|
3678
|
+
const path6 = `${to_path(String(pr_number))}${COMMENTS_QUERY}`;
|
|
3899
3679
|
try {
|
|
3900
|
-
return to_json(await git_gh_exec.exec_gh_api({ path:
|
|
3680
|
+
return to_json(await git_gh_exec.exec_gh_api({ path: path6, should_paginate: true }));
|
|
3901
3681
|
} catch {
|
|
3902
3682
|
return void 0;
|
|
3903
3683
|
}
|
|
@@ -4045,16 +3825,16 @@ var git_gh_pr_rollup = {
|
|
|
4045
3825
|
|
|
4046
3826
|
// scripts/git/git-gh-pr-snapshot.ts
|
|
4047
3827
|
var NO_HEAD_SHA_MESSAGE = "gh api answered a pull request with no head commit";
|
|
4048
|
-
async function read_object_pages(
|
|
3828
|
+
async function read_object_pages(path6) {
|
|
4049
3829
|
return await git_gh_exec.exec_gh_api({
|
|
4050
|
-
path: `${
|
|
3830
|
+
path: `${path6}${FULL_PAGE_QUERY}`,
|
|
4051
3831
|
should_paginate: true,
|
|
4052
3832
|
should_slurp: true
|
|
4053
3833
|
});
|
|
4054
3834
|
}
|
|
4055
|
-
async function read_array_pages(
|
|
3835
|
+
async function read_array_pages(path6) {
|
|
4056
3836
|
return await git_gh_exec.exec_gh_api({
|
|
4057
|
-
path: `${
|
|
3837
|
+
path: `${path6}${FULL_PAGE_QUERY}`,
|
|
4058
3838
|
should_paginate: true
|
|
4059
3839
|
});
|
|
4060
3840
|
}
|
|
@@ -4071,8 +3851,8 @@ async function read_status_check_rollup(commit_sha) {
|
|
|
4071
3851
|
return git_gh_pr_rollup.to_status_check_rollup({ check_runs_json, status_json });
|
|
4072
3852
|
}
|
|
4073
3853
|
async function pr_get_review_decision(pr_number) {
|
|
4074
|
-
const
|
|
4075
|
-
return git_gh_pr_review.to_review_decision(await read_array_pages(
|
|
3854
|
+
const path6 = git_gh_api_path.pull_reviews_api_path(String(pr_number));
|
|
3855
|
+
return git_gh_pr_review.to_review_decision(await read_array_pages(path6));
|
|
4076
3856
|
}
|
|
4077
3857
|
async function build_checks_fields(pull) {
|
|
4078
3858
|
return {
|
|
@@ -4697,9 +4477,9 @@ var epic_busy = {
|
|
|
4697
4477
|
};
|
|
4698
4478
|
|
|
4699
4479
|
// scripts/run/run-liveness.ts
|
|
4700
|
-
import { readFileSync as
|
|
4480
|
+
import { readFileSync as readFileSync6, realpathSync, statSync } from "node:fs";
|
|
4701
4481
|
import { homedir, tmpdir } from "node:os";
|
|
4702
|
-
import
|
|
4482
|
+
import path5 from "node:path";
|
|
4703
4483
|
import { setTimeout as sleep2 } from "node:timers/promises";
|
|
4704
4484
|
|
|
4705
4485
|
// scripts/agent/agent-event.ts
|
|
@@ -4991,7 +4771,7 @@ function to_advice(verdict, has_work_to_stash) {
|
|
|
4991
4771
|
if (verdict === UNDETERMINED_VERDICT) return UNDETERMINED_ADVICE;
|
|
4992
4772
|
return has_work_to_stash ? STOPPED_DIRTY_ADVICE : STOPPED_CLEAN_ADVICE;
|
|
4993
4773
|
}
|
|
4994
|
-
function
|
|
4774
|
+
function decide3(traces) {
|
|
4995
4775
|
const verdict = to_verdict(traces);
|
|
4996
4776
|
const has_work_to_stash = verdict === STOPPED_VERDICT && traces.is_tree_dirty;
|
|
4997
4777
|
return {
|
|
@@ -5014,12 +4794,12 @@ var ALLOWED_ROOTS = [
|
|
|
5014
4794
|
)
|
|
5015
4795
|
];
|
|
5016
4796
|
function is_within(candidate, root) {
|
|
5017
|
-
const relative =
|
|
5018
|
-
return relative !== "" && !relative.startsWith("..") && !
|
|
4797
|
+
const relative = path5.relative(root, candidate);
|
|
4798
|
+
return relative !== "" && !relative.startsWith("..") && !path5.isAbsolute(relative);
|
|
5019
4799
|
}
|
|
5020
4800
|
function to_safe_path(output_path) {
|
|
5021
|
-
const normalized2 =
|
|
5022
|
-
if (!
|
|
4801
|
+
const normalized2 = path5.normalize(output_path);
|
|
4802
|
+
if (!path5.isAbsolute(normalized2)) return void 0;
|
|
5023
4803
|
return ALLOWED_ROOTS.some((root) => is_within(normalized2, root)) ? normalized2 : void 0;
|
|
5024
4804
|
}
|
|
5025
4805
|
function sample_output(output_path) {
|
|
@@ -5033,7 +4813,7 @@ function read_agent_state(output_path) {
|
|
|
5033
4813
|
const safe_path = to_safe_path(output_path);
|
|
5034
4814
|
if (safe_path === void 0) return void 0;
|
|
5035
4815
|
try {
|
|
5036
|
-
const state = agent_event.read(
|
|
4816
|
+
const state = agent_event.read(readFileSync6(safe_path, "utf8"));
|
|
5037
4817
|
return state.provider === void 0 ? void 0 : state;
|
|
5038
4818
|
} catch {
|
|
5039
4819
|
return void 0;
|
|
@@ -5090,11 +4870,11 @@ async function read_traces(request) {
|
|
|
5090
4870
|
}
|
|
5091
4871
|
async function check(request) {
|
|
5092
4872
|
run_issue_number.require_issue_number(request.issue);
|
|
5093
|
-
return
|
|
4873
|
+
return decide3(await read_traces(request));
|
|
5094
4874
|
}
|
|
5095
4875
|
var run_liveness = {
|
|
5096
4876
|
check,
|
|
5097
|
-
decide:
|
|
4877
|
+
decide: decide3,
|
|
5098
4878
|
describe_agent_state,
|
|
5099
4879
|
read_child_settled,
|
|
5100
4880
|
read_agent_state,
|
|
@@ -5244,7 +5024,7 @@ function to_advice2(verdict, issue, tree) {
|
|
|
5244
5024
|
if (verdict === PARK_VERDICT) return PARK_ADVICE;
|
|
5245
5025
|
return CLEAN_ADVICE;
|
|
5246
5026
|
}
|
|
5247
|
-
function
|
|
5027
|
+
function decide4(tree, child, issue) {
|
|
5248
5028
|
const verdict = to_verdict2(tree, child);
|
|
5249
5029
|
return {
|
|
5250
5030
|
advice: to_advice2(verdict, issue, tree),
|
|
@@ -5313,8 +5093,8 @@ async function read_child_state(issue) {
|
|
|
5313
5093
|
async function check2(issue) {
|
|
5314
5094
|
require_issue_number(issue);
|
|
5315
5095
|
const tree = await read_tree_state();
|
|
5316
|
-
if (needs_reclaim(tree)) return
|
|
5317
|
-
return
|
|
5096
|
+
if (needs_reclaim(tree)) return decide4(tree, NO_CHILD_WORK, issue);
|
|
5097
|
+
return decide4(tree, await read_child_state(issue), issue);
|
|
5318
5098
|
}
|
|
5319
5099
|
var run_preflight = {
|
|
5320
5100
|
CLEAN_ADVICE,
|
|
@@ -5329,7 +5109,7 @@ var run_preflight = {
|
|
|
5329
5109
|
MERGED_PR,
|
|
5330
5110
|
STASH_LABEL_PREFIX,
|
|
5331
5111
|
check: check2,
|
|
5332
|
-
decide:
|
|
5112
|
+
decide: decide4,
|
|
5333
5113
|
read_child_state,
|
|
5334
5114
|
read_pr_state: read_pr_state2,
|
|
5335
5115
|
to_pr_state: to_pr_state2
|