capt-hook 3.3.0__tar.gz → 3.3.2__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 (90) hide show
  1. {capt_hook-3.3.0 → capt_hook-3.3.2}/PKG-INFO +1 -1
  2. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/cli.py +19 -12
  3. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/review/cli.py +3 -1
  4. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/skills/bootstrapping-hooks/SKILL.md +10 -13
  5. {capt_hook-3.3.0 → capt_hook-3.3.2}/pyproject.toml +1 -1
  6. {capt_hook-3.3.0 → capt_hook-3.3.2}/LICENSE +0 -0
  7. {capt_hook-3.3.0 → capt_hook-3.3.2}/README.md +0 -0
  8. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/.claude-plugin/plugin.json +0 -0
  9. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/__init__.py +0 -0
  10. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/__main__.py +0 -0
  11. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/app.py +0 -0
  12. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/classifiers/__init__.py +0 -0
  13. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/classifiers/conductor.py +0 -0
  14. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/classifiers/droid.py +0 -0
  15. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/classifiers/native.py +0 -0
  16. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/command.py +0 -0
  17. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/conditions.py +0 -0
  18. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/context.py +0 -0
  19. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/decisions.py +0 -0
  20. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/dispatch.py +0 -0
  21. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/events.py +0 -0
  22. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/file.py +0 -0
  23. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/llm/__init__.py +0 -0
  24. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/loader.py +0 -0
  25. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/log.py +0 -0
  26. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/packs/__init__.py +0 -0
  27. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/packs/general/capt-hook.toml +0 -0
  28. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/packs/general/commands.py +0 -0
  29. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/packs/general/docs.py +0 -0
  30. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/packs/general/plans.py +0 -0
  31. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/packs/general/prompts.py +0 -0
  32. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/packs/general/review.py +0 -0
  33. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/packs/general/stewardship.py +0 -0
  34. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/packs/general/tasks.py +0 -0
  35. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/packs/manager.py +0 -0
  36. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/packs/python/capt-hook.toml +0 -0
  37. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/packs/python/style.py +0 -0
  38. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/packs/python/testing.py +0 -0
  39. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/packs/python/toolchain.py +0 -0
  40. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/primitives/__init__.py +0 -0
  41. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/primitives/commands.py +0 -0
  42. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/primitives/lint.py +0 -0
  43. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/primitives/llm.py +0 -0
  44. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/primitives/nudge.py +0 -0
  45. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/primitives/workflow.py +0 -0
  46. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/prompt.py +0 -0
  47. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/py.typed +0 -0
  48. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/review/__init__.py +0 -0
  49. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/review/fix.py +0 -0
  50. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/review/formats.py +0 -0
  51. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/review/judge.py +0 -0
  52. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/review/pipeline.py +0 -0
  53. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/review/repo.py +0 -0
  54. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/review/scan.py +0 -0
  55. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/review/settings.py +0 -0
  56. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/review/store.py +0 -0
  57. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/review/sync.py +0 -0
  58. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/session.py +0 -0
  59. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/settings.py +0 -0
  60. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/signals/__init__.py +0 -0
  61. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/signals/nlp.py +0 -0
  62. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/skills/authoring-hooks/SKILL.md +0 -0
  63. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/skills/authoring-hooks/references/capt-hook-api.md +0 -0
  64. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/skills/authoring-hooks/references/pattern-catalog.md +0 -0
  65. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/skills/authoring-hooks/references/pitfalls.md +0 -0
  66. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/skills/authoring-hooks/references/testing-hooks.md +0 -0
  67. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/skills/scanning-sessions/SKILL.md +0 -0
  68. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/skills/scanning-sessions/references/pr-workflow.md +0 -0
  69. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/skills/scanning-sessions/references/review-cli.md +0 -0
  70. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/skills/translating-styleguides/SKILL.md +0 -0
  71. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/skills/translating-styleguides/references/llm-rule-patterns.md +0 -0
  72. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/skills/translating-styleguides/references/matcher-reference.md +0 -0
  73. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/skills/translating-styleguides/references/tier-rubric.md +0 -0
  74. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/state.py +0 -0
  75. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/style/__init__.py +0 -0
  76. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/style/matchers.py +0 -0
  77. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/style/scope.py +0 -0
  78. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/style/types.py +0 -0
  79. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/tasks.py +0 -0
  80. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/templates/example_hook.py.tmpl +0 -0
  81. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/testing/__init__.py +0 -0
  82. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/testing/helpers.py +0 -0
  83. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/testing/session_cache.py +0 -0
  84. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/testing/types.py +0 -0
  85. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/tests/__init__.py +0 -0
  86. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/tests/helpers.py +0 -0
  87. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/types.py +0 -0
  88. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/util/__init__.py +0 -0
  89. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/util/model_cache.py +0 -0
  90. {capt_hook-3.3.0 → capt_hook-3.3.2}/captain_hook/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: capt-hook
3
- Version: 3.3.0
3
+ Version: 3.3.2
4
4
  Summary: Declarative hook framework for Claude Code
5
5
  Keywords: claude,claude-code,hooks,llm,agents,guardrails,cli
6
6
  Author: Yasyf Mohamedali
@@ -157,20 +157,33 @@ def is_captain_hook_group(group: dict[str, Any]) -> bool:
157
157
  return any("capt-hook" in (h.get("command") or "") for h in group.get("hooks") or [])
158
158
 
159
159
 
160
+ def capt_hook_events(path: Path) -> set[str]:
161
+ if not path.exists():
162
+ return set()
163
+ return {
164
+ event
165
+ for event, groups in (json.loads(path.read_text()).get("hooks") or {}).items()
166
+ if any(is_captain_hook_group(g) for g in groups)
167
+ }
168
+
169
+
160
170
  def merge_settings(
161
171
  hooks_dir: str, settings_path: Path, from_source: str = DIST_NAME
162
172
  ) -> tuple[dict[str, Any], dict[str, str]]:
163
173
  new_hooks: dict[str, list[dict[str, Any]]] = generate_settings(hooks_dir, from_source=from_source)["hooks"]
164
174
  existing = json.loads(settings_path.read_text()) if settings_path.exists() else {}
165
175
  existing_hooks: dict[str, list[dict[str, Any]]] = existing.get("hooks") or {}
176
+ committed = capt_hook_events(settings_path.parent / "settings.json")
166
177
 
167
178
  summary: dict[str, str] = {}
168
179
  merged_hooks: dict[str, list[dict[str, Any]]] = {}
169
180
  for event in sorted(existing_hooks.keys() | new_hooks.keys()):
170
181
  foreign = [g for g in existing_hooks.get(event, []) if not is_captain_hook_group(g)]
171
182
  old_own = [g for g in existing_hooks.get(event, []) if is_captain_hook_group(g)]
172
- fresh_own = new_hooks.get(event, [])
173
- if old_own or fresh_own:
183
+ fresh_own = [] if event in committed else new_hooks.get(event, [])
184
+ if event in committed and (old_own or new_hooks.get(event)):
185
+ summary[event] = "deferred"
186
+ elif old_own or fresh_own:
174
187
  summary[event] = (
175
188
  "unchanged"
176
189
  if old_own == fresh_own
@@ -207,6 +220,8 @@ def print_hook_summary(label: str, summary: dict[str, str]) -> None:
207
220
  click.echo(f" - removed {event}")
208
221
  if unchanged := by_status["unchanged"]:
209
222
  click.echo(f" unchanged: {', '.join(unchanged)} (already present)")
223
+ if deferred := by_status["deferred"]:
224
+ click.echo(f" deferred to settings.json: {', '.join(deferred)}")
210
225
 
211
226
 
212
227
  def regenerate_settings(state: CliState) -> None:
@@ -218,16 +233,8 @@ def regenerate_settings(state: CliState) -> None:
218
233
 
219
234
 
220
235
  def settings_drift(root: Path) -> set[str]:
221
- settings = [p for name in ("settings.json", "settings.local.json") if (p := root / ".claude" / name).exists()]
222
- if not settings:
223
- return set()
224
- wired = {
225
- event
226
- for path in settings
227
- for event, groups in (json.loads(path.read_text()).get("hooks") or {}).items()
228
- if any(is_captain_hook_group(g) for g in groups)
229
- }
230
- return subscribed_events() - wired
236
+ paths = [p for name in ("settings.json", "settings.local.json") if (p := root / ".claude" / name).exists()]
237
+ return subscribed_events() - {event for p in paths for event in capt_hook_events(p)} if paths else set()
231
238
 
232
239
 
233
240
  def warn_settings_drift(
@@ -60,8 +60,10 @@ def ensure_review_wiring(settings_path: Path) -> bool:
60
60
  from captain_hook.cli import write_settings
61
61
 
62
62
  existing: dict[str, Any] = json.loads(settings_path.read_text()) if settings_path.exists() else {}
63
+ committed = settings_path.parent / "settings.json"
64
+ committed_hooks: dict[str, Any] = (json.loads(committed.read_text()).get("hooks") or {}) if committed.exists() else {}
63
65
  hooks: dict[str, Any] = existing.get("hooks") or {}
64
- if review_wired(hooks):
66
+ if review_wired(hooks) or review_wired(committed_hooks):
65
67
  return False
66
68
  group = {"hooks": [{"type": "command", "command": f"uvx {REVIEW_RUN_COMMAND}"}]}
67
69
  write_settings(
@@ -29,7 +29,7 @@ Copy this checklist into your response and check off steps as you complete them:
29
29
 
30
30
  ```
31
31
  Bootstrap Progress:
32
- - [ ] Step 1: Locate + scaffold (init or review enable) + pre-flight
32
+ - [ ] Step 1: Locate + scaffold (init) + pre-flight
33
33
  - [ ] Step 2: Survey the repo (docs, CI, lint configs, git log)
34
34
  - [ ] Step 3: Mine candidates onto the taxonomy
35
35
  - [ ] Step 4: Propose via AskUserQuestion — nothing written before approval
@@ -50,16 +50,12 @@ grep -lq 'capt-hook' .claude/settings.json 2>/dev/null && echo COMMITTED || echo
50
50
  ```
51
51
 
52
52
  Then scaffold up front, so the framework and the session reviewer are live before you propose
53
- anything pick the command by what you found:
54
-
55
- - **FRESH** (no committed capt-hook wiring)run `uvx capt-hook init`. It scaffolds
56
- `.claude/hooks/`, wires `.claude/settings.local.json`, installs the skills, and **enables the
57
- session reviewer** (watching this repo; it mines ended sessions and opens hook PRs
58
- `uvx capt-hook review disable` to stop).
59
- - **COMMITTED** (a checked-in `.claude/settings.json` already runs `uvx capt-hook run …`) — do
60
- **not** run `init`; it would duplicate those hooks into `settings.local.json` and double-fire.
61
- Run `uvx capt-hook review enable` instead — it installs the reviewer skills and arms the session
62
- reviewer without touching the committed event hooks.
53
+ anything. Run `uvx capt-hook init` in every repo. It scaffolds `.claude/hooks/`,
54
+ wires `.claude/settings.local.json`, installs the skills, and **enables the session reviewer**
55
+ (watching this repo; it mines ended sessions and opens hook PRs — `uvx capt-hook review disable`
56
+ to stop). In a **COMMITTED** repo (a checked-in `.claude/settings.json` already runs
57
+ `uvx capt-hook run …`), `init` defers those events to the committed file instead of re-wiring
58
+ them locally. It prints `deferred to settings.json: …` and never double-fires.
63
59
 
64
60
  Read `.claude/settings.local.json` and `.claude/settings.json`. If capt-hook hooks already exist,
65
61
  switch to **additive mode**: never overwrite existing hook files; new categories go in new files,
@@ -115,8 +111,9 @@ into enforced style rules (runs the translating-styleguides skill)"**.
115
111
  ### 5. Clear the demo example.py
116
112
 
117
113
  Scaffolding already ran in Step 1. If that `init` created the demo `.claude/hooks/example.py`,
118
- delete it once you've drafted the real hooks (Step 6) — the approved hooks replace it. (In
119
- **COMMITTED** repos `review enable` writes no `example.py`, so there's nothing to clear.)
114
+ delete it once you've drafted the real hooks (Step 6) — the approved hooks replace it. (In a repo
115
+ that already had its own `.claude/hooks/`, `init` leaves those files untouched and only adds
116
+ `example.py` if it was absent; clear it the same way.)
120
117
 
121
118
  ### 6. Write hooks
122
119
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "capt-hook"
3
- version = "3.3.0"
3
+ version = "3.3.2"
4
4
  description = "Declarative hook framework for Claude Code"
5
5
  readme = "README.md"
6
6
  license = "PolyForm-Noncommercial-1.0.0"
File without changes
File without changes
File without changes
File without changes