claude-dev-env 2.8.0 → 2.10.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 +8 -2
- package/_shared/pr-loop/CLAUDE.md +1 -1
- package/_shared/pr-loop/audit-contract.md +17 -6
- package/_shared/pr-loop/audit-reply-template.md +4 -4
- package/_shared/pr-loop/code-rules-gate.md +3 -5
- package/_shared/pr-loop/fix-protocol.md +2 -3
- package/_shared/pr-loop/gh-payloads.md +1 -1
- package/_shared/pr-loop/scripts/CLAUDE.md +1 -1
- package/_shared/pr-loop/scripts/README.md +1 -1
- package/_shared/pr-loop/scripts/code_rules_gate.py +2 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_running.py +16 -1
- package/_shared/pr-loop/scripts/code_rules_gate_parts/git_blob_readers.py +11 -5
- package/_shared/pr-loop/scripts/preflight.py +9 -4
- package/_shared/pr-loop/scripts/reviews_disabled.py +50 -22
- package/_shared/pr-loop/scripts/tests/conftest.py +20 -0
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +6 -6
- package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +50 -6
- package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +1 -1
- package/_shared/pr-loop/state-schema.md +5 -14
- package/agents/CLAUDE.md +2 -2
- package/agents/clean-coder.md +62 -562
- package/agents/code-quality-agent.md +10 -2
- package/agents/code-verifier.md +1 -1
- package/agents/test_agent_frontmatter.py +58 -40
- package/audit-rubrics/CLAUDE.md +2 -1
- package/audit-rubrics/audit-categories.json +704 -0
- package/audit-rubrics/prompts/category-i-concurrency.md +1 -1
- package/bin/CLAUDE.md +16 -5
- package/bin/ever-shipped-skills.mjs +2 -0
- package/bin/install-plan.mjs +402 -0
- package/bin/install-transaction.mjs +455 -0
- package/bin/install.mjs +593 -147
- package/bin/install.plan.test.mjs +194 -0
- package/bin/install.profile-root.test.mjs +154 -0
- package/bin/install.profiles.test.mjs +253 -0
- package/bin/install.settings-defaults.test.mjs +200 -0
- package/bin/install.transaction.test.mjs +400 -0
- package/bin/install.uninstall-transaction.test.mjs +418 -0
- package/bin/merge_managed_permissions.mjs +130 -0
- package/bin/resolve-install-root.mjs +181 -0
- package/bin/select-install-targets.mjs +401 -0
- package/commands/CLAUDE.md +0 -2
- package/docs/CODE_RULES.md +4 -2
- package/docs/references/CLAUDE.md +3 -2
- package/docs/references/advisor-tool.md +44 -6
- package/docs/references/prose-style-enforcement.md +25 -0
- package/docs/references/team-advisor-skill.md +14 -8
- package/hooks/blocking/CLAUDE.md +6 -6
- package/hooks/blocking/_path_setup.py +9 -5
- package/hooks/blocking/code_rules_docstrings.py +124 -30
- package/hooks/blocking/code_rules_enforcer.py +161 -16
- package/hooks/blocking/code_rules_shared.py +40 -23
- package/hooks/blocking/config/CLAUDE.md +3 -5
- package/hooks/blocking/config/prose_style_enforcement_constants.py +38 -0
- package/hooks/blocking/config/test_prose_style_enforcement_constants.py +45 -0
- package/hooks/blocking/eli11_reply_enforcer.py +70 -113
- package/hooks/blocking/hedging_language_blocker.py +103 -20
- package/hooks/blocking/hook_prose_detector_consistency.py +6 -0
- package/hooks/blocking/intent_only_ending_blocker.py +6 -0
- package/hooks/blocking/plain_language_blocker.py +139 -20
- package/hooks/blocking/pre_tool_use_dispatcher.py +102 -20
- package/hooks/blocking/state_description_blocker.py +7 -1
- package/hooks/blocking/tdd_enforcer.py +8 -0
- package/hooks/blocking/test__path_setup.py +28 -0
- package/hooks/blocking/test_code_rules_enforcer_agent_home_tooling.py +99 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_args_span_scope.py +232 -10
- package/hooks/blocking/test_code_rules_enforcer_ephemeral.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_join_separator_magic.py +41 -0
- package/hooks/blocking/test_code_rules_enforcer_string_magic.py +98 -0
- package/hooks/blocking/test_eli11_reply_enforcer.py +98 -165
- package/hooks/blocking/test_hedging_language_blocker.py +120 -1
- package/hooks/blocking/test_hook_prose_detector_consistency.py +28 -8
- package/hooks/blocking/test_intent_only_ending_blocker.py +27 -2
- package/hooks/blocking/test_package_inventory_stale_blocker.py +11 -4
- package/hooks/blocking/test_plain_language_blocker.py +129 -19
- package/hooks/blocking/test_plain_language_blocker_allowlist.py +70 -26
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +99 -26
- package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +87 -50
- package/hooks/blocking/test_state_description_blocker.py +45 -2
- package/hooks/blocking/test_stop_dispatcher.py +11 -7
- package/hooks/blocking/test_volatile_path_in_post_blocker.py +12 -12
- package/hooks/blocking/volatile_path_in_post_blocker.py +2 -2
- package/hooks/hooks.json +15 -0
- package/hooks/hooks_constants/CLAUDE.md +14 -3
- package/hooks/hooks_constants/ask_user_question_shape.py +281 -0
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +2 -1
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
- package/hooks/hooks_constants/eli11_reply_enforcer_constants.py +5 -12
- package/hooks/hooks_constants/hedging_uncertainty_constants.py +42 -0
- package/hooks/hooks_constants/issue_tracker_session_starter_constants.py +23 -0
- package/hooks/hooks_constants/orchestrator_auto_starter_constants.py +23 -0
- package/hooks/hooks_constants/piped_pytest_blocker_constants.py +4 -1
- package/hooks/hooks_constants/plain_language_blocker_constants.py +4 -1
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +6 -0
- package/hooks/hooks_constants/project_paths_reader.py +31 -4
- package/hooks/hooks_constants/prose_matcher_precision_constants.py +40 -0
- package/hooks/hooks_constants/pytest_invocation.py +354 -0
- package/hooks/hooks_constants/session_start_injector.py +163 -0
- package/hooks/hooks_constants/session_start_injector_constants.py +46 -0
- package/hooks/hooks_constants/shell_command_pipeline.py +397 -0
- package/hooks/hooks_constants/shell_command_segments.py +5 -0
- package/hooks/hooks_constants/test_ask_user_question_shape.py +167 -0
- package/hooks/hooks_constants/test_project_paths_reader.py +29 -0
- package/hooks/hooks_constants/test_prose_metrics_parity.py +8 -0
- package/hooks/hooks_constants/test_pytest_invocation.py +130 -0
- package/hooks/hooks_constants/test_session_start_injector.py +168 -0
- package/hooks/hooks_constants/test_shell_command_pipeline.py +135 -0
- package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +1 -1
- package/hooks/hooks_constants/working_style_prompt_constants.py +30 -0
- package/hooks/observability/CLAUDE.md +2 -0
- package/hooks/observability/prose_matcher_advisory.py +237 -0
- package/hooks/observability/test_prose_matcher_advisory.py +143 -0
- package/hooks/session/CLAUDE.md +9 -1
- package/hooks/session/_path_setup.py +13 -0
- package/hooks/session/issue_tracker_session_starter.py +135 -0
- package/hooks/session/orchestrator_auto_starter.py +100 -0
- package/hooks/session/test__path_setup.py +28 -0
- package/hooks/session/test_issue_tracker_session_starter.py +104 -0
- package/hooks/session/test_orchestrator_auto_starter.py +99 -0
- package/hooks/session/test_working_style_prompt.py +47 -0
- package/hooks/session/untracked_repo_detector.py +1 -24
- package/hooks/session/working_style_prompt.py +36 -0
- package/hooks/validators/_path_setup.py +19 -0
- package/hooks/validators/run_all_validators.py +8 -13
- package/installable-surfaces.manifest.json +21 -0
- package/output-styles/CLAUDE.md +15 -0
- package/package.json +5 -2
- package/rules/CLAUDE.md +1 -0
- package/rules/code-standards.md +33 -7
- package/rules/durable-post-artifacts.md +2 -2
- package/rules/eli11-replies.md +7 -2
- package/rules/hedging-claims.md +4 -2
- package/rules/long-horizon-autonomy.md +3 -1
- package/rules/opus5-communication-contract.md +45 -0
- package/rules/plain-language.md +2 -2
- package/rules/research-mode.md +1 -1
- package/scripts/CLAUDE.md +13 -2
- package/scripts/Sync-RepoMain.ps1 +215 -0
- package/scripts/active_capability_references.py +218 -0
- package/scripts/ci/windows-installer-lifecycle.ps1 +78 -0
- package/scripts/claude_chain_runner.py +394 -6
- package/scripts/claude_chain_usage.py +1 -1
- package/scripts/codex_compat_materializer.py +105 -85
- package/scripts/dev_env_scripts_constants/CLAUDE.md +3 -1
- package/scripts/dev_env_scripts_constants/active_capability_constants.py +46 -0
- package/scripts/dev_env_scripts_constants/claude_chain_constants.py +74 -0
- package/scripts/dev_env_scripts_constants/grok_run_ledger_constants.py +50 -0
- package/scripts/dev_env_scripts_constants/grok_worker_constants.py +104 -0
- package/scripts/dev_env_scripts_constants/verify_installable_package_constants.py +116 -0
- package/scripts/grok_patch_artifacts.py +123 -0
- package/scripts/grok_run_ledger.py +318 -0
- package/scripts/profile-isolation-launchers/config/mcp-bundles.json +25 -0
- package/scripts/profile-isolation-launchers/config/profile-isolation-constants.mjs +60 -0
- package/scripts/profile-isolation-launchers/config/profiles.manifest.json +54 -0
- package/scripts/profile-isolation-launchers/config/shared-allowlist.json +64 -0
- package/scripts/profile-isolation-launchers/launcher-runtime.mjs +180 -0
- package/scripts/profile-isolation-launchers/lib/profile-manifest.mjs +288 -0
- package/scripts/profile-isolation-launchers/mcp-bundles.mjs +275 -0
- package/scripts/profile-isolation-launchers/profile-isolation-contract.test.mjs +221 -0
- package/scripts/profile-isolation-launchers/tests/launcher-runtime.test.mjs +108 -0
- package/scripts/profile-isolation-launchers/tests/mcp-bundles.test.mjs +147 -0
- package/scripts/profile-isolation-launchers/tests/shortcut-contract.test.ps1 +102 -0
- package/scripts/profile-isolation-launchers/tests/version-compatibility.test.mjs +210 -0
- package/scripts/profile-isolation-launchers/version-compatibility.mjs +299 -0
- package/scripts/profile-isolation-launchers/windows/shortcut-inventory.ps1 +127 -0
- package/scripts/profile-isolation-launchers/windows/shortcut-manifest.json +51 -0
- package/scripts/profile-isolation-launchers/windows/shortcut-reconcile.ps1 +77 -0
- package/scripts/spawn_grok_batch.py +556 -9
- package/scripts/test_active_capability_references.py +108 -0
- package/scripts/test_claude_chain_runner.py +414 -82
- package/scripts/test_claude_chain_usage.py +12 -12
- package/scripts/test_grok_patch_artifacts.py +82 -0
- package/scripts/test_grok_run_ledger.py +116 -0
- package/scripts/test_resolve_worker_spawn.py +2 -2
- package/scripts/test_spawn_grok_batch.py +295 -0
- package/scripts/test_verify_installable_package.py +208 -0
- package/scripts/tests/test_codex_compat_materializer.py +33 -0
- package/scripts/verify_installable_package.py +612 -0
- package/settings.json +10 -0
- package/skills/CLAUDE.md +6 -2
- package/skills/_shared/CLAUDE.md +37 -4
- package/skills/_shared/advisor/CLAUDE.md +9 -0
- package/skills/_shared/advisor/advisor-protocol.md +5 -0
- package/skills/_shared/advisor/scripts/README.md +9 -0
- package/skills/_shared/end-of-run-gotcha-recommendations.md +156 -0
- package/skills/_shared/pr-loop/CLAUDE.md +18 -1
- package/skills/_shared/pr-loop/audit-contract.md +5 -0
- package/skills/_shared/pr-loop/audit-reply-template.md +5 -0
- package/skills/_shared/pr-loop/code-rules-gate.md +5 -0
- package/skills/_shared/pr-loop/fix-protocol.md +5 -0
- package/skills/_shared/pr-loop/gh-payloads.md +5 -0
- package/skills/_shared/pr-loop/post-audit-thread-contract.md +5 -0
- package/skills/_shared/pr-loop/precatch-rubric.md +5 -0
- package/skills/_shared/pr-loop/scripts/CLAUDE.md +9 -1
- package/skills/_shared/pr-loop/scripts/RUNTIME_SCRIPTS.md +29 -0
- package/skills/_shared/pr-loop/scripts/audit_category_schema.py +355 -0
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +1 -0
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py +32 -0
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +7 -19
- package/skills/_shared/pr-loop/scripts/test_audit_category_schema.py +94 -0
- package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +21 -0
- package/skills/_shared/pr-loop/state-schema.md +5 -0
- package/skills/_shared/pr-loop/worker-spawn.md +5 -0
- package/skills/autoconverge/reference/convergence.md +2 -1
- package/skills/autoconverge/reference/stop-conditions.md +5 -3
- package/skills/beat-sheet/SKILL.md +54 -0
- package/skills/beat-sheet/reference/visual-beats.md +29 -0
- package/skills/bugteam/CONSTRAINTS.md +4 -4
- package/skills/bugteam/EXAMPLES.md +1 -1
- package/skills/bugteam/reference/README.md +1 -1
- package/skills/e-code-review/SKILL.md +29 -3
- package/skills/e-code-review/reference/effort-evaluation.md +35 -0
- package/skills/e-code-review/reference/medium.md +15 -4
- package/skills/e-code-review/reference/runner-selection.md +40 -0
- package/skills/e-code-review/scripts/config/e_code_review_effort_constants/__init__.py +41 -0
- package/skills/e-code-review/scripts/config/e_code_review_effort_constants/effort_constants.py +40 -0
- package/skills/e-code-review/scripts/e_code_review_scripts_constants/__init__.py +1 -0
- package/skills/e-code-review/scripts/e_code_review_scripts_constants/finding_pipeline_constants.py +49 -0
- package/skills/e-code-review/scripts/e_code_review_scripts_constants/grok_code_review_constants.py +55 -0
- package/skills/e-code-review/scripts/effort_defaults_evidence.json +186 -0
- package/skills/e-code-review/scripts/effort_evaluation.py +362 -0
- package/skills/e-code-review/scripts/finding_pipeline.py +140 -0
- package/skills/e-code-review/scripts/fixtures/demanding.json +26 -0
- package/skills/e-code-review/scripts/fixtures/easy.json +14 -0
- package/skills/e-code-review/scripts/fixtures/medium.json +20 -0
- package/skills/e-code-review/scripts/grok_code_review.py +230 -0
- package/skills/e-code-review/scripts/test_effort_evaluation.py +180 -0
- package/skills/e-code-review/scripts/test_finding_pipeline.py +197 -0
- package/skills/e-code-review/scripts/test_grok_code_review.py +289 -0
- package/skills/grok-spawn/SKILL.md +5 -0
- package/skills/orchestrator/SKILL.md +5 -0
- package/skills/plan-to-pr/scripts/create_packet.py +4 -4
- package/skills/plan-to-pr/scripts/load_skill_constants.py +41 -0
- package/skills/plan-to-pr/scripts/validate_packet.py +4 -4
- package/skills/plan-to-pr/scripts/validate_protocol.py +4 -1
- package/skills/plan-to-pr/scripts/validate_run.py +4 -1
- package/skills/pr-converge/scripts/check_convergence.py +21 -19
- package/skills/pr-converge/scripts/check_convergence_availability.py +50 -7
- package/skills/pr-converge/scripts/conftest.py +35 -0
- package/skills/pr-converge/scripts/test_check_convergence_availability.py +65 -0
- package/skills/pr-converge/scripts/test_check_convergence_codex.py +11 -1
- package/skills/pr-converge/scripts/test_check_convergence_contract.py +9 -2
- package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
- package/skills/rebase/SKILL.md +15 -3
- package/skills/reviewer-gates/SKILL.md +2 -2
- package/skills/show/SKILL.md +51 -0
- package/skills/show/references/accessibility.md +7 -0
- package/skills/show/references/art.md +3 -0
- package/skills/show/references/charts.md +3 -0
- package/skills/show/references/core-design.md +14 -0
- package/skills/show/references/erds.md +3 -0
- package/skills/show/references/flowcharts.md +3 -0
- package/skills/show/references/host-and-html.md +3 -0
- package/skills/show/references/illustrative-diagrams.md +10 -0
- package/skills/show/references/interaction.md +3 -0
- package/skills/show/references/mockups.md +3 -0
- package/skills/show/references/quality-gates.md +7 -0
- package/skills/show/references/structural-diagrams.md +3 -0
- package/skills/show/references/subject-inventory.md +21 -0
- package/skills/show/references/svg-contract.md +22 -0
- package/skills/show/routing.yaml +30 -0
- package/skills/show/samples/pr1262-v2.svg +222 -0
- package/skills/show/scripts/README.md +6 -0
- package/skills/show/scripts/validate-artifact.py +91 -0
- package/skills/show/scripts/validate-package.py +18 -0
- package/skills/show/templates/html-widget.html +4 -0
- package/skills/show/templates/svg-base.svg +19 -0
- package/skills/show/tests/fixtures/css-var.svg +6 -0
- package/skills/show/tests/fixtures/dead-ref.svg +7 -0
- package/skills/show/tests/fixtures/filled-glyph.svg +8 -0
- package/skills/show/tests/fixtures/inherited-fill.svg +18 -0
- package/skills/show/tests/fixtures/invalid.svg +1 -0
- package/skills/show/tests/fixtures/large-canvas.svg +21 -0
- package/skills/show/tests/fixtures/unfilled-connector.svg +15 -0
- package/skills/show/tests/fixtures/valid.html +1 -0
- package/skills/show/tests/test_validate-artifact.py +74 -0
- package/skills/show/tests/test_validators.py +59 -0
- package/skills/show/workflows/create-visual.md +13 -0
- package/skills/show/workflows/review-visual.md +20 -0
- package/skills/split-pr/SKILL.md +85 -0
- package/skills/split-pr/reference/path-layers.md +16 -0
- package/skills/split-pr/reference/proposal-format.md +15 -0
- package/skills/split-pr/reference/split-further-loop.md +10 -0
- package/skills/split-pr/reference/splitting-principles.md +26 -0
- package/skills/split-pr/scripts/analyze_pr.py +279 -0
- package/skills/split-pr/scripts/categorize_files.py +106 -0
- package/skills/split-pr/scripts/config/__init__.py +1 -0
- package/skills/split-pr/scripts/config/dependency_constants.py +14 -0
- package/skills/split-pr/scripts/config/git_operations_constants.py +36 -0
- package/skills/split-pr/scripts/config/packing_constants.py +61 -0
- package/skills/split-pr/scripts/config/plan_constants.py +49 -0
- package/skills/split-pr/scripts/config/split_pr_constants.py +110 -0
- package/skills/split-pr/scripts/execute_split_slices.py +82 -0
- package/skills/split-pr/scripts/pack_files_into_slices.py +212 -0
- package/skills/split-pr/scripts/split_pr_dependency_graph.py +70 -0
- package/skills/split-pr/scripts/split_pr_git_operations.py +184 -0
- package/skills/split-pr/scripts/split_pr_layer_order.py +58 -0
- package/skills/split-pr/scripts/split_pr_paginate.py +119 -0
- package/skills/split-pr/scripts/split_pr_process_runner.py +52 -0
- package/skills/split-pr/scripts/split_pr_script_types.py +126 -0
- package/skills/split-pr/scripts/split_pr_title.py +41 -0
- package/skills/split-pr/scripts/test_analyze_pr.py +228 -0
- package/skills/split-pr/scripts/test_categorize_files.py +55 -0
- package/skills/split-pr/scripts/test_categorize_files_packing.py +59 -0
- package/skills/split-pr/scripts/test_execute_split_slices.py +99 -0
- package/skills/split-pr/scripts/test_split_pr_dependency_graph.py +47 -0
- package/skills/split-pr/scripts/test_split_pr_git_operations.py +125 -0
- package/skills/split-pr/scripts/test_split_pr_layer_order.py +36 -0
- package/skills/split-pr/scripts/test_split_pr_paginate.py +65 -0
- package/skills/split-pr/scripts/test_split_pr_script_types.py +73 -0
- package/skills/split-pr/scripts/test_split_pr_title.py +28 -0
- package/skills/split-pr/scripts/test_verify_dependency_graph.py +46 -0
- package/skills/split-pr/scripts/test_verify_plan.py +56 -0
- package/skills/split-pr/scripts/test_verify_plan_contract.py +50 -0
- package/skills/split-pr/scripts/test_verify_plan_path_normalization.py +45 -0
- package/skills/split-pr/scripts/verify_dependency_graph.py +111 -0
- package/skills/split-pr/scripts/verify_plan.py +139 -0
- package/skills/task-build/reference/tool-routing.md +3 -0
- package/skills/team-advisor/SKILL.md +23 -44
- package/system-prompts/software-engineer.xml +17 -5
- package/commands/initialize.md +0 -90
- package/commands/stubcheck.md +0 -88
- package/skills/test_markdown_link_integrity.py +0 -107
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Advisor
|
|
2
|
+
|
|
3
|
+
**Canonical home:**
|
|
4
|
+
|
|
5
|
+
@~/.claude/_shared/advisor/CLAUDE.md
|
|
6
|
+
|
|
7
|
+
@~/.claude/_shared/advisor/advisor-protocol.md
|
|
8
|
+
|
|
9
|
+
Scripts: `~/.claude/_shared/advisor/scripts/` (`model_tier_run_validator.py`, `tier_model_ids.py`, constants under `scripts/config/`).
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# End-of-run gotcha recommendations
|
|
2
|
+
|
|
3
|
+
**Shared close-out** for every skill.
|
|
4
|
+
|
|
5
|
+
After the deliverable, recommend pasteable gotchas from issues **this run** hit.
|
|
6
|
+
|
|
7
|
+
## When
|
|
8
|
+
|
|
9
|
+
- **With issues:** end of work, after the deliverable.
|
|
10
|
+
- **Clean run:** end with the deliverable only.
|
|
11
|
+
|
|
12
|
+
## What counts
|
|
13
|
+
|
|
14
|
+
Keep only items that **bit this run**:
|
|
15
|
+
|
|
16
|
+
- Wrong **path, file, or config**
|
|
17
|
+
- **Command / CLI** failed or needed different flags
|
|
18
|
+
- **Auth, permission, or environment** blocked a step
|
|
19
|
+
- **Data shape** mismatch (empty set, wrong schema, bad field)
|
|
20
|
+
- **Tool, UI, or selector** missed the target
|
|
21
|
+
- **Timing** needed a retry or longer wait
|
|
22
|
+
- **Dependency or install** gap or wrong version
|
|
23
|
+
- **Output format** wrong for the next step
|
|
24
|
+
- A **workaround** required to finish
|
|
25
|
+
|
|
26
|
+
## What to produce
|
|
27
|
+
|
|
28
|
+
**Order (hard):**
|
|
29
|
+
|
|
30
|
+
1. Show findings in chat; categorize as P0-P3.
|
|
31
|
+
2. Then `AskUserQuestion`.
|
|
32
|
+
3. On confirm only, file via `issue-tracker`.
|
|
33
|
+
|
|
34
|
+
### Cold-reader frame
|
|
35
|
+
|
|
36
|
+
Assume zero context.
|
|
37
|
+
|
|
38
|
+
**Exactly two setup sentences**, each on its **own rendered line**:
|
|
39
|
+
|
|
40
|
+
1. What we were doing.
|
|
41
|
+
2. Why these notes matter.
|
|
42
|
+
|
|
43
|
+
### 1. Chat findings (always first)
|
|
44
|
+
|
|
45
|
+
#### Sentence law (HARD)
|
|
46
|
+
|
|
47
|
+
| Rule | Required |
|
|
48
|
+
|---|---|
|
|
49
|
+
| **One sentence per rendered line** | Exactly one terminal `.` `?` or `!` on that line |
|
|
50
|
+
| **No collapsed pairs** | A single markdown newline is **not** a line break — it joins sentences |
|
|
51
|
+
| **Force the break** | Blank line between every prose sentence, **or** one list item per sentence |
|
|
52
|
+
| **No clause glue** | No em-dash or semicolon joining two clauses |
|
|
53
|
+
| **One idea** | If you can split it, split it |
|
|
54
|
+
| **Heavy detail** | Prefer an **ASCII infographic** over a stack of sentences |
|
|
55
|
+
|
|
56
|
+
**Self-check before send:** paste the chat into a viewer that collapses soft breaks.
|
|
57
|
+
|
|
58
|
+
If two periods appear on one visual line, **split or diagram**.
|
|
59
|
+
|
|
60
|
+
#### Per gotcha (HARD shape)
|
|
61
|
+
|
|
62
|
+
| Part | Form |
|
|
63
|
+
|---|---|
|
|
64
|
+
| **Title** | Heading fragment only |
|
|
65
|
+
| **Broke** | One sentence alone on its rendered line |
|
|
66
|
+
| **Fix** | One sentence alone on its rendered line |
|
|
67
|
+
| **ASCII** | Required for path, flag, or before/after |
|
|
68
|
+
|
|
69
|
+
**Cap: two prose sentences per gotcha.**
|
|
70
|
+
|
|
71
|
+
```markdown
|
|
72
|
+
## Session close-out
|
|
73
|
+
|
|
74
|
+
We built phone-theme prompts from a project style catalog.
|
|
75
|
+
|
|
76
|
+
Keep these traps for the next run.
|
|
77
|
+
|
|
78
|
+
## Gotcha recommendations
|
|
79
|
+
|
|
80
|
+
### Wrong catalog folder
|
|
81
|
+
|
|
82
|
+
The style picker failed outside the project.
|
|
83
|
+
|
|
84
|
+
Run from the project root with data/midjourney_sref_catalog.json.
|
|
85
|
+
|
|
86
|
+
```text
|
|
87
|
+
wrong folder --> fail
|
|
88
|
+
project root --> data/midjourney_sref_catalog.json
|
|
89
|
+
```
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Wrong (soft break joins sentences into one line):**
|
|
93
|
+
|
|
94
|
+
```markdown
|
|
95
|
+
The style picker failed outside the project.
|
|
96
|
+
Run from the project root with data/midjourney_sref_catalog.json.
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Renders as one line with **two** sentences.
|
|
100
|
+
|
|
101
|
+
**Right (blank line forces two lines):**
|
|
102
|
+
|
|
103
|
+
```markdown
|
|
104
|
+
The style picker failed outside the project.
|
|
105
|
+
|
|
106
|
+
Run from the project root with data/midjourney_sref_catalog.json.
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Right (list items):**
|
|
110
|
+
|
|
111
|
+
```markdown
|
|
112
|
+
- The style picker failed outside the project.
|
|
113
|
+
- Run from the project root with data/midjourney_sref_catalog.json.
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 2. Issue offer (`AskUserQuestion`)
|
|
117
|
+
|
|
118
|
+
Only after the chat block.
|
|
119
|
+
|
|
120
|
+
| Field | Limit |
|
|
121
|
+
|---|---|
|
|
122
|
+
| **Question** | One short sentence |
|
|
123
|
+
| **Header** | `File issues` |
|
|
124
|
+
| **label** | Plain name |
|
|
125
|
+
| **description** | One short sentence |
|
|
126
|
+
|
|
127
|
+
Detail and ASCII stay in chat.
|
|
128
|
+
|
|
129
|
+
**On confirm:** file via `issue-tracker`.
|
|
130
|
+
|
|
131
|
+
Cold-reader issue body.
|
|
132
|
+
|
|
133
|
+
Clean run: skip.
|
|
134
|
+
|
|
135
|
+
## Built-in skill gotcha
|
|
136
|
+
|
|
137
|
+
**Two sentences on one rendered line.**
|
|
138
|
+
|
|
139
|
+
Markdown soft-breaks join prose.
|
|
140
|
+
|
|
141
|
+
Review the close-out before send.
|
|
142
|
+
|
|
143
|
+
Split with a blank line or a list item.
|
|
144
|
+
|
|
145
|
+
Or move the detail into an ASCII infographic.
|
|
146
|
+
|
|
147
|
+
## Rules
|
|
148
|
+
|
|
149
|
+
- Cold-reader first.
|
|
150
|
+
- Chat first, then ask.
|
|
151
|
+
- One sentence per **rendered** line.
|
|
152
|
+
- Blank line or list item between prose sentences.
|
|
153
|
+
- Two prose sentences per gotcha max.
|
|
154
|
+
- ASCII for path, flag, or before/after.
|
|
155
|
+
- File only on confirm.
|
|
156
|
+
- Lived issues only.
|
|
@@ -12,7 +12,24 @@ those skills invoke during each loop tick.
|
|
|
12
12
|
| `prompts/` | XML agent prompt templates. |
|
|
13
13
|
| `scripts/` | Python scripts for loop state management, prompt building, outcome recording, path resolution, pacer selection, and preflight checks. |
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## Canonical-path stubs (`~/.claude/_shared/pr-loop/`)
|
|
16
|
+
|
|
17
|
+
Open a stub, then load the `@` target:
|
|
18
|
+
|
|
19
|
+
| Stub here | Load |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `audit-contract.md` | `@~/.claude/_shared/pr-loop/audit-contract.md` |
|
|
22
|
+
| `audit-reply-template.md` | `@~/.claude/_shared/pr-loop/audit-reply-template.md` |
|
|
23
|
+
| `code-rules-gate.md` | `@~/.claude/_shared/pr-loop/code-rules-gate.md` |
|
|
24
|
+
| `fix-protocol.md` | `@~/.claude/_shared/pr-loop/fix-protocol.md` |
|
|
25
|
+
| `gh-payloads.md` | `@~/.claude/_shared/pr-loop/gh-payloads.md` |
|
|
26
|
+
| `post-audit-thread-contract.md` | `@~/.claude/_shared/pr-loop/post-audit-thread-contract.md` |
|
|
27
|
+
| `precatch-rubric.md` | `@~/.claude/_shared/pr-loop/precatch-rubric.md` |
|
|
28
|
+
| `state-schema.md` | `@~/.claude/_shared/pr-loop/state-schema.md` |
|
|
29
|
+
| `worker-spawn.md` | `@~/.claude/_shared/pr-loop/worker-spawn.md` |
|
|
30
|
+
| `scripts/RUNTIME_SCRIPTS.md` | `@~/.claude/_shared/pr-loop/scripts/` |
|
|
31
|
+
|
|
32
|
+
## Key files (live in this tree)
|
|
16
33
|
|
|
17
34
|
| File | Role |
|
|
18
35
|
|---|---|
|
|
@@ -2,10 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
Python scripts that run the PR audit-fix loop at runtime. Both `bugteam` and `pr-converge` invoke these scripts during each loop tick.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Two script homes
|
|
6
|
+
|
|
7
|
+
| Home | Path |
|
|
8
|
+
|---|---|
|
|
9
|
+
| **Skill-local** (this folder) | Converge helpers: `build_*_prompt.py`, `init_loop_state.py`, `portable_converge_driver.py`, … |
|
|
10
|
+
| **Runtime** (top-level shared) | Gate / preflight / review helpers — see `RUNTIME_SCRIPTS.md` → `@~/.claude/_shared/pr-loop/scripts/` |
|
|
11
|
+
|
|
12
|
+
## Key files (skill-local)
|
|
6
13
|
|
|
7
14
|
| File | Role |
|
|
8
15
|
|---|---|
|
|
16
|
+
| `audit_category_schema.py` | Loads the A-Q schema JSON, exports category entries, validates rubric/prompt projections, renders deterministic skeletons. |
|
|
9
17
|
| `build_audit_prompt.py` | Assembles the audit agent prompt from loop state and category constants. |
|
|
10
18
|
| `build_fix_prompt.py` | Assembles the fix agent prompt from loop state and findings XML. |
|
|
11
19
|
| `init_loop_state.py` | Initializes the per-PR `loop-state.json` file in the workspace directory. |
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# PR-loop runtime scripts
|
|
2
|
+
|
|
3
|
+
**Canonical home:**
|
|
4
|
+
|
|
5
|
+
@~/.claude/_shared/pr-loop/scripts/code_rules_gate.py
|
|
6
|
+
|
|
7
|
+
@~/.claude/_shared/pr-loop/scripts/preflight.py
|
|
8
|
+
|
|
9
|
+
@~/.claude/_shared/pr-loop/scripts/post_audit_thread.py
|
|
10
|
+
|
|
11
|
+
@~/.claude/_shared/pr-loop/scripts/post_audit_review.py
|
|
12
|
+
|
|
13
|
+
@~/.claude/_shared/pr-loop/scripts/gh_util.py
|
|
14
|
+
|
|
15
|
+
@~/.claude/_shared/pr-loop/scripts/reviews_disabled.py
|
|
16
|
+
|
|
17
|
+
@~/.claude/_shared/pr-loop/scripts/reviewer_availability.py
|
|
18
|
+
|
|
19
|
+
@~/.claude/_shared/pr-loop/scripts/copilot_quota.py
|
|
20
|
+
|
|
21
|
+
@~/.claude/_shared/pr-loop/scripts/grant_project_claude_permissions.py
|
|
22
|
+
|
|
23
|
+
@~/.claude/_shared/pr-loop/scripts/revoke_project_claude_permissions.py
|
|
24
|
+
|
|
25
|
+
@~/.claude/_shared/pr-loop/scripts/verify_review.py
|
|
26
|
+
|
|
27
|
+
@~/.claude/_shared/pr-loop/scripts/README.md
|
|
28
|
+
|
|
29
|
+
Skill-local converge helpers (`build_*_prompt.py`, `init_loop_state.py`, and siblings) live in this `scripts/` folder. Runtime gate, preflight, and review helpers live under `~/.claude/_shared/pr-loop/scripts/`.
|
|
@@ -0,0 +1,355 @@
|
|
|
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:]))
|
|
@@ -8,6 +8,7 @@ Python package of named constants for the `pr-loop` shared scripts. All constant
|
|
|
8
8
|
|---|---|
|
|
9
9
|
| `__init__.py` | Package marker. |
|
|
10
10
|
| `path_resolver_constants.py` | Path template strings and format constants: workspace directory naming, worktree directory name, diff patch filename pattern, outcome XML filename patterns, fix status values, audit constraint texts, audit category entries, fix execution steps, and fix constraint texts. |
|
|
11
|
+
| `audit_category_schema_constants.py` | Paths, regexes, and encoding constants for `audit_category_schema.py` (A-Q schema load and projection parity). |
|
|
11
12
|
| `preflight_constants.py` | Constants used by `preflight_worktree.py` for exit codes and output marker strings. |
|
|
12
13
|
| `handoff_constants.py` | Filename, path-segment, and template constants for the durable handoff writer. |
|
|
13
14
|
| `pacer_constants.py` | Entry-skill names, pacer tokens, CLI flags, and result keys for `select_converge_pacer.py`. |
|