capt-hook 8.0.0__tar.gz → 8.2.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 (117) hide show
  1. {capt_hook-8.0.0 → capt_hook-8.2.0}/PKG-INFO +1 -1
  2. capt_hook-8.2.0/captain_hook/packs/general/capt-hook.toml +5 -0
  3. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/packs/general/models.py +207 -18
  4. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/packs/general/prompts.py +10 -0
  5. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/packs/general/tasks.py +15 -1
  6. capt_hook-8.2.0/captain_hook/packs/steering/capt-hook.toml +5 -0
  7. capt_hook-8.2.0/captain_hook/packs/steering/steering.py +731 -0
  8. {capt_hook-8.0.0 → capt_hook-8.2.0}/pyproject.toml +1 -1
  9. capt_hook-8.0.0/captain_hook/packs/general/capt-hook.toml +0 -5
  10. capt_hook-8.0.0/captain_hook/packs/steering/capt-hook.toml +0 -5
  11. capt_hook-8.0.0/captain_hook/packs/steering/steering.py +0 -350
  12. {capt_hook-8.0.0 → capt_hook-8.2.0}/LICENSE +0 -0
  13. {capt_hook-8.0.0 → capt_hook-8.2.0}/README.md +0 -0
  14. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/.claude-plugin/plugin.json +0 -0
  15. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/__init__.py +0 -0
  16. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/__main__.py +0 -0
  17. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/app.py +0 -0
  18. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/ast_grep.py +0 -0
  19. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/classifiers/__init__.py +0 -0
  20. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/classifiers/conductor.py +0 -0
  21. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/classifiers/droid.py +0 -0
  22. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/classifiers/native.py +0 -0
  23. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/cli.py +0 -0
  24. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/conditions.py +0 -0
  25. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/context.py +0 -0
  26. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/contexts.py +0 -0
  27. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/decisions.py +0 -0
  28. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/dispatch.py +0 -0
  29. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/durable.py +0 -0
  30. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/events.py +0 -0
  31. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/fields.py +0 -0
  32. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/file.py +0 -0
  33. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/loader.py +0 -0
  34. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/log.py +0 -0
  35. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/packs/__init__.py +0 -0
  36. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/packs/fixes/__init__.py +0 -0
  37. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/packs/fixes/capt-hook.toml +0 -0
  38. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/packs/fixes/teammate_permissions.py +0 -0
  39. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/packs/general/__init__.py +0 -0
  40. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/packs/general/_lib.py +0 -0
  41. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/packs/general/commands.py +0 -0
  42. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/packs/general/comments.py +0 -0
  43. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/packs/general/detours.py +0 -0
  44. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/packs/general/docs.py +0 -0
  45. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/packs/general/plans.py +0 -0
  46. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/packs/general/review.py +0 -0
  47. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/packs/general/tombstones.py +0 -0
  48. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/packs/go/__init__.py +0 -0
  49. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/packs/go/capt-hook.toml +0 -0
  50. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/packs/go/testing.py +0 -0
  51. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/packs/go/toolchain.py +0 -0
  52. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/packs/manager.py +0 -0
  53. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/packs/python/__init__.py +0 -0
  54. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/packs/python/capt-hook.toml +0 -0
  55. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/packs/python/style.py +0 -0
  56. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/packs/python/testing.py +0 -0
  57. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/packs/python/toolchain.py +0 -0
  58. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/packs/steering/__init__.py +0 -0
  59. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/primitives/__init__.py +0 -0
  60. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/primitives/commands.py +0 -0
  61. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/primitives/lint.py +0 -0
  62. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/primitives/llm.py +0 -0
  63. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/primitives/nudge.py +0 -0
  64. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/primitives/permissions.py +0 -0
  65. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/primitives/rewrite.py +0 -0
  66. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/primitives/workflow.py +0 -0
  67. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/prompt.py +0 -0
  68. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/py.typed +0 -0
  69. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/review/__init__.py +0 -0
  70. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/review/cli.py +0 -0
  71. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/review/dashboard.py +0 -0
  72. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/review/fix.py +0 -0
  73. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/review/formats.py +0 -0
  74. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/review/judge.py +0 -0
  75. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/review/pipeline.py +0 -0
  76. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/review/repo.py +0 -0
  77. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/review/scan.py +0 -0
  78. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/review/settings.py +0 -0
  79. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/review/store.py +0 -0
  80. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/review/sync.py +0 -0
  81. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/session.py +0 -0
  82. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/settings.py +0 -0
  83. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/signals/__init__.py +0 -0
  84. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/signals/nlp.py +0 -0
  85. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/skills/authoring-hooks/SKILL.md +0 -0
  86. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/skills/authoring-hooks/references/capt-hook-api.md +0 -0
  87. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/skills/authoring-hooks/references/pattern-catalog.md +0 -0
  88. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/skills/authoring-hooks/references/pitfalls.md +0 -0
  89. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/skills/authoring-hooks/references/testing-hooks.md +0 -0
  90. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/skills/bootstrapping-hooks/SKILL.md +0 -0
  91. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/skills/scanning-sessions/SKILL.md +0 -0
  92. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/skills/scanning-sessions/references/pr-workflow.md +0 -0
  93. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/skills/scanning-sessions/references/review-cli.md +0 -0
  94. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/skills/translating-styleguides/SKILL.md +0 -0
  95. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/skills/translating-styleguides/references/llm-rule-patterns.md +0 -0
  96. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/skills/translating-styleguides/references/matcher-reference.md +0 -0
  97. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/skills/translating-styleguides/references/tier-rubric.md +0 -0
  98. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/state.py +0 -0
  99. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/style/__init__.py +0 -0
  100. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/style/ast_grep.py +0 -0
  101. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/style/matchers.py +0 -0
  102. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/style/scope.py +0 -0
  103. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/style/types.py +0 -0
  104. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/tasks.py +0 -0
  105. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/templates/example_hook.py.tmpl +0 -0
  106. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/testing/__init__.py +0 -0
  107. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/testing/helpers.py +0 -0
  108. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/testing/session_cache.py +0 -0
  109. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/testing/types.py +0 -0
  110. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/types.py +0 -0
  111. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/util/__init__.py +0 -0
  112. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/util/automode.py +0 -0
  113. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/util/fs.py +0 -0
  114. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/util/http.py +0 -0
  115. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/util/model_cache.py +0 -0
  116. {capt_hook-8.0.0 → capt_hook-8.2.0}/captain_hook/util/proc.py +0 -0
  117. {capt_hook-8.0.0 → capt_hook-8.2.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: 8.0.0
3
+ Version: 8.2.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
@@ -0,0 +1,5 @@
1
+ name = "general"
2
+ version = "0.13.0"
3
+ description = "Language-agnostic guards: git/command safety, doc & prompt nudges, task & plan discipline, model-routing nudges (review, security audit, and diagnosis route to gpt-5.5), code-review + docs-freshness gates, a detour check-in nudge, a verbose-comment nudge, and delegated-prose nudges that catch doc writing handed to a subagent or workflow without the writing-docs skill."
4
+ hooks = "."
5
+ nlp = true
@@ -321,7 +321,8 @@ The Models rubric: implementation delegates to opus-4.8 at xhigh — opus is ~2x
321
321
  than fable and nearly as capable. Fable's lanes are orchestration, design/architecture
322
322
  review, hard planning, all prose/writing, and implementation that is very sensitive or
323
323
  error-prone (auth, migrations, concurrency, data loss, crypto, subtle algorithms).
324
- Code/diff review and bug diagnosis have their own gpt-5.5 lane with a separate nudge.
324
+ Code/diff review, security review/audit, and bug diagnosis have their own gpt-5.5
325
+ lanes with separate nudges.
325
326
 
326
327
  Set fire=true only when the prompt is clearly routine implementation — building, fixing,
327
328
  wiring, or refactoring code — with no fable-lane signal. A prompt that reviews, plans,
@@ -476,20 +477,25 @@ Auth plus concurrency is a sensitive surface — fable's inline lane.
476
477
  )
477
478
 
478
479
  llm_nudge(
479
- """Decide whether this delegated subagent runs code review or bug diagnosis that
480
- should route to gpt-5.5 instead of fable.
480
+ """Decide whether this delegated subagent runs code review, a security review/audit
481
+ or verification of security-sensitive code, or bug diagnosis that should route to
482
+ gpt-5.5 instead of fable.
481
483
 
482
484
  <delegated_spawn> holds the pending Agent/Task call: its model pin (or that it inherits
483
485
  the session model, fable), agent type, and prompt.
484
486
 
485
487
  The Models rubric: code/diff review — sweeping a diff or codebase for bugs,
486
- correctness, or cleanups; finder and refuter passes over findings — and bug diagnosis
487
- route to gpt-5.5 via the codex skill; fable is the escalation target when gpt-5.5's
488
- output misses. Fable keeps design/architecture review, "is this the right approach"
489
- judgment, prose review, and the synthesis/accept-reject pass over review findings.
490
-
491
- Set fire=true only when the prompt clearly reviews code or diffs for defects, or
492
- diagnoses a bug, and the spawn would run on fable. Design review, approach judgment,
488
+ correctness, or cleanups; finder and refuter passes over findings — security
489
+ review/audit and verification of security-sensitive code (auth, input validation,
490
+ crypto, secrets), and bug diagnosis route to gpt-5.5 via the codex skill; fable is
491
+ the escalation target when gpt-5.5's output misses. Fable keeps design/architecture
492
+ review, "is this the right approach" judgment, prose review, the synthesis/
493
+ accept-reject pass over review findings and security-sensitive implementation,
494
+ which is not review.
495
+
496
+ Set fire=true only when the prompt clearly reviews code or diffs for defects, audits
497
+ or verifies security-sensitive code, or diagnoses a bug, and the spawn would run on
498
+ fable. Design review, approach judgment,
493
499
  synthesis over findings, and prose review are fable's lanes: fire=false. When
494
500
  uncertain, fire=false — the agent may have chosen fable deliberately, and a false
495
501
  alarm teaches it to ignore this nudge. Keep reasoning under 40 words.
@@ -519,10 +525,23 @@ The accept-reject pass over findings stays on fable.
519
525
  Review the README draft for factual errors.
520
526
  Prose review stays on fable.
521
527
  </example>
528
+ <example fire="true">
529
+ Audit the session-token handling in auth/middleware.py for vulnerabilities.
530
+ Security review/audit of code — gpt-5.5's lane via codex.
531
+ </example>
532
+ <example fire="true">
533
+ Verify the new input-validation layer rejects path traversal and injection payloads.
534
+ Verification of security-sensitive code routes to gpt-5.5.
535
+ </example>
536
+ <example fire="false">
537
+ Implement mitigations for the security-audit findings in auth.py.
538
+ Security-sensitive implementation, not review — the implementation lanes apply.
539
+ </example>
522
540
  </examples>""",
523
541
  message=lambda r: (
524
542
  f"This review/diagnosis delegation would run on fable. {r.reasoning} "
525
- "Code/diff review and bug diagnosis route to gpt-5.5: run the codex skill (from a "
543
+ "Code/diff review, security review/audit and verification of security-sensitive code, "
544
+ "and bug diagnosis route to gpt-5.5: run the codex skill (from a "
526
545
  "workflow or subagent, spawn a model='sonnet', effort='low' wrapper that writes a "
527
546
  "self-contained codex prompt), and escalate to fable only when gpt-5.5's output misses. "
528
547
  "Design review and findings synthesis stay on fable. "
@@ -532,7 +551,11 @@ Prose review stays on fable.
532
551
  events=Event.PreToolUse,
533
552
  only_if=[
534
553
  Tool("Agent|Task"),
535
- ToolInput("prompt", r"(?i)\b(review|refut|adversari|audit|correctness|diagnos|root.?caus)"),
554
+ ToolInput(
555
+ "prompt",
556
+ r"(?i)(\b(review|refut|adversari|audit|correctness|diagnos|root.?caus|secur|vuln|pentest)"
557
+ r"|\bverif\w*[\s\S]{0,160}?\b(auth|crypt|secret|sanitiz|inject|input.?valid|token|session))",
558
+ ),
536
559
  ],
537
560
  skip_if=[
538
561
  ToolInput("model", r"(?i)\b(opus|sonnet|haiku)\b"),
@@ -552,6 +575,13 @@ Prose review stays on fable.
552
575
  prompt="Synthesize the confirmed review findings and decide which to fix",
553
576
  llm={"fire": False},
554
577
  ): Allow(),
578
+ Input(prompt="Audit auth/session.py for security vulnerabilities"): Warn(pattern="gpt-5.5"),
579
+ Input(prompt="Verify the input-validation change blocks path traversal"): Warn(pattern="codex"),
580
+ Input(prompt="Verify the pagination change renders the last page correctly"): Allow(),
581
+ Input(
582
+ prompt="Implement mitigations for the security audit findings in auth.py",
583
+ llm={"fire": False},
584
+ ): Allow(),
555
585
  },
556
586
  )
557
587
 
@@ -666,10 +696,13 @@ line that pins a model — a stage not quoted there carries no pin and inherits
666
696
  session model, fable.
667
697
 
668
698
  The Models rubric: code/diff review stages — finder sweeps over a diff or codebase,
669
- adversarial refuters over findings — and bug diagnosis route to gpt-5.5 via the codex
670
- skill. A stage does that correctly when it pins model 'sonnet' at low effort and its
671
- prompt writes a self-contained codex prompt and runs the codex skill. Fable keeps the
672
- synthesis/accept-reject stage over findings and design/architecture judgment.
699
+ adversarial refuters over findings — security review/audit stages and verification
700
+ of security-sensitive code (auth, input validation, crypto, secrets), and bug
701
+ diagnosis route to gpt-5.5 via the codex skill. A stage does that correctly when it
702
+ pins model 'sonnet' at low effort and its prompt writes a self-contained codex prompt
703
+ and runs the codex skill. Fable keeps the synthesis/accept-reject stage over findings
704
+ and design/architecture judgment — and security-sensitive implementation, which is
705
+ not review.
673
706
 
674
707
  Set fire=true only when at least one review or diagnosis stage would run on fable —
675
708
  unpinned, or pinned 'fable'. Stages already wrapped for codex, synthesis stages, and
@@ -695,10 +728,14 @@ Already the codex wrapper — correctly routed.
695
728
  agent(`Synthesize the confirmed findings and decide which to fix`)
696
729
  Synthesis/accept-reject stays on fable.
697
730
  </example>
731
+ <example fire="true">
732
+ agent(`Audit the auth flow for injection and session-fixation issues; return findings as JSON`)
733
+ An unpinned security audit inherits fable; security review/audit is the codex-wrapper lane.
734
+ </example>
698
735
  </examples>""",
699
736
  message=lambda r: (
700
737
  f"This workflow runs review/diagnosis stages on fable. {r.reasoning} "
701
- "Route finder, refuter, and diagnosis stages to gpt-5.5: make each a model: 'sonnet', "
738
+ "Route finder, refuter, security-audit, and diagnosis stages to gpt-5.5: make each a model: 'sonnet', "
702
739
  "effort: 'low' stage that writes a self-contained codex prompt and runs the codex skill; "
703
740
  "keep the synthesis/accept-reject stage on fable (inherit the session model). "
704
741
  "See CLAUDE.md § Plan Execution & Orchestration (Models)."
@@ -707,7 +744,10 @@ Synthesis/accept-reject stays on fable.
707
744
  events=Event.PreToolUse,
708
745
  only_if=[
709
746
  Tool("Workflow"),
710
- WorkflowScript(pattern=r"(?i)\b(review|refut|adversari|audit|correctness|diagnos)"),
747
+ WorkflowScript(
748
+ pattern=r"(?i)(\b(review|refut|adversari|audit|correctness|diagnos|root.?caus|secur|vuln|pentest)"
749
+ r"|\bverif\w*[\s\S]{0,160}?\b(auth|crypt|secret|sanitiz|inject|input.?valid|token|session))",
750
+ ),
711
751
  ],
712
752
  max_fires=2,
713
753
  max_context=16_000,
@@ -730,5 +770,154 @@ Synthesis/accept-reject stays on fable.
730
770
  script="agent(`Synthesize the confirmed review findings and decide which to fix`)",
731
771
  llm={"fire": False},
732
772
  ): Allow(),
773
+ Input(
774
+ script="agent(`Audit the login flow for auth bypass and injection; return findings as JSON`)"
775
+ ): Warn(pattern="gpt-5.5"),
776
+ Input(script="agent('Verify the CLI renders the last page correctly')"): Allow(),
777
+ },
778
+ )
779
+
780
+ llm_nudge(
781
+ """Decide whether this delegated subagent call delegates documentation or prose
782
+ writing without directing the subagent to read the writing-docs skill.
783
+
784
+ <delegated_spawn> holds the pending Agent/Task call: its model pin, agent type, and
785
+ prompt, ending with the sentences a clause prefilter matched — each asks a writing verb
786
+ of a prose artifact, with negated asks ("do NOT edit the docs") already screened out.
787
+
788
+ You are watching an orchestrating agent spawn a subagent. Decide whether the pending
789
+ prompt delegates documentation or prose writing — a README, docs page, CHANGELOG,
790
+ tutorial, release notes, or similar deliverable — without directing the subagent to
791
+ read the writing-docs skill. Restated style rules ('technical-builder voice', 'no hype
792
+ adjectives', 'first person, confident') do not count as reading the skill; that
793
+ paraphrase is exactly the failure to catch. Fire only when the prompt's deliverable is
794
+ prose the writing-docs skill governs. Do not fire for code work that incidentally
795
+ mentions a doc file, for reading or reviewing docs without writing them, or for a prompt
796
+ that already tells the agent to read the skill or its references.
797
+
798
+ When uncertain, fire=false — a false alarm teaches the agent to ignore this nudge. Keep
799
+ reasoning under 40 words.
800
+
801
+ <examples>
802
+ <example fire="true">
803
+ Rewrite the README for this repo. You are fable; technical-builder voice, no hype adjectives.
804
+ README prose with the style rules paraphrased in place of the skill — the drift this catches.
805
+ </example>
806
+ <example fire="true">
807
+ Draft the release notes for v2. Keep it first-person and confident, no marketing fluff.
808
+ Release-notes prose; restated voice rules, no pointer to the writing-docs skill.
809
+ </example>
810
+ <example fire="false">
811
+ Fix the failing test in cli.py; the README already documents the new flag.
812
+ Code work that only mentions the README — no prose is produced.
813
+ </example>
814
+ <example fire="false">
815
+ Rewrite the README, but read the doc-writing skill and its references first.
816
+ Already directs the subagent to the skill — nothing to nudge.
817
+ </example>
818
+ </examples>""",
819
+ message=lambda r: (
820
+ "This prompt delegates prose but paraphrases the writing rules instead of pointing at them. "
821
+ f"{r.reasoning} A paraphrase drifts and silently overrides the skill — rewrite the prompt to "
822
+ "direct the agent to READ the writing-docs skill and its references (the installed plugin under "
823
+ "~/.claude/plugins/cache/skills/writing-docs, or plugins/writing-docs in the cc-skills repo) "
824
+ "before it writes."
825
+ ),
826
+ contexts=[ProseSpawn()],
827
+ events=Event.PreToolUse,
828
+ only_if=[Tool("Agent|Task")],
829
+ skip_if=[
830
+ ToolInput("prompt", r"(?i)writing-docs"),
831
+ Agent("Explore|claude-code-guide"),
832
+ ],
833
+ max_context=16_000,
834
+ agent=False,
835
+ transcript=False,
836
+ tests={
837
+ Input(
838
+ prompt="Rewrite the README of /repo. You are fable; technical-builder voice, no hype "
839
+ "adjectives. Verify commands against the binary."
840
+ ): Warn(pattern="writing-docs"),
841
+ Input(
842
+ prompt="Rewrite the README of /repo; technical-builder voice, no hype adjectives. Read "
843
+ "the writing-docs skill at ~/.claude/plugins/cache/skills/writing-docs first."
844
+ ): Allow(),
845
+ Input(prompt="Fix the race in daemon.go; update the failing test"): Allow(),
846
+ Input(
847
+ prompt="Rewrite the README, but read the doc-writing skill and its references first",
848
+ llm={"fire": False},
849
+ ): Allow(),
850
+ },
851
+ )
852
+
853
+ llm_nudge(
854
+ """Decide whether this workflow script delegates documentation or prose writing to an
855
+ agent() stage without directing that subagent to read the writing-docs skill.
856
+
857
+ <workflow_script> holds the pending Workflow call's script source, headed by every line
858
+ that pins a model, followed by the sentences a clause prefilter matched — each asks a
859
+ writing verb of a prose artifact, with negated asks ("do NOT edit CHANGELOG.md") already
860
+ screened out.
861
+
862
+ Decide whether an agent() prompt delegates documentation or prose writing — a README,
863
+ docs page, CHANGELOG, tutorial, release notes, or similar deliverable — without
864
+ directing that subagent to read the writing-docs skill. Restated style rules
865
+ ('technical-builder voice', 'no hype adjectives', 'first person, confident') do not
866
+ count as reading the skill; that paraphrase is exactly the failure to catch. Fire only
867
+ when a stage's deliverable is prose the writing-docs skill governs. Do not fire for code
868
+ work that incidentally mentions a doc file, for reading or reviewing docs without writing
869
+ them, or for a stage that already tells its subagent to read the skill or its references.
870
+
871
+ When uncertain, fire=false — a false alarm teaches the agent to ignore this nudge. Keep
872
+ reasoning under 40 words and name the offending stage.
873
+
874
+ <examples>
875
+ <example fire="true">
876
+ agent('Rewrite the README for the new CLI. Technical-builder voice, no hype adjectives', {model: 'opus'})
877
+ The stage's deliverable is README prose with the style rules paraphrased in place of the skill.
878
+ </example>
879
+ <example fire="true">
880
+ agent(`Draft the docs-site page for ${feature}. First-person, confident, no marketing fluff`)
881
+ A docs page delegated with restated voice rules and no pointer to the writing-docs skill.
882
+ </example>
883
+ <example fire="false">
884
+ agent('Fix the failing import in cli.py; the README already documents the flag', {model: 'opus'})
885
+ Code work that only mentions the README — no prose is produced.
886
+ </example>
887
+ <example fire="false">
888
+ agent('Rewrite the troubleshooting guide, but read the doc-writing skill and its references first')
889
+ The stage already directs its subagent to the skill — nothing to nudge.
890
+ </example>
891
+ </examples>""",
892
+ message=lambda r: (
893
+ "This workflow script delegates prose but paraphrases the writing rules instead of pointing at "
894
+ f"them. {r.reasoning} A paraphrase drifts and silently overrides the skill — rewrite the offending "
895
+ "agent() prompt to direct its subagent to READ the writing-docs skill and its references (the "
896
+ "installed plugin under ~/.claude/plugins/cache/skills/writing-docs, or plugins/writing-docs in "
897
+ "the cc-skills repo) before it writes."
898
+ ),
899
+ contexts=[ProseWorkflowScript()],
900
+ events=Event.PreToolUse,
901
+ only_if=[Tool("Workflow")],
902
+ skip_if=[WorkflowScript(pattern=r"(?i)writing-docs")],
903
+ max_fires=2,
904
+ max_context=16_000,
905
+ agent=False,
906
+ transcript=False,
907
+ tests={
908
+ Input(
909
+ script="agent('Rewrite the README. You are fable; technical-builder voice, no hype "
910
+ "adjectives', {model: 'opus'})"
911
+ ): Warn(pattern="writing-docs"),
912
+ Input(
913
+ script="agent('Rewrite the README per the writing-docs skill at "
914
+ "~/.claude/plugins/cache/skills/writing-docs', {model: 'opus'})"
915
+ ): Allow(),
916
+ Input(script="agent('Fix the race in daemon.go; update the failing test')"): Allow(),
917
+ Input(
918
+ script="agent('Rewrite the README, but read the doc-writing skill and its references "
919
+ "first', {model: 'opus'})",
920
+ llm={"fire": False},
921
+ ): Allow(),
733
922
  },
734
923
  )
@@ -24,6 +24,7 @@ nudge(
24
24
  skip_if=[
25
25
  UsedSkill("llm-prompts"),
26
26
  UsedSkill("slop-cop-check", "slop-cop-prose"),
27
+ Content(r"\.count_tokens\(", project_only=False),
27
28
  ],
28
29
  tests={
29
30
  Input(
@@ -31,5 +32,14 @@ nudge(
31
32
  ): Warn(),
32
33
  Input(file="prompt.md", content="<instruction>\nSummarize the document.\n</instruction>\n"): Warn(),
33
34
  Input(file="util.py", content="def add(a, b):\n return a + b\n"): Allow(),
35
+ Input(
36
+ file="tokens.py",
37
+ content=(
38
+ "def api_count(text: str, model: str) -> int:\n"
39
+ " resp = client.messages.count_tokens(\n"
40
+ ' model=model,\n messages=[{"role": "user", "content": text or " "}],\n'
41
+ " )\n return int(resp.input_tokens)\n"
42
+ ),
43
+ ): Allow(),
34
44
  },
35
45
  )
@@ -55,6 +55,13 @@ class DriftedFromTasks(CustomCondition):
55
55
  return since.tool_calls.named("Bash|Grep|Glob|WebSearch|WebFetch|LSP|Skill").count() >= TASK_DRIFT_THRESHOLD
56
56
 
57
57
 
58
+ class TaskNotification(CustomCondition):
59
+ """Matches when the prompt is a system-injected background-task report, not free-form user text."""
60
+
61
+ def check(self, evt: BaseHookEvent) -> bool:
62
+ return (evt.user_prompt or "").strip().startswith("<task-notification>")
63
+
64
+
58
65
  gate(
59
66
  "Open tasks remain. Before stopping, mark each finished task status='completed' via the "
60
67
  "TaskUpdate tool (add a note if you're deliberately deferring one), or output "
@@ -120,7 +127,7 @@ nudge(
120
127
  nudge(
121
128
  "This message has several distinct requests. Use the TaskCreate tool for each item "
122
129
  "before starting work, so none gets dropped. See: CLAUDE.md § Task Tracking.",
123
- skip_if=[InPlanMode()],
130
+ skip_if=[InPlanMode(), TaskNotification()],
124
131
  events=Event.UserPromptSubmit,
125
132
  signals=Signals(
126
133
  [
@@ -141,5 +148,12 @@ nudge(
141
148
  Input(prompt="1. add foo\n2. fix bar\n3. update baz"): Warn(),
142
149
  Input(prompt="just fix the typo"): Allow(),
143
150
  Input(prompt="1. add foo\n2. fix bar\n3. update baz", permission_mode="plan"): Allow(),
151
+ Input(
152
+ prompt=(
153
+ "<task-notification>\n<task-id>abc</task-id>\n<status>completed</status>\n"
154
+ "<summary>Agent finished</summary>\n<result>\n- `cc-transcript` -> version 7.0.1\n"
155
+ "- `spawnllm` -> version 0.5.2\n</result>\n</task-notification>"
156
+ )
157
+ ): Allow(),
144
158
  },
145
159
  )
@@ -0,0 +1,5 @@
1
+ name = "steering"
2
+ version = "0.4.0"
3
+ description = "Steer the agent's judgment on what's worth fixing: prefer fundamental, first-principles fixes over band-aids, don't dismiss a real pre-existing issue, don't chase trivial type noise, and don't defer the requested fix by swapping in docs or a follow-up nobody asked for."
4
+ hooks = "."
5
+ nlp = true