capt-hook 6.4.0__tar.gz → 6.5.0__tar.gz

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.
Files changed (111) hide show
  1. capt_hook-6.5.0/PKG-INFO +146 -0
  2. capt_hook-6.5.0/README.md +98 -0
  3. capt_hook-6.5.0/captain_hook/packs/general/capt-hook.toml +5 -0
  4. capt_hook-6.5.0/captain_hook/packs/general/detours.py +151 -0
  5. {capt_hook-6.4.0 → capt_hook-6.5.0}/pyproject.toml +2 -2
  6. capt_hook-6.4.0/PKG-INFO +0 -137
  7. capt_hook-6.4.0/README.md +0 -89
  8. capt_hook-6.4.0/captain_hook/packs/general/capt-hook.toml +0 -5
  9. {capt_hook-6.4.0 → capt_hook-6.5.0}/LICENSE +0 -0
  10. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/.claude-plugin/plugin.json +0 -0
  11. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/__init__.py +0 -0
  12. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/__main__.py +0 -0
  13. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/app.py +0 -0
  14. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/ast_grep.py +0 -0
  15. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/classifiers/__init__.py +0 -0
  16. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/classifiers/conductor.py +0 -0
  17. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/classifiers/droid.py +0 -0
  18. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/classifiers/native.py +0 -0
  19. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/cli.py +0 -0
  20. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/command.py +0 -0
  21. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/conditions.py +0 -0
  22. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/context.py +0 -0
  23. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/contexts.py +0 -0
  24. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/decisions.py +0 -0
  25. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/dispatch.py +0 -0
  26. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/durable.py +0 -0
  27. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/events.py +0 -0
  28. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/fields.py +0 -0
  29. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/file.py +0 -0
  30. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/loader.py +0 -0
  31. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/log.py +0 -0
  32. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/packs/__init__.py +0 -0
  33. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/packs/general/__init__.py +0 -0
  34. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/packs/general/_lib.py +0 -0
  35. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/packs/general/commands.py +0 -0
  36. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/packs/general/docs.py +0 -0
  37. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/packs/general/models.py +0 -0
  38. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/packs/general/plans.py +0 -0
  39. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/packs/general/prompts.py +0 -0
  40. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/packs/general/review.py +0 -0
  41. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/packs/general/tasks.py +0 -0
  42. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/packs/general/tombstones.py +0 -0
  43. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/packs/go/__init__.py +0 -0
  44. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/packs/go/capt-hook.toml +0 -0
  45. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/packs/go/testing.py +0 -0
  46. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/packs/go/toolchain.py +0 -0
  47. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/packs/manager.py +0 -0
  48. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/packs/python/__init__.py +0 -0
  49. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/packs/python/capt-hook.toml +0 -0
  50. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/packs/python/style.py +0 -0
  51. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/packs/python/testing.py +0 -0
  52. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/packs/python/toolchain.py +0 -0
  53. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/packs/steering/__init__.py +0 -0
  54. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/packs/steering/capt-hook.toml +0 -0
  55. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/packs/steering/steering.py +0 -0
  56. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/primitives/__init__.py +0 -0
  57. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/primitives/commands.py +0 -0
  58. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/primitives/lint.py +0 -0
  59. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/primitives/llm.py +0 -0
  60. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/primitives/nudge.py +0 -0
  61. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/primitives/rewrite.py +0 -0
  62. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/primitives/workflow.py +0 -0
  63. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/prompt.py +0 -0
  64. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/py.typed +0 -0
  65. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/review/__init__.py +0 -0
  66. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/review/cli.py +0 -0
  67. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/review/dashboard.py +0 -0
  68. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/review/fix.py +0 -0
  69. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/review/formats.py +0 -0
  70. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/review/judge.py +0 -0
  71. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/review/pipeline.py +0 -0
  72. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/review/repo.py +0 -0
  73. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/review/scan.py +0 -0
  74. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/review/settings.py +0 -0
  75. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/review/store.py +0 -0
  76. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/review/sync.py +0 -0
  77. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/session.py +0 -0
  78. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/settings.py +0 -0
  79. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/signals/__init__.py +0 -0
  80. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/signals/nlp.py +0 -0
  81. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/skills/authoring-hooks/SKILL.md +0 -0
  82. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/skills/authoring-hooks/references/capt-hook-api.md +0 -0
  83. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/skills/authoring-hooks/references/pattern-catalog.md +0 -0
  84. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/skills/authoring-hooks/references/pitfalls.md +0 -0
  85. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/skills/authoring-hooks/references/testing-hooks.md +0 -0
  86. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/skills/bootstrapping-hooks/SKILL.md +0 -0
  87. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/skills/scanning-sessions/SKILL.md +0 -0
  88. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/skills/scanning-sessions/references/pr-workflow.md +0 -0
  89. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/skills/scanning-sessions/references/review-cli.md +0 -0
  90. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/skills/translating-styleguides/SKILL.md +0 -0
  91. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/skills/translating-styleguides/references/llm-rule-patterns.md +0 -0
  92. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/skills/translating-styleguides/references/matcher-reference.md +0 -0
  93. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/skills/translating-styleguides/references/tier-rubric.md +0 -0
  94. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/state.py +0 -0
  95. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/style/__init__.py +0 -0
  96. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/style/ast_grep.py +0 -0
  97. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/style/matchers.py +0 -0
  98. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/style/scope.py +0 -0
  99. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/style/types.py +0 -0
  100. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/tasks.py +0 -0
  101. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/templates/example_hook.py.tmpl +0 -0
  102. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/testing/__init__.py +0 -0
  103. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/testing/helpers.py +0 -0
  104. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/testing/session_cache.py +0 -0
  105. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/testing/types.py +0 -0
  106. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/types.py +0 -0
  107. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/util/__init__.py +0 -0
  108. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/util/fs.py +0 -0
  109. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/util/http.py +0 -0
  110. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/util/model_cache.py +0 -0
  111. {capt_hook-6.4.0 → capt_hook-6.5.0}/captain_hook/util/text.py +0 -0
@@ -0,0 +1,146 @@
1
+ Metadata-Version: 2.4
2
+ Name: capt-hook
3
+ Version: 6.5.0
4
+ Summary: Stop repeating yourself to Claude.
5
+ Keywords: claude,claude-code,hooks,llm,agents,guardrails,cli
6
+ Author: Yasyf Mohamedali
7
+ Author-email: Yasyf Mohamedali <yasyfm@gmail.com>
8
+ License-Expression: PolyForm-Noncommercial-1.0.0
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Topic :: Software Development :: Quality Assurance
17
+ Classifier: Topic :: Software Development :: Testing
18
+ Classifier: Typing :: Typed
19
+ Requires-Dist: cc-transcript>=7.1,<8
20
+ Requires-Dist: pydantic>=2.0
21
+ Requires-Dist: pydantic-settings>=2.0
22
+ Requires-Dist: tree-sitter>=0.24
23
+ Requires-Dist: tree-sitter-bash>=0.23
24
+ Requires-Dist: ast-grep-py>=0.39,<1
25
+ Requires-Dist: funcy>=2.0
26
+ Requires-Dist: spacy>=3.7
27
+ Requires-Dist: click>=8
28
+ Requires-Dist: rich>=13
29
+ Requires-Dist: orjsonl>=1.0
30
+ Requires-Dist: wn>=1.1.0
31
+ Requires-Dist: lazy-object-proxy>=1.12.0
32
+ Requires-Dist: filelock>=3
33
+ Requires-Dist: loguru>=0.7.3
34
+ Requires-Dist: spawnllm>=0.5.2,<0.6
35
+ Requires-Dist: pytest>=8.0 ; extra == 'dev'
36
+ Requires-Dist: pytest-asyncio>=0.24 ; extra == 'dev'
37
+ Requires-Dist: pyright>=1.1 ; extra == 'dev'
38
+ Requires-Dist: pyyaml>=6 ; extra == 'dev'
39
+ Requires-Dist: ruff>=0.8 ; extra == 'dev'
40
+ Requires-Python: >=3.13
41
+ Project-URL: Homepage, https://github.com/yasyf/captain-hook
42
+ Project-URL: Documentation, https://yasyf.github.io/captain-hook/
43
+ Project-URL: Repository, https://github.com/yasyf/captain-hook
44
+ Project-URL: Issues, https://github.com/yasyf/captain-hook/issues
45
+ Project-URL: Changelog, https://github.com/yasyf/captain-hook/blob/main/CHANGELOG.md
46
+ Provides-Extra: dev
47
+ Description-Content-Type: text/markdown
48
+
49
+ # ![captain-hook](https://github.com/yasyf/captain-hook/raw/main/docs/assets/readme-banner.webp)
50
+
51
+ **Stop repeating yourself to Claude.** captain-hook mines your transcripts for the corrections you keep giving and opens PRs that turn each one into a typed, tested Python hook.
52
+
53
+ [![CI](https://github.com/yasyf/captain-hook/actions/workflows/ci.yml/badge.svg)](https://github.com/yasyf/captain-hook/actions/workflows/ci.yml)
54
+ [![PyPI](https://img.shields.io/pypi/v/capt-hook)](https://pypi.org/project/capt-hook/)
55
+ [![License: PolyForm Noncommercial](https://img.shields.io/badge/license-PolyForm--Noncommercial--1.0.0-blue)](https://github.com/yasyf/captain-hook/blob/main/LICENSE)
56
+
57
+ ## Get started
58
+
59
+ ```bash
60
+ uvx capt-hook init
61
+ ```
62
+
63
+ `init` scaffolds `.claude/hooks/`, wires Claude Code's settings, and arms the session reviewer. One `block_command` later, a force-push dies at `PreToolUse` and the hook's inline tests run green:
64
+
65
+ <img src="https://github.com/yasyf/captain-hook/raw/main/docs/assets/demo.png" alt="Terminal running 'uvx capt-hook test' — a hook blocks git push --force and both inline tests pass" width="700">
66
+
67
+ Driving with an agent? Paste this:
68
+
69
+ ```text
70
+ /plugin marketplace add yasyf/captain-hook
71
+ /plugin install captain-hook@captain-hook
72
+ ```
73
+
74
+ <details>
75
+ <summary>Prefer a prompt over the plugin?</summary>
76
+
77
+ ```text
78
+ Run `uvx capt-hook init` in this repo, write one hook that blocks force-pushes,
79
+ and verify it with `uvx capt-hook test`. Read https://yasyf.github.io/captain-hook/
80
+ if you get stuck.
81
+ ```
82
+
83
+ </details>
84
+
85
+ ---
86
+
87
+ ## Use cases
88
+
89
+ ### Block force-push and rm -rf before they run
90
+
91
+ One bad Bash call rewrites shared history or eats a directory, and by the time you spot it in the transcript it already ran. Declare the block once, tests inline:
92
+
93
+ ```python
94
+ # .claude/hooks/safety.py
95
+ from captain_hook import Allow, Block, Input, block_command
96
+
97
+ block_command(
98
+ ["git", "push", "--force"],
99
+ reason="Force-pushing rewrites shared history",
100
+ hint="Use `git push --force-with-lease` instead",
101
+ tests={
102
+ Input(command="git push --force"): Block(),
103
+ Input(command="git push origin main"): Allow(),
104
+ },
105
+ )
106
+ ```
107
+
108
+ The next `git push --force` never executes: the agent sees `BLOCKED: Force-pushing rewrites shared history` plus the hint, and reaches for `--force-with-lease` instead. `rm -rf` is one more `block_command` away.
109
+
110
+ ### Turn repeated corrections into rules Claude can't forget
111
+
112
+ You've typed "use uv, not pip" in a dozen sessions, and session thirteen makes the same mistake. After `init`, the session reviewer reads each transcript as the session ends, keeps the corrections that are standing rules, and — once a pattern proves itself across sessions — opens a PR that codifies it as a hook. Watch the pipeline:
113
+
114
+ ```bash
115
+ uvx capt-hook status
116
+ ```
117
+
118
+ The dashboard lists every correction it's tracking, staged from first sighting to open PR. You review the PR like any other; merged hooks enforce the rule from then on.
119
+
120
+ ### Gate "done" until the tests actually pass
121
+
122
+ The agent declares victory while the suite is red. A Stop gate holds the line:
123
+
124
+ ```python
125
+ # .claude/hooks/quality.py
126
+ from captain_hook import RanCommand, TouchedFile, gate
127
+
128
+ gate(
129
+ "You edited Python files but never ran the tests. Run `uv run pytest` before finishing.",
130
+ only_if=[TouchedFile("**/*.py")],
131
+ skip_if=[RanCommand(r"\bpytest\b")],
132
+ )
133
+ ```
134
+
135
+ The agent can't end the turn until a pytest run shows up in the transcript, and the gate stands down on its own once one does.
136
+
137
+ ## More in the docs
138
+
139
+ - **Session reviewer** — the full corrections lifecycle, from transcript to merged hook PR — [guide](https://yasyf.github.io/captain-hook/docs/guide/session-reviewer.html)
140
+ - **Conditions** — typed filters over tools, files, commands, and transcript history — [guide](https://yasyf.github.io/captain-hook/docs/guide/conditions.html)
141
+ - **LLM hooks** — gate on a model's verdict when a regex can't decide — [guide](https://yasyf.github.io/captain-hook/docs/guide/llm-hooks.html)
142
+ - **Workflows** — multi-step Stop gates with artifact checks and checklists — [guide](https://yasyf.github.io/captain-hook/docs/guide/workflows.html)
143
+ - **Packs** — the shipped `general`, `python`, and `go` hook packs — [guide](https://yasyf.github.io/captain-hook/docs/guide/packs.html)
144
+ - **Testing** — run `uvx capt-hook test --json` in CI so a regressed hook fails the build — [guide](https://yasyf.github.io/captain-hook/docs/guide/testing.html)
145
+
146
+ Read the [docs](https://yasyf.github.io/captain-hook/) for the full guide. Licensed under [PolyForm Noncommercial 1.0.0](https://github.com/yasyf/captain-hook/blob/main/LICENSE), free for noncommercial use.
@@ -0,0 +1,98 @@
1
+ # ![captain-hook](https://github.com/yasyf/captain-hook/raw/main/docs/assets/readme-banner.webp)
2
+
3
+ **Stop repeating yourself to Claude.** captain-hook mines your transcripts for the corrections you keep giving and opens PRs that turn each one into a typed, tested Python hook.
4
+
5
+ [![CI](https://github.com/yasyf/captain-hook/actions/workflows/ci.yml/badge.svg)](https://github.com/yasyf/captain-hook/actions/workflows/ci.yml)
6
+ [![PyPI](https://img.shields.io/pypi/v/capt-hook)](https://pypi.org/project/capt-hook/)
7
+ [![License: PolyForm Noncommercial](https://img.shields.io/badge/license-PolyForm--Noncommercial--1.0.0-blue)](https://github.com/yasyf/captain-hook/blob/main/LICENSE)
8
+
9
+ ## Get started
10
+
11
+ ```bash
12
+ uvx capt-hook init
13
+ ```
14
+
15
+ `init` scaffolds `.claude/hooks/`, wires Claude Code's settings, and arms the session reviewer. One `block_command` later, a force-push dies at `PreToolUse` and the hook's inline tests run green:
16
+
17
+ <img src="https://github.com/yasyf/captain-hook/raw/main/docs/assets/demo.png" alt="Terminal running 'uvx capt-hook test' — a hook blocks git push --force and both inline tests pass" width="700">
18
+
19
+ Driving with an agent? Paste this:
20
+
21
+ ```text
22
+ /plugin marketplace add yasyf/captain-hook
23
+ /plugin install captain-hook@captain-hook
24
+ ```
25
+
26
+ <details>
27
+ <summary>Prefer a prompt over the plugin?</summary>
28
+
29
+ ```text
30
+ Run `uvx capt-hook init` in this repo, write one hook that blocks force-pushes,
31
+ and verify it with `uvx capt-hook test`. Read https://yasyf.github.io/captain-hook/
32
+ if you get stuck.
33
+ ```
34
+
35
+ </details>
36
+
37
+ ---
38
+
39
+ ## Use cases
40
+
41
+ ### Block force-push and rm -rf before they run
42
+
43
+ One bad Bash call rewrites shared history or eats a directory, and by the time you spot it in the transcript it already ran. Declare the block once, tests inline:
44
+
45
+ ```python
46
+ # .claude/hooks/safety.py
47
+ from captain_hook import Allow, Block, Input, block_command
48
+
49
+ block_command(
50
+ ["git", "push", "--force"],
51
+ reason="Force-pushing rewrites shared history",
52
+ hint="Use `git push --force-with-lease` instead",
53
+ tests={
54
+ Input(command="git push --force"): Block(),
55
+ Input(command="git push origin main"): Allow(),
56
+ },
57
+ )
58
+ ```
59
+
60
+ The next `git push --force` never executes: the agent sees `BLOCKED: Force-pushing rewrites shared history` plus the hint, and reaches for `--force-with-lease` instead. `rm -rf` is one more `block_command` away.
61
+
62
+ ### Turn repeated corrections into rules Claude can't forget
63
+
64
+ You've typed "use uv, not pip" in a dozen sessions, and session thirteen makes the same mistake. After `init`, the session reviewer reads each transcript as the session ends, keeps the corrections that are standing rules, and — once a pattern proves itself across sessions — opens a PR that codifies it as a hook. Watch the pipeline:
65
+
66
+ ```bash
67
+ uvx capt-hook status
68
+ ```
69
+
70
+ The dashboard lists every correction it's tracking, staged from first sighting to open PR. You review the PR like any other; merged hooks enforce the rule from then on.
71
+
72
+ ### Gate "done" until the tests actually pass
73
+
74
+ The agent declares victory while the suite is red. A Stop gate holds the line:
75
+
76
+ ```python
77
+ # .claude/hooks/quality.py
78
+ from captain_hook import RanCommand, TouchedFile, gate
79
+
80
+ gate(
81
+ "You edited Python files but never ran the tests. Run `uv run pytest` before finishing.",
82
+ only_if=[TouchedFile("**/*.py")],
83
+ skip_if=[RanCommand(r"\bpytest\b")],
84
+ )
85
+ ```
86
+
87
+ The agent can't end the turn until a pytest run shows up in the transcript, and the gate stands down on its own once one does.
88
+
89
+ ## More in the docs
90
+
91
+ - **Session reviewer** — the full corrections lifecycle, from transcript to merged hook PR — [guide](https://yasyf.github.io/captain-hook/docs/guide/session-reviewer.html)
92
+ - **Conditions** — typed filters over tools, files, commands, and transcript history — [guide](https://yasyf.github.io/captain-hook/docs/guide/conditions.html)
93
+ - **LLM hooks** — gate on a model's verdict when a regex can't decide — [guide](https://yasyf.github.io/captain-hook/docs/guide/llm-hooks.html)
94
+ - **Workflows** — multi-step Stop gates with artifact checks and checklists — [guide](https://yasyf.github.io/captain-hook/docs/guide/workflows.html)
95
+ - **Packs** — the shipped `general`, `python`, and `go` hook packs — [guide](https://yasyf.github.io/captain-hook/docs/guide/packs.html)
96
+ - **Testing** — run `uvx capt-hook test --json` in CI so a regressed hook fails the build — [guide](https://yasyf.github.io/captain-hook/docs/guide/testing.html)
97
+
98
+ Read the [docs](https://yasyf.github.io/captain-hook/) for the full guide. Licensed under [PolyForm Noncommercial 1.0.0](https://github.com/yasyf/captain-hook/blob/main/LICENSE), free for noncommercial use.
@@ -0,0 +1,5 @@
1
+ name = "general"
2
+ version = "0.10.0"
3
+ description = "Language-agnostic guards: git/command safety, doc & prompt nudges, task & plan discipline, code-review + docs-freshness gates, and a detour check-in nudge."
4
+ hooks = "."
5
+ nlp = true
@@ -0,0 +1,151 @@
1
+ from __future__ import annotations
2
+
3
+ from captain_hook import (
4
+ Allow,
5
+ Event,
6
+ Input,
7
+ Signal,
8
+ Signals,
9
+ Tool,
10
+ Warn,
11
+ llm_nudge,
12
+ )
13
+
14
+ llm_nudge(
15
+ """You are a senior engineer watching another engineer ("the agent") mid-task. You are
16
+ running in agent mode in the project's working directory, with read tools. Your one job:
17
+ decide whether the agent has veered onto an UNREQUESTED DETOUR — side work nobody asked
18
+ for — without checking in first.
19
+
20
+ Read first, judge second:
21
+ - The session transcript is rendered above inside `<transcript path="...">`; long content is
22
+ clipped (you'll see `…(+Nch)` markers). From the full history (prefer `cc-transcript
23
+ show`/`grep`; else read the file) establish: (1) what was actually ASKED — the user's
24
+ original request plus any later redirections or standing permissions — and (2) what the
25
+ agent is doing RIGHT NOW — the just-run tool call and the last few assistant messages.
26
+
27
+ Discriminator: the current work is either (a) the requested task, (b) a necessary
28
+ prerequisite of it (the task cannot land or be verified without it), or (c) authorized —
29
+ the user said some form of "also…", "while you're there…", "fix anything you find", or it
30
+ is a small stewardship fix inside code the task already touches. Any of those -> fire=false.
31
+ Otherwise — the agent noticed something adjacent and started acting on it without surfacing
32
+ it — fire=true.
33
+
34
+ Detour tells (lean fire=true): "while I'm here" / "might as well" / "let me also" followed
35
+ by edits to files the task doesn't need; fixing or refactoring code the request never
36
+ mentioned and the task doesn't depend on; a cleanup sweep starting mid-task; chasing a side
37
+ mystery at length while the requested work sits unfinished.
38
+
39
+ Do NOT fire when: the side work blocks the task (a broken build or failing test the change
40
+ trips over); it's a small stewardship fix in a file already being edited for the task; the
41
+ user pre-authorized it; the agent is gathering context it needs; or the agent already
42
+ surfaced the discovery and offered options instead of acting.
43
+
44
+ <examples>
45
+ <example fire="true">
46
+ Asked: "rename the config flag". Agent: "While I'm here, the retry logic in client.py looks
47
+ wrong — let me fix that too", then edits client.py.
48
+ Unrequested fix in a file the rename never touches.
49
+ </example>
50
+ <example fire="true">
51
+ Asked: "add a --json flag". Agent: "I also noticed the error handling is inconsistent
52
+ across commands; I'll clean that up as well", then starts a multi-file sweep.
53
+ Scope expansion nobody asked for, no options offered.
54
+ </example>
55
+ <example fire="false">
56
+ Asked: "fix the failing test". Agent: "The fixture helper it calls has the actual bug —
57
+ fixing that first."
58
+ A prerequisite: the requested fix cannot land without it.
59
+ </example>
60
+ <example fire="false">
61
+ User said "clean up anything you find along the way"; agent fixes a stale docstring in a
62
+ file it was already editing.
63
+ Pre-authorized stewardship.
64
+ </example>
65
+ <example fire="false">
66
+ Agent: "I noticed X while working on Y — options: (1) finish Y and file X as a follow-up,
67
+ (2) fix X now, (3) ignore it. Which?"
68
+ Already surfacing options instead of acting.
69
+ </example>
70
+ </examples>
71
+
72
+ When uncertain, return fire=false. A missed detour costs one review comment; a false alarm
73
+ on legitimate work teaches the agent to ignore this nudge. Fire only when the current action
74
+ is clearly outside both the request and its prerequisites. Put your reasoning (under 50
75
+ words, naming the detour and the requested task) in `reasoning`.""",
76
+ message=lambda r: (
77
+ f"This looks like a detour — side work nobody asked for. {r.reasoning} "
78
+ "Stop and check in before acting: say what you noticed, then propose 2-4 concrete "
79
+ "options (finish the task and file this as a follow-up; pause and fix it now; ignore "
80
+ "it) and let the user pick — or, if you are a delegated agent, stop and return early "
81
+ "with findings plus options for your orchestrator instead of improvising. "
82
+ "See: AGENTS.md § Ask Before Assuming."
83
+ ),
84
+ only_if=[Tool("Edit|Write|MultiEdit|NotebookEdit|Bash")],
85
+ events=Event.PostToolUse,
86
+ signals=Signals(
87
+ [
88
+ Signal(pattern=r"(?i)\bwhile (?:I'm|I am|we're|we are) (?:here|at it|in (?:here|there))\b", weight=2),
89
+ Signal(pattern=r"(?i)\bmight as well\b", weight=2),
90
+ Signal(pattern=r"(?i)\b(?:let me|I'll|I will) also\b", weight=2),
91
+ Signal(pattern=r"(?i)\bas a bonus\b", weight=2),
92
+ Signal(pattern=r"(?i)\bI (?:also )?noticed\b", weight=1),
93
+ Signal(pattern=r"(?i)\b(?:unrelated|a side note|tangent)\b", weight=1),
94
+ Signal(pattern=r"(?i)\bone more thing\b", weight=1),
95
+ Signal(pattern=r"(?i)\bquick(?:ly)? (?:fix|clean|tidy|refactor)\w*\b", weight=1),
96
+ ],
97
+ threshold=2,
98
+ window=8,
99
+ ),
100
+ tests={
101
+ Input(
102
+ file="client.py",
103
+ content="retry = 3\n",
104
+ transcript=[
105
+ {
106
+ "type": "assistant",
107
+ "message": {
108
+ "content": [
109
+ {"type": "text", "text": "While I'm here, the retry logic looks wrong — fixing it too."}
110
+ ]
111
+ },
112
+ }
113
+ ],
114
+ ): Warn(pattern="detour"),
115
+ Input(
116
+ command="./scripts/cleanup.sh",
117
+ transcript=[
118
+ {
119
+ "type": "assistant",
120
+ "message": {
121
+ "content": [
122
+ {"type": "text", "text": "I also noticed stale artifacts. One more thing to clean up."}
123
+ ]
124
+ },
125
+ }
126
+ ],
127
+ ): Warn(pattern="options"),
128
+ Input(
129
+ file="flags.py",
130
+ content="json_flag = True\n",
131
+ transcript=[
132
+ {
133
+ "type": "assistant",
134
+ "message": {"content": [{"type": "text", "text": "Implementing the requested --json flag now."}]},
135
+ }
136
+ ],
137
+ ): Allow(),
138
+ Input(
139
+ tool="Read",
140
+ file="client.py",
141
+ transcript=[
142
+ {
143
+ "type": "assistant",
144
+ "message": {
145
+ "content": [{"type": "text", "text": "While I'm here, might as well look at the retry logic."}]
146
+ },
147
+ }
148
+ ],
149
+ ): Allow(),
150
+ },
151
+ )
@@ -1,8 +1,8 @@
1
1
  [project]
2
2
  name = "capt-hook"
3
3
  # Inert sentinel — the real version is stamped from the release tag (uv version --frozen).
4
- version = "6.4.0"
5
- description = "Declarative hook framework for Claude Code"
4
+ version = "6.5.0"
5
+ description = "Stop repeating yourself to Claude."
6
6
  readme = "README.md"
7
7
  license = "PolyForm-Noncommercial-1.0.0"
8
8
  license-files = ["LICENSE"]
capt_hook-6.4.0/PKG-INFO DELETED
@@ -1,137 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: capt-hook
3
- Version: 6.4.0
4
- Summary: Declarative hook framework for Claude Code
5
- Keywords: claude,claude-code,hooks,llm,agents,guardrails,cli
6
- Author: Yasyf Mohamedali
7
- Author-email: Yasyf Mohamedali <yasyfm@gmail.com>
8
- License-Expression: PolyForm-Noncommercial-1.0.0
9
- License-File: LICENSE
10
- Classifier: Development Status :: 4 - Beta
11
- Classifier: Intended Audience :: Developers
12
- Classifier: Operating System :: OS Independent
13
- Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.13
15
- Classifier: Programming Language :: Python :: 3 :: Only
16
- Classifier: Topic :: Software Development :: Quality Assurance
17
- Classifier: Topic :: Software Development :: Testing
18
- Classifier: Typing :: Typed
19
- Requires-Dist: cc-transcript>=7.1,<8
20
- Requires-Dist: pydantic>=2.0
21
- Requires-Dist: pydantic-settings>=2.0
22
- Requires-Dist: tree-sitter>=0.24
23
- Requires-Dist: tree-sitter-bash>=0.23
24
- Requires-Dist: ast-grep-py>=0.39,<1
25
- Requires-Dist: funcy>=2.0
26
- Requires-Dist: spacy>=3.7
27
- Requires-Dist: click>=8
28
- Requires-Dist: rich>=13
29
- Requires-Dist: orjsonl>=1.0
30
- Requires-Dist: wn>=1.1.0
31
- Requires-Dist: lazy-object-proxy>=1.12.0
32
- Requires-Dist: filelock>=3
33
- Requires-Dist: loguru>=0.7.3
34
- Requires-Dist: spawnllm>=0.5.2,<0.6
35
- Requires-Dist: pytest>=8.0 ; extra == 'dev'
36
- Requires-Dist: pytest-asyncio>=0.24 ; extra == 'dev'
37
- Requires-Dist: pyright>=1.1 ; extra == 'dev'
38
- Requires-Dist: pyyaml>=6 ; extra == 'dev'
39
- Requires-Dist: ruff>=0.8 ; extra == 'dev'
40
- Requires-Python: >=3.13
41
- Project-URL: Homepage, https://github.com/yasyf/captain-hook
42
- Project-URL: Documentation, https://yasyf.github.io/captain-hook/
43
- Project-URL: Repository, https://github.com/yasyf/captain-hook
44
- Project-URL: Issues, https://github.com/yasyf/captain-hook/issues
45
- Project-URL: Changelog, https://github.com/yasyf/captain-hook/blob/main/CHANGELOG.md
46
- Provides-Extra: dev
47
- Description-Content-Type: text/markdown
48
-
49
- # captain-hook
50
-
51
- ![captain-hook banner](https://github.com/yasyf/captain-hook/raw/main/docs/assets/readme-banner.webp)
52
-
53
- [![PyPI](https://img.shields.io/pypi/v/capt-hook.svg)](https://pypi.org/project/capt-hook/)
54
- [![Python](https://img.shields.io/pypi/pyversions/capt-hook.svg)](https://pypi.org/project/capt-hook/)
55
- [![Docs](https://github.com/yasyf/captain-hook/actions/workflows/docs.yml/badge.svg)](https://yasyf.github.io/captain-hook/)
56
- [![License: PolyForm-Noncommercial-1.0.0](https://img.shields.io/badge/License-PolyForm--Noncommercial--1.0.0-blue.svg)](https://github.com/yasyf/captain-hook/blob/main/LICENSE)
57
-
58
- Guardrails for Claude Code, written as typed, testable data — and learned from the corrections you give Claude.
59
-
60
- A captain-hook hook is declarative Python: an event, some conditions, an action. Block a footgun before it runs, nudge the agent off a bad pattern, gate "done" until the tests pass. Then captain-hook closes the loop: it reads the corrections you give Claude as you work and opens pull requests that codify the durable ones as new hooks. You write the first few; it writes the rest.
61
-
62
- ## Install
63
-
64
- captain-hook needs no install — it runs through [uvx](https://docs.astral.sh/uv/). From your project root:
65
-
66
- ```bash
67
- uvx capt-hook init
68
- ```
69
-
70
- `init` scaffolds `.claude/hooks/`, wires Claude Code's settings, registers the captain-hook plugin so its skills install on workspace-trust, and arms the [session reviewer](#it-learns-from-your-corrections). Or do it all from a session. Run `/plugin marketplace add yasyf/captain-hook`, then ask Claude to "set up captain hook".
71
-
72
- ## Your first hook
73
-
74
- A hook is an event, some conditions, and an action. This one stops the agent from finishing a UI change it never looked at:
75
-
76
- ```python
77
- # .claude/hooks/visual_review.py
78
- from captain_hook import gate, TouchedFile, UsedSkill
79
-
80
- gate(
81
- "You edited UI files. Open them with agent-browser and verify they render before finishing.",
82
- only_if=[TouchedFile("**/src/routes/**", "**/src/components/**")],
83
- skip_if=[UsedSkill("agent-browser")],
84
- )
85
- ```
86
-
87
- `only_if` arms the gate only when UI files changed; `skip_if` stands it down once the agent has done the review. Conditions match tools, files, commands, and even which skills the agent used.
88
-
89
- ## It learns from your corrections
90
-
91
- Most hooks you'll never write by hand.
92
-
93
- The corrections you give Claude as you work are exactly the rules a hook should enforce: "never force-push", "use `uv`, not `pip`", "you weakened that test". Writing the hook by hand is friction you skip in the moment, so the **session reviewer** notices for you. When a session ends, it reads the transcript, finds the durable corrections and the hooks that misfired, judges which ones are standing rules and which are one-offs, and once a pattern proves itself across sessions, opens a pull request that adds the hook — or fixes the one that misfired. You review the PR like any other.
94
-
95
- It's on by default after `init`. Turn it off for a repo with `uvx capt-hook review disable`. The [session reviewer guide](https://yasyf.github.io/captain-hook/docs/guide/session-reviewer.html) covers the prerequisites (an authenticated `claude` and `gh`) and the `HOOKS_REVIEW_*` thresholds.
96
-
97
- ## Tested like code
98
-
99
- Every deterministic hook carries inline tests, so a broken hook fails like broken code:
100
-
101
- ```python
102
- # .claude/hooks/safety.py
103
- from captain_hook import Allow, Block, Input, block_command
104
-
105
- block_command(
106
- ["git", "stash"],
107
- reason="Use the team's VCS workflow for shelving changes",
108
- hint="Commit a WIP change instead of stashing",
109
- tests={
110
- Input(command="git stash"): Block(),
111
- Input(command="git status"): Allow(),
112
- },
113
- )
114
- ```
115
-
116
- Run them from your project root, where `--hooks` defaults to `.claude/hooks`:
117
-
118
- ```bash
119
- uvx capt-hook test
120
- ```
121
-
122
- Wire that into CI and you catch a broken hook the way you catch broken code.
123
-
124
- ## What it's for
125
-
126
- - Block footguns before they run on `PreToolUse`: force-push, `rm -rf`, package-manager traps.
127
- - Steer the agent with feedback that fires on the patterns it actually emits: repeated failures, weakened tests, missed conventions.
128
- - Hold the line on multi-step work with Stop gates and artifact checks, so the agent can't call it "done" before the tests run or the report's written.
129
- - Keep all of it testable; every hook ships with inline tests that run in CI.
130
-
131
- ## Docs
132
-
133
- [Read the docs](https://yasyf.github.io/captain-hook/) for the full guide to conditions, primitives, LLM hooks, workflows, state, and real-world patterns. To work on captain-hook itself, see the [development guide](https://yasyf.github.io/captain-hook/docs/development/).
134
-
135
- ## License
136
-
137
- Licensed under [PolyForm Noncommercial 1.0.0](LICENSE), free for noncommercial use.
capt_hook-6.4.0/README.md DELETED
@@ -1,89 +0,0 @@
1
- # captain-hook
2
-
3
- ![captain-hook banner](https://github.com/yasyf/captain-hook/raw/main/docs/assets/readme-banner.webp)
4
-
5
- [![PyPI](https://img.shields.io/pypi/v/capt-hook.svg)](https://pypi.org/project/capt-hook/)
6
- [![Python](https://img.shields.io/pypi/pyversions/capt-hook.svg)](https://pypi.org/project/capt-hook/)
7
- [![Docs](https://github.com/yasyf/captain-hook/actions/workflows/docs.yml/badge.svg)](https://yasyf.github.io/captain-hook/)
8
- [![License: PolyForm-Noncommercial-1.0.0](https://img.shields.io/badge/License-PolyForm--Noncommercial--1.0.0-blue.svg)](https://github.com/yasyf/captain-hook/blob/main/LICENSE)
9
-
10
- Guardrails for Claude Code, written as typed, testable data — and learned from the corrections you give Claude.
11
-
12
- A captain-hook hook is declarative Python: an event, some conditions, an action. Block a footgun before it runs, nudge the agent off a bad pattern, gate "done" until the tests pass. Then captain-hook closes the loop: it reads the corrections you give Claude as you work and opens pull requests that codify the durable ones as new hooks. You write the first few; it writes the rest.
13
-
14
- ## Install
15
-
16
- captain-hook needs no install — it runs through [uvx](https://docs.astral.sh/uv/). From your project root:
17
-
18
- ```bash
19
- uvx capt-hook init
20
- ```
21
-
22
- `init` scaffolds `.claude/hooks/`, wires Claude Code's settings, registers the captain-hook plugin so its skills install on workspace-trust, and arms the [session reviewer](#it-learns-from-your-corrections). Or do it all from a session. Run `/plugin marketplace add yasyf/captain-hook`, then ask Claude to "set up captain hook".
23
-
24
- ## Your first hook
25
-
26
- A hook is an event, some conditions, and an action. This one stops the agent from finishing a UI change it never looked at:
27
-
28
- ```python
29
- # .claude/hooks/visual_review.py
30
- from captain_hook import gate, TouchedFile, UsedSkill
31
-
32
- gate(
33
- "You edited UI files. Open them with agent-browser and verify they render before finishing.",
34
- only_if=[TouchedFile("**/src/routes/**", "**/src/components/**")],
35
- skip_if=[UsedSkill("agent-browser")],
36
- )
37
- ```
38
-
39
- `only_if` arms the gate only when UI files changed; `skip_if` stands it down once the agent has done the review. Conditions match tools, files, commands, and even which skills the agent used.
40
-
41
- ## It learns from your corrections
42
-
43
- Most hooks you'll never write by hand.
44
-
45
- The corrections you give Claude as you work are exactly the rules a hook should enforce: "never force-push", "use `uv`, not `pip`", "you weakened that test". Writing the hook by hand is friction you skip in the moment, so the **session reviewer** notices for you. When a session ends, it reads the transcript, finds the durable corrections and the hooks that misfired, judges which ones are standing rules and which are one-offs, and once a pattern proves itself across sessions, opens a pull request that adds the hook — or fixes the one that misfired. You review the PR like any other.
46
-
47
- It's on by default after `init`. Turn it off for a repo with `uvx capt-hook review disable`. The [session reviewer guide](https://yasyf.github.io/captain-hook/docs/guide/session-reviewer.html) covers the prerequisites (an authenticated `claude` and `gh`) and the `HOOKS_REVIEW_*` thresholds.
48
-
49
- ## Tested like code
50
-
51
- Every deterministic hook carries inline tests, so a broken hook fails like broken code:
52
-
53
- ```python
54
- # .claude/hooks/safety.py
55
- from captain_hook import Allow, Block, Input, block_command
56
-
57
- block_command(
58
- ["git", "stash"],
59
- reason="Use the team's VCS workflow for shelving changes",
60
- hint="Commit a WIP change instead of stashing",
61
- tests={
62
- Input(command="git stash"): Block(),
63
- Input(command="git status"): Allow(),
64
- },
65
- )
66
- ```
67
-
68
- Run them from your project root, where `--hooks` defaults to `.claude/hooks`:
69
-
70
- ```bash
71
- uvx capt-hook test
72
- ```
73
-
74
- Wire that into CI and you catch a broken hook the way you catch broken code.
75
-
76
- ## What it's for
77
-
78
- - Block footguns before they run on `PreToolUse`: force-push, `rm -rf`, package-manager traps.
79
- - Steer the agent with feedback that fires on the patterns it actually emits: repeated failures, weakened tests, missed conventions.
80
- - Hold the line on multi-step work with Stop gates and artifact checks, so the agent can't call it "done" before the tests run or the report's written.
81
- - Keep all of it testable; every hook ships with inline tests that run in CI.
82
-
83
- ## Docs
84
-
85
- [Read the docs](https://yasyf.github.io/captain-hook/) for the full guide to conditions, primitives, LLM hooks, workflows, state, and real-world patterns. To work on captain-hook itself, see the [development guide](https://yasyf.github.io/captain-hook/docs/development/).
86
-
87
- ## License
88
-
89
- Licensed under [PolyForm Noncommercial 1.0.0](LICENSE), free for noncommercial use.
@@ -1,5 +0,0 @@
1
- name = "general"
2
- version = "0.9.0"
3
- description = "Language-agnostic guards: git/command safety, doc & prompt nudges, task & plan discipline, and code-review + docs-freshness gates."
4
- hooks = "."
5
- nlp = true
File without changes
File without changes
File without changes
File without changes