@joshuafolkken/kit 1.1128.0 → 1.1166.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/investigator.md +17 -0
- package/.claude/settings.json +5 -4
- package/.claude/skills/epic-commands/SKILL.md +27 -239
- package/.claude/skills/epic-commands/epic-bundle.md +115 -0
- package/.claude/skills/epic-commands/execution-waves.md +126 -0
- package/.claude/skills/workflow-commands/SKILL.md +48 -194
- package/.claude/skills/workflow-commands/background-commands.md +1 -1
- package/.claude/skills/workflow-commands/backlogrun-child.md +12 -123
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +11 -60
- package/.claude/skills/workflow-commands/backlogrun-park.md +10 -8
- package/.claude/skills/workflow-commands/backlogrun-progress.md +19 -175
- package/.claude/skills/workflow-commands/backlogrun-recovery.md +155 -0
- package/.claude/skills/workflow-commands/backlogrun-steps.md +64 -144
- package/.claude/skills/workflow-commands/backlogrun.md +5 -4
- package/.claude/skills/workflow-commands/chain-rule.md +3 -2
- package/.claude/skills/workflow-commands/delegation.md +7 -3
- package/.claude/skills/workflow-commands/entry-sequence.md +62 -0
- package/.claude/skills/workflow-commands/followup.md +104 -181
- package/.claude/skills/workflow-commands/fullrun-steps.md +8 -9
- package/.claude/skills/workflow-commands/fullrun.md +16 -73
- package/.claude/skills/workflow-commands/halfrun.md +10 -36
- package/.claude/skills/workflow-commands/into-target.md +4 -4
- package/.claude/skills/workflow-commands/issue-comments.md +7 -8
- package/.claude/skills/workflow-commands/issue-scout.md +6 -6
- package/.claude/skills/workflow-commands/kickoff.md +6 -6
- package/.claude/skills/workflow-commands/needs-human-review.md +6 -6
- package/.claude/skills/workflow-commands/observation-filing.md +21 -172
- package/.claude/skills/workflow-commands/observation-ledger.md +150 -0
- package/.claude/skills/workflow-commands/pre-gate-cut.md +33 -93
- package/.claude/skills/workflow-commands/prerequisite.md +6 -6
- package/.claude/skills/workflow-commands/progress-watcher.md +90 -0
- package/.claude/skills/workflow-commands/prrun.md +5 -5
- package/.claude/skills/workflow-commands/retrospective.md +9 -9
- package/.claude/skills/workflow-commands/split-assessment.md +2 -2
- package/.claude/skills/workflow-commands/target-repository.md +2 -2
- package/.claude/skills/workflow-commands/working-tree-hold.md +7 -5
- package/.codex/hooks.json +4 -4
- package/AGENTS.md +1 -6
- package/CLAUDE.md +58 -120
- package/GEMINI.md +1 -6
- package/SECURITY.md +1 -0
- package/dist/CLAUDE.md +58 -120
- package/dist/hooks/batch-guard.js +4 -4
- package/dist/hooks/{chunk-MKVGBQPA.js → chunk-5KTCMCOT.js} +14 -1
- package/dist/hooks/{chunk-NXYMNPUQ.js → chunk-A6P7PN7P.js} +59 -21
- package/dist/hooks/{chunk-77J7IEQM.js → chunk-ANJ2WGFD.js} +1 -1
- package/dist/hooks/{chunk-4JZDP5X5.js → chunk-DYAVAM6S.js} +572 -127
- package/dist/hooks/{chunk-7HVB7SZG.js → chunk-ENA7P5FB.js} +4 -4
- package/dist/hooks/{chunk-M7QKEKWO.js → chunk-GU5MATVE.js} +214 -85
- package/dist/hooks/chunk-GYNGP7HI.js +56 -0
- package/dist/hooks/{chunk-5SD2YM7Z.js → chunk-URJA6XY4.js} +4 -4
- package/dist/hooks/{chunk-XYUIQOW3.js → chunk-V2BTOZHT.js} +6 -4
- package/dist/hooks/{chunk-AIOIOMPX.js → chunk-ZDZ555YH.js} +225 -78
- package/dist/hooks/codex-hook-adapter.js +10 -10
- package/dist/hooks/duplicate-read-guard.js +5 -5
- package/dist/hooks/format-edited.js +2 -2
- package/dist/hooks/inputs.json +186 -184
- package/dist/hooks/investigation-guard.js +5 -5
- package/dist/hooks/pretool-guard.js +11 -10
- package/dist/hooks/stop-guard.js +5 -5
- package/dist/josh.js +33 -3
- package/dist/version/index.js +5 -1
- package/package.json +7 -4
- package/prompts/collaboration-workflow/file-edits.md +11 -10
- package/prompts/collaboration-workflow/gh-rest.md +2 -4
- package/prompts/collaboration-workflow/glossary.md +1 -1
- package/prompts/collaboration-workflow/issue-citation.md +5 -9
- package/prompts/collaboration-workflow/issue-template.md +40 -115
- package/prompts/collaboration-workflow/operating-rules.md +4 -5
- package/prompts/collaboration-workflow/output-bounds.md +12 -78
- package/prompts/collaboration-workflow/overview.md +7 -13
- package/prompts/collaboration-workflow/plan-comment.md +11 -39
- package/prompts/collaboration-workflow/principles.md +3 -4
- package/prompts/collaboration-workflow/report-format.md +28 -74
- package/prompts/collaboration-workflow/residency.md +12 -109
- package/prompts/collaboration-workflow/rule-delivery.md +98 -97
- package/prompts/collaboration-workflow/shell-body.md +3 -13
- package/prompts/collaboration-workflow/turn-batching.md +14 -88
- package/prompts/collaboration-workflow/upstream-interrupt.md +12 -14
- package/prompts/collaboration-workflow/wip-cap.md +6 -45
- package/prompts/collaboration-workflow.md +1 -13
- package/prompts/review-rubric.md +8 -71
- package/prompts/review.md +25 -55
- package/prompts/testing-guide.md +18 -67
- package/scripts/agent/agent-role-profile.ts +1 -1
- package/scripts/agent/codex-hooks.ts +3 -12
- package/scripts/backlog/backlog-budget.ts +1 -1
- package/scripts/backlog/backlog-drive-cli.ts +4 -3
- package/scripts/backlog/backlog-drive-offer-argv.ts +10 -1
- package/scripts/backlog/backlog-offer.ts +6 -6
- package/scripts/behavior/behavior-rules.ts +11 -4
- package/scripts/delegation/delegation-policy.ts +9 -1
- package/scripts/delegation/investigation-guard.ts +1 -1
- package/scripts/delegation/investigation-reads.ts +109 -23
- package/scripts/discovery/repo-discovery.ts +21 -7
- package/scripts/discovery/repo-origin.ts +26 -0
- package/scripts/discovery/repo-party.ts +42 -1
- package/scripts/document/backlogrun-parent-read-set.ts +14 -15
- package/scripts/document/capped-print.ts +74 -0
- package/scripts/document/document-byte-budget.ts +34 -20
- package/scripts/document/document-section-cli.ts +4 -3
- package/scripts/document/entry-read-budget.ts +21 -9
- package/scripts/document/lane-child-read-set.ts +12 -14
- package/scripts/document/read-set-cli.ts +4 -4
- package/scripts/document/read-set-point-of-use.ts +10 -2
- package/scripts/document/resident-budget.ts +47 -0
- package/scripts/epic/epic-shape.ts +10 -13
- package/scripts/epic/epic-solo.ts +1 -1
- package/scripts/exports/unused-members-cli.ts +7 -3
- package/scripts/gate/scoped-green.ts +25 -14
- package/scripts/gh/git-gh-api-path.ts +7 -0
- package/scripts/hooks/pretool-guard-cli.ts +2 -2
- package/scripts/hooks/pretool-guard.ts +45 -2
- package/scripts/hooks/run-hook.sh +41 -0
- package/scripts/init/claude-kit-only-settings.ts +29 -0
- package/scripts/init/hook-command-rewrite.ts +18 -35
- package/scripts/init/hook-launch.ts +21 -32
- package/scripts/init/init-copy-content.ts +8 -2
- package/scripts/init/initial-commit.ts +24 -0
- package/scripts/init/start-exec.ts +16 -3
- package/scripts/init/start-steps.ts +8 -12
- package/scripts/issue/issue-auto-ok.ts +95 -0
- package/scripts/issue/issue-file-cli.ts +53 -7
- package/scripts/issue/issue-file.ts +28 -5
- package/scripts/issue/issue-labels.ts +12 -12
- package/scripts/issue/issue-rank.ts +1 -1
- package/scripts/issue/issue-read-cli.ts +4 -3
- package/scripts/issue/issue-read.ts +2 -2
- package/scripts/issue/issue-report-failures.ts +6 -2
- package/scripts/issue/issue-scout-cli.ts +2 -2
- package/scripts/issue/issue-state-cli.ts +2 -2
- package/scripts/issue/issue-state.ts +2 -2
- package/scripts/issue/issue-wip.ts +70 -0
- package/scripts/josh/hook-decision.ts +17 -0
- package/scripts/josh/josh-commands-development.ts +10 -0
- package/scripts/josh/josh-commands-issue.ts +2 -2
- package/scripts/josh/josh-commands-lane.ts +1 -1
- package/scripts/josh/josh-commands-project.ts +7 -0
- package/scripts/lane/lane-await.ts +46 -2
- package/scripts/lane/lane-cli.ts +11 -13
- package/scripts/lines/bytes-command.ts +29 -6
- package/scripts/observations/observation-ledger-line.ts +1 -1
- package/scripts/release/github-release-environment.ts +1 -0
- package/scripts/release/github-release.ts +103 -17
- package/scripts/release/release-progress.ts +167 -0
- package/scripts/release/release-publish.ts +19 -8
- package/scripts/release/release-tag.ts +22 -8
- package/scripts/rules/decision-oracle-stage.ts +1 -1
- package/scripts/rules/decision-oracle.ts +6 -6
- package/scripts/rules/delivered-rules.ts +11 -11
- package/scripts/rules/direct-pr-create.ts +110 -0
- package/scripts/rules/file-body.ts +98 -42
- package/scripts/rules/filing-cap.ts +2 -2
- package/scripts/rules/filing-offer.ts +1 -1
- package/scripts/rules/gh-api.ts +18 -1
- package/scripts/rules/implementation-cut.ts +3 -1
- package/scripts/rules/lane-background.ts +67 -10
- package/scripts/rules/permission-guards.ts +10 -2
- package/scripts/rules/stop-rules.ts +4 -4
- package/scripts/rules/test-declared-commit.ts +1 -1
- package/scripts/rules/third-party-write.ts +56 -12
- package/scripts/run/run-carry-cli.ts +3 -1
- package/scripts/run/run-carry-conversation.ts +94 -0
- package/scripts/run/run-carry-reclaim.ts +37 -0
- package/scripts/run/run-carry.ts +21 -5
- package/scripts/run/run-cut-handoff.ts +21 -1
- package/scripts/run/run-entry-cli.ts +11 -3
- package/scripts/run/run-hold-cli.ts +16 -2
- package/scripts/run/run-label.ts +50 -0
- package/scripts/run/run-merge-steps.ts +12 -4
- package/scripts/run/run-merge.ts +1 -1
- package/scripts/run/run-prep-cli.ts +6 -2
- package/scripts/run/run-prep.ts +17 -0
- package/scripts/run/run-ship-preflight.ts +34 -3
- package/scripts/run/run-ship-review-steps.ts +1 -1
- package/scripts/run/run-ship-review.ts +26 -5
- package/scripts/run/run-step-cli.ts +20 -7
- package/scripts/run/run-wake-driver.ts +24 -1
- package/scripts/run/run-wake-session.ts +2 -1
- package/scripts/test/test-declared-logic.ts +1 -1
- package/scripts/test/test-related.ts +1 -6
- package/scripts/test/test-unit-guard.ts +21 -7
- package/scripts/time-runtime/time-batch-guard.ts +52 -16
- package/scripts/time-runtime/time-density-hook.ts +20 -2
- package/scripts/time-runtime/time-density.ts +4 -3
- package/scripts/time-runtime/time-markers.ts +15 -4
- package/scripts/time-runtime/time-spans.ts +4 -0
- package/scripts/version/npm-registry.ts +11 -1
- package/templates/workflows/ci.yml +9 -1
- package/templates/workflows/github-release.yml +13 -25
- package/.claude/skills/workflow-commands/followup-reference.md +0 -118
- package/dist/hooks/chunk-QVLT52OV.js +0 -464
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
investigation_reads
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-A6P7PN7P.js";
|
|
4
4
|
import {
|
|
5
5
|
lane_guard_policy
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-GYNGP7HI.js";
|
|
7
7
|
import {
|
|
8
8
|
hook_decision
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-5KTCMCOT.js";
|
|
10
10
|
|
|
11
11
|
// scripts/delegation/investigation-guard.ts
|
|
12
12
|
import { text } from "node:stream/consumers";
|
|
@@ -25,7 +25,7 @@ function should_notify(tail, call, notified_at_ms, run) {
|
|
|
25
25
|
if (investigation_mode() !== "notice") return false;
|
|
26
26
|
return investigation_reads.should_block(tail, call, notified_at_ms, run);
|
|
27
27
|
}
|
|
28
|
-
var NOTICE = `\u{1F4A1} investigation: files read and not edited since the last delegated unit reached the threshold, so the reading from here should go to a unit of its own. This is a notice, not a refusal \u2014 the read proceeds, because a dispatched lane child ends its turn on a denial. Ask \`pnpm josh delegate investigation\`, then
|
|
28
|
+
var NOTICE = `\u{1F4A1} investigation: files read and not edited, or search turns, since the last delegated unit reached the threshold, so the reading from here should go to a unit of its own. This is a notice, not a refusal \u2014 the read proceeds, because a dispatched lane child ends its turn on a denial. Ask \`pnpm josh delegate investigation\`, then dispatch the \`${investigation_reads.INVESTIGATOR_AGENT}\` agent (\`kit:${investigation_reads.INVESTIGATOR_AGENT}\` in a consumer) with a brief of what the main line has already concluded and what is left to find out; it returns the conclusion plus its \`file:line\` citations, never the file text. Keep a read in the main line only where this run will edit that file. This recurs on the next accumulation.`;
|
|
29
29
|
function notice_text(_call, tail) {
|
|
30
30
|
const pending = investigation_reads.tally_of(tail).pending.map((absolute) => investigation_reads.repository_relative(absolute));
|
|
31
31
|
return pending.length === 0 ? NOTICE : `${NOTICE} Read and not edited so far: ${pending.join(" ")}.`;
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
PORCELAIN_FLAG,
|
|
8
8
|
RECENT_REQUEST_WINDOW,
|
|
9
9
|
agent_role_profile,
|
|
10
|
-
agent_session_environment,
|
|
11
10
|
bash_triggers,
|
|
12
11
|
cli_flags,
|
|
13
12
|
error_text,
|
|
@@ -24,42 +23,43 @@ import {
|
|
|
24
23
|
lane_park,
|
|
25
24
|
lane_registry,
|
|
26
25
|
open_issue_schema,
|
|
26
|
+
own_session,
|
|
27
27
|
parse_json_object_safe,
|
|
28
28
|
read_json_listing,
|
|
29
29
|
repo_origin,
|
|
30
30
|
repo_party,
|
|
31
31
|
run_carry,
|
|
32
32
|
run_cut,
|
|
33
|
+
run_cut_handoff,
|
|
33
34
|
run_headless,
|
|
34
35
|
run_hold,
|
|
35
36
|
run_tail,
|
|
36
37
|
shell_segments,
|
|
37
38
|
tail_commands
|
|
38
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-ZDZ555YH.js";
|
|
39
40
|
import {
|
|
40
41
|
find_local_bin_upwards
|
|
41
42
|
} from "./chunk-3V37IID6.js";
|
|
42
43
|
import {
|
|
43
44
|
batch_outcome
|
|
44
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-V2BTOZHT.js";
|
|
45
46
|
import {
|
|
46
47
|
investigation_refusal
|
|
47
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-ENA7P5FB.js";
|
|
48
49
|
import {
|
|
49
50
|
duplicate_read_outcome
|
|
50
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-URJA6XY4.js";
|
|
51
52
|
import {
|
|
52
53
|
investigation_reads
|
|
53
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-A6P7PN7P.js";
|
|
54
55
|
import {
|
|
55
|
-
lane_guard_policy
|
|
56
|
-
|
|
57
|
-
} from "./chunk-QVLT52OV.js";
|
|
56
|
+
lane_guard_policy
|
|
57
|
+
} from "./chunk-GYNGP7HI.js";
|
|
58
58
|
import {
|
|
59
59
|
hook_decision,
|
|
60
60
|
lane_child_marker,
|
|
61
61
|
run_issue_number
|
|
62
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-5KTCMCOT.js";
|
|
63
63
|
import {
|
|
64
64
|
PLATFORM_TEMP_ROOT,
|
|
65
65
|
cost_attribute,
|
|
@@ -70,8 +70,9 @@ import {
|
|
|
70
70
|
json_value,
|
|
71
71
|
lane_paths,
|
|
72
72
|
stamp_file,
|
|
73
|
+
time_batch_guard,
|
|
73
74
|
time_shell
|
|
74
|
-
} from "./chunk-
|
|
75
|
+
} from "./chunk-DYAVAM6S.js";
|
|
75
76
|
|
|
76
77
|
// scripts/rules/direct-filing.ts
|
|
77
78
|
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-automation.md` \u2192 "`josh issue:file`". **This rule fires on every occurrence, not once per run.**';
|
|
@@ -466,15 +467,28 @@ var early_heartbeat = {
|
|
|
466
467
|
|
|
467
468
|
// scripts/rules/file-body.ts
|
|
468
469
|
import { existsSync } from "node:fs";
|
|
469
|
-
var HEREDOC =
|
|
470
|
+
var HEREDOC = /(?<!<)<<(?!<)-?\s*['"]?([A-Z_a-z]\w*)/u;
|
|
470
471
|
var LINE_BREAK = "\n";
|
|
471
|
-
function first_line(command) {
|
|
472
|
-
const [line] = command.split(LINE_BREAK);
|
|
473
|
-
return line ?? "";
|
|
474
|
-
}
|
|
475
472
|
function has_heredoc(command) {
|
|
476
473
|
return HEREDOC.test(command);
|
|
477
474
|
}
|
|
475
|
+
function with_body_line(lines, line) {
|
|
476
|
+
const opener = lines.at(-1);
|
|
477
|
+
if (opener === void 0) return [...lines];
|
|
478
|
+
return [...lines.slice(0, -1), { ...opener, body: `${opener.body}${line}${LINE_BREAK}` }];
|
|
479
|
+
}
|
|
480
|
+
function scan_line(scan, line) {
|
|
481
|
+
if (scan.delimiter === void 0) {
|
|
482
|
+
return { lines: [...scan.lines, { text: line, body: "" }], delimiter: HEREDOC.exec(line)?.[1] };
|
|
483
|
+
}
|
|
484
|
+
if (line.trim() === scan.delimiter) return { ...scan, delimiter: void 0 };
|
|
485
|
+
return { ...scan, lines: with_body_line(scan.lines, line) };
|
|
486
|
+
}
|
|
487
|
+
function command_lines(command) {
|
|
488
|
+
let scan = { lines: [], delimiter: void 0 };
|
|
489
|
+
for (const line of command.split(LINE_BREAK)) scan = scan_line(scan, line);
|
|
490
|
+
return scan.lines;
|
|
491
|
+
}
|
|
478
492
|
var FILE_REDIRECT = /(?:^|\s)>>?\s*(?!&)("[^"]+"|'[^']+'|\S+)/u;
|
|
479
493
|
var TEE_TARGET = /\btee\s+(?:-a\s+)?(?!-)("[^"]+"|'[^']+'|\S+)/u;
|
|
480
494
|
var QUOTES = /^['"]|['"]$/gu;
|
|
@@ -482,22 +496,32 @@ function redirect_target(line) {
|
|
|
482
496
|
const target = FILE_REDIRECT.exec(line)?.[1] ?? TEE_TARGET.exec(line)?.[1];
|
|
483
497
|
return target === void 0 ? void 0 : target.replaceAll(QUOTES, "");
|
|
484
498
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
499
|
+
var BODY_INTERPRETERS = /* @__PURE__ */ new Set([
|
|
500
|
+
"python",
|
|
501
|
+
"python3",
|
|
502
|
+
"ruby",
|
|
503
|
+
"perl",
|
|
504
|
+
"php",
|
|
505
|
+
"node"
|
|
506
|
+
]);
|
|
491
507
|
var NODE = "node";
|
|
492
508
|
var NODE_EVAL = /(?:^|\s)(?:-e|--eval)\b/u;
|
|
493
|
-
var WRITE_CALL = /writeFile|appendFile|createWriteStream|\.write(?:lines)?\s*\(/u;
|
|
509
|
+
var WRITE_CALL = /writeFile|appendFile|createWriteStream|\.write(?:lines|_text|_bytes)?\s*\(/u;
|
|
494
510
|
var WRITE_MODE = /open\s*\([^)]*['"][rbt+]*[wax]|os\.replace|shutil\.(?:copy|move)/u;
|
|
495
|
-
function body_writes(
|
|
496
|
-
return WRITE_CALL.test(
|
|
511
|
+
function body_writes(body) {
|
|
512
|
+
return WRITE_CALL.test(body) || WRITE_MODE.test(body);
|
|
497
513
|
}
|
|
498
|
-
|
|
514
|
+
var SCRIPT_FILE = /\.(?:py|rb|pl|php|js|mjs|cjs)$/u;
|
|
515
|
+
function is_heredoc_write(line, has_file) {
|
|
516
|
+
const target = has_heredoc(line.text) ? redirect_target(line.text) : void 0;
|
|
517
|
+
if (target === void 0) return false;
|
|
518
|
+
if (has_file(target)) return true;
|
|
519
|
+
return SCRIPT_FILE.test(target) && body_writes(line.body);
|
|
520
|
+
}
|
|
521
|
+
function is_interpreter_write(segment, body) {
|
|
499
522
|
const leading = time_shell.leading_word(segment);
|
|
500
|
-
if (BODY_INTERPRETERS.has(leading)) return
|
|
523
|
+
if (!BODY_INTERPRETERS.has(leading)) return false;
|
|
524
|
+
if (has_heredoc(segment)) return body_writes(body);
|
|
501
525
|
return leading === NODE && NODE_EVAL.test(segment) && body_writes(segment);
|
|
502
526
|
}
|
|
503
527
|
var PERL = "perl";
|
|
@@ -507,13 +531,15 @@ function is_in_place_perl(segment) {
|
|
|
507
531
|
if (time_shell.leading_word(segment) !== PERL) return false;
|
|
508
532
|
return segment.split(WHITESPACE).some((word) => PERL_IN_PLACE.test(word));
|
|
509
533
|
}
|
|
510
|
-
function has_body_interpreter(
|
|
511
|
-
return shell_segments.segments_of(
|
|
534
|
+
function has_body_interpreter(line) {
|
|
535
|
+
return shell_segments.segments_of(line.text).some((segment) => is_interpreter_write(segment, line.body) || is_in_place_perl(segment));
|
|
512
536
|
}
|
|
513
537
|
function carries_a_file_body(command, has_file = existsSync) {
|
|
514
|
-
return
|
|
538
|
+
return command_lines(command).some(
|
|
539
|
+
(line) => is_heredoc_write(line, has_file) || has_body_interpreter(line)
|
|
540
|
+
);
|
|
515
541
|
}
|
|
516
|
-
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),
|
|
542
|
+
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, a script file (`cat > edit.py <<EOF`) 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); a change scattered so widely that the partial edits would carry more than the file is rewritten whole with the Write tool, never a heredoc. 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.**";
|
|
517
543
|
var ROW2 = {
|
|
518
544
|
id: "file-body",
|
|
519
545
|
is_trigger: bash_triggers.on_bash_command((command) => carries_a_file_body(command)),
|
|
@@ -535,6 +561,7 @@ var READ_METHOD = "GET";
|
|
|
535
561
|
var REPO_PATH = /repos\/([^/\s'"?]+)\/([^/\s'"?]+)/u;
|
|
536
562
|
var OWNER_GROUP = 1;
|
|
537
563
|
var REPO_GROUP = 2;
|
|
564
|
+
var OWNER_PLACEHOLDERS = /* @__PURE__ */ new Set(["{owner}", ":owner"]);
|
|
538
565
|
function is_gh_api(segment) {
|
|
539
566
|
return GH_API_COMMAND.test(segment);
|
|
540
567
|
}
|
|
@@ -554,7 +581,18 @@ function repo_target(segment) {
|
|
|
554
581
|
if (match === null) return void 0;
|
|
555
582
|
return { owner: match[OWNER_GROUP] ?? "", repo: match[REPO_GROUP] ?? "" };
|
|
556
583
|
}
|
|
557
|
-
|
|
584
|
+
function is_owner_placeholder(owner) {
|
|
585
|
+
return OWNER_PLACEHOLDERS.has(owner);
|
|
586
|
+
}
|
|
587
|
+
var gh_api = {
|
|
588
|
+
GH_FLAGS,
|
|
589
|
+
is_gh_api,
|
|
590
|
+
is_owner_placeholder,
|
|
591
|
+
is_read,
|
|
592
|
+
is_write,
|
|
593
|
+
method_of,
|
|
594
|
+
repo_target
|
|
595
|
+
};
|
|
558
596
|
|
|
559
597
|
// scripts/rules/git-argv.ts
|
|
560
598
|
var ENV_ASSIGNMENT = /^[A-Za-z_]\w*=/u;
|
|
@@ -893,25 +931,6 @@ var cost_verdict = {
|
|
|
893
931
|
report_over
|
|
894
932
|
};
|
|
895
933
|
|
|
896
|
-
// scripts/cost-runtime/own-session.ts
|
|
897
|
-
var NOT_FOUND = -1;
|
|
898
|
-
function session_id_of(environment) {
|
|
899
|
-
const value = environment[agent_session_environment.SESSION_ID_KEY];
|
|
900
|
-
return value === void 0 || value === "" ? void 0 : value;
|
|
901
|
-
}
|
|
902
|
-
function select_own_session(files, environment) {
|
|
903
|
-
const session_id = session_id_of(environment);
|
|
904
|
-
if (session_id === void 0) {
|
|
905
|
-
return { kind: "own", index: cost_transcript.latest_own_index(files) };
|
|
906
|
-
}
|
|
907
|
-
const index = files.findIndex((file) => file.session_id === session_id);
|
|
908
|
-
return index === NOT_FOUND ? { kind: "absent", session_id } : { kind: "own", index };
|
|
909
|
-
}
|
|
910
|
-
var own_session = {
|
|
911
|
-
session_id_of,
|
|
912
|
-
select_own_session
|
|
913
|
-
};
|
|
914
|
-
|
|
915
934
|
// scripts/cost-runtime/transcript-cwd.ts
|
|
916
935
|
import path3 from "node:path";
|
|
917
936
|
function resolve(target, cwd) {
|
|
@@ -1163,7 +1182,7 @@ function is_over_threshold_edit(call, state = current_state()) {
|
|
|
1163
1182
|
return warrants_the_cut(state);
|
|
1164
1183
|
}
|
|
1165
1184
|
var THRESHOLD_TEXT = `${cost_format.format_tokens(CONTEXT_CUT_THRESHOLD)}-token`;
|
|
1166
|
-
var IMPLEMENTATION_CUT_REASON = `\u26D4 implementation-phase cut: this checkout is a lane dispatched for this issue, or a run holds it for this issue (joshuafolkken/kit#2760), and its recent-context cost has crossed the shared ${THRESHOLD_TEXT} threshold mid-implementation, so the thinking accumulated so far is now re-read on every later request. Take the cut before this edit. First write a handoff file with the Write tool \u2014 the user\u2019s instruction verbatim, what you have completed, what remains, and what you deliberately did not touch \u2014 and pass it as \`--handoff <path>\`, so the fresh process resumes on the original instruction rather than the working tree alone (joshuafolkken/kit#2354); a resume that finds no instruction is refused \`incomplete\` rather than continuing blind. \`pnpm josh run:cut --impl <N> --handoff <path>\` ends this process and relaunches a fresh one that resumes back into implementation (\`resume-impl\`), dropping the accumulated context rather than carrying it \u2014 joshuafolkken/kit#1933 built the boundary and joshuafolkken/kit#2310 measured it firing 0 times because the verdict was only ever read at session entry, where the context has not yet grown. The verdict is \`pnpm josh cost --cut\`'s exactly \u2014 the same per-request billed-input statistic against the same threshold, never a second measurement. Issue \`pnpm josh run:cut --impl <N> --handoff <path>\` now and read the verdict: on \`cut\`, **end the turn immediately** \u2014 in a lane the fresh process owns the run and continues implementing, so it must not be waited for; outside a lane nothing is relaunched, so first send the \`confirmation\` Telegram naming the resume command \`fullrun #<N>\` and keep the hold; on \`not-a-lane\`, \`unready\`, \`busy\`, \`failed\` or \`unknown\`, this process carries the run on and the edit is simply the next call. Never relaunch a second process after \`busy\`. This fired because the dispatch mark names this lane or the run hold names this issue; a person working here carries no mark and holds no run, so 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 this edit once the cut has answered \u2014 an edit reissued right after this refusal passes, so \`busy\` / \`failed\` cannot wedge the run edit after edit; this fires again on the next threshold crossing rather than once per run.`;
|
|
1185
|
+
var IMPLEMENTATION_CUT_REASON = `\u26D4 implementation-phase cut: this checkout is a lane dispatched for this issue, or a run holds it for this issue (joshuafolkken/kit#2760), and its recent-context cost has crossed the shared ${THRESHOLD_TEXT} threshold mid-implementation, so the thinking accumulated so far is now re-read on every later request. Take the cut before this edit. First write a handoff file with the Write tool \u2014 the user\u2019s instruction verbatim, what you have completed, what remains, and what you deliberately did not touch, as ${run_cut_handoff.HANDOFF_FORMAT} (Markdown is refused) \u2014 and pass it as \`--handoff <path>\`, so the fresh process resumes on the original instruction rather than the working tree alone (joshuafolkken/kit#2354); a resume that finds no instruction is refused \`incomplete\` rather than continuing blind. \`pnpm josh run:cut --impl <N> --handoff <path>\` ends this process and relaunches a fresh one that resumes back into implementation (\`resume-impl\`), dropping the accumulated context rather than carrying it \u2014 joshuafolkken/kit#1933 built the boundary and joshuafolkken/kit#2310 measured it firing 0 times because the verdict was only ever read at session entry, where the context has not yet grown. The verdict is \`pnpm josh cost --cut\`'s exactly \u2014 the same per-request billed-input statistic against the same threshold, never a second measurement. Issue \`pnpm josh run:cut --impl <N> --handoff <path>\` now and read the verdict: on \`cut\`, **end the turn immediately** \u2014 in a lane the fresh process owns the run and continues implementing, so it must not be waited for; outside a lane nothing is relaunched, so first send the \`confirmation\` Telegram naming the resume command \`fullrun #<N>\` and keep the hold; on \`not-a-lane\`, \`unready\`, \`busy\`, \`failed\` or \`unknown\`, this process carries the run on and the edit is simply the next call. Never relaunch a second process after \`busy\`. This fired because the dispatch mark names this lane or the run hold names this issue; a person working here carries no mark and holds no run, so 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 this edit once the cut has answered \u2014 an edit reissued right after this refusal passes, so \`busy\` / \`failed\` cannot wedge the run edit after edit; this fires again on the next threshold crossing rather than once per run.`;
|
|
1167
1186
|
var REISSUE_WINDOW_MS = 9e4;
|
|
1168
1187
|
function is_reissued_refusal(now_ms, delivered_at_ms) {
|
|
1169
1188
|
return now_ms - delivered_at_ms < REISSUE_WINDOW_MS;
|
|
@@ -1455,13 +1474,13 @@ var RUN_ENTRY_ORACLE = {
|
|
|
1455
1474
|
decision: "Where a run on an issue starts, and whether the typed command has already been reached",
|
|
1456
1475
|
args: "<N> [--to kickoff|halfrun|prrun|fullrun]",
|
|
1457
1476
|
vocabulary: run_stage.START_TOKENS,
|
|
1458
|
-
single_source: "
|
|
1477
|
+
single_source: "docs/how-to/run-issues.md"
|
|
1459
1478
|
};
|
|
1460
1479
|
|
|
1461
1480
|
// scripts/rules/decision-oracle.ts
|
|
1462
1481
|
var CHAIN_RULE_MD = ".claude/skills/workflow-commands/chain-rule.md";
|
|
1463
1482
|
var BACKLOGRUN_PROGRESS_MD = ".claude/skills/workflow-commands/backlogrun-progress.md";
|
|
1464
|
-
var SKILL_2E = ".claude/skills/workflow-commands/
|
|
1483
|
+
var SKILL_2E = ".claude/skills/workflow-commands/issue-scout.md";
|
|
1465
1484
|
var SPLIT_ASSESSMENT_QUESTION = ".claude/skills/workflow-commands/split-assessment.md \u2192 The question";
|
|
1466
1485
|
var PRE_GATE_CUT_MD = ".claude/skills/workflow-commands/pre-gate-cut.md";
|
|
1467
1486
|
var BACKLOGRUN_MD = ".claude/skills/workflow-commands/backlogrun.md";
|
|
@@ -1494,7 +1513,7 @@ var DECISION_ORACLES = [
|
|
|
1494
1513
|
decision: "Whether a run step may be delegated to a cheaper execution tier",
|
|
1495
1514
|
args: "<step>",
|
|
1496
1515
|
vocabulary: ["delegate", "keep"],
|
|
1497
|
-
single_source: ".claude/skills/workflow-commands/
|
|
1516
|
+
single_source: ".claude/skills/workflow-commands/delegation.md"
|
|
1498
1517
|
},
|
|
1499
1518
|
{
|
|
1500
1519
|
name: "review:level",
|
|
@@ -1537,7 +1556,7 @@ var DECISION_ORACLES = [
|
|
|
1537
1556
|
decision: "Whether this working tree is free for a run to claim",
|
|
1538
1557
|
args: `[${ISSUE_N_ARG}]`,
|
|
1539
1558
|
vocabulary: ["hold", BUSY, UNKNOWN],
|
|
1540
|
-
single_source: ".claude/skills/workflow-commands/
|
|
1559
|
+
single_source: ".claude/skills/workflow-commands/working-tree-hold.md"
|
|
1541
1560
|
},
|
|
1542
1561
|
{
|
|
1543
1562
|
name: "cost:cut",
|
|
@@ -1552,7 +1571,7 @@ var DECISION_ORACLES = [
|
|
|
1552
1571
|
decision: "Whether a release is owed after this merge",
|
|
1553
1572
|
args: "",
|
|
1554
1573
|
vocabulary: [REQUIRED, SKIP, UNKNOWN],
|
|
1555
|
-
single_source: ".claude/skills/workflow-commands/followup
|
|
1574
|
+
single_source: ".claude/skills/workflow-commands/followup.md"
|
|
1556
1575
|
},
|
|
1557
1576
|
{
|
|
1558
1577
|
name: "epic:bundle",
|
|
@@ -1601,7 +1620,7 @@ var DECISION_ORACLES = [
|
|
|
1601
1620
|
decision: "An issue's state, labels, and human-review flag",
|
|
1602
1621
|
args: ISSUE_N_ARG,
|
|
1603
1622
|
vocabulary: ["human_review: yes", "human_review: no"],
|
|
1604
|
-
single_source: ".claude/skills/workflow-commands/
|
|
1623
|
+
single_source: ".claude/skills/workflow-commands/needs-human-review.md"
|
|
1605
1624
|
},
|
|
1606
1625
|
{
|
|
1607
1626
|
name: "run:cut:resume",
|
|
@@ -1638,7 +1657,7 @@ var DECISION_ORACLES = [
|
|
|
1638
1657
|
decision: "Whether a named stash entry exists and can be applied",
|
|
1639
1658
|
args: "<message>",
|
|
1640
1659
|
vocabulary: ["popped", "conflicted", "no-match", "ambiguous"],
|
|
1641
|
-
single_source: ".claude/skills/workflow-commands/
|
|
1660
|
+
single_source: ".claude/skills/workflow-commands/prerequisite.md"
|
|
1642
1661
|
},
|
|
1643
1662
|
{
|
|
1644
1663
|
name: "backlog:next",
|
|
@@ -1908,6 +1927,67 @@ var ROW10 = {
|
|
|
1908
1927
|
};
|
|
1909
1928
|
var destructive_command = { DESTRUCTIVE_COMMAND_REASON, ROW: ROW10, is_destructive_command };
|
|
1910
1929
|
|
|
1930
|
+
// scripts/rules/direct-pr-create.ts
|
|
1931
|
+
var GH_COMMAND2 = "gh";
|
|
1932
|
+
var PR_GROUP2 = "pr";
|
|
1933
|
+
var PR_CREATE_ACTIONS = /* @__PURE__ */ new Set(["create", "new"]);
|
|
1934
|
+
var FLAG_PREFIX3 = "-";
|
|
1935
|
+
var VALUE_TAKING_FLAGS = /* @__PURE__ */ new Set(["-R", "--repo"]);
|
|
1936
|
+
var API_VALUE_TAKING_FLAGS = /* @__PURE__ */ new Set([
|
|
1937
|
+
...VALUE_TAKING_FLAGS,
|
|
1938
|
+
"-X",
|
|
1939
|
+
"--method",
|
|
1940
|
+
"-f",
|
|
1941
|
+
"--raw-field",
|
|
1942
|
+
"-F",
|
|
1943
|
+
"--field",
|
|
1944
|
+
"-H",
|
|
1945
|
+
"--header",
|
|
1946
|
+
"--input",
|
|
1947
|
+
"-q",
|
|
1948
|
+
"--jq",
|
|
1949
|
+
"-t",
|
|
1950
|
+
"--template",
|
|
1951
|
+
"--hostname",
|
|
1952
|
+
"-p",
|
|
1953
|
+
"--preview",
|
|
1954
|
+
"--cache"
|
|
1955
|
+
]);
|
|
1956
|
+
var API_SUBCOMMAND = "api";
|
|
1957
|
+
var PULLS_PATH = /^['"]?(?:https:\/\/api\.github\.com)?\/?repos\/[^/\s'"]+\/[^/\s'"]+\/pulls\/?['"]?$/u;
|
|
1958
|
+
function positional_words(args, value_taking_flags) {
|
|
1959
|
+
return args.filter((argument, index) => {
|
|
1960
|
+
if (argument.startsWith(FLAG_PREFIX3)) return false;
|
|
1961
|
+
return !value_taking_flags.has(args[index - 1] ?? "");
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
function is_pr_create_api_call(args) {
|
|
1965
|
+
const call = [GH_COMMAND2, ...args].join(" ");
|
|
1966
|
+
if (!gh_api.is_gh_api(call) || !gh_api.is_write(call)) return false;
|
|
1967
|
+
const [subcommand, endpoint = ""] = positional_words(args, API_VALUE_TAKING_FLAGS);
|
|
1968
|
+
return subcommand === API_SUBCOMMAND && PULLS_PATH.test(endpoint);
|
|
1969
|
+
}
|
|
1970
|
+
function is_pr_create_cli_call(args) {
|
|
1971
|
+
const [group, action = ""] = positional_words(args, VALUE_TAKING_FLAGS);
|
|
1972
|
+
return group === PR_GROUP2 && PR_CREATE_ACTIONS.has(action);
|
|
1973
|
+
}
|
|
1974
|
+
function is_pr_create_segment(segment) {
|
|
1975
|
+
const args = git_argv.arguments_of(segment, GH_COMMAND2);
|
|
1976
|
+
if (args === void 0) return false;
|
|
1977
|
+
return is_pr_create_cli_call(args) || is_pr_create_api_call(args);
|
|
1978
|
+
}
|
|
1979
|
+
function is_direct_pr_create(command) {
|
|
1980
|
+
return shell_segments.segments_of(command).some((segment) => is_pr_create_segment(segment));
|
|
1981
|
+
}
|
|
1982
|
+
var DIRECT_PR_CREATE_REASON = "\u26D4 direct PR creation: `gh pr create` (or a `gh api` POST to `repos/<o>/<r>/pulls`) bypasses `build_body`, which writes the `closes #N` line \u2014 the Issue would stay open after the merge (`CLAUDE.md` \u2192 Git Rules). Open the PR with `pnpm josh pr` instead; after a failed push, fix, push, then run `pnpm josh pr`. **This rule fires on every occurrence, not once per run.**";
|
|
1983
|
+
var ROW11 = {
|
|
1984
|
+
id: "direct-pr-create",
|
|
1985
|
+
is_trigger: bash_triggers.on_bash_command(is_direct_pr_create),
|
|
1986
|
+
reason: DIRECT_PR_CREATE_REASON,
|
|
1987
|
+
decide: () => true
|
|
1988
|
+
};
|
|
1989
|
+
var direct_pr_create = { DIRECT_PR_CREATE_REASON, ROW: ROW11, is_direct_pr_create };
|
|
1990
|
+
|
|
1911
1991
|
// scripts/rules/worktree-guard.ts
|
|
1912
1992
|
var CHECKOUT_SUBCOMMAND = "checkout";
|
|
1913
1993
|
var RESTORE_SUBCOMMAND = "restore";
|
|
@@ -1968,14 +2048,14 @@ function is_unauthorized_worktree_change(command) {
|
|
|
1968
2048
|
return shell_segments.segments_of(command).some((segment) => is_unauthorized_segment(segment));
|
|
1969
2049
|
}
|
|
1970
2050
|
var WORKTREE_MUTATION_REASON = '\u26D4 unauthorized working-tree change: `git checkout -- <path>`, `git restore <path>`, a forced `git clean` and an unauthorized `git stash` discard work or mutate 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.**';
|
|
1971
|
-
var
|
|
2051
|
+
var ROW12 = {
|
|
1972
2052
|
id: "worktree-mutation",
|
|
1973
2053
|
is_trigger: bash_triggers.on_bash_command(is_unauthorized_worktree_change),
|
|
1974
2054
|
reason: WORKTREE_MUTATION_REASON,
|
|
1975
2055
|
decide: () => true
|
|
1976
2056
|
};
|
|
1977
2057
|
var worktree_guard = {
|
|
1978
|
-
ROW:
|
|
2058
|
+
ROW: ROW12,
|
|
1979
2059
|
WORKTREE_MUTATION_REASON,
|
|
1980
2060
|
is_unauthorized_worktree_change,
|
|
1981
2061
|
touches_worktree
|
|
@@ -2004,13 +2084,13 @@ function is_index_mutation(command) {
|
|
|
2004
2084
|
return shell_segments.segments_of(command).some((segment) => is_index_segment(segment));
|
|
2005
2085
|
}
|
|
2006
2086
|
var INDEX_MUTATION_REASON = '\u26D4 git index mutation: `git add` / `stage` / `commit` / `reset` / `rm` / `mv` and `git restore --staged` overwrite the user\'s staged snapshot, which `CLAUDE.md` \u2192 Git Rules forbids on your own judgement (`prompts/collaboration-workflow/operating-rules.md` \u2192 "git index \u3092\u52DD\u624B\u306B\u5909\u66F4\u3057\u306A\u3044"). The `.claude/settings.json` deny list matches prefix globs, so it misses `git -C . commit`, `git -c k=v commit` and `env git commit`; this row reads the argv instead, so the spelling does not matter. A commit goes through `pnpm josh git -y`; any other staging needs explicit current-turn user instruction, run by the user in their own terminal. **This rule fires on every occurrence, not once per run.**';
|
|
2007
|
-
var
|
|
2087
|
+
var ROW13 = {
|
|
2008
2088
|
id: "index-mutation",
|
|
2009
2089
|
is_trigger: bash_triggers.on_bash_command(is_index_mutation),
|
|
2010
2090
|
reason: INDEX_MUTATION_REASON,
|
|
2011
2091
|
decide: () => true
|
|
2012
2092
|
};
|
|
2013
|
-
var index_guard = { INDEX_MUTATION_REASON, ROW:
|
|
2093
|
+
var index_guard = { INDEX_MUTATION_REASON, ROW: ROW13, is_index_mutation };
|
|
2014
2094
|
|
|
2015
2095
|
// scripts/rules/protected-files.ts
|
|
2016
2096
|
import path5 from "node:path";
|
|
@@ -2159,16 +2239,21 @@ function is_protected_file_call(call, is_kit_consumer2 = doctor_consumer.is_kit_
|
|
|
2159
2239
|
return is_environment_read(call) || is_consumer_settings_edit(call, is_kit_consumer2);
|
|
2160
2240
|
}
|
|
2161
2241
|
var PROTECTED_FILE_REASON = '\u26D4 protected file: `.env` holds the Telegram bot token, so reading it puts a secret in the transcript \u2014 the scripts that need it load it in their own process. `.claude/settings.json` in a consumer repository is the deny list and the hooks every other rule rests on, so a run must not rewrite it; `josh sync` owns it, and a change belongs upstream in kit (`CLAUDE.md` \u2192 "Route distributed-doc / config changes upstream to kit"). Leave both files alone, or ask the user. **This rule fires on every occurrence, not once per run.**';
|
|
2162
|
-
var
|
|
2242
|
+
var ROW14 = {
|
|
2163
2243
|
id: "protected-file",
|
|
2164
2244
|
is_trigger: is_protected_file_call,
|
|
2165
2245
|
reason: PROTECTED_FILE_REASON,
|
|
2166
2246
|
decide: () => true
|
|
2167
2247
|
};
|
|
2168
|
-
var protected_files = { ROW:
|
|
2248
|
+
var protected_files = { ROW: ROW14, is_protected_file_call };
|
|
2169
2249
|
|
|
2170
2250
|
// scripts/rules/permission-guards.ts
|
|
2171
|
-
var ROWS2 = [
|
|
2251
|
+
var ROWS2 = [
|
|
2252
|
+
index_guard.ROW,
|
|
2253
|
+
destructive_command.ROW,
|
|
2254
|
+
protected_files.ROW,
|
|
2255
|
+
direct_pr_create.ROW
|
|
2256
|
+
];
|
|
2172
2257
|
var permission_guards = { ROWS: ROWS2 };
|
|
2173
2258
|
|
|
2174
2259
|
// scripts/rules/piped-verification.ts
|
|
@@ -2239,7 +2324,7 @@ function is_poll_loop(command) {
|
|
|
2239
2324
|
return LOOP_KEYWORD2.test(plain) && SLEEP_COMMAND.test(plain);
|
|
2240
2325
|
}
|
|
2241
2326
|
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.**';
|
|
2242
|
-
var
|
|
2327
|
+
var ROW15 = {
|
|
2243
2328
|
id: "poll-loop",
|
|
2244
2329
|
is_trigger: bash_triggers.on_bash_command(is_poll_loop),
|
|
2245
2330
|
reason: POLL_LOOP_REASON,
|
|
@@ -2247,7 +2332,7 @@ var ROW14 = {
|
|
|
2247
2332
|
};
|
|
2248
2333
|
var poll_loop = {
|
|
2249
2334
|
POLL_LOOP_REASON,
|
|
2250
|
-
ROW:
|
|
2335
|
+
ROW: ROW15,
|
|
2251
2336
|
is_poll_loop
|
|
2252
2337
|
};
|
|
2253
2338
|
|
|
@@ -2367,13 +2452,13 @@ function posts_at_path_literally(command) {
|
|
|
2367
2452
|
return RAW_FIELD_BODY_AT.test(command);
|
|
2368
2453
|
}
|
|
2369
2454
|
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.";
|
|
2370
|
-
var
|
|
2455
|
+
var ROW16 = {
|
|
2371
2456
|
id: "raw-field-body",
|
|
2372
2457
|
is_trigger: bash_triggers.on_bash_command(posts_at_path_literally),
|
|
2373
2458
|
reason: RAW_FIELD_BODY_REASON,
|
|
2374
2459
|
decide: () => true
|
|
2375
2460
|
};
|
|
2376
|
-
var raw_field_body = { RAW_FIELD_BODY_REASON, ROW:
|
|
2461
|
+
var raw_field_body = { RAW_FIELD_BODY_REASON, ROW: ROW16, posts_at_path_literally };
|
|
2377
2462
|
|
|
2378
2463
|
// scripts/rules/rule-body-guard.ts
|
|
2379
2464
|
var EDIT_TOOL = "Edit";
|
|
@@ -2444,7 +2529,7 @@ function runs_placement_oracle(command) {
|
|
|
2444
2529
|
(names) => shell_segments.segments_of(command).some((segment) => shell_segments.is_josh_command(segment, names))
|
|
2445
2530
|
);
|
|
2446
2531
|
}
|
|
2447
|
-
var
|
|
2532
|
+
var ROW17 = {
|
|
2448
2533
|
id: "rule-body",
|
|
2449
2534
|
is_trigger: writes_rule_prose,
|
|
2450
2535
|
reason: RULE_BODY_REASON,
|
|
@@ -2453,7 +2538,7 @@ var ROW16 = {
|
|
|
2453
2538
|
};
|
|
2454
2539
|
var rule_body_guard = {
|
|
2455
2540
|
RULE_BODY_REASON,
|
|
2456
|
-
ROW:
|
|
2541
|
+
ROW: ROW17,
|
|
2457
2542
|
answered_placement,
|
|
2458
2543
|
is_rule_document,
|
|
2459
2544
|
writes_rule_prose
|
|
@@ -2888,31 +2973,53 @@ function is_untested_commit(call) {
|
|
|
2888
2973
|
return test_declared_changed.current_verdict() === "required";
|
|
2889
2974
|
}
|
|
2890
2975
|
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.';
|
|
2891
|
-
var
|
|
2892
|
-
var test_declared_commit = { REASON, ROW:
|
|
2976
|
+
var ROW18 = { id: "test-declared", is_trigger: is_untested_commit, reason: REASON };
|
|
2977
|
+
var test_declared_commit = { REASON, ROW: ROW18 };
|
|
2893
2978
|
|
|
2894
2979
|
// scripts/rules/third-party-write.ts
|
|
2895
|
-
function is_third_party_write_segment(segment, session_owner) {
|
|
2980
|
+
function is_third_party_write_segment(segment, session_owner, resolve_placeholder_owners) {
|
|
2896
2981
|
if (!gh_api.is_gh_api(segment) || !gh_api.is_write(segment)) return false;
|
|
2897
2982
|
const target = gh_api.repo_target(segment);
|
|
2898
2983
|
if (target === void 0) return false;
|
|
2899
|
-
|
|
2984
|
+
if (!gh_api.is_owner_placeholder(target.owner)) {
|
|
2985
|
+
return repo_party.classify(session_owner, target.owner) === repo_party.THIRD_PARTY;
|
|
2986
|
+
}
|
|
2987
|
+
return resolve_placeholder_owners().some(
|
|
2988
|
+
(owner) => owner === void 0 || repo_party.classify(session_owner, owner) === repo_party.THIRD_PARTY
|
|
2989
|
+
);
|
|
2900
2990
|
}
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2991
|
+
var GH_CONTEXT_CHANGE = /(?:^|\s)(?:cd|pushd)(?:\s|$)|(?:^|\s)(?:export\s+)?GH_REPO=/u;
|
|
2992
|
+
function unreadable_candidates() {
|
|
2993
|
+
return [void 0];
|
|
2994
|
+
}
|
|
2995
|
+
function candidates_at(segments, index, resolve_placeholder_owners) {
|
|
2996
|
+
const has_moved = segments.slice(0, index).some((segment) => GH_CONTEXT_CHANGE.test(segment));
|
|
2997
|
+
return has_moved ? unreadable_candidates : resolve_placeholder_owners;
|
|
2998
|
+
}
|
|
2999
|
+
function writes_third_party(command, resolve_owner = repo_party.current_owner, resolve_placeholder_owners = repo_party.placeholder_owners) {
|
|
3000
|
+
const segments = shell_segments.segments_of(command);
|
|
3001
|
+
const has_write = segments.some(
|
|
3002
|
+
(segment) => gh_api.is_gh_api(segment) && gh_api.is_write(segment)
|
|
3003
|
+
);
|
|
3004
|
+
if (!has_write) return false;
|
|
2904
3005
|
const session_owner = resolve_owner();
|
|
2905
|
-
return
|
|
3006
|
+
return segments.some(
|
|
3007
|
+
(segment, index) => is_third_party_write_segment(
|
|
3008
|
+
segment,
|
|
3009
|
+
session_owner,
|
|
3010
|
+
candidates_at(segments, index, resolve_placeholder_owners)
|
|
3011
|
+
)
|
|
3012
|
+
);
|
|
2906
3013
|
}
|
|
2907
3014
|
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.**';
|
|
2908
|
-
var
|
|
3015
|
+
var ROW19 = {
|
|
2909
3016
|
id: "third-party-write",
|
|
2910
3017
|
is_trigger: bash_triggers.on_bash_command((command) => writes_third_party(command)),
|
|
2911
3018
|
reason: THIRD_PARTY_WRITE_REASON,
|
|
2912
3019
|
decide: () => true
|
|
2913
3020
|
};
|
|
2914
3021
|
var third_party_write = {
|
|
2915
|
-
ROW:
|
|
3022
|
+
ROW: ROW19,
|
|
2916
3023
|
THIRD_PARTY_WRITE_REASON,
|
|
2917
3024
|
writes_third_party
|
|
2918
3025
|
};
|
|
@@ -2922,7 +3029,7 @@ var SWITCH_ENV_KEY = "JOSH_RULE_GUARD";
|
|
|
2922
3029
|
var STAMP_PREFIX = "josh-rule-guard-";
|
|
2923
3030
|
var { is_issue_filing, on_bash_command } = bash_triggers;
|
|
2924
3031
|
var WIP_CAP = 30;
|
|
2925
|
-
var WIP_CAP_REASON = `\u26D4 backlog WIP cap:
|
|
3032
|
+
var WIP_CAP_REASON = `\u26D4 backlog WIP cap: \`pnpm josh issue:file\` counts the target repository's open Issues before filing. With more than ${String(WIP_CAP)} open, close one first; nothing honestly closable means do not file \u2014 the command holds such a filing. Two filings are exempt and proceed while stating the overage \u2014 one the run is blocked by (\`--over-cap\`, or the \`tier-a\` / \`split\` route), and an interrupt (\`--route 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. Both procedures are in \`prompts/collaboration-workflow/wip-cap.md\`. Reissue this call and let the command count \u2014 it fires once per run and cannot repeat on the call in hand.`;
|
|
2926
3033
|
var ISSUE_VIEW_COMMAND = new RegExp(String.raw`^gh\s+${gh_api.GH_FLAGS}issue\s+view\s`, "u");
|
|
2927
3034
|
var ISSUE_BODY_PATH = /repos\/[^\s'"]*\/issues\/\d+(?=$|["'\s])/u;
|
|
2928
3035
|
var FETCHES_COMMENTS = /--comments\b|--json\s[\w,]*\bcomments\b|\/comments\b/u;
|
|
@@ -2947,7 +3054,7 @@ function is_body_only_issue_read(command) {
|
|
|
2947
3054
|
if (segments.some((segment) => fetches_issue_comments(segment))) return false;
|
|
2948
3055
|
return segments.some((segment) => is_body_read_segment(segment));
|
|
2949
3056
|
}
|
|
2950
|
-
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/
|
|
3057
|
+
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/issue-comments.md`. Every body-only read is refused until the comments are read.";
|
|
2951
3058
|
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;
|
|
2952
3059
|
var ISSUE_LISTING = /^gh\s+(?:-{1,2}[\w-]+(?:[= ]\S+)?\s+)*issue\s+list\b/u;
|
|
2953
3060
|
var ISSUES_QUERY = /repos\/[^\s'"]*\/issues\?[^\s'"]*state=open/u;
|
|
@@ -4316,7 +4423,29 @@ async function pretool_outcome_async(raw_payload) {
|
|
|
4316
4423
|
if (reason !== void 0) return { reason, notice: void 0, fault: void 0 };
|
|
4317
4424
|
return with_step_zero_notice(base, raw_payload);
|
|
4318
4425
|
}
|
|
4319
|
-
|
|
4426
|
+
function guarded_call(raw_payload) {
|
|
4427
|
+
try {
|
|
4428
|
+
const payload = hook_decision.parse_hook_payload(raw_payload);
|
|
4429
|
+
return payload === void 0 ? void 0 : { name: payload.tool_name, input: payload.tool_input };
|
|
4430
|
+
} catch {
|
|
4431
|
+
return void 0;
|
|
4432
|
+
}
|
|
4433
|
+
}
|
|
4434
|
+
function foreground_rewrite(outcome, raw_payload, is_lane_child) {
|
|
4435
|
+
if (outcome.reason !== void 0) return void 0;
|
|
4436
|
+
const call = guarded_call(raw_payload);
|
|
4437
|
+
const input = call === void 0 ? void 0 : lane_background.foreground_input(call, is_lane_child);
|
|
4438
|
+
if (input === void 0) return void 0;
|
|
4439
|
+
const context = [lane_background.FOREGROUND_NOTE, outcome.notice ?? outcome.fault];
|
|
4440
|
+
return hook_decision.rewrite_envelope(input, context.filter(Boolean).join("\n\n"));
|
|
4441
|
+
}
|
|
4442
|
+
function emit(raw_payload, outcome) {
|
|
4443
|
+
const rewrite = foreground_rewrite(outcome, raw_payload);
|
|
4444
|
+
if (rewrite === void 0) hook_decision.emit_outcome(outcome);
|
|
4445
|
+
else process.stdout.write(`${rewrite}
|
|
4446
|
+
`);
|
|
4447
|
+
}
|
|
4448
|
+
var pretool_guard = { combine_outcomes, emit, foreground_rewrite, pretool_outcome };
|
|
4320
4449
|
|
|
4321
4450
|
export {
|
|
4322
4451
|
pretool_outcome_async,
|