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.
- package/CLAUDE.md +53 -49
- package/_shared/pr-loop/scripts/_claude_permissions_common.py +84 -0
- package/_shared/pr-loop/scripts/code_rules_gate.py +4 -2
- package/_shared/pr-loop/scripts/grant_project_claude_permissions.py +306 -306
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/claude_permissions_constants.py +44 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/copilot_quota_constants.py +24 -24
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/stale_worktree_rule_sweep_constants.py +107 -107
- package/_shared/pr-loop/scripts/revoke_project_claude_permissions.py +290 -48
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +42 -2
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_constants.py +36 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +100 -1
- package/_shared/pr-loop/scripts/tests/test_fix_hookspath.py +497 -497
- package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +311 -2
- package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep.py +301 -301
- package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep_constants.py +85 -85
- package/_shared/pr-loop/worker-spawn.md +1 -1
- package/agents/CLAUDE.md +2 -1
- package/agents/caveman.md +0 -1
- package/agents/clasp-deployment-orchestrator.md +0 -1
- package/agents/clean-coder.md +0 -1
- package/agents/code-advisor.md +0 -1
- package/agents/code-quality-agent.md +1 -2
- package/agents/code-verifier.md +0 -1
- package/agents/deep-research.md +0 -1
- package/agents/docs-agent.md +0 -1
- package/agents/git-commit-crafter.md +0 -1
- package/agents/issue-tracker.md +42 -0
- package/agents/plan-packet-validator.md +0 -1
- package/agents/pr-description-writer.md +0 -1
- package/agents/test_agent_frontmatter.py +67 -18
- package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +143 -141
- package/bin/CLAUDE.md +68 -5
- package/bin/ever-shipped-skills.mjs +1 -0
- package/bin/install-constants.mjs +88 -0
- package/bin/install.mjs +1138 -114
- package/bin/install.prune.test.mjs +869 -19
- package/bin/install.test.mjs +906 -2
- package/commands/implement.md +1 -1
- package/commands/right-size.md +1 -1
- package/docs/CLAUDE.md +1 -0
- package/docs/host-pool-health-monitor.md +102 -0
- package/docs/references/CLAUDE.md +4 -2
- package/docs/references/advisor-tool.md +13 -0
- package/docs/references/code-review-enforcement.md +10 -0
- package/docs/references/team-advisor-skill.md +14 -0
- package/hooks/blocking/CLAUDE.md +1 -0
- package/hooks/blocking/code_review_pr_create_gate.py +7 -3
- package/hooks/blocking/code_review_push_gate.py +9 -4
- package/hooks/blocking/code_review_stamp_directory_write_blocker.py +8 -0
- package/hooks/blocking/config/__init__.py +5 -5
- package/hooks/blocking/config/code_review_enforcement_constants.py +4 -1
- package/hooks/blocking/config/test_code_review_enforcement_constants.py +5 -0
- package/hooks/blocking/config/verified_commit_constants.py +160 -159
- package/hooks/blocking/orchestrator_refresh_reschedule_gate.py +256 -0
- package/hooks/blocking/pre_tool_use_dispatcher.py +24 -24
- package/hooks/blocking/test_code_review_pr_create_gate.py +14 -0
- package/hooks/blocking/test_code_review_push_gate.py +16 -0
- package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +19 -0
- package/hooks/blocking/test_orchestrator_refresh_reschedule_gate.py +231 -0
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +10 -1
- package/hooks/blocking/test_verdict_directory_write_blocker.py +808 -808
- package/hooks/blocking/test_verification_verdict_store.py +54 -0
- package/hooks/blocking/test_verified_commit_gate.py +581 -581
- package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +131 -131
- package/hooks/blocking/verdict_directory_write_blocker.py +687 -687
- package/hooks/blocking/verification_verdict_store.py +1039 -1036
- package/hooks/blocking/verified_commit_message_accuracy_blocker.py +167 -167
- package/hooks/blocking/verifier_verdict_minter.py +280 -280
- package/hooks/git-hooks/test_pre_push.py +25 -0
- package/hooks/hooks.json +10 -0
- package/hooks/hooks_constants/CLAUDE.md +2 -1
- package/hooks/hooks_constants/enter_worktree_prefetch_constants.py +18 -18
- package/hooks/hooks_constants/orchestrator_refresh_reschedule_gate_constants.py +48 -0
- package/hooks/hooks_constants/ruff_integration_constants.py +16 -0
- package/hooks/lifecycle/enter_worktree_origin_prefetch.py +163 -146
- package/hooks/lifecycle/test_enter_worktree_origin_prefetch.py +185 -178
- package/hooks/pyproject.toml +1 -0
- package/hooks/validators/CLAUDE.md +1 -0
- package/hooks/validators/config/__init__.py +0 -0
- package/hooks/validators/config/directory_exemption_constants.py +183 -0
- package/hooks/validators/config/test_directory_exemption_constants.py +21 -0
- package/hooks/validators/conftest.py +4 -0
- package/hooks/validators/ruff_integration.py +49 -5
- package/hooks/validators/run_all_validators.py +206 -9
- package/hooks/validators/test_directory_exemption_constants.py +185 -0
- package/hooks/validators/test_python_antipattern_checks.py +110 -5
- package/hooks/validators/test_ruff_integration.py +92 -1
- package/hooks/validators/test_run_all_validators.py +115 -68
- package/hooks/validators/test_run_all_validators_pretooluse.py +159 -1
- package/package.json +10 -2
- package/rules/CLAUDE.md +1 -0
- package/rules/docstring-prose-matches-implementation.md +45 -44
- package/rules/state-what-is.md +25 -0
- package/rules/verified-commit-gate-skip.md +1 -1
- package/scripts/CLAUDE.md +1 -0
- package/scripts/Capture-PoolHealth.ps1 +410 -0
- package/scripts/_code_review_test_support.py +404 -0
- package/scripts/claude_chain_runner.py +141 -1
- package/scripts/conftest.py +16 -1
- package/scripts/dev_env_scripts_constants/CLAUDE.md +1 -1
- package/scripts/dev_env_scripts_constants/claude_chain_constants.py +9 -0
- package/scripts/resolve_worker_spawn.py +626 -626
- package/scripts/spawn_grok_batch.py +672 -672
- package/scripts/test_claude_chain_runner.py +131 -0
- package/scripts/test_invoke_code_review_chain.py +70 -0
- package/scripts/test_invoke_code_review_cli.py +192 -0
- package/scripts/test_invoke_code_review_contract.py +256 -0
- package/scripts/test_invoke_code_review_git.py +123 -0
- package/scripts/test_invoke_code_review_mode.py +99 -0
- package/scripts/test_resolve_worker_spawn.py +1014 -1014
- package/skills/CLAUDE.md +2 -0
- package/skills/auditing-claude-config/SKILL.md +114 -114
- package/skills/autoconverge/SKILL.md +427 -427
- package/skills/autoconverge/reference/convergence.md +24 -3
- package/skills/autoconverge/workflow/CLAUDE.md +1 -0
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +3 -3
- package/skills/autoconverge/workflow/converge.contract.test.mjs +1263 -1263
- package/skills/autoconverge/workflow/converge.mjs +167 -0
- package/skills/autoconverge/workflow/converge.p2-advance.test.mjs +202 -0
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a11d903476b803493.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a26213978adeef6fb.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a3def0d15ed9d9110.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a41f41b1b708ee3b7.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a758b880abecc3ff7.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a8897b89656b1bd16.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-abd463d744a1437bc.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ad19d027ae8ee1816.jsonl +2 -2
- package/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +265 -265
- package/skills/closeout/SKILL.md +33 -50
- package/skills/codex-review/scripts/codex_review_scripts_constants/run_constants.py +8 -0
- package/skills/codex-review/scripts/run_codex_review.py +233 -1
- package/skills/codex-review/scripts/test_run_codex_review.py +189 -0
- package/skills/condensing-instructions/SKILL.md +81 -0
- package/skills/copilot-review/SKILL.md +119 -119
- package/skills/e-code-review/SKILL.md +52 -0
- package/skills/e-code-review/reference/fix.md +54 -0
- package/skills/e-code-review/reference/loop.md +43 -0
- package/skills/e-code-review/reference/low.md +57 -0
- package/skills/e-code-review/reference/medium.md +153 -0
- package/skills/e-code-review/reference/xhigh.md +182 -0
- package/skills/e-simplify/SKILL.md +97 -0
- package/skills/issue-tracker/SKILL.md +92 -0
- package/skills/issue-tracker/reference/epic-and-sub-issue-model.md +55 -0
- package/skills/issue-tracker/reference/handoff-schema.md +64 -0
- package/skills/issue-tracker/reference/operation-matrix.md +41 -0
- package/skills/orchestrator/SKILL.md +162 -21
- package/skills/orchestrator/scripts/status_gate.py +625 -0
- package/skills/orchestrator/scripts/status_gate_constants/__init__.py +1 -0
- package/skills/orchestrator/scripts/status_gate_constants/config/__init__.py +1 -0
- package/skills/orchestrator/scripts/status_gate_constants/config/constants.py +47 -0
- package/skills/orchestrator/scripts/test_status_gate.py +439 -0
- package/skills/orchestrator-refresh/SKILL.md +110 -35
- package/skills/plan-to-pr/SKILL.md +155 -0
- package/skills/plan-to-pr/reference/final-validation-tasks.md +15 -0
- package/skills/plan-to-pr/reference/model-routing.md +36 -0
- package/skills/plan-to-pr/reference/packet-contract.md +43 -0
- package/skills/plan-to-pr/reference/packet-schema.json +57 -0
- package/skills/plan-to-pr/reference/process-inventory.md +22 -0
- package/skills/plan-to-pr/reference/review-loop.md +33 -0
- package/skills/plan-to-pr/reference/run-record.schema.json +27 -0
- package/skills/plan-to-pr/reference/self-audit-tasks.md +15 -0
- package/skills/plan-to-pr/reference/task-seeds.md +14 -0
- package/skills/plan-to-pr/reference/task-ticket.md +38 -0
- package/skills/plan-to-pr/scripts/config/__init__.py +1 -0
- package/skills/plan-to-pr/scripts/config/constants.py +193 -0
- package/skills/plan-to-pr/scripts/create_packet.py +173 -0
- package/skills/plan-to-pr/scripts/test_create_packet.py +102 -0
- package/skills/plan-to-pr/scripts/test_validate_packet.py +256 -0
- package/skills/plan-to-pr/scripts/test_validate_protocol.py +135 -0
- package/skills/plan-to-pr/scripts/test_validate_run.py +158 -0
- package/skills/plan-to-pr/scripts/validate_packet.py +655 -0
- package/skills/plan-to-pr/scripts/validate_protocol.py +622 -0
- package/skills/plan-to-pr/scripts/validate_run.py +173 -0
- package/skills/plan-to-pr/test_skill_contract.py +207 -0
- package/skills/plan-to-pr/test_task_ticket_contract.py +151 -0
- package/skills/pr-converge/SKILL.md +472 -469
- package/skills/pr-converge/reference/examples.md +3 -3
- package/skills/pr-converge/reference/fix-protocol.md +1 -1
- package/skills/pr-converge/reference/ground-rules.md +7 -4
- package/skills/pr-converge/reference/multi-pr-orchestration.md +4 -1
- package/skills/pr-converge/reference/per-tick.md +5 -5
- package/skills/pr-converge/reference/progress-checklist.md +1 -1
- package/skills/pr-converge/scripts/check_convergence_gates.py +279 -279
- package/skills/pr-converge/scripts/test_check_convergence_codex.py +507 -507
- package/skills/pr-converge/scripts/test_check_convergence_gates.py +84 -84
- package/skills/pr-converge/test_step5_host_branch.py +1 -1
- package/skills/pr-fix-protocol/SKILL.md +1 -1
- package/skills/privacy-hygiene/SKILL.md +68 -68
- package/skills/prototype/workflows/promotion.md +1 -1
- package/skills/release-notes-html/SKILL.md +164 -0
- package/skills/task-build/CLAUDE.md +8 -7
- package/skills/task-build/SKILL.md +16 -8
- package/skills/task-build/reference/tool-routing.md +19 -0
- package/scripts/test_invoke_code_review.py +0 -966
- 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)
|