claude-dev-env 2.4.0 → 2.5.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.
Files changed (195) hide show
  1. package/CLAUDE.md +53 -49
  2. package/_shared/pr-loop/scripts/_claude_permissions_common.py +84 -0
  3. package/_shared/pr-loop/scripts/code_rules_gate.py +4 -2
  4. package/_shared/pr-loop/scripts/grant_project_claude_permissions.py +306 -306
  5. package/_shared/pr-loop/scripts/pr_loop_shared_constants/claude_permissions_constants.py +44 -0
  6. package/_shared/pr-loop/scripts/pr_loop_shared_constants/copilot_quota_constants.py +24 -24
  7. package/_shared/pr-loop/scripts/pr_loop_shared_constants/stale_worktree_rule_sweep_constants.py +107 -107
  8. package/_shared/pr-loop/scripts/revoke_project_claude_permissions.py +290 -48
  9. package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +42 -2
  10. package/_shared/pr-loop/scripts/tests/test_claude_permissions_constants.py +36 -0
  11. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +100 -1
  12. package/_shared/pr-loop/scripts/tests/test_fix_hookspath.py +497 -497
  13. package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +311 -2
  14. package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep.py +301 -301
  15. package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep_constants.py +85 -85
  16. package/_shared/pr-loop/worker-spawn.md +1 -1
  17. package/agents/CLAUDE.md +2 -1
  18. package/agents/caveman.md +0 -1
  19. package/agents/clasp-deployment-orchestrator.md +0 -1
  20. package/agents/clean-coder.md +0 -1
  21. package/agents/code-advisor.md +0 -1
  22. package/agents/code-quality-agent.md +1 -2
  23. package/agents/code-verifier.md +0 -1
  24. package/agents/deep-research.md +0 -1
  25. package/agents/docs-agent.md +0 -1
  26. package/agents/git-commit-crafter.md +0 -1
  27. package/agents/issue-tracker.md +42 -0
  28. package/agents/plan-packet-validator.md +0 -1
  29. package/agents/pr-description-writer.md +0 -1
  30. package/agents/test_agent_frontmatter.py +67 -18
  31. package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +143 -141
  32. package/bin/CLAUDE.md +68 -5
  33. package/bin/ever-shipped-skills.mjs +1 -0
  34. package/bin/install-constants.mjs +88 -0
  35. package/bin/install.mjs +1138 -114
  36. package/bin/install.prune.test.mjs +869 -19
  37. package/bin/install.test.mjs +906 -2
  38. package/commands/implement.md +1 -1
  39. package/commands/right-size.md +1 -1
  40. package/docs/CLAUDE.md +1 -0
  41. package/docs/host-pool-health-monitor.md +102 -0
  42. package/docs/references/CLAUDE.md +4 -2
  43. package/docs/references/advisor-tool.md +13 -0
  44. package/docs/references/code-review-enforcement.md +10 -0
  45. package/docs/references/team-advisor-skill.md +14 -0
  46. package/hooks/blocking/CLAUDE.md +1 -0
  47. package/hooks/blocking/code_review_pr_create_gate.py +7 -3
  48. package/hooks/blocking/code_review_push_gate.py +9 -4
  49. package/hooks/blocking/code_review_stamp_directory_write_blocker.py +8 -0
  50. package/hooks/blocking/config/__init__.py +5 -5
  51. package/hooks/blocking/config/code_review_enforcement_constants.py +4 -1
  52. package/hooks/blocking/config/test_code_review_enforcement_constants.py +5 -0
  53. package/hooks/blocking/config/verified_commit_constants.py +160 -159
  54. package/hooks/blocking/orchestrator_refresh_reschedule_gate.py +256 -0
  55. package/hooks/blocking/pre_tool_use_dispatcher.py +24 -24
  56. package/hooks/blocking/test_code_review_pr_create_gate.py +14 -0
  57. package/hooks/blocking/test_code_review_push_gate.py +16 -0
  58. package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +19 -0
  59. package/hooks/blocking/test_orchestrator_refresh_reschedule_gate.py +231 -0
  60. package/hooks/blocking/test_pre_tool_use_dispatcher.py +10 -1
  61. package/hooks/blocking/test_verdict_directory_write_blocker.py +808 -808
  62. package/hooks/blocking/test_verification_verdict_store.py +54 -0
  63. package/hooks/blocking/test_verified_commit_gate.py +581 -581
  64. package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +131 -131
  65. package/hooks/blocking/verdict_directory_write_blocker.py +687 -687
  66. package/hooks/blocking/verification_verdict_store.py +1039 -1036
  67. package/hooks/blocking/verified_commit_message_accuracy_blocker.py +167 -167
  68. package/hooks/blocking/verifier_verdict_minter.py +280 -280
  69. package/hooks/git-hooks/test_pre_push.py +25 -0
  70. package/hooks/hooks.json +10 -0
  71. package/hooks/hooks_constants/CLAUDE.md +2 -1
  72. package/hooks/hooks_constants/enter_worktree_prefetch_constants.py +18 -18
  73. package/hooks/hooks_constants/orchestrator_refresh_reschedule_gate_constants.py +48 -0
  74. package/hooks/hooks_constants/ruff_integration_constants.py +16 -0
  75. package/hooks/lifecycle/enter_worktree_origin_prefetch.py +163 -146
  76. package/hooks/lifecycle/test_enter_worktree_origin_prefetch.py +185 -178
  77. package/hooks/pyproject.toml +1 -0
  78. package/hooks/validators/CLAUDE.md +1 -0
  79. package/hooks/validators/config/__init__.py +0 -0
  80. package/hooks/validators/config/directory_exemption_constants.py +183 -0
  81. package/hooks/validators/config/test_directory_exemption_constants.py +21 -0
  82. package/hooks/validators/conftest.py +4 -0
  83. package/hooks/validators/ruff_integration.py +49 -5
  84. package/hooks/validators/run_all_validators.py +206 -9
  85. package/hooks/validators/test_directory_exemption_constants.py +185 -0
  86. package/hooks/validators/test_python_antipattern_checks.py +110 -5
  87. package/hooks/validators/test_ruff_integration.py +92 -1
  88. package/hooks/validators/test_run_all_validators.py +115 -68
  89. package/hooks/validators/test_run_all_validators_pretooluse.py +159 -1
  90. package/package.json +10 -2
  91. package/rules/CLAUDE.md +1 -0
  92. package/rules/docstring-prose-matches-implementation.md +45 -44
  93. package/rules/state-what-is.md +25 -0
  94. package/rules/verified-commit-gate-skip.md +1 -1
  95. package/scripts/CLAUDE.md +1 -0
  96. package/scripts/Capture-PoolHealth.ps1 +410 -0
  97. package/scripts/_code_review_test_support.py +404 -0
  98. package/scripts/claude_chain_runner.py +141 -1
  99. package/scripts/conftest.py +16 -1
  100. package/scripts/dev_env_scripts_constants/CLAUDE.md +1 -1
  101. package/scripts/dev_env_scripts_constants/claude_chain_constants.py +9 -0
  102. package/scripts/resolve_worker_spawn.py +626 -626
  103. package/scripts/spawn_grok_batch.py +672 -672
  104. package/scripts/test_claude_chain_runner.py +131 -0
  105. package/scripts/test_invoke_code_review_chain.py +70 -0
  106. package/scripts/test_invoke_code_review_cli.py +192 -0
  107. package/scripts/test_invoke_code_review_contract.py +256 -0
  108. package/scripts/test_invoke_code_review_git.py +123 -0
  109. package/scripts/test_invoke_code_review_mode.py +99 -0
  110. package/scripts/test_resolve_worker_spawn.py +1014 -1014
  111. package/skills/CLAUDE.md +2 -0
  112. package/skills/auditing-claude-config/SKILL.md +114 -114
  113. package/skills/autoconverge/SKILL.md +427 -427
  114. package/skills/autoconverge/reference/convergence.md +24 -3
  115. package/skills/autoconverge/workflow/CLAUDE.md +1 -0
  116. package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +3 -3
  117. package/skills/autoconverge/workflow/converge.contract.test.mjs +1263 -1263
  118. package/skills/autoconverge/workflow/converge.mjs +167 -0
  119. package/skills/autoconverge/workflow/converge.p2-advance.test.mjs +202 -0
  120. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a11d903476b803493.jsonl +2 -2
  121. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a26213978adeef6fb.jsonl +2 -2
  122. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a3def0d15ed9d9110.jsonl +2 -2
  123. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a41f41b1b708ee3b7.jsonl +2 -2
  124. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a758b880abecc3ff7.jsonl +2 -2
  125. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a8897b89656b1bd16.jsonl +2 -2
  126. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-abd463d744a1437bc.jsonl +2 -2
  127. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ad19d027ae8ee1816.jsonl +2 -2
  128. package/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +265 -265
  129. package/skills/closeout/SKILL.md +33 -50
  130. package/skills/codex-review/scripts/codex_review_scripts_constants/run_constants.py +8 -0
  131. package/skills/codex-review/scripts/run_codex_review.py +233 -1
  132. package/skills/codex-review/scripts/test_run_codex_review.py +189 -0
  133. package/skills/condensing-instructions/SKILL.md +81 -0
  134. package/skills/copilot-review/SKILL.md +119 -119
  135. package/skills/e-code-review/SKILL.md +52 -0
  136. package/skills/e-code-review/reference/fix.md +54 -0
  137. package/skills/e-code-review/reference/loop.md +43 -0
  138. package/skills/e-code-review/reference/low.md +57 -0
  139. package/skills/e-code-review/reference/medium.md +153 -0
  140. package/skills/e-code-review/reference/xhigh.md +182 -0
  141. package/skills/e-simplify/SKILL.md +97 -0
  142. package/skills/issue-tracker/SKILL.md +92 -0
  143. package/skills/issue-tracker/reference/epic-and-sub-issue-model.md +55 -0
  144. package/skills/issue-tracker/reference/handoff-schema.md +64 -0
  145. package/skills/issue-tracker/reference/operation-matrix.md +41 -0
  146. package/skills/orchestrator/SKILL.md +162 -21
  147. package/skills/orchestrator/scripts/status_gate.py +625 -0
  148. package/skills/orchestrator/scripts/status_gate_constants/__init__.py +1 -0
  149. package/skills/orchestrator/scripts/status_gate_constants/config/__init__.py +1 -0
  150. package/skills/orchestrator/scripts/status_gate_constants/config/constants.py +47 -0
  151. package/skills/orchestrator/scripts/test_status_gate.py +439 -0
  152. package/skills/orchestrator-refresh/SKILL.md +110 -35
  153. package/skills/plan-to-pr/SKILL.md +155 -0
  154. package/skills/plan-to-pr/reference/final-validation-tasks.md +15 -0
  155. package/skills/plan-to-pr/reference/model-routing.md +36 -0
  156. package/skills/plan-to-pr/reference/packet-contract.md +43 -0
  157. package/skills/plan-to-pr/reference/packet-schema.json +57 -0
  158. package/skills/plan-to-pr/reference/process-inventory.md +22 -0
  159. package/skills/plan-to-pr/reference/review-loop.md +33 -0
  160. package/skills/plan-to-pr/reference/run-record.schema.json +27 -0
  161. package/skills/plan-to-pr/reference/self-audit-tasks.md +15 -0
  162. package/skills/plan-to-pr/reference/task-seeds.md +14 -0
  163. package/skills/plan-to-pr/reference/task-ticket.md +38 -0
  164. package/skills/plan-to-pr/scripts/config/__init__.py +1 -0
  165. package/skills/plan-to-pr/scripts/config/constants.py +193 -0
  166. package/skills/plan-to-pr/scripts/create_packet.py +173 -0
  167. package/skills/plan-to-pr/scripts/test_create_packet.py +102 -0
  168. package/skills/plan-to-pr/scripts/test_validate_packet.py +256 -0
  169. package/skills/plan-to-pr/scripts/test_validate_protocol.py +135 -0
  170. package/skills/plan-to-pr/scripts/test_validate_run.py +158 -0
  171. package/skills/plan-to-pr/scripts/validate_packet.py +655 -0
  172. package/skills/plan-to-pr/scripts/validate_protocol.py +622 -0
  173. package/skills/plan-to-pr/scripts/validate_run.py +173 -0
  174. package/skills/plan-to-pr/test_skill_contract.py +207 -0
  175. package/skills/plan-to-pr/test_task_ticket_contract.py +151 -0
  176. package/skills/pr-converge/SKILL.md +472 -469
  177. package/skills/pr-converge/reference/examples.md +3 -3
  178. package/skills/pr-converge/reference/fix-protocol.md +1 -1
  179. package/skills/pr-converge/reference/ground-rules.md +7 -4
  180. package/skills/pr-converge/reference/multi-pr-orchestration.md +4 -1
  181. package/skills/pr-converge/reference/per-tick.md +5 -5
  182. package/skills/pr-converge/reference/progress-checklist.md +1 -1
  183. package/skills/pr-converge/scripts/check_convergence_gates.py +279 -279
  184. package/skills/pr-converge/scripts/test_check_convergence_codex.py +507 -507
  185. package/skills/pr-converge/scripts/test_check_convergence_gates.py +84 -84
  186. package/skills/pr-converge/test_step5_host_branch.py +1 -1
  187. package/skills/pr-fix-protocol/SKILL.md +1 -1
  188. package/skills/privacy-hygiene/SKILL.md +68 -68
  189. package/skills/prototype/workflows/promotion.md +1 -1
  190. package/skills/release-notes-html/SKILL.md +164 -0
  191. package/skills/task-build/CLAUDE.md +8 -7
  192. package/skills/task-build/SKILL.md +16 -8
  193. package/skills/task-build/reference/tool-routing.md +19 -0
  194. package/scripts/test_invoke_code_review.py +0 -966
  195. package/skills/closeout/reference/issue-body-templates.md +0 -108
@@ -0,0 +1,173 @@
1
+ """Validate a complete task-record set against an explicit Git commit set."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import subprocess
7
+ import sys
8
+ from collections.abc import Mapping, Sequence
9
+ from pathlib import Path
10
+
11
+ from config.constants import (
12
+ COMMIT_RANGE_ARGUMENT_INDEX,
13
+ COMMIT_RANGE_SEPARATOR,
14
+ EXIT_CODE_INVALID_SET,
15
+ EXIT_CODE_VALID_SET,
16
+ JSON_ENCODING,
17
+ RUN_ARGUMENT_COUNT_REQUIRED,
18
+ RUN_ARGUMENT_COUNT_WITH_WORKTREE,
19
+ SET_VALIDATION_PASSED,
20
+ OPTION_ARGUMENT_INDEX,
21
+ WORKTREE_OPTION_ARGUMENT_INDEX,
22
+ WORKTREE_PATH_ARGUMENT_INDEX,
23
+ WORKTREE_OPTION,
24
+ )
25
+ from validate_protocol import ProtocolValidationError, validate_record
26
+
27
+
28
+ def _load_record_list(record_path: Path) -> list[dict[str, object]]:
29
+ try:
30
+ parsed_records = json.loads(record_path.read_text(encoding=JSON_ENCODING))
31
+ except (OSError, UnicodeDecodeError, json.JSONDecodeError) as error:
32
+ raise ProtocolValidationError("task-record set is not valid JSON") from error
33
+ if not isinstance(parsed_records, list) or any(
34
+ not isinstance(each_record, dict) for each_record in parsed_records
35
+ ):
36
+ raise ProtocolValidationError("task-record set must be a JSON list of objects")
37
+ return parsed_records
38
+
39
+
40
+ def _git_commits(worktree: Path, commit_range: str) -> set[str]:
41
+ try:
42
+ completed = subprocess.run(
43
+ ["git", "rev-list", "--reverse", commit_range],
44
+ cwd=worktree,
45
+ check=True,
46
+ capture_output=True,
47
+ text=True,
48
+ )
49
+ except (OSError, subprocess.CalledProcessError) as error:
50
+ raise ProtocolValidationError("cannot read the requested commit set") from error
51
+ return {each_commit for each_commit in completed.stdout.splitlines() if each_commit}
52
+
53
+
54
+ def validate_run(
55
+ all_task_records: Sequence[Mapping[str, object]],
56
+ all_commit_hashes: set[str],
57
+ schema_path: Path,
58
+ all_explicit_mapping: Mapping[str, str],
59
+ ) -> None:
60
+ """Validate task identity uniqueness and one-to-one commit coverage.
61
+
62
+ Args:
63
+ all_task_records: Task records to validate.
64
+ all_commit_hashes: Commits that the record set must cover.
65
+ schema_path: Path to the task-record schema JSON file.
66
+ all_explicit_mapping: Task-to-commit mapping; an empty mapping means
67
+ records must match commits by count and commit field.
68
+
69
+ Returns:
70
+ None.
71
+
72
+ Raises:
73
+ ProtocolValidationError: If task identity, commit coverage, or record
74
+ schema validation fails.
75
+ """
76
+ task_ids = [
77
+ each_task_id
78
+ for each_record in all_task_records
79
+ if isinstance((each_task_id := each_record.get("task_identity")), str)
80
+ ]
81
+ commits = [
82
+ each_commit
83
+ for each_record in all_task_records
84
+ if isinstance((each_commit := each_record.get("commit")), str)
85
+ ]
86
+ if len(task_ids) != len(all_task_records):
87
+ raise ProtocolValidationError("every task record needs a task_identity")
88
+ if len(task_ids) != len(set(task_ids)):
89
+ raise ProtocolValidationError("task identities must be unique")
90
+ if len(commits) != len(all_task_records):
91
+ raise ProtocolValidationError("every task record needs a commit")
92
+ if not all_explicit_mapping and len(all_task_records) != len(all_commit_hashes):
93
+ raise ProtocolValidationError("task and commit counts differ without an explicit mapping")
94
+ if all_explicit_mapping and len(all_explicit_mapping) != len(set(all_explicit_mapping.values())):
95
+ raise ProtocolValidationError("explicit task mappings must reference unique commits")
96
+ if all_explicit_mapping and set(all_explicit_mapping) != set(task_ids):
97
+ raise ProtocolValidationError("explicit task mappings must reference every task identity")
98
+ if all_explicit_mapping and any(
99
+ all_explicit_mapping[each_task_id] != each_commit
100
+ for each_task_id, each_commit in zip(task_ids, commits)
101
+ ):
102
+ raise ProtocolValidationError("explicit task mappings must match record commits")
103
+ mapped_commits = set(all_explicit_mapping.values()) if all_explicit_mapping else set(commits)
104
+ if mapped_commits != all_commit_hashes:
105
+ raise ProtocolValidationError("task records must reference every and only requested commit")
106
+ for each_record in all_task_records:
107
+ validate_record(each_record, schema_path)
108
+
109
+
110
+ def main(all_cli_arguments: Sequence[str]) -> int:
111
+ """Validate a record list against a requested commit set.
112
+
113
+ Args:
114
+ all_cli_arguments: Program name followed by record and commit-set
115
+ arguments, with an optional worktree path.
116
+
117
+ Returns:
118
+ Exit code `0` for a valid set or `2` for invalid input.
119
+
120
+ Raises:
121
+ No exceptions: validation failures are reported and converted to an
122
+ invalid-set exit code.
123
+ """
124
+ if len(all_cli_arguments) not in {
125
+ RUN_ARGUMENT_COUNT_REQUIRED,
126
+ RUN_ARGUMENT_COUNT_WITH_WORKTREE,
127
+ RUN_ARGUMENT_COUNT_WITH_WORKTREE + 1,
128
+ }:
129
+ print("usage: validate_run.py <records.json> --base-head <BASE..HEAD> [--worktree PATH]", file=sys.stderr)
130
+ return EXIT_CODE_INVALID_SET
131
+ record_path = Path(all_cli_arguments[1])
132
+ option = all_cli_arguments[OPTION_ARGUMENT_INDEX]
133
+ commit_range = all_cli_arguments[COMMIT_RANGE_ARGUMENT_INDEX]
134
+ has_legacy_worktree_argument = len(all_cli_arguments) == RUN_ARGUMENT_COUNT_WITH_WORKTREE
135
+ has_flagged_worktree_argument = len(all_cli_arguments) == RUN_ARGUMENT_COUNT_WITH_WORKTREE + 1
136
+ if has_flagged_worktree_argument and all_cli_arguments[WORKTREE_OPTION_ARGUMENT_INDEX] != WORKTREE_OPTION:
137
+ print("usage: validate_run.py <records.json> --base-head <BASE..HEAD> [--worktree PATH]", file=sys.stderr)
138
+ return EXIT_CODE_INVALID_SET
139
+ worktree = Path.cwd()
140
+ if has_legacy_worktree_argument:
141
+ worktree = Path(all_cli_arguments[WORKTREE_OPTION_ARGUMENT_INDEX])
142
+ if has_flagged_worktree_argument:
143
+ worktree = Path(all_cli_arguments[WORKTREE_PATH_ARGUMENT_INDEX])
144
+ try:
145
+ all_task_records = _load_record_list(record_path)
146
+ if has_legacy_worktree_argument or has_flagged_worktree_argument:
147
+ for each_record in all_task_records:
148
+ each_record["worktree"] = str(worktree)
149
+ if option == "--base-head":
150
+ if COMMIT_RANGE_SEPARATOR not in commit_range:
151
+ raise ProtocolValidationError("base-head must be BASE..HEAD")
152
+ all_commit_hashes = _git_commits(worktree, commit_range)
153
+ elif option == "--commits":
154
+ all_commit_hashes = {
155
+ each_commit for each_commit in commit_range.split(",") if each_commit
156
+ }
157
+ else:
158
+ raise ProtocolValidationError("unknown commit-set option")
159
+ validate_run(
160
+ all_task_records,
161
+ all_commit_hashes,
162
+ Path(__file__).parent.parent / "reference" / "run-record.schema.json",
163
+ {},
164
+ )
165
+ except ProtocolValidationError as error:
166
+ print(f"run validation failed: {error}", file=sys.stderr)
167
+ return EXIT_CODE_INVALID_SET
168
+ print(SET_VALIDATION_PASSED)
169
+ return EXIT_CODE_VALID_SET
170
+
171
+
172
+ if __name__ == "__main__":
173
+ raise SystemExit(main(sys.argv))
@@ -0,0 +1,207 @@
1
+ import re
2
+ from pathlib import Path
3
+
4
+
5
+ SKILL_DIRECTORY = Path(__file__).parent
6
+ SKILL_PATH = SKILL_DIRECTORY / "SKILL.md"
7
+ MODEL_ROUTING_PATH = SKILL_DIRECTORY / "reference" / "model-routing.md"
8
+ TASK_TICKET_PATH = SKILL_DIRECTORY / "reference" / "task-ticket.md"
9
+ PACKET_CONTRACT_PATH = SKILL_DIRECTORY / "reference" / "packet-contract.md"
10
+ PACKET_SCHEMA_PATH = SKILL_DIRECTORY / "reference" / "packet-schema.json"
11
+ VALIDATION_SCRIPT_PATH = SKILL_DIRECTORY / "scripts" / "validate_protocol.py"
12
+ RUN_VALIDATION_SCRIPT_PATH = SKILL_DIRECTORY / "scripts" / "validate_run.py"
13
+ PACKET_CREATOR_PATH = SKILL_DIRECTORY / "scripts" / "create_packet.py"
14
+ PACKET_VALIDATOR_PATH = SKILL_DIRECTORY / "scripts" / "validate_packet.py"
15
+ FORBIDDEN_ROUTE_TEXT = ("external planning provider", "Workflow dependency")
16
+ EXPECTED_TASK_PROTOCOL_HEADING = "## One-task and one-commit protocol"
17
+
18
+
19
+ def read_skill_text() -> str:
20
+ return SKILL_PATH.read_text(encoding="utf-8")
21
+
22
+
23
+ def read_reference_texts() -> tuple[str, str, str]:
24
+ return (
25
+ MODEL_ROUTING_PATH.read_text(encoding="utf-8"),
26
+ TASK_TICKET_PATH.read_text(encoding="utf-8"),
27
+ PACKET_CONTRACT_PATH.read_text(encoding="utf-8"),
28
+ )
29
+
30
+
31
+ def test_skill_frontmatter_defines_third_person_trigger_catalog() -> None:
32
+ skill_text = read_skill_text()
33
+
34
+ assert skill_text.startswith("---\nname: plan-to-pr\n")
35
+ description_text = skill_text.split("description:", 1)[1].split("---", 1)[0]
36
+ assert "Triggers:" in description_text
37
+ assert "Coordinates" in description_text
38
+ assert not re.search(r"\b(I|[Ww]e|[Oo]ur|[Mm]y)\b", description_text)
39
+
40
+
41
+ def test_skill_contract_names_capability_boundary_and_refusals() -> None:
42
+ skill_text = read_skill_text()
43
+
44
+ assert "## Capability boundary" in skill_text
45
+ assert "## Refusal cases" in skill_text
46
+ assert "one deliverable" in skill_text
47
+ assert "one allowed file set" in skill_text
48
+ assert "one acceptance check" in skill_text
49
+ assert "one commit" in skill_text
50
+ assert "fail closed" in skill_text
51
+ assert "Plan-to-PR blocked: <missing input or capability>." in skill_text
52
+
53
+
54
+ def test_skill_contract_requires_native_packet_planning_before_task_seeding() -> None:
55
+ skill_text = read_skill_text()
56
+
57
+ assert "## Native planning phase" in skill_text
58
+ assert "before task seeding and before implementation" in skill_text
59
+ assert "Luna max planner" in skill_text
60
+ assert "Sol xhigh advisor" in skill_text
61
+ assert "native planning packet" in skill_text
62
+ assert (
63
+ "Only a passing packet with `status: approved` may seed host tasks"
64
+ in skill_text
65
+ )
66
+ assert skill_text.index("## Native planning phase") < skill_text.index(
67
+ "## Runtime and task seeding"
68
+ )
69
+ assert "anthropic-plan" not in skill_text
70
+ assert "Workflow" not in skill_text
71
+
72
+
73
+ def test_skill_contract_pins_model_roles_and_worker_routing() -> None:
74
+ skill_text = read_skill_text()
75
+
76
+ assert "planner and final validator use Luna max" in skill_text
77
+ assert "orchestrator uses the max route" in skill_text
78
+ assert "Sol xhigh advisor" in skill_text
79
+ assert "Sol xhigh advisor heavily at scope" in skill_text
80
+ assert (
81
+ "Every implementation,\nreview, and repair worker uses fast, low-effort Luna"
82
+ in skill_text
83
+ )
84
+ assert "separate fast low-effort Luna review worker" in skill_text
85
+ assert "separate fast low-effort Luna repair worker" in skill_text
86
+ assert "Unavailable models or routing tools\nfail closed" in skill_text
87
+
88
+
89
+ def test_skill_contract_enforces_task_commit_and_review_order() -> None:
90
+ skill_text = read_skill_text()
91
+
92
+ assert EXPECTED_TASK_PROTOCOL_HEADING in skill_text
93
+ assert "fresh verification and `verified_commit_gate`" in skill_text
94
+ assert (
95
+ "native\nfindings-only correctness review at `/e-code-review low`" in skill_text
96
+ )
97
+ assert "findings-only" in skill_text
98
+ assert "has no repair" in skill_text
99
+ assert "separate fast low-effort Luna repair worker" in skill_text
100
+ assert "Rerun the task acceptance check and fresh" in skill_text
101
+ assert "exact-surface verification" in skill_text
102
+ assert "amend the task commit" in skill_text.lower()
103
+ assert (
104
+ "Record resolved model, effort, command, findings, repair status, and"
105
+ in skill_text
106
+ )
107
+ assert "surface" in skill_text
108
+ assert "repeat native review" in skill_text
109
+ assert "until clean" in skill_text
110
+ assert "maps every commit to one packet task" in skill_text
111
+
112
+
113
+ def test_skill_contract_uses_only_the_native_review_route() -> None:
114
+ all_contract_text = "\n".join((read_skill_text(), *read_reference_texts()))
115
+
116
+ assert "/e-code-review low" in all_contract_text
117
+ assert "findings only" in all_contract_text
118
+ assert "no repair flag" in all_contract_text
119
+ assert "separate fast low-effort Luna" in all_contract_text
120
+ assert "fresh exact-surface verification" in all_contract_text
121
+ assert "/e-simplify" in all_contract_text
122
+ assert "cleanup-only" in all_contract_text
123
+ assert "cleanup-only" in all_contract_text
124
+ assert "skill-builder" not in all_contract_text
125
+ for each_forbidden_route_text in FORBIDDEN_ROUTE_TEXT:
126
+ assert each_forbidden_route_text not in all_contract_text
127
+
128
+
129
+ def test_skill_contract_records_review_fields_in_both_references() -> None:
130
+ model_routing_text, task_ticket_text, packet_contract_text = read_reference_texts()
131
+ review_fields = (
132
+ "resolved model",
133
+ "effort",
134
+ "command",
135
+ "findings",
136
+ "repair status",
137
+ "surface hash",
138
+ )
139
+
140
+ for each_review_field in review_fields:
141
+ assert each_review_field in model_routing_text
142
+ assert each_review_field in task_ticket_text
143
+ assert "packet.json" in packet_contract_text
144
+
145
+
146
+ def test_skill_contract_references_future_fixed_artifacts_without_copying_tables() -> (
147
+ None
148
+ ):
149
+ skill_text = read_skill_text()
150
+
151
+ assert "reference/model-routing.md" in skill_text
152
+ assert "reference/task-ticket.md" in skill_text
153
+ assert "reference/packet-contract.md" in skill_text
154
+ assert "reference/packet-schema.json" in skill_text
155
+ assert "scripts/validate_protocol.py" in skill_text
156
+ assert "fixed routing and gate matrix lives only" not in skill_text
157
+ assert "fixed fields live in" in skill_text
158
+ assert (
159
+ "[`reference/run-record.schema.json`](reference/run-record.schema.json)"
160
+ in skill_text
161
+ )
162
+
163
+
164
+ def test_skill_contract_companion_reference_paths_exist() -> None:
165
+ skill_text = read_skill_text()
166
+ local_reference_paths = re.findall(r"\]\(([^)]+)\)", skill_text)
167
+ repository_paths = [
168
+ each_path
169
+ for each_path in local_reference_paths
170
+ if not each_path.startswith(("http://", "https://", "#"))
171
+ ]
172
+
173
+ assert repository_paths
174
+ for each_path in repository_paths:
175
+ assert (SKILL_DIRECTORY / each_path).exists(), each_path
176
+
177
+
178
+ def test_skill_contract_requires_self_audit_and_publication_gates() -> None:
179
+ skill_text = read_skill_text()
180
+
181
+ assert "Run the workflow self-audit and retain its evidence." in skill_text
182
+ assert "unresolved finding blocks\npublication." in skill_text
183
+ assert "Publish only when final validation\nand self-audit pass." in skill_text
184
+ assert "Luna max `/e-simplify`" in skill_text
185
+ assert "Luna low `/e-code-review max loop`" in skill_text
186
+ assert "no repair flag" in skill_text
187
+
188
+
189
+ def test_skill_contract_requires_packet_companions_and_validation_scripts() -> None:
190
+ skill_text = read_skill_text()
191
+
192
+ for each_path in (
193
+ PACKET_CONTRACT_PATH,
194
+ PACKET_SCHEMA_PATH,
195
+ PACKET_CREATOR_PATH,
196
+ PACKET_VALIDATOR_PATH,
197
+ VALIDATION_SCRIPT_PATH,
198
+ RUN_VALIDATION_SCRIPT_PATH,
199
+ ):
200
+ assert each_path.exists(), each_path
201
+
202
+ assert "reference/packet-contract.md" in skill_text
203
+ assert "reference/packet-schema.json" in skill_text
204
+ assert "scripts/create_packet.py" in skill_text
205
+ assert "scripts/validate_packet.py" in skill_text
206
+ assert "scripts/validate_protocol.py" in skill_text
207
+ assert "scripts/validate_run.py" in skill_text
@@ -0,0 +1,151 @@
1
+ import json
2
+ import re
3
+ from pathlib import Path
4
+
5
+
6
+ SKILL_DIRECTORY = Path(__file__).parent
7
+ REFERENCE_DIRECTORY = SKILL_DIRECTORY / "reference"
8
+ SKILL_PATH = SKILL_DIRECTORY / "SKILL.md"
9
+ SCHEMA_PATH = REFERENCE_DIRECTORY / "run-record.schema.json"
10
+ REFERENCE_NAMES = (
11
+ "packet-contract.md",
12
+ "review-loop.md",
13
+ "task-seeds.md",
14
+ "final-validation-tasks.md",
15
+ "process-inventory.md",
16
+ "self-audit-tasks.md",
17
+ )
18
+ FORBIDDEN_ROUTE_TEXT = (
19
+ "external planning provider",
20
+ "Workflow dependency",
21
+ "skill-builder",
22
+ "--fix",
23
+ "C:\\Users\\",
24
+ )
25
+
26
+
27
+ def read_reference_texts() -> str:
28
+ return "\n".join(
29
+ [SKILL_PATH.read_text(encoding="utf-8")]
30
+ + [
31
+ (REFERENCE_DIRECTORY / each_name).read_text(encoding="utf-8")
32
+ for each_name in REFERENCE_NAMES
33
+ ]
34
+ )
35
+
36
+
37
+ def test_references_exist_and_are_linked_directly() -> None:
38
+ skill_text = SKILL_PATH.read_text(encoding="utf-8")
39
+
40
+ for each_name in REFERENCE_NAMES:
41
+ assert (REFERENCE_DIRECTORY / each_name).exists()
42
+ assert f"reference/{each_name}" in skill_text
43
+
44
+ assert (REFERENCE_DIRECTORY / "packet-schema.json").exists()
45
+ assert "reference/packet-schema.json" in skill_text
46
+ assert (SKILL_DIRECTORY / "scripts" / "validate_protocol.py").exists()
47
+ assert (SKILL_DIRECTORY / "scripts" / "validate_run.py").exists()
48
+
49
+
50
+ def test_packet_schema_requires_native_planning_fields() -> None:
51
+ packet_schema = json.loads(
52
+ (REFERENCE_DIRECTORY / "packet-schema.json").read_text(encoding="utf-8")
53
+ )
54
+
55
+ assert {
56
+ "schema_version",
57
+ "slug",
58
+ "status",
59
+ "request",
60
+ "allowed_files",
61
+ "sources",
62
+ "decisions",
63
+ "open_questions",
64
+ "tasks",
65
+ "validation",
66
+ } <= set(packet_schema["required"])
67
+ assert packet_schema["properties"]["status"]["enum"] == ["draft", "approved"]
68
+ assert packet_schema["$defs"]["validation"]["properties"]["validated_by"] == {
69
+ "const": "native-plan-to-pr"
70
+ }
71
+
72
+
73
+ def test_run_record_schema_requires_task_and_review_records() -> None:
74
+ schema = json.loads(SCHEMA_PATH.read_text(encoding="utf-8"))
75
+ required_fields = set(schema["required"])
76
+
77
+ assert {
78
+ "task_identity",
79
+ "commit",
80
+ "review_record",
81
+ "repair_record",
82
+ "reverification_record",
83
+ "verification_record",
84
+ } <= required_fields
85
+ review_fields = set(schema["$defs"]["review_record"]["required"])
86
+ assert {"findings_only", "has_repair_flag", "command"} <= review_fields
87
+ assert schema["$defs"]["review_record"]["properties"]["command"] == {
88
+ "type": "string",
89
+ "minLength": 1,
90
+ }
91
+
92
+
93
+ def test_review_loop_requires_separate_native_review_and_repair() -> None:
94
+ contract_text = read_reference_texts()
95
+
96
+ assert "separate fast low-effort Luna review worker" in contract_text
97
+ assert "native findings-only" in contract_text
98
+ assert "/e-code-review low" in contract_text
99
+ assert "correctness" in contract_text
100
+ assert "/e-code-review low" in contract_text
101
+ assert "has no repair flag" in contract_text
102
+ assert "separate fast low-effort Luna repair worker" in contract_text
103
+ assert "confirmed findings" in contract_text
104
+ assert "Amend the task commit" in contract_text
105
+ assert "repeat the native review until clean" in contract_text
106
+ assert "packet is complete before `TaskCreate` or `TodoWrite` runs" in contract_text
107
+
108
+
109
+ def test_post_pr_cleanup_and_max_review_are_distinct() -> None:
110
+ contract_text = read_reference_texts()
111
+
112
+ assert "Luna max `/e-simplify`" in contract_text
113
+ assert "cleanup-only" in contract_text
114
+ assert "Luna low `/e-code-review max loop`" in contract_text
115
+ assert "separate Luna low repair worker" in contract_text
116
+ assert "commits, and pushes" in contract_text
117
+ assert "clean" in contract_text
118
+ assert "skill-builder" not in contract_text
119
+
120
+
121
+ def test_packet_planning_precedes_task_seeding_and_has_no_external_dependency() -> None:
122
+ contract_text = read_reference_texts()
123
+
124
+ assert "before any `TaskCreate` or `TodoWrite` seeding" in contract_text
125
+ assert (
126
+ "Only a passing packet with `status: approved` may seed host tasks"
127
+ in contract_text
128
+ )
129
+ assert "external planning provider" not in contract_text
130
+ assert "Workflow dependency" not in contract_text
131
+
132
+
133
+ def test_task_seeding_and_audit_inventories_are_present() -> None:
134
+ contract_text = read_reference_texts()
135
+
136
+ assert "TaskCreate" in contract_text
137
+ assert "TodoWrite" in contract_text
138
+ assert "deterministic" in contract_text
139
+ assert "judgment" in contract_text
140
+ assert "borderline" in contract_text
141
+ assert "final-validation" in contract_text
142
+ assert "self-audit" in contract_text
143
+ assert re.search(r"\n1\. .*\n2\. .*\n3\. ", contract_text)
144
+
145
+
146
+ def test_contract_avoids_forbidden_route_strings_and_absolute_paths() -> None:
147
+ contract_text = read_reference_texts()
148
+
149
+ for each_forbidden_route_text in FORBIDDEN_ROUTE_TEXT:
150
+ assert each_forbidden_route_text not in contract_text
151
+ assert not re.search(r"[A-Za-z]:[/\\]", contract_text)