claude-dev-env 4.2.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/agents/pr-description-writer.md +2 -2
- package/.agents/agents/test_agent_frontmatter.py +2 -2
- package/.agents/agents/test_pr_description_writer_style.py +1 -3
- package/.agents/skills/e-code-review/SKILL.md +12 -12
- package/.agents/skills/e-code-review/preflight-proposal.contract.test.mjs +20 -0
- package/.agents/skills/e-code-review/reference/fix.md +15 -17
- package/.agents/skills/e-code-review/reference/loop.md +35 -39
- package/.agents/skills/e-code-review/reference/mode-contract.test.mjs +2 -1
- package/.agents/skills/e-code-review/reference/preflight-proposal.md +44 -9
- package/.agents/skills/e-code-review/reference/runner-selection.md +13 -14
- package/.agents/skills/issue-tracker/SKILL.md +7 -0
- package/.agents/skills/pull-request/SKILL.md +216 -0
- package/.agents/skills/pull-request/reference/publication-tasks.md +18 -0
- package/.agents/skills/pull-request/scripts/github_pr_command_constants/__init__.py +1 -0
- package/.agents/skills/pull-request/scripts/github_pr_command_constants/config/__init__.py +1 -0
- package/.agents/skills/pull-request/scripts/github_pr_command_constants/config/constants.py +69 -0
- package/.agents/skills/pull-request/scripts/pull_request.py +265 -0
- package/.agents/skills/pull-request/scripts/recover_legacy_author.py +251 -0
- package/.agents/skills/pull-request/scripts/test_github_pr_command_constants.py +58 -0
- package/.agents/skills/pull-request/scripts/test_pull_request.py +396 -0
- package/.agents/skills/pull-request/scripts/test_recover_legacy_author.py +221 -0
- package/.agents/skills/skill-builder/SKILL.md +3 -196
- package/.agents/skills/test-runner/SKILL.md +69 -0
- package/.agents/skills/test-runner/scripts/preflight_checks.py +365 -0
- package/.agents/skills/test-runner/scripts/run_tests.py +316 -0
- package/.agents/skills/test-runner/scripts/test_preflight_checks.py +185 -0
- package/.agents/skills/test-runner/scripts/test_run_tests.py +281 -0
- package/.agents/skills/test-runner/scripts/test_runner_constants/__init__.py +1 -0
- package/.agents/skills/test-runner/scripts/test_runner_constants/config/__init__.py +1 -0
- package/.agents/skills/test-runner/scripts/test_runner_constants/config/constants.py +96 -0
- package/.agents/skills-archived/pr-loop-cloud-transport/reference/identity-and-hooks.md +1 -1
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +6 -0
- package/_shared/pr-loop/scripts/terminology_sweep.py +64 -19
- package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +59 -0
- package/bin/ever-shipped-skills.mjs +2 -0
- package/bin/ever-shipped-skills.test.mjs +8 -0
- package/bin/install.agents-home.test.mjs +15 -13
- package/bin/install.mjs +37 -1
- package/bin/install.policy-lint.test.mjs +78 -0
- package/bin/install.prune.test.mjs +149 -20
- package/bin/install.pstack-plugin.test.mjs +114 -0
- package/bin/install.test.mjs +61 -7
- package/bin/refresh-pstack-plugin-skills.test.mjs +211 -0
- package/hooks/advisory/pr_done_reminder.py +342 -0
- package/hooks/advisory/test_pr_done_reminder.py +249 -0
- package/hooks/blocking/bash_post_call_dispatcher.py +53 -6
- package/hooks/blocking/code_rules_paired_test.py +22 -15
- package/hooks/blocking/pii_prevention_blocker.py +32 -51
- package/hooks/blocking/test_bash_post_call_dispatcher.py +55 -17
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +1 -4
- package/hooks/blocking/test_code_rules_command_dispatch.py +2 -2
- package/hooks/blocking/test_code_rules_enforcer_paired_test.py +36 -29
- package/hooks/blocking/test_code_rules_js_resume_task_enumeration.py +4 -4
- package/hooks/blocking/test_code_rules_js_returns_object_schemaless.py +5 -5
- package/hooks/blocking/test_pii_prevention_blocker.py +37 -89
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +68 -133
- package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +20 -51
- package/hooks/blocking/test_precommit_code_rules_gate_native_owner.py +18 -18
- package/hooks/gh_post_body_texts.py +195 -0
- package/hooks/git-hooks/git_hooks_constants/staged_policy_lint.py +12 -0
- package/hooks/git-hooks/pre_commit.py +52 -11
- package/hooks/git-hooks/test_installed_policy_lint.py +159 -0
- package/hooks/git-hooks/test_native_policy_commit.py +87 -0
- package/hooks/git-hooks/test_pre_commit.py +246 -0
- package/hooks/hooks.json +1 -16
- package/hooks/hooks_constants/bash_post_call_dispatcher_constants.py +14 -2
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +0 -8
- package/hooks/hooks_constants/gh_post_body_texts_constants.py +12 -0
- package/hooks/hooks_constants/pr_done_reminder_constants.py +98 -0
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +10 -43
- package/hooks/hooks_constants/test_bash_post_call_dispatcher_constants.py +17 -7
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +0 -7
- package/hooks/hooks_constants/test_failure_recorder_constants.py +3 -3
- package/hooks/hooks_constants/test_gh_post_body_texts_constants.py +19 -0
- package/hooks/hooks_constants/test_pr_done_reminder_constants.py +40 -0
- package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +20 -32
- package/hooks/observability/test_failure_recorder.py +1 -1
- package/hooks/pyproject.toml +1 -1
- package/hooks/test_gh_post_body_texts.py +79 -0
- package/package.json +2 -2
- package/rules/durable-post-artifacts.md +32 -20
- package/rules/gh-cli-conventions.md +23 -13
- package/rules/git-workflow.md +21 -8
- package/scripts/check.ps1 +0 -1
- package/{.agents/skills/run-claude-dev-env/driver.mjs → scripts/ci/installer-lifecycle.mjs} +3 -28
- package/scripts/ci/installer-lifecycle.test.mjs +26 -0
- package/scripts/ci/windows-installer-lifecycle.ps1 +1 -11
- package/scripts/dev_env_scripts_constants/shared_tree_constants.py +4 -5
- package/scripts/durable_post_lint.py +206 -0
- package/scripts/durable_post_lint_config/config/constants.py +96 -0
- package/scripts/durable_post_lint_config/config/test_constants.py +25 -0
- package/scripts/policy_lint/adapter_configuration.py +53 -12
- package/scripts/policy_lint/adapter_detectors.py +13 -6
- package/scripts/policy_lint/adapters.py +7 -4
- package/scripts/policy_lint/config/constants.py +5 -0
- package/scripts/policy_lint/engine.py +48 -4
- package/scripts/policy_lint/model.py +1 -0
- package/scripts/policy_lint/registry.py +1 -1
- package/scripts/policy_lint/selection.py +2 -1
- package/scripts/policy_lint/selection_git.py +2 -0
- package/scripts/refresh_pstack_plugin_skills.mjs +200 -0
- package/scripts/repository_checks/config/constants.py +0 -5
- package/scripts/shared_tree_paths.py +32 -3
- package/scripts/test_durable_post_lint.py +332 -0
- package/scripts/test_shared_tree_paths.py +33 -0
- package/scripts/tests/test_adapter_configuration.py +54 -0
- package/scripts/tests/test_policy_lint_rules_archive.py +120 -0
- package/scripts/tests/test_policy_lint_rules_engine.py +25 -4
- package/scripts/tests/test_policy_lint_rules_installed_loader.py +29 -0
- package/scripts/tests/test_policy_lint_rules_registry.py +227 -13
- package/scripts/tests/test_policy_lint_selection.py +25 -2
- package/scripts/tests/test_policy_lint_selection_native_index.py +30 -0
- package/scripts/tests/test_rules.py +3 -7
- package/.agents/skills/_shared/pr-loop/.claude/CLAUDE.md +0 -1
- package/.agents/skills/_shared/pr-loop/AGENTS.md +0 -1
- package/.agents/skills/_shared/pr-loop/audit-contract.md +0 -5
- package/.agents/skills/_shared/pr-loop/audit-reply-template.md +0 -5
- package/.agents/skills/_shared/pr-loop/code-rules-gate.md +0 -5
- package/.agents/skills/_shared/pr-loop/fix-protocol.md +0 -5
- package/.agents/skills/_shared/pr-loop/gh-payloads.md +0 -5
- package/.agents/skills/_shared/pr-loop/portable-driver.md +0 -149
- package/.agents/skills/_shared/pr-loop/post-audit-thread-contract.md +0 -5
- package/.agents/skills/_shared/pr-loop/precatch-rubric.md +0 -5
- package/.agents/skills/_shared/pr-loop/preflight-proposal.contract.test.mjs +0 -119
- package/.agents/skills/_shared/pr-loop/preflight-proposal.md +0 -5
- package/.agents/skills/_shared/pr-loop/prompts/.claude/CLAUDE.md +0 -1
- package/.agents/skills/_shared/pr-loop/prompts/AGENTS.md +0 -1
- package/.agents/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +0 -324
- package/.agents/skills/_shared/pr-loop/scripts/.claude/CLAUDE.md +0 -1
- package/.agents/skills/_shared/pr-loop/scripts/AGENTS.md +0 -1
- package/.agents/skills/_shared/pr-loop/scripts/RUNTIME_SCRIPTS.md +0 -29
- package/.agents/skills/_shared/pr-loop/scripts/_cli_utils.py +0 -22
- package/.agents/skills/_shared/pr-loop/scripts/_path_resolver.py +0 -186
- package/.agents/skills/_shared/pr-loop/scripts/_xml_utils.py +0 -20
- package/.agents/skills/_shared/pr-loop/scripts/audit_category_schema.py +0 -355
- package/.agents/skills/_shared/pr-loop/scripts/build_audit_prompt.py +0 -289
- package/.agents/skills/_shared/pr-loop/scripts/build_converge_task_list.py +0 -310
- package/.agents/skills/_shared/pr-loop/scripts/build_fix_prompt.py +0 -292
- package/.agents/skills/_shared/pr-loop/scripts/init_loop_state.py +0 -134
- package/.agents/skills/_shared/pr-loop/scripts/portable_converge_driver.py +0 -1646
- package/.agents/skills/_shared/pr-loop/scripts/preflight_worktree.py +0 -392
- package/.agents/skills/_shared/pr-loop/scripts/select_converge_pacer.py +0 -215
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/.claude/CLAUDE.md +0 -1
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/AGENTS.md +0 -1
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/__init__.py +0 -0
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py +0 -37
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/converge_task_list_constants.py +0 -56
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/handoff_constants.py +0 -45
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/pacer_constants.py +0 -47
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +0 -157
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/portable_driver_constants.py +0 -177
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/preflight_constants.py +0 -70
- package/.agents/skills/_shared/pr-loop/scripts/teardown_worktrees.py +0 -172
- package/.agents/skills/_shared/pr-loop/scripts/test__path_resolver.py +0 -106
- package/.agents/skills/_shared/pr-loop/scripts/test_audit_category_schema.py +0 -94
- package/.agents/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +0 -499
- package/.agents/skills/_shared/pr-loop/scripts/test_build_converge_task_list.py +0 -140
- package/.agents/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +0 -262
- package/.agents/skills/_shared/pr-loop/scripts/test_init_loop_state.py +0 -71
- package/.agents/skills/_shared/pr-loop/scripts/test_portable_converge_driver.py +0 -1119
- package/.agents/skills/_shared/pr-loop/scripts/test_preflight_worktree.py +0 -287
- package/.agents/skills/_shared/pr-loop/scripts/test_select_converge_pacer.py +0 -207
- package/.agents/skills/_shared/pr-loop/scripts/test_teardown_worktrees.py +0 -59
- package/.agents/skills/_shared/pr-loop/scripts/test_write_handoff.py +0 -201
- package/.agents/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +0 -182
- package/.agents/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +0 -206
- package/.agents/skills/_shared/pr-loop/scripts/write_handoff.py +0 -309
- package/.agents/skills/_shared/pr-loop/state-schema.md +0 -5
- package/.agents/skills/_shared/pr-loop/worker-spawn.md +0 -5
- package/.agents/skills/autoconverge/.claude/CLAUDE.md +0 -1
- package/.agents/skills/autoconverge/AGENTS.md +0 -1
- package/.agents/skills/autoconverge/SKILL.md +0 -431
- package/.agents/skills/autoconverge/reference/.claude/CLAUDE.md +0 -1
- package/.agents/skills/autoconverge/reference/AGENTS.md +0 -1
- package/.agents/skills/autoconverge/reference/closing-report.md +0 -93
- package/.agents/skills/autoconverge/reference/convergence-gates.md +0 -31
- package/.agents/skills/autoconverge/reference/convergence.md +0 -219
- package/.agents/skills/autoconverge/reference/copilot-findings.md +0 -51
- package/.agents/skills/autoconverge/reference/gotchas.md +0 -66
- package/.agents/skills/autoconverge/reference/headless-safety.md +0 -44
- package/.agents/skills/autoconverge/reference/multi-pr.md +0 -128
- package/.agents/skills/autoconverge/reference/self-closing-loop.md +0 -84
- package/.agents/skills/autoconverge/reference/stop-conditions.md +0 -127
- package/.agents/skills/autoconverge/test_portable_pacer_gate.py +0 -54
- package/.agents/skills/autoconverge/workflow/.claude/CLAUDE.md +0 -1
- package/.agents/skills/autoconverge/workflow/AGENTS.md +0 -1
- package/.agents/skills/autoconverge/workflow/aggregate_runs.py +0 -371
- package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/.claude/CLAUDE.md +0 -1
- package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/AGENTS.md +0 -1
- package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/__init__.py +0 -0
- package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +0 -250
- package/.agents/skills/autoconverge/workflow/converge.clean-audit.test.mjs +0 -665
- package/.agents/skills/autoconverge/workflow/converge.codex-gate.test.mjs +0 -472
- package/.agents/skills/autoconverge/workflow/converge.contract.test.mjs +0 -64
- package/.agents/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +0 -1009
- package/.agents/skills/autoconverge/workflow/converge.fix-progress.test.mjs +0 -107
- package/.agents/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +0 -33
- package/.agents/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +0 -15
- package/.agents/skills/autoconverge/workflow/converge.mjs +0 -3132
- package/.agents/skills/autoconverge/workflow/converge.p2-advance.test.mjs +0 -202
- package/.agents/skills/autoconverge/workflow/converge.path-aware.test.mjs +0 -47
- package/.agents/skills/autoconverge/workflow/converge.precatch.test.mjs +0 -152
- package/.agents/skills/autoconverge/workflow/converge.run-input.test.mjs +0 -116
- package/.agents/skills/autoconverge/workflow/converge_multi.mjs +0 -169
- package/.agents/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +0 -173
- package/.agents/skills/autoconverge/workflow/convergence_summary.py +0 -110
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a11d903476b803493.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a26213978adeef6fb.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a3def0d15ed9d9110.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a41f41b1b708ee3b7.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a758b880abecc3ff7.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a8897b89656b1bd16.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ab1c2d3e4f5a6b7c8.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-abd463d744a1437bc.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ad19d027ae8ee1816.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +0 -266
- package/.agents/skills/autoconverge/workflow/render_report.py +0 -1024
- package/.agents/skills/autoconverge/workflow/test_aggregate_runs.py +0 -134
- package/.agents/skills/autoconverge/workflow/test_convergence_summary.py +0 -149
- package/.agents/skills/autoconverge/workflow/test_render_report.py +0 -812
- package/.agents/skills/hitl/SKILL.md +0 -45
- package/.agents/skills/pr-cleanup/SKILL.md +0 -140
- package/.agents/skills/pr-cleanup/reference/process-inventory.md +0 -17
- package/.agents/skills/pr-cleanup/reference/task-seeds.md +0 -22
- package/.agents/skills/pr-name-by-capability/SKILL.md +0 -73
- package/.agents/skills/pr-name-by-capability/reference/fetch-commands.md +0 -29
- package/.agents/skills/pr-name-by-capability/reference/offense-examples.md +0 -32
- package/.agents/skills/pr-name-by-capability/reference/report-template.md +0 -29
- package/.agents/skills/pr-name-by-capability/reference/rule-checklist.md +0 -38
- package/.agents/skills/pr-name-by-capability/reference/task-seeds.md +0 -10
- package/.agents/skills/pr-plain-language-cleanup/SKILL.md +0 -93
- package/.agents/skills/pr-refinement/SKILL.md +0 -31
- package/.agents/skills/pr-shared-extraction/SKILL.md +0 -177
- package/.agents/skills/pr-shared-extraction/reference/examples.md +0 -46
- package/.agents/skills/pr-shared-extraction/reference/offense-taxonomy.md +0 -100
- package/.agents/skills/pr-small-cl/SKILL.md +0 -52
- package/.agents/skills/pr-title-description/SKILL.md +0 -96
- package/.agents/skills/prototype/SKILL.md +0 -85
- package/.agents/skills/prototype/reference/honest-limitations.md +0 -23
- package/.agents/skills/prototype/reference/promotion-tasks.md +0 -23
- package/.agents/skills/prototype/scripts/build_sandbox_settings.py +0 -249
- package/.agents/skills/prototype/scripts/conftest.py +0 -15
- package/.agents/skills/prototype/scripts/launch_sandbox.py +0 -205
- package/.agents/skills/prototype/scripts/probe_sandbox_safety.py +0 -311
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/__init__.py +0 -1
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/__init__.py +0 -0
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/build_sandbox_settings_constants.py +0 -41
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/launch_sandbox_constants.py +0 -23
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/probe_sandbox_safety_constants.py +0 -45
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/prototype_common_constants.py +0 -10
- package/.agents/skills/prototype/scripts/test_build_sandbox_settings.py +0 -275
- package/.agents/skills/prototype/scripts/test_launch_sandbox.py +0 -303
- package/.agents/skills/prototype/scripts/test_probe_sandbox_safety.py +0 -312
- package/.agents/skills/prototype/workflows/promotion.md +0 -25
- package/.agents/skills/prototype/workflows/sandbox.md +0 -35
- package/.agents/skills/rebase/.claude/CLAUDE.md +0 -1
- package/.agents/skills/rebase/AGENTS.md +0 -1
- package/.agents/skills/rebase/SKILL.md +0 -178
- package/.agents/skills/review-router/SKILL.md +0 -51
- package/.agents/skills/review-router/reference/route-policy.json +0 -7
- package/.agents/skills/review-router/reference/task-seeds.md +0 -1
- package/.agents/skills/review-router/scripts/conftest.py +0 -4
- package/.agents/skills/review-router/scripts/review_router.py +0 -50
- package/.agents/skills/review-router/scripts/review_router_cli.py +0 -211
- package/.agents/skills/review-router/scripts/review_router_constants/__init__.py +0 -1
- package/.agents/skills/review-router/scripts/review_router_constants/config/__init__.py +0 -0
- package/.agents/skills/review-router/scripts/review_router_constants/config/constants.py +0 -13
- package/.agents/skills/review-router/scripts/test_review_router.py +0 -23
- package/.agents/skills/review-router/scripts/test_review_router_cli.py +0 -47
- package/.agents/skills/review-tier/SKILL.md +0 -56
- package/.agents/skills/review-tier/reference/task-seeds.md +0 -1
- package/.agents/skills/review-tier/reference/tier-policy.json +0 -9
- package/.agents/skills/review-tier/scripts/conftest.py +0 -4
- package/.agents/skills/review-tier/scripts/review_tier.py +0 -359
- package/.agents/skills/review-tier/scripts/review_tier_constants/__init__.py +0 -1
- package/.agents/skills/review-tier/scripts/review_tier_constants/config/__init__.py +0 -0
- package/.agents/skills/review-tier/scripts/review_tier_constants/config/constants.py +0 -29
- package/.agents/skills/review-tier/scripts/test_generation.py +0 -159
- package/.agents/skills/review-tier/scripts/test_review_tier.py +0 -33
- package/.agents/skills/run-claude-dev-env/SKILL.md +0 -84
- package/.agents/skills/session-log/.claude/CLAUDE.md +0 -1
- package/.agents/skills/session-log/AGENTS.md +0 -1
- package/.agents/skills/session-log/SKILL.md +0 -170
- package/.agents/skills/session-log/templates/frontmatter.md +0 -40
- package/.agents/skills/session-tidy/.claude/CLAUDE.md +0 -1
- package/.agents/skills/session-tidy/AGENTS.md +0 -1
- package/.agents/skills/session-tidy/SKILL.md +0 -191
- package/.agents/skills/skill-builder/references/.claude/CLAUDE.md +0 -1
- package/.agents/skills/skill-builder/references/AGENTS.md +0 -1
- package/.agents/skills/skill-builder/references/delegation-map.md +0 -130
- package/.agents/skills/skill-builder/references/description-field.md +0 -111
- package/.agents/skills/skill-builder/references/deterministic-elements.md +0 -218
- package/.agents/skills/skill-builder/references/progressive-disclosure.md +0 -122
- package/.agents/skills/skill-builder/references/self-audit-checklist.md +0 -126
- package/.agents/skills/skill-builder/references/skill-modularity.md +0 -111
- package/.agents/skills/skill-builder/references/skill-types.md +0 -232
- package/.agents/skills/skill-builder/references/thariq-x-post-skills.json +0 -33
- package/.agents/skills/skill-builder/templates/.claude/CLAUDE.md +0 -1
- package/.agents/skills/skill-builder/templates/AGENTS.md +0 -1
- package/.agents/skills/skill-builder/templates/gap-analysis.md +0 -87
- package/.agents/skills/skill-builder/workflows/.claude/CLAUDE.md +0 -1
- package/.agents/skills/skill-builder/workflows/AGENTS.md +0 -1
- package/.agents/skills/skill-builder/workflows/improve-skill.md +0 -136
- package/.agents/skills/skill-builder/workflows/new-skill.md +0 -179
- package/.agents/skills/skill-builder/workflows/polish-skill.md +0 -139
- package/.agents/skills/source-command-sr-loop/SKILL.md +0 -82
- package/.agents/skills/update/.claude/CLAUDE.md +0 -1
- package/.agents/skills/update/AGENTS.md +0 -1
- package/.agents/skills/update/SKILL.md +0 -190
- package/_shared/pr-loop/AGENTS.md +0 -1
- package/_shared/pr-loop/CLAUDE.md +0 -1
- package/_shared/pr-loop/audit-contract.md +0 -183
- package/_shared/pr-loop/audit-reply-template.md +0 -151
- package/_shared/pr-loop/code-rules-gate.md +0 -65
- package/_shared/pr-loop/fix-protocol.md +0 -60
- package/_shared/pr-loop/gh-payloads.md +0 -75
- package/_shared/pr-loop/post-audit-thread-contract.md +0 -51
- package/_shared/pr-loop/precatch-rubric.md +0 -66
- package/_shared/pr-loop/preflight-proposal.md +0 -56
- package/_shared/pr-loop/scripts/post_audit_thread.py +0 -1242
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/post_audit_thread_constants.py +0 -199
- package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +0 -1115
- package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +0 -129
- package/_shared/pr-loop/state-schema.md +0 -45
- package/_shared/pr-loop/worker-spawn.md +0 -189
- package/hooks/_gh_pr_author_swap_utils.py +0 -1225
- package/hooks/blocking/conventional_pr_title_gate.py +0 -443
- package/hooks/blocking/gh_body_arg_blocker.py +0 -176
- package/hooks/blocking/gh_pr_author_enforcer.py +0 -487
- package/hooks/blocking/gh_pr_author_restore.py +0 -105
- package/hooks/blocking/pr_description_writer_gate.py +0 -256
- package/hooks/blocking/test_conventional_pr_title_gate.py +0 -640
- package/hooks/blocking/test_gh_body_arg_blocker.py +0 -441
- package/hooks/blocking/test_gh_pr_author_enforcer.py +0 -1166
- package/hooks/blocking/test_gh_pr_author_restore.py +0 -513
- package/hooks/blocking/test_gh_pr_author_swap_utils.py +0 -910
- package/hooks/blocking/test_pr_description_writer_gate.py +0 -234
- package/hooks/blocking/test_preflight_check.py +0 -343
- package/hooks/blocking/test_test_preflight_check.py +0 -80
- package/hooks/blocking/test_volatile_path_in_post_blocker.py +0 -322
- package/hooks/blocking/volatile_path_in_post_blocker.py +0 -461
- package/hooks/hooks_constants/conventional_pr_title_gate_constants.py +0 -58
- package/hooks/hooks_constants/gh_pr_author_swap_constants.py +0 -76
- package/hooks/hooks_constants/pr_description_writer_gate_constants.py +0 -73
- package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +0 -55
- package/hooks/observability/pr_description_writer_spawn_tracker.py +0 -113
- package/hooks/observability/test_pr_description_writer_spawn_tracker.py +0 -150
- package/hooks/session/gh_pr_author_session_cleanup.py +0 -175
- package/hooks/session/test_gh_pr_author_session_cleanup.py +0 -576
- package/hooks/test__gh_pr_author_swap_utils.py +0 -333
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import test from 'node:test';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const CI_DIRECTORY = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const DRIVER_PATH = join(CI_DIRECTORY, 'installer-lifecycle.mjs');
|
|
10
|
+
const DRIVER_TIMEOUT_MILLISECONDS = 60_000;
|
|
11
|
+
|
|
12
|
+
test('the installer lifecycle driver completes in an isolated home', () => {
|
|
13
|
+
const lifecycleProcess = spawnSync(process.execPath, [DRIVER_PATH], {
|
|
14
|
+
encoding: 'utf8',
|
|
15
|
+
timeout: DRIVER_TIMEOUT_MILLISECONDS,
|
|
16
|
+
});
|
|
17
|
+
assert.equal(lifecycleProcess.status, 0, `${lifecycleProcess.stdout}\n${lifecycleProcess.stderr}`);
|
|
18
|
+
assert.match(lifecycleProcess.stdout, /ALL CHECKS PASSED/);
|
|
19
|
+
assert.match(lifecycleProcess.stdout, /Sandbox removed:/);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('the Windows adapter invokes the CI-owned lifecycle driver', () => {
|
|
23
|
+
const adapterSource = readFileSync(join(CI_DIRECTORY, 'windows-installer-lifecycle.ps1'), 'utf8');
|
|
24
|
+
assert.match(adapterSource, /\$driverPath = Join-Path \$PSScriptRoot 'installer-lifecycle\.mjs'/);
|
|
25
|
+
assert.doesNotMatch(adapterSource, /skills\\run-claude-dev-env/);
|
|
26
|
+
});
|
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
# Windows CI adapter for the Node installer lifecycle driver.
|
|
2
|
-
#
|
|
3
|
-
# The driver already redirects HOME, USERPROFILE, and GIT_CONFIG_GLOBAL into a
|
|
4
|
-
# throwaway sandbox. This adapter records that isolation contract, runs the
|
|
5
|
-
# 16-check driver, and writes bounded evidence under -EvidenceRoot so a failed
|
|
6
|
-
# Windows runner leaves an actionable artifact.
|
|
7
|
-
#
|
|
8
|
-
# Usage (from packages/claude-dev-env):
|
|
9
|
-
# pwsh -NoProfile -File scripts/ci/windows-installer-lifecycle.ps1 -EvidenceRoot <dir>
|
|
10
|
-
|
|
11
1
|
[CmdletBinding()]
|
|
12
2
|
param(
|
|
13
3
|
[Parameter(Mandatory = $true)]
|
|
@@ -17,7 +7,7 @@ param(
|
|
|
17
7
|
$ErrorActionPreference = 'Stop'
|
|
18
8
|
|
|
19
9
|
$packageRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
|
|
20
|
-
$driverPath = Join-Path $
|
|
10
|
+
$driverPath = Join-Path $PSScriptRoot 'installer-lifecycle.mjs'
|
|
21
11
|
$allIsolationEnvNames = @(
|
|
22
12
|
'HOME',
|
|
23
13
|
'USERPROFILE',
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
These name the shape of the installed tree itself, not any one worker or
|
|
4
|
-
skill that reads it, so every caller of shared_tree_paths shares them.
|
|
5
|
-
"""
|
|
1
|
+
"""Directory names for the installed shared tree."""
|
|
6
2
|
|
|
7
3
|
from __future__ import annotations
|
|
8
4
|
|
|
@@ -15,3 +11,6 @@ SCRIPTS_DIRECTORY_NAME: str = "scripts"
|
|
|
15
11
|
PROCESS_TREE_DIRECTORY_NAME: str = "process-tree"
|
|
16
12
|
|
|
17
13
|
PROCESS_TREE_KILL_MODULE_FILENAME: str = "process_tree_kill.py"
|
|
14
|
+
|
|
15
|
+
AGENTS_DIRECTORY_SUFFIX: str = ".agents"
|
|
16
|
+
DEFAULT_MANAGED_ROOT_NAME: str = ".claude"
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""Validate durable GitHub post inputs before publication."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
from collections.abc import Sequence
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from durable_post_lint_config.config.constants import (
|
|
12
|
+
ACTION_PR_CREATE,
|
|
13
|
+
ACTION_PR_EDIT,
|
|
14
|
+
ALL_BARE_VOLATILE_PATH_MARKERS,
|
|
15
|
+
ALL_BODY_REQUIRED_ACTIONS,
|
|
16
|
+
ALL_PATH_ANCHORED_VOLATILE_PATH_MARKERS,
|
|
17
|
+
ALL_POST_ACTIONS,
|
|
18
|
+
ALL_PR_DESCRIPTION_ACTIONS,
|
|
19
|
+
ALL_REQUIRED_PR_DESCRIPTION_HEADINGS,
|
|
20
|
+
ALL_TITLE_ACTIONS,
|
|
21
|
+
BODY_FILE_ENCODING,
|
|
22
|
+
BODY_FILE_UNREADABLE_MESSAGE,
|
|
23
|
+
BODY_MUST_NOT_BE_EMPTY_MESSAGE,
|
|
24
|
+
BODY_REQUIRED_MESSAGE,
|
|
25
|
+
CLEAN_EXIT_CODE,
|
|
26
|
+
CONTENT_FINDING_EXIT_CODE,
|
|
27
|
+
CONVENTIONAL_TITLE_PATTERN,
|
|
28
|
+
EDIT_INPUT_REQUIRED_MESSAGE,
|
|
29
|
+
EMPTY_BODY_CODE,
|
|
30
|
+
INPUT_ERROR_EXIT_CODE,
|
|
31
|
+
INVALID_ACTION_MESSAGE,
|
|
32
|
+
INVALID_TITLE_CODE,
|
|
33
|
+
INVALID_TITLE_MESSAGE,
|
|
34
|
+
MISSING_HEADING_CODE,
|
|
35
|
+
MISSING_HEADING_MESSAGE_TEMPLATE,
|
|
36
|
+
PATH_ANCHOR_CHARACTER,
|
|
37
|
+
PATH_SEGMENT_START_CHARACTERS,
|
|
38
|
+
TITLE_NOT_ALLOWED_MESSAGE,
|
|
39
|
+
TITLE_REQUIRED_MESSAGE,
|
|
40
|
+
VOLATILE_PATH_CODE,
|
|
41
|
+
VOLATILE_PATH_MESSAGE,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class DurablePostUsageError(ValueError):
|
|
46
|
+
"""Report an invalid GitHub post request shape."""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class DurablePostInputError(OSError):
|
|
50
|
+
"""Report an unreadable GitHub post input file."""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True)
|
|
54
|
+
class DurablePostFinding:
|
|
55
|
+
"""One GitHub post content finding."""
|
|
56
|
+
|
|
57
|
+
code: str
|
|
58
|
+
message: str
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _character_starts_path_segment(character: str) -> bool:
|
|
62
|
+
return bool(character) and (
|
|
63
|
+
character.isalnum() or character in PATH_SEGMENT_START_CHARACTERS
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _text_has_anchored_marker(normalized_text: str, marker: str) -> bool:
|
|
68
|
+
search_start = 0
|
|
69
|
+
while True:
|
|
70
|
+
found_index = normalized_text.find(marker, search_start)
|
|
71
|
+
if found_index < 0:
|
|
72
|
+
return False
|
|
73
|
+
preceding_character = normalized_text[found_index - 1 : found_index]
|
|
74
|
+
following_index = found_index + len(marker)
|
|
75
|
+
following_character = normalized_text[following_index : following_index + 1]
|
|
76
|
+
if (
|
|
77
|
+
preceding_character == PATH_ANCHOR_CHARACTER
|
|
78
|
+
or _character_starts_path_segment(following_character)
|
|
79
|
+
):
|
|
80
|
+
return True
|
|
81
|
+
search_start = found_index + 1
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def find_volatile_path_marker(body_text: str) -> str | None:
|
|
85
|
+
"""Return the first volatile path marker in a GitHub post body."""
|
|
86
|
+
normalized_text = body_text.replace("\\", PATH_ANCHOR_CHARACTER).lower()
|
|
87
|
+
for each_marker in ALL_PATH_ANCHORED_VOLATILE_PATH_MARKERS:
|
|
88
|
+
if _text_has_anchored_marker(normalized_text, each_marker):
|
|
89
|
+
return each_marker
|
|
90
|
+
for each_marker in ALL_BARE_VOLATILE_PATH_MARKERS:
|
|
91
|
+
if each_marker in normalized_text:
|
|
92
|
+
return each_marker
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def read_body_file(body_file: Path) -> str:
|
|
97
|
+
"""Read one UTF-8 body file without exposing its contents on failure."""
|
|
98
|
+
try:
|
|
99
|
+
return body_file.read_text(encoding=BODY_FILE_ENCODING)
|
|
100
|
+
except (OSError, UnicodeError) as error:
|
|
101
|
+
raise DurablePostInputError(BODY_FILE_UNREADABLE_MESSAGE) from error
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _validate_request_shape(
|
|
105
|
+
action: str,
|
|
106
|
+
title: str | None,
|
|
107
|
+
body_text: str | None,
|
|
108
|
+
) -> None:
|
|
109
|
+
if action not in ALL_POST_ACTIONS:
|
|
110
|
+
raise DurablePostUsageError(INVALID_ACTION_MESSAGE)
|
|
111
|
+
if action in ALL_BODY_REQUIRED_ACTIONS and body_text is None:
|
|
112
|
+
raise DurablePostUsageError(BODY_REQUIRED_MESSAGE)
|
|
113
|
+
if action == ACTION_PR_CREATE and not title:
|
|
114
|
+
raise DurablePostUsageError(TITLE_REQUIRED_MESSAGE)
|
|
115
|
+
if action == ACTION_PR_EDIT and title is None and body_text is None:
|
|
116
|
+
raise DurablePostUsageError(EDIT_INPUT_REQUIRED_MESSAGE)
|
|
117
|
+
if title is not None and action not in ALL_TITLE_ACTIONS:
|
|
118
|
+
raise DurablePostUsageError(TITLE_NOT_ALLOWED_MESSAGE)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _description_heading_findings(body_text: str) -> list[DurablePostFinding]:
|
|
122
|
+
all_body_lines = {each_line.strip() for each_line in body_text.splitlines()}
|
|
123
|
+
return [
|
|
124
|
+
DurablePostFinding(
|
|
125
|
+
code=MISSING_HEADING_CODE,
|
|
126
|
+
message=MISSING_HEADING_MESSAGE_TEMPLATE.format(heading=each_heading),
|
|
127
|
+
)
|
|
128
|
+
for each_heading in ALL_REQUIRED_PR_DESCRIPTION_HEADINGS
|
|
129
|
+
if f"## {each_heading}" not in all_body_lines
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _title_findings(title: str | None) -> list[DurablePostFinding]:
|
|
134
|
+
if title is None or CONVENTIONAL_TITLE_PATTERN.fullmatch(title) is not None:
|
|
135
|
+
return []
|
|
136
|
+
return [DurablePostFinding(code=INVALID_TITLE_CODE, message=INVALID_TITLE_MESSAGE)]
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _body_findings(
|
|
140
|
+
action: str,
|
|
141
|
+
body_text: str | None,
|
|
142
|
+
) -> list[DurablePostFinding]:
|
|
143
|
+
if body_text is None:
|
|
144
|
+
return []
|
|
145
|
+
all_findings: list[DurablePostFinding] = []
|
|
146
|
+
if not body_text.strip():
|
|
147
|
+
all_findings.append(
|
|
148
|
+
DurablePostFinding(
|
|
149
|
+
code=EMPTY_BODY_CODE,
|
|
150
|
+
message=BODY_MUST_NOT_BE_EMPTY_MESSAGE,
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
if action in ALL_PR_DESCRIPTION_ACTIONS:
|
|
154
|
+
all_findings.extend(_description_heading_findings(body_text))
|
|
155
|
+
if find_volatile_path_marker(body_text) is not None:
|
|
156
|
+
all_findings.append(
|
|
157
|
+
DurablePostFinding(
|
|
158
|
+
code=VOLATILE_PATH_CODE,
|
|
159
|
+
message=VOLATILE_PATH_MESSAGE,
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
return all_findings
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def lint_durable_post(
|
|
166
|
+
action: str,
|
|
167
|
+
title: str | None,
|
|
168
|
+
body_text: str | None,
|
|
169
|
+
) -> tuple[DurablePostFinding, ...]:
|
|
170
|
+
"""Return content findings for one locally valid GitHub post request."""
|
|
171
|
+
_validate_request_shape(action, title, body_text)
|
|
172
|
+
return (*_title_findings(title), *_body_findings(action, body_text))
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _parse_arguments(all_arguments: Sequence[str]) -> argparse.Namespace:
|
|
176
|
+
parser = argparse.ArgumentParser(description=__doc__, allow_abbrev=False)
|
|
177
|
+
parser.add_argument("--action", required=True)
|
|
178
|
+
parser.add_argument("--title")
|
|
179
|
+
parser.add_argument("--body-file", type=Path)
|
|
180
|
+
return parser.parse_args(list(all_arguments))
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def main(all_arguments: Sequence[str]) -> int:
|
|
184
|
+
"""Validate one GitHub post and return its result status."""
|
|
185
|
+
arguments = _parse_arguments(all_arguments)
|
|
186
|
+
try:
|
|
187
|
+
body_text = (
|
|
188
|
+
read_body_file(arguments.body_file)
|
|
189
|
+
if arguments.body_file is not None
|
|
190
|
+
else None
|
|
191
|
+
)
|
|
192
|
+
all_findings = lint_durable_post(
|
|
193
|
+
action=arguments.action,
|
|
194
|
+
title=arguments.title,
|
|
195
|
+
body_text=body_text,
|
|
196
|
+
)
|
|
197
|
+
except (DurablePostInputError, DurablePostUsageError) as error:
|
|
198
|
+
sys.stderr.write(f"{error}\n")
|
|
199
|
+
return INPUT_ERROR_EXIT_CODE
|
|
200
|
+
for each_finding in all_findings:
|
|
201
|
+
sys.stderr.write(f"{each_finding.message}\n")
|
|
202
|
+
return CONTENT_FINDING_EXIT_CODE if all_findings else CLEAN_EXIT_CODE
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
if __name__ == "__main__":
|
|
206
|
+
raise SystemExit(main(sys.argv[1:]))
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Configuration constants for durable GitHub post validation."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
ACTION_GITHUB_MCP_POST = "github-mcp-post"
|
|
6
|
+
ACTION_ISSUE_COMMENT = "issue-comment"
|
|
7
|
+
ACTION_ISSUE_CREATE = "issue-create"
|
|
8
|
+
ACTION_ISSUE_EDIT = "issue-edit"
|
|
9
|
+
ACTION_PR_COMMENT = "pr-comment"
|
|
10
|
+
ACTION_PR_CREATE = "pr-create"
|
|
11
|
+
ACTION_PR_EDIT = "pr-edit"
|
|
12
|
+
ACTION_PR_REVIEW = "pr-review"
|
|
13
|
+
|
|
14
|
+
ALL_POST_ACTIONS: frozenset[str] = frozenset(
|
|
15
|
+
{
|
|
16
|
+
ACTION_GITHUB_MCP_POST,
|
|
17
|
+
ACTION_ISSUE_COMMENT,
|
|
18
|
+
ACTION_ISSUE_CREATE,
|
|
19
|
+
ACTION_ISSUE_EDIT,
|
|
20
|
+
ACTION_PR_COMMENT,
|
|
21
|
+
ACTION_PR_CREATE,
|
|
22
|
+
ACTION_PR_EDIT,
|
|
23
|
+
ACTION_PR_REVIEW,
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
ALL_BODY_REQUIRED_ACTIONS: frozenset[str] = frozenset(
|
|
27
|
+
{
|
|
28
|
+
ACTION_GITHUB_MCP_POST,
|
|
29
|
+
ACTION_ISSUE_COMMENT,
|
|
30
|
+
ACTION_ISSUE_CREATE,
|
|
31
|
+
ACTION_ISSUE_EDIT,
|
|
32
|
+
ACTION_PR_COMMENT,
|
|
33
|
+
ACTION_PR_CREATE,
|
|
34
|
+
ACTION_PR_REVIEW,
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
ALL_PR_DESCRIPTION_ACTIONS: frozenset[str] = frozenset(
|
|
38
|
+
{ACTION_PR_CREATE, ACTION_PR_EDIT}
|
|
39
|
+
)
|
|
40
|
+
ALL_TITLE_ACTIONS: frozenset[str] = frozenset({ACTION_PR_CREATE, ACTION_PR_EDIT})
|
|
41
|
+
ALL_CONVENTIONAL_TITLE_TYPES: tuple[str, ...] = (
|
|
42
|
+
"build",
|
|
43
|
+
"chore",
|
|
44
|
+
"ci",
|
|
45
|
+
"docs",
|
|
46
|
+
"feat",
|
|
47
|
+
"fix",
|
|
48
|
+
"perf",
|
|
49
|
+
"refactor",
|
|
50
|
+
"revert",
|
|
51
|
+
"style",
|
|
52
|
+
"test",
|
|
53
|
+
)
|
|
54
|
+
CONVENTIONAL_TITLE_PATTERN: re.Pattern[str] = re.compile(
|
|
55
|
+
rf"^(?:{'|'.join(ALL_CONVENTIONAL_TITLE_TYPES)})(?:\([^)\r\n]+\))?!?: [^\s].*$"
|
|
56
|
+
)
|
|
57
|
+
ALL_REQUIRED_PR_DESCRIPTION_HEADINGS: tuple[str, ...] = (
|
|
58
|
+
"Summary",
|
|
59
|
+
"Description",
|
|
60
|
+
"Why",
|
|
61
|
+
"How",
|
|
62
|
+
"Verification",
|
|
63
|
+
)
|
|
64
|
+
ALL_PATH_ANCHORED_VOLATILE_PATH_MARKERS: tuple[str, ...] = (
|
|
65
|
+
".claude-profile-a/jobs/",
|
|
66
|
+
".claude/worktrees/",
|
|
67
|
+
)
|
|
68
|
+
ALL_BARE_VOLATILE_PATH_MARKERS: tuple[str, ...] = (
|
|
69
|
+
"appdata/local/temp",
|
|
70
|
+
"/tmp/",
|
|
71
|
+
"%temp%",
|
|
72
|
+
"$env:temp",
|
|
73
|
+
"$claude_job_dir",
|
|
74
|
+
)
|
|
75
|
+
BODY_FILE_ENCODING = "utf-8"
|
|
76
|
+
CLEAN_EXIT_CODE = 0
|
|
77
|
+
CONTENT_FINDING_EXIT_CODE = 1
|
|
78
|
+
EMPTY_BODY_CODE = "empty-body"
|
|
79
|
+
INPUT_ERROR_EXIT_CODE = 2
|
|
80
|
+
PATH_ANCHOR_CHARACTER = "/"
|
|
81
|
+
PATH_SEGMENT_START_CHARACTERS = "_-"
|
|
82
|
+
BODY_FILE_UNREADABLE_MESSAGE = "body file is not readable UTF-8 text"
|
|
83
|
+
BODY_REQUIRED_MESSAGE = "this action requires a body file"
|
|
84
|
+
BODY_MUST_NOT_BE_EMPTY_MESSAGE = "body file must contain text"
|
|
85
|
+
EDIT_INPUT_REQUIRED_MESSAGE = "pr-edit requires a title or body file"
|
|
86
|
+
INVALID_ACTION_MESSAGE = "unsupported durable post action"
|
|
87
|
+
INVALID_TITLE_CODE = "invalid-pr-title"
|
|
88
|
+
INVALID_TITLE_MESSAGE = (
|
|
89
|
+
"pull request title does not use the repository Conventional Commit form"
|
|
90
|
+
)
|
|
91
|
+
MISSING_HEADING_CODE = "missing-pr-description-heading"
|
|
92
|
+
MISSING_HEADING_MESSAGE_TEMPLATE = "body is missing required heading: {heading}"
|
|
93
|
+
TITLE_NOT_ALLOWED_MESSAGE = "this action does not accept a title"
|
|
94
|
+
TITLE_REQUIRED_MESSAGE = "pr-create requires a title"
|
|
95
|
+
VOLATILE_PATH_CODE = "volatile-local-path"
|
|
96
|
+
VOLATILE_PATH_MESSAGE = "body contains a volatile local artifact path"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Tests for durable GitHub post linter constants."""
|
|
2
|
+
|
|
3
|
+
from durable_post_lint_config.config.constants import (
|
|
4
|
+
ALL_CONVENTIONAL_TITLE_TYPES,
|
|
5
|
+
ALL_POST_ACTIONS,
|
|
6
|
+
CONVENTIONAL_TITLE_PATTERN,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_action_inventory_covers_pull_request_issue_and_tool_posts() -> None:
|
|
11
|
+
assert ALL_POST_ACTIONS == {
|
|
12
|
+
"github-mcp-post",
|
|
13
|
+
"issue-comment",
|
|
14
|
+
"issue-create",
|
|
15
|
+
"issue-edit",
|
|
16
|
+
"pr-comment",
|
|
17
|
+
"pr-create",
|
|
18
|
+
"pr-edit",
|
|
19
|
+
"pr-review",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_title_pattern_uses_every_configured_type() -> None:
|
|
24
|
+
for each_title_type in ALL_CONVENTIONAL_TITLE_TYPES:
|
|
25
|
+
assert CONVENTIONAL_TITLE_PATTERN.fullmatch(f"{each_title_type}: one change")
|
|
@@ -23,30 +23,69 @@ def hook_configuration_diagnostics(
|
|
|
23
23
|
adapter_support._document_path(repository_root, document)
|
|
24
24
|
if document.path.name != "hooks.json":
|
|
25
25
|
return ()
|
|
26
|
-
all_messages = _hook_configuration_messages(document.text)
|
|
26
|
+
all_messages = _hook_configuration_messages(document.text, document.prior_text)
|
|
27
27
|
return adapter_support._diagnostics_for_messages(
|
|
28
28
|
document, "hook-configuration", all_messages
|
|
29
29
|
)
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
def
|
|
32
|
+
def _parsed_configuration(
|
|
33
|
+
configuration_text: str,
|
|
34
|
+
invalid_json_message: str,
|
|
35
|
+
invalid_object_message: str,
|
|
36
|
+
) -> dict[object, object]:
|
|
33
37
|
try:
|
|
34
38
|
parsed_configuration = json.loads(configuration_text)
|
|
35
|
-
except json.JSONDecodeError:
|
|
36
|
-
|
|
39
|
+
except json.JSONDecodeError as error:
|
|
40
|
+
raise ValueError(invalid_json_message) from error
|
|
37
41
|
if not isinstance(parsed_configuration, dict):
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
raise TypeError(invalid_object_message)
|
|
43
|
+
return parsed_configuration
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _action_boundary_strings(
|
|
47
|
+
all_configuration_entries: dict[object, object],
|
|
48
|
+
) -> frozenset[str]:
|
|
49
|
+
return frozenset(
|
|
50
|
+
each_string
|
|
51
|
+
for each_string in _registered_hook_strings(all_configuration_entries)
|
|
52
|
+
if _contains_action_boundary_marker(each_string)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _hook_configuration_messages(
|
|
57
|
+
configuration_text: str,
|
|
58
|
+
prior_configuration_text: str | None,
|
|
59
|
+
) -> tuple[str, ...]:
|
|
60
|
+
try:
|
|
61
|
+
current_configuration = _parsed_configuration(
|
|
62
|
+
configuration_text,
|
|
63
|
+
"Hook configuration is not valid JSON",
|
|
64
|
+
"Hook configuration must be an object",
|
|
65
|
+
)
|
|
66
|
+
all_current_action_boundary_strings = _action_boundary_strings(
|
|
67
|
+
current_configuration
|
|
68
|
+
)
|
|
69
|
+
if prior_configuration_text is None:
|
|
70
|
+
all_prior_action_boundary_strings: frozenset[str] = frozenset()
|
|
71
|
+
else:
|
|
72
|
+
prior_configuration = _parsed_configuration(
|
|
73
|
+
prior_configuration_text,
|
|
74
|
+
"Prior hook configuration is not valid JSON",
|
|
75
|
+
"Prior hook configuration must be an object",
|
|
76
|
+
)
|
|
77
|
+
all_prior_action_boundary_strings = _action_boundary_strings(
|
|
78
|
+
prior_configuration
|
|
79
|
+
)
|
|
80
|
+
except (TypeError, ValueError) as error:
|
|
81
|
+
return (str(error),)
|
|
82
|
+
if all_current_action_boundary_strings - all_prior_action_boundary_strings:
|
|
44
83
|
return ("Hook configuration registers a deny, block, or ask policy path",)
|
|
45
84
|
return ()
|
|
46
85
|
|
|
47
86
|
|
|
48
87
|
def _registered_hook_strings(
|
|
49
|
-
all_configuration_entries: dict[object, object]
|
|
88
|
+
all_configuration_entries: dict[object, object],
|
|
50
89
|
) -> tuple[str, ...]:
|
|
51
90
|
all_registered_strings: list[str] = []
|
|
52
91
|
for each_key, each_entry in all_configuration_entries.items():
|
|
@@ -62,7 +101,9 @@ def _registered_strings_for_entry(
|
|
|
62
101
|
all_registered_strings: list[str] = []
|
|
63
102
|
if each_key in constants.ALL_HOOK_REGISTRATION_KEYS and isinstance(each_entry, str):
|
|
64
103
|
all_registered_strings.append(each_entry)
|
|
65
|
-
if each_key in constants.ALL_DECISION_REGISTRATION_KEYS and isinstance(
|
|
104
|
+
if each_key in constants.ALL_DECISION_REGISTRATION_KEYS and isinstance(
|
|
105
|
+
each_entry, str
|
|
106
|
+
):
|
|
66
107
|
all_registered_strings.append(each_entry)
|
|
67
108
|
all_registered_strings.extend(_registered_nested_strings(each_entry))
|
|
68
109
|
return tuple(all_registered_strings)
|
|
@@ -29,24 +29,31 @@ def terminology_diagnostics(
|
|
|
29
29
|
document_set: DocumentSet,
|
|
30
30
|
load_module: adapter_support.HookModuleLoader,
|
|
31
31
|
) -> tuple[Diagnostic, ...]:
|
|
32
|
-
"""Report
|
|
32
|
+
"""Report prose that near-misses a newly introduced identifier.
|
|
33
33
|
|
|
34
34
|
Args:
|
|
35
|
-
document_set:
|
|
35
|
+
document_set: Staged or base change selection and repository root.
|
|
36
36
|
load_module: Shared script module loader.
|
|
37
37
|
|
|
38
38
|
Returns:
|
|
39
39
|
Diagnostics that name the prose file and line.
|
|
40
40
|
"""
|
|
41
|
-
|
|
41
|
+
all_arguments: tuple[Path | str, ...]
|
|
42
|
+
if document_set.selection is SelectionKind.BASE:
|
|
43
|
+
if document_set.base_revision is None:
|
|
44
|
+
raise ValueError("Base selection is missing its comparison revision")
|
|
45
|
+
checker_name = "strict_base_terminology_findings"
|
|
46
|
+
all_arguments = (document_set.repository_root, document_set.base_revision)
|
|
47
|
+
elif document_set.selection is SelectionKind.STAGED:
|
|
48
|
+
checker_name = "strict_staged_terminology_findings"
|
|
49
|
+
all_arguments = (document_set.repository_root,)
|
|
50
|
+
else:
|
|
42
51
|
return ()
|
|
43
52
|
terminology_module = load_module(constants.TERMINOLOGY_SWEEP_MODULE_NAME)
|
|
44
53
|
helper_stdout = StringIO()
|
|
45
54
|
helper_stderr = StringIO()
|
|
46
55
|
with redirect_stdout(helper_stdout), redirect_stderr(helper_stderr):
|
|
47
|
-
all_findings = terminology_module
|
|
48
|
-
document_set.repository_root
|
|
49
|
-
)
|
|
56
|
+
all_findings = getattr(terminology_module, checker_name)(*all_arguments)
|
|
50
57
|
return tuple(_terminology_diagnostic(each_finding) for each_finding in all_findings)
|
|
51
58
|
|
|
52
59
|
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import importlib
|
|
4
|
+
import os
|
|
4
5
|
import sys
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from types import ModuleType
|
|
7
8
|
|
|
9
|
+
from shared_tree_paths import resolve_shared_scripts_directory
|
|
10
|
+
|
|
8
11
|
from . import adapter_configuration, adapter_detectors, adapter_pairing, adapter_support
|
|
9
12
|
from .config import constants
|
|
10
13
|
from .model import Diagnostic, Document, DocumentSet
|
|
@@ -24,10 +27,10 @@ def _hooks_module(module_name: str) -> ModuleType:
|
|
|
24
27
|
|
|
25
28
|
|
|
26
29
|
def _pr_loop_script_module(module_name: str) -> ModuleType:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
)
|
|
30
|
+
scripts_directory = str(resolve_shared_scripts_directory(
|
|
31
|
+
__file__, os.environ, constants.PR_LOOP_DIRECTORY_NAME,
|
|
32
|
+
f"{module_name}{constants.PYTHON_SUFFIX}", constants.SHARED_ROOT_PARENT_INDEX
|
|
33
|
+
))
|
|
31
34
|
if scripts_directory not in sys.path:
|
|
32
35
|
sys.path.insert(0, scripts_directory)
|
|
33
36
|
return importlib.import_module(module_name)
|
|
@@ -5,6 +5,7 @@ INVALID_INPUT_EXIT_CODE = 2
|
|
|
5
5
|
GIT_EXECUTABLE = "git"
|
|
6
6
|
UTF8_ENCODING = "utf-8"
|
|
7
7
|
PATH_SEPARATOR = "/"
|
|
8
|
+
ARCHIVED_SKILLS_DIRECTORY_NAME = "skill-archive"
|
|
8
9
|
NUL_BYTE = b"\0"
|
|
9
10
|
ALL_GIT_ROOT_ARGUMENTS = ("rev-parse", "--show-toplevel")
|
|
10
11
|
ALL_GIT_HEAD_ARGUMENTS = ("rev-parse", "--verify", "HEAD")
|
|
@@ -13,6 +14,7 @@ GIT_MERGE_BASE_ARGUMENT = "merge-base"
|
|
|
13
14
|
GIT_HEAD_REFERENCE = "HEAD"
|
|
14
15
|
GIT_INDEX_REFERENCE_PREFIX = ""
|
|
15
16
|
GIT_ENVIRONMENT_VARIABLE_PREFIX = "GIT_"
|
|
17
|
+
GIT_INDEX_ENVIRONMENT_VARIABLE = "GIT_INDEX_FILE"
|
|
16
18
|
ALL_GIT_BLOB_ARGUMENTS = ("cat-file", "blob")
|
|
17
19
|
ALL_MISSING_BLOB_ERROR_FRAGMENTS = (
|
|
18
20
|
"does not exist in",
|
|
@@ -75,3 +77,6 @@ TERMINOLOGY_FINDING_PATTERN = re.compile(
|
|
|
75
77
|
TERMINOLOGY_PATH_GROUP = "path"
|
|
76
78
|
TERMINOLOGY_LINE_NUMBER_GROUP = "line_number"
|
|
77
79
|
TERMINOLOGY_MESSAGE_GROUP = "message"
|
|
80
|
+
|
|
81
|
+
PR_LOOP_DIRECTORY_NAME = "pr-loop"
|
|
82
|
+
SHARED_ROOT_PARENT_INDEX = 2
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from collections.abc import Iterable, Sequence
|
|
4
|
-
from pathlib import Path
|
|
4
|
+
from pathlib import Path, PurePosixPath
|
|
5
5
|
|
|
6
|
+
from .config.constants import ARCHIVED_SKILLS_DIRECTORY_NAME
|
|
6
7
|
from .model import (
|
|
7
8
|
ChangeSetRule,
|
|
8
9
|
Diagnostic,
|
|
@@ -25,6 +26,9 @@ def lint(
|
|
|
25
26
|
) -> LintReport:
|
|
26
27
|
"""Run the selected rules on one source selection.
|
|
27
28
|
|
|
29
|
+
Repository and diff selections check active code. File and editor
|
|
30
|
+
selections include archived skills.
|
|
31
|
+
|
|
28
32
|
Args:
|
|
29
33
|
request: Repository root, source, and rule sets.
|
|
30
34
|
all_registry: Optional registry replacement.
|
|
@@ -32,10 +36,14 @@ def lint(
|
|
|
32
36
|
Returns:
|
|
33
37
|
A report of diagnostics and rule execution state.
|
|
34
38
|
"""
|
|
35
|
-
document_set = select_documents(request)
|
|
36
|
-
candidate_rules =
|
|
39
|
+
document_set = _runtime_document_set(select_documents(request))
|
|
40
|
+
candidate_rules = (
|
|
41
|
+
tuple(all_registry) if all_registry is not None else default_registry()
|
|
42
|
+
)
|
|
37
43
|
selected = selected_rules(candidate_rules, request.rule_sets)
|
|
38
|
-
all_diagnostics, executed, failed, skipped = _run_selected_rules(
|
|
44
|
+
all_diagnostics, executed, failed, skipped = _run_selected_rules(
|
|
45
|
+
selected, document_set
|
|
46
|
+
)
|
|
39
47
|
return LintReport(
|
|
40
48
|
1,
|
|
41
49
|
tuple(sorted(all_diagnostics, key=_diagnostic_sort_key)),
|
|
@@ -46,6 +54,42 @@ def lint(
|
|
|
46
54
|
)
|
|
47
55
|
|
|
48
56
|
|
|
57
|
+
def _is_runtime_path(path: PurePosixPath) -> bool:
|
|
58
|
+
return path.parts[0] != ARCHIVED_SKILLS_DIRECTORY_NAME
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _runtime_document_set(document_set: DocumentSet) -> DocumentSet:
|
|
62
|
+
if document_set.selection in {SelectionKind.FILES, SelectionKind.TEXT}:
|
|
63
|
+
return document_set
|
|
64
|
+
all_documents = tuple(
|
|
65
|
+
each_document
|
|
66
|
+
for each_document in document_set.documents
|
|
67
|
+
if _is_runtime_path(each_document.path)
|
|
68
|
+
)
|
|
69
|
+
all_deleted_paths = tuple(
|
|
70
|
+
each_path
|
|
71
|
+
for each_path in document_set.deleted_paths
|
|
72
|
+
if _is_runtime_path(each_path)
|
|
73
|
+
) + tuple(
|
|
74
|
+
old_path
|
|
75
|
+
for old_path, new_path in document_set.renamed_paths
|
|
76
|
+
if _is_runtime_path(old_path) and not _is_runtime_path(new_path)
|
|
77
|
+
)
|
|
78
|
+
all_renamed_paths = tuple(
|
|
79
|
+
each_pair
|
|
80
|
+
for each_pair in document_set.renamed_paths
|
|
81
|
+
if _is_runtime_path(each_pair[1])
|
|
82
|
+
)
|
|
83
|
+
return DocumentSet(
|
|
84
|
+
all_documents,
|
|
85
|
+
document_set.selection,
|
|
86
|
+
document_set.repository_root,
|
|
87
|
+
all_deleted_paths,
|
|
88
|
+
all_renamed_paths,
|
|
89
|
+
document_set.base_revision,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
49
93
|
def _run_selected_rules(
|
|
50
94
|
all_rules: tuple[Rule, ...], document_set: DocumentSet
|
|
51
95
|
) -> tuple[list[Diagnostic], tuple[str, ...], tuple[str, ...], tuple[str, ...]]:
|
|
@@ -141,7 +141,7 @@ def default_registry() -> tuple[model.Rule, ...]:
|
|
|
141
141
|
model.ChangeSetRule(
|
|
142
142
|
"terminology-sweep",
|
|
143
143
|
frozenset({"changed"}),
|
|
144
|
-
frozenset({model.SelectionKind.STAGED}),
|
|
144
|
+
frozenset({model.SelectionKind.STAGED, model.SelectionKind.BASE}),
|
|
145
145
|
adapters.terminology_diagnostics,
|
|
146
146
|
),
|
|
147
147
|
model.RepositoryRule(
|