workstate-system 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. workstate_system-0.1.0/.github/copilot-instructions.md +7 -0
  2. workstate_system-0.1.0/.github/hooks/guard-main-branch.py +239 -0
  3. workstate_system-0.1.0/.github/hooks/guard-worktree-drift.py +18 -0
  4. workstate_system-0.1.0/.github/hooks/terminal-guard.json +90 -0
  5. workstate_system-0.1.0/.github/hooks/test_guard_main_branch.py +382 -0
  6. workstate_system-0.1.0/.github/hooks/test_guard_worktree_drift.py +559 -0
  7. workstate_system-0.1.0/.github/hooks/token_budget.py +72 -0
  8. workstate_system-0.1.0/.github/prompts/auto-fix.prompt.md +151 -0
  9. workstate_system-0.1.0/.github/prompts/branch-lifecycle.prompt.md +142 -0
  10. workstate_system-0.1.0/.github/prompts/branch-review.prompt.md +115 -0
  11. workstate_system-0.1.0/.github/prompts/handoff-lifecycle.prompt.md +43 -0
  12. workstate_system-0.1.0/.github/prompts/incremental-implementation.prompt.md +108 -0
  13. workstate_system-0.1.0/.github/prompts/investigate.prompt.md +139 -0
  14. workstate_system-0.1.0/.github/prompts/plan-analyze.prompt.md +96 -0
  15. workstate_system-0.1.0/.github/prompts/planning-review.prompt.md +115 -0
  16. workstate_system-0.1.0/.github/prompts/review-parallel.prompt.md +122 -0
  17. workstate_system-0.1.0/.github/prompts/scope.prompt.md +94 -0
  18. workstate_system-0.1.0/.github/prompts/tdd.prompt.md +106 -0
  19. workstate_system-0.1.0/.gitignore +4 -0
  20. workstate_system-0.1.0/.vscode/settings.json +5 -0
  21. workstate_system-0.1.0/CHANGELOG.md +120 -0
  22. workstate_system-0.1.0/CLAUDE.md +37 -0
  23. workstate_system-0.1.0/Makefile.d/compaction.mk +39 -0
  24. workstate_system-0.1.0/Makefile.d/lifecycle.mk +156 -0
  25. workstate_system-0.1.0/Makefile.d/plans.mk +66 -0
  26. workstate_system-0.1.0/Makefile.d/plugins.mk +42 -0
  27. workstate_system-0.1.0/Makefile.d/workflows.mk +39 -0
  28. workstate_system-0.1.0/PKG-INFO +69 -0
  29. workstate_system-0.1.0/README.md +56 -0
  30. workstate_system-0.1.0/config/agent-workflows/mcp_servers.yaml +39 -0
  31. workstate_system-0.1.0/config/agent-workflows/portable_commands.json +383 -0
  32. workstate_system-0.1.0/config/agent-workflows/prompts/review-parallel/default.md +51 -0
  33. workstate_system-0.1.0/docs/plugin-distribution.md +487 -0
  34. workstate_system-0.1.0/docs/workstate/contracts/harness-protocol.yaml +266 -0
  35. workstate_system-0.1.0/docs/workstate/contracts/overlay-manifest.yaml +150 -0
  36. workstate_system-0.1.0/docs/workstate/contracts/repo-intel-mcp-candidates.md +39 -0
  37. workstate_system-0.1.0/docs/workstate/contracts/subagent-bridge-interface-note.md +74 -0
  38. workstate_system-0.1.0/docs/workstate/contracts/workstate-handoff-mcp.md +778 -0
  39. workstate_system-0.1.0/docs/workstate/contracts/workstate-orchestrator-mcp.md +375 -0
  40. workstate_system-0.1.0/docs/workstate/generated/codex-command-router.md +28 -0
  41. workstate_system-0.1.0/docs/workstate/maps/mcp-tool-routing.yaml +110 -0
  42. workstate_system-0.1.0/docs/workstate/rules/branch-review-guide.md +581 -0
  43. workstate_system-0.1.0/docs/workstate/rules/development-workflow.md +664 -0
  44. workstate_system-0.1.0/docs/workstate/rules/lifecycle-recovery.md +147 -0
  45. workstate_system-0.1.0/docs/workstate/rules/planning-artifact-home.md +178 -0
  46. workstate_system-0.1.0/docs/workstate/templates/ADR.template.md +139 -0
  47. workstate_system-0.1.0/docs/workstate/templates/ASSESSMENT.template.md +124 -0
  48. workstate_system-0.1.0/docs/workstate/templates/CURRENT_TASK.template.md +89 -0
  49. workstate_system-0.1.0/docs/workstate/templates/DECISION_BREAKING_CHANGE.template.md +41 -0
  50. workstate_system-0.1.0/docs/workstate/templates/DECISION_CONTRACT_CHANGE.template.md +41 -0
  51. workstate_system-0.1.0/docs/workstate/templates/DECISION_CROSS_LANE.template.md +41 -0
  52. workstate_system-0.1.0/docs/workstate/templates/EPIC.template.md +130 -0
  53. workstate_system-0.1.0/docs/workstate/templates/ROADMAP.template.md +132 -0
  54. workstate_system-0.1.0/docs/workstate/templates/SKILL_ANATOMY.template.md +65 -0
  55. workstate_system-0.1.0/docs/workstate/templates/SPEC.template.md +166 -0
  56. workstate_system-0.1.0/docs/workstate/templates/TASK_PLAN.template.md +225 -0
  57. workstate_system-0.1.0/docs/workstate/templates/WORKTREE_LANE_BRIEF.template.md +42 -0
  58. workstate_system-0.1.0/docs/workstate/templates/WORKTREE_LANE_REPORT.template.md +33 -0
  59. workstate_system-0.1.0/docs/workstate/templates/slice-complete-template.md +47 -0
  60. workstate_system-0.1.0/pyproject.toml +65 -0
  61. workstate_system-0.1.0/scripts/check_harness_sync.py +1076 -0
  62. workstate_system-0.1.0/scripts/check_recordkeeping.py +144 -0
  63. workstate_system-0.1.0/scripts/check_skills.py +398 -0
  64. workstate_system-0.1.0/scripts/check_workflow_facade.py +358 -0
  65. workstate_system-0.1.0/scripts/generate_agent_workflows.py +1566 -0
  66. workstate_system-0.1.0/scripts/hooks/_active_task_context.py +154 -0
  67. workstate_system-0.1.0/scripts/hooks/_bash_isolation_guard.py +376 -0
  68. workstate_system-0.1.0/scripts/hooks/_branch_isolation_guard.py +469 -0
  69. workstate_system-0.1.0/scripts/hooks/_guard_main_branch_inline.py +174 -0
  70. workstate_system-0.1.0/scripts/hooks/_harness_protocol.py +393 -0
  71. workstate_system-0.1.0/scripts/hooks/_post_commit_refresh_sha.py +83 -0
  72. workstate_system-0.1.0/scripts/hooks/_protocol.py +99 -0
  73. workstate_system-0.1.0/scripts/hooks/_worktree_drift.py +455 -0
  74. workstate_system-0.1.0/scripts/hooks/ace-detect.py +108 -0
  75. workstate_system-0.1.0/scripts/hooks/advise-worktree-cd.py +140 -0
  76. workstate_system-0.1.0/scripts/hooks/check_branch_naming.py +520 -0
  77. workstate_system-0.1.0/scripts/hooks/check_main_clean.py +205 -0
  78. workstate_system-0.1.0/scripts/hooks/check_root_on_main.py +69 -0
  79. workstate_system-0.1.0/scripts/hooks/compact-session.py +392 -0
  80. workstate_system-0.1.0/scripts/hooks/filter-test-output.py +316 -0
  81. workstate_system-0.1.0/scripts/hooks/git/post-checkout +42 -0
  82. workstate_system-0.1.0/scripts/hooks/git/post-commit +19 -0
  83. workstate_system-0.1.0/scripts/hooks/git/post-merge +25 -0
  84. workstate_system-0.1.0/scripts/hooks/git/post-rewrite +15 -0
  85. workstate_system-0.1.0/scripts/hooks/git/pre-commit +16 -0
  86. workstate_system-0.1.0/scripts/hooks/git/pre-push +31 -0
  87. workstate_system-0.1.0/scripts/hooks/guard-bash-main-branch.py +148 -0
  88. workstate_system-0.1.0/scripts/hooks/guard-main-branch.sh +80 -0
  89. workstate_system-0.1.0/scripts/hooks/guard-rationale-size.py +143 -0
  90. workstate_system-0.1.0/scripts/hooks/guard-task-plan-findings.py +658 -0
  91. workstate_system-0.1.0/scripts/hooks/guard-worktree-drift.sh +6 -0
  92. workstate_system-0.1.0/scripts/hooks/lint-dashboard-txt.py +124 -0
  93. workstate_system-0.1.0/scripts/hooks/lint-expected-revision.py +175 -0
  94. workstate_system-0.1.0/scripts/hooks/lint-no-inline-python-heredoc.py +179 -0
  95. workstate_system-0.1.0/scripts/hooks/record-file-touch.py +186 -0
  96. workstate_system-0.1.0/scripts/hooks/regenerate-task-views.sh +78 -0
  97. workstate_system-0.1.0/scripts/hooks/slim-handoff-response.py +281 -0
  98. workstate_system-0.1.0/scripts/hooks/test_ace_detect.py +73 -0
  99. workstate_system-0.1.0/scripts/hooks/test_active_task_context.py +214 -0
  100. workstate_system-0.1.0/scripts/hooks/test_advise_worktree_cd.py +219 -0
  101. workstate_system-0.1.0/scripts/hooks/test_bash_isolation_guard_formatters.py +162 -0
  102. workstate_system-0.1.0/scripts/hooks/test_branch_isolation_policy_split.py +180 -0
  103. workstate_system-0.1.0/scripts/hooks/test_check_branch_naming.py +937 -0
  104. workstate_system-0.1.0/scripts/hooks/test_check_main_clean.py +437 -0
  105. workstate_system-0.1.0/scripts/hooks/test_check_root_on_main.py +48 -0
  106. workstate_system-0.1.0/scripts/hooks/test_compact_session_hook.py +930 -0
  107. workstate_system-0.1.0/scripts/hooks/test_dev_workflow_compaction_docs.py +65 -0
  108. workstate_system-0.1.0/scripts/hooks/test_dirty_permitted_carveout.py +90 -0
  109. workstate_system-0.1.0/scripts/hooks/test_doc_references.py +128 -0
  110. workstate_system-0.1.0/scripts/hooks/test_filter_test_output.py +410 -0
  111. workstate_system-0.1.0/scripts/hooks/test_guard_bash_main_branch_lightweight.py +164 -0
  112. workstate_system-0.1.0/scripts/hooks/test_guard_rationale_size.py +193 -0
  113. workstate_system-0.1.0/scripts/hooks/test_guard_task_plan_findings.py +805 -0
  114. workstate_system-0.1.0/scripts/hooks/test_harness_contract_missing_policy.py +131 -0
  115. workstate_system-0.1.0/scripts/hooks/test_harness_hook_configs.py +119 -0
  116. workstate_system-0.1.0/scripts/hooks/test_lint_dashboard_txt.py +106 -0
  117. workstate_system-0.1.0/scripts/hooks/test_protocol_validation_wiring.py +103 -0
  118. workstate_system-0.1.0/scripts/hooks/test_record_file_touch.py +241 -0
  119. workstate_system-0.1.0/scripts/hooks/test_regenerate_task_views.py +135 -0
  120. workstate_system-0.1.0/scripts/hooks/test_slim_handoff_response.py +312 -0
  121. workstate_system-0.1.0/scripts/hooks/test_validate_mcp_dict_params.py +154 -0
  122. workstate_system-0.1.0/scripts/hooks/test_worktree_branch_resolution.py +165 -0
  123. workstate_system-0.1.0/scripts/hooks/test_worktree_drift_unchanged.py +178 -0
  124. workstate_system-0.1.0/scripts/hooks/validate-mcp-dict-params.py +110 -0
  125. workstate_system-0.1.0/scripts/lint_hoisted_paths.py +175 -0
  126. workstate_system-0.1.0/scripts/migrate_skills_to_neutral_layout.py +178 -0
  127. workstate_system-0.1.0/scripts/overlay_resolver.py +405 -0
  128. workstate_system-0.1.0/scripts/pytest_path_guard.py +87 -0
  129. workstate_system-0.1.0/scripts/validate_claude_settings_pin.py +174 -0
  130. workstate_system-0.1.0/scripts/workstate/git-plan-cat.sh +27 -0
  131. workstate_system-0.1.0/scripts/workstate/lifecycle/__init__.py +12 -0
  132. workstate_system-0.1.0/scripts/workstate/lifecycle/__main__.py +15 -0
  133. workstate_system-0.1.0/scripts/workstate/lifecycle/cli.py +157 -0
  134. workstate_system-0.1.0/scripts/workstate/lifecycle/handlers/__init__.py +11 -0
  135. workstate_system-0.1.0/scripts/workstate/lifecycle/handlers/_common.py +646 -0
  136. workstate_system-0.1.0/scripts/workstate/lifecycle/handlers/backfill_plan_acceptance.py +254 -0
  137. workstate_system-0.1.0/scripts/workstate/lifecycle/handlers/close_check.py +171 -0
  138. workstate_system-0.1.0/scripts/workstate/lifecycle/handlers/context.py +248 -0
  139. workstate_system-0.1.0/scripts/workstate/lifecycle/handlers/doctor.py +872 -0
  140. workstate_system-0.1.0/scripts/workstate/lifecycle/handlers/plan_accept.py +446 -0
  141. workstate_system-0.1.0/scripts/workstate/lifecycle/handlers/plan_baseline.py +620 -0
  142. workstate_system-0.1.0/scripts/workstate/lifecycle/handlers/project_events_replay.py +177 -0
  143. workstate_system-0.1.0/scripts/workstate/lifecycle/handlers/provision_env.py +95 -0
  144. workstate_system-0.1.0/scripts/workstate/lifecycle/handlers/review_ready.py +635 -0
  145. workstate_system-0.1.0/scripts/workstate/lifecycle/handlers/shell_out.py +275 -0
  146. workstate_system-0.1.0/scripts/workstate/lifecycle/handlers/skill_broadcast.py +149 -0
  147. workstate_system-0.1.0/scripts/workstate/lifecycle/handlers/slice_commit.py +275 -0
  148. workstate_system-0.1.0/scripts/workstate/lifecycle/handlers/slice_start.py +198 -0
  149. workstate_system-0.1.0/scripts/workstate/lifecycle/handlers/status.py +669 -0
  150. workstate_system-0.1.0/scripts/workstate/lifecycle/handlers/sync_task_plan_checklist.py +919 -0
  151. workstate_system-0.1.0/scripts/workstate/lifecycle/handlers/task_finish.py +460 -0
  152. workstate_system-0.1.0/scripts/workstate/lifecycle/handlers/task_plan_checklist_audit.py +340 -0
  153. workstate_system-0.1.0/scripts/workstate/lifecycle/handlers/task_plan_checklist_backfill.py +268 -0
  154. workstate_system-0.1.0/scripts/workstate/lifecycle/handlers/task_start.py +836 -0
  155. workstate_system-0.1.0/scripts/workstate/lifecycle/handlers/tasks.py +296 -0
  156. workstate_system-0.1.0/scripts/workstate/lifecycle/projection.py +246 -0
  157. workstate_system-0.1.0/scripts/workstate/lifecycle/receipts.py +321 -0
  158. workstate_system-0.1.0/scripts/workstate/lifecycle/resolver.py +279 -0
  159. workstate_system-0.1.0/scripts/workstate/lifecycle/uv_provisioning.py +440 -0
  160. workstate_system-0.1.0/skills/auto-fix/body.md +122 -0
  161. workstate_system-0.1.0/skills/auto-fix/skill.yaml +20 -0
  162. workstate_system-0.1.0/skills/branch-lifecycle/body.md +110 -0
  163. workstate_system-0.1.0/skills/branch-lifecycle/skill.yaml +20 -0
  164. workstate_system-0.1.0/skills/branch-review/body.md +85 -0
  165. workstate_system-0.1.0/skills/branch-review/skill.yaml +20 -0
  166. workstate_system-0.1.0/skills/commit2git/body.md +125 -0
  167. workstate_system-0.1.0/skills/commit2git/skill.yaml +12 -0
  168. workstate_system-0.1.0/skills/daemon-lifecycle/body.md +113 -0
  169. workstate_system-0.1.0/skills/daemon-lifecycle/skill.yaml +16 -0
  170. workstate_system-0.1.0/skills/document-sync/body.md +205 -0
  171. workstate_system-0.1.0/skills/document-sync/skill.yaml +16 -0
  172. workstate_system-0.1.0/skills/handoff-lifecycle/body.md +94 -0
  173. workstate_system-0.1.0/skills/handoff-lifecycle/skill.yaml +23 -0
  174. workstate_system-0.1.0/skills/incremental-implementation/body.md +79 -0
  175. workstate_system-0.1.0/skills/incremental-implementation/skill.yaml +17 -0
  176. workstate_system-0.1.0/skills/investigate/body.md +110 -0
  177. workstate_system-0.1.0/skills/investigate/skill.yaml +17 -0
  178. workstate_system-0.1.0/skills/plan-analyze/body.md +70 -0
  179. workstate_system-0.1.0/skills/plan-analyze/skill.yaml +15 -0
  180. workstate_system-0.1.0/skills/planning-review/body.md +86 -0
  181. workstate_system-0.1.0/skills/planning-review/skill.yaml +16 -0
  182. workstate_system-0.1.0/skills/refactor/body.md +309 -0
  183. workstate_system-0.1.0/skills/refactor/skill.yaml +15 -0
  184. workstate_system-0.1.0/skills/rescue-lane/body.md +77 -0
  185. workstate_system-0.1.0/skills/rescue-lane/skill.yaml +16 -0
  186. workstate_system-0.1.0/skills/review/body.md +132 -0
  187. workstate_system-0.1.0/skills/review/skill.yaml +20 -0
  188. workstate_system-0.1.0/skills/review-parallel/body.md +98 -0
  189. workstate_system-0.1.0/skills/review-parallel/skill.yaml +18 -0
  190. workstate_system-0.1.0/skills/scope/body.md +69 -0
  191. workstate_system-0.1.0/skills/scope/skill.yaml +15 -0
  192. workstate_system-0.1.0/skills/security-audit/body.md +297 -0
  193. workstate_system-0.1.0/skills/security-audit/skill.yaml +17 -0
  194. workstate_system-0.1.0/skills/spec/body.md +97 -0
  195. workstate_system-0.1.0/skills/spec/skill.yaml +17 -0
  196. workstate_system-0.1.0/skills/subfeature-committer/body.md +78 -0
  197. workstate_system-0.1.0/skills/subfeature-committer/skill.yaml +12 -0
  198. workstate_system-0.1.0/skills/tdd/body.md +79 -0
  199. workstate_system-0.1.0/skills/tdd/skill.yaml +15 -0
  200. workstate_system-0.1.0/skills/worktree-orchestrator/body.md +185 -0
  201. workstate_system-0.1.0/skills/worktree-orchestrator/skill.yaml +17 -0
  202. workstate_system-0.1.0/skills/worktree-worker/body.md +153 -0
  203. workstate_system-0.1.0/skills/worktree-worker/skill.yaml +17 -0
  204. workstate_system-0.1.0/workstate_system/__init__.py +24 -0
@@ -0,0 +1,7 @@
1
+ # Agentic System Copilot Instructions
2
+
3
+ Run `make context` at session start when you are working in this package worktree.
4
+
5
+ Prefer MCP tool calls over ad-hoc direct access when the harness already exposes the operation.
6
+
7
+ For Python fallback surfaces, import from the package root, for example `from workstate_handoff_mcp import ...`.
@@ -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 os
8
+ import subprocess
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ HELPER_DIR = Path(__file__).resolve().parents[2] / "scripts" / "hooks"
13
+ if str(HELPER_DIR) not in sys.path:
14
+ sys.path.insert(0, str(HELPER_DIR))
15
+
16
+ from _harness_protocol import ( # noqa: WORKSTATE-REF-402
17
+ HarnessContractMissingError,
18
+ find_permitted_main_surface,
19
+ load_branch_isolation_policy,
20
+ )
21
+ from _branch_isolation_guard import ( # noqa: WORKSTATE-REF-402
22
+ build_branch_naming_block_reason as _build_branch_naming_block_reason,
23
+ check_branch_naming as _check_branch_naming,
24
+ check_file_edit as _check_file_edit,
25
+ extract_candidate_paths as _extract_candidate_paths,
26
+ find_dirty_protected_paths as _check_dirty_protected_paths,
27
+ resolve_path_branch as _resolve_path_branch,
28
+ to_repo_relative as _to_repo_relative,
29
+ )
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/workstate/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/workstate/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 (implementation note implementation note). Fires before the
153
+ # protected-path check so non-conforming branches reject
154
+ # uniformly. ``WORKSTATE_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 os.environ.get("WORKSTATE_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: WORKSTATE-REF-402
15
+
16
+
17
+ if __name__ == "__main__":
18
+ raise SystemExit(main())
@@ -0,0 +1,90 @@
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 .github/hooks/guard-worktree-drift.py",
8
+ "timeout": 5
9
+ },
10
+ {
11
+ "matcher": "Edit|Write|apply_patch|create_file|replace_string_in_file|multi_replace_string_in_file",
12
+ "type": "command",
13
+ "command": "python3 .github/hooks/guard-main-branch.py",
14
+ "timeout": 5
15
+ },
16
+ {
17
+ "matcher": "Bash",
18
+ "type": "command",
19
+ "command": "python3 scripts/hooks/guard-bash-main-branch.py",
20
+ "timeout": 5
21
+ },
22
+ {
23
+ "matcher": "Edit|Write",
24
+ "type": "command",
25
+ "command": "python3 scripts/hooks/guard-task-plan-findings.py",
26
+ "timeout": 5
27
+ },
28
+ {
29
+ "matcher": "mcp_workstate-handoff-mcp_record_event|mcp_workstate-handoff-mcp_close_slice|mcp__workstate-handoff-mcp__record_event|mcp__workstate-handoff-mcp__close_slice",
30
+ "type": "command",
31
+ "command": "python3 scripts/hooks/guard-rationale-size.py",
32
+ "timeout": 5
33
+ },
34
+ {
35
+ "matcher": "mcp_workstate-handoff-mcp_record_event|mcp_workstate-handoff-mcp_review_findings|mcp_workstate-handoff-mcp_review_runs|mcp__workstate-handoff-mcp__record_event|mcp__workstate-handoff-mcp__review_findings|mcp__workstate-handoff-mcp__review_runs",
36
+ "type": "command",
37
+ "command": "python3 scripts/hooks/validate-mcp-dict-params.py",
38
+ "timeout": 5
39
+ }
40
+ ],
41
+ "PostToolUse": [
42
+ {
43
+ "matcher": "Edit|Write",
44
+ "hooks": [
45
+ {
46
+ "type": "command",
47
+ "command": "python3 scripts/hooks/record-file-touch.py",
48
+ "timeout": 10
49
+ }
50
+ ]
51
+ },
52
+ {
53
+ "matcher": "mcp_workstate-handoff-mcp_review_findings|mcp__workstate-handoff-mcp__review_findings",
54
+ "hooks": [
55
+ {
56
+ "type": "command",
57
+ "command": "python3 scripts/hooks/ace-detect.py",
58
+ "timeout": 5
59
+ }
60
+ ]
61
+ },
62
+ {
63
+ "matcher": "mcp_workstate-handoff-mcp_get_handoff_state|mcp_workstate-handoff-mcp_load_session|mcp_workstate-handoff-mcp_render_handoff|mcp__workstate-handoff-mcp__get_handoff_state|mcp__workstate-handoff-mcp__load_session|mcp__workstate-handoff-mcp__render_handoff",
64
+ "type": "command",
65
+ "command": "python3 scripts/hooks/slim-handoff-response.py",
66
+ "timeout": 5
67
+ },
68
+ {
69
+ "matcher": "Bash",
70
+ "type": "command",
71
+ "command": "python3 scripts/hooks/filter-test-output.py",
72
+ "timeout": 5
73
+ }
74
+ ],
75
+ "SessionStart": [
76
+ {
77
+ "type": "command",
78
+ "command": "python3 scripts/hooks/advise-worktree-cd.py",
79
+ "timeout": 5
80
+ }
81
+ ],
82
+ "UserPromptSubmit": [
83
+ {
84
+ "type": "command",
85
+ "command": "python3 scripts/hooks/advise-worktree-cd.py",
86
+ "timeout": 5
87
+ }
88
+ ]
89
+ }
90
+ }