capt-hook 7.0.0__tar.gz → 8.0.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 (114) hide show
  1. {capt_hook-7.0.0 → capt_hook-8.0.0}/PKG-INFO +1 -1
  2. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/general/capt-hook.toml +2 -2
  3. capt_hook-8.0.0/captain_hook/packs/general/comments.py +160 -0
  4. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/primitives/commands.py +1 -1
  5. {capt_hook-7.0.0 → capt_hook-8.0.0}/pyproject.toml +1 -1
  6. {capt_hook-7.0.0 → capt_hook-8.0.0}/LICENSE +0 -0
  7. {capt_hook-7.0.0 → capt_hook-8.0.0}/README.md +0 -0
  8. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/.claude-plugin/plugin.json +0 -0
  9. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/__init__.py +0 -0
  10. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/__main__.py +0 -0
  11. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/app.py +0 -0
  12. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/ast_grep.py +0 -0
  13. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/classifiers/__init__.py +0 -0
  14. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/classifiers/conductor.py +0 -0
  15. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/classifiers/droid.py +0 -0
  16. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/classifiers/native.py +0 -0
  17. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/cli.py +0 -0
  18. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/conditions.py +0 -0
  19. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/context.py +0 -0
  20. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/contexts.py +0 -0
  21. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/decisions.py +0 -0
  22. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/dispatch.py +0 -0
  23. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/durable.py +0 -0
  24. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/events.py +0 -0
  25. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/fields.py +0 -0
  26. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/file.py +0 -0
  27. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/loader.py +0 -0
  28. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/log.py +0 -0
  29. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/__init__.py +0 -0
  30. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/fixes/__init__.py +0 -0
  31. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/fixes/capt-hook.toml +0 -0
  32. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/fixes/teammate_permissions.py +0 -0
  33. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/general/__init__.py +0 -0
  34. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/general/_lib.py +0 -0
  35. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/general/commands.py +0 -0
  36. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/general/detours.py +0 -0
  37. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/general/docs.py +0 -0
  38. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/general/models.py +0 -0
  39. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/general/plans.py +0 -0
  40. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/general/prompts.py +0 -0
  41. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/general/review.py +0 -0
  42. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/general/tasks.py +0 -0
  43. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/general/tombstones.py +0 -0
  44. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/go/__init__.py +0 -0
  45. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/go/capt-hook.toml +0 -0
  46. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/go/testing.py +0 -0
  47. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/go/toolchain.py +0 -0
  48. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/manager.py +0 -0
  49. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/python/__init__.py +0 -0
  50. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/python/capt-hook.toml +0 -0
  51. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/python/style.py +0 -0
  52. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/python/testing.py +0 -0
  53. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/python/toolchain.py +0 -0
  54. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/steering/__init__.py +0 -0
  55. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/steering/capt-hook.toml +0 -0
  56. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/packs/steering/steering.py +0 -0
  57. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/primitives/__init__.py +0 -0
  58. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/primitives/lint.py +0 -0
  59. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/primitives/llm.py +0 -0
  60. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/primitives/nudge.py +0 -0
  61. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/primitives/permissions.py +0 -0
  62. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/primitives/rewrite.py +0 -0
  63. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/primitives/workflow.py +0 -0
  64. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/prompt.py +0 -0
  65. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/py.typed +0 -0
  66. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/review/__init__.py +0 -0
  67. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/review/cli.py +0 -0
  68. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/review/dashboard.py +0 -0
  69. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/review/fix.py +0 -0
  70. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/review/formats.py +0 -0
  71. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/review/judge.py +0 -0
  72. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/review/pipeline.py +0 -0
  73. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/review/repo.py +0 -0
  74. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/review/scan.py +0 -0
  75. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/review/settings.py +0 -0
  76. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/review/store.py +0 -0
  77. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/review/sync.py +0 -0
  78. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/session.py +0 -0
  79. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/settings.py +0 -0
  80. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/signals/__init__.py +0 -0
  81. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/signals/nlp.py +0 -0
  82. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/skills/authoring-hooks/SKILL.md +0 -0
  83. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/skills/authoring-hooks/references/capt-hook-api.md +0 -0
  84. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/skills/authoring-hooks/references/pattern-catalog.md +0 -0
  85. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/skills/authoring-hooks/references/pitfalls.md +0 -0
  86. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/skills/authoring-hooks/references/testing-hooks.md +0 -0
  87. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/skills/bootstrapping-hooks/SKILL.md +0 -0
  88. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/skills/scanning-sessions/SKILL.md +0 -0
  89. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/skills/scanning-sessions/references/pr-workflow.md +0 -0
  90. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/skills/scanning-sessions/references/review-cli.md +0 -0
  91. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/skills/translating-styleguides/SKILL.md +0 -0
  92. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/skills/translating-styleguides/references/llm-rule-patterns.md +0 -0
  93. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/skills/translating-styleguides/references/matcher-reference.md +0 -0
  94. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/skills/translating-styleguides/references/tier-rubric.md +0 -0
  95. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/state.py +0 -0
  96. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/style/__init__.py +0 -0
  97. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/style/ast_grep.py +0 -0
  98. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/style/matchers.py +0 -0
  99. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/style/scope.py +0 -0
  100. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/style/types.py +0 -0
  101. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/tasks.py +0 -0
  102. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/templates/example_hook.py.tmpl +0 -0
  103. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/testing/__init__.py +0 -0
  104. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/testing/helpers.py +0 -0
  105. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/testing/session_cache.py +0 -0
  106. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/testing/types.py +0 -0
  107. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/types.py +0 -0
  108. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/util/__init__.py +0 -0
  109. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/util/automode.py +0 -0
  110. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/util/fs.py +0 -0
  111. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/util/http.py +0 -0
  112. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/util/model_cache.py +0 -0
  113. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/util/proc.py +0 -0
  114. {capt_hook-7.0.0 → capt_hook-8.0.0}/captain_hook/util/text.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: capt-hook
3
- Version: 7.0.0
3
+ Version: 8.0.0
4
4
  Summary: Stop repeating yourself to Claude.
5
5
  Keywords: claude,claude-code,hooks,llm,agents,guardrails,cli
6
6
  Author: Yasyf Mohamedali
@@ -1,5 +1,5 @@
1
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."
2
+ version = "0.11.0"
3
+ description = "Language-agnostic guards: git/command safety, doc & prompt nudges, task & plan discipline, code-review + docs-freshness gates, a detour check-in nudge, and a verbose-comment nudge."
4
4
  hooks = "."
5
5
  nlp = true
@@ -0,0 +1,160 @@
1
+ """Discourage verbose comments: warn when an edit introduces a long comment or line-comment run.
2
+
3
+ Comments should be terse and used sparingly — names, types, and organization carry the
4
+ meaning. The one legitimate exception is documentation-generation comments (godoc, rustdoc,
5
+ docstrings), and even a long doc run trips this warn by design: the threshold is deliberately
6
+ strict so verbosity of any kind gets a nudge. Language-agnostic via the tree-sitter comment
7
+ kinds in :data:`~captain_hook.ast_grep.COMMENT_TYPES`; diff-based, so only comments the edit
8
+ *introduces* count — a pre-existing long comment re-saved unchanged never fires.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import TYPE_CHECKING
14
+
15
+ from captain_hook import (
16
+ Allow,
17
+ BaseHookEvent,
18
+ CustomCondition,
19
+ Event,
20
+ FileFixture,
21
+ Input,
22
+ Tool,
23
+ Warn,
24
+ nudge,
25
+ )
26
+ from captain_hook.ast_grep import introduced_comments, lang_for_path
27
+
28
+ if TYPE_CHECKING:
29
+ from collections.abc import Iterable
30
+
31
+ from captain_hook.ast_grep import Match
32
+
33
+ MAX_COMMENT_LINES = 4
34
+ MAX_COMMENT_CHARS = 300
35
+
36
+ # Fixtures for the inline tests — kept as module constants so each physical line stays short.
37
+ GO_NO_COMMENT = "package p\n\nfunc F() {}\n"
38
+ GO_ONE_LINE_DOC = "package p\n\n// F does a thing.\nfunc F() {}\n"
39
+ GO_THREE_LINE_DOC = (
40
+ "package p\n\n// F does a thing.\n// It handles the empty case.\n// Returns an error otherwise.\nfunc F() {}\n"
41
+ )
42
+ GO_LONG_RUN = (
43
+ "package p\n\nfunc F() {\n"
44
+ "\t// explanation line one goes here\n"
45
+ "\t// explanation line two goes here\n"
46
+ "\t// explanation line three is here\n"
47
+ "\t// explanation line four is here\n"
48
+ "\t// explanation line five is here\n"
49
+ "\t// explanation line six is here ok\n"
50
+ "\tx := 1\n}\n"
51
+ )
52
+ GO_EXISTING_OLD = "func F() {\n\t// aaa\n\t// bbb\n\t// ccc\n\t// ddd\n\t// eee\n\t// fff\n\tx := 1\n}\n"
53
+ GO_EXISTING_NEW = "func F() {\n\t// aaa\n\t// bbb\n\t// ccc\n\t// ddd\n\t// eee\n\t// fff\n\tx := 2\n}\n"
54
+ GO_WRITE_PLAIN = "package p\n\nvar x = 1\n"
55
+ GO_WRITE_RUN = (
56
+ "package p\n\n"
57
+ "// note one here\n// note two here\n// note three here\n"
58
+ "// note four here\n// note five here\n// note six here\n"
59
+ "var x = 1\n"
60
+ )
61
+ GO_WRITE_EXISTING_OLD = "package p\n\n// aaa\n// bbb\n// ccc\n// ddd\n// eee\n// fff\nvar x = 1\n"
62
+ GO_WRITE_EXISTING_NEW = "package p\n\n// aaa\n// bbb\n// ccc\n// ddd\n// eee\n// fff\nvar x = 2\n"
63
+ RS_LONG_BLOCK = "/*\n one\n two\n three\n four\n five\n six\n seven\n eight\n*/\nfn f() {}\n"
64
+ RS_LONG_DOC = (
65
+ "/// line one of the rustdoc here\n"
66
+ "/// line two of the rustdoc here\n"
67
+ "/// line three of the rustdoc\n"
68
+ "/// line four of the rustdoc\n"
69
+ "/// line five of the rustdoc\n"
70
+ "/// line six of the rustdoc ok\n"
71
+ "pub fn f() {}\n"
72
+ )
73
+ PY_LONG_RUN = (
74
+ "# note one here\n# note two here\n# note three here\n# note four here\n# note five here\n# note six here\nx = 1\n"
75
+ )
76
+ PY_LONG_DOCSTRING = (
77
+ 'def f():\n """\n'
78
+ " line one\n line two\n line three\n"
79
+ " line four\n line five\n line six\n"
80
+ ' """\n return 1\n'
81
+ )
82
+
83
+
84
+ def runs(matches: Iterable[Match]) -> list[list[Match]]:
85
+ """Group comments into runs of adjacent lines — a block comment, or consecutive line
86
+ comments with no code between — each a list of :class:`Match` in document order."""
87
+ grouped: list[list[Match]] = []
88
+ for m in sorted(matches, key=lambda c: c.line):
89
+ if grouped and m.line <= grouped[-1][-1].end_line + 1:
90
+ grouped[-1].append(m)
91
+ else:
92
+ grouped.append([m])
93
+ return grouped
94
+
95
+
96
+ def too_long(run: list[Match]) -> bool:
97
+ """Whether a comment run exceeds the line or character budget."""
98
+ lines = run[-1].end_line - run[0].line + 1
99
+ chars = sum(len(m.text) for m in run)
100
+ return lines > MAX_COMMENT_LINES or chars > MAX_COMMENT_CHARS
101
+
102
+
103
+ class LongCommentIntroduced(CustomCondition):
104
+ """True when the pending edit introduces a comment (or adjacent line-comment run) past
105
+ the length budget. Diffs the edit's pre-image against its new text, so a comment already
106
+ present before the edit never counts; files whose language ast-grep can't parse yield False."""
107
+
108
+ def check(self, evt: BaseHookEvent) -> bool:
109
+ if (
110
+ not (file := evt.file)
111
+ or not (lang := lang_for_path(file.path))
112
+ or (old := evt.replaced) is None
113
+ or (new := evt.content) is None
114
+ ):
115
+ return False
116
+ return any(too_long(run) for run in runs(introduced_comments(old, new, lang)))
117
+
118
+
119
+ nudge(
120
+ "Verbose comment introduced. Comments should be terse and used sparingly — let names, "
121
+ "types, and organization document the code. Keep documentation-generation comments "
122
+ "(godoc / rustdoc / docstrings) to a real description, and drop long inline commentary "
123
+ "or anything that restates the code. See: STYLEGUIDE.md § Comments.",
124
+ only_if=[Tool("Edit", "Write", "MultiEdit"), LongCommentIntroduced()],
125
+ events=Event.PreToolUse,
126
+ tests={
127
+ # Short / within-budget comments — allowed.
128
+ Input(file="doc.go", old=GO_NO_COMMENT, content=GO_ONE_LINE_DOC): Allow(),
129
+ Input(file="doc.go", old=GO_NO_COMMENT, content=GO_THREE_LINE_DOC): Allow(),
130
+ Input(file="m.py", old="x = 1\n", content="# set x\nx = 1\n"): Allow(),
131
+ Input(file="lib.rs", old="pub fn f() {}\n", content="/// Builds a widget.\npub fn f() {}\n"): Allow(),
132
+ # Long introduced comment runs — warned.
133
+ Input(file="svc.go", old="package p\n\nfunc F() {\n\tx := 1\n}\n", content=GO_LONG_RUN): Warn(
134
+ pattern="Verbose comment"
135
+ ), # 6-line // run > 4 lines
136
+ Input(file="big.go", old=GO_NO_COMMENT, content="package p\n\n// " + "x" * 320 + "\nfunc F() {}\n"): Warn(
137
+ pattern="Verbose comment"
138
+ ), # single line, > 300 chars
139
+ Input(file="lib.rs", old="fn f() {}\n", content=RS_LONG_BLOCK): Warn(pattern="Verbose comment"),
140
+ Input(file="lib.rs", old="pub fn f() {}\n", content=RS_LONG_DOC): Warn(
141
+ pattern="Verbose comment"
142
+ ), # long rustdoc run also warns (strict threshold, by design)
143
+ Input(file="m.py", old="x = 1\n", content=PY_LONG_RUN): Warn(pattern="Verbose comment"),
144
+ # Diff-gating: a pre-existing long run re-saved unchanged does not fire.
145
+ Input(file="svc.go", old=GO_EXISTING_OLD, content=GO_EXISTING_NEW): Allow(),
146
+ # Python docstrings are string nodes, not comments — never trip this.
147
+ Input(file="m.py", old="def f():\n return 1\n", content=PY_LONG_DOCSTRING): Allow(),
148
+ # Write tool: pre-image comes from disk at PreToolUse.
149
+ Input(
150
+ tool="Write",
151
+ file=FileFixture(name="cmt_new.go", content=GO_WRITE_PLAIN),
152
+ content=GO_WRITE_RUN,
153
+ ): Warn(pattern="Verbose comment"), # long run added to existing file
154
+ Input(
155
+ tool="Write",
156
+ file=FileFixture(name="cmt_pre.go", content=GO_WRITE_EXISTING_OLD),
157
+ content=GO_WRITE_EXISTING_NEW,
158
+ ): Allow(), # run already on disk: not introduced
159
+ },
160
+ )
@@ -97,7 +97,7 @@ def rewrite_command(
97
97
 
98
98
  - **(pattern, replace)** — polymorphic by the shape of ``pattern``. When it carries an ast-grep
99
99
  metavariable (``$NAME`` / ``$$$NAME``), the command is rewritten *structurally* over
100
- tree-sitter-bash via [`CommandLine.rewrite`][captain_hook.CommandLine.rewrite]; otherwise it is
100
+ tree-sitter-bash via [`ast_grep.rewrite`][captain_hook.ast_grep.rewrite]; otherwise it is
101
101
  a regex rewritten via ``re.sub``. So ``rewrite_command("cat $$$ARGS", "bat $$$ARGS")`` is
102
102
  structural and ``rewrite_command(r"^cat\s+(\S+)$", r"bat \1")`` is regex. ``note`` surfaces as
103
103
  ``additionalContext`` so the model sees the substitution.
@@ -1,7 +1,7 @@
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 = "7.0.0"
4
+ version = "8.0.0"
5
5
  description = "Stop repeating yourself to Claude."
6
6
  readme = "README.md"
7
7
  license = "PolyForm-Noncommercial-1.0.0"
File without changes
File without changes
File without changes
File without changes
File without changes