claude-dev-env 2.4.0 → 2.7.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.md +26 -59
- package/_shared/pr-loop/scripts/_claude_permissions_common.py +84 -0
- package/_shared/pr-loop/scripts/code_rules_gate.py +6 -3
- 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/grant_project_claude_permissions.py +306 -306
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/claude_permissions_constants.py +44 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +202 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/copilot_quota_constants.py +24 -24
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/stale_worktree_rule_sweep_constants.py +107 -107
- package/_shared/pr-loop/scripts/revoke_project_claude_permissions.py +290 -48
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +42 -2
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_constants.py +36 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +100 -1
- package/_shared/pr-loop/scripts/tests/test_fix_hookspath.py +497 -497
- package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +311 -2
- package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep.py +301 -301
- package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep_constants.py +85 -85
- package/_shared/pr-loop/worker-spawn.md +1 -1
- package/agents/CLAUDE.md +3 -2
- package/agents/caveman.md +0 -1
- package/agents/clasp-deployment-orchestrator.md +0 -1
- package/agents/clean-coder.md +0 -1
- package/agents/code-advisor.md +0 -1
- package/agents/code-quality-agent.md +1 -2
- package/agents/code-verifier.md +36 -8
- package/agents/deep-research.md +0 -1
- package/agents/docs-agent.md +0 -1
- package/agents/git-commit-crafter.md +0 -1
- package/agents/issue-tracker.md +42 -0
- package/agents/plan-packet-validator.md +0 -1
- package/agents/pr-description-writer.md +0 -1
- package/agents/test_agent_frontmatter.py +67 -18
- package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +143 -141
- package/bin/CLAUDE.md +68 -5
- package/bin/codex-compat.mjs +104 -0
- package/bin/codex-compat.test.mjs +51 -0
- package/bin/ever-shipped-skills.mjs +1 -0
- package/bin/install-constants.mjs +88 -0
- package/bin/install.mjs +1138 -114
- package/bin/install.prune.test.mjs +869 -19
- package/bin/install.test.mjs +906 -2
- package/codex-capability-map.json +13 -0
- package/commands/implement.md +1 -1
- package/commands/right-size.md +1 -1
- package/docs/CLAUDE.md +1 -0
- package/docs/CODE_RULES.md +2 -0
- package/docs/codex-compatibility.md +25 -0
- package/docs/host-pool-health-monitor.md +102 -0
- package/docs/nas-ssh-invocation.md +96 -12
- package/docs/references/CLAUDE.md +4 -2
- package/docs/references/advisor-tool.md +13 -0
- package/docs/references/code-review-enforcement.md +35 -0
- package/docs/references/team-advisor-skill.md +14 -0
- package/hooks/blocking/CLAUDE.md +4 -0
- package/hooks/blocking/code_review_pr_create_gate.py +7 -3
- package/hooks/blocking/code_review_push_gate.py +9 -4
- package/hooks/blocking/code_review_stamp_directory_write_blocker.py +8 -0
- package/hooks/blocking/config/__init__.py +5 -5
- package/hooks/blocking/config/code_review_enforcement_constants.py +40 -7
- package/hooks/blocking/config/test_code_review_enforcement_constants.py +58 -0
- package/hooks/blocking/config/verified_commit_constants.py +160 -159
- 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/orchestrator_refresh_reschedule_gate.py +256 -0
- package/hooks/blocking/pre_tool_use_dispatcher.py +24 -24
- 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_code_review_pr_create_gate.py +14 -0
- package/hooks/blocking/test_code_review_push_gate.py +16 -0
- package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +19 -0
- package/hooks/blocking/test_eli11_reply_enforcer.py +457 -0
- package/hooks/blocking/test_orchestrator_refresh_reschedule_gate.py +231 -0
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +10 -1
- 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 +804 -808
- package/hooks/blocking/test_verification_verdict_store.py +54 -0
- package/hooks/blocking/test_verified_commit_gate.py +581 -581
- package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +131 -131
- package/hooks/blocking/unscoped_search_blocker.py +391 -0
- package/hooks/blocking/verdict_directory_write_blocker.py +687 -687
- package/hooks/blocking/verification_verdict_store.py +1039 -1036
- package/hooks/blocking/verified_commit_message_accuracy_blocker.py +167 -167
- package/hooks/blocking/verifier_verdict_minter.py +280 -280
- 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 +257 -23
- package/hooks/git-hooks/test_pre_push_base_reference.py +339 -0
- package/hooks/hooks.json +10 -12
- package/hooks/hooks_constants/CLAUDE.md +7 -2
- 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/enter_worktree_prefetch_constants.py +18 -18
- package/hooks/hooks_constants/nas_ssh_binary_enforcer_constants.py +2 -8
- package/hooks/hooks_constants/orchestrator_refresh_reschedule_gate_constants.py +48 -0
- package/hooks/hooks_constants/ruff_integration_constants.py +16 -0
- 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/hooks/lifecycle/enter_worktree_origin_prefetch.py +163 -146
- package/hooks/lifecycle/test_enter_worktree_origin_prefetch.py +185 -178
- package/hooks/pyproject.toml +1 -0
- package/hooks/validators/CLAUDE.md +1 -0
- package/hooks/validators/config/__init__.py +0 -0
- package/hooks/validators/config/directory_exemption_constants.py +183 -0
- package/hooks/validators/config/test_directory_exemption_constants.py +21 -0
- package/hooks/validators/conftest.py +4 -0
- package/hooks/validators/ruff_integration.py +49 -5
- package/hooks/validators/run_all_validators.py +206 -9
- package/hooks/validators/test_directory_exemption_constants.py +185 -0
- package/hooks/validators/test_python_antipattern_checks.py +110 -5
- package/hooks/validators/test_ruff_integration.py +92 -1
- package/hooks/validators/test_run_all_validators.py +115 -68
- package/hooks/validators/test_run_all_validators_pretooluse.py +159 -1
- package/package.json +13 -3
- package/rules/CLAUDE.md +17 -22
- 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 +53 -44
- 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 +1 -0
- package/scripts/Capture-PoolHealth.ps1 +410 -0
- package/scripts/Migrate-ShellPolicy.ps1 +1 -1
- package/scripts/_code_review_test_support.py +404 -0
- package/scripts/claude_chain_runner.py +141 -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/conftest.py +16 -1
- package/scripts/dev_env_scripts_constants/CLAUDE.md +1 -1
- package/scripts/dev_env_scripts_constants/claude_chain_constants.py +9 -0
- package/scripts/dev_env_scripts_constants/code_review_constants.py +37 -0
- package/scripts/invoke_code_review.py +11 -4
- package/scripts/resolve_worker_spawn.py +626 -626
- package/scripts/spawn_grok_batch.py +672 -672
- package/scripts/sync_to_cursor/rules.py +0 -10
- package/scripts/test_claude_chain_runner.py +131 -0
- package/scripts/test_invoke_code_review.py +85 -908
- package/scripts/test_invoke_code_review_chain.py +70 -0
- package/scripts/test_invoke_code_review_cli.py +192 -0
- package/scripts/test_invoke_code_review_contract.py +256 -0
- package/scripts/test_invoke_code_review_git.py +123 -0
- package/scripts/test_invoke_code_review_mode.py +99 -0
- package/scripts/test_resolve_worker_spawn.py +1014 -1014
- 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/CLAUDE.md +2 -0
- package/skills/auditing-claude-config/SKILL.md +114 -114
- package/skills/autoconverge/SKILL.md +427 -427
- package/skills/autoconverge/reference/convergence.md +24 -3
- package/skills/autoconverge/workflow/CLAUDE.md +1 -0
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +3 -3
- package/skills/autoconverge/workflow/converge.contract.test.mjs +1263 -1263
- package/skills/autoconverge/workflow/converge.mjs +168 -1
- package/skills/autoconverge/workflow/converge.p2-advance.test.mjs +202 -0
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a11d903476b803493.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a26213978adeef6fb.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a3def0d15ed9d9110.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a41f41b1b708ee3b7.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a758b880abecc3ff7.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a8897b89656b1bd16.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-abd463d744a1437bc.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ad19d027ae8ee1816.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +265 -265
- package/skills/bugteam/reference/copilot-gap-analysis.md +1 -1
- package/skills/closeout/SKILL.md +33 -50
- package/skills/codex-review/scripts/codex_review_scripts_constants/run_constants.py +8 -0
- package/skills/codex-review/scripts/run_codex_review.py +233 -1
- package/skills/codex-review/scripts/test_run_codex_review.py +189 -0
- package/skills/condensing-instructions/SKILL.md +72 -0
- package/skills/copilot-review/SKILL.md +119 -119
- package/skills/e-code-review/SKILL.md +52 -0
- package/skills/e-code-review/reference/fix.md +54 -0
- package/skills/e-code-review/reference/loop.md +43 -0
- package/skills/e-code-review/reference/low.md +57 -0
- package/skills/e-code-review/reference/medium.md +153 -0
- package/skills/e-code-review/reference/xhigh.md +182 -0
- package/skills/e-simplify/SKILL.md +97 -0
- 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/issue-tracker/SKILL.md +92 -0
- package/skills/issue-tracker/reference/epic-and-sub-issue-model.md +55 -0
- package/skills/issue-tracker/reference/handoff-schema.md +64 -0
- package/skills/issue-tracker/reference/operation-matrix.md +41 -0
- package/skills/orchestrator/SKILL.md +177 -22
- package/skills/orchestrator/scripts/status_gate.py +625 -0
- package/skills/orchestrator/scripts/status_gate_constants/__init__.py +1 -0
- package/skills/orchestrator/scripts/status_gate_constants/config/__init__.py +1 -0
- package/skills/orchestrator/scripts/status_gate_constants/config/constants.py +47 -0
- package/skills/orchestrator/scripts/test_status_gate.py +439 -0
- package/skills/orchestrator-refresh/SKILL.md +129 -35
- package/skills/plan-to-pr/SKILL.md +155 -0
- package/skills/plan-to-pr/reference/final-validation-tasks.md +15 -0
- package/skills/plan-to-pr/reference/model-routing.md +36 -0
- package/skills/plan-to-pr/reference/packet-contract.md +43 -0
- package/skills/plan-to-pr/reference/packet-schema.json +57 -0
- package/skills/plan-to-pr/reference/process-inventory.md +22 -0
- package/skills/plan-to-pr/reference/review-loop.md +33 -0
- package/skills/plan-to-pr/reference/run-record.schema.json +27 -0
- package/skills/plan-to-pr/reference/self-audit-tasks.md +15 -0
- package/skills/plan-to-pr/reference/task-seeds.md +14 -0
- package/skills/plan-to-pr/reference/task-ticket.md +38 -0
- package/skills/plan-to-pr/scripts/config/__init__.py +1 -0
- package/skills/plan-to-pr/scripts/config/constants.py +193 -0
- package/skills/plan-to-pr/scripts/create_packet.py +173 -0
- package/skills/plan-to-pr/scripts/test_create_packet.py +102 -0
- package/skills/plan-to-pr/scripts/test_validate_packet.py +256 -0
- package/skills/plan-to-pr/scripts/test_validate_protocol.py +135 -0
- package/skills/plan-to-pr/scripts/test_validate_run.py +158 -0
- package/skills/plan-to-pr/scripts/validate_packet.py +655 -0
- package/skills/plan-to-pr/scripts/validate_protocol.py +622 -0
- package/skills/plan-to-pr/scripts/validate_run.py +173 -0
- package/skills/plan-to-pr/test_skill_contract.py +207 -0
- package/skills/plan-to-pr/test_task_ticket_contract.py +151 -0
- package/skills/pr-converge/SKILL.md +472 -469
- package/skills/pr-converge/reference/examples.md +3 -3
- package/skills/pr-converge/reference/fix-protocol.md +1 -1
- package/skills/pr-converge/reference/ground-rules.md +7 -4
- package/skills/pr-converge/reference/multi-pr-orchestration.md +4 -1
- package/skills/pr-converge/reference/per-tick.md +5 -5
- package/skills/pr-converge/reference/progress-checklist.md +1 -1
- package/skills/pr-converge/scripts/check_convergence_gates.py +279 -279
- package/skills/pr-converge/scripts/test_check_convergence_codex.py +507 -507
- package/skills/pr-converge/scripts/test_check_convergence_gates.py +84 -84
- package/skills/pr-converge/test_step5_host_branch.py +1 -1
- package/skills/pr-fix-protocol/SKILL.md +1 -1
- package/skills/privacy-hygiene/SKILL.md +68 -68
- package/skills/privacy-hygiene/reference/sweep-procedure.md +1 -1
- package/skills/prototype/workflows/promotion.md +1 -1
- package/skills/release-notes-html/SKILL.md +164 -0
- package/skills/session-log/SKILL.md +1 -1
- package/skills/task-build/CLAUDE.md +8 -7
- package/skills/task-build/SKILL.md +16 -8
- package/skills/task-build/reference/tool-routing.md +19 -0
- 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/tdd.md +0 -7
- package/skills/closeout/reference/issue-body-templates.md +0 -108
|
@@ -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()
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: issue-tracker
|
|
3
|
+
description: >-
|
|
4
|
+
File, update, and close GitHub work as one epic with native sub-issues.
|
|
5
|
+
Dedup open and closed issues first. Edit status only inside marker sections.
|
|
6
|
+
Refresh the epic checklist so it matches its children. Triggers: issue tracker,
|
|
7
|
+
file an issue, track this issue, open an epic, update the epic, close the issue,
|
|
8
|
+
refresh the epic checklist, attach a sub-issue.
|
|
9
|
+
argument-hint: "[issue action - file | update | close | refresh-epic | full handoff]"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Issue tracker
|
|
13
|
+
|
|
14
|
+
**User wants an issue action done. Here is how those actions work.**
|
|
15
|
+
|
|
16
|
+
**dedup -> do the work -> markers only -> numbers + URLs**
|
|
17
|
+
|
|
18
|
+
Run every step the ask needs in one go (file, label, attach, refresh, and so on). The `issue-tracker` agent is the primary handler for one action per turn; this skill is the session path when the agent is unavailable or the ask spans several steps.
|
|
19
|
+
|
|
20
|
+
Prefer the **same warm** `issue-tracker` agent for follow-ups on the same issue or a related issue on the same epic. Spawn a new agent only for an unrelated work-stream or when the warm agent is gone.
|
|
21
|
+
|
|
22
|
+
## Voice
|
|
23
|
+
|
|
24
|
+
Use the `plain-brief` output style (`output-styles/plain-brief.md`). End with the issue number(s) and URL(s) the caller must keep.
|
|
25
|
+
|
|
26
|
+
## Gotchas
|
|
27
|
+
|
|
28
|
+
Append a bullet when an action fails in a new way.
|
|
29
|
+
|
|
30
|
+
- Use REST `.id`, not display `#N`, to attach a sub-issue - [operation-matrix](reference/operation-matrix.md).
|
|
31
|
+
- `gh` attach: `-F sub_issue_id=<n>` (typed int), never `-f`.
|
|
32
|
+
- Status goes in markers, not comments - comments are cross-links only.
|
|
33
|
+
- Dedup open **and** closed; closed twin -> reopen vs file new, never silent refile.
|
|
34
|
+
- Edit only between markers - free-form epic edits break the children checklist.
|
|
35
|
+
- Auto-close is PR-body-first: a related-only UI link does not close the issue on merge.
|
|
36
|
+
|
|
37
|
+
## The model
|
|
38
|
+
|
|
39
|
+
One **epic** issue owns a work-stream. Each unit of work is a native GitHub **sub-issue** under it. The epic checklist and every status block live inside marker pairs. Skeletons and refresh steps: [reference/epic-and-sub-issue-model.md](reference/epic-and-sub-issue-model.md).
|
|
40
|
+
|
|
41
|
+
## Labels
|
|
42
|
+
|
|
43
|
+
- Parent: `epic`.
|
|
44
|
+
- Sub-issue: `type: roadblock`, `type: task`, `type: bug`, or `type: enhancement`.
|
|
45
|
+
|
|
46
|
+
Create a missing label, then apply it. Label create is in the operation matrix.
|
|
47
|
+
|
|
48
|
+
## Markers
|
|
49
|
+
|
|
50
|
+
- Status (any issue): `<!-- issue-tracker:status -->` … `<!-- /issue-tracker:status -->`
|
|
51
|
+
- Children checklist (epic only): `<!-- issue-tracker:children -->` … `<!-- /issue-tracker:children -->`
|
|
52
|
+
|
|
53
|
+
Update path: read body -> replace only the text between the target pair -> write the full body back.
|
|
54
|
+
|
|
55
|
+
## Dedup
|
|
56
|
+
|
|
57
|
+
Search open and closed issues on the target repo before create. Open twin -> update in place. Closed twin -> ask reopen vs file new.
|
|
58
|
+
|
|
59
|
+
## Tools
|
|
60
|
+
|
|
61
|
+
Prefer GitHub MCP. Fall back to `gh` on the same REST endpoints. Full map and `.id` rule: [reference/operation-matrix.md](reference/operation-matrix.md).
|
|
62
|
+
|
|
63
|
+
## Handoff input
|
|
64
|
+
|
|
65
|
+
Optional issue-candidate JSON from orchestrator or closeout. Full schema and consume path: [reference/handoff-schema.md](reference/handoff-schema.md).
|
|
66
|
+
|
|
67
|
+
## Close the sub-issue through the PR (required when a fix ships)
|
|
68
|
+
|
|
69
|
+
GitHub closes the sub-issue when someone **merges** a pull request into the **default branch** and that PR carries a closing keyword for the issue.
|
|
70
|
+
|
|
71
|
+
**Required on the PR that merges into the default branch:** put `Closes #N` in the PR body for each finished sub-issue. One keyword per issue: write `Closes #12` and `Closes #13`, not `Closes #12, #13` (only the first number closes in the comma form).
|
|
72
|
+
|
|
73
|
+
**Preferred on commits:** also put `Closes #N` in the first commit message when the fix starts. Commit alone is backup; the PR body is the contract.
|
|
74
|
+
|
|
75
|
+
**Stacked / intermediate PRs** that do not merge into the default branch: use a plain `#N` reference in the body so the issue links without a false close promise. Put `Closes #N` only on the PR that actually lands on the default branch.
|
|
76
|
+
|
|
77
|
+
**Not enough:** a Development "related" link with no closing keyword. **Not enough:** closing the PR without merging.
|
|
78
|
+
|
|
79
|
+
**Who owns `#N`:** the agent or session writing the PR must use the correct sub-issue number. Nothing in this package verifies the number matches the fix.
|
|
80
|
+
|
|
81
|
+
## Return shape
|
|
82
|
+
|
|
83
|
+
Reply with every affected issue number and URL - no narration after that payload.
|
|
84
|
+
|
|
85
|
+
## File index
|
|
86
|
+
|
|
87
|
+
| File | Purpose |
|
|
88
|
+
|------|---------|
|
|
89
|
+
| `SKILL.md` | Hub: how issue actions work, gotchas, model, markers, dedup, PR auto-close, return shape |
|
|
90
|
+
| `reference/operation-matrix.md` | MCP + `gh` per action; sub-issue `.id` rule |
|
|
91
|
+
| `reference/epic-and-sub-issue-model.md` | Epic model, markers, body skeleton, checklist refresh |
|
|
92
|
+
| `reference/handoff-schema.md` | Issue-candidate JSON and consume flow |
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Epic and sub-issue model
|
|
2
|
+
|
|
3
|
+
One **epic** issue holds a whole work-stream. Each unit of work under it is a native GitHub **sub-issue**. The epic carries the `epic` label; each sub-issue carries a `type:` label. The epic body mirrors its children as a checklist, and every issue — epic and sub-issue alike — carries a status section. Both sections live between marker comments so a body edit can replace one section and leave the rest untouched.
|
|
4
|
+
|
|
5
|
+
## The two marker pairs
|
|
6
|
+
|
|
7
|
+
- `<!-- issue-tracker:status -->` … `<!-- /issue-tracker:status -->` — on every issue. Holds the current status line.
|
|
8
|
+
- `<!-- issue-tracker:children -->` … `<!-- /issue-tracker:children -->` — on the epic only. Holds the checklist of sub-issues.
|
|
9
|
+
|
|
10
|
+
An edit reads the whole body, swaps only the text between one marker pair, and writes the whole body back. Text outside the markers stays byte-for-byte the same.
|
|
11
|
+
|
|
12
|
+
## Epic body skeleton
|
|
13
|
+
|
|
14
|
+
```markdown
|
|
15
|
+
## Epic — <work-stream label>
|
|
16
|
+
|
|
17
|
+
<One sentence naming the work-stream and its goal.>
|
|
18
|
+
|
|
19
|
+
<!-- issue-tracker:status -->
|
|
20
|
+
Status: in progress — 1 of 3 children closed.
|
|
21
|
+
<!-- /issue-tracker:status -->
|
|
22
|
+
|
|
23
|
+
<!-- issue-tracker:children -->
|
|
24
|
+
- [x] owner/repo#12 — parser rejects an empty path
|
|
25
|
+
- [ ] owner/repo#13 — attach reads the display number
|
|
26
|
+
- [ ] owner/repo#14 — label create skips an existing label
|
|
27
|
+
<!-- /issue-tracker:children -->
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Sub-issue body skeleton
|
|
31
|
+
|
|
32
|
+
```markdown
|
|
33
|
+
## <sub-issue title>
|
|
34
|
+
|
|
35
|
+
<One sentence: what the work is.>
|
|
36
|
+
|
|
37
|
+
<!-- issue-tracker:status -->
|
|
38
|
+
Status: open.
|
|
39
|
+
<!-- /issue-tracker:status -->
|
|
40
|
+
|
|
41
|
+
## Detail
|
|
42
|
+
|
|
43
|
+
<The evidence, where, impact, and proposed fix — self-contained.>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Refresh the epic checklist
|
|
47
|
+
|
|
48
|
+
After you create a sub-issue or close one, refresh the epic's `children` section so it matches the children:
|
|
49
|
+
|
|
50
|
+
1. Read the epic body.
|
|
51
|
+
2. Add a `- [ ] owner/repo#<N> — <title>` line for a newly created sub-issue, or flip a line to `- [x]` for a closed one.
|
|
52
|
+
3. Update the epic's status line to name the closed count against the total.
|
|
53
|
+
4. Write the whole body back with the issue-update op, replacing only the two marker sections you touched.
|
|
54
|
+
|
|
55
|
+
A created or closed sub-issue always triggers this refresh, so the epic checklist stays in step with the sub-issues at every step.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Handoff schema
|
|
2
|
+
|
|
3
|
+
An orchestrator or the closeout skill hands the tracker one **issue-candidate** JSON record per obstacle. The tracker consumes each record through the full path and returns the issue number and URL.
|
|
4
|
+
|
|
5
|
+
## The record
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"kind": "roadblock | task | bug | enhancement",
|
|
10
|
+
"title": "<short imperative title>",
|
|
11
|
+
"epic": "<work-stream label the sub-issue belongs under>",
|
|
12
|
+
"summary": "<one sentence a reader with zero session context acts on>",
|
|
13
|
+
"evidence": "<verbatim line captured this session — error text, command, or log line>",
|
|
14
|
+
"where": "<file, hook, gate, or tool the evidence names — path relative to the repo root>",
|
|
15
|
+
"impact": "<what the obstacle cost: work blocked, count of times hit, workaround forced>",
|
|
16
|
+
"proposed_fix": "<the specific change — name the failure mode and the condition>",
|
|
17
|
+
"blocking": true
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Field meanings
|
|
22
|
+
|
|
23
|
+
| Field | Meaning |
|
|
24
|
+
|-------|---------|
|
|
25
|
+
| `kind` | The sub-issue's `type:` label stem. `roadblock`, `task`, `bug`, or `enhancement`. |
|
|
26
|
+
| `title` | The sub-issue title. Short and imperative. |
|
|
27
|
+
| `epic` | The work-stream the sub-issue belongs under. The tracker finds or creates the matching epic. |
|
|
28
|
+
| `summary` | The one-sentence body opener. Self-contained — a reader with no session context acts on it. |
|
|
29
|
+
| `evidence` | A verbatim line captured this session, placed in a fenced block in the body. |
|
|
30
|
+
| `where` | The file, hook, gate, or tool the evidence names. A repo-relative path, no volatile temp path. |
|
|
31
|
+
| `impact` | What the obstacle cost. |
|
|
32
|
+
| `proposed_fix` | The specific change. |
|
|
33
|
+
| `blocking` | `true` routes the sub-issue to `type: roadblock`, overriding `kind`. `false` keeps the `kind` label. |
|
|
34
|
+
|
|
35
|
+
`kind` maps to the `type:` label. `blocking: true` takes precedence and routes the sub-issue to `type: roadblock`.
|
|
36
|
+
|
|
37
|
+
## Filled example
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"kind": "bug",
|
|
42
|
+
"title": "Inline-collection gate fires in exempt test files",
|
|
43
|
+
"epic": "hook exemptions for test files",
|
|
44
|
+
"summary": "The code_rules_enforcer inline-collection check blocks a valid list literal in a test file, where test files are exempt from the magic-value gate.",
|
|
45
|
+
"evidence": "BLOCKED: [MAGIC_VALUE] Inline list literal [200, 404, 500] in a function body -- extract to a named constant in config/.",
|
|
46
|
+
"where": "packages/claude-dev-env/hooks/blocking/code_rules_enforcer.py",
|
|
47
|
+
"impact": "Hit 3 times in one session on three test files; each forced a workaround move to a module constant.",
|
|
48
|
+
"proposed_fix": "Extend the magic-value test-file exemption to cover the inline-collection check, so list and set literals in test bodies pass.",
|
|
49
|
+
"blocking": false
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Consume flow
|
|
54
|
+
|
|
55
|
+
For each record, in order:
|
|
56
|
+
|
|
57
|
+
1. **Dedup** — search open and closed issues on the target repository for a twin.
|
|
58
|
+
2. **Find or create the epic** — match the `epic` label; open a parent with the `epic` label when none matches.
|
|
59
|
+
3. **Create the sub-issue** — body from `summary`, `evidence`, `where`, `impact`, `proposed_fix`.
|
|
60
|
+
4. **Labels** — apply the `type:` label from `kind`, or `type: roadblock` when `blocking` is `true`. Create the label when the repository lacks it.
|
|
61
|
+
5. **Attach the native sub-issue** — read the child's REST database `.id` and attach it under the epic.
|
|
62
|
+
6. **Refresh the epic checklist** — add the child's `- [ ] owner/repo#<N>` line and update the epic status.
|
|
63
|
+
|
|
64
|
+
The return is the sub-issue number and URL (and the epic number and URL when the epic was created this call).
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Operation matrix
|
|
2
|
+
|
|
3
|
+
Each issue op maps to a GitHub MCP tool and a `gh` fallback that reaches the same REST endpoint. Prefer the MCP tool; fall back to `gh` when the MCP server is unreachable. The REST endpoint column names the authoritative contract both surfaces call, so a session with neither surface can still drive the raw API.
|
|
4
|
+
|
|
5
|
+
The MCP tool names below match the connected GitHub MCP server's issue toolset. When a server exposes a tool under a different name, pick the tool by what the row describes, and the REST endpoint tells you what it must call.
|
|
6
|
+
|
|
7
|
+
| Operation | GitHub MCP tool | REST endpoint | `gh` fallback |
|
|
8
|
+
|-----------|-----------------|---------------|---------------|
|
|
9
|
+
| Search open + closed | `mcp__github__search_issues` | `GET /search/issues?q=<terms>+repo:{owner}/{repo}` | `gh issue list --search "<terms>" --state all` |
|
|
10
|
+
| Create epic / sub-issue | `mcp__github__issue_write` (create) | `POST /repos/{owner}/{repo}/issues` | `gh issue create --title "<t>" --body-file <path> --label <label>` |
|
|
11
|
+
| Read issue body | `mcp__github__issue_read` | `GET /repos/{owner}/{repo}/issues/{number}` | `gh issue view <number> --json body` |
|
|
12
|
+
| Update body / labels | `mcp__github__issue_write` (update) | `PATCH /repos/{owner}/{repo}/issues/{number}` | `gh issue edit <number> --body-file <path> --add-label <label>` |
|
|
13
|
+
| Attach native sub-issue | `mcp__github__sub_issue_write` | `POST /repos/{owner}/{repo}/issues/{number}/sub_issues` | `gh api repos/{owner}/{repo}/issues/{number}/sub_issues -F sub_issue_id=<int>` |
|
|
14
|
+
| Add cross-reference comment | `mcp__github__add_issue_comment` | `POST /repos/{owner}/{repo}/issues/{number}/comments` | `gh issue comment <number> --body-file <path>` |
|
|
15
|
+
| Create label | `mcp__github__` label-create tool | `POST /repos/{owner}/{repo}/labels` | `gh label create <name> --color <hex> --description "<text>"` |
|
|
16
|
+
|
|
17
|
+
## The sub-issue `.id` rule (read this before every attach)
|
|
18
|
+
|
|
19
|
+
The sub-issues endpoint identifies the child by its REST **database `.id`** — a large integer such as `2138472019` — not by its display number (`#42`). The two are different values. Passing the display number attaches nothing.
|
|
20
|
+
|
|
21
|
+
Read the child's `.id` from the create op's response, or fetch it:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
gh api repos/{owner}/{repo}/issues/{number} --jq .id
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`gh issue view <number> --json id` returns the GraphQL node id, a base64 string — the wrong value for this endpoint. Use `gh api ... --jq .id` for the REST database id.
|
|
28
|
+
|
|
29
|
+
The gh attach uses `-F` (typed field), which sends the id as a JSON integer. `-f` sends a string, and the endpoint rejects a string sub-issue id.
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
gh api repos/{owner}/{repo}/issues/{parent}/sub_issues -F sub_issue_id=2138472019
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Body content through `--body-file`
|
|
36
|
+
|
|
37
|
+
Every `gh issue create`, `gh issue edit`, and `gh issue comment` passes body content with `--body-file <path>`, never `--body "<text>"`. A `--body` string mangles backticks on GitHub — they land as literal `\``. Write the body to a temp file and point `--body-file` at it.
|
|
38
|
+
|
|
39
|
+
## Paginated reads
|
|
40
|
+
|
|
41
|
+
`gh issue list` needs no pagination flags. For a `gh api` read of a paginated list endpoint (an issue's comments, a repository's issues), pass `--paginate --slurp` and pipe to external `jq` — `gh`'s built-in `--jq` runs per page and gives wrong cross-page results.
|