claude-dev-env 2.5.0 → 2.7.1
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.md +20 -57
- package/_shared/pr-loop/scripts/code_rules_gate.py +2 -1
- package/_shared/pr-loop/scripts/code_rules_gate_parts/CLAUDE.md +12 -2
- package/_shared/pr-loop/scripts/code_rules_gate_parts/baseline_import_isolation.py +309 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/staged_test_regression.py +540 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/staged_test_running.py +206 -70
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/__init__.py +1 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/_repo_test_helpers.py +76 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_baseline_import_isolation.py +248 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_staged_test_regression.py +309 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_staged_test_running.py +91 -58
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +202 -0
- package/_shared/pr-loop/worker-spawn.md +3 -1
- package/agents/CLAUDE.md +1 -1
- package/agents/code-verifier.md +36 -7
- package/bin/codex-compat.mjs +104 -0
- package/bin/codex-compat.test.mjs +51 -0
- package/codex-capability-map.json +13 -0
- package/docs/CODE_RULES.md +2 -0
- package/docs/codex-compatibility.md +25 -0
- package/docs/nas-ssh-invocation.md +96 -12
- package/docs/references/code-review-enforcement.md +31 -6
- package/hooks/blocking/CLAUDE.md +3 -0
- package/hooks/blocking/config/code_review_enforcement_constants.py +40 -10
- package/hooks/blocking/config/test_code_review_enforcement_constants.py +56 -3
- package/hooks/blocking/eli11_reply_enforcer.py +479 -0
- package/hooks/blocking/gh_body_arg_blocker.py +1 -1
- package/hooks/blocking/nas_ssh_binary_enforcer.py +8 -46
- package/hooks/blocking/shell_substitution_blocker.py +129 -0
- package/hooks/blocking/state_description_blocker.py +1 -1
- package/hooks/blocking/stop_dispatcher.py +1 -1
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +2 -3
- package/hooks/blocking/test_eli11_reply_enforcer.py +457 -0
- package/hooks/blocking/test_shell_substitution_blocker.py +124 -0
- package/hooks/blocking/test_stop_dispatcher.py +23 -0
- package/hooks/blocking/test_unscoped_search_blocker.py +102 -0
- package/hooks/blocking/test_verdict_directory_write_blocker.py +4 -8
- package/hooks/blocking/unscoped_search_blocker.py +391 -0
- package/hooks/git-hooks/CLAUDE.md +3 -0
- package/hooks/git-hooks/conftest.py +30 -0
- package/hooks/git-hooks/gate_utils.py +2 -2
- package/hooks/git-hooks/git_hooks_constants/__init__.py +41 -2
- package/hooks/git-hooks/pre_push.py +75 -4
- package/hooks/git-hooks/pre_push_base_reference.py +166 -0
- package/hooks/git-hooks/test_config.py +0 -15
- package/hooks/git-hooks/test_gate_utils.py +3 -15
- package/hooks/git-hooks/test_pre_commit.py +1 -15
- package/hooks/git-hooks/test_pre_push.py +236 -27
- package/hooks/git-hooks/test_pre_push_base_reference.py +339 -0
- package/hooks/hooks.json +0 -12
- package/hooks/hooks_constants/CLAUDE.md +5 -1
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +4 -4
- package/hooks/hooks_constants/eli11_reply_enforcer_constants.py +101 -0
- package/hooks/hooks_constants/nas_ssh_binary_enforcer_constants.py +2 -8
- package/hooks/hooks_constants/shell_command_segments.py +82 -0
- package/hooks/hooks_constants/shell_substitution_blocker_constants.py +67 -0
- package/hooks/hooks_constants/stop_dispatcher_constants.py +1 -0
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +5 -6
- package/hooks/hooks_constants/test_stop_dispatcher_constants.py +1 -0
- package/hooks/hooks_constants/unscoped_search_blocker_constants.py +153 -0
- package/package.json +4 -2
- package/rules/CLAUDE.md +17 -23
- package/rules/agent-spawn-protocol.md +6 -6
- package/rules/anti-corollary-tests.md +1 -1
- package/rules/bdd.md +1 -1
- package/rules/cleanup-temp-files.md +10 -4
- package/rules/code-standards.md +7 -0
- package/rules/conservative-action.md +1 -5
- package/rules/context7.md +0 -4
- package/rules/destructive-commands.md +47 -0
- package/rules/doc-inventory-integrity.md +48 -0
- package/rules/doc-prose-cuts.md +58 -0
- package/rules/docstring-prose-matches-implementation.md +10 -2
- package/rules/durable-post-artifacts.md +0 -4
- package/rules/eli11-replies.md +31 -0
- package/rules/explore-thoroughly.md +4 -4
- package/rules/falsify-before-green.md +68 -0
- package/rules/file-global-constants.md +1 -1
- package/rules/filesystem-search.md +51 -0
- package/rules/gh-cli-conventions.md +27 -0
- package/rules/git-workflow.md +26 -0
- package/rules/hedging-claims.md +9 -0
- package/rules/long-horizon-autonomy.md +0 -4
- package/rules/measurement-denominators.md +48 -0
- package/rules/nas-ssh-invocation.md +23 -5
- package/rules/parallel-tools.md +2 -2
- package/rules/plain-illustrative-docstrings.md +3 -7
- package/rules/plain-language.md +2 -0
- package/rules/proof-of-work-pr-comments.md +0 -4
- package/rules/re-stage-before-commit.md +2 -0
- package/rules/research-mode.md +10 -0
- package/rules/shell-invocation.md +21 -0
- package/rules/testing.md +4 -0
- package/rules/verified-commit-gate-skip.md +3 -27
- package/rules/verify-before-asking.md +5 -0
- package/rules/windows-filesystem-safe.md +1 -1
- package/rules/workers-done-before-complete.md +4 -0
- package/scripts/CLAUDE.md +3 -3
- package/scripts/Migrate-ShellPolicy.ps1 +1 -1
- package/scripts/codex_capability_bridge.py +171 -0
- package/scripts/codex_compat_materializer.py +1087 -0
- package/scripts/codex_compat_watcher.py +502 -0
- package/scripts/dev_env_scripts_constants/CLAUDE.md +1 -1
- package/scripts/dev_env_scripts_constants/code_review_constants.py +37 -0
- package/scripts/dev_env_scripts_constants/grok_worker_constants.py +79 -13
- package/scripts/grok_headless_runner.py +213 -16
- package/scripts/invoke_code_review.py +11 -4
- package/scripts/resolve_worker_spawn.py +56 -10
- package/scripts/spawn_grok_batch.py +43 -22
- package/scripts/sync_to_cursor/rules.py +0 -10
- package/scripts/test_grok_headless_runner.py +592 -10
- package/scripts/test_invoke_code_review.py +143 -0
- package/scripts/test_invoke_code_review_chain.py +1 -1
- package/scripts/test_invoke_code_review_contract.py +1 -1
- package/scripts/test_resolve_worker_spawn.py +179 -15
- package/scripts/test_spawn_grok_batch.py +225 -22
- package/scripts/tests/test_code_review_constants.py +80 -0
- package/scripts/tests/test_codex_capability_bridge.py +91 -0
- package/scripts/tests/test_codex_compat_materializer.py +632 -0
- package/scripts/tests/test_codex_compat_watcher.py +599 -0
- package/scripts/tests/test_sync_to_cursor.py +0 -1
- package/skills/autoconverge/workflow/converge.contract.test.mjs +28 -6
- package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +73 -0
- package/skills/autoconverge/workflow/converge.mjs +64 -15
- package/skills/bugteam/reference/copilot-gap-analysis.md +1 -1
- package/skills/condensing-instructions/SKILL.md +42 -51
- package/skills/fresh-branch/CLAUDE.md +1 -1
- package/skills/fresh-branch/SKILL.md +5 -6
- package/skills/fresh-branch/scripts/create_fresh_branch.py +42 -24
- package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +1 -3
- package/skills/fresh-branch/scripts/test_create_fresh_branch.py +30 -126
- package/skills/grok-spawn/SKILL.md +5 -3
- package/skills/grok-spawn/reference/flag-profiles.md +3 -1
- package/skills/orchestrator/SKILL.md +23 -9
- package/skills/orchestrator-refresh/SKILL.md +20 -1
- package/skills/privacy-hygiene/reference/sweep-procedure.md +1 -1
- package/skills/session-log/SKILL.md +1 -1
- package/rules/claude-md-orphan-file.md +0 -28
- package/rules/cleanup-command-forms.md +0 -23
- package/rules/code-reviews.md +0 -11
- package/rules/env-var-table-code-drift.md +0 -10
- package/rules/gh-body-file.md +0 -5
- package/rules/gh-paginate.md +0 -3
- package/rules/hook-prose-matches-detector.md +0 -15
- package/rules/no-historical-clutter.md +0 -26
- package/rules/no-inline-destructive-literals.md +0 -9
- package/rules/no-justification-noise.md +0 -61
- package/rules/package-inventory-stale-entry.md +0 -25
- package/rules/right-sized-engineering.md +0 -28
- package/rules/self-contained-docs.md +0 -17
- package/rules/shell-invocation-policy.md +0 -5
- package/rules/state-what-is.md +0 -25
- package/rules/tdd.md +0 -7
|
@@ -245,28 +245,23 @@ class TestResolveAgentSlug:
|
|
|
245
245
|
|
|
246
246
|
|
|
247
247
|
class TestResolveAgentWorktreeRoot:
|
|
248
|
-
def
|
|
248
|
+
def should_nest_under_the_repository_claude_worktrees_directory(
|
|
249
249
|
self,
|
|
250
|
-
|
|
250
|
+
tmp_path: Path,
|
|
251
251
|
) -> None:
|
|
252
252
|
module = load_create_fresh_branch_module()
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
worktree_root = module.resolve_agent_worktree_root("grok")
|
|
256
|
-
assert worktree_root == (
|
|
257
|
-
Path(r"C:\Users\example") / "AppData" / "Local" / "Temp" / "grok"
|
|
258
|
-
)
|
|
253
|
+
worktree_root = module.resolve_agent_worktree_root(tmp_path, "grok")
|
|
254
|
+
assert worktree_root == tmp_path / ".claude" / "worktrees" / "grok"
|
|
259
255
|
|
|
260
|
-
def
|
|
256
|
+
def should_keep_each_agent_in_its_own_subdirectory(
|
|
261
257
|
self,
|
|
262
|
-
monkeypatch: pytest.MonkeyPatch,
|
|
263
258
|
tmp_path: Path,
|
|
264
259
|
) -> None:
|
|
265
260
|
module = load_create_fresh_branch_module()
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
assert
|
|
261
|
+
grok_root = module.resolve_agent_worktree_root(tmp_path, "grok")
|
|
262
|
+
claude_root = module.resolve_agent_worktree_root(tmp_path, "claude")
|
|
263
|
+
assert grok_root != claude_root
|
|
264
|
+
assert grok_root.parent == claude_root.parent
|
|
270
265
|
|
|
271
266
|
|
|
272
267
|
class TestResolveUniqueWorktreePath:
|
|
@@ -301,18 +296,9 @@ class TestAgentSlugPathSafety:
|
|
|
301
296
|
def should_reject_parent_segment_agent_slug_without_mkdir_outside(
|
|
302
297
|
self,
|
|
303
298
|
tmp_path: Path,
|
|
304
|
-
monkeypatch: pytest.MonkeyPatch,
|
|
305
299
|
) -> None:
|
|
306
300
|
module = load_create_fresh_branch_module()
|
|
307
|
-
|
|
308
|
-
agent_scratch_parent.mkdir()
|
|
309
|
-
escaped_agent_root = (agent_scratch_parent / ".." / "escape-agent").resolve()
|
|
310
|
-
monkeypatch.setattr(module.sys, "platform", "linux")
|
|
311
|
-
monkeypatch.setattr(
|
|
312
|
-
module.tempfile,
|
|
313
|
-
"gettempdir",
|
|
314
|
-
lambda: str(agent_scratch_parent),
|
|
315
|
-
)
|
|
301
|
+
escaped_agent_root = (tmp_path / "escape-agent").resolve()
|
|
316
302
|
with pytest.raises(ValueError, match="agent slug"):
|
|
317
303
|
module.create_fresh_branch(
|
|
318
304
|
branch_name="fix/safe-branch",
|
|
@@ -321,22 +307,13 @@ class TestAgentSlugPathSafety:
|
|
|
321
307
|
base_ref=DEFAULT_BASE_REF,
|
|
322
308
|
)
|
|
323
309
|
assert not escaped_agent_root.exists()
|
|
324
|
-
assert not (
|
|
310
|
+
assert not (tmp_path / "unused-repo").exists()
|
|
325
311
|
|
|
326
312
|
def should_reject_empty_agent_slug(
|
|
327
313
|
self,
|
|
328
314
|
tmp_path: Path,
|
|
329
|
-
monkeypatch: pytest.MonkeyPatch,
|
|
330
315
|
) -> None:
|
|
331
316
|
module = load_create_fresh_branch_module()
|
|
332
|
-
agent_scratch_parent = tmp_path / "agent-scratch"
|
|
333
|
-
agent_scratch_parent.mkdir()
|
|
334
|
-
monkeypatch.setattr(module.sys, "platform", "linux")
|
|
335
|
-
monkeypatch.setattr(
|
|
336
|
-
module.tempfile,
|
|
337
|
-
"gettempdir",
|
|
338
|
-
lambda: str(agent_scratch_parent),
|
|
339
|
-
)
|
|
340
317
|
with pytest.raises(ValueError, match="agent slug"):
|
|
341
318
|
module.create_fresh_branch(
|
|
342
319
|
branch_name="fix/safe-branch",
|
|
@@ -344,25 +321,16 @@ class TestAgentSlugPathSafety:
|
|
|
344
321
|
agent_slug="",
|
|
345
322
|
base_ref=DEFAULT_BASE_REF,
|
|
346
323
|
)
|
|
347
|
-
assert not
|
|
324
|
+
assert not (tmp_path / "unused-repo").exists()
|
|
348
325
|
|
|
349
326
|
|
|
350
327
|
class TestBranchNamePathSafety:
|
|
351
328
|
def should_reject_parent_segments_without_mkdir_outside(
|
|
352
329
|
self,
|
|
353
330
|
tmp_path: Path,
|
|
354
|
-
monkeypatch: pytest.MonkeyPatch,
|
|
355
331
|
) -> None:
|
|
356
332
|
module = load_create_fresh_branch_module()
|
|
357
|
-
|
|
358
|
-
agent_scratch_parent.mkdir()
|
|
359
|
-
monkeypatch.setattr(module.sys, "platform", "linux")
|
|
360
|
-
monkeypatch.setattr(
|
|
361
|
-
module.tempfile,
|
|
362
|
-
"gettempdir",
|
|
363
|
-
lambda: str(agent_scratch_parent),
|
|
364
|
-
)
|
|
365
|
-
escaped_path = agent_scratch_parent / "escape"
|
|
333
|
+
escaped_path = tmp_path / "escape"
|
|
366
334
|
with pytest.raises(ValueError, match="relative path"):
|
|
367
335
|
module.create_fresh_branch(
|
|
368
336
|
branch_name="fix/../../escape",
|
|
@@ -371,23 +339,14 @@ class TestBranchNamePathSafety:
|
|
|
371
339
|
base_ref=DEFAULT_BASE_REF,
|
|
372
340
|
)
|
|
373
341
|
assert not escaped_path.exists()
|
|
374
|
-
assert not (
|
|
342
|
+
assert not (tmp_path / "unused-repo").exists()
|
|
375
343
|
|
|
376
344
|
def should_reject_absolute_branch_without_mkdir_outside(
|
|
377
345
|
self,
|
|
378
346
|
tmp_path: Path,
|
|
379
|
-
monkeypatch: pytest.MonkeyPatch,
|
|
380
347
|
) -> None:
|
|
381
348
|
module = load_create_fresh_branch_module()
|
|
382
|
-
agent_scratch_parent = tmp_path / "agent-scratch"
|
|
383
|
-
agent_scratch_parent.mkdir()
|
|
384
349
|
outside_target = tmp_path / "outside-evil"
|
|
385
|
-
monkeypatch.setattr(module.sys, "platform", "linux")
|
|
386
|
-
monkeypatch.setattr(
|
|
387
|
-
module.tempfile,
|
|
388
|
-
"gettempdir",
|
|
389
|
-
lambda: str(agent_scratch_parent),
|
|
390
|
-
)
|
|
391
350
|
with pytest.raises(ValueError, match="relative path"):
|
|
392
351
|
module.create_fresh_branch(
|
|
393
352
|
branch_name=str(outside_target),
|
|
@@ -396,7 +355,7 @@ class TestBranchNamePathSafety:
|
|
|
396
355
|
base_ref=DEFAULT_BASE_REF,
|
|
397
356
|
)
|
|
398
357
|
assert not outside_target.exists()
|
|
399
|
-
assert not (
|
|
358
|
+
assert not (tmp_path / "unused-repo").exists()
|
|
400
359
|
|
|
401
360
|
|
|
402
361
|
class TestCreateFreshBranchIntegration:
|
|
@@ -409,13 +368,6 @@ class TestCreateFreshBranchIntegration:
|
|
|
409
368
|
repository_path = build_repo_with_origin(tmp_path / "repo")
|
|
410
369
|
caller_head_before = read_head_branch(repository_path)
|
|
411
370
|
caller_commit_before = read_head_commit(repository_path)
|
|
412
|
-
agent_scratch_parent = tmp_path / "agent-scratch"
|
|
413
|
-
monkeypatch.setattr(module.sys, "platform", "linux")
|
|
414
|
-
monkeypatch.setattr(
|
|
415
|
-
module.tempfile,
|
|
416
|
-
"gettempdir",
|
|
417
|
-
lambda: str(agent_scratch_parent),
|
|
418
|
-
)
|
|
419
371
|
success_payload = module.create_fresh_branch(
|
|
420
372
|
branch_name="fix/example-one",
|
|
421
373
|
repo_path=repository_path,
|
|
@@ -424,7 +376,14 @@ class TestCreateFreshBranchIntegration:
|
|
|
424
376
|
)
|
|
425
377
|
worktree_path = Path(success_payload[PAYLOAD_KEY_WORKTREE_PATH])
|
|
426
378
|
assert worktree_path.is_dir()
|
|
427
|
-
assert worktree_path ==
|
|
379
|
+
assert worktree_path == (
|
|
380
|
+
repository_path.resolve()
|
|
381
|
+
/ ".claude"
|
|
382
|
+
/ "worktrees"
|
|
383
|
+
/ "grok"
|
|
384
|
+
/ "fix"
|
|
385
|
+
/ "example-one"
|
|
386
|
+
)
|
|
428
387
|
assert read_head_branch(worktree_path) == "fix/example-one"
|
|
429
388
|
assert read_head_commit(worktree_path) == caller_commit_before
|
|
430
389
|
assert success_payload[PAYLOAD_KEY_BRANCH] == "fix/example-one"
|
|
@@ -445,14 +404,10 @@ class TestCreateFreshBranchIntegration:
|
|
|
445
404
|
) -> None:
|
|
446
405
|
module = load_create_fresh_branch_module()
|
|
447
406
|
repository_path = build_repo_with_origin(tmp_path / "repo")
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
monkeypatch.setattr(
|
|
451
|
-
module.tempfile,
|
|
452
|
-
"gettempdir",
|
|
453
|
-
lambda: str(agent_scratch_parent),
|
|
407
|
+
repository_worktree_root = (
|
|
408
|
+
repository_path.resolve() / ".claude" / "worktrees" / "claude"
|
|
454
409
|
)
|
|
455
|
-
occupied_path =
|
|
410
|
+
occupied_path = repository_worktree_root / "fix" / "collision"
|
|
456
411
|
occupied_path.mkdir(parents=True)
|
|
457
412
|
success_payload = module.create_fresh_branch(
|
|
458
413
|
branch_name="fix/collision",
|
|
@@ -461,7 +416,7 @@ class TestCreateFreshBranchIntegration:
|
|
|
461
416
|
base_ref=DEFAULT_BASE_REF,
|
|
462
417
|
)
|
|
463
418
|
worktree_path = Path(success_payload[PAYLOAD_KEY_WORKTREE_PATH])
|
|
464
|
-
assert worktree_path ==
|
|
419
|
+
assert worktree_path == repository_worktree_root / "fix" / "collision-2"
|
|
465
420
|
assert worktree_path.is_dir()
|
|
466
421
|
assert read_head_branch(worktree_path) == "fix/collision"
|
|
467
422
|
|
|
@@ -472,13 +427,6 @@ class TestCreateFreshBranchIntegration:
|
|
|
472
427
|
) -> None:
|
|
473
428
|
module = load_create_fresh_branch_module()
|
|
474
429
|
repository_path = build_repo_with_origin(tmp_path / "repo")
|
|
475
|
-
agent_scratch_parent = tmp_path / "agent-scratch"
|
|
476
|
-
monkeypatch.setattr(module.sys, "platform", "linux")
|
|
477
|
-
monkeypatch.setattr(
|
|
478
|
-
module.tempfile,
|
|
479
|
-
"gettempdir",
|
|
480
|
-
lambda: str(agent_scratch_parent),
|
|
481
|
-
)
|
|
482
430
|
module.create_fresh_branch(
|
|
483
431
|
branch_name="fix/dup",
|
|
484
432
|
repo_path=repository_path,
|
|
@@ -515,13 +463,6 @@ class TestCreateFreshBranchIntegration:
|
|
|
515
463
|
)
|
|
516
464
|
local_main_commit = read_head_commit(repository_path)
|
|
517
465
|
assert local_main_commit != origin_tip_before
|
|
518
|
-
agent_scratch_parent = tmp_path / "agent-scratch"
|
|
519
|
-
monkeypatch.setattr(module.sys, "platform", "linux")
|
|
520
|
-
monkeypatch.setattr(
|
|
521
|
-
module.tempfile,
|
|
522
|
-
"gettempdir",
|
|
523
|
-
lambda: str(agent_scratch_parent),
|
|
524
|
-
)
|
|
525
466
|
success_payload = module.create_fresh_branch(
|
|
526
467
|
branch_name="fix/bare-base",
|
|
527
468
|
repo_path=repository_path,
|
|
@@ -543,13 +484,6 @@ class TestWorktreeBranchTracking:
|
|
|
543
484
|
) -> None:
|
|
544
485
|
module = load_create_fresh_branch_module()
|
|
545
486
|
repository_path = build_repo_with_origin(tmp_path / "repo")
|
|
546
|
-
agent_scratch_parent = tmp_path / "agent-scratch"
|
|
547
|
-
monkeypatch.setattr(module.sys, "platform", "linux")
|
|
548
|
-
monkeypatch.setattr(
|
|
549
|
-
module.tempfile,
|
|
550
|
-
"gettempdir",
|
|
551
|
-
lambda: str(agent_scratch_parent),
|
|
552
|
-
)
|
|
553
487
|
success_payload = module.create_fresh_branch(
|
|
554
488
|
branch_name="fix/no-upstream",
|
|
555
489
|
repo_path=repository_path,
|
|
@@ -574,13 +508,6 @@ class TestWorktreeBranchTracking:
|
|
|
574
508
|
repository_path,
|
|
575
509
|
empty_hooks_path=empty_hooks_path,
|
|
576
510
|
)
|
|
577
|
-
agent_scratch_parent = tmp_path / "agent-scratch"
|
|
578
|
-
monkeypatch.setattr(module.sys, "platform", "linux")
|
|
579
|
-
monkeypatch.setattr(
|
|
580
|
-
module.tempfile,
|
|
581
|
-
"gettempdir",
|
|
582
|
-
lambda: str(agent_scratch_parent),
|
|
583
|
-
)
|
|
584
511
|
success_payload = module.create_fresh_branch(
|
|
585
512
|
branch_name="feature/silent-push",
|
|
586
513
|
repo_path=repository_path,
|
|
@@ -619,13 +546,6 @@ class TestMainCli:
|
|
|
619
546
|
) -> None:
|
|
620
547
|
module = load_create_fresh_branch_module()
|
|
621
548
|
repository_path = build_repo_with_origin(tmp_path / "repo")
|
|
622
|
-
agent_scratch_parent = tmp_path / "agent-scratch"
|
|
623
|
-
monkeypatch.setattr(module.sys, "platform", "linux")
|
|
624
|
-
monkeypatch.setattr(
|
|
625
|
-
module.tempfile,
|
|
626
|
-
"gettempdir",
|
|
627
|
-
lambda: str(agent_scratch_parent),
|
|
628
|
-
)
|
|
629
549
|
monkeypatch.setattr(
|
|
630
550
|
sys,
|
|
631
551
|
"argv",
|
|
@@ -697,15 +617,7 @@ class TestMainCli:
|
|
|
697
617
|
) -> None:
|
|
698
618
|
module = load_create_fresh_branch_module()
|
|
699
619
|
repository_path = build_repo_with_origin(tmp_path / "repo")
|
|
700
|
-
|
|
701
|
-
agent_scratch_parent.mkdir()
|
|
702
|
-
escaped_path = agent_scratch_parent / "escape"
|
|
703
|
-
monkeypatch.setattr(module.sys, "platform", "linux")
|
|
704
|
-
monkeypatch.setattr(
|
|
705
|
-
module.tempfile,
|
|
706
|
-
"gettempdir",
|
|
707
|
-
lambda: str(agent_scratch_parent),
|
|
708
|
-
)
|
|
620
|
+
escaped_path = tmp_path / "escape"
|
|
709
621
|
monkeypatch.setattr(
|
|
710
622
|
sys,
|
|
711
623
|
"argv",
|
|
@@ -726,7 +638,7 @@ class TestMainCli:
|
|
|
726
638
|
assert PAYLOAD_KEY_ERROR in error_payload
|
|
727
639
|
assert "relative path" in error_payload[PAYLOAD_KEY_ERROR]
|
|
728
640
|
assert not escaped_path.exists()
|
|
729
|
-
assert not (
|
|
641
|
+
assert not (tmp_path / "unused-repo").exists()
|
|
730
642
|
|
|
731
643
|
def should_print_error_json_for_absolute_branch_name(
|
|
732
644
|
self,
|
|
@@ -736,15 +648,7 @@ class TestMainCli:
|
|
|
736
648
|
) -> None:
|
|
737
649
|
module = load_create_fresh_branch_module()
|
|
738
650
|
repository_path = build_repo_with_origin(tmp_path / "repo")
|
|
739
|
-
agent_scratch_parent = tmp_path / "agent-scratch"
|
|
740
|
-
agent_scratch_parent.mkdir()
|
|
741
651
|
outside_target = tmp_path / "outside-evil"
|
|
742
|
-
monkeypatch.setattr(module.sys, "platform", "linux")
|
|
743
|
-
monkeypatch.setattr(
|
|
744
|
-
module.tempfile,
|
|
745
|
-
"gettempdir",
|
|
746
|
-
lambda: str(agent_scratch_parent),
|
|
747
|
-
)
|
|
748
652
|
monkeypatch.setattr(
|
|
749
653
|
sys,
|
|
750
654
|
"argv",
|
|
@@ -765,4 +669,4 @@ class TestMainCli:
|
|
|
765
669
|
assert PAYLOAD_KEY_ERROR in error_payload
|
|
766
670
|
assert "relative path" in error_payload[PAYLOAD_KEY_ERROR]
|
|
767
671
|
assert not outside_target.exists()
|
|
768
|
-
assert not (
|
|
672
|
+
assert not (tmp_path / "unused-repo").exists()
|
|
@@ -125,7 +125,6 @@ Shape:
|
|
|
125
125
|
"tool_profile": "readonly",
|
|
126
126
|
"timeout_seconds": 600,
|
|
127
127
|
"is_repo_only": true,
|
|
128
|
-
"max_turns": 8,
|
|
129
128
|
"agent_name": null
|
|
130
129
|
}
|
|
131
130
|
]
|
|
@@ -141,11 +140,14 @@ Shape:
|
|
|
141
140
|
| `prompt_parts` | Ordered absolute paths to part files |
|
|
142
141
|
| `cwd` | Working directory for that worker |
|
|
143
142
|
| `tool_profile` | `readonly` or `build` |
|
|
144
|
-
| `timeout_seconds` | Per-worker timeout (default 600) |
|
|
143
|
+
| `timeout_seconds` | Per-worker timeout (default 600, ceiling 5400). A spec asking for more is refused |
|
|
145
144
|
| `is_repo_only` | Readonly only: when true, also pass `--disable-web-search` |
|
|
146
|
-
| `max_turns` | Turn cap (default 8) |
|
|
147
145
|
| `agent_name` | Optional `--agent` name, or `null` |
|
|
148
146
|
|
|
147
|
+
Workers run with no turn cap. The timeout is the only bound on a worker's
|
|
148
|
+
length, and a worker that hits it is killed with its whole process tree and
|
|
149
|
+
reported as `timeout`.
|
|
150
|
+
|
|
149
151
|
Put the spec file under the run state directory (or any path you pass to
|
|
150
152
|
`--spec`).
|
|
151
153
|
|
|
@@ -20,10 +20,12 @@ The headless runner always passes:
|
|
|
20
20
|
| `--cwd` | Worker `cwd` from the batch spec |
|
|
21
21
|
| `--output-format` | `json` |
|
|
22
22
|
| `--always-approve` | present (auto-approve tool runs) |
|
|
23
|
-
| `--max-turns` | Worker `max_turns` (default `8`) |
|
|
24
23
|
| `--leader-socket` | Unique per-worker socket path under the run state dir |
|
|
25
24
|
| `--debug-file` | Unique per-worker debug log (batch launcher adds this) |
|
|
26
25
|
|
|
26
|
+
The runner passes no `--max-turns`. Worker length is bounded by the per-worker
|
|
27
|
+
timeout alone. The preflight ping keeps its own single-turn cap.
|
|
28
|
+
|
|
27
29
|
Optional:
|
|
28
30
|
|
|
29
31
|
| Flag | When |
|
|
@@ -64,6 +64,16 @@ schedule prompt must carry it: `/orchestrator-refresh --run-slug SLUG`.
|
|
|
64
64
|
|
|
65
65
|
### Single-pending re-arm protocol (all hosts)
|
|
66
66
|
|
|
67
|
+
**The re-arm never interrupts the run.** Every "stop" in the five steps
|
|
68
|
+
below ends the *re-arm* and nothing else: the session returns to
|
|
69
|
+
orchestrating in the same turn. Arming a delayed wake schedules a later
|
|
70
|
+
reminder; it neither ends the turn nor pauses in-flight executors, and
|
|
71
|
+
the session never waits for the refresh to fire before it carries on.
|
|
72
|
+
When a create fails, keep orchestrating and re-arm at the next natural
|
|
73
|
+
break. When the denial is `rearm_already_pending`, a refresh is already
|
|
74
|
+
queued — keep orchestrating and arm nothing further this turn; the next
|
|
75
|
+
refresh firing clears the latch and arms again.
|
|
76
|
+
|
|
67
77
|
Exactly one delayed refresh may be outstanding. **Create then claim**
|
|
68
78
|
(order matters on Claude: PreToolUse denies `ScheduleWakeup` when the
|
|
69
79
|
slot is already pending).
|
|
@@ -105,11 +115,11 @@ pending, or when the tool is `CronCreate`.
|
|
|
105
115
|
1. **Invocation guard.** One `/orchestrator` per session. When a refresh
|
|
106
116
|
one-shot is already queued (`should-reschedule` exits 1 with
|
|
107
117
|
`rearm_already_pending`), do not stack a second: reuse the live
|
|
108
|
-
advisor bind
|
|
109
|
-
is already active and a re-arm is
|
|
110
|
-
|
|
111
|
-
`set --status active` preserves
|
|
112
|
-
but still do not
|
|
118
|
+
advisor bind, skip step 4 and the re-arm half of step 5, and carry on
|
|
119
|
+
from step 5's dispatch — status is already active and a re-arm is
|
|
120
|
+
already latched, so a second registration would stack a duplicate
|
|
121
|
+
host schedule. (Re-asserting `set --status active` preserves
|
|
122
|
+
`rearm_pending` when already active, but still do not re-arm.)
|
|
113
123
|
2. **Bind the shared advisor before any executor.** Follow
|
|
114
124
|
[`_shared/advisor/advisor-protocol.md`](../../_shared/advisor/advisor-protocol.md)
|
|
115
125
|
end to end: detect the host profile, compute the floor from the
|
|
@@ -123,11 +133,15 @@ pending, or when the tool is `CronCreate`.
|
|
|
123
133
|
3. **Write the run artifacts** (next section) before the first spawn.
|
|
124
134
|
4. **Activate status_gate** when the first open ledger task exists:
|
|
125
135
|
`python scripts/status_gate.py set --status active`.
|
|
126
|
-
5. **
|
|
127
|
-
|
|
128
|
-
|
|
136
|
+
5. **Dispatch the first task with its ticket** (Spawn ticket section),
|
|
137
|
+
**then register the discipline reminder** via the single-pending
|
|
138
|
+
re-arm protocol (cancel matching → `should-reschedule` → one
|
|
139
|
+
non-recurring delayed wake → `claim-rearm`; default delay about
|
|
140
|
+
2700s). Spawn before you arm, so the run is already moving, and go
|
|
141
|
+
straight on to step 6 in the same turn — the armed wake is a later
|
|
142
|
+
reminder, not the next thing to wait for.
|
|
129
143
|
6. **Orchestrate.** Hold the plan and the user conversation. Spawn each
|
|
130
|
-
task with a ticket (Spawn ticket section), keep driving while
|
|
144
|
+
remaining task with a ticket (Spawn ticket section), keep driving while
|
|
131
145
|
executors work, and keep the ledger reconciled (Task ledger
|
|
132
146
|
discipline).
|
|
133
147
|
7. **Consult the advisor at hard decisions.** The trigger list, consult
|
|
@@ -33,7 +33,7 @@ python <status_gate.py> begin-firing [--run-slug SLUG]
|
|
|
33
33
|
|
|
34
34
|
| Exit | Action |
|
|
35
35
|
|---|---|
|
|
36
|
-
| **1** |
|
|
36
|
+
| **1** | End the refresh. Cancel matching host schedules for `/orchestrator-refresh` if the host allows. Report inactive/done. **Do not** re-arm. Do not spawn. Any work already in flight keeps running. |
|
|
37
37
|
| **0** | Latch cleared. Continue with steps 1–6. |
|
|
38
38
|
|
|
39
39
|
### 0b. Done after ledger (step 1)
|
|
@@ -47,6 +47,25 @@ python <status_gate.py> set --status done [--run-slug SLUG]
|
|
|
47
47
|
|
|
48
48
|
Cancel matching host schedules; stop without re-arming.
|
|
49
49
|
|
|
50
|
+
## The refresh never interrupts the run
|
|
51
|
+
|
|
52
|
+
A refresh firing reinforces discipline alongside work already in flight.
|
|
53
|
+
It never pauses, cancels, or waits on a running executor. Reconcile the
|
|
54
|
+
ledger, re-assert the routing, re-arm once, and hand control straight back
|
|
55
|
+
to the work in progress.
|
|
56
|
+
|
|
57
|
+
Inside the re-arm protocol (step 6), every "stop" ends the *re-arm* and
|
|
58
|
+
nothing else. A `should-reschedule` exit 1 means no schedule is created this
|
|
59
|
+
firing, and a `claim-rearm` exit 1 means the schedule just created is
|
|
60
|
+
cancelled; either way the session keeps orchestrating in the same turn.
|
|
61
|
+
|
|
62
|
+
Two stops end the whole firing, and both leave running executors alone:
|
|
63
|
+
`begin-firing` exit 1 (step 0a) and the done branch (step 0b). Each means the
|
|
64
|
+
run is finished, not active, or has no readable status file, so the refresh
|
|
65
|
+
reports and adds nothing further. A fail-closed advisor bind (step 3) stops
|
|
66
|
+
advisor consultation alone; the firing still reconciles the ledger, re-arms
|
|
67
|
+
once, and reports the unreachable advisor.
|
|
68
|
+
|
|
50
69
|
## Discipline steps
|
|
51
70
|
|
|
52
71
|
1. **Reconcile the task ledger first.** Call `TaskList` after the gate.
|
|
@@ -49,7 +49,7 @@ Review each hit. Ignore:
|
|
|
49
49
|
## 4. Re-check before commit / post
|
|
50
50
|
|
|
51
51
|
- Stage only clean files
|
|
52
|
-
- Prefer `--body-file` for `gh` posts (also required by the gh-
|
|
52
|
+
- Prefer `--body-file` for `gh` posts (also required by the gh-cli-conventions rule)
|
|
53
53
|
- Let `pii_prevention_blocker` re-run on the next Write / commit / post
|
|
54
54
|
|
|
55
55
|
## Accepted residual (do not over-scrub)
|
|
@@ -25,7 +25,7 @@ Sessions come in many shapes — convergence loops, feature builds, research div
|
|
|
25
25
|
- **The `Artifact` tool wraps the file in a document skeleton at publish time.** It adds `<!doctype html>…<head>…</head><body>` around whatever the file holds — write page content only (a `<title>`, a `<style>` block, and the body markup). Do not include `<!DOCTYPE>`, `<html>`, `<head>`, or `<body>` tags in the file itself.
|
|
26
26
|
- **The `Artifact` tool redeploys to the same URL only within the current run.** Editing the session's HTML file in a later step of this same skill run and calling `Artifact` again does not mint a new URL — it updates the same published page in place. A fresh session has no memory of an artifact's URL from an earlier run, so republishing a session report written in a prior session always mints a new URL (there is no other way to target the old one without the user supplying it).
|
|
27
27
|
- **Vault paths sit outside `.claude/`.** Headless vault paths (e.g., `$OBSIDIAN_VAULT_PATH`) resolve outside the project tree. Session reports use HTML regardless of vault location — the Artifact tool needs a written file to publish.
|
|
28
|
-
- **Sessions describe current state by convention.** The state_description_blocker hook does not scan .html, but the rule at `~/.claude/rules/
|
|
28
|
+
- **Sessions describe current state by convention.** The state_description_blocker hook does not scan .html, but the rule at `~/.claude/rules/doc-prose-cuts.md` applies as a writing standard — skip historical and comparative language when composing the report; the rule file lists the full trigger set.
|
|
29
29
|
- **`write_existing_file_blocker` rejects Write on existing paths.** Use Write only when creating a fresh session report; use Edit for the vault-context append in step 3.
|
|
30
30
|
- **Obsidian frontmatter index is HTML-blind.** Obsidian's native YAML-frontmatter parser reads only `.md` files. HTML files do not appear in Obsidian's frontmatter index. Search by content still works; search by `type: session-report` does not.
|
|
31
31
|
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
paths:
|
|
3
|
-
- "**/CLAUDE.md"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Orphan File Reference in a Per-Directory CLAUDE.md
|
|
7
|
-
|
|
8
|
-
**When this applies:** Any Write, Edit, or MultiEdit to a file named `CLAUDE.md` that lists files in a markdown table whose first column names each file in backticks, or that shows run commands invoking those files inside fenced code blocks.
|
|
9
|
-
|
|
10
|
-
## Rule
|
|
11
|
-
|
|
12
|
-
Every bare filename a per-directory `CLAUDE.md` names points at a file that exists in the directory subtree the `CLAUDE.md` describes — both the filenames its table cells list and the scripts its fenced run commands invoke (`python script.py`). A table cell or a run command naming a file that exists nowhere in that subtree points a reader at something that is not there: the doc claims a file the directory does not hold.
|
|
13
|
-
|
|
14
|
-
When you add a table row or a run command, the file it names already exists in this directory or a subdirectory of it. When you remove a file, drop the row and the run command that named it.
|
|
15
|
-
|
|
16
|
-
## What the gate checks
|
|
17
|
-
|
|
18
|
-
The `claude_md_orphan_file_blocker.py` hook runs on every Write, Edit, and MultiEdit whose target basename is `CLAUDE.md`. It:
|
|
19
|
-
|
|
20
|
-
1. Reads the content the tool would leave on disk. For a Write that is the full `content`. For an Edit or MultiEdit it reconstructs the post-edit file — the existing on-disk file with the replacements applied — and also notes which orphans the file already held before the edit, so a pre-existing orphan on an untouched line is excluded and only an orphan the edit introduces is reported; when the existing file cannot be read, it scans the raw `new_string` fragment(s) instead.
|
|
21
|
-
2. Collects two kinds of referenced filename. Table cells: the first column of each markdown table row **outside** a fenced code block, keeping cells that name a bare filename wrapped in backticks, no path separator, not a slash-command, ending in a known file extension (`.py`, `.md`, `.json`, `.mjs`, `.js`, `.ts`, `.ps1`, `.cmd`, `.ahk`, `.yml`, `.yaml`, `.sh`, `.txt`, `.cfg`, `.toml`, `.ini`). Run commands: each line **inside** a fenced code block (between a ``` or `~~~` fence pair) that invokes an interpreter (`python`, `python.exe`, `python3`, `node`, `pwsh`, `powershell`, `bash`, `sh`, `ruby`, `perl`) on a script, taking that script's basename when it ends in `.py`, `.mjs`, `.js`, `.ts`, `.ps1`, `.sh`, `.rb`, or `.pl`. A fenced *table row* is an example, not a live listing, so it contributes no table-cell filename; a fenced *run command* is the contract a reader runs, so its script filename is checked.
|
|
22
|
-
3. Blocks the write when a referenced filename — from a table cell or a fenced run command — exists nowhere under the scan root — the `CLAUDE.md` directory's parent, which covers the directory, its subdirectories, and its siblings. A filesystem error that halts the whole subtree walk fails open (the write proceeds), so an unreadable tree never blocks a write.
|
|
23
|
-
|
|
24
|
-
The check stays quiet for a target that is not a `CLAUDE.md`, for a table cell that holds a path, a subdirectory ending in `/`, or a slash-command, for a table row inside a fenced code block, for an inline `python x.py` mention outside a fence (prose, not a runnable contract), and for a table whose content names an explicit relative-path source (a `../` token), since that table documents files that sit outside the subtree by design.
|
|
25
|
-
|
|
26
|
-
## Why this is a hook, not a lint pass
|
|
27
|
-
|
|
28
|
-
A table row or a run command that names an absent file reads as a contract: a reader trusts the listing to map the directory and trusts the shown command to run. A wrong row sends the reader looking for a file that is not there; a stale run command fails the moment the reader runs it. Both erode trust in every other entry. Catching them as each line is written keeps the doc and the directory in step.
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# Cleanup Command Forms
|
|
2
|
-
|
|
3
|
-
Never use bash `rm` in any form to clean up. The `destructive_command_blocker` hook watches every Bash-tool command and matches `rm -rf` (and the rest of the destructive patterns) as raw text. It allows an `rm` without a prompt only for a narrow set of shapes it can prove safe: an `rm` whose every target is an absolute path under the OS temp root, `/tmp`, `/temp`, or a worktrees directory — standalone, or in a chain whose other segments are plain reporting commands such as `echo` or `cat`; an `rm` run from an ephemeral working directory; and an `rm` whose every target sits inside `~/.claude`. It falls through to a permission prompt on anything outside that set: a `$`, `$(...)`, or backtick expansion whose value it cannot resolve, a target it cannot place in a safe directory, a glob basename, or a string-executing wrapper (`bash -c 'rm -rf …'`). In a background or auto-mode run no human can answer that prompt, so the call stalls.
|
|
4
|
-
|
|
5
|
-
Remove files with these forms, which the hook never prompts on:
|
|
6
|
-
|
|
7
|
-
- **Scratch and probe files:** the PowerShell tool — `Remove-Item -Recurse -Force -Confirm:$false <absolute path>`. The hook watches only the Bash tool, so a PowerShell removal never reaches it. A file left in the OS temp dir or `$CLAUDE_JOB_DIR/tmp` is ephemeral and needs no explicit removal.
|
|
8
|
-
- **Worktrees:** `git worktree remove --force <path>`. This matches no destructive pattern.
|
|
9
|
-
- **When bash `rm` is unavoidable:** one standalone `rm` command, an absolute literal path under the OS temp root or a worktrees directory, no chaining, no variables, no globs. The hook auto-allows this shape without a prompt.
|
|
10
|
-
|
|
11
|
-
## Every subagent prompt carries the rule
|
|
12
|
-
|
|
13
|
-
A prompt-delivered directive reaches only the agent that gets it. An agent that spawns its own workers — review lenses, fix agents, verifiers — copies this line into every subagent prompt it issues, so a grandchild cleaning up its own probe file uses an allowed form:
|
|
14
|
-
|
|
15
|
-
> Never use bash rm in any form. Delete scratch/probe files with the PowerShell tool (Remove-Item -Recurse -Force -Confirm:$false <absolute path>), or leave them in the OS temp dir; remove worktrees only via git worktree remove --force.
|
|
16
|
-
|
|
17
|
-
Prefer that a child leaves its scratch files in place for the parent to remove at teardown with `Remove-Item`.
|
|
18
|
-
|
|
19
|
-
## Sibling rules
|
|
20
|
-
|
|
21
|
-
- [`no-inline-destructive-literals`](no-inline-destructive-literals.md) — keep a destructive literal out of the Bash command string even when it rides only as data.
|
|
22
|
-
- [`cleanup-temp-files`](cleanup-temp-files.md) — remove the scratch files a task created once the task is done.
|
|
23
|
-
- [`windows-filesystem-safe`](windows-filesystem-safe.md) — the safe `rmtree` / `force_rmtree` patterns for read-only Windows files.
|
package/rules/code-reviews.md
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# Responding to Code Reviews
|
|
2
|
-
|
|
3
|
-
**When this applies:** GitHub PR review feedback on a branch you are fixing.
|
|
4
|
-
|
|
5
|
-
**MANDATORY PROTOCOL:**
|
|
6
|
-
|
|
7
|
-
1. Fetch ALL reviewer comments BEFORE any fixes
|
|
8
|
-
2. Create TodoWrite checklist - One item per comment
|
|
9
|
-
3. Fix systematically - Mark each todo complete
|
|
10
|
-
4. Reply to EACH comment inline
|
|
11
|
-
5. Create ONE review fix commit - DO NOT squash with original
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
paths:
|
|
3
|
-
- "**/*.md"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Env-Var Summary Table Names a Code File That Reads the Variable
|
|
7
|
-
|
|
8
|
-
Every row in an env-var summary table pairs an UPPER_SNAKE variable with a code-file path that reads it — written as `` | `GOOGLE_APPLICATION_CREDENTIALS` | `auth/google_auth.py` | ... | ``. When a code change removes the last read of a variable from a file, the same change drops or corrects the table row that names that file.
|
|
9
|
-
|
|
10
|
-
`env_var_table_code_drift_blocker.py` (PreToolUse on Write|Edit|MultiEdit of `.md`) blocks a row whose named code file exists yet never references the variable, and names the fix. For an Edit, drift a file already held on an untouched row is excluded; a row whose code file resolves nowhere stays quiet (the hook cannot prove the drift).
|
package/rules/gh-body-file.md
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
# gh --body-file Rule
|
|
2
|
-
|
|
3
|
-
Every `gh` command carrying markdown body content (`gh pr create/edit/comment/review`, `gh issue create/edit/comment`) uses `--body-file <path>` with a temp file — never a `--body`/`-b` string, where backticks land on GitHub as literal `\``. Write the temp file BOM-free: `[IO.File]::WriteAllText($bodyPath, $body, [Text.UTF8Encoding]::new($false))`. MCP GitHub tools take `body` as a structured parameter and are unaffected.
|
|
4
|
-
|
|
5
|
-
`gh_body_arg_blocker.py` (PreToolUse on Bash) blocks `--body <arg>` and returns the corrective message.
|
package/rules/gh-paginate.md
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
# gh API Pagination
|
|
2
|
-
|
|
3
|
-
Every `gh api` read of a paginated GitHub list endpoint (PR `reviews`/`comments`/`files`, issue `comments`, `pulls`, `issues`) uses `--paginate --slurp` piped to **external** `jq` — `gh`'s built-in `--jq` runs per page, so cross-page operations like `sort_by | last` give wrong-but-confident results. Single-object endpoints (`pulls/<n>`, `issues/<n>`) skip pagination and may use `--jq` directly. For a newest-first walk, sort the slurped array and take the last element; for single-page bounds, cap with a `per_page` query parameter.
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
paths: **/hooks/**/*.py
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# Hook Prose Matches Its Detector
|
|
6
|
-
|
|
7
|
-
A hook's docstring lead narrative and its `CORRECTIVE_MESSAGE` describe exactly the shapes the detector flags — no broader trigger surface than the regex enforces.
|
|
8
|
-
|
|
9
|
-
`hook_prose_detector_consistency` (PreToolUse on Write|Edit of hook modules and `*_constants.py` companions) blocks prose that claims a trigger the detector never fires on, and names the fix.
|
|
10
|
-
|
|
11
|
-
## Judgment
|
|
12
|
-
|
|
13
|
-
After writing a hook, ask: would a token that matches every word of this message actually trip the detector? When the message names a shape the regex skips, rewrite the message to name only what the regex catches.
|
|
14
|
-
|
|
15
|
-
The path-shape case is the common overstatement: a detector that keys off a path separator must not claim it blocks an "output-key segment". The corrective message spells the rewrite.
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
paths:
|
|
3
|
-
- "**/*.md"
|
|
4
|
-
- "**/*.py"
|
|
5
|
-
- "**/*.mjs"
|
|
6
|
-
- "**/*.js"
|
|
7
|
-
- "**/*.ts"
|
|
8
|
-
- "**/*.ps1"
|
|
9
|
-
- "**/*.sh"
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
# No Historical Clutter in Documentation or Comments
|
|
13
|
-
|
|
14
|
-
Never reference removed implementations, old defaults, prior behaviors, or earlier contracts when updating documentation or comments. The current state is all that matters. A module or function docstring carries the same describe-current-state-only contract as a `.md` file.
|
|
15
|
-
|
|
16
|
-
`state_description_blocker` (PreToolUse on Write|Edit) blocks historical and comparative phrases in `.md` prose, code comments, and Python docstrings; a phrase wrapped in double quotes or backticks inside a docstring counts as a mention and is skipped. The denial names the matched phrases and shows a rewrite example.
|
|
17
|
-
|
|
18
|
-
## What stays allowed
|
|
19
|
-
|
|
20
|
-
- Comparisons to alternatives that still exist (for example, "use `--paginate --slurp | jq`, not `--jq` alone")
|
|
21
|
-
- Rationale that explains why a pattern is wrong in terms of present behavior (for example, "`--jq` runs per-page, so cross-page operations produce wrong results")
|
|
22
|
-
- References to external sources for defects that still exist (for example, gh CLI #10459)
|
|
23
|
-
|
|
24
|
-
## The test
|
|
25
|
-
|
|
26
|
-
After writing, ask: if someone reads this a year from now with no knowledge of earlier states, does every sentence still make sense and add value? If a sentence only helps someone who knew an earlier state, delete it.
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
# No Inline Destructive-Command Literals in Bash
|
|
2
|
-
|
|
3
|
-
The `destructive_command_blocker` PreToolUse hook matches destructive patterns (`rm -rf`, `git reset --hard`, `dd`, `mkfs`, `chmod -R`, fork bombs) as raw text anywhere in a Bash-tool command, with no quote-awareness — so a destructive literal carried only as data (a commit message, a PR/issue body, an echoed string, a `python -c` / `node -e` / `awk` argument, a heredoc) trips the confirmation prompt even though the shell never executes it. In a background or auto-mode run no human can answer that prompt, so the call stalls.
|
|
4
|
-
|
|
5
|
-
Keep destructive literals out of the Bash command string:
|
|
6
|
-
|
|
7
|
-
- Bodies that describe destructive-command behavior go in a file passed by path — `git commit -F <file>`, `gh ... --body-file <file>` (see [`gh-body-file`](gh-body-file.md)) — never `git commit -m` / `gh ... -b`.
|
|
8
|
-
- To exercise or verify the blocker (or any hook), run the committed test suite (`python -m pytest <test_file>`), which passes the command strings as in-language data — never an inline `python -c` harness.
|
|
9
|
-
- Genuine cleanup targets the OS temp dir or `$CLAUDE_JOB_DIR/tmp` (auto-allowed as ephemeral), never a repository or worktree path.
|