claude-dev-env 2.11.0 → 2.13.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/_shared/advisor/reference/sol-rung.md +5 -2
- package/_shared/advisor/reference/warm-up.md +1 -0
- package/_shared/advisor/scripts/codex_sol_advisor.py +96 -31
- package/_shared/advisor/scripts/config/advisor_scripts_constants/sol_advisor_constants.py +5 -0
- package/_shared/advisor/scripts/tests/test_codex_sol_advisor.py +173 -11
- package/_shared/pr-loop/scripts/code_rules_gate.py +2 -3
- package/_shared/pr-loop/scripts/test_code_rules_gate.py +0 -26
- package/agents/CLAUDE.md +1 -2
- package/agents/pr-description-writer.md +11 -81
- package/bin/install.mjs +48 -9
- package/bin/install.prune.test.mjs +26 -0
- package/bin/install.settings-defaults.test.mjs +60 -33
- package/bin/install.test.mjs +2 -4
- package/commands/CLAUDE.md +1 -0
- package/commands/sr-loop.md +52 -0
- package/docs/CLAUDE.md +0 -1
- package/docs/references/CLAUDE.md +0 -1
- package/hooks/CLAUDE.md +1 -1
- package/hooks/blocking/CLAUDE.md +2 -27
- package/hooks/blocking/config/CLAUDE.md +1 -13
- package/hooks/blocking/conventional_pr_title_gate.py +1 -2
- package/hooks/blocking/send_user_file_open_locally_blocker.py +1 -1
- package/hooks/blocking/stop_dispatcher.py +4 -4
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +0 -3
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +6 -7
- package/hooks/blocking/test_send_user_file_open_locally_blocker.py +4 -3
- package/hooks/blocking/test_shared_stdin_adoption.py +0 -2
- package/hooks/diagnostic/CLAUDE.md +3 -3
- package/hooks/diagnostic/hook_log_extractor.py +2 -36
- package/hooks/diagnostic/hook_log_stop_wrapper.py +6 -155
- package/hooks/diagnostic/test_hook_log_extractor.py +8 -21
- package/hooks/diagnostic/test_hook_log_stop_wrapper.py +3 -331
- package/hooks/git-hooks/git_hooks_constants/__init__.py +0 -6
- package/hooks/git-hooks/pre_push.py +3 -85
- package/hooks/git-hooks/test_pre_push.py +0 -130
- package/hooks/hooks.json +0 -35
- package/hooks/hooks_constants/CLAUDE.md +0 -9
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +0 -11
- package/hooks/hooks_constants/convergence_branch_constants.py +0 -1
- package/hooks/hooks_constants/fable_spawn_gate_constants.py +3 -4
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +0 -8
- package/hooks/hooks_constants/send_user_file_open_locally_blocker_constants.py +1 -6
- package/hooks/hooks_constants/stop_dispatcher_constants.py +0 -1
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +1 -26
- package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +0 -5
- package/hooks/hooks_constants/test_stop_dispatcher_constants.py +0 -1
- package/hooks/lifecycle/CLAUDE.md +1 -3
- package/package.json +1 -1
- package/rules/CLAUDE.md +0 -2
- package/rules/gh-cli-conventions.md +0 -1
- package/rules/git-workflow.md +1 -3
- package/scripts/CLAUDE.md +0 -1
- package/scripts/_code_review_test_support.py +0 -95
- package/scripts/dev_env_scripts_constants/code_review_constants.py +9 -98
- package/scripts/dev_env_scripts_constants/test_code_review_constants.py +1 -44
- package/scripts/invoke_code_review.py +8 -451
- package/scripts/test_invoke_code_review.py +5 -61
- package/scripts/test_invoke_code_review_cli.py +1 -45
- package/scripts/test_invoke_code_review_contract.py +1 -118
- package/settings.json +1 -10
- package/skills/CLAUDE.md +8 -0
- package/skills/autoconverge/reference/CLAUDE.md +1 -1
- package/skills/autoconverge/reference/convergence.md +7 -7
- package/skills/autoconverge/reference/gotchas.md +3 -10
- package/skills/autoconverge/workflow/converge.contract.test.mjs +53 -1388
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +4 -3
- package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +33 -493
- package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +15 -97
- package/skills/autoconverge/workflow/converge.mjs +201 -238
- package/skills/autoconverge/workflow/converge.precatch.test.mjs +1 -1
- package/skills/bugteam/CONSTRAINTS.md +1 -1
- package/skills/bugteam/SKILL.md +9 -0
- package/skills/bugteam/reference/audit-and-teammates.md +10 -6
- package/skills/bugteam/test_skill_additions.py +9 -0
- package/skills/comments/SKILL.md +65 -0
- package/skills/descriptions/SKILL.md +51 -0
- package/skills/emergencies/SKILL.md +42 -0
- package/skills/imagegen/SKILL.md +21 -0
- package/skills/imagegen/scripts/config/__init__.py +1 -0
- package/skills/imagegen/scripts/config/constants.py +28 -0
- package/skills/imagegen/scripts/imagegen.py +45 -0
- package/skills/imagegen/scripts/imagegen_core.py +528 -0
- package/skills/imagegen/scripts/test_imagegen.py +204 -0
- package/skills/orchestrator/SKILL.md +2 -4
- package/skills/plan-to-pr/SKILL.md +6 -7
- package/skills/plan-to-pr/reference/final-validation-tasks.md +1 -1
- package/skills/plan-to-pr/reference/process-inventory.md +2 -2
- package/skills/plan-to-pr/reference/review-loop.md +2 -2
- package/skills/plan-to-pr/reference/run-record.schema.json +1 -1
- package/skills/plan-to-pr/reference/task-seeds.md +1 -1
- package/skills/plan-to-pr/reference/task-ticket.md +3 -4
- package/skills/plan-to-pr/scripts/config/constants.py +1 -2
- package/skills/plan-to-pr/scripts/test_validate_protocol.py +1 -1
- package/skills/plan-to-pr/test_skill_contract.py +2 -2
- package/skills/pr-converge/SKILL.md +13 -16
- package/skills/pr-converge/reference/progress-checklist.md +6 -5
- package/skills/pr-converge/reference/state-schema.md +1 -27
- package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
- package/skills/pr-loop-cloud-transport/reference/identity-and-hooks.md +3 -3
- package/skills/pr-loop-lifecycle/SKILL.md +1 -1
- package/skills/pr-loop-lifecycle/reference/teardown-publish-permissions.md +1 -1
- package/skills/prototype/SKILL.md +4 -4
- package/skills/prototype/reference/honest-limitations.md +4 -4
- package/skills/prototype/reference/promotion-tasks.md +2 -2
- package/skills/prototype/workflows/promotion.md +4 -6
- package/skills/reviews/SKILL.md +85 -0
- package/skills/small-cl/SKILL.md +52 -0
- package/skills/usage-pause/SKILL.md +1 -1
- package/skills/usage-pause/scripts/resolve_usage_window.py +32 -4
- package/skills/usage-pause/scripts/test_resolve_usage_window.py +26 -0
- package/skills/usage-pause/scripts/usage_pause_constants/resolve_usage_window_constants.py +3 -1
- package/agents/code-verifier.md +0 -74
- package/docs/PR_DESCRIPTION_GUIDE.md +0 -157
- package/docs/references/code-review-enforcement.md +0 -132
- package/hooks/blocking/code_review_enforcement_config_bootstrap.py +0 -53
- package/hooks/blocking/code_review_gate_deny.py +0 -74
- package/hooks/blocking/code_review_pr_create_gate.py +0 -198
- package/hooks/blocking/code_review_push_gate.py +0 -145
- package/hooks/blocking/code_review_stamp_directory_write_blocker.py +0 -348
- package/hooks/blocking/code_review_stamp_store.py +0 -233
- package/hooks/blocking/code_review_stamp_write_blocker_parts/__init__.py +0 -7
- package/hooks/blocking/code_review_stamp_write_blocker_parts/conftest.py +0 -15
- package/hooks/blocking/code_review_stamp_write_blocker_parts/obfuscated_stamp_path_reference.py +0 -212
- package/hooks/blocking/code_review_stamp_write_blocker_parts/split_directory_change_into_stamp.py +0 -138
- package/hooks/blocking/code_review_stamp_write_blocker_parts/test_obfuscated_stamp_path_reference.py +0 -49
- package/hooks/blocking/code_review_stamp_write_blocker_parts/test_split_directory_change_into_stamp.py +0 -38
- package/hooks/blocking/code_verifier_spawn_preflight_gate.py +0 -483
- package/hooks/blocking/config/code_review_enforcement_constants.py +0 -143
- package/hooks/blocking/config/test_code_review_enforcement_constants.py +0 -166
- package/hooks/blocking/config/verified_commit_constants.py +0 -160
- package/hooks/blocking/config/verified_commit_context_constants.py +0 -21
- package/hooks/blocking/config/verified_commit_gate_output_constants.py +0 -14
- package/hooks/blocking/conftest.py +0 -123
- package/hooks/blocking/convergence_gate_blocker.py +0 -310
- package/hooks/blocking/pr_converge_bugteam_enforcer.py +0 -170
- package/hooks/blocking/pr_description_body_audit.py +0 -148
- package/hooks/blocking/pr_description_command_parser.py +0 -237
- package/hooks/blocking/pr_description_enforcer.py +0 -227
- package/hooks/blocking/pr_description_pr_number.py +0 -155
- package/hooks/blocking/pr_description_proof_of_work.py +0 -385
- package/hooks/blocking/pr_description_readability.py +0 -364
- package/hooks/blocking/reviewer_spawn_gate.py +0 -182
- package/hooks/blocking/test_code_review_enforcement_config_bootstrap.py +0 -62
- package/hooks/blocking/test_code_review_gate_deny.py +0 -54
- package/hooks/blocking/test_code_review_pr_create_gate.py +0 -199
- package/hooks/blocking/test_code_review_push_gate.py +0 -205
- package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +0 -199
- package/hooks/blocking/test_code_review_stamp_store.py +0 -205
- package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +0 -710
- package/hooks/blocking/test_code_verifier_tools_contract.py +0 -28
- package/hooks/blocking/test_convergence_gate_blocker.py +0 -428
- package/hooks/blocking/test_pr_converge_bugteam_enforcer.py +0 -311
- package/hooks/blocking/test_pr_converge_bugteam_enforcer_state_tolerance.py +0 -184
- package/hooks/blocking/test_pr_description_enforcer.py +0 -221
- package/hooks/blocking/test_pr_description_enforcer_body_audit.py +0 -247
- package/hooks/blocking/test_pr_description_enforcer_body_rules.py +0 -493
- package/hooks/blocking/test_pr_description_enforcer_command_parser.py +0 -366
- package/hooks/blocking/test_pr_description_enforcer_pr_number.py +0 -159
- package/hooks/blocking/test_pr_description_enforcer_proof_gate.py +0 -175
- package/hooks/blocking/test_pr_description_enforcer_readability.py +0 -443
- package/hooks/blocking/test_pr_description_proof_of_work.py +0 -313
- package/hooks/blocking/test_reviewer_spawn_gate.py +0 -230
- package/hooks/blocking/test_verdict_directory_write_blocker.py +0 -804
- package/hooks/blocking/test_verification_verdict_store.py +0 -974
- package/hooks/blocking/test_verified_commit_config_bootstrap.py +0 -67
- package/hooks/blocking/test_verified_commit_docs_delta.py +0 -176
- package/hooks/blocking/test_verified_commit_gate.py +0 -581
- package/hooks/blocking/test_verified_commit_gate_additional_context.py +0 -134
- package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +0 -131
- package/hooks/blocking/test_verifier_verdict_minter.py +0 -299
- package/hooks/blocking/tests/test_verified_commit_gate.py +0 -41
- package/hooks/blocking/verdict_directory_write_blocker.py +0 -687
- package/hooks/blocking/verification_verdict_store.py +0 -1039
- package/hooks/blocking/verified_commit_config_bootstrap.py +0 -63
- package/hooks/blocking/verified_commit_gate.py +0 -113
- package/hooks/blocking/verified_commit_gate_parts/CLAUDE.md +0 -28
- package/hooks/blocking/verified_commit_gate_parts/__init__.py +0 -1
- package/hooks/blocking/verified_commit_gate_parts/command_tokenization.py +0 -174
- package/hooks/blocking/verified_commit_gate_parts/deny_payload.py +0 -53
- package/hooks/blocking/verified_commit_gate_parts/deny_reason.py +0 -80
- package/hooks/blocking/verified_commit_gate_parts/directory_resolution.py +0 -170
- package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +0 -217
- package/hooks/blocking/verified_commit_gate_parts/tests/conftest.py +0 -10
- package/hooks/blocking/verified_commit_gate_parts/tests/test_command_tokenization.py +0 -94
- package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_payload.py +0 -17
- package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_reason.py +0 -38
- package/hooks/blocking/verified_commit_gate_parts/tests/test_directory_resolution.py +0 -71
- package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +0 -96
- package/hooks/blocking/verified_commit_message_accuracy_blocker.py +0 -167
- package/hooks/blocking/verifier_verdict_minter.py +0 -280
- package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +0 -64
- package/hooks/hooks_constants/convergence_gate_blocker_constants.py +0 -53
- package/hooks/hooks_constants/pr_converge_bugteam_enforcer_constants.py +0 -55
- package/hooks/hooks_constants/pr_converge_bugteam_enforcer_state.py +0 -67
- package/hooks/hooks_constants/pr_description_enforcer_constants.py +0 -167
- package/hooks/hooks_constants/pr_description_proof_of_work_constants.py +0 -107
- package/hooks/hooks_constants/reviewer_spawn_gate_constants.py +0 -41
- package/hooks/hooks_constants/test_pr_description_enforcer_constants.py +0 -292
- package/hooks/lifecycle/pr_converge_bugteam_skill_tracker.py +0 -198
- package/hooks/lifecycle/test_pr_converge_bugteam_skill_tracker.py +0 -283
- package/rules/proof-of-work-pr-comments.md +0 -22
- package/rules/verified-commit-gate-skip.md +0 -4
- package/scripts/Show-Asset.ps1 +0 -106
|
@@ -36,8 +36,6 @@ _hooks_dir = str(Path(__file__).resolve().parent.parent)
|
|
|
36
36
|
if _hooks_dir not in sys.path:
|
|
37
37
|
sys.path.insert(0, _hooks_dir)
|
|
38
38
|
|
|
39
|
-
from verification_verdict_store import resolve_repo_root # noqa: E402
|
|
40
|
-
|
|
41
39
|
from blocking._gh_body_arg_utils import ( # noqa: E402
|
|
42
40
|
all_value_flags,
|
|
43
41
|
count_extra_tokens_to_skip_for_split_quoted_value,
|
|
@@ -68,6 +66,7 @@ from hooks_constants.conventional_pr_title_gate_constants import ( # noqa: E402
|
|
|
68
66
|
YAML_LIST_ITEM_PREFIX,
|
|
69
67
|
)
|
|
70
68
|
from hooks_constants.hook_block_logger import log_hook_block # noqa: E402
|
|
69
|
+
from hooks_constants.project_paths_reader import find_git_root as resolve_repo_root # noqa: E402
|
|
71
70
|
|
|
72
71
|
|
|
73
72
|
def _matches_gh_pr_title_subcommand(command: str) -> bool:
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
SendUserFile attaches a file to the session. While the user is at the terminal
|
|
5
5
|
(status "normal" or unset) an attach does not let them see the file — it must
|
|
6
|
-
open on screen in its
|
|
6
|
+
open on screen in its native Windows app. The one attach allowed
|
|
7
7
|
through is an away-from-desk phone push (status "proactive").
|
|
8
8
|
"""
|
|
9
9
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Stop-hook dispatcher that hosts the
|
|
2
|
+
"""Stop-hook dispatcher that hosts the Stop-chain blockers in one process.
|
|
3
3
|
|
|
4
4
|
Reads the Stop payload from stdin once, runs each hosted hook in registration
|
|
5
5
|
order via the shared hosted-hook runner, and emits the first block decision the
|
|
6
|
-
chain produced. Later hooks still run
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
chain produced. Later hooks still run after an earlier block so every hosted
|
|
7
|
+
hook gets a chance to run. A single hosted hook crash fails open and does not
|
|
8
|
+
suppress the remaining hooks.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
@@ -221,9 +221,6 @@ def test_powershell_selects_the_shared_hooks_in_registration_order() -> None:
|
|
|
221
221
|
assert powershell_paths == [
|
|
222
222
|
"blocking/unscoped_search_blocker.py",
|
|
223
223
|
"blocking/pii_prevention_blocker.py",
|
|
224
|
-
"blocking/code_review_push_gate.py",
|
|
225
|
-
"blocking/code_review_pr_create_gate.py",
|
|
226
|
-
"blocking/code_review_stamp_directory_write_blocker.py",
|
|
227
224
|
]
|
|
228
225
|
|
|
229
226
|
|
|
@@ -715,8 +715,8 @@ def test_dispatcher_write_applies_both_groups() -> None:
|
|
|
715
715
|
assert "blocking/plain_language_blocker.py" in all_write_script_paths, (
|
|
716
716
|
"plain_language_blocker (Group B) must be in Write applicable set"
|
|
717
717
|
)
|
|
718
|
-
assert len(all_write_entries) ==
|
|
719
|
-
f"Write tool must apply to all
|
|
718
|
+
assert len(all_write_entries) == 19, (
|
|
719
|
+
f"Write tool must apply to all 19 hosted hooks, got {len(all_write_entries)}"
|
|
720
720
|
)
|
|
721
721
|
|
|
722
722
|
|
|
@@ -732,16 +732,16 @@ def test_dispatcher_edit_applies_both_groups() -> None:
|
|
|
732
732
|
assert "advisory/refactor_guard.py" in all_edit_script_paths, (
|
|
733
733
|
"refactor_guard is Edit-scoped and hosted, so it belongs in the Edit applicable set"
|
|
734
734
|
)
|
|
735
|
-
assert len(all_edit_entries) ==
|
|
736
|
-
f"expected
|
|
735
|
+
assert len(all_edit_entries) == 22, (
|
|
736
|
+
f"expected 22 Edit entries, got {len(all_edit_entries)}"
|
|
737
737
|
)
|
|
738
738
|
|
|
739
739
|
|
|
740
740
|
def test_dispatcher_multi_edit_applies_only_group_b() -> None:
|
|
741
741
|
"""MultiEdit tool triggers only Group B (10 hooks), not Group A."""
|
|
742
742
|
all_multi_edit_entries = _applicable_entries_for_tool(MULTI_EDIT_TOOL_NAME)
|
|
743
|
-
assert len(all_multi_edit_entries) ==
|
|
744
|
-
f"MultiEdit tool must apply to exactly
|
|
743
|
+
assert len(all_multi_edit_entries) == 9, (
|
|
744
|
+
f"MultiEdit tool must apply to exactly 9 Group-B hooks, got {len(all_multi_edit_entries)}"
|
|
745
745
|
)
|
|
746
746
|
|
|
747
747
|
|
|
@@ -969,7 +969,6 @@ def test_hosted_hook_set_covers_all_write_edit_blocking_hooks() -> None:
|
|
|
969
969
|
"blocking/state_description_blocker.py",
|
|
970
970
|
"blocking/subprocess_budget_completeness.py",
|
|
971
971
|
"blocking/hook_prose_detector_consistency.py",
|
|
972
|
-
"blocking/verified_commit_message_accuracy_blocker.py",
|
|
973
972
|
"blocking/workflow_substitution_slot_blocker.py",
|
|
974
973
|
"blocking/claude_md_orphan_file_blocker.py",
|
|
975
974
|
"blocking/pytest_testpaths_orphan_blocker.py",
|
|
@@ -45,8 +45,9 @@ def test_allows_proactive_status() -> None:
|
|
|
45
45
|
assert _should_block(PROACTIVE_STATUS) is False
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
def
|
|
49
|
-
assert "
|
|
48
|
+
def test_corrective_message_points_to_native_windows_app() -> None:
|
|
49
|
+
assert "native Windows app" in CORRECTIVE_MESSAGE
|
|
50
|
+
assert "Show-Asset.ps1" not in CORRECTIVE_MESSAGE
|
|
50
51
|
|
|
51
52
|
|
|
52
53
|
def test_corrective_message_names_proactive_escape_hatch() -> None:
|
|
@@ -71,7 +72,7 @@ def test_main_blocks_normal_attach() -> None:
|
|
|
71
72
|
output_text = _run_main_with_io(json.dumps(hook_input))
|
|
72
73
|
output = json.loads(output_text)
|
|
73
74
|
assert output["hookSpecificOutput"]["permissionDecision"] == "deny"
|
|
74
|
-
assert "
|
|
75
|
+
assert "native Windows app" in output["hookSpecificOutput"]["permissionDecisionReason"]
|
|
75
76
|
|
|
76
77
|
|
|
77
78
|
def test_main_allows_proactive_attach() -> None:
|
|
@@ -30,8 +30,6 @@ _BLOCKING_DIRECTORY = Path(__file__).resolve().parent
|
|
|
30
30
|
ALL_CONVERTED_HOOK_FILENAMES = (
|
|
31
31
|
"open_questions_in_plans_blocker.py",
|
|
32
32
|
"claude_md_orphan_file_blocker.py",
|
|
33
|
-
"pr_converge_bugteam_enforcer.py",
|
|
34
|
-
"verdict_directory_write_blocker.py",
|
|
35
33
|
"package_inventory_stale_blocker.py",
|
|
36
34
|
)
|
|
37
35
|
|
|
@@ -14,8 +14,8 @@ Hooks and scripts that collect, store, and query hook-firing records. The pipeli
|
|
|
14
14
|
| File | What it does |
|
|
15
15
|
|---|---|
|
|
16
16
|
| `hook_log_init.py` | One-time setup: creates the Neon schema (runs `schema.sql`), then verifies read-write parity with a sentinel round-trip |
|
|
17
|
-
| `hook_log_extractor.py` |
|
|
18
|
-
| `hook_log_stop_wrapper.py` |
|
|
17
|
+
| `hook_log_extractor.py` | Disabled CLI: exits 0 without reading transcripts or rewriting offset state; body remains for a re-enable path that ingests `hook_*` attachments into Neon |
|
|
18
|
+
| `hook_log_stop_wrapper.py` | Disabled Stop wrapper stub: exits 0; not on the Stop dispatcher roster |
|
|
19
19
|
| `schema.sql` | DDL for the `hook_events` table, `blocked_commands` view, and supporting indexes |
|
|
20
20
|
| `requirements-hook-logs.txt` | Runtime dependencies (`psycopg`) for the extractor |
|
|
21
21
|
| `requirements-hook-logs-dev.txt` | Dev/test dependencies |
|
|
@@ -38,6 +38,6 @@ The `blocked_commands` view filters to `outcome = 'blocked'`.
|
|
|
38
38
|
|
|
39
39
|
## Conventions
|
|
40
40
|
|
|
41
|
-
-
|
|
41
|
+
- Extractor and Stop wrapper mains are disabled and exit 0 with no work.
|
|
42
42
|
- Constants for the extractor (table name, offset state file, timeout) live in `hooks_constants/hook_log_extractor_constants.py`.
|
|
43
43
|
- Tests run with `python -m pytest diagnostic/test_hook_log_*.py`.
|
|
@@ -67,15 +67,10 @@ from hooks_constants.hook_log_extractor_constants import (
|
|
|
67
67
|
CATEGORY_PATH_MINIMUM_PARTS,
|
|
68
68
|
COMMAND_EXCERPT_MAX_CHARACTERS,
|
|
69
69
|
CONNECT_TIMEOUT_SECONDS,
|
|
70
|
-
DEFAULT_QUERY_FOR_SUMMARY,
|
|
71
70
|
EMPTY_STRING,
|
|
72
71
|
EXIT_CODE_EXTRACTOR_ENVIRONMENT_MISSING,
|
|
73
72
|
EXIT_CODE_SUCCESS,
|
|
74
73
|
EXIT_CODE_UNKNOWN_QUERY,
|
|
75
|
-
FLAG_FULL_REBUILD,
|
|
76
|
-
FLAG_INCREMENTAL,
|
|
77
|
-
FLAG_QUERY,
|
|
78
|
-
FLAG_SUMMARY,
|
|
79
74
|
HOOK_CATEGORY_UNCATEGORIZED,
|
|
80
75
|
HOOK_EVENTS_INSERT_SQL,
|
|
81
76
|
HOOK_EVENTS_TRUNCATE_SQL,
|
|
@@ -94,10 +89,8 @@ from hooks_constants.hook_log_extractor_constants import (
|
|
|
94
89
|
NEON_DATABASE_URL_ENVIRONMENT_VARIABLE,
|
|
95
90
|
NEWLINE_JOINER,
|
|
96
91
|
OFFLINE_WARNING_LOG,
|
|
97
|
-
OFFSET_STATE_FILE,
|
|
98
92
|
OFFSETS_JSON_INDENT,
|
|
99
93
|
OUTCOME_BY_ATTACHMENT_TYPE,
|
|
100
|
-
PROJECTS_TRANSCRIPT_ROOT,
|
|
101
94
|
QUERIES_DIRECTORY_NAME,
|
|
102
95
|
QUERY_NAME_PATTERN,
|
|
103
96
|
QUERY_NO_ROWS_RETURNED_MESSAGE,
|
|
@@ -874,35 +867,8 @@ def run_query(named_query: str) -> int:
|
|
|
874
867
|
|
|
875
868
|
|
|
876
869
|
def main() -> int:
|
|
877
|
-
"""
|
|
878
|
-
|
|
879
|
-
Supported flags:
|
|
880
|
-
|
|
881
|
-
* ``--summary`` prints the top blockers of the last twenty-four hours.
|
|
882
|
-
* ``--query <name>`` runs a pre-baked SQL file under ``queries/``.
|
|
883
|
-
* ``--full-rebuild`` truncates ``hook_events`` and re-reads every
|
|
884
|
-
JSONL from byte zero.
|
|
885
|
-
* ``--incremental`` is a documented no-op; it selects the default
|
|
886
|
-
byte-offset resumption path that the Stop hook also uses when no
|
|
887
|
-
flags are passed.
|
|
888
|
-
"""
|
|
889
|
-
all_cli_arguments = list(sys.argv[1:])
|
|
890
|
-
if FLAG_SUMMARY in all_cli_arguments:
|
|
891
|
-
return run_summary()
|
|
892
|
-
if FLAG_QUERY in all_cli_arguments:
|
|
893
|
-
flag_index = all_cli_arguments.index(FLAG_QUERY)
|
|
894
|
-
if flag_index + 1 >= len(all_cli_arguments):
|
|
895
|
-
return run_query(DEFAULT_QUERY_FOR_SUMMARY)
|
|
896
|
-
return run_query(all_cli_arguments[flag_index + 1])
|
|
897
|
-
is_full_rebuild_requested = FLAG_FULL_REBUILD in all_cli_arguments
|
|
898
|
-
is_incremental_requested = FLAG_INCREMENTAL in all_cli_arguments
|
|
899
|
-
if is_incremental_requested and is_full_rebuild_requested:
|
|
900
|
-
is_full_rebuild_requested = False
|
|
901
|
-
return run_full_extraction(
|
|
902
|
-
transcripts_root=PROJECTS_TRANSCRIPT_ROOT,
|
|
903
|
-
state_file_path=OFFSET_STATE_FILE,
|
|
904
|
-
full_rebuild=is_full_rebuild_requested,
|
|
905
|
-
)
|
|
870
|
+
"""Disabled extractor entry point: exit success with no work."""
|
|
871
|
+
return EXIT_CODE_SUCCESS
|
|
906
872
|
|
|
907
873
|
|
|
908
874
|
if __name__ == "__main__":
|
|
@@ -1,173 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Stop-hook wrapper for
|
|
2
|
+
"""Disabled Stop-hook wrapper for the hook-log extractor.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
1. Reads the last-spawn timestamp; if it falls within the debounce
|
|
8
|
-
window, exits 0 immediately without spawning anything (typical fast
|
|
9
|
-
path: a small file read, well under 10ms).
|
|
10
|
-
2. Otherwise records the current timestamp, then launches the extractor
|
|
11
|
-
as a fully detached background process (no stdio, separate process
|
|
12
|
-
group on POSIX or CREATE_NO_WINDOW|CREATE_NEW_PROCESS_GROUP on
|
|
13
|
-
Windows) and returns without waiting for it. CREATE_NO_WINDOW
|
|
14
|
-
prevents a console flash on Windows even when the wrapper goes
|
|
15
|
-
through ``bws run`` first; DETACHED_PROCESS would let the
|
|
16
|
-
grandchild python.exe allocate a fresh console.
|
|
17
|
-
|
|
18
|
-
Bitwarden injection: when both ``bws`` is on PATH and
|
|
19
|
-
``BWS_ACCESS_TOKEN`` is set, the extractor is launched via
|
|
20
|
-
``bws run --`` so the Neon URL never hits disk; otherwise it is
|
|
21
|
-
launched directly. The extractor itself exits 0 when
|
|
22
|
-
``NEON_HOOK_LOGS_DATABASE_URL`` is unset, so missing dependencies
|
|
23
|
-
cannot block session shutdown.
|
|
24
|
-
|
|
25
|
-
This wrapper always exits 0 so the Stop hook never surfaces a failure.
|
|
4
|
+
The wrapper is off the Stop dispatcher roster and exits 0 without spawning
|
|
5
|
+
the extractor or writing debounce state. Git history holds the prior
|
|
6
|
+
debounce-and-spawn implementation if Neon-backed extraction returns.
|
|
26
7
|
"""
|
|
27
8
|
|
|
28
9
|
from __future__ import annotations
|
|
29
10
|
|
|
30
|
-
import os
|
|
31
|
-
import shutil
|
|
32
|
-
import subprocess
|
|
33
11
|
import sys
|
|
34
|
-
import time
|
|
35
12
|
from pathlib import Path
|
|
36
13
|
|
|
37
14
|
if str(Path(__file__).resolve().parent.parent) not in sys.path:
|
|
38
15
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
39
16
|
|
|
40
|
-
from hooks_constants.hook_log_extractor_constants import
|
|
41
|
-
BWS_ACCESS_TOKEN_ENV_VAR,
|
|
42
|
-
BWS_EXECUTABLE_NAME,
|
|
43
|
-
BWS_RUN_SEPARATOR,
|
|
44
|
-
BWS_RUN_SUBCOMMAND,
|
|
45
|
-
EXIT_CODE_SUCCESS,
|
|
46
|
-
FLAG_INCREMENTAL,
|
|
47
|
-
STOP_WRAPPER_DEBOUNCE_SECONDS,
|
|
48
|
-
STOP_WRAPPER_EXTRACTOR_SCRIPT_NAME,
|
|
49
|
-
STOP_WRAPPER_LAST_RUN_TIMESTAMP_FILE,
|
|
50
|
-
WINDOWS_CREATE_NEW_PROCESS_GROUP_FLAG,
|
|
51
|
-
WINDOWS_CREATE_NO_WINDOW_FLAG,
|
|
52
|
-
WINDOWS_OS_NAME,
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def _extractor_script_path() -> str:
|
|
57
|
-
return str(
|
|
58
|
-
Path(__file__).resolve().parent / STOP_WRAPPER_EXTRACTOR_SCRIPT_NAME
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def _last_run_timestamp_path() -> Path:
|
|
63
|
-
return Path(STOP_WRAPPER_LAST_RUN_TIMESTAMP_FILE)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def _is_within_debounce_window() -> bool:
|
|
67
|
-
timestamp_path = _last_run_timestamp_path()
|
|
68
|
-
if not timestamp_path.exists():
|
|
69
|
-
return False
|
|
70
|
-
try:
|
|
71
|
-
previous_timestamp = float(timestamp_path.read_text().strip())
|
|
72
|
-
except (OSError, ValueError):
|
|
73
|
-
return False
|
|
74
|
-
seconds_since_previous_spawn = time.time() - previous_timestamp
|
|
75
|
-
if seconds_since_previous_spawn < 0:
|
|
76
|
-
return False
|
|
77
|
-
return seconds_since_previous_spawn < STOP_WRAPPER_DEBOUNCE_SECONDS
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def _record_current_timestamp() -> None:
|
|
81
|
-
timestamp_path = _last_run_timestamp_path()
|
|
82
|
-
timestamp_path.parent.mkdir(parents=True, exist_ok=True)
|
|
83
|
-
timestamp_path.write_text(str(time.time()))
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def _clear_recorded_timestamp() -> None:
|
|
87
|
-
"""Remove the debounce timestamp regardless of which process wrote it.
|
|
88
|
-
|
|
89
|
-
The unlink is unconditional: if process A writes a timestamp, process
|
|
90
|
-
B observes it and debounces, then A's spawn fails, A's rollback here
|
|
91
|
-
deletes the timestamp B already saw. The next Stop hook then spawns
|
|
92
|
-
a fresh extractor instead of debouncing the remainder of the window.
|
|
93
|
-
Consequence is benign because the extractor's own offset-file lock
|
|
94
|
-
(LOCK_MAXIMUM_RETRY_COUNT in hook_log_extractor_constants) serializes
|
|
95
|
-
concurrent extractor runs, so at most one extra extractor briefly
|
|
96
|
-
waits on the lock. Scoping the rollback to A's own write would
|
|
97
|
-
require a per-process sentinel and tighter atomicity than the
|
|
98
|
-
benefit warrants for this Stop-hook fast path.
|
|
99
|
-
"""
|
|
100
|
-
timestamp_path = _last_run_timestamp_path()
|
|
101
|
-
timestamp_path.unlink(missing_ok=True)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def _can_use_bws() -> bool:
|
|
105
|
-
if not os.environ.get(BWS_ACCESS_TOKEN_ENV_VAR):
|
|
106
|
-
return False
|
|
107
|
-
return shutil.which(BWS_EXECUTABLE_NAME) is not None
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def _detached_spawn_keyword_arguments() -> dict[str, object]:
|
|
111
|
-
spawn_arguments: dict[str, object] = {
|
|
112
|
-
"stdin": subprocess.DEVNULL,
|
|
113
|
-
"stdout": subprocess.DEVNULL,
|
|
114
|
-
"stderr": subprocess.DEVNULL,
|
|
115
|
-
"close_fds": True,
|
|
116
|
-
}
|
|
117
|
-
if os.name == WINDOWS_OS_NAME:
|
|
118
|
-
spawn_arguments["creationflags"] = (
|
|
119
|
-
WINDOWS_CREATE_NO_WINDOW_FLAG
|
|
120
|
-
| WINDOWS_CREATE_NEW_PROCESS_GROUP_FLAG
|
|
121
|
-
)
|
|
122
|
-
startup_info = subprocess.STARTUPINFO()
|
|
123
|
-
startup_info.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
|
124
|
-
startup_info.wShowWindow = subprocess.SW_HIDE
|
|
125
|
-
spawn_arguments["startupinfo"] = startup_info
|
|
126
|
-
else:
|
|
127
|
-
spawn_arguments["start_new_session"] = True
|
|
128
|
-
return spawn_arguments
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def _spawn_with_bws() -> None:
|
|
132
|
-
subprocess.Popen(
|
|
133
|
-
[
|
|
134
|
-
BWS_EXECUTABLE_NAME,
|
|
135
|
-
BWS_RUN_SUBCOMMAND,
|
|
136
|
-
BWS_RUN_SEPARATOR,
|
|
137
|
-
sys.executable,
|
|
138
|
-
_extractor_script_path(),
|
|
139
|
-
FLAG_INCREMENTAL,
|
|
140
|
-
],
|
|
141
|
-
**_detached_spawn_keyword_arguments(),
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
def _spawn_without_bws() -> None:
|
|
146
|
-
subprocess.Popen(
|
|
147
|
-
[
|
|
148
|
-
sys.executable,
|
|
149
|
-
_extractor_script_path(),
|
|
150
|
-
FLAG_INCREMENTAL,
|
|
151
|
-
],
|
|
152
|
-
**_detached_spawn_keyword_arguments(),
|
|
153
|
-
)
|
|
17
|
+
from hooks_constants.hook_log_extractor_constants import EXIT_CODE_SUCCESS
|
|
154
18
|
|
|
155
19
|
|
|
156
20
|
def main() -> int:
|
|
157
|
-
"""
|
|
158
|
-
try:
|
|
159
|
-
if _is_within_debounce_window():
|
|
160
|
-
return EXIT_CODE_SUCCESS
|
|
161
|
-
_record_current_timestamp()
|
|
162
|
-
try:
|
|
163
|
-
if _can_use_bws():
|
|
164
|
-
_spawn_with_bws()
|
|
165
|
-
else:
|
|
166
|
-
_spawn_without_bws()
|
|
167
|
-
except Exception:
|
|
168
|
-
_clear_recorded_timestamp()
|
|
169
|
-
except Exception:
|
|
170
|
-
pass
|
|
21
|
+
"""Exit success without spawning the extractor."""
|
|
171
22
|
return EXIT_CODE_SUCCESS
|
|
172
23
|
|
|
173
24
|
|
|
@@ -805,32 +805,19 @@ def test_offline_fallback_still_exits_zero_when_warning_log_write_raises(
|
|
|
805
805
|
assert exit_code == 0
|
|
806
806
|
|
|
807
807
|
|
|
808
|
-
def
|
|
809
|
-
tmp_path: Path,
|
|
808
|
+
def test_main_is_disabled_and_skips_extraction(
|
|
810
809
|
monkeypatch: pytest.MonkeyPatch,
|
|
811
810
|
) -> None:
|
|
812
|
-
"""
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
state_file_path: str,
|
|
818
|
-
full_rebuild: bool,
|
|
819
|
-
) -> int:
|
|
820
|
-
captured_arguments["transcripts_root"] = transcripts_root
|
|
821
|
-
captured_arguments["state_file_path"] = state_file_path
|
|
822
|
-
captured_arguments["full_rebuild"] = full_rebuild
|
|
823
|
-
return 0
|
|
824
|
-
|
|
825
|
-
monkeypatch.setattr(sys, "argv", ["hook_log_extractor.py", "--incremental"])
|
|
811
|
+
"""Disabled main exits success without running extraction."""
|
|
812
|
+
|
|
813
|
+
def _fail_if_called(*_args: object, **_kwargs: object) -> int:
|
|
814
|
+
raise AssertionError("run_full_extraction must not run while disabled")
|
|
815
|
+
|
|
826
816
|
monkeypatch.setattr(
|
|
827
|
-
hook_log_extractor, "run_full_extraction",
|
|
817
|
+
hook_log_extractor, "run_full_extraction", _fail_if_called
|
|
828
818
|
)
|
|
829
819
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
assert exit_code == 0
|
|
833
|
-
assert captured_arguments["full_rebuild"] is False
|
|
820
|
+
assert hook_log_extractor.main() == 0
|
|
834
821
|
|
|
835
822
|
|
|
836
823
|
def test_run_query_returns_nonzero_for_unknown_query(
|