claude-dev-env 1.38.0 → 1.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +10 -36
- package/_shared/pr-loop/audit-reply-template.md +147 -0
- package/_shared/pr-loop/fix-protocol.md +25 -4
- package/_shared/pr-loop/gh-payloads.md +37 -50
- package/_shared/pr-loop/scripts/code_rules_gate.py +0 -60
- package/_shared/pr-loop/scripts/config/post_audit_thread_constants.py +189 -0
- package/_shared/pr-loop/scripts/post_audit_thread.py +947 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +0 -19
- package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +923 -0
- package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +127 -0
- package/_shared/pr-loop/state-schema.md +1 -1
- package/agents/clean-coder.md +2 -2
- package/bin/install.mjs +6 -7
- package/bin/install.test.mjs +8 -0
- package/commands/doc-gist.md +16 -0
- package/commands/plan.md +0 -2
- package/commands/review-plan.md +1 -1
- package/docs/CODE_RULES.md +122 -2
- package/hooks/blocking/bot_mention_comment_blocker.py +75 -0
- package/hooks/blocking/code_rules_enforcer.py +1236 -161
- package/hooks/blocking/convergence_gate_blocker.py +130 -0
- package/hooks/blocking/destructive_command_blocker.py +74 -0
- package/hooks/blocking/gh_body_arg_blocker.py +30 -0
- package/hooks/blocking/md_to_html_blocker.py +119 -0
- package/hooks/blocking/test_bot_mention_comment_blocker.py +131 -0
- package/hooks/blocking/test_code_rules_enforcer.py +21 -0
- package/hooks/blocking/test_code_rules_enforcer_any_exempt_files.py +70 -0
- package/hooks/blocking/test_code_rules_enforcer_any_imports_and_cast.py +92 -0
- package/hooks/blocking/test_code_rules_enforcer_banned_import_alias.py +143 -0
- package/hooks/blocking/test_code_rules_enforcer_banned_prefixes.py +152 -0
- package/hooks/blocking/test_code_rules_enforcer_bare_except.py +120 -0
- package/hooks/blocking/test_code_rules_enforcer_boundary_types.py +175 -0
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -1
- package/hooks/blocking/test_code_rules_enforcer_collection_prefix.py +50 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_format.py +255 -0
- package/hooks/blocking/test_code_rules_enforcer_inline_tuple_string_magic.py +130 -0
- package/hooks/blocking/test_code_rules_enforcer_stub_implementations.py +141 -0
- package/hooks/blocking/test_code_rules_enforcer_test_branching.py +143 -0
- package/hooks/blocking/test_code_rules_enforcer_thin_wrapper_files.py +169 -0
- package/hooks/blocking/test_code_rules_enforcer_todo_markers.py +99 -0
- package/hooks/blocking/test_code_rules_enforcer_typed_dict_pairs.py +141 -0
- package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +158 -0
- package/hooks/blocking/test_convergence_gate_blocker.py +63 -0
- package/hooks/blocking/test_destructive_command_blocker.py +146 -0
- package/hooks/blocking/test_destructive_command_blocker_no_verify.py +102 -0
- package/hooks/blocking/test_gh_body_arg_blocker.py +45 -0
- package/hooks/blocking/test_md_to_html_blocker.py +317 -0
- package/hooks/config/any_type_config.py +7 -0
- package/hooks/config/banned_identifiers_constants.py +11 -0
- package/hooks/config/blocking_check_limits.py +38 -0
- package/hooks/config/bot_mention_comment_blocker_constants.py +20 -0
- package/hooks/config/code_rules_enforcer_constants.py +53 -0
- package/hooks/config/convergence_branch_constants.py +9 -0
- package/hooks/config/doc_gist_auto_publish_constants.py +18 -0
- package/hooks/config/html_companion_constants.py +20 -0
- package/hooks/config/inline_tuple_string_magic_constants.py +22 -0
- package/hooks/config/test_banned_identifiers_constants.py +17 -0
- package/hooks/hooks.json +28 -20
- package/hooks/pyproject.toml +69 -0
- package/hooks/validators/mypy_integration.py +47 -1
- package/hooks/validators/run_all_validators.py +3 -3
- package/hooks/validators/test_mypy_integration.py +50 -1
- package/hooks/workflow/doc_gist_auto_publish.py +144 -0
- package/hooks/workflow/md_to_html_companion.py +365 -0
- package/hooks/workflow/test_doc_gist_auto_publish.py +117 -0
- package/hooks/workflow/test_md_to_html_companion.py +452 -0
- package/package.json +1 -1
- package/rules/gh-body-file.md +2 -0
- package/scripts/Install-SweepEmptyDirs.ps1 +111 -0
- package/scripts/check.ps1 +106 -0
- package/scripts/config/timing.py +11 -0
- package/scripts/sweep_empty_dirs.py +138 -0
- package/scripts/sync_to_cursor/rules.py +1 -1
- package/scripts/test_sweep_empty_dirs.py +183 -0
- package/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +323 -0
- package/skills/_shared/pr-loop/scripts/_cli_utils.py +22 -0
- package/skills/_shared/pr-loop/scripts/_path_resolver.py +165 -0
- package/skills/_shared/pr-loop/scripts/_xml_utils.py +20 -0
- package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +182 -0
- package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +185 -0
- package/skills/_shared/pr-loop/scripts/config/__init__.py +0 -0
- package/skills/_shared/pr-loop/scripts/config/path_resolver_constants.py +78 -0
- package/skills/_shared/pr-loop/scripts/init_loop_state.py +135 -0
- package/skills/_shared/pr-loop/scripts/teardown_worktrees.py +175 -0
- package/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +182 -0
- package/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +206 -0
- package/skills/bugteam/CONSTRAINTS.md +21 -22
- package/skills/bugteam/EXAMPLES.md +3 -3
- package/skills/bugteam/PROMPTS.md +227 -67
- package/skills/bugteam/SKILL.md +114 -455
- package/skills/bugteam/reference/README.md +1 -1
- package/skills/bugteam/reference/audit-and-teammates.md +112 -39
- package/skills/bugteam/reference/audit-contract.md +4 -22
- package/skills/bugteam/reference/copilot-gap-analysis.md +8 -5
- package/skills/bugteam/reference/design-rationale.md +2 -2
- package/skills/bugteam/reference/github-pr-reviews.md +50 -57
- package/skills/bugteam/reference/obstacles/audit-assign-ids.md +13 -0
- package/skills/bugteam/reference/obstacles/audit-capture-excerpts.md +13 -0
- package/skills/bugteam/reference/obstacles/audit-walk-categories.md +13 -0
- package/skills/bugteam/reference/obstacles/audit-write-xml.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-append-summary.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-apply-fixes.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-git-add-commit.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-git-push.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-post-reply.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-publish-summary.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-py-compile.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-read-files.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-resolve-thread.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-test-suite.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-violation-count.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-write-xml.md +13 -0
- package/skills/bugteam/reference/team-setup.md +106 -9
- package/skills/bugteam/reference/teardown-publish-permissions.md +39 -8
- package/skills/bugteam/scripts/README.md +60 -0
- package/skills/bugteam/scripts/_claude_permissions_common.py +358 -0
- package/skills/bugteam/scripts/bugteam_code_rules_gate.py +976 -0
- package/skills/bugteam/scripts/bugteam_fix_hookspath.py +375 -0
- package/skills/bugteam/scripts/bugteam_preflight.py +294 -0
- package/skills/bugteam/scripts/config/bugteam_code_rules_gate_constants.py +25 -0
- package/skills/bugteam/scripts/config/bugteam_fix_hookspath_constants.py +26 -0
- package/skills/bugteam/scripts/config/bugteam_preflight_constants.py +35 -0
- package/skills/bugteam/scripts/config/claude_permissions_common_constants.py +20 -0
- package/skills/bugteam/scripts/config/probe_code_rules_enforcer_check_constants.py +12 -0
- package/skills/bugteam/scripts/config/windows_safe_rmtree_constants.py +7 -0
- package/skills/bugteam/scripts/grant_project_claude_permissions.py +175 -0
- package/skills/bugteam/scripts/probe_code_rules_enforcer_check.py +107 -0
- package/skills/bugteam/scripts/revoke_project_claude_permissions.py +220 -0
- package/skills/bugteam/scripts/test__claude_permissions_common.py +112 -0
- package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +400 -0
- package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +384 -0
- package/skills/bugteam/scripts/test_bugteam_preflight.py +268 -0
- package/skills/bugteam/scripts/test_claude_permissions_common.py +195 -0
- package/skills/bugteam/scripts/test_grant_project_claude_permissions.py +55 -0
- package/skills/bugteam/scripts/test_probe_code_rules_enforcer_check.py +76 -0
- package/skills/bugteam/scripts/test_revoke_project_claude_permissions.py +55 -0
- package/skills/bugteam/scripts/test_windows_safe_rmtree.py +108 -0
- package/skills/bugteam/scripts/windows_safe_rmtree.py +100 -0
- package/skills/bugteam/test_skill_additions.py +1 -11
- package/skills/code/SKILL.md +176 -0
- package/skills/doc-gist/SKILL.md +99 -0
- package/skills/doc-gist/references/examples/01-exploration-code-approaches.html +453 -0
- package/skills/doc-gist/references/examples/02-exploration-visual-designs.html +515 -0
- package/skills/doc-gist/references/examples/03-code-review-pr.html +638 -0
- package/skills/doc-gist/references/examples/04-code-understanding.html +491 -0
- package/skills/doc-gist/references/examples/05-design-system.html +629 -0
- package/skills/doc-gist/references/examples/06-component-variants.html +605 -0
- package/skills/doc-gist/references/examples/07-prototype-animation.html +455 -0
- package/skills/doc-gist/references/examples/08-prototype-interaction.html +396 -0
- package/skills/doc-gist/references/examples/09-slide-deck.html +592 -0
- package/skills/doc-gist/references/examples/10-svg-illustrations.html +492 -0
- package/skills/doc-gist/references/examples/11-status-report.html +528 -0
- package/skills/doc-gist/references/examples/12-incident-report.html +596 -0
- package/skills/doc-gist/references/examples/13-flowchart-diagram.html +395 -0
- package/skills/doc-gist/references/examples/14-research-feature-explainer.html +381 -0
- package/skills/doc-gist/references/examples/15-research-concept-explainer.html +368 -0
- package/skills/doc-gist/references/examples/16-implementation-plan.html +702 -0
- package/skills/doc-gist/references/examples/17-pr-writeup.html +595 -0
- package/skills/doc-gist/references/examples/18-editor-triage-board.html +573 -0
- package/skills/doc-gist/references/examples/19-editor-feature-flags.html +663 -0
- package/skills/doc-gist/references/examples/20-editor-prompt-tuner.html +722 -0
- package/skills/doc-gist/references/examples/README.md +5 -0
- package/skills/doc-gist/scripts/config/__init__.py +0 -0
- package/skills/doc-gist/scripts/config/gist_upload_constants.py +16 -0
- package/skills/doc-gist/scripts/gist_upload.py +177 -0
- package/skills/doc-gist/scripts/test_gist_upload.py +51 -0
- package/skills/findbugs/SKILL.md +68 -2
- package/skills/monitor-open-prs/SKILL.md +13 -32
- package/skills/monitor-open-prs/test_skill_contract.py +0 -11
- package/skills/pr-consistency-audit/SKILL.md +112 -0
- package/skills/pr-consistency-audit/reference/detection-rules.md +96 -0
- package/skills/pr-consistency-audit/reference/illustrations.md +78 -0
- package/skills/pr-converge/SKILL.md +227 -23
- package/skills/pr-converge/config/__init__.py +0 -0
- package/skills/pr-converge/config/constants.py +62 -0
- package/skills/pr-converge/reference/convergence-gates.md +138 -44
- package/skills/pr-converge/reference/examples.md +43 -11
- package/skills/pr-converge/reference/fix-protocol.md +6 -5
- package/skills/pr-converge/reference/ground-rules.md +5 -3
- package/skills/pr-converge/reference/multi-pr-orchestration.md +44 -19
- package/skills/pr-converge/reference/obstacles/fix-post-replies.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-publish-summary.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-push.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-read-filelines.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-reset-state.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-resolve-threads.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-spawn-clean-coder.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-stage-commit.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-trigger-bugbot.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-write-test.md +13 -0
- package/skills/pr-converge/reference/per-tick.md +90 -31
- package/skills/pr-converge/reference/state-schema.md +22 -1
- package/skills/pr-converge/reference/stop-conditions.md +9 -7
- package/skills/pr-converge/scripts/README.md +34 -46
- package/skills/pr-converge/scripts/check_bugbot_ci.py +174 -0
- package/skills/pr-converge/scripts/check_convergence.py +497 -0
- package/skills/pr-converge/scripts/check_pending_reviews.py +154 -0
- package/skills/pr-converge/scripts/config/pr_converge_constants.py +118 -0
- package/skills/pr-converge/scripts/fetch_copilot_reviews.py +134 -0
- package/skills/pr-converge/scripts/post_fix_reply.py +168 -0
- package/skills/pr-converge/workflows/schedule-wakeup-loop.md +5 -12
- package/skills/qbug/SKILL.md +132 -27
- package/skills/session-log/SKILL.md +216 -114
- package/skills/session-tidy/SKILL.md +1 -1
- package/skills/skill-builder/SKILL.md +138 -56
- package/skills/skill-builder/references/delegation-map.md +72 -113
- package/skills/skill-builder/references/progressive-disclosure.md +122 -0
- package/skills/skill-builder/references/self-audit-checklist.md +92 -0
- package/skills/skill-builder/references/skill-types.md +228 -0
- package/skills/skill-builder/references/thariq-x-post-skills.json +33 -0
- package/skills/skill-builder/templates/gap-analysis.md +15 -8
- package/skills/skill-builder/workflows/improve-skill.md +86 -57
- package/skills/skill-builder/workflows/new-skill.md +80 -168
- package/skills/skill-builder/workflows/polish-skill.md +78 -54
- package/skills/structure-prompt/SKILL.md +50 -0
- package/skills/structure-prompt/reference/adversarial-tuning.md +62 -0
- package/skills/structure-prompt/reference/block-classification.md +27 -0
- package/skills/structure-prompt/reference/canonical-case.md +48 -0
- package/skills/structure-prompt/reference/citation-depth.md +70 -0
- package/skills/structure-prompt/reference/cleanup.md +33 -0
- package/skills/structure-prompt/reference/constraints.md +33 -0
- package/skills/structure-prompt/reference/directives.md +37 -0
- package/skills/structure-prompt/reference/examples.md +72 -0
- package/skills/structure-prompt/reference/instantiation.md +51 -0
- package/skills/structure-prompt/reference/output-contract.md +72 -0
- package/skills/structure-prompt/reference/per-category.md +23 -0
- package/skills/structure-prompt/reference/persona.md +38 -0
- package/skills/structure-prompt/reference/research.md +33 -0
- package/skills/structure-prompt/reference/structure.md +28 -0
- package/agents/code-standards-agent.md +0 -93
- package/agents/groq-coder.md +0 -113
- package/agents/plan-executor.md +0 -226
- package/agents/project-docs-analyzer.md +0 -53
- package/agents/project-structure-organizer-agent.md +0 -72
- package/agents/skill-to-agent-converter.md +0 -370
- package/agents/skill-writer-agent.md +0 -470
- package/agents/user-docs-writer.md +0 -67
- package/agents/workflow-visual-documenter.md +0 -82
- package/commands/readability-review.md +0 -20
- package/hooks/mypy.ini +0 -2
- package/hooks/notification/attention_needed_notify.py +0 -71
- package/hooks/notification/claude_notification_handler.py +0 -67
- package/hooks/notification/notification_utils.py +0 -267
- package/hooks/notification/subagent_complete_notify.py +0 -381
- package/hooks/notification/test_attention_needed_notify.py +0 -47
- package/hooks/notification/test_claude_notification_handler.py +0 -54
- package/hooks/notification/test_notification_utils.py +0 -91
- package/hooks/notification/test_subagent_complete_notify.py +0 -79
- package/scripts/config/groq_bugteam_config.py +0 -230
- package/scripts/config/test_groq_bugteam_config.py +0 -83
- package/scripts/config/test_spec_implementer_prompt.py +0 -32
- package/scripts/groq_bugteam.README.md +0 -131
- package/scripts/groq_bugteam.py +0 -647
- package/scripts/groq_bugteam_dotenv.py +0 -40
- package/scripts/groq_bugteam_spec.py +0 -226
- package/scripts/test_groq_bugteam.py +0 -529
- package/scripts/test_groq_bugteam_apply_fix_from_spec.py +0 -426
- package/scripts/test_groq_bugteam_dotenv.py +0 -66
- package/scripts/test_groq_bugteam_spec.py +0 -338
- package/skills/bugteam/SKILL_EVALS.md +0 -309
- package/skills/dream/SKILL.md +0 -118
- package/skills/ingest/SKILL.md +0 -40
- package/skills/npm-creator/SKILL.md +0 -187
- package/skills/readability-review/SKILL.md +0 -127
- package/skills/resume-review/SKILL.md +0 -261
- package/skills/rule-audit/SKILL.md +0 -307
- package/skills/rule-creator/SKILL.md +0 -150
- package/skills/searching-obsidian-vault/SKILL.md +0 -131
- package/skills/skill-writer/REFERENCE.md +0 -284
- package/skills/skill-writer/SKILL.md +0 -222
- package/skills/tdd-team/SKILL.md +0 -128
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Centralized configuration for hook tooling.
|
|
2
|
+
#
|
|
3
|
+
# Replaces the standalone hooks/mypy.ini and provides a single config surface
|
|
4
|
+
# for mypy, ruff, and pytest invocations rooted under packages/claude-dev-env/hooks/.
|
|
5
|
+
# C3 (mypy_integration walk-up) finds this file by walking parents from each
|
|
6
|
+
# checked .py file and matching on the [tool.mypy] table.
|
|
7
|
+
|
|
8
|
+
[tool.mypy]
|
|
9
|
+
mypy_path = "."
|
|
10
|
+
python_version = "3.13"
|
|
11
|
+
ignore_missing_imports = true
|
|
12
|
+
warn_unused_ignores = true
|
|
13
|
+
warn_redundant_casts = true
|
|
14
|
+
no_implicit_optional = true
|
|
15
|
+
strict_equality = true
|
|
16
|
+
|
|
17
|
+
[[tool.mypy.overrides]]
|
|
18
|
+
module = "tests.*"
|
|
19
|
+
disallow_untyped_defs = false
|
|
20
|
+
|
|
21
|
+
[tool.ruff]
|
|
22
|
+
line-length = 100
|
|
23
|
+
target-version = "py313"
|
|
24
|
+
extend-exclude = [
|
|
25
|
+
".audit-trail",
|
|
26
|
+
".claude",
|
|
27
|
+
"node_modules",
|
|
28
|
+
"__pycache__",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[tool.ruff.lint]
|
|
32
|
+
select = [
|
|
33
|
+
"E",
|
|
34
|
+
"F",
|
|
35
|
+
"W",
|
|
36
|
+
"I",
|
|
37
|
+
"B",
|
|
38
|
+
"UP",
|
|
39
|
+
"SIM",
|
|
40
|
+
"PL",
|
|
41
|
+
]
|
|
42
|
+
ignore = [
|
|
43
|
+
"E501",
|
|
44
|
+
"PLR0913",
|
|
45
|
+
"PLR2004",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
[tool.ruff.lint.per-file-ignores]
|
|
49
|
+
"test_*.py" = ["B011", "PLR2004"]
|
|
50
|
+
"*_test.py" = ["B011", "PLR2004"]
|
|
51
|
+
"conftest.py" = ["B011"]
|
|
52
|
+
|
|
53
|
+
[tool.coverage.run]
|
|
54
|
+
branch = true
|
|
55
|
+
omit = [
|
|
56
|
+
"test_*.py",
|
|
57
|
+
"*_test.py",
|
|
58
|
+
"conftest.py",
|
|
59
|
+
"__pycache__/*",
|
|
60
|
+
".audit-trail/*",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[tool.coverage.report]
|
|
64
|
+
exclude_lines = [
|
|
65
|
+
"pragma: no cover",
|
|
66
|
+
"raise NotImplementedError",
|
|
67
|
+
"if TYPE_CHECKING:",
|
|
68
|
+
"if __name__ == .__main__.:",
|
|
69
|
+
]
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Mypy integration for static type checking."""
|
|
2
2
|
|
|
3
3
|
import subprocess
|
|
4
|
+
import tomllib
|
|
4
5
|
from dataclasses import dataclass
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
|
|
@@ -25,6 +26,43 @@ def check_mypy_available() -> bool:
|
|
|
25
26
|
return False
|
|
26
27
|
|
|
27
28
|
|
|
29
|
+
def _pyproject_contains_tool_mypy(pyproject_path: Path) -> bool:
|
|
30
|
+
try:
|
|
31
|
+
with pyproject_path.open("rb") as readable_handle:
|
|
32
|
+
parsed_toml = tomllib.load(readable_handle)
|
|
33
|
+
except (OSError, tomllib.TOMLDecodeError):
|
|
34
|
+
return False
|
|
35
|
+
tool_table = parsed_toml.get("tool", {})
|
|
36
|
+
return isinstance(tool_table, dict) and "mypy" in tool_table
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def find_pyproject_with_mypy_config(starting_file: Path) -> Path | None:
|
|
40
|
+
"""Walk up from a starting file to locate a pyproject.toml that configures mypy.
|
|
41
|
+
|
|
42
|
+
The walk skips pyproject.toml files that do not declare a [tool.mypy]
|
|
43
|
+
table so that an unrelated package config (for example, a project root
|
|
44
|
+
pyproject.toml) does not shadow a hook-tree pyproject.toml that
|
|
45
|
+
actually configures the type checker.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
starting_file: The file (or directory) the walk begins from. The walk
|
|
49
|
+
climbs through every parent directory in order.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
The first ``pyproject.toml`` Path that declares a ``[tool.mypy]``
|
|
53
|
+
table, or ``None`` when no such file exists between ``starting_file``
|
|
54
|
+
and the filesystem root.
|
|
55
|
+
"""
|
|
56
|
+
pyproject_filename_for_lookup = "pyproject.toml"
|
|
57
|
+
resolved_starting_file = starting_file.resolve()
|
|
58
|
+
current_directory = resolved_starting_file.parent if resolved_starting_file.is_file() else resolved_starting_file
|
|
59
|
+
for each_candidate_directory in [current_directory, *current_directory.parents]:
|
|
60
|
+
candidate_pyproject = each_candidate_directory / pyproject_filename_for_lookup
|
|
61
|
+
if candidate_pyproject.is_file() and _pyproject_contains_tool_mypy(candidate_pyproject):
|
|
62
|
+
return candidate_pyproject
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
|
|
28
66
|
def run_mypy_check(files: list[Path]) -> MypyResult:
|
|
29
67
|
"""Run mypy on files."""
|
|
30
68
|
if not files:
|
|
@@ -37,8 +75,16 @@ def run_mypy_check(files: list[Path]) -> MypyResult:
|
|
|
37
75
|
if not py_files:
|
|
38
76
|
return MypyResult(passed=True, output="No Python files", error_count=0)
|
|
39
77
|
|
|
78
|
+
config_argument: list[str] = []
|
|
79
|
+
for each_py_file in py_files:
|
|
80
|
+
discovered_pyproject = find_pyproject_with_mypy_config(Path(each_py_file))
|
|
81
|
+
if discovered_pyproject is not None:
|
|
82
|
+
config_argument = ["--config-file", str(discovered_pyproject)]
|
|
83
|
+
break
|
|
84
|
+
|
|
40
85
|
result = subprocess.run(
|
|
41
|
-
["mypy", "--ignore-missing-imports", "--no-error-summary"]
|
|
86
|
+
["mypy", *config_argument, "--ignore-missing-imports", "--no-error-summary"]
|
|
87
|
+
+ py_files,
|
|
42
88
|
capture_output=True,
|
|
43
89
|
text=True,
|
|
44
90
|
)
|
|
@@ -14,7 +14,7 @@ import time
|
|
|
14
14
|
from dataclasses import dataclass
|
|
15
15
|
from datetime import datetime
|
|
16
16
|
from pathlib import Path
|
|
17
|
-
from typing import
|
|
17
|
+
from typing import Callable, Dict, List, Optional, Tuple
|
|
18
18
|
|
|
19
19
|
from .health_check import get_system_health, get_validator_version, print_health_report
|
|
20
20
|
from .mypy_integration import check_mypy_available, run_mypy_check
|
|
@@ -179,10 +179,10 @@ def print_header() -> None:
|
|
|
179
179
|
|
|
180
180
|
|
|
181
181
|
def build_json_output(
|
|
182
|
-
results: List[
|
|
182
|
+
results: List["ValidatorResult"],
|
|
183
183
|
metrics: TimingMetrics,
|
|
184
184
|
include_timing: bool,
|
|
185
|
-
) -> Dict[str,
|
|
185
|
+
) -> Dict[str, object]:
|
|
186
186
|
"""Build JSON output dictionary.
|
|
187
187
|
|
|
188
188
|
Args:
|
|
@@ -3,7 +3,12 @@
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from unittest.mock import patch
|
|
5
5
|
|
|
6
|
-
from .mypy_integration import
|
|
6
|
+
from .mypy_integration import (
|
|
7
|
+
MypyResult,
|
|
8
|
+
check_mypy_available,
|
|
9
|
+
find_pyproject_with_mypy_config,
|
|
10
|
+
run_mypy_check,
|
|
11
|
+
)
|
|
7
12
|
|
|
8
13
|
|
|
9
14
|
def test_mypy_result_dataclass() -> None:
|
|
@@ -25,3 +30,47 @@ def test_run_mypy_check_returns_passed_for_empty_files() -> None:
|
|
|
25
30
|
result = run_mypy_check([])
|
|
26
31
|
assert result.passed is True
|
|
27
32
|
assert "No files" in result.output
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_find_pyproject_returns_pyproject_with_tool_mypy(tmp_path: Path) -> None:
|
|
36
|
+
project_root = tmp_path / "myproj"
|
|
37
|
+
nested_dir = project_root / "src" / "deep"
|
|
38
|
+
nested_dir.mkdir(parents=True)
|
|
39
|
+
project_pyproject = project_root / "pyproject.toml"
|
|
40
|
+
project_pyproject.write_text("[tool.mypy]\nignore_missing_imports = true\n")
|
|
41
|
+
target_file = nested_dir / "module.py"
|
|
42
|
+
target_file.write_text("x: int = 1\n")
|
|
43
|
+
found_path = find_pyproject_with_mypy_config(target_file)
|
|
44
|
+
assert found_path == project_pyproject
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_find_pyproject_skips_pyproject_without_tool_mypy(tmp_path: Path) -> None:
|
|
48
|
+
outer_root = tmp_path / "outer"
|
|
49
|
+
inner_root = outer_root / "inner"
|
|
50
|
+
inner_root.mkdir(parents=True)
|
|
51
|
+
outer_pyproject = outer_root / "pyproject.toml"
|
|
52
|
+
outer_pyproject.write_text("[tool.mypy]\nignore_missing_imports = true\n")
|
|
53
|
+
inner_pyproject = inner_root / "pyproject.toml"
|
|
54
|
+
inner_pyproject.write_text("[project]\nname = 'inner-package'\n")
|
|
55
|
+
target_file = inner_root / "module.py"
|
|
56
|
+
target_file.write_text("y: str = 'hello'\n")
|
|
57
|
+
found_path = find_pyproject_with_mypy_config(target_file)
|
|
58
|
+
assert found_path == outer_pyproject
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_find_pyproject_returns_none_when_no_match(tmp_path: Path) -> None:
|
|
62
|
+
isolated_dir = tmp_path / "isolated"
|
|
63
|
+
isolated_dir.mkdir()
|
|
64
|
+
target_file = isolated_dir / "module.py"
|
|
65
|
+
target_file.write_text("z: float = 1.0\n")
|
|
66
|
+
assert find_pyproject_with_mypy_config(target_file) is None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_find_pyproject_handles_malformed_toml(tmp_path: Path) -> None:
|
|
70
|
+
project_root = tmp_path / "broken"
|
|
71
|
+
project_root.mkdir()
|
|
72
|
+
project_pyproject = project_root / "pyproject.toml"
|
|
73
|
+
project_pyproject.write_text("[tool.mypy\nbroken = true\n")
|
|
74
|
+
target_file = project_root / "module.py"
|
|
75
|
+
target_file.write_text("a = 1\n")
|
|
76
|
+
assert find_pyproject_with_mypy_config(target_file) is None
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""PostToolUse hook: auto-publish HTML files marked with the doc-gist sentinel.
|
|
3
|
+
|
|
4
|
+
When Claude writes an .html file containing the marker
|
|
5
|
+
`<!-- @publish-as-gist -->`, this hook invokes the doc-gist `gist_upload.py`
|
|
6
|
+
script against that file and prints the resulting gist + htmlpreview URLs
|
|
7
|
+
into the harness output so Claude can quote them back to the user.
|
|
8
|
+
|
|
9
|
+
The marker is the on-demand trigger: HTML files without it are ignored. This
|
|
10
|
+
keeps the hook silent for HTML that is part of code (React components, test
|
|
11
|
+
fixtures, scraped pages) and active only for HTML Claude intentionally
|
|
12
|
+
designed as a shareable artifact.
|
|
13
|
+
|
|
14
|
+
The hook does not modify the file, does not block the write, and exits 0
|
|
15
|
+
even on upload failure (failure is logged to stderr but does not break
|
|
16
|
+
Claude's flow).
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import logging
|
|
23
|
+
import subprocess
|
|
24
|
+
import sys
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import IO
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
logging.basicConfig(stream=sys.stderr, level=logging.WARNING, format="%(message)s")
|
|
30
|
+
|
|
31
|
+
_hooks_directory = str(Path(__file__).resolve().parent.parent)
|
|
32
|
+
if _hooks_directory not in sys.path:
|
|
33
|
+
sys.path.insert(0, _hooks_directory)
|
|
34
|
+
|
|
35
|
+
from config.doc_gist_auto_publish_constants import ( # noqa: E402
|
|
36
|
+
ALL_TARGET_TOOL_NAMES,
|
|
37
|
+
HOOK_SUBPROCESS_TIMEOUT_SECONDS,
|
|
38
|
+
HTML_FILE_EXTENSION,
|
|
39
|
+
PUBLISH_SENTINEL,
|
|
40
|
+
UPLOAD_SCRIPT_RELATIVE_PATH,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _read_hook_payload() -> dict[str, object]:
|
|
45
|
+
"""Read the PostToolUse JSON payload from stdin. Empty/invalid payload exits clean."""
|
|
46
|
+
try:
|
|
47
|
+
payload = json.load(sys.stdin)
|
|
48
|
+
except json.JSONDecodeError as decode_error:
|
|
49
|
+
logging.warning("doc_gist_auto_publish: invalid JSON payload: %s", decode_error)
|
|
50
|
+
sys.exit(0)
|
|
51
|
+
if not isinstance(payload, dict):
|
|
52
|
+
sys.exit(0)
|
|
53
|
+
return payload
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _resolve_target_path(payload: dict[str, object]) -> Path | None:
|
|
57
|
+
"""Extract a writable .html file path from the hook payload, or None to skip."""
|
|
58
|
+
if payload.get("tool_name") not in ALL_TARGET_TOOL_NAMES:
|
|
59
|
+
return None
|
|
60
|
+
tool_input = payload.get("tool_input", {})
|
|
61
|
+
if not isinstance(tool_input, dict):
|
|
62
|
+
return None
|
|
63
|
+
raw_path = tool_input.get("file_path", "")
|
|
64
|
+
if not isinstance(raw_path, str) or not raw_path.lower().endswith(HTML_FILE_EXTENSION):
|
|
65
|
+
return None
|
|
66
|
+
candidate = Path(raw_path)
|
|
67
|
+
if not candidate.is_file():
|
|
68
|
+
return None
|
|
69
|
+
return candidate
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _has_publish_sentinel(target_path: Path) -> bool:
|
|
73
|
+
"""Read the HTML and check for the publish marker."""
|
|
74
|
+
try:
|
|
75
|
+
contents = target_path.read_text(encoding="utf-8", errors="replace")
|
|
76
|
+
except OSError:
|
|
77
|
+
logging.warning("doc_gist_auto_publish: cannot read %s", target_path)
|
|
78
|
+
return False
|
|
79
|
+
return PUBLISH_SENTINEL in contents
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _resolve_upload_script() -> Path:
|
|
83
|
+
"""Locate the gist_upload.py script bundled with the doc-gist skill."""
|
|
84
|
+
plugin_root_argument = sys.argv[1] if len(sys.argv) > 1 else None
|
|
85
|
+
if plugin_root_argument:
|
|
86
|
+
return Path(plugin_root_argument) / UPLOAD_SCRIPT_RELATIVE_PATH
|
|
87
|
+
plugin_root_directory = Path(__file__).resolve().parent.parent.parent
|
|
88
|
+
return plugin_root_directory / UPLOAD_SCRIPT_RELATIVE_PATH
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _invoke_upload(
|
|
92
|
+
script_path: Path,
|
|
93
|
+
target_path: Path,
|
|
94
|
+
out_stream: IO[str],
|
|
95
|
+
err_stream: IO[str],
|
|
96
|
+
) -> None:
|
|
97
|
+
"""Run gist_upload.py against target_path and surface its URLs to the harness."""
|
|
98
|
+
try:
|
|
99
|
+
completed = subprocess.run(
|
|
100
|
+
[sys.executable, str(script_path), "--input", str(target_path), "--no-open"],
|
|
101
|
+
check=False,
|
|
102
|
+
capture_output=True,
|
|
103
|
+
text=True,
|
|
104
|
+
encoding="utf-8",
|
|
105
|
+
errors="replace",
|
|
106
|
+
timeout=HOOK_SUBPROCESS_TIMEOUT_SECONDS,
|
|
107
|
+
)
|
|
108
|
+
except FileNotFoundError:
|
|
109
|
+
logging.warning("doc_gist_auto_publish: gist_upload script not found: %s", script_path)
|
|
110
|
+
return
|
|
111
|
+
except subprocess.TimeoutExpired:
|
|
112
|
+
logging.warning("doc_gist_auto_publish: gist_upload timed out after %ds", HOOK_SUBPROCESS_TIMEOUT_SECONDS)
|
|
113
|
+
return
|
|
114
|
+
if completed.stderr:
|
|
115
|
+
err_stream.write(completed.stderr)
|
|
116
|
+
if completed.returncode != 0:
|
|
117
|
+
logging.warning("doc_gist_auto_publish: gist_upload exited %d", completed.returncode)
|
|
118
|
+
return
|
|
119
|
+
if completed.stdout:
|
|
120
|
+
out_stream.write(completed.stdout)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def main() -> None:
|
|
124
|
+
"""Entry point — process one PostToolUse event, exit 0 always.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
None — exits 0 on success or when no action is needed.
|
|
128
|
+
"""
|
|
129
|
+
payload = _read_hook_payload()
|
|
130
|
+
target_path = _resolve_target_path(payload)
|
|
131
|
+
if target_path is None:
|
|
132
|
+
sys.exit(0)
|
|
133
|
+
if not _has_publish_sentinel(target_path):
|
|
134
|
+
sys.exit(0)
|
|
135
|
+
script_path = _resolve_upload_script()
|
|
136
|
+
if not script_path.is_file():
|
|
137
|
+
logging.warning("doc_gist_auto_publish: missing %s", script_path)
|
|
138
|
+
sys.exit(0)
|
|
139
|
+
_invoke_upload(script_path, target_path, sys.stdout, sys.stderr)
|
|
140
|
+
sys.exit(0)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
if __name__ == "__main__":
|
|
144
|
+
main()
|