workbay-system 0.1.0__py3-none-any.whl
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.
- workbay_system/__init__.py +25 -0
- workbay_system/payload/.claude/hooks/ensure-agent-surfaces.sh +44 -0
- workbay_system/payload/.claude/settings.hooks.json +162 -0
- workbay_system/payload/.codex/hooks.json +127 -0
- workbay_system/payload/.github/hooks/guard-main-branch.py +239 -0
- workbay_system/payload/.github/hooks/guard-worktree-drift.py +18 -0
- workbay_system/payload/.github/hooks/terminal-guard.json +113 -0
- workbay_system/payload/.github/hooks/test_guard_main_branch.py +382 -0
- workbay_system/payload/.github/hooks/test_guard_worktree_drift.py +559 -0
- workbay_system/payload/.github/hooks/token_budget.py +72 -0
- workbay_system/payload/.github/prompts/auto-fix.prompt.md +155 -0
- workbay_system/payload/.github/prompts/branch-lifecycle.prompt.md +150 -0
- workbay_system/payload/.github/prompts/branch-review.prompt.md +123 -0
- workbay_system/payload/.github/prompts/handoff-lifecycle.prompt.md +50 -0
- workbay_system/payload/.github/prompts/incremental-implementation.prompt.md +112 -0
- workbay_system/payload/.github/prompts/investigate.prompt.md +143 -0
- workbay_system/payload/.github/prompts/plan-analyze.prompt.md +101 -0
- workbay_system/payload/.github/prompts/planning-review.prompt.md +120 -0
- workbay_system/payload/.github/prompts/review-parallel.prompt.md +133 -0
- workbay_system/payload/.github/prompts/scope.prompt.md +99 -0
- workbay_system/payload/.github/prompts/tdd.prompt.md +109 -0
- workbay_system/payload/Makefile.d/ace.mk +43 -0
- workbay_system/payload/Makefile.d/compaction.mk +39 -0
- workbay_system/payload/Makefile.d/lifecycle.mk +174 -0
- workbay_system/payload/Makefile.d/plans.mk +66 -0
- workbay_system/payload/Makefile.d/plugins.mk +55 -0
- workbay_system/payload/Makefile.d/update.mk +11 -0
- workbay_system/payload/Makefile.d/workflows.mk +70 -0
- workbay_system/payload/config/agent-workflows/mcp_servers.yaml +53 -0
- workbay_system/payload/config/agent-workflows/portable_commands.json +442 -0
- workbay_system/payload/config/agent-workflows/prompts/review-parallel/default.md +51 -0
- workbay_system/payload/docs/workbay/contracts/harness-protocol.yaml +559 -0
- workbay_system/payload/docs/workbay/contracts/overlay-manifest.yaml +150 -0
- workbay_system/payload/docs/workbay/contracts/repo-intel-mcp-candidates.md +39 -0
- workbay_system/payload/docs/workbay/contracts/subagent-bridge-interface-note.md +74 -0
- workbay_system/payload/docs/workbay/contracts/workbay-handoff-mcp.md +840 -0
- workbay_system/payload/docs/workbay/contracts/workbay-orchestrator-mcp.md +375 -0
- workbay_system/payload/docs/workbay/in-session-skills.md +26 -0
- workbay_system/payload/docs/workbay/maps/mcp-tool-routing.yaml +109 -0
- workbay_system/payload/docs/workbay/rules/branch-review-guide.md +601 -0
- workbay_system/payload/docs/workbay/rules/branch-review-php.md +81 -0
- workbay_system/payload/docs/workbay/rules/branch-review-python.md +116 -0
- workbay_system/payload/docs/workbay/rules/branch-review-typescript.md +76 -0
- workbay_system/payload/docs/workbay/rules/contract-change-checklist.md +76 -0
- workbay_system/payload/docs/workbay/rules/development-workflow.md +732 -0
- workbay_system/payload/docs/workbay/rules/lifecycle-recovery.md +147 -0
- workbay_system/payload/docs/workbay/rules/planning-artifact-home.md +215 -0
- workbay_system/payload/docs/workbay/rules/planning-review-guide.md +233 -0
- workbay_system/payload/docs/workbay/templates/ADR.template.md +139 -0
- workbay_system/payload/docs/workbay/templates/ASSESSMENT.template.md +124 -0
- workbay_system/payload/docs/workbay/templates/CURRENT_TASK.template.md +89 -0
- workbay_system/payload/docs/workbay/templates/DECISION_BREAKING_CHANGE.template.md +41 -0
- workbay_system/payload/docs/workbay/templates/DECISION_CONTRACT_CHANGE.template.md +41 -0
- workbay_system/payload/docs/workbay/templates/DECISION_CROSS_LANE.template.md +41 -0
- workbay_system/payload/docs/workbay/templates/EPIC.template.md +130 -0
- workbay_system/payload/docs/workbay/templates/ROADMAP.template.md +132 -0
- workbay_system/payload/docs/workbay/templates/SKILL_ANATOMY.template.md +65 -0
- workbay_system/payload/docs/workbay/templates/SPEC.template.md +166 -0
- workbay_system/payload/docs/workbay/templates/TASK_PLAN.template.md +245 -0
- workbay_system/payload/docs/workbay/templates/WORKTREE_LANE_BRIEF.template.md +42 -0
- workbay_system/payload/docs/workbay/templates/WORKTREE_LANE_REPORT.template.md +33 -0
- workbay_system/payload/docs/workbay/templates/slice-complete-template.md +52 -0
- workbay_system/payload/scripts/_guard_wrap.py +71 -0
- workbay_system/payload/scripts/check_workflow_facade.py +358 -0
- workbay_system/payload/scripts/generate_agent_workflows.py +2231 -0
- workbay_system/payload/scripts/hooks/_active_task_context.py +154 -0
- workbay_system/payload/scripts/hooks/_bash_isolation_guard.py +558 -0
- workbay_system/payload/scripts/hooks/_branch_isolation_guard.py +469 -0
- workbay_system/payload/scripts/hooks/_guard_main_branch_inline.py +178 -0
- workbay_system/payload/scripts/hooks/_harness_protocol.py +393 -0
- workbay_system/payload/scripts/hooks/_interp.py +174 -0
- workbay_system/payload/scripts/hooks/_post_commit_refresh_sha.py +83 -0
- workbay_system/payload/scripts/hooks/_post_merge_reap_tasks.py +186 -0
- workbay_system/payload/scripts/hooks/_protocol.py +105 -0
- workbay_system/payload/scripts/hooks/_run_guard.py +189 -0
- workbay_system/payload/scripts/hooks/_worktree_drift.py +455 -0
- workbay_system/payload/scripts/hooks/ace-detect.py +108 -0
- workbay_system/payload/scripts/hooks/advise-worktree-cd.py +140 -0
- workbay_system/payload/scripts/hooks/capture-agent-errors.py +252 -0
- workbay_system/payload/scripts/hooks/check_branch_naming.py +588 -0
- workbay_system/payload/scripts/hooks/check_main_clean.py +205 -0
- workbay_system/payload/scripts/hooks/check_root_on_main.py +69 -0
- workbay_system/payload/scripts/hooks/coherence-self-check.py +123 -0
- workbay_system/payload/scripts/hooks/compact-session.py +472 -0
- workbay_system/payload/scripts/hooks/filter-test-output.py +316 -0
- workbay_system/payload/scripts/hooks/git/post-checkout +41 -0
- workbay_system/payload/scripts/hooks/git/post-commit +25 -0
- workbay_system/payload/scripts/hooks/git/post-merge +32 -0
- workbay_system/payload/scripts/hooks/git/post-rewrite +21 -0
- workbay_system/payload/scripts/hooks/git/pre-commit +22 -0
- workbay_system/payload/scripts/hooks/git/pre-push +37 -0
- workbay_system/payload/scripts/hooks/guard-bash-main-branch.py +246 -0
- workbay_system/payload/scripts/hooks/guard-main-branch.sh +80 -0
- workbay_system/payload/scripts/hooks/guard-rationale-size.py +143 -0
- workbay_system/payload/scripts/hooks/guard-task-plan-findings.py +658 -0
- workbay_system/payload/scripts/hooks/guard-worktree-drift.sh +6 -0
- workbay_system/payload/scripts/hooks/lint-dashboard-txt.py +124 -0
- workbay_system/payload/scripts/hooks/lint-expected-revision.py +175 -0
- workbay_system/payload/scripts/hooks/lint-no-inline-python-heredoc.py +179 -0
- workbay_system/payload/scripts/hooks/mcp_launch.py +184 -0
- workbay_system/payload/scripts/hooks/record-file-touch.py +157 -0
- workbay_system/payload/scripts/hooks/regenerate-task-views.sh +78 -0
- workbay_system/payload/scripts/hooks/reinject-context.py +773 -0
- workbay_system/payload/scripts/hooks/resolve_handoff_src.py +64 -0
- workbay_system/payload/scripts/hooks/slim-handoff-response.py +281 -0
- workbay_system/payload/scripts/hooks/test_ace_detect.py +146 -0
- workbay_system/payload/scripts/hooks/test_active_task_context.py +214 -0
- workbay_system/payload/scripts/hooks/test_advise_worktree_cd.py +219 -0
- workbay_system/payload/scripts/hooks/test_bash_isolation_guard_cwd.py +346 -0
- workbay_system/payload/scripts/hooks/test_bash_isolation_guard_formatters.py +162 -0
- workbay_system/payload/scripts/hooks/test_branch_grammar_fallback_drift.py +96 -0
- workbay_system/payload/scripts/hooks/test_branch_isolation_policy_split.py +180 -0
- workbay_system/payload/scripts/hooks/test_capture_agent_errors.py +298 -0
- workbay_system/payload/scripts/hooks/test_check_branch_naming.py +937 -0
- workbay_system/payload/scripts/hooks/test_check_main_clean.py +437 -0
- workbay_system/payload/scripts/hooks/test_check_root_on_main.py +48 -0
- workbay_system/payload/scripts/hooks/test_coherence_self_check.py +114 -0
- workbay_system/payload/scripts/hooks/test_compact_session_hook.py +1466 -0
- workbay_system/payload/scripts/hooks/test_compaction_failed_capture.py +192 -0
- workbay_system/payload/scripts/hooks/test_dev_workflow_compaction_docs.py +157 -0
- workbay_system/payload/scripts/hooks/test_dirty_permitted_carveout.py +90 -0
- workbay_system/payload/scripts/hooks/test_doc_references.py +128 -0
- workbay_system/payload/scripts/hooks/test_filter_test_output.py +410 -0
- workbay_system/payload/scripts/hooks/test_guard_bash_main_branch_env_alias.py +61 -0
- workbay_system/payload/scripts/hooks/test_guard_bash_main_branch_lightweight.py +164 -0
- workbay_system/payload/scripts/hooks/test_guard_rationale_size.py +193 -0
- workbay_system/payload/scripts/hooks/test_guard_task_plan_findings.py +805 -0
- workbay_system/payload/scripts/hooks/test_harness_contract_missing_policy.py +131 -0
- workbay_system/payload/scripts/hooks/test_harness_hook_configs.py +124 -0
- workbay_system/payload/scripts/hooks/test_interp.py +331 -0
- workbay_system/payload/scripts/hooks/test_lint_dashboard_txt.py +106 -0
- workbay_system/payload/scripts/hooks/test_protocol_validation_wiring.py +103 -0
- workbay_system/payload/scripts/hooks/test_record_file_touch.py +243 -0
- workbay_system/payload/scripts/hooks/test_regenerate_task_views.py +135 -0
- workbay_system/payload/scripts/hooks/test_reinject_context_hook.py +1300 -0
- workbay_system/payload/scripts/hooks/test_resolve_handoff_src.py +73 -0
- workbay_system/payload/scripts/hooks/test_run_guard.py +426 -0
- workbay_system/payload/scripts/hooks/test_slim_handoff_response.py +312 -0
- workbay_system/payload/scripts/hooks/test_validate_mcp_dict_params.py +154 -0
- workbay_system/payload/scripts/hooks/test_worktree_branch_resolution.py +165 -0
- workbay_system/payload/scripts/hooks/test_worktree_drift_unchanged.py +178 -0
- workbay_system/payload/scripts/hooks/validate-mcp-dict-params.py +110 -0
- workbay_system/payload/scripts/measure_reinject_ab.py +539 -0
- workbay_system/payload/scripts/plugin_override_compose.py +817 -0
- workbay_system/payload/scripts/test_measure_reinject_ab.py +442 -0
- workbay_system/payload/scripts/validate_claude_settings_pin.py +174 -0
- workbay_system/payload/scripts/workbay/git-plan-cat.sh +27 -0
- workbay_system/payload/scripts/workbay/lifecycle/__init__.py +12 -0
- workbay_system/payload/scripts/workbay/lifecycle/__main__.py +15 -0
- workbay_system/payload/scripts/workbay/lifecycle/cli.py +169 -0
- workbay_system/payload/scripts/workbay/lifecycle/handlers/__init__.py +11 -0
- workbay_system/payload/scripts/workbay/lifecycle/handlers/_common.py +662 -0
- workbay_system/payload/scripts/workbay/lifecycle/handlers/backfill_plan_acceptance.py +254 -0
- workbay_system/payload/scripts/workbay/lifecycle/handlers/close_check.py +171 -0
- workbay_system/payload/scripts/workbay/lifecycle/handlers/context.py +345 -0
- workbay_system/payload/scripts/workbay/lifecycle/handlers/doctor.py +968 -0
- workbay_system/payload/scripts/workbay/lifecycle/handlers/finalize_plan.py +136 -0
- workbay_system/payload/scripts/workbay/lifecycle/handlers/plan_accept.py +446 -0
- workbay_system/payload/scripts/workbay/lifecycle/handlers/plan_baseline.py +620 -0
- workbay_system/payload/scripts/workbay/lifecycle/handlers/plan_done.py +234 -0
- workbay_system/payload/scripts/workbay/lifecycle/handlers/project_events_replay.py +235 -0
- workbay_system/payload/scripts/workbay/lifecycle/handlers/provision_env.py +95 -0
- workbay_system/payload/scripts/workbay/lifecycle/handlers/review_ready.py +635 -0
- workbay_system/payload/scripts/workbay/lifecycle/handlers/shell_out.py +275 -0
- workbay_system/payload/scripts/workbay/lifecycle/handlers/skill_broadcast.py +149 -0
- workbay_system/payload/scripts/workbay/lifecycle/handlers/slice_commit.py +275 -0
- workbay_system/payload/scripts/workbay/lifecycle/handlers/slice_start.py +198 -0
- workbay_system/payload/scripts/workbay/lifecycle/handlers/status.py +669 -0
- workbay_system/payload/scripts/workbay/lifecycle/handlers/sync_task_plan_checklist.py +923 -0
- workbay_system/payload/scripts/workbay/lifecycle/handlers/task_finish.py +596 -0
- workbay_system/payload/scripts/workbay/lifecycle/handlers/task_plan_checklist_audit.py +340 -0
- workbay_system/payload/scripts/workbay/lifecycle/handlers/task_plan_checklist_backfill.py +268 -0
- workbay_system/payload/scripts/workbay/lifecycle/handlers/task_reap.py +117 -0
- workbay_system/payload/scripts/workbay/lifecycle/handlers/task_start.py +1068 -0
- workbay_system/payload/scripts/workbay/lifecycle/handlers/tasks.py +296 -0
- workbay_system/payload/scripts/workbay/lifecycle/projection.py +307 -0
- workbay_system/payload/scripts/workbay/lifecycle/receipts.py +328 -0
- workbay_system/payload/scripts/workbay/lifecycle/resolver.py +299 -0
- workbay_system/payload/scripts/workbay/lifecycle/uv_provisioning.py +440 -0
- workbay_system/payload/scripts/workbay/update.sh +182 -0
- workbay_system/payload/skills/auto-fix/body.md +122 -0
- workbay_system/payload/skills/auto-fix/skill.yaml +20 -0
- workbay_system/payload/skills/branch-lifecycle/body.md +114 -0
- workbay_system/payload/skills/branch-lifecycle/skill.yaml +20 -0
- workbay_system/payload/skills/branch-review/body.md +89 -0
- workbay_system/payload/skills/branch-review/skill.yaml +20 -0
- workbay_system/payload/skills/commit2git/body.md +125 -0
- workbay_system/payload/skills/commit2git/skill.yaml +12 -0
- workbay_system/payload/skills/daemon-lifecycle/body.md +113 -0
- workbay_system/payload/skills/daemon-lifecycle/skill.yaml +16 -0
- workbay_system/payload/skills/document-sync/body.md +205 -0
- workbay_system/payload/skills/document-sync/skill.yaml +16 -0
- workbay_system/payload/skills/handoff-lifecycle/body.md +98 -0
- workbay_system/payload/skills/handoff-lifecycle/skill.yaml +27 -0
- workbay_system/payload/skills/incremental-implementation/body.md +79 -0
- workbay_system/payload/skills/incremental-implementation/skill.yaml +17 -0
- workbay_system/payload/skills/investigate/body.md +110 -0
- workbay_system/payload/skills/investigate/skill.yaml +17 -0
- workbay_system/payload/skills/plan-analyze/body.md +71 -0
- workbay_system/payload/skills/plan-analyze/skill.yaml +15 -0
- workbay_system/payload/skills/planning-review/body.md +87 -0
- workbay_system/payload/skills/planning-review/skill.yaml +16 -0
- workbay_system/payload/skills/refactor/body.md +309 -0
- workbay_system/payload/skills/refactor/skill.yaml +15 -0
- workbay_system/payload/skills/rescue-lane/body.md +77 -0
- workbay_system/payload/skills/rescue-lane/skill.yaml +16 -0
- workbay_system/payload/skills/review/body.md +132 -0
- workbay_system/payload/skills/review/skill.yaml +20 -0
- workbay_system/payload/skills/review-parallel/body.md +101 -0
- workbay_system/payload/skills/review-parallel/skill.yaml +18 -0
- workbay_system/payload/skills/scope/body.md +70 -0
- workbay_system/payload/skills/scope/skill.yaml +15 -0
- workbay_system/payload/skills/security-audit/body.md +297 -0
- workbay_system/payload/skills/security-audit/skill.yaml +17 -0
- workbay_system/payload/skills/spec/body.md +97 -0
- workbay_system/payload/skills/spec/skill.yaml +17 -0
- workbay_system/payload/skills/subfeature-committer/body.md +78 -0
- workbay_system/payload/skills/subfeature-committer/skill.yaml +12 -0
- workbay_system/payload/skills/tdd/body.md +78 -0
- workbay_system/payload/skills/tdd/skill.yaml +15 -0
- workbay_system/payload/skills/worktree-orchestrator/body.md +185 -0
- workbay_system/payload/skills/worktree-orchestrator/skill.yaml +17 -0
- workbay_system/payload/skills/worktree-worker/body.md +153 -0
- workbay_system/payload/skills/worktree-worker/skill.yaml +17 -0
- workbay_system-0.1.0.dist-info/METADATA +72 -0
- workbay_system-0.1.0.dist-info/RECORD +227 -0
- workbay_system-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""workbay-system overlay payload package.
|
|
2
|
+
|
|
3
|
+
Ships the canonical overlay surfaces — skills, the workflow generator,
|
|
4
|
+
agent-workflows config, and the shared hook/contract surfaces — as package
|
|
5
|
+
data so ``workbay-bootstrap`` can materialize them from an installed
|
|
6
|
+
distribution (the package delivery source) instead of a git clone.
|
|
7
|
+
|
|
8
|
+
The payload is co-located under ``workbay_system/payload/`` and ships by
|
|
9
|
+
"ship the package" (``only-include = ["workbay_system"]`` in
|
|
10
|
+
``pyproject.toml`` — no force-include map), so :func:`data_root` resolves to the
|
|
11
|
+
``payload/`` directory that contains ``skills/``, ``scripts/``, ``config/``,
|
|
12
|
+
``docs/``, ``Makefile.d/``, and ``.github/`` once installed.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from importlib import resources
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
__all__ = ["data_root"]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def data_root() -> Path:
|
|
24
|
+
"""Return the filesystem root of the installed overlay payload."""
|
|
25
|
+
return Path(str(resources.files(__name__) / "payload"))
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# SessionStart hook — ensure this clone's GENERATED agent surfaces exist.
|
|
3
|
+
#
|
|
4
|
+
# Three agent surfaces are generated into GITIGNORED paths, so a fresh clone
|
|
5
|
+
# carries the pins/sources but not the generated output — and the surfaces
|
|
6
|
+
# silently fail to load until they are built:
|
|
7
|
+
# - Claude plugin tree .workbay/generated/plugins/workbay-system/base/claude (make plugins-build)
|
|
8
|
+
# - Codex plugin tree .workbay/generated/plugins/workbay-system/base/codex (make plugins-build)
|
|
9
|
+
# - VS Code Copilot .github/prompts/ (make generate-agent-workflows)
|
|
10
|
+
#
|
|
11
|
+
# This hook regenerates all of them when missing. It can only run on CLAUDE CODE
|
|
12
|
+
# session start — Codex and VS Code Copilot have no equivalent trigger (see the
|
|
13
|
+
# README "Developing in this repo" section) — but because `plugins-build` emits
|
|
14
|
+
# BOTH plugin trees and `generate-agent-workflows` emits the Copilot prompts,
|
|
15
|
+
# opening Claude once bootstraps every surface. Plugins load only at session
|
|
16
|
+
# start, so a one-time restart is needed after the first build. The hook is a
|
|
17
|
+
# no-op (fast, silent) once the surfaces exist and never blocks session start.
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
|
|
20
|
+
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
21
|
+
claude_manifest="$repo_root/.workbay/generated/plugins/workbay-system/base/claude/.claude-plugin/plugin.json"
|
|
22
|
+
codex_manifest="$repo_root/.workbay/generated/plugins/workbay-system/base/codex/.codex-plugin/plugin.json"
|
|
23
|
+
copilot_prompts="$repo_root/.github/prompts"
|
|
24
|
+
|
|
25
|
+
# All three surfaces must be present to skip; checking only Claude + Copilot
|
|
26
|
+
# would leave a partial-drift clone (e.g. only the codex tree deleted) unhealed.
|
|
27
|
+
if [ -f "$claude_manifest" ] && [ -f "$codex_manifest" ] && [ -d "$copilot_prompts" ]; then
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
echo "workbay-system agent surfaces missing (fresh clone?) — generating them…"
|
|
32
|
+
ok=1
|
|
33
|
+
make -C "$repo_root" plugins-build >/dev/null 2>&1 || ok=0
|
|
34
|
+
# Pass WORKFLOW_TARGET_ROOT so the Copilot prompts land in this repo's root
|
|
35
|
+
# .github/prompts/ (the surface VS Code reads); without --target the generator
|
|
36
|
+
# only rewrites the tracked package-level source copy.
|
|
37
|
+
make -C "$repo_root" generate-agent-workflows WORKFLOW_TARGET_ROOT="$repo_root" >/dev/null 2>&1 || ok=0
|
|
38
|
+
if [ "$ok" -eq 1 ]; then
|
|
39
|
+
echo "Generated the Claude + Codex plugin trees and the VS Code Copilot prompts."
|
|
40
|
+
echo "RESTART Claude Code (/exit, then reopen) to load the /branch-review, /plan-analyze, … slash commands."
|
|
41
|
+
else
|
|
42
|
+
echo "Could not auto-generate every surface. Run 'cd \"$repo_root\" && make plugins-build && make generate-agent-workflows WORKFLOW_TARGET_ROOT=\"\$PWD\"' (or 'workbay-bootstrap install --target \"$repo_root\"') manually, then restart Claude Code."
|
|
43
|
+
fi
|
|
44
|
+
exit 0
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PreToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "Edit|Write|apply_patch|create_file|replace_string_in_file|multi_replace_string_in_file",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "bash \"$CLAUDE_PROJECT_DIR/scripts/hooks/guard-worktree-drift.sh\"",
|
|
10
|
+
"timeout": 5
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"matcher": "Edit|Write|apply_patch|create_file|replace_string_in_file|multi_replace_string_in_file",
|
|
16
|
+
"hooks": [
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": "bash \"$CLAUDE_PROJECT_DIR/scripts/hooks/guard-main-branch.sh\"",
|
|
20
|
+
"timeout": 5
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"matcher": "Bash",
|
|
26
|
+
"hooks": [
|
|
27
|
+
{
|
|
28
|
+
"type": "command",
|
|
29
|
+
"command": "python3 \"$CLAUDE_PROJECT_DIR/scripts/hooks/guard-bash-main-branch.py\"",
|
|
30
|
+
"timeout": 5
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"matcher": "Edit|Write",
|
|
36
|
+
"hooks": [
|
|
37
|
+
{
|
|
38
|
+
"type": "command",
|
|
39
|
+
"command": "python3 \"$CLAUDE_PROJECT_DIR/scripts/hooks/guard-task-plan-findings.py\"",
|
|
40
|
+
"timeout": 5
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"matcher": "mcp__workbay-handoff-mcp__record_event|mcp__workbay-handoff-mcp__close_slice",
|
|
46
|
+
"hooks": [
|
|
47
|
+
{
|
|
48
|
+
"type": "command",
|
|
49
|
+
"command": "python3 \"$CLAUDE_PROJECT_DIR/scripts/hooks/guard-rationale-size.py\"",
|
|
50
|
+
"timeout": 5
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"matcher": "mcp__workbay-handoff-mcp__record_event|mcp__workbay-handoff-mcp__review_findings|mcp__workbay-handoff-mcp__review_runs",
|
|
56
|
+
"hooks": [
|
|
57
|
+
{
|
|
58
|
+
"type": "command",
|
|
59
|
+
"command": "python3 \"$CLAUDE_PROJECT_DIR/scripts/hooks/validate-mcp-dict-params.py\"",
|
|
60
|
+
"timeout": 5
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"PostToolUse": [
|
|
66
|
+
{
|
|
67
|
+
"matcher": "Edit|Write",
|
|
68
|
+
"hooks": [
|
|
69
|
+
{
|
|
70
|
+
"type": "command",
|
|
71
|
+
"command": "python3 \"$CLAUDE_PROJECT_DIR/scripts/hooks/record-file-touch.py\"",
|
|
72
|
+
"timeout": 10
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"matcher": "mcp__workbay-handoff-mcp__review_findings",
|
|
78
|
+
"hooks": [
|
|
79
|
+
{
|
|
80
|
+
"type": "command",
|
|
81
|
+
"command": "python3 \"$CLAUDE_PROJECT_DIR/scripts/hooks/ace-detect.py\"",
|
|
82
|
+
"timeout": 5
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"matcher": "mcp__workbay-handoff-mcp__get_handoff_state|mcp__workbay-handoff-mcp__load_session|mcp__workbay-handoff-mcp__render_handoff",
|
|
88
|
+
"hooks": [
|
|
89
|
+
{
|
|
90
|
+
"type": "command",
|
|
91
|
+
"command": "python3 \"$CLAUDE_PROJECT_DIR/scripts/hooks/slim-handoff-response.py\"",
|
|
92
|
+
"timeout": 5
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"matcher": "Bash",
|
|
98
|
+
"hooks": [
|
|
99
|
+
{
|
|
100
|
+
"type": "command",
|
|
101
|
+
"command": "python3 \"$CLAUDE_PROJECT_DIR/scripts/hooks/filter-test-output.py\"",
|
|
102
|
+
"timeout": 5
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"matcher": "Bash",
|
|
108
|
+
"hooks": [
|
|
109
|
+
{
|
|
110
|
+
"type": "command",
|
|
111
|
+
"command": "python3 \"$CLAUDE_PROJECT_DIR/scripts/hooks/capture-agent-errors.py\"",
|
|
112
|
+
"timeout": 15
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
],
|
|
117
|
+
"SessionStart": [
|
|
118
|
+
{
|
|
119
|
+
"matcher": "",
|
|
120
|
+
"hooks": [
|
|
121
|
+
{
|
|
122
|
+
"type": "command",
|
|
123
|
+
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/ensure-agent-surfaces.sh\"",
|
|
124
|
+
"timeout": 60
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"matcher": "",
|
|
130
|
+
"hooks": [
|
|
131
|
+
{
|
|
132
|
+
"type": "command",
|
|
133
|
+
"command": "python3 \"$CLAUDE_PROJECT_DIR/scripts/hooks/advise-worktree-cd.py\"",
|
|
134
|
+
"timeout": 5
|
|
135
|
+
}
|
|
136
|
+
]
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"matcher": "",
|
|
140
|
+
"hooks": [
|
|
141
|
+
{
|
|
142
|
+
"type": "command",
|
|
143
|
+
"command": "python3 \"$CLAUDE_PROJECT_DIR/scripts/hooks/coherence-self-check.py\"",
|
|
144
|
+
"timeout": 10
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
],
|
|
149
|
+
"UserPromptSubmit": [
|
|
150
|
+
{
|
|
151
|
+
"matcher": "",
|
|
152
|
+
"hooks": [
|
|
153
|
+
{
|
|
154
|
+
"type": "command",
|
|
155
|
+
"command": "python3 \"$CLAUDE_PROJECT_DIR/scripts/hooks/advise-worktree-cd.py\"",
|
|
156
|
+
"timeout": 5
|
|
157
|
+
}
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PreToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "Edit|Write|apply_patch|create_file|replace_string_in_file|multi_replace_string_in_file",
|
|
6
|
+
"type": "command",
|
|
7
|
+
"command": "python3 scripts/hooks/_run_guard.py .github/hooks/guard-worktree-drift.py",
|
|
8
|
+
"timeout": 5,
|
|
9
|
+
"statusMessage": "Guarding worktree drift"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"matcher": "Edit|Write|apply_patch|create_file|replace_string_in_file|multi_replace_string_in_file",
|
|
13
|
+
"type": "command",
|
|
14
|
+
"command": "python3 scripts/hooks/_run_guard.py .github/hooks/guard-main-branch.py",
|
|
15
|
+
"timeout": 5,
|
|
16
|
+
"statusMessage": "Guarding main branch edits"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"matcher": "Bash",
|
|
20
|
+
"type": "command",
|
|
21
|
+
"command": "python3 scripts/hooks/_run_guard.py scripts/hooks/guard-bash-main-branch.py",
|
|
22
|
+
"timeout": 5,
|
|
23
|
+
"statusMessage": "Guarding main branch"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"matcher": "Edit|Write",
|
|
27
|
+
"type": "command",
|
|
28
|
+
"command": "python3 scripts/hooks/_run_guard.py scripts/hooks/guard-task-plan-findings.py",
|
|
29
|
+
"timeout": 5,
|
|
30
|
+
"statusMessage": "Guarding task plan findings"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"matcher": "mcp__workbay-handoff-mcp__record_event|mcp__workbay-handoff-mcp__close_slice",
|
|
34
|
+
"type": "command",
|
|
35
|
+
"command": "python3 scripts/hooks/_run_guard.py scripts/hooks/guard-rationale-size.py",
|
|
36
|
+
"timeout": 5,
|
|
37
|
+
"statusMessage": "Checking rationale size"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"matcher": "mcp__workbay-handoff-mcp__record_event|mcp__workbay-handoff-mcp__review_findings|mcp__workbay-handoff-mcp__review_runs",
|
|
41
|
+
"type": "command",
|
|
42
|
+
"command": "python3 scripts/hooks/_run_guard.py scripts/hooks/validate-mcp-dict-params.py",
|
|
43
|
+
"timeout": 5,
|
|
44
|
+
"statusMessage": "Validating MCP dict params"
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"PostToolUse": [
|
|
48
|
+
{
|
|
49
|
+
"matcher": "Edit|Write",
|
|
50
|
+
"hooks": [
|
|
51
|
+
{
|
|
52
|
+
"type": "command",
|
|
53
|
+
"command": "python3 scripts/hooks/_run_guard.py scripts/hooks/record-file-touch.py",
|
|
54
|
+
"timeout": 10,
|
|
55
|
+
"statusMessage": "Recording file touch"
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"matcher": "mcp__workbay-handoff-mcp__review_findings",
|
|
61
|
+
"hooks": [
|
|
62
|
+
{
|
|
63
|
+
"type": "command",
|
|
64
|
+
"command": "python3 scripts/hooks/_run_guard.py scripts/hooks/ace-detect.py",
|
|
65
|
+
"timeout": 5,
|
|
66
|
+
"statusMessage": "Detecting ACE references"
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"matcher": "mcp__workbay-handoff-mcp__get_handoff_state|mcp__workbay-handoff-mcp__load_session|mcp__workbay-handoff-mcp__render_handoff",
|
|
72
|
+
"hooks": [
|
|
73
|
+
{
|
|
74
|
+
"type": "command",
|
|
75
|
+
"command": "python3 scripts/hooks/_run_guard.py scripts/hooks/slim-handoff-response.py",
|
|
76
|
+
"timeout": 5,
|
|
77
|
+
"statusMessage": "Slimming handoff response"
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"matcher": "Bash",
|
|
83
|
+
"hooks": [
|
|
84
|
+
{
|
|
85
|
+
"type": "command",
|
|
86
|
+
"command": "python3 scripts/hooks/_run_guard.py scripts/hooks/filter-test-output.py",
|
|
87
|
+
"timeout": 5,
|
|
88
|
+
"statusMessage": "Filtering test output"
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"matcher": "Bash",
|
|
94
|
+
"hooks": [
|
|
95
|
+
{
|
|
96
|
+
"type": "command",
|
|
97
|
+
"command": "python3 scripts/hooks/_run_guard.py scripts/hooks/capture-agent-errors.py",
|
|
98
|
+
"timeout": 15,
|
|
99
|
+
"statusMessage": "Capturing agent errors"
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
],
|
|
104
|
+
"SessionStart": [
|
|
105
|
+
{
|
|
106
|
+
"type": "command",
|
|
107
|
+
"command": "python3 scripts/hooks/_run_guard.py scripts/hooks/advise-worktree-cd.py",
|
|
108
|
+
"timeout": 5,
|
|
109
|
+
"statusMessage": "Advising worktree cd"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"type": "command",
|
|
113
|
+
"command": "python3 scripts/hooks/_run_guard.py scripts/hooks/coherence-self-check.py",
|
|
114
|
+
"timeout": 10,
|
|
115
|
+
"statusMessage": "Checking hook-surface coherence"
|
|
116
|
+
}
|
|
117
|
+
],
|
|
118
|
+
"UserPromptSubmit": [
|
|
119
|
+
{
|
|
120
|
+
"type": "command",
|
|
121
|
+
"command": "python3 scripts/hooks/_run_guard.py scripts/hooks/advise-worktree-cd.py",
|
|
122
|
+
"timeout": 5,
|
|
123
|
+
"statusMessage": "Advising worktree cd"
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""PreToolUse hook: block protected edits on main/master in the VS Code harness."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import datetime
|
|
6
|
+
import json
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
HELPER_DIR = Path(__file__).resolve().parents[2] / "scripts" / "hooks"
|
|
12
|
+
if str(HELPER_DIR) not in sys.path:
|
|
13
|
+
sys.path.insert(0, str(HELPER_DIR))
|
|
14
|
+
|
|
15
|
+
from _harness_protocol import ( # noqa: E402
|
|
16
|
+
HarnessContractMissingError,
|
|
17
|
+
find_permitted_main_surface,
|
|
18
|
+
load_branch_isolation_policy,
|
|
19
|
+
)
|
|
20
|
+
from _branch_isolation_guard import ( # noqa: E402
|
|
21
|
+
build_branch_naming_block_reason as _build_branch_naming_block_reason,
|
|
22
|
+
check_branch_naming as _check_branch_naming,
|
|
23
|
+
check_file_edit as _check_file_edit,
|
|
24
|
+
extract_candidate_paths as _extract_candidate_paths,
|
|
25
|
+
find_dirty_protected_paths as _check_dirty_protected_paths,
|
|
26
|
+
resolve_path_branch as _resolve_path_branch,
|
|
27
|
+
to_repo_relative as _to_repo_relative,
|
|
28
|
+
)
|
|
29
|
+
from _interp import resolve_env_alias # noqa: E402
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
_PROTECTED_BRANCHES = {"main", "master"}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _run_git(*args: str) -> str:
|
|
36
|
+
proc = subprocess.run(
|
|
37
|
+
["git", *args],
|
|
38
|
+
capture_output=True,
|
|
39
|
+
text=True,
|
|
40
|
+
timeout=5,
|
|
41
|
+
check=False,
|
|
42
|
+
)
|
|
43
|
+
if proc.returncode != 0:
|
|
44
|
+
return ""
|
|
45
|
+
return proc.stdout.strip()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _repo_root() -> str:
|
|
49
|
+
return _run_git("rev-parse", "--show-toplevel")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _current_branch() -> str:
|
|
53
|
+
return _run_git("branch", "--show-current")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _build_reason(branch: str, blocked_paths: list[str]) -> str:
|
|
57
|
+
rendered_paths = "\n".join(f" - {path}" for path in blocked_paths)
|
|
58
|
+
return (
|
|
59
|
+
"BLOCKED: Protected edits are not allowed on the main branch.\n\n"
|
|
60
|
+
f"Branch: {branch}\n"
|
|
61
|
+
"Files:\n"
|
|
62
|
+
f"{rendered_paths}\n\n"
|
|
63
|
+
"Create a feature branch first:\n"
|
|
64
|
+
" git checkout -b feature/<task-id>-<slug>\n\n"
|
|
65
|
+
"If you already have dirty code changes on main, move them to a feature branch or stash them before continuing.\n\n"
|
|
66
|
+
"Isolation options:\n"
|
|
67
|
+
" 1. Feature branch for single-agent work\n"
|
|
68
|
+
" 2. Worktree isolation for delegated subtasks\n"
|
|
69
|
+
" 3. Lane orchestration for multi-agent parallel work\n\n"
|
|
70
|
+
"Only explicitly permitted operator docs/config surfaces remain allowed on main.\n"
|
|
71
|
+
"Planning docs and implementation files now require a feature branch from the first edit.\n"
|
|
72
|
+
"See: docs/workbay/rules/development-workflow.md#branch-isolation-protocol-mandatory"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _build_dirty_reason(branch: str, dirty_paths: list[str]) -> str:
|
|
77
|
+
rendered_paths = "\n".join(f" - {path}" for path in dirty_paths)
|
|
78
|
+
return (
|
|
79
|
+
"BLOCKED: Protected code files are already dirty on the main branch.\n\n"
|
|
80
|
+
f"Branch: {branch}\n"
|
|
81
|
+
"Dirty files:\n"
|
|
82
|
+
f"{rendered_paths}\n\n"
|
|
83
|
+
"Move the work onto a feature branch or stash it before making more edits.\n\n"
|
|
84
|
+
"Recommended recovery:\n"
|
|
85
|
+
" 1. git checkout -b feature/<task-id>-<slug>\n"
|
|
86
|
+
" 2. keep the dirty changes on that branch, or stash them intentionally\n"
|
|
87
|
+
" 3. return to main only after the protected paths are clean again\n\n"
|
|
88
|
+
"See: docs/workbay/rules/development-workflow.md#branch-isolation-protocol-mandatory"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _permitted_candidate_paths(tool_name: str, tool_input: dict, repo_root: str, policy) -> list[str]:
|
|
93
|
+
candidate_paths = [
|
|
94
|
+
_to_repo_relative(raw_path, repo_root)
|
|
95
|
+
for raw_path in _extract_candidate_paths(tool_name, tool_input)
|
|
96
|
+
]
|
|
97
|
+
normalized = [path for path in candidate_paths if path]
|
|
98
|
+
if not normalized:
|
|
99
|
+
return []
|
|
100
|
+
if not all(find_permitted_main_surface(path, policy) is not None for path in normalized):
|
|
101
|
+
return []
|
|
102
|
+
return normalized
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _log_telemetry(tool_name: str, blocked_paths: list[str], branch: str, *, outcome: str) -> None:
|
|
106
|
+
try:
|
|
107
|
+
state_dir = Path(".task-state")
|
|
108
|
+
state_dir.mkdir(exist_ok=True)
|
|
109
|
+
record = {
|
|
110
|
+
"timestamp": datetime.datetime.now(tz=datetime.timezone.utc).isoformat(),
|
|
111
|
+
"tool": tool_name,
|
|
112
|
+
"branch": branch,
|
|
113
|
+
"outcome": outcome,
|
|
114
|
+
"paths": blocked_paths,
|
|
115
|
+
}
|
|
116
|
+
with (state_dir / "branch_isolation_guard.jsonl").open("a", encoding="utf-8") as handle:
|
|
117
|
+
handle.write(json.dumps(record) + "\n")
|
|
118
|
+
except OSError:
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def main() -> None:
|
|
123
|
+
try:
|
|
124
|
+
data = json.load(sys.stdin)
|
|
125
|
+
except (json.JSONDecodeError, ValueError):
|
|
126
|
+
sys.exit(0)
|
|
127
|
+
|
|
128
|
+
tool_name = data.get("toolName") or data.get("tool_name") or ""
|
|
129
|
+
tool_input = data.get("toolInput") or data.get("tool_input") or {}
|
|
130
|
+
if not isinstance(tool_input, dict):
|
|
131
|
+
sys.exit(0)
|
|
132
|
+
|
|
133
|
+
branch = _current_branch()
|
|
134
|
+
repo_root = _repo_root()
|
|
135
|
+
workspace_root = Path(repo_root or Path.cwd())
|
|
136
|
+
try:
|
|
137
|
+
policy = load_branch_isolation_policy(workspace_root)
|
|
138
|
+
except HarnessContractMissingError as exc:
|
|
139
|
+
print(
|
|
140
|
+
json.dumps(
|
|
141
|
+
{
|
|
142
|
+
"hookSpecificOutput": {
|
|
143
|
+
"hookEventName": "PreToolUse",
|
|
144
|
+
"permissionDecision": "block",
|
|
145
|
+
"permissionDecisionReason": str(exc),
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
sys.exit(0)
|
|
151
|
+
|
|
152
|
+
# Branch-naming gate (internal). Fires before the
|
|
153
|
+
# protected-path check so non-conforming branches reject
|
|
154
|
+
# uniformly. ``WORKBAY_ALLOW_NONCONFORMING_BRANCH=1`` is the
|
|
155
|
+
# documented escape valve; pre-commit / pre-push gates honour
|
|
156
|
+
# their own env vars per implementation note §4 / §4b.
|
|
157
|
+
non_conforming = _check_branch_naming(branch)
|
|
158
|
+
if non_conforming is not None and resolve_env_alias("WORKBAY_ALLOW_NONCONFORMING_BRANCH") != "1":
|
|
159
|
+
_log_telemetry(tool_name, [], non_conforming, outcome="non_conforming_branch")
|
|
160
|
+
print(
|
|
161
|
+
json.dumps(
|
|
162
|
+
{
|
|
163
|
+
"hookSpecificOutput": {
|
|
164
|
+
"hookEventName": "PreToolUse",
|
|
165
|
+
"permissionDecision": "block",
|
|
166
|
+
"permissionDecisionReason": _build_branch_naming_block_reason(non_conforming),
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
)
|
|
170
|
+
)
|
|
171
|
+
sys.exit(0)
|
|
172
|
+
|
|
173
|
+
result = _check_file_edit(
|
|
174
|
+
tool_name,
|
|
175
|
+
tool_input,
|
|
176
|
+
branch=branch,
|
|
177
|
+
repo_root=repo_root,
|
|
178
|
+
policy=policy,
|
|
179
|
+
protected_branches=_PROTECTED_BRANCHES,
|
|
180
|
+
)
|
|
181
|
+
if result is not None:
|
|
182
|
+
resolved_branch, blocked_paths = result
|
|
183
|
+
_log_telemetry(tool_name, blocked_paths, resolved_branch, outcome="attempted_protected_edit")
|
|
184
|
+
print(
|
|
185
|
+
json.dumps(
|
|
186
|
+
{
|
|
187
|
+
"hookSpecificOutput": {
|
|
188
|
+
"hookEventName": "PreToolUse",
|
|
189
|
+
"permissionDecision": "block",
|
|
190
|
+
"permissionDecisionReason": _build_reason(resolved_branch, blocked_paths),
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
)
|
|
195
|
+
sys.exit(0)
|
|
196
|
+
|
|
197
|
+
dirty_result = _check_dirty_protected_paths(
|
|
198
|
+
branch=branch,
|
|
199
|
+
repo_root=repo_root,
|
|
200
|
+
policy=policy,
|
|
201
|
+
protected_branches=_PROTECTED_BRANCHES,
|
|
202
|
+
)
|
|
203
|
+
if dirty_result is None:
|
|
204
|
+
sys.exit(0)
|
|
205
|
+
|
|
206
|
+
resolved_branch, dirty_paths = dirty_result
|
|
207
|
+
|
|
208
|
+
# Per-path worktree resolution: if every candidate edit path resolves
|
|
209
|
+
# (via its own worktree) to a non-protected branch, the edit isn't a
|
|
210
|
+
# main-branch write at all and the dirty-paths-on-main check shouldn't
|
|
211
|
+
# apply. This unblocks edits to sibling worktrees while the harness cwd
|
|
212
|
+
# remains on main.
|
|
213
|
+
raw_paths = [p for p in _extract_candidate_paths(tool_name, tool_input) if p]
|
|
214
|
+
if raw_paths:
|
|
215
|
+
resolved = [(_resolve_path_branch(p) or branch) for p in raw_paths]
|
|
216
|
+
if all(b not in _PROTECTED_BRANCHES for b in resolved):
|
|
217
|
+
sys.exit(0)
|
|
218
|
+
|
|
219
|
+
candidate_paths = _permitted_candidate_paths(tool_name, tool_input, repo_root, policy)
|
|
220
|
+
if candidate_paths and not any(path in set(dirty_paths) for path in candidate_paths):
|
|
221
|
+
sys.exit(0)
|
|
222
|
+
|
|
223
|
+
_log_telemetry(tool_name, dirty_paths, resolved_branch, outcome="dirty_protected_main_paths")
|
|
224
|
+
print(
|
|
225
|
+
json.dumps(
|
|
226
|
+
{
|
|
227
|
+
"hookSpecificOutput": {
|
|
228
|
+
"hookEventName": "PreToolUse",
|
|
229
|
+
"permissionDecision": "block",
|
|
230
|
+
"permissionDecisionReason": _build_dirty_reason(resolved_branch, dirty_paths),
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
sys.exit(0)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
if __name__ == "__main__":
|
|
239
|
+
main()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""VS Code PreToolUse wrapper for the shared worktree-drift hook."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
HELPER_DIR = Path(__file__).resolve().parents[2] / "scripts" / "hooks"
|
|
11
|
+
if str(HELPER_DIR) not in sys.path:
|
|
12
|
+
sys.path.insert(0, str(HELPER_DIR))
|
|
13
|
+
|
|
14
|
+
from _worktree_drift import main # noqa: E402
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
if __name__ == "__main__":
|
|
18
|
+
raise SystemExit(main())
|