claude-dev-env 2.9.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 +1 -1
- 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 +58 -548
- package/agents/code-quality-agent.md +10 -2
- package/agents/code-verifier.md +1 -1
- package/agents/test_agent_frontmatter.py +32 -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/references/CLAUDE.md +1 -0
- package/docs/references/advisor-tool.md +1 -1
- package/docs/references/prose-style-enforcement.md +25 -0
- 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/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 +1 -3
- package/package.json +4 -2
- package/rules/CLAUDE.md +1 -0
- package/rules/durable-post-artifacts.md +2 -2
- package/rules/eli11-replies.md +6 -1
- 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 +11 -0
- 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 +2 -0
- 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/verify_installable_package_constants.py +116 -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 +3 -0
- 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_resolve_worker_spawn.py +2 -2
- 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 +2 -0
- package/skills/_shared/pr-loop/scripts/CLAUDE.md +1 -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/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 +26 -5
- 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/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/finding_pipeline_constants.py +49 -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 +16 -7
- 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 +77 -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/system-prompts/software-engineer.xml +11 -2
- package/commands/initialize.md +0 -90
- package/commands/stubcheck.md +0 -88
- package/output-styles/caveman-agent.md +0 -37
|
@@ -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`. |
|
package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Constants for audit_category_schema path resolution and parity checks."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
SCRIPTS_DIRECTORY = Path(__file__).resolve().parent.parent
|
|
9
|
+
PACKAGE_ROOT = SCRIPTS_DIRECTORY.parents[3]
|
|
10
|
+
SCHEMA_RELATIVE_PATH = Path("audit-rubrics") / "audit-categories.json"
|
|
11
|
+
CATEGORY_RUBRICS_DIRECTORY = PACKAGE_ROOT / "audit-rubrics" / "category_rubrics"
|
|
12
|
+
PROMPTS_DIRECTORY = PACKAGE_ROOT / "audit-rubrics" / "prompts"
|
|
13
|
+
HEADING_PATTERN = re.compile(r"^# Category ([A-Q]) — (.+)$")
|
|
14
|
+
HEADING_LETTER_GROUP = 1
|
|
15
|
+
HEADING_TITLE_GROUP = 2
|
|
16
|
+
EXPECTED_CATEGORY_IDS = tuple("ABCDEFGHIJKLMNOPQ")
|
|
17
|
+
SCHEMA_VERSION = 1
|
|
18
|
+
JSON_INDENT = 2
|
|
19
|
+
NEWLINE = "\n"
|
|
20
|
+
UTF8_ENCODING = "utf-8"
|
|
21
|
+
RUBRIC_SUB_BUCKET_ROW_TEMPLATE = r"^\| ({category_id}\d+) \| ([^|]+) \|"
|
|
22
|
+
SKELETON_COUNT_PATTERN = re.compile(r"decomposed into (\d+) sub-buckets")
|
|
23
|
+
SKELETON_PLACEHOLDER_FRAGMENT = "decomposed into [N] sub-buckets"
|
|
24
|
+
SKELETON_SEPARATOR_LINE = "---"
|
|
25
|
+
CATEGORY_SLUG_PREFIX_TEMPLATE = "category-{letter}-"
|
|
26
|
+
CATEGORY_ID_KEY = "id"
|
|
27
|
+
CATEGORY_TITLE_KEY = "title"
|
|
28
|
+
CATEGORY_SLUG_KEY = "slug"
|
|
29
|
+
CATEGORY_SUB_BUCKETS_KEY = "sub_buckets"
|
|
30
|
+
SUB_BUCKET_AXIS_KEY = "axis_name"
|
|
31
|
+
SCHEMA_CATEGORIES_KEY = "categories"
|
|
32
|
+
SCHEMA_VERSION_KEY = "schema_version"
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
|
|
5
|
+
from audit_category_schema import category_id_title_entries
|
|
6
|
+
|
|
5
7
|
RUN_NAME_TEMPLATE_SINGLE = "bugteam-pr-{number}"
|
|
6
8
|
RUN_NAME_TEMPLATE_MULTI = "bugteam-{sanitized_branch}"
|
|
7
9
|
PER_PR_WORKSPACE_TEMPLATE = "pr-{number}"
|
|
@@ -24,30 +26,16 @@ ALL_AUDIT_CONSTRAINT_TEXTS = [
|
|
|
24
26
|
"Work exclusively within the worktree directory.",
|
|
25
27
|
"Every finding must cite file:line.",
|
|
26
28
|
"Document each finding with severity, file, line, and suggested fix.",
|
|
29
|
+
"Report every real finding. Collection retains all severities "
|
|
30
|
+
"(P0, P1, P2). Do not drop findings by severity during collection; "
|
|
31
|
+
"severity or action filtering is a separate consumer stage after the "
|
|
32
|
+
"collection record is complete.",
|
|
27
33
|
"Read each file in the diff before reporting on it.",
|
|
28
34
|
"Double-quote every path in shell commands and write paths with "
|
|
29
35
|
"forward slashes (e.g. C:/Users/...), even on Windows.",
|
|
30
36
|
]
|
|
31
37
|
|
|
32
|
-
ALL_AUDIT_CATEGORY_ENTRIES =
|
|
33
|
-
("A", "API contract verification"),
|
|
34
|
-
("B", "Selector / query / engine compatibility"),
|
|
35
|
-
("C", "Resource cleanup and lifecycle"),
|
|
36
|
-
("D", "Variable scoping, ordering, and unbound references"),
|
|
37
|
-
("E", "Dead code and unused imports"),
|
|
38
|
-
("F", "Silent failures"),
|
|
39
|
-
("G", "Off-by-one, bounds, integer overflow"),
|
|
40
|
-
("H", "Security boundaries"),
|
|
41
|
-
("I", "Concurrency hazards"),
|
|
42
|
-
("J", "CODE_RULES.md compliance"),
|
|
43
|
-
("K", "Codebase conflicts (incomplete propagation)"),
|
|
44
|
-
("L", "Behavior-equivalence for refactors"),
|
|
45
|
-
("M", "Producer/consumer cardinality vs collection-type contract"),
|
|
46
|
-
("N", "Test-name scenario verifier"),
|
|
47
|
-
("O", "Docstring / fixture-prose vs implementation drift"),
|
|
48
|
-
("P", "Name / regex / word-list vs behavior-contract precision"),
|
|
49
|
-
("Q", "Cross-surface claim consistency (terminology, PR-description claims, message-vs-guard)"),
|
|
50
|
-
]
|
|
38
|
+
ALL_AUDIT_CATEGORY_ENTRIES = category_id_title_entries()
|
|
51
39
|
|
|
52
40
|
AUDIT_RUBRIC_REFERENCE_TEXT = (
|
|
53
41
|
"The category list above is a summary. The binding definition of each "
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Parity tests for the A-Q audit-category schema and projections."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib.util
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from types import ModuleType
|
|
10
|
+
|
|
11
|
+
_SCRIPTS_DIR = Path(__file__).resolve().parent
|
|
12
|
+
if str(_SCRIPTS_DIR) not in sys.path:
|
|
13
|
+
sys.path.insert(0, str(_SCRIPTS_DIR))
|
|
14
|
+
|
|
15
|
+
from skills_pr_loop_constants.audit_category_schema_constants import (
|
|
16
|
+
CATEGORY_SLUG_KEY,
|
|
17
|
+
CATEGORY_SUB_BUCKETS_KEY,
|
|
18
|
+
PROMPTS_DIRECTORY,
|
|
19
|
+
SCHEMA_CATEGORIES_KEY,
|
|
20
|
+
SKELETON_PLACEHOLDER_FRAGMENT,
|
|
21
|
+
SKELETON_SEPARATOR_LINE,
|
|
22
|
+
UTF8_ENCODING,
|
|
23
|
+
)
|
|
24
|
+
from skills_pr_loop_constants.path_resolver_constants import ALL_AUDIT_CATEGORY_ENTRIES
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _load_audit_category_schema() -> ModuleType:
|
|
28
|
+
module_path = _SCRIPTS_DIR / "audit_category_schema.py"
|
|
29
|
+
spec = importlib.util.spec_from_file_location("audit_category_schema", module_path)
|
|
30
|
+
assert spec is not None
|
|
31
|
+
assert spec.loader is not None
|
|
32
|
+
module = importlib.util.module_from_spec(spec)
|
|
33
|
+
sys.modules["audit_category_schema"] = module
|
|
34
|
+
spec.loader.exec_module(module)
|
|
35
|
+
return module
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
audit_category_schema = _load_audit_category_schema()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_schema_lists_a_through_q_in_order() -> None:
|
|
42
|
+
schema = audit_category_schema.load_audit_category_schema()
|
|
43
|
+
all_ids = [each_category["id"] for each_category in schema["categories"]]
|
|
44
|
+
assert all_ids == list("ABCDEFGHIJKLMNOPQ")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_schema_entries_match_constant_export() -> None:
|
|
48
|
+
assert (
|
|
49
|
+
audit_category_schema.category_id_title_entries()
|
|
50
|
+
== list(ALL_AUDIT_CATEGORY_ENTRIES)
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_validate_projections_matches_rubrics_and_prompts() -> None:
|
|
55
|
+
all_findings = audit_category_schema.validate_projections()
|
|
56
|
+
assert all_findings == [], all_findings
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_skeleton_projection_is_byte_stable() -> None:
|
|
60
|
+
schema = audit_category_schema.load_audit_category_schema()
|
|
61
|
+
first_render = audit_category_schema.render_skeleton_projections(schema)
|
|
62
|
+
second_render = audit_category_schema.render_skeleton_projections(
|
|
63
|
+
json.loads(json.dumps(schema))
|
|
64
|
+
)
|
|
65
|
+
assert first_render == second_render
|
|
66
|
+
assert "A1" in first_render["A"]
|
|
67
|
+
assert first_render["A"].count("\n") >= 1
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_schema_omits_worked_example_prose() -> None:
|
|
71
|
+
schema_path = audit_category_schema.audit_category_schema_path()
|
|
72
|
+
schema_text = schema_path.read_text(encoding="utf-8")
|
|
73
|
+
assert "Examples of Category" not in schema_text
|
|
74
|
+
assert "Sample prompt" not in schema_text
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_no_prompt_skeleton_uses_placeholder_count() -> None:
|
|
78
|
+
"""Migrated prompts state a numeric sub-bucket count, never [N]."""
|
|
79
|
+
schema = audit_category_schema.load_audit_category_schema()
|
|
80
|
+
all_findings = audit_category_schema.validate_projections()
|
|
81
|
+
assert all_findings == [], all_findings
|
|
82
|
+
for each_category in schema[SCHEMA_CATEGORIES_KEY]:
|
|
83
|
+
slug = each_category[CATEGORY_SLUG_KEY]
|
|
84
|
+
assert isinstance(slug, str)
|
|
85
|
+
prompt_path = PROMPTS_DIRECTORY / f"{slug}.md"
|
|
86
|
+
all_skeleton_lines: list[str] = []
|
|
87
|
+
for each_line in prompt_path.read_text(encoding=UTF8_ENCODING).splitlines():
|
|
88
|
+
if each_line == SKELETON_SEPARATOR_LINE:
|
|
89
|
+
break
|
|
90
|
+
all_skeleton_lines.append(each_line)
|
|
91
|
+
skeleton_text = "\n".join(all_skeleton_lines)
|
|
92
|
+
assert SKELETON_PLACEHOLDER_FRAGMENT not in skeleton_text, slug
|
|
93
|
+
sub_bucket_count = len(each_category[CATEGORY_SUB_BUCKETS_KEY])
|
|
94
|
+
assert f"decomposed into {sub_bucket_count} sub-buckets" in skeleton_text, slug
|
|
@@ -424,3 +424,24 @@ def test_precatch_rubric_adversarial_lane_uses_a_through_q() -> None:
|
|
|
424
424
|
rubric_text = _PRECATCH_RUBRIC_PATH.read_text(encoding="utf-8")
|
|
425
425
|
assert _A_THROUGH_Q_PATTERN.search(rubric_text) is not None
|
|
426
426
|
assert _STALE_A_THROUGH_P_PATTERN.search(rubric_text) is None
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def test_audit_prompt_constraints_require_collection_before_severity_filter() -> None:
|
|
430
|
+
root = _build_audit_root()
|
|
431
|
+
constraints = root.find("constraints")
|
|
432
|
+
assert constraints is not None
|
|
433
|
+
all_constraint_texts = [
|
|
434
|
+
each_constraint.text or "" for each_constraint in constraints
|
|
435
|
+
]
|
|
436
|
+
joined = "\n".join(all_constraint_texts)
|
|
437
|
+
assert "Report every real finding" in joined
|
|
438
|
+
assert "Do not drop findings by severity during collection" in joined
|
|
439
|
+
assert "separate consumer stage" in joined
|
|
440
|
+
assert "P0" in joined and "P1" in joined and "P2" in joined
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def test_audit_contract_documents_collection_before_severity_filtering() -> None:
|
|
444
|
+
contract_text = _AUDIT_CONTRACT_PATH.read_text(encoding="utf-8")
|
|
445
|
+
assert "## Collection before severity filtering" in contract_text
|
|
446
|
+
assert "separate consumer stage" in contract_text
|
|
447
|
+
assert "Do not drop a real finding because its severity is low" in contract_text
|
|
@@ -152,7 +152,8 @@ existing fix + re-converge path.
|
|
|
152
152
|
- Runs after Bugbot and Copilot. Honors `reviews_disabled.py --reviewer codex`,
|
|
153
153
|
the weekly usage probe via `is_codex_review_required` (shared threshold
|
|
154
154
|
constant — no inline percent), and the wrapper's `codex_down` class.
|
|
155
|
-
-
|
|
155
|
+
- Missing opt-in (the default), opt-out token, or `codex_down` → set
|
|
156
|
+
`codexDown`, no stamp, move to the
|
|
156
157
|
convergence check with `--codex-down`. A `codex_down` classification also
|
|
157
158
|
records a `codexNote` so the skip stays visible in the final report.
|
|
158
159
|
- Usage at/below threshold or null → skip with no stamp; the machine checklist
|
|
@@ -88,10 +88,12 @@ Each reviewer-down condition below skips its own convergence-check gate. The fla
|
|
|
88
88
|
`--copilot-down` (the Copilot review gate and the pending-requested-reviews
|
|
89
89
|
gate bypassed), then mark-ready. `copilotNote` records the bypass for the
|
|
90
90
|
final report.
|
|
91
|
-
- **Terminal Codex gate skipped (opt-out, usage, or `codex_down`)** — the
|
|
91
|
+
- **Terminal Codex gate skipped (no opt-in, opt-out, usage, or `codex_down`)** — the
|
|
92
92
|
conditional-required Codex gate runs after Bugbot and Copilot. When
|
|
93
|
-
`
|
|
94
|
-
|
|
93
|
+
`reviews_disabled.py --reviewer codex` exits 0 — which it does whenever
|
|
94
|
+
`CLAUDE_REVIEWS_ENABLED` omits `codex`, the default, and whenever
|
|
95
|
+
`CLAUDE_REVIEWS_DISABLED` lists it — the gate sets `codexDown` and advances
|
|
96
|
+
with no review. When the weekly
|
|
95
97
|
usage probe reports `percent_left` null or at/below the shared threshold
|
|
96
98
|
(`is_codex_review_required` false), the gate skips without a stamp; the
|
|
97
99
|
convergence check applies the same rule. When the codex-review wrapper
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: beat-sheet
|
|
3
|
+
description: "Formats a reply as single-line beats in plain everyday words. Triggers: '/beat-sheet', 'beat sheet this', 'give me the beats', 'write this as beats', 'too long', 'shorter', 'what?', 'english', 'no sense', 'jargon', 'huh?'."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# beat-sheet
|
|
7
|
+
|
|
8
|
+
Write in single-line beats. Make each line one complete thought,
|
|
9
|
+
under 12 words, with a blank line between lines. Keep it to ten
|
|
10
|
+
lines, each one covering a new point. Order: situation, problem,
|
|
11
|
+
fix, how it works. Put any command in its own code fence. Start
|
|
12
|
+
with one short bold title line. Use plain everyday words a tired
|
|
13
|
+
reader can skim.
|
|
14
|
+
|
|
15
|
+
## Plain words
|
|
16
|
+
|
|
17
|
+
Name the real thing first — a file, an image, a control, a
|
|
18
|
+
screen, a size, an id, a proof. Prefer a word the reader can point
|
|
19
|
+
at over an abstract one. When a word only makes sense on this
|
|
20
|
+
project, say what it is in the app or on disk (a Theme Studio
|
|
21
|
+
control, a package file name).
|
|
22
|
+
|
|
23
|
+
## What the reader gets
|
|
24
|
+
|
|
25
|
+
Lead with the answer. Put most of the reply on what they asked;
|
|
26
|
+
keep warnings short. For "what's wrong" or "what's missing",
|
|
27
|
+
answer in this order: what's wrong, what's missing, what follows.
|
|
28
|
+
For an explain request, give a short summary unless they ask for
|
|
29
|
+
more.
|
|
30
|
+
|
|
31
|
+
## Around the work
|
|
32
|
+
|
|
33
|
+
Before the first tool call, say in one sentence what you'll do.
|
|
34
|
+
While working, speak up only on an important find or a change of
|
|
35
|
+
course. When you finish, open with the result — what happened or
|
|
36
|
+
what you found — then add detail only if it helps.
|
|
37
|
+
|
|
38
|
+
## Job size
|
|
39
|
+
|
|
40
|
+
Do the job asked, at the size asked. Decide small things
|
|
41
|
+
yourself; ask only when two readings would change the work. When
|
|
42
|
+
the ask looks off or a better path is clear, say so in one
|
|
43
|
+
sentence, then do what was asked. Finish the whole job.
|
|
44
|
+
|
|
45
|
+
## Before sending
|
|
46
|
+
|
|
47
|
+
Read the reply as someone new to the project. Replace any word
|
|
48
|
+
that needs a glossary with the thing itself.
|
|
49
|
+
|
|
50
|
+
## Visual beats
|
|
51
|
+
|
|
52
|
+
Add a visual only when it earns its space beyond what the beats
|
|
53
|
+
already show on their own. See `reference/visual-beats.md` for the
|
|
54
|
+
shape picker, the `rich` setup, and a working example.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Visual beats
|
|
2
|
+
|
|
3
|
+
Use one visual per reply, at most, sitting between beats in place
|
|
4
|
+
of the beat it explains.
|
|
5
|
+
|
|
6
|
+
Build it with `rich` (`pip install rich`), then paste the printed
|
|
7
|
+
text into a fenced block. Set `no_color=True` and a fixed `width`
|
|
8
|
+
so the output holds its shape in a terminal and on GitHub.
|
|
9
|
+
|
|
10
|
+
Pick the shape by what the beat carries:
|
|
11
|
+
|
|
12
|
+
| Shape | Carries |
|
|
13
|
+
|---|---|
|
|
14
|
+
| `Panel` | one claim worth framing |
|
|
15
|
+
| `Table` | values a reader compares |
|
|
16
|
+
| `Tree` | a stack, or parts inside a whole |
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from rich.console import Console
|
|
20
|
+
from rich.table import Table
|
|
21
|
+
from rich import box
|
|
22
|
+
|
|
23
|
+
console = Console(width=64, no_color=True, force_terminal=False)
|
|
24
|
+
table = Table(box=box.SIMPLE_HEAVY, title="Slice budget")
|
|
25
|
+
table.add_column("slice")
|
|
26
|
+
table.add_column("lines", justify="right")
|
|
27
|
+
table.add_row("tests", "200")
|
|
28
|
+
console.print(table)
|
|
29
|
+
```
|