claude-dev-env 4.3.0 → 7.0.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/.agents/agents/pr-description-writer.md +2 -2
- package/.agents/agents/test_agent_frontmatter.py +2 -2
- package/.agents/agents/test_pr_description_writer_style.py +1 -3
- package/.agents/skills/e-code-review/SKILL.md +12 -12
- package/.agents/skills/e-code-review/preflight-proposal.contract.test.mjs +20 -0
- package/.agents/skills/e-code-review/reference/fix.md +15 -17
- package/.agents/skills/e-code-review/reference/loop.md +35 -39
- package/.agents/skills/e-code-review/reference/mode-contract.test.mjs +2 -1
- package/.agents/skills/e-code-review/reference/preflight-proposal.md +44 -9
- package/.agents/skills/e-code-review/reference/runner-selection.md +13 -14
- package/.agents/skills/issue-tracker/SKILL.md +5 -4
- package/.agents/skills/pull-request/SKILL.md +11 -13
- package/.agents/skills/pull-request/reference/publication-tasks.md +3 -3
- package/.agents/skills/pull-request/scripts/github_pr_command_constants/config/constants.py +9 -1
- package/.agents/skills/pull-request/scripts/pull_request.py +33 -44
- package/.agents/skills/pull-request/scripts/test_github_pr_command_constants.py +9 -0
- package/.agents/skills/pull-request/scripts/test_pull_request.py +1 -3
- package/.agents/skills/skill-builder/SKILL.md +3 -196
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +6 -0
- package/_shared/pr-loop/scripts/terminology_sweep.py +64 -19
- package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +59 -0
- package/bin/install.agents-home.test.mjs +15 -13
- package/bin/install.mjs +28 -0
- package/bin/install.policy-lint.test.mjs +78 -0
- package/bin/install.prune.test.mjs +59 -20
- package/bin/install.pstack-plugin.test.mjs +114 -0
- package/bin/install.test.mjs +37 -0
- package/bin/refresh-pstack-plugin-skills.test.mjs +211 -0
- package/hooks/advisory/pr_done_reminder.py +342 -0
- package/hooks/advisory/test_pr_done_reminder.py +249 -0
- package/hooks/blocking/bash_post_call_dispatcher.py +53 -6
- package/hooks/blocking/code_rules_paired_test.py +22 -15
- package/hooks/blocking/test_bash_post_call_dispatcher.py +65 -4
- package/hooks/blocking/test_code_rules_enforcer_paired_test.py +36 -29
- package/hooks/blocking/test_code_rules_js_resume_task_enumeration.py +4 -4
- package/hooks/blocking/test_code_rules_js_returns_object_schemaless.py +5 -5
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +68 -133
- package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +20 -51
- package/hooks/blocking/test_precommit_code_rules_gate_native_owner.py +18 -18
- package/hooks/git-hooks/git_hooks_constants/staged_policy_lint.py +12 -0
- package/hooks/git-hooks/pre_commit.py +52 -11
- package/hooks/git-hooks/test_installed_policy_lint.py +159 -0
- package/hooks/git-hooks/test_native_policy_commit.py +87 -0
- package/hooks/git-hooks/test_pre_commit.py +246 -0
- package/hooks/hooks.json +1 -1
- package/hooks/hooks_constants/bash_post_call_dispatcher_constants.py +14 -1
- package/hooks/hooks_constants/pr_done_reminder_constants.py +98 -0
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +10 -43
- package/hooks/hooks_constants/test_bash_post_call_dispatcher_constants.py +19 -2
- package/hooks/hooks_constants/test_pr_done_reminder_constants.py +40 -0
- package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +20 -32
- package/package.json +2 -2
- package/rules/durable-post-artifacts.md +2 -1
- package/rules/git-workflow.md +6 -6
- package/scripts/check.ps1 +0 -1
- package/{.agents/skills/run-claude-dev-env/driver.mjs → scripts/ci/installer-lifecycle.mjs} +3 -28
- package/scripts/ci/installer-lifecycle.test.mjs +26 -0
- package/scripts/ci/windows-installer-lifecycle.ps1 +1 -11
- package/scripts/dev_env_scripts_constants/shared_tree_constants.py +4 -5
- package/{.agents/skills/_shared/pr-loop/scripts → scripts}/durable_post_lint.py +5 -41
- package/{.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/durable_post_lint_constants.py → scripts/durable_post_lint_config/config/constants.py} +0 -10
- package/{.agents/skills/_shared/pr-loop/scripts/test_durable_post_lint_constants.py → scripts/durable_post_lint_config/config/test_constants.py} +1 -1
- package/scripts/policy_lint/adapter_detectors.py +13 -6
- package/scripts/policy_lint/adapters.py +7 -4
- package/scripts/policy_lint/config/constants.py +5 -0
- package/scripts/policy_lint/engine.py +48 -4
- package/scripts/policy_lint/model.py +1 -0
- package/scripts/policy_lint/registry.py +1 -1
- package/scripts/policy_lint/selection.py +2 -1
- package/scripts/policy_lint/selection_git.py +2 -0
- package/scripts/refresh_pstack_plugin_skills.mjs +200 -0
- package/scripts/shared_tree_paths.py +32 -3
- package/scripts/test_shared_tree_paths.py +33 -0
- package/scripts/tests/test_policy_lint_rules_archive.py +120 -0
- package/scripts/tests/test_policy_lint_rules_engine.py +25 -4
- package/scripts/tests/test_policy_lint_rules_installed_loader.py +29 -0
- package/scripts/tests/test_policy_lint_rules_registry.py +227 -13
- package/scripts/tests/test_policy_lint_selection.py +25 -2
- package/scripts/tests/test_policy_lint_selection_native_index.py +30 -0
- package/scripts/tests/test_rules.py +3 -7
- package/.agents/skills/_shared/pr-loop/.claude/CLAUDE.md +0 -1
- package/.agents/skills/_shared/pr-loop/AGENTS.md +0 -1
- package/.agents/skills/_shared/pr-loop/audit-contract.md +0 -5
- package/.agents/skills/_shared/pr-loop/audit-reply-template.md +0 -5
- package/.agents/skills/_shared/pr-loop/code-rules-gate.md +0 -5
- package/.agents/skills/_shared/pr-loop/fix-protocol.md +0 -5
- package/.agents/skills/_shared/pr-loop/gh-payloads.md +0 -5
- package/.agents/skills/_shared/pr-loop/portable-driver.md +0 -149
- package/.agents/skills/_shared/pr-loop/post-audit-thread-contract.md +0 -5
- package/.agents/skills/_shared/pr-loop/precatch-rubric.md +0 -5
- package/.agents/skills/_shared/pr-loop/preflight-proposal.contract.test.mjs +0 -119
- package/.agents/skills/_shared/pr-loop/preflight-proposal.md +0 -5
- package/.agents/skills/_shared/pr-loop/prompts/.claude/CLAUDE.md +0 -1
- package/.agents/skills/_shared/pr-loop/prompts/AGENTS.md +0 -1
- package/.agents/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +0 -324
- package/.agents/skills/_shared/pr-loop/scripts/.claude/CLAUDE.md +0 -1
- package/.agents/skills/_shared/pr-loop/scripts/AGENTS.md +0 -1
- package/.agents/skills/_shared/pr-loop/scripts/RUNTIME_SCRIPTS.md +0 -29
- package/.agents/skills/_shared/pr-loop/scripts/_cli_utils.py +0 -22
- package/.agents/skills/_shared/pr-loop/scripts/_path_resolver.py +0 -186
- package/.agents/skills/_shared/pr-loop/scripts/_xml_utils.py +0 -20
- package/.agents/skills/_shared/pr-loop/scripts/audit_category_schema.py +0 -355
- package/.agents/skills/_shared/pr-loop/scripts/build_audit_prompt.py +0 -289
- package/.agents/skills/_shared/pr-loop/scripts/build_converge_task_list.py +0 -310
- package/.agents/skills/_shared/pr-loop/scripts/build_fix_prompt.py +0 -292
- package/.agents/skills/_shared/pr-loop/scripts/init_loop_state.py +0 -134
- package/.agents/skills/_shared/pr-loop/scripts/portable_converge_driver.py +0 -1646
- package/.agents/skills/_shared/pr-loop/scripts/preflight_worktree.py +0 -392
- package/.agents/skills/_shared/pr-loop/scripts/select_converge_pacer.py +0 -215
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/.claude/CLAUDE.md +0 -1
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/AGENTS.md +0 -1
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/__init__.py +0 -0
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py +0 -37
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/converge_task_list_constants.py +0 -56
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/handoff_constants.py +0 -45
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/pacer_constants.py +0 -47
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +0 -157
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/portable_driver_constants.py +0 -177
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/preflight_constants.py +0 -70
- package/.agents/skills/_shared/pr-loop/scripts/teardown_worktrees.py +0 -172
- package/.agents/skills/_shared/pr-loop/scripts/test__path_resolver.py +0 -106
- package/.agents/skills/_shared/pr-loop/scripts/test_audit_category_schema.py +0 -94
- package/.agents/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +0 -499
- package/.agents/skills/_shared/pr-loop/scripts/test_build_converge_task_list.py +0 -140
- package/.agents/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +0 -262
- package/.agents/skills/_shared/pr-loop/scripts/test_init_loop_state.py +0 -71
- package/.agents/skills/_shared/pr-loop/scripts/test_portable_converge_driver.py +0 -1119
- package/.agents/skills/_shared/pr-loop/scripts/test_preflight_worktree.py +0 -287
- package/.agents/skills/_shared/pr-loop/scripts/test_select_converge_pacer.py +0 -207
- package/.agents/skills/_shared/pr-loop/scripts/test_teardown_worktrees.py +0 -59
- package/.agents/skills/_shared/pr-loop/scripts/test_write_handoff.py +0 -201
- package/.agents/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +0 -182
- package/.agents/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +0 -206
- package/.agents/skills/_shared/pr-loop/scripts/write_handoff.py +0 -309
- package/.agents/skills/_shared/pr-loop/state-schema.md +0 -5
- package/.agents/skills/_shared/pr-loop/worker-spawn.md +0 -5
- package/.agents/skills/autoconverge/.claude/CLAUDE.md +0 -1
- package/.agents/skills/autoconverge/AGENTS.md +0 -1
- package/.agents/skills/autoconverge/SKILL.md +0 -431
- package/.agents/skills/autoconverge/reference/.claude/CLAUDE.md +0 -1
- package/.agents/skills/autoconverge/reference/AGENTS.md +0 -1
- package/.agents/skills/autoconverge/reference/closing-report.md +0 -93
- package/.agents/skills/autoconverge/reference/convergence-gates.md +0 -31
- package/.agents/skills/autoconverge/reference/convergence.md +0 -219
- package/.agents/skills/autoconverge/reference/copilot-findings.md +0 -51
- package/.agents/skills/autoconverge/reference/gotchas.md +0 -66
- package/.agents/skills/autoconverge/reference/headless-safety.md +0 -44
- package/.agents/skills/autoconverge/reference/multi-pr.md +0 -128
- package/.agents/skills/autoconverge/reference/self-closing-loop.md +0 -84
- package/.agents/skills/autoconverge/reference/stop-conditions.md +0 -127
- package/.agents/skills/autoconverge/test_portable_pacer_gate.py +0 -54
- package/.agents/skills/autoconverge/workflow/.claude/CLAUDE.md +0 -1
- package/.agents/skills/autoconverge/workflow/AGENTS.md +0 -1
- package/.agents/skills/autoconverge/workflow/aggregate_runs.py +0 -371
- package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/.claude/CLAUDE.md +0 -1
- package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/AGENTS.md +0 -1
- package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/__init__.py +0 -0
- package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +0 -250
- package/.agents/skills/autoconverge/workflow/converge.clean-audit.test.mjs +0 -665
- package/.agents/skills/autoconverge/workflow/converge.codex-gate.test.mjs +0 -472
- package/.agents/skills/autoconverge/workflow/converge.contract.test.mjs +0 -64
- package/.agents/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +0 -1009
- package/.agents/skills/autoconverge/workflow/converge.fix-progress.test.mjs +0 -107
- package/.agents/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +0 -33
- package/.agents/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +0 -15
- package/.agents/skills/autoconverge/workflow/converge.mjs +0 -3132
- package/.agents/skills/autoconverge/workflow/converge.p2-advance.test.mjs +0 -202
- package/.agents/skills/autoconverge/workflow/converge.path-aware.test.mjs +0 -47
- package/.agents/skills/autoconverge/workflow/converge.precatch.test.mjs +0 -152
- package/.agents/skills/autoconverge/workflow/converge.run-input.test.mjs +0 -116
- package/.agents/skills/autoconverge/workflow/converge_multi.mjs +0 -169
- package/.agents/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +0 -173
- package/.agents/skills/autoconverge/workflow/convergence_summary.py +0 -110
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a11d903476b803493.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a26213978adeef6fb.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a3def0d15ed9d9110.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a41f41b1b708ee3b7.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a758b880abecc3ff7.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a8897b89656b1bd16.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ab1c2d3e4f5a6b7c8.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-abd463d744a1437bc.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ad19d027ae8ee1816.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +0 -266
- package/.agents/skills/autoconverge/workflow/render_report.py +0 -1024
- package/.agents/skills/autoconverge/workflow/test_aggregate_runs.py +0 -134
- package/.agents/skills/autoconverge/workflow/test_convergence_summary.py +0 -149
- package/.agents/skills/autoconverge/workflow/test_render_report.py +0 -812
- package/.agents/skills/hitl/SKILL.md +0 -45
- package/.agents/skills/pr-cleanup/SKILL.md +0 -140
- package/.agents/skills/pr-cleanup/reference/process-inventory.md +0 -17
- package/.agents/skills/pr-cleanup/reference/task-seeds.md +0 -22
- package/.agents/skills/pr-name-by-capability/SKILL.md +0 -73
- package/.agents/skills/pr-name-by-capability/reference/fetch-commands.md +0 -29
- package/.agents/skills/pr-name-by-capability/reference/offense-examples.md +0 -32
- package/.agents/skills/pr-name-by-capability/reference/report-template.md +0 -29
- package/.agents/skills/pr-name-by-capability/reference/rule-checklist.md +0 -38
- package/.agents/skills/pr-name-by-capability/reference/task-seeds.md +0 -10
- package/.agents/skills/pr-plain-language-cleanup/SKILL.md +0 -93
- package/.agents/skills/pr-refinement/SKILL.md +0 -31
- package/.agents/skills/pr-shared-extraction/SKILL.md +0 -177
- package/.agents/skills/pr-shared-extraction/reference/examples.md +0 -46
- package/.agents/skills/pr-shared-extraction/reference/offense-taxonomy.md +0 -100
- package/.agents/skills/pr-small-cl/SKILL.md +0 -52
- package/.agents/skills/pr-title-description/SKILL.md +0 -96
- package/.agents/skills/prototype/SKILL.md +0 -85
- package/.agents/skills/prototype/reference/honest-limitations.md +0 -23
- package/.agents/skills/prototype/reference/promotion-tasks.md +0 -23
- package/.agents/skills/prototype/scripts/build_sandbox_settings.py +0 -249
- package/.agents/skills/prototype/scripts/conftest.py +0 -15
- package/.agents/skills/prototype/scripts/launch_sandbox.py +0 -205
- package/.agents/skills/prototype/scripts/probe_sandbox_safety.py +0 -311
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/__init__.py +0 -1
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/__init__.py +0 -0
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/build_sandbox_settings_constants.py +0 -41
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/launch_sandbox_constants.py +0 -23
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/probe_sandbox_safety_constants.py +0 -45
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/prototype_common_constants.py +0 -10
- package/.agents/skills/prototype/scripts/test_build_sandbox_settings.py +0 -275
- package/.agents/skills/prototype/scripts/test_launch_sandbox.py +0 -303
- package/.agents/skills/prototype/scripts/test_probe_sandbox_safety.py +0 -312
- package/.agents/skills/prototype/workflows/promotion.md +0 -25
- package/.agents/skills/prototype/workflows/sandbox.md +0 -35
- package/.agents/skills/rebase/.claude/CLAUDE.md +0 -1
- package/.agents/skills/rebase/AGENTS.md +0 -1
- package/.agents/skills/rebase/SKILL.md +0 -178
- package/.agents/skills/review-router/SKILL.md +0 -51
- package/.agents/skills/review-router/reference/route-policy.json +0 -7
- package/.agents/skills/review-router/reference/task-seeds.md +0 -1
- package/.agents/skills/review-router/scripts/conftest.py +0 -4
- package/.agents/skills/review-router/scripts/review_router.py +0 -50
- package/.agents/skills/review-router/scripts/review_router_cli.py +0 -211
- package/.agents/skills/review-router/scripts/review_router_constants/__init__.py +0 -1
- package/.agents/skills/review-router/scripts/review_router_constants/config/__init__.py +0 -0
- package/.agents/skills/review-router/scripts/review_router_constants/config/constants.py +0 -13
- package/.agents/skills/review-router/scripts/test_review_router.py +0 -23
- package/.agents/skills/review-router/scripts/test_review_router_cli.py +0 -47
- package/.agents/skills/review-tier/SKILL.md +0 -56
- package/.agents/skills/review-tier/reference/task-seeds.md +0 -1
- package/.agents/skills/review-tier/reference/tier-policy.json +0 -9
- package/.agents/skills/review-tier/scripts/conftest.py +0 -4
- package/.agents/skills/review-tier/scripts/review_tier.py +0 -359
- package/.agents/skills/review-tier/scripts/review_tier_constants/__init__.py +0 -1
- package/.agents/skills/review-tier/scripts/review_tier_constants/config/__init__.py +0 -0
- package/.agents/skills/review-tier/scripts/review_tier_constants/config/constants.py +0 -29
- package/.agents/skills/review-tier/scripts/test_generation.py +0 -159
- package/.agents/skills/review-tier/scripts/test_review_tier.py +0 -33
- package/.agents/skills/run-claude-dev-env/SKILL.md +0 -84
- package/.agents/skills/session-log/.claude/CLAUDE.md +0 -1
- package/.agents/skills/session-log/AGENTS.md +0 -1
- package/.agents/skills/session-log/SKILL.md +0 -170
- package/.agents/skills/session-log/templates/frontmatter.md +0 -40
- package/.agents/skills/session-tidy/.claude/CLAUDE.md +0 -1
- package/.agents/skills/session-tidy/AGENTS.md +0 -1
- package/.agents/skills/session-tidy/SKILL.md +0 -191
- package/.agents/skills/skill-builder/references/.claude/CLAUDE.md +0 -1
- package/.agents/skills/skill-builder/references/AGENTS.md +0 -1
- package/.agents/skills/skill-builder/references/delegation-map.md +0 -130
- package/.agents/skills/skill-builder/references/description-field.md +0 -111
- package/.agents/skills/skill-builder/references/deterministic-elements.md +0 -218
- package/.agents/skills/skill-builder/references/progressive-disclosure.md +0 -122
- package/.agents/skills/skill-builder/references/self-audit-checklist.md +0 -126
- package/.agents/skills/skill-builder/references/skill-modularity.md +0 -111
- package/.agents/skills/skill-builder/references/skill-types.md +0 -232
- package/.agents/skills/skill-builder/references/thariq-x-post-skills.json +0 -33
- package/.agents/skills/skill-builder/templates/.claude/CLAUDE.md +0 -1
- package/.agents/skills/skill-builder/templates/AGENTS.md +0 -1
- package/.agents/skills/skill-builder/templates/gap-analysis.md +0 -87
- package/.agents/skills/skill-builder/workflows/.claude/CLAUDE.md +0 -1
- package/.agents/skills/skill-builder/workflows/AGENTS.md +0 -1
- package/.agents/skills/skill-builder/workflows/improve-skill.md +0 -136
- package/.agents/skills/skill-builder/workflows/new-skill.md +0 -179
- package/.agents/skills/skill-builder/workflows/polish-skill.md +0 -139
- package/.agents/skills/source-command-sr-loop/SKILL.md +0 -82
- package/.agents/skills/update/.claude/CLAUDE.md +0 -1
- package/.agents/skills/update/AGENTS.md +0 -1
- package/.agents/skills/update/SKILL.md +0 -190
- package/_shared/pr-loop/AGENTS.md +0 -1
- package/_shared/pr-loop/CLAUDE.md +0 -1
- package/_shared/pr-loop/audit-contract.md +0 -183
- package/_shared/pr-loop/audit-reply-template.md +0 -151
- package/_shared/pr-loop/code-rules-gate.md +0 -65
- package/_shared/pr-loop/fix-protocol.md +0 -60
- package/_shared/pr-loop/gh-payloads.md +0 -75
- package/_shared/pr-loop/post-audit-thread-contract.md +0 -51
- package/_shared/pr-loop/precatch-rubric.md +0 -66
- package/_shared/pr-loop/preflight-proposal.md +0 -56
- package/_shared/pr-loop/scripts/post_audit_thread.py +0 -1242
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/post_audit_thread_constants.py +0 -199
- package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +0 -1115
- package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +0 -129
- package/_shared/pr-loop/state-schema.md +0 -45
- package/_shared/pr-loop/worker-spawn.md +0 -189
- /package/{.agents/skills/_shared/pr-loop/scripts → scripts}/test_durable_post_lint.py +0 -0
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
"""Canonical path resolution for pr-loop skills (bugteam, qbug, findbugs, fixbugs).
|
|
2
|
-
|
|
3
|
-
Single source of truth for all path patterns — when a path changes, only this
|
|
4
|
-
file is edited. Pure functions with no side effects.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
import tempfile
|
|
10
|
-
from dataclasses import dataclass
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
|
|
13
|
-
from skills_pr_loop_constants.path_resolver_constants import (
|
|
14
|
-
DIFF_PATCH_TEMPLATE,
|
|
15
|
-
FIX_OUTCOME_XML_TEMPLATE,
|
|
16
|
-
MULTI_PR_SLUG_TEMPLATE,
|
|
17
|
-
OUTCOME_XML_TEMPLATE,
|
|
18
|
-
PER_PR_WORKSPACE_TEMPLATE,
|
|
19
|
-
RUN_NAME_TEMPLATE_MULTI,
|
|
20
|
-
RUN_NAME_TEMPLATE_SINGLE,
|
|
21
|
-
SLUGIFY_REPLACEMENT,
|
|
22
|
-
SLUGIFY_SAFE_CHARS,
|
|
23
|
-
WORKTREE_DIRNAME,
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@dataclass(frozen=True)
|
|
28
|
-
class PerPrWorkspace:
|
|
29
|
-
"""Resolved per-PR workspace paths for a pr-loop run.
|
|
30
|
-
|
|
31
|
-
Attributes:
|
|
32
|
-
worktree: Absolute path to the git worktree checkout.
|
|
33
|
-
diff_patch_template: A ``str.format`` template carrying a ``{loop}``
|
|
34
|
-
placeholder for the per-loop diff/patch file path.
|
|
35
|
-
outcome_xml_template: A ``str.format`` template carrying ``{number}``
|
|
36
|
-
and ``{loop}`` placeholders for the AUDIT outcome XML filename.
|
|
37
|
-
fix_outcome_xml_template: A ``str.format`` template carrying
|
|
38
|
-
``{number}`` and ``{loop}`` placeholders for the FIX outcome XML
|
|
39
|
-
filename.
|
|
40
|
-
"""
|
|
41
|
-
|
|
42
|
-
worktree: Path
|
|
43
|
-
diff_patch_template: str
|
|
44
|
-
outcome_xml_template: str
|
|
45
|
-
fix_outcome_xml_template: str
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def sanitize_branch_name(head_branch: str) -> str:
|
|
49
|
-
"""Replace filesystem-unsafe characters in a git branch name.
|
|
50
|
-
|
|
51
|
-
Args:
|
|
52
|
-
head_branch: Raw git branch name (e.g. 'feat/my-branch').
|
|
53
|
-
|
|
54
|
-
Returns:
|
|
55
|
-
Sanitized string safe for use in directory names.
|
|
56
|
-
"""
|
|
57
|
-
return SLUGIFY_SAFE_CHARS.sub(SLUGIFY_REPLACEMENT, head_branch)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def build_run_name(pr_number: int, head_branch: str, *, is_multi_pr: bool) -> str:
|
|
61
|
-
"""Build the run_name directory token for a single or multi-PR run.
|
|
62
|
-
|
|
63
|
-
Args:
|
|
64
|
-
pr_number: Pull request number.
|
|
65
|
-
head_branch: Head branch ref (used for multi-PR naming).
|
|
66
|
-
is_multi_pr: True when the run spans multiple PRs.
|
|
67
|
-
|
|
68
|
-
Returns:
|
|
69
|
-
Run name string (e.g. 'bugteam-pr-422' or 'bugteam-feat-my-branch').
|
|
70
|
-
"""
|
|
71
|
-
if is_multi_pr:
|
|
72
|
-
return RUN_NAME_TEMPLATE_MULTI.format(
|
|
73
|
-
sanitized_branch=sanitize_branch_name(head_branch),
|
|
74
|
-
)
|
|
75
|
-
return RUN_NAME_TEMPLATE_SINGLE.format(number=pr_number)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def resolve_run_temp_dir(run_name: str) -> Path:
|
|
79
|
-
"""Resolve the temporary directory for a given run name.
|
|
80
|
-
|
|
81
|
-
Args:
|
|
82
|
-
run_name: Run name token (from build_run_name).
|
|
83
|
-
|
|
84
|
-
Returns:
|
|
85
|
-
Absolute path to the run's temp directory.
|
|
86
|
-
"""
|
|
87
|
-
return Path(tempfile.gettempdir()) / run_name
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def slugify_pr_identity(owner: str, repo: str, pr_number: int) -> str:
|
|
91
|
-
"""Generate a slugified identity token for a PR.
|
|
92
|
-
|
|
93
|
-
Used as a subdirectory name under the run temp dir in multi-PR scenarios.
|
|
94
|
-
|
|
95
|
-
Args:
|
|
96
|
-
owner: GitHub repository owner.
|
|
97
|
-
repo: GitHub repository name.
|
|
98
|
-
pr_number: Pull request number.
|
|
99
|
-
|
|
100
|
-
Returns:
|
|
101
|
-
Slugified token (e.g. 'jl-cmd-claude-dev-env-pr-422').
|
|
102
|
-
"""
|
|
103
|
-
return MULTI_PR_SLUG_TEMPLATE.format(owner=owner, repo=repo, number=pr_number)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def per_pr_workspace(
|
|
107
|
-
run_temp_dir: Path, owner: str, repo: str, pr_number: int
|
|
108
|
-
) -> PerPrWorkspace:
|
|
109
|
-
"""Build the per-PR workspace paths.
|
|
110
|
-
|
|
111
|
-
Args:
|
|
112
|
-
run_temp_dir: Run temp directory (from resolve_run_temp_dir).
|
|
113
|
-
owner: GitHub repository owner.
|
|
114
|
-
repo: GitHub repository name.
|
|
115
|
-
pr_number: Pull request number.
|
|
116
|
-
|
|
117
|
-
Returns:
|
|
118
|
-
A PerPrWorkspace whose ``worktree`` is the git worktree checkout Path
|
|
119
|
-
and whose ``diff_patch_template`` / ``outcome_xml_template`` /
|
|
120
|
-
``fix_outcome_xml_template`` are ``str.format`` templates carrying
|
|
121
|
-
``{loop}`` (and, for the XML templates, ``{number}``) placeholders.
|
|
122
|
-
"""
|
|
123
|
-
pr_workspace_dir = run_temp_dir / PER_PR_WORKSPACE_TEMPLATE.format(number=pr_number)
|
|
124
|
-
slug = slugify_pr_identity(owner, repo, pr_number)
|
|
125
|
-
return PerPrWorkspace(
|
|
126
|
-
worktree=pr_workspace_dir / WORKTREE_DIRNAME,
|
|
127
|
-
diff_patch_template=(pr_workspace_dir / slug / DIFF_PATCH_TEMPLATE).as_posix(),
|
|
128
|
-
outcome_xml_template=OUTCOME_XML_TEMPLATE,
|
|
129
|
-
fix_outcome_xml_template=FIX_OUTCOME_XML_TEMPLATE,
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
def outcome_xml_path(worktree_path: Path, pr_number: int, loop_number: int) -> Path:
|
|
134
|
-
"""Construct the canonical path for an AUDIT outcome XML file.
|
|
135
|
-
|
|
136
|
-
Args:
|
|
137
|
-
worktree_path: Path to the git worktree (from per_pr_workspace).
|
|
138
|
-
pr_number: Pull request number.
|
|
139
|
-
loop_number: Current loop iteration.
|
|
140
|
-
|
|
141
|
-
Returns:
|
|
142
|
-
Absolute path (e.g. '<worktree>/.bugteam-pr422-loop3.outcomes.xml').
|
|
143
|
-
"""
|
|
144
|
-
return worktree_path / OUTCOME_XML_TEMPLATE.format(
|
|
145
|
-
number=pr_number, loop=loop_number
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
def fix_outcome_xml_path(worktree_path: Path, pr_number: int, loop_number: int) -> Path:
|
|
150
|
-
"""Construct the canonical path for a FIX outcome XML file.
|
|
151
|
-
|
|
152
|
-
Args:
|
|
153
|
-
worktree_path: Path to the git worktree (from per_pr_workspace).
|
|
154
|
-
pr_number: Pull request number.
|
|
155
|
-
loop_number: Current loop iteration.
|
|
156
|
-
|
|
157
|
-
Returns:
|
|
158
|
-
Absolute path (e.g. '<worktree>/.bugteam-pr422-loop3.fix-outcomes.xml').
|
|
159
|
-
"""
|
|
160
|
-
return worktree_path / FIX_OUTCOME_XML_TEMPLATE.format(
|
|
161
|
-
number=pr_number, loop=loop_number
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def diff_patch_path(
|
|
166
|
-
run_temp_dir: Path,
|
|
167
|
-
owner: str,
|
|
168
|
-
repo: str,
|
|
169
|
-
pr_number: int,
|
|
170
|
-
loop_number: int,
|
|
171
|
-
) -> Path:
|
|
172
|
-
"""Construct the path for a diff/patch file.
|
|
173
|
-
|
|
174
|
-
Args:
|
|
175
|
-
run_temp_dir: Run temp directory (from resolve_run_temp_dir).
|
|
176
|
-
owner: GitHub repository owner.
|
|
177
|
-
repo: GitHub repository name.
|
|
178
|
-
pr_number: Pull request number.
|
|
179
|
-
loop_number: Current loop iteration.
|
|
180
|
-
|
|
181
|
-
Returns:
|
|
182
|
-
Absolute path (e.g. '<run_temp>/jl-cmd-claude-dev-env-pr-422/loop-3.patch').
|
|
183
|
-
"""
|
|
184
|
-
slug = slugify_pr_identity(owner, repo, pr_number)
|
|
185
|
-
filename = DIFF_PATCH_TEMPLATE.format(loop=loop_number)
|
|
186
|
-
return run_temp_dir / slug / filename
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
"""Shared XML utilities for pr-loop scripts."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from xml.dom import minidom
|
|
6
|
-
from xml.etree.ElementTree import Element, tostring
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def emit_pretty_xml(root: Element) -> str:
|
|
10
|
-
"""Serialize an ElementTree to a pretty-printed XML string.
|
|
11
|
-
|
|
12
|
-
Args:
|
|
13
|
-
root: Root XML element.
|
|
14
|
-
|
|
15
|
-
Returns:
|
|
16
|
-
Pretty-printed XML string.
|
|
17
|
-
"""
|
|
18
|
-
raw_text = tostring(root, encoding="unicode")
|
|
19
|
-
reparsed_dom = minidom.parseString(raw_text)
|
|
20
|
-
return reparsed_dom.toprettyxml(indent=" ")
|
|
@@ -1,355 +0,0 @@
|
|
|
1
|
-
"""Load and validate the A-Q audit-category schema.
|
|
2
|
-
|
|
3
|
-
One machine-readable source under audit-rubrics/audit-categories.json holds
|
|
4
|
-
each category id, title, slug, and sub-bucket id/axis pairs. Worked examples
|
|
5
|
-
stay in the rubric markdown files only.
|
|
6
|
-
|
|
7
|
-
::
|
|
8
|
-
|
|
9
|
-
schema = load_audit_category_schema()
|
|
10
|
-
entries = category_id_title_entries()
|
|
11
|
-
# entries[0] == ("A", "API contract verification")
|
|
12
|
-
findings = validate_projections()
|
|
13
|
-
# findings == [] when rubrics and prompt skeletons match the schema
|
|
14
|
-
|
|
15
|
-
Use validate_projections before shipping a rubric or prompt change so the
|
|
16
|
-
three surfaces cannot drift.
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
from __future__ import annotations
|
|
20
|
-
|
|
21
|
-
import argparse
|
|
22
|
-
import json
|
|
23
|
-
import re
|
|
24
|
-
import sys
|
|
25
|
-
from pathlib import Path
|
|
26
|
-
|
|
27
|
-
from skills_pr_loop_constants.audit_category_schema_constants import (
|
|
28
|
-
CATEGORY_ID_KEY,
|
|
29
|
-
CATEGORY_RUBRICS_DIRECTORY,
|
|
30
|
-
CATEGORY_SLUG_KEY,
|
|
31
|
-
CATEGORY_SLUG_PREFIX_TEMPLATE,
|
|
32
|
-
CATEGORY_SUB_BUCKETS_KEY,
|
|
33
|
-
CATEGORY_TITLE_KEY,
|
|
34
|
-
EXPECTED_CATEGORY_IDS,
|
|
35
|
-
HEADING_LETTER_GROUP,
|
|
36
|
-
HEADING_PATTERN,
|
|
37
|
-
HEADING_TITLE_GROUP,
|
|
38
|
-
JSON_INDENT,
|
|
39
|
-
NEWLINE,
|
|
40
|
-
PACKAGE_ROOT,
|
|
41
|
-
PROMPTS_DIRECTORY,
|
|
42
|
-
RUBRIC_SUB_BUCKET_ROW_TEMPLATE,
|
|
43
|
-
SCHEMA_CATEGORIES_KEY,
|
|
44
|
-
SCHEMA_RELATIVE_PATH,
|
|
45
|
-
SCHEMA_VERSION,
|
|
46
|
-
SCHEMA_VERSION_KEY,
|
|
47
|
-
SKELETON_COUNT_PATTERN,
|
|
48
|
-
SKELETON_PLACEHOLDER_FRAGMENT,
|
|
49
|
-
SKELETON_SEPARATOR_LINE,
|
|
50
|
-
SUB_BUCKET_AXIS_KEY,
|
|
51
|
-
UTF8_ENCODING,
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
CategoryMapping = dict[str, object]
|
|
55
|
-
SchemaMapping = dict[str, object]
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def audit_category_schema_path() -> Path:
|
|
59
|
-
"""Return the path to the committed A-Q schema JSON file.
|
|
60
|
-
|
|
61
|
-
Returns:
|
|
62
|
-
Absolute path to audit-categories.json under the package root.
|
|
63
|
-
"""
|
|
64
|
-
return PACKAGE_ROOT / SCHEMA_RELATIVE_PATH
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def load_audit_category_schema() -> SchemaMapping:
|
|
68
|
-
"""Load and structurally check the A-Q schema document.
|
|
69
|
-
|
|
70
|
-
Returns:
|
|
71
|
-
The schema object with schema_version and categories.
|
|
72
|
-
|
|
73
|
-
Raises:
|
|
74
|
-
FileNotFoundError: When the schema file is missing.
|
|
75
|
-
ValueError: When required fields or order are wrong.
|
|
76
|
-
json.JSONDecodeError: When the schema file is not valid JSON.
|
|
77
|
-
"""
|
|
78
|
-
schema_path = audit_category_schema_path()
|
|
79
|
-
loaded_document = json.loads(schema_path.read_text(encoding=UTF8_ENCODING))
|
|
80
|
-
if not isinstance(loaded_document, dict):
|
|
81
|
-
raise ValueError("schema root must be an object")
|
|
82
|
-
if loaded_document.get(SCHEMA_VERSION_KEY) != SCHEMA_VERSION:
|
|
83
|
-
raise ValueError(
|
|
84
|
-
f"schema_version must be {SCHEMA_VERSION}, "
|
|
85
|
-
f"got {loaded_document.get(SCHEMA_VERSION_KEY)!r}"
|
|
86
|
-
)
|
|
87
|
-
all_categories = loaded_document.get(SCHEMA_CATEGORIES_KEY)
|
|
88
|
-
if not isinstance(all_categories, list) or not all_categories:
|
|
89
|
-
raise ValueError("categories must be a non-empty list")
|
|
90
|
-
all_ids = [
|
|
91
|
-
each_category.get(CATEGORY_ID_KEY)
|
|
92
|
-
for each_category in all_categories
|
|
93
|
-
if isinstance(each_category, dict)
|
|
94
|
-
]
|
|
95
|
-
if all_ids != list(EXPECTED_CATEGORY_IDS):
|
|
96
|
-
raise ValueError(f"categories must list A-Q in order, got {all_ids!r}")
|
|
97
|
-
all_checked_categories: list[CategoryMapping] = []
|
|
98
|
-
for each_category in all_categories:
|
|
99
|
-
if not isinstance(each_category, dict):
|
|
100
|
-
raise ValueError("each category must be an object")
|
|
101
|
-
all_checked_categories.append(_require_category_shape(each_category))
|
|
102
|
-
return {
|
|
103
|
-
SCHEMA_VERSION_KEY: SCHEMA_VERSION,
|
|
104
|
-
SCHEMA_CATEGORIES_KEY: all_checked_categories,
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def _require_category_shape(all_category_fields: CategoryMapping) -> CategoryMapping:
|
|
109
|
-
category_id = all_category_fields.get(CATEGORY_ID_KEY)
|
|
110
|
-
if not isinstance(category_id, str) or len(category_id) != 1:
|
|
111
|
-
raise ValueError(f"category id must be a single letter, got {category_id!r}")
|
|
112
|
-
title = all_category_fields.get(CATEGORY_TITLE_KEY)
|
|
113
|
-
if not isinstance(title, str) or not title.strip():
|
|
114
|
-
raise ValueError(f"category {category_id} title must be a non-empty string")
|
|
115
|
-
slug = all_category_fields.get(CATEGORY_SLUG_KEY)
|
|
116
|
-
expected_slug_prefix = CATEGORY_SLUG_PREFIX_TEMPLATE.format(letter=category_id.lower())
|
|
117
|
-
if not isinstance(slug, str) or not slug.startswith(expected_slug_prefix):
|
|
118
|
-
raise ValueError(
|
|
119
|
-
f"category {category_id} slug must start with {expected_slug_prefix!r}, "
|
|
120
|
-
f"got {slug!r}"
|
|
121
|
-
)
|
|
122
|
-
all_sub_bucket_fields = all_category_fields.get(CATEGORY_SUB_BUCKETS_KEY)
|
|
123
|
-
if not isinstance(all_sub_bucket_fields, list) or not all_sub_bucket_fields:
|
|
124
|
-
raise ValueError(f"category {category_id} needs a non-empty sub_buckets list")
|
|
125
|
-
all_sub_buckets: list[CategoryMapping] = []
|
|
126
|
-
for each_index, each_sub_bucket in enumerate(all_sub_bucket_fields):
|
|
127
|
-
if not isinstance(each_sub_bucket, dict):
|
|
128
|
-
raise ValueError(f"category {category_id} sub_bucket {each_index} must be object")
|
|
129
|
-
expected_id = f"{category_id}{each_index + 1}"
|
|
130
|
-
sub_bucket_id = each_sub_bucket.get(CATEGORY_ID_KEY)
|
|
131
|
-
if sub_bucket_id != expected_id:
|
|
132
|
-
raise ValueError(
|
|
133
|
-
f"category {category_id} sub_bucket {each_index} id must be "
|
|
134
|
-
f"{expected_id}, got {sub_bucket_id!r}"
|
|
135
|
-
)
|
|
136
|
-
axis_name = each_sub_bucket.get(SUB_BUCKET_AXIS_KEY)
|
|
137
|
-
if not isinstance(axis_name, str) or not axis_name.strip():
|
|
138
|
-
raise ValueError(
|
|
139
|
-
f"category {category_id} sub_bucket {sub_bucket_id} needs axis_name"
|
|
140
|
-
)
|
|
141
|
-
all_sub_buckets.append(
|
|
142
|
-
{CATEGORY_ID_KEY: expected_id, SUB_BUCKET_AXIS_KEY: axis_name}
|
|
143
|
-
)
|
|
144
|
-
return {
|
|
145
|
-
CATEGORY_ID_KEY: category_id,
|
|
146
|
-
CATEGORY_TITLE_KEY: title,
|
|
147
|
-
CATEGORY_SLUG_KEY: slug,
|
|
148
|
-
CATEGORY_SUB_BUCKETS_KEY: all_sub_buckets,
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def category_id_title_entries() -> list[tuple[str, str]]:
|
|
153
|
-
"""Return (id, title) pairs in A-Q order from the schema.
|
|
154
|
-
|
|
155
|
-
Returns:
|
|
156
|
-
List of (category letter, title) tuples from the schema file.
|
|
157
|
-
"""
|
|
158
|
-
all_schema = load_audit_category_schema()
|
|
159
|
-
all_categories = all_schema[SCHEMA_CATEGORIES_KEY]
|
|
160
|
-
assert isinstance(all_categories, list)
|
|
161
|
-
all_entries: list[tuple[str, str]] = []
|
|
162
|
-
for each_category in all_categories:
|
|
163
|
-
assert isinstance(each_category, dict)
|
|
164
|
-
category_id = each_category[CATEGORY_ID_KEY]
|
|
165
|
-
title = each_category[CATEGORY_TITLE_KEY]
|
|
166
|
-
assert isinstance(category_id, str)
|
|
167
|
-
assert isinstance(title, str)
|
|
168
|
-
all_entries.append((category_id, title))
|
|
169
|
-
return all_entries
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
def render_skeleton_projections(all_schema: SchemaMapping) -> dict[str, str]:
|
|
173
|
-
"""Build deterministic skeleton text per category from the schema only.
|
|
174
|
-
|
|
175
|
-
Args:
|
|
176
|
-
all_schema: Loaded audit-category schema.
|
|
177
|
-
|
|
178
|
-
Returns:
|
|
179
|
-
Map of category letter to a stable multi-line skeleton projection.
|
|
180
|
-
"""
|
|
181
|
-
projection_by_letter: dict[str, str] = {}
|
|
182
|
-
all_categories = all_schema[SCHEMA_CATEGORIES_KEY]
|
|
183
|
-
assert isinstance(all_categories, list)
|
|
184
|
-
for each_category in all_categories:
|
|
185
|
-
assert isinstance(each_category, dict)
|
|
186
|
-
category_id = each_category[CATEGORY_ID_KEY]
|
|
187
|
-
title = each_category[CATEGORY_TITLE_KEY]
|
|
188
|
-
slug = each_category[CATEGORY_SLUG_KEY]
|
|
189
|
-
all_sub_buckets = each_category[CATEGORY_SUB_BUCKETS_KEY]
|
|
190
|
-
assert isinstance(category_id, str)
|
|
191
|
-
assert isinstance(title, str)
|
|
192
|
-
assert isinstance(slug, str)
|
|
193
|
-
assert isinstance(all_sub_buckets, list)
|
|
194
|
-
sub_bucket_count = len(all_sub_buckets)
|
|
195
|
-
all_lines = [
|
|
196
|
-
f"Category {category_id} — {title}",
|
|
197
|
-
f"slug: {slug}",
|
|
198
|
-
f"decomposed into {sub_bucket_count} sub-buckets",
|
|
199
|
-
f"For each sub-bucket {category_id}1-{category_id}{sub_bucket_count}:",
|
|
200
|
-
]
|
|
201
|
-
for each_sub_bucket in all_sub_buckets:
|
|
202
|
-
assert isinstance(each_sub_bucket, dict)
|
|
203
|
-
sub_bucket_id = each_sub_bucket[CATEGORY_ID_KEY]
|
|
204
|
-
axis_name = each_sub_bucket[SUB_BUCKET_AXIS_KEY]
|
|
205
|
-
all_lines.append(f"{sub_bucket_id}. {axis_name}")
|
|
206
|
-
projection_by_letter[category_id] = NEWLINE.join(all_lines) + NEWLINE
|
|
207
|
-
return projection_by_letter
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
def _validate_one_category(each_category: CategoryMapping) -> list[str]:
|
|
211
|
-
category_id = each_category[CATEGORY_ID_KEY]
|
|
212
|
-
slug = each_category[CATEGORY_SLUG_KEY]
|
|
213
|
-
title = each_category[CATEGORY_TITLE_KEY]
|
|
214
|
-
all_sub_buckets = each_category[CATEGORY_SUB_BUCKETS_KEY]
|
|
215
|
-
assert isinstance(category_id, str)
|
|
216
|
-
assert isinstance(slug, str)
|
|
217
|
-
assert isinstance(title, str)
|
|
218
|
-
assert isinstance(all_sub_buckets, list)
|
|
219
|
-
all_findings: list[str] = []
|
|
220
|
-
rubric_path = CATEGORY_RUBRICS_DIRECTORY / f"{slug}.md"
|
|
221
|
-
prompt_path = PROMPTS_DIRECTORY / f"{slug}.md"
|
|
222
|
-
if not rubric_path.is_file():
|
|
223
|
-
return [f"{category_id}: missing rubric {rubric_path.name}"]
|
|
224
|
-
if not prompt_path.is_file():
|
|
225
|
-
return [f"{category_id}: missing prompt {prompt_path.name}"]
|
|
226
|
-
rubric_text = rubric_path.read_text(encoding=UTF8_ENCODING)
|
|
227
|
-
all_rubric_lines = rubric_text.splitlines()
|
|
228
|
-
if not all_rubric_lines:
|
|
229
|
-
return [f"{category_id}: empty rubric"]
|
|
230
|
-
heading_match = HEADING_PATTERN.match(all_rubric_lines[0])
|
|
231
|
-
if heading_match is None:
|
|
232
|
-
return [f"{category_id}: rubric heading does not match pattern"]
|
|
233
|
-
heading_letter = heading_match.group(HEADING_LETTER_GROUP)
|
|
234
|
-
heading_title = heading_match.group(HEADING_TITLE_GROUP)
|
|
235
|
-
if heading_letter != category_id:
|
|
236
|
-
all_findings.append(f"{category_id}: rubric heading letter {heading_letter!r}")
|
|
237
|
-
if heading_title != title:
|
|
238
|
-
all_findings.append(
|
|
239
|
-
f"{category_id}: rubric title {heading_title!r} != schema {title!r}"
|
|
240
|
-
)
|
|
241
|
-
row_pattern = re.compile(
|
|
242
|
-
RUBRIC_SUB_BUCKET_ROW_TEMPLATE.format(category_id=re.escape(category_id)),
|
|
243
|
-
re.MULTILINE,
|
|
244
|
-
)
|
|
245
|
-
all_rubric_rows = row_pattern.findall(rubric_text)
|
|
246
|
-
all_schema_rows: list[tuple[str, str]] = []
|
|
247
|
-
for each_sub_bucket in all_sub_buckets:
|
|
248
|
-
assert isinstance(each_sub_bucket, dict)
|
|
249
|
-
sub_bucket_id = each_sub_bucket[CATEGORY_ID_KEY]
|
|
250
|
-
axis_name = each_sub_bucket[SUB_BUCKET_AXIS_KEY]
|
|
251
|
-
assert isinstance(sub_bucket_id, str)
|
|
252
|
-
assert isinstance(axis_name, str)
|
|
253
|
-
all_schema_rows.append((sub_bucket_id, axis_name))
|
|
254
|
-
all_rubric_pairs = [(row_id, axis.strip()) for row_id, axis in all_rubric_rows]
|
|
255
|
-
if all_rubric_pairs != all_schema_rows:
|
|
256
|
-
all_findings.append(
|
|
257
|
-
f"{category_id}: rubric sub-buckets {all_rubric_pairs!r} "
|
|
258
|
-
f"!= schema {all_schema_rows!r}"
|
|
259
|
-
)
|
|
260
|
-
all_findings.extend(
|
|
261
|
-
_validate_prompt_skeleton_count(category_id, prompt_path, len(all_sub_buckets))
|
|
262
|
-
)
|
|
263
|
-
return all_findings
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
def _validate_prompt_skeleton_count(
|
|
267
|
-
category_id: str,
|
|
268
|
-
prompt_path: Path,
|
|
269
|
-
sub_bucket_count: int,
|
|
270
|
-
) -> list[str]:
|
|
271
|
-
prompt_text = prompt_path.read_text(encoding=UTF8_ENCODING)
|
|
272
|
-
all_skeleton_lines: list[str] = []
|
|
273
|
-
for each_line in prompt_text.splitlines():
|
|
274
|
-
if each_line == SKELETON_SEPARATOR_LINE:
|
|
275
|
-
break
|
|
276
|
-
all_skeleton_lines.append(each_line)
|
|
277
|
-
skeleton_text = NEWLINE.join(all_skeleton_lines)
|
|
278
|
-
if SKELETON_PLACEHOLDER_FRAGMENT in skeleton_text:
|
|
279
|
-
return [
|
|
280
|
-
f"{category_id}: prompt skeleton still uses [N] placeholder; "
|
|
281
|
-
f"use numeric count {sub_bucket_count}"
|
|
282
|
-
]
|
|
283
|
-
count_match = SKELETON_COUNT_PATTERN.search(skeleton_text)
|
|
284
|
-
if count_match is None:
|
|
285
|
-
return [f"{category_id}: prompt skeleton missing sub-bucket count form"]
|
|
286
|
-
skeleton_count = int(count_match.group(HEADING_LETTER_GROUP))
|
|
287
|
-
if skeleton_count != sub_bucket_count:
|
|
288
|
-
return [
|
|
289
|
-
f"{category_id}: prompt skeleton count {skeleton_count} "
|
|
290
|
-
f"!= schema {sub_bucket_count}"
|
|
291
|
-
]
|
|
292
|
-
return []
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
def validate_projections() -> list[str]:
|
|
296
|
-
"""Compare schema against on-disk rubric and prompt skeleton projections.
|
|
297
|
-
|
|
298
|
-
Returns:
|
|
299
|
-
Finding strings; empty when every category is in parity.
|
|
300
|
-
"""
|
|
301
|
-
all_schema = load_audit_category_schema()
|
|
302
|
-
all_categories = all_schema[SCHEMA_CATEGORIES_KEY]
|
|
303
|
-
assert isinstance(all_categories, list)
|
|
304
|
-
all_findings: list[str] = []
|
|
305
|
-
for each_category in all_categories:
|
|
306
|
-
assert isinstance(each_category, dict)
|
|
307
|
-
all_findings.extend(_validate_one_category(each_category))
|
|
308
|
-
return all_findings
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
def main(all_argv: list[str]) -> int:
|
|
312
|
-
"""CLI: validate projections or print deterministic skeleton projections.
|
|
313
|
-
|
|
314
|
-
Args:
|
|
315
|
-
all_argv: Argument list without the program name.
|
|
316
|
-
|
|
317
|
-
Returns:
|
|
318
|
-
Process exit code (0 success, 1 validation findings).
|
|
319
|
-
"""
|
|
320
|
-
parser = argparse.ArgumentParser(description=__doc__)
|
|
321
|
-
parser.add_argument(
|
|
322
|
-
"--validate",
|
|
323
|
-
action="store_true",
|
|
324
|
-
help="Validate rubric and prompt projections against the schema.",
|
|
325
|
-
)
|
|
326
|
-
parser.add_argument(
|
|
327
|
-
"--render-skeletons",
|
|
328
|
-
action="store_true",
|
|
329
|
-
help="Print deterministic skeleton projections as JSON.",
|
|
330
|
-
)
|
|
331
|
-
arguments = parser.parse_args(all_argv)
|
|
332
|
-
if arguments.validate:
|
|
333
|
-
all_findings = validate_projections()
|
|
334
|
-
if all_findings:
|
|
335
|
-
for each_finding in all_findings:
|
|
336
|
-
print(each_finding, file=sys.stderr)
|
|
337
|
-
return 1
|
|
338
|
-
print("OK")
|
|
339
|
-
return 0
|
|
340
|
-
if arguments.render_skeletons:
|
|
341
|
-
all_schema = load_audit_category_schema()
|
|
342
|
-
print(
|
|
343
|
-
json.dumps(
|
|
344
|
-
render_skeleton_projections(all_schema),
|
|
345
|
-
indent=JSON_INDENT,
|
|
346
|
-
ensure_ascii=False,
|
|
347
|
-
)
|
|
348
|
-
)
|
|
349
|
-
return 0
|
|
350
|
-
parser.print_help()
|
|
351
|
-
return 0
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
if __name__ == "__main__":
|
|
355
|
-
raise SystemExit(main(sys.argv[1:]))
|