claude-dev-env 4.3.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/agents/pr-description-writer.md +2 -2
- package/.agents/agents/test_agent_frontmatter.py +2 -2
- package/.agents/agents/test_pr_description_writer_style.py +1 -3
- package/.agents/skills/e-code-review/SKILL.md +12 -12
- package/.agents/skills/e-code-review/preflight-proposal.contract.test.mjs +20 -0
- package/.agents/skills/e-code-review/reference/fix.md +15 -17
- package/.agents/skills/e-code-review/reference/loop.md +35 -39
- package/.agents/skills/e-code-review/reference/mode-contract.test.mjs +2 -1
- package/.agents/skills/e-code-review/reference/preflight-proposal.md +44 -9
- package/.agents/skills/e-code-review/reference/runner-selection.md +13 -14
- package/.agents/skills/issue-tracker/SKILL.md +5 -4
- package/.agents/skills/pull-request/SKILL.md +11 -13
- package/.agents/skills/pull-request/reference/publication-tasks.md +3 -3
- package/.agents/skills/pull-request/scripts/github_pr_command_constants/config/constants.py +9 -1
- package/.agents/skills/pull-request/scripts/pull_request.py +33 -44
- package/.agents/skills/pull-request/scripts/test_github_pr_command_constants.py +9 -0
- package/.agents/skills/pull-request/scripts/test_pull_request.py +1 -3
- package/.agents/skills/skill-builder/SKILL.md +3 -196
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +6 -0
- package/_shared/pr-loop/scripts/terminology_sweep.py +64 -19
- package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +59 -0
- package/bin/install.agents-home.test.mjs +15 -13
- package/bin/install.mjs +28 -0
- package/bin/install.policy-lint.test.mjs +78 -0
- package/bin/install.prune.test.mjs +59 -20
- package/bin/install.pstack-plugin.test.mjs +114 -0
- package/bin/install.test.mjs +37 -0
- package/bin/refresh-pstack-plugin-skills.test.mjs +211 -0
- package/hooks/advisory/pr_done_reminder.py +342 -0
- package/hooks/advisory/test_pr_done_reminder.py +249 -0
- package/hooks/blocking/bash_post_call_dispatcher.py +53 -6
- package/hooks/blocking/code_rules_paired_test.py +22 -15
- package/hooks/blocking/test_bash_post_call_dispatcher.py +65 -4
- package/hooks/blocking/test_code_rules_enforcer_paired_test.py +36 -29
- package/hooks/blocking/test_code_rules_js_resume_task_enumeration.py +4 -4
- package/hooks/blocking/test_code_rules_js_returns_object_schemaless.py +5 -5
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +68 -133
- package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +20 -51
- package/hooks/blocking/test_precommit_code_rules_gate_native_owner.py +18 -18
- package/hooks/git-hooks/git_hooks_constants/staged_policy_lint.py +12 -0
- package/hooks/git-hooks/pre_commit.py +52 -11
- package/hooks/git-hooks/test_installed_policy_lint.py +159 -0
- package/hooks/git-hooks/test_native_policy_commit.py +87 -0
- package/hooks/git-hooks/test_pre_commit.py +246 -0
- package/hooks/hooks.json +1 -1
- package/hooks/hooks_constants/bash_post_call_dispatcher_constants.py +14 -1
- package/hooks/hooks_constants/pr_done_reminder_constants.py +98 -0
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +10 -43
- package/hooks/hooks_constants/test_bash_post_call_dispatcher_constants.py +19 -2
- package/hooks/hooks_constants/test_pr_done_reminder_constants.py +40 -0
- package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +20 -32
- package/package.json +2 -2
- package/rules/durable-post-artifacts.md +2 -1
- package/rules/git-workflow.md +6 -6
- package/scripts/check.ps1 +0 -1
- package/{.agents/skills/run-claude-dev-env/driver.mjs → scripts/ci/installer-lifecycle.mjs} +3 -28
- package/scripts/ci/installer-lifecycle.test.mjs +26 -0
- package/scripts/ci/windows-installer-lifecycle.ps1 +1 -11
- package/scripts/dev_env_scripts_constants/shared_tree_constants.py +4 -5
- package/{.agents/skills/_shared/pr-loop/scripts → scripts}/durable_post_lint.py +5 -41
- package/{.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/durable_post_lint_constants.py → scripts/durable_post_lint_config/config/constants.py} +0 -10
- package/{.agents/skills/_shared/pr-loop/scripts/test_durable_post_lint_constants.py → scripts/durable_post_lint_config/config/test_constants.py} +1 -1
- package/scripts/policy_lint/adapter_detectors.py +13 -6
- package/scripts/policy_lint/adapters.py +7 -4
- package/scripts/policy_lint/config/constants.py +5 -0
- package/scripts/policy_lint/engine.py +48 -4
- package/scripts/policy_lint/model.py +1 -0
- package/scripts/policy_lint/registry.py +1 -1
- package/scripts/policy_lint/selection.py +2 -1
- package/scripts/policy_lint/selection_git.py +2 -0
- package/scripts/refresh_pstack_plugin_skills.mjs +200 -0
- package/scripts/shared_tree_paths.py +32 -3
- package/scripts/test_shared_tree_paths.py +33 -0
- package/scripts/tests/test_policy_lint_rules_archive.py +120 -0
- package/scripts/tests/test_policy_lint_rules_engine.py +25 -4
- package/scripts/tests/test_policy_lint_rules_installed_loader.py +29 -0
- package/scripts/tests/test_policy_lint_rules_registry.py +227 -13
- package/scripts/tests/test_policy_lint_selection.py +25 -2
- package/scripts/tests/test_policy_lint_selection_native_index.py +30 -0
- package/scripts/tests/test_rules.py +3 -7
- package/.agents/skills/_shared/pr-loop/.claude/CLAUDE.md +0 -1
- package/.agents/skills/_shared/pr-loop/AGENTS.md +0 -1
- package/.agents/skills/_shared/pr-loop/audit-contract.md +0 -5
- package/.agents/skills/_shared/pr-loop/audit-reply-template.md +0 -5
- package/.agents/skills/_shared/pr-loop/code-rules-gate.md +0 -5
- package/.agents/skills/_shared/pr-loop/fix-protocol.md +0 -5
- package/.agents/skills/_shared/pr-loop/gh-payloads.md +0 -5
- package/.agents/skills/_shared/pr-loop/portable-driver.md +0 -149
- package/.agents/skills/_shared/pr-loop/post-audit-thread-contract.md +0 -5
- package/.agents/skills/_shared/pr-loop/precatch-rubric.md +0 -5
- package/.agents/skills/_shared/pr-loop/preflight-proposal.contract.test.mjs +0 -119
- package/.agents/skills/_shared/pr-loop/preflight-proposal.md +0 -5
- package/.agents/skills/_shared/pr-loop/prompts/.claude/CLAUDE.md +0 -1
- package/.agents/skills/_shared/pr-loop/prompts/AGENTS.md +0 -1
- package/.agents/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +0 -324
- package/.agents/skills/_shared/pr-loop/scripts/.claude/CLAUDE.md +0 -1
- package/.agents/skills/_shared/pr-loop/scripts/AGENTS.md +0 -1
- package/.agents/skills/_shared/pr-loop/scripts/RUNTIME_SCRIPTS.md +0 -29
- package/.agents/skills/_shared/pr-loop/scripts/_cli_utils.py +0 -22
- package/.agents/skills/_shared/pr-loop/scripts/_path_resolver.py +0 -186
- package/.agents/skills/_shared/pr-loop/scripts/_xml_utils.py +0 -20
- package/.agents/skills/_shared/pr-loop/scripts/audit_category_schema.py +0 -355
- package/.agents/skills/_shared/pr-loop/scripts/build_audit_prompt.py +0 -289
- package/.agents/skills/_shared/pr-loop/scripts/build_converge_task_list.py +0 -310
- package/.agents/skills/_shared/pr-loop/scripts/build_fix_prompt.py +0 -292
- package/.agents/skills/_shared/pr-loop/scripts/init_loop_state.py +0 -134
- package/.agents/skills/_shared/pr-loop/scripts/portable_converge_driver.py +0 -1646
- package/.agents/skills/_shared/pr-loop/scripts/preflight_worktree.py +0 -392
- package/.agents/skills/_shared/pr-loop/scripts/select_converge_pacer.py +0 -215
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/.claude/CLAUDE.md +0 -1
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/AGENTS.md +0 -1
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/__init__.py +0 -0
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py +0 -37
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/converge_task_list_constants.py +0 -56
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/handoff_constants.py +0 -45
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/pacer_constants.py +0 -47
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +0 -157
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/portable_driver_constants.py +0 -177
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/preflight_constants.py +0 -70
- package/.agents/skills/_shared/pr-loop/scripts/teardown_worktrees.py +0 -172
- package/.agents/skills/_shared/pr-loop/scripts/test__path_resolver.py +0 -106
- package/.agents/skills/_shared/pr-loop/scripts/test_audit_category_schema.py +0 -94
- package/.agents/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +0 -499
- package/.agents/skills/_shared/pr-loop/scripts/test_build_converge_task_list.py +0 -140
- package/.agents/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +0 -262
- package/.agents/skills/_shared/pr-loop/scripts/test_init_loop_state.py +0 -71
- package/.agents/skills/_shared/pr-loop/scripts/test_portable_converge_driver.py +0 -1119
- package/.agents/skills/_shared/pr-loop/scripts/test_preflight_worktree.py +0 -287
- package/.agents/skills/_shared/pr-loop/scripts/test_select_converge_pacer.py +0 -207
- package/.agents/skills/_shared/pr-loop/scripts/test_teardown_worktrees.py +0 -59
- package/.agents/skills/_shared/pr-loop/scripts/test_write_handoff.py +0 -201
- package/.agents/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +0 -182
- package/.agents/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +0 -206
- package/.agents/skills/_shared/pr-loop/scripts/write_handoff.py +0 -309
- package/.agents/skills/_shared/pr-loop/state-schema.md +0 -5
- package/.agents/skills/_shared/pr-loop/worker-spawn.md +0 -5
- package/.agents/skills/autoconverge/.claude/CLAUDE.md +0 -1
- package/.agents/skills/autoconverge/AGENTS.md +0 -1
- package/.agents/skills/autoconverge/SKILL.md +0 -431
- package/.agents/skills/autoconverge/reference/.claude/CLAUDE.md +0 -1
- package/.agents/skills/autoconverge/reference/AGENTS.md +0 -1
- package/.agents/skills/autoconverge/reference/closing-report.md +0 -93
- package/.agents/skills/autoconverge/reference/convergence-gates.md +0 -31
- package/.agents/skills/autoconverge/reference/convergence.md +0 -219
- package/.agents/skills/autoconverge/reference/copilot-findings.md +0 -51
- package/.agents/skills/autoconverge/reference/gotchas.md +0 -66
- package/.agents/skills/autoconverge/reference/headless-safety.md +0 -44
- package/.agents/skills/autoconverge/reference/multi-pr.md +0 -128
- package/.agents/skills/autoconverge/reference/self-closing-loop.md +0 -84
- package/.agents/skills/autoconverge/reference/stop-conditions.md +0 -127
- package/.agents/skills/autoconverge/test_portable_pacer_gate.py +0 -54
- package/.agents/skills/autoconverge/workflow/.claude/CLAUDE.md +0 -1
- package/.agents/skills/autoconverge/workflow/AGENTS.md +0 -1
- package/.agents/skills/autoconverge/workflow/aggregate_runs.py +0 -371
- package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/.claude/CLAUDE.md +0 -1
- package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/AGENTS.md +0 -1
- package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/__init__.py +0 -0
- package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +0 -250
- package/.agents/skills/autoconverge/workflow/converge.clean-audit.test.mjs +0 -665
- package/.agents/skills/autoconverge/workflow/converge.codex-gate.test.mjs +0 -472
- package/.agents/skills/autoconverge/workflow/converge.contract.test.mjs +0 -64
- package/.agents/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +0 -1009
- package/.agents/skills/autoconverge/workflow/converge.fix-progress.test.mjs +0 -107
- package/.agents/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +0 -33
- package/.agents/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +0 -15
- package/.agents/skills/autoconverge/workflow/converge.mjs +0 -3132
- package/.agents/skills/autoconverge/workflow/converge.p2-advance.test.mjs +0 -202
- package/.agents/skills/autoconverge/workflow/converge.path-aware.test.mjs +0 -47
- package/.agents/skills/autoconverge/workflow/converge.precatch.test.mjs +0 -152
- package/.agents/skills/autoconverge/workflow/converge.run-input.test.mjs +0 -116
- package/.agents/skills/autoconverge/workflow/converge_multi.mjs +0 -169
- package/.agents/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +0 -173
- package/.agents/skills/autoconverge/workflow/convergence_summary.py +0 -110
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a11d903476b803493.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a26213978adeef6fb.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a3def0d15ed9d9110.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a41f41b1b708ee3b7.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a758b880abecc3ff7.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a8897b89656b1bd16.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ab1c2d3e4f5a6b7c8.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-abd463d744a1437bc.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ad19d027ae8ee1816.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +0 -266
- package/.agents/skills/autoconverge/workflow/render_report.py +0 -1024
- package/.agents/skills/autoconverge/workflow/test_aggregate_runs.py +0 -134
- package/.agents/skills/autoconverge/workflow/test_convergence_summary.py +0 -149
- package/.agents/skills/autoconverge/workflow/test_render_report.py +0 -812
- package/.agents/skills/hitl/SKILL.md +0 -45
- package/.agents/skills/pr-cleanup/SKILL.md +0 -140
- package/.agents/skills/pr-cleanup/reference/process-inventory.md +0 -17
- package/.agents/skills/pr-cleanup/reference/task-seeds.md +0 -22
- package/.agents/skills/pr-name-by-capability/SKILL.md +0 -73
- package/.agents/skills/pr-name-by-capability/reference/fetch-commands.md +0 -29
- package/.agents/skills/pr-name-by-capability/reference/offense-examples.md +0 -32
- package/.agents/skills/pr-name-by-capability/reference/report-template.md +0 -29
- package/.agents/skills/pr-name-by-capability/reference/rule-checklist.md +0 -38
- package/.agents/skills/pr-name-by-capability/reference/task-seeds.md +0 -10
- package/.agents/skills/pr-plain-language-cleanup/SKILL.md +0 -93
- package/.agents/skills/pr-refinement/SKILL.md +0 -31
- package/.agents/skills/pr-shared-extraction/SKILL.md +0 -177
- package/.agents/skills/pr-shared-extraction/reference/examples.md +0 -46
- package/.agents/skills/pr-shared-extraction/reference/offense-taxonomy.md +0 -100
- package/.agents/skills/pr-small-cl/SKILL.md +0 -52
- package/.agents/skills/pr-title-description/SKILL.md +0 -96
- package/.agents/skills/prototype/SKILL.md +0 -85
- package/.agents/skills/prototype/reference/honest-limitations.md +0 -23
- package/.agents/skills/prototype/reference/promotion-tasks.md +0 -23
- package/.agents/skills/prototype/scripts/build_sandbox_settings.py +0 -249
- package/.agents/skills/prototype/scripts/conftest.py +0 -15
- package/.agents/skills/prototype/scripts/launch_sandbox.py +0 -205
- package/.agents/skills/prototype/scripts/probe_sandbox_safety.py +0 -311
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/__init__.py +0 -1
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/__init__.py +0 -0
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/build_sandbox_settings_constants.py +0 -41
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/launch_sandbox_constants.py +0 -23
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/probe_sandbox_safety_constants.py +0 -45
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/prototype_common_constants.py +0 -10
- package/.agents/skills/prototype/scripts/test_build_sandbox_settings.py +0 -275
- package/.agents/skills/prototype/scripts/test_launch_sandbox.py +0 -303
- package/.agents/skills/prototype/scripts/test_probe_sandbox_safety.py +0 -312
- package/.agents/skills/prototype/workflows/promotion.md +0 -25
- package/.agents/skills/prototype/workflows/sandbox.md +0 -35
- package/.agents/skills/rebase/.claude/CLAUDE.md +0 -1
- package/.agents/skills/rebase/AGENTS.md +0 -1
- package/.agents/skills/rebase/SKILL.md +0 -178
- package/.agents/skills/review-router/SKILL.md +0 -51
- package/.agents/skills/review-router/reference/route-policy.json +0 -7
- package/.agents/skills/review-router/reference/task-seeds.md +0 -1
- package/.agents/skills/review-router/scripts/conftest.py +0 -4
- package/.agents/skills/review-router/scripts/review_router.py +0 -50
- package/.agents/skills/review-router/scripts/review_router_cli.py +0 -211
- package/.agents/skills/review-router/scripts/review_router_constants/__init__.py +0 -1
- package/.agents/skills/review-router/scripts/review_router_constants/config/__init__.py +0 -0
- package/.agents/skills/review-router/scripts/review_router_constants/config/constants.py +0 -13
- package/.agents/skills/review-router/scripts/test_review_router.py +0 -23
- package/.agents/skills/review-router/scripts/test_review_router_cli.py +0 -47
- package/.agents/skills/review-tier/SKILL.md +0 -56
- package/.agents/skills/review-tier/reference/task-seeds.md +0 -1
- package/.agents/skills/review-tier/reference/tier-policy.json +0 -9
- package/.agents/skills/review-tier/scripts/conftest.py +0 -4
- package/.agents/skills/review-tier/scripts/review_tier.py +0 -359
- package/.agents/skills/review-tier/scripts/review_tier_constants/__init__.py +0 -1
- package/.agents/skills/review-tier/scripts/review_tier_constants/config/__init__.py +0 -0
- package/.agents/skills/review-tier/scripts/review_tier_constants/config/constants.py +0 -29
- package/.agents/skills/review-tier/scripts/test_generation.py +0 -159
- package/.agents/skills/review-tier/scripts/test_review_tier.py +0 -33
- package/.agents/skills/run-claude-dev-env/SKILL.md +0 -84
- package/.agents/skills/session-log/.claude/CLAUDE.md +0 -1
- package/.agents/skills/session-log/AGENTS.md +0 -1
- package/.agents/skills/session-log/SKILL.md +0 -170
- package/.agents/skills/session-log/templates/frontmatter.md +0 -40
- package/.agents/skills/session-tidy/.claude/CLAUDE.md +0 -1
- package/.agents/skills/session-tidy/AGENTS.md +0 -1
- package/.agents/skills/session-tidy/SKILL.md +0 -191
- package/.agents/skills/skill-builder/references/.claude/CLAUDE.md +0 -1
- package/.agents/skills/skill-builder/references/AGENTS.md +0 -1
- package/.agents/skills/skill-builder/references/delegation-map.md +0 -130
- package/.agents/skills/skill-builder/references/description-field.md +0 -111
- package/.agents/skills/skill-builder/references/deterministic-elements.md +0 -218
- package/.agents/skills/skill-builder/references/progressive-disclosure.md +0 -122
- package/.agents/skills/skill-builder/references/self-audit-checklist.md +0 -126
- package/.agents/skills/skill-builder/references/skill-modularity.md +0 -111
- package/.agents/skills/skill-builder/references/skill-types.md +0 -232
- package/.agents/skills/skill-builder/references/thariq-x-post-skills.json +0 -33
- package/.agents/skills/skill-builder/templates/.claude/CLAUDE.md +0 -1
- package/.agents/skills/skill-builder/templates/AGENTS.md +0 -1
- package/.agents/skills/skill-builder/templates/gap-analysis.md +0 -87
- package/.agents/skills/skill-builder/workflows/.claude/CLAUDE.md +0 -1
- package/.agents/skills/skill-builder/workflows/AGENTS.md +0 -1
- package/.agents/skills/skill-builder/workflows/improve-skill.md +0 -136
- package/.agents/skills/skill-builder/workflows/new-skill.md +0 -179
- package/.agents/skills/skill-builder/workflows/polish-skill.md +0 -139
- package/.agents/skills/source-command-sr-loop/SKILL.md +0 -82
- package/.agents/skills/update/.claude/CLAUDE.md +0 -1
- package/.agents/skills/update/AGENTS.md +0 -1
- package/.agents/skills/update/SKILL.md +0 -190
- package/_shared/pr-loop/AGENTS.md +0 -1
- package/_shared/pr-loop/CLAUDE.md +0 -1
- package/_shared/pr-loop/audit-contract.md +0 -183
- package/_shared/pr-loop/audit-reply-template.md +0 -151
- package/_shared/pr-loop/code-rules-gate.md +0 -65
- package/_shared/pr-loop/fix-protocol.md +0 -60
- package/_shared/pr-loop/gh-payloads.md +0 -75
- package/_shared/pr-loop/post-audit-thread-contract.md +0 -51
- package/_shared/pr-loop/precatch-rubric.md +0 -66
- package/_shared/pr-loop/preflight-proposal.md +0 -56
- package/_shared/pr-loop/scripts/post_audit_thread.py +0 -1242
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/post_audit_thread_constants.py +0 -199
- package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +0 -1115
- package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +0 -129
- package/_shared/pr-loop/state-schema.md +0 -45
- package/_shared/pr-loop/worker-spawn.md +0 -189
- /package/{.agents/skills/_shared/pr-loop/scripts → scripts}/test_durable_post_lint.py +0 -0
|
@@ -1,472 +0,0 @@
|
|
|
1
|
-
import { test } from 'node:test';
|
|
2
|
-
import { strict as assert } from 'node:assert';
|
|
3
|
-
import { readFileSync } from 'node:fs';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { dirname, join } from 'node:path';
|
|
6
|
-
|
|
7
|
-
const workflowDirectory = dirname(fileURLToPath(import.meta.url));
|
|
8
|
-
const convergeSource = readFileSync(join(workflowDirectory, 'converge.mjs'), 'utf8');
|
|
9
|
-
const skillSource = readFileSync(join(workflowDirectory, '..', 'SKILL.md'), 'utf8');
|
|
10
|
-
const stopConditionsSource = readFileSync(
|
|
11
|
-
join(workflowDirectory, '..', 'reference', 'stop-conditions.md'),
|
|
12
|
-
'utf8',
|
|
13
|
-
);
|
|
14
|
-
|
|
15
|
-
function functionBody(functionName) {
|
|
16
|
-
const functionStart = convergeSource.indexOf(`function ${functionName}(`);
|
|
17
|
-
assert.notEqual(functionStart, -1, `expected ${functionName} to exist`);
|
|
18
|
-
const nextFunctionStart = convergeSource.indexOf('\nfunction ', functionStart + 1);
|
|
19
|
-
const functionEnd = nextFunctionStart === -1 ? convergeSource.length : nextFunctionStart;
|
|
20
|
-
return convergeSource.slice(functionStart, functionEnd);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const productionModule = new Function(
|
|
24
|
-
`${functionBody('classifyCodexGateOutcome')}\n` +
|
|
25
|
-
'return { classifyCodexGateOutcome };',
|
|
26
|
-
)();
|
|
27
|
-
const { classifyCodexGateOutcome } = productionModule;
|
|
28
|
-
|
|
29
|
-
function codexResult(overrides) {
|
|
30
|
-
return {
|
|
31
|
-
sha: 'abcdef0',
|
|
32
|
-
clean: false,
|
|
33
|
-
down: false,
|
|
34
|
-
skipped: false,
|
|
35
|
-
skipReason: '',
|
|
36
|
-
findings: [],
|
|
37
|
-
...overrides,
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function codexFinding(overrides) {
|
|
42
|
-
return {
|
|
43
|
-
file: 'a.py',
|
|
44
|
-
line: 1,
|
|
45
|
-
severity: 'P1',
|
|
46
|
-
category: 'bug',
|
|
47
|
-
title: 't',
|
|
48
|
-
detail: 'd',
|
|
49
|
-
replyToCommentId: null,
|
|
50
|
-
...overrides,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
test('a required clean Codex result routes to clean so the run stamps codex-clean HEAD', () => {
|
|
55
|
-
const outcome = classifyCodexGateOutcome(codexResult({ clean: true }));
|
|
56
|
-
assert.equal(outcome.kind, 'clean');
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test('an opt-out token skip routes to skip-token', () => {
|
|
60
|
-
const outcome = classifyCodexGateOutcome(
|
|
61
|
-
codexResult({ clean: true, down: true, skipped: true, skipReason: 'token' }),
|
|
62
|
-
);
|
|
63
|
-
assert.equal(outcome.kind, 'skip-token');
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test('a usage-threshold skip routes to skip-usage', () => {
|
|
67
|
-
const outcome = classifyCodexGateOutcome(
|
|
68
|
-
codexResult({ clean: true, down: false, skipped: true, skipReason: 'usage' }),
|
|
69
|
-
);
|
|
70
|
-
assert.equal(outcome.kind, 'skip-usage');
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test('a codex_down result routes to down', () => {
|
|
74
|
-
const outcome = classifyCodexGateOutcome(codexResult({ clean: false, down: true }));
|
|
75
|
-
assert.equal(outcome.kind, 'down');
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test('Codex findings route to a fix when the review completed with findings', () => {
|
|
79
|
-
const outcome = classifyCodexGateOutcome(
|
|
80
|
-
codexResult({
|
|
81
|
-
findings: [codexFinding()],
|
|
82
|
-
}),
|
|
83
|
-
);
|
|
84
|
-
assert.equal(outcome.kind, 'fix');
|
|
85
|
-
assert.equal(outcome.findings.length, 1);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test('a dead Codex gate agent retries rather than passing', () => {
|
|
89
|
-
assert.equal(classifyCodexGateOutcome(null).kind, 'retry');
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test('a reachable Codex gate with no findings and no clean verdict retries', () => {
|
|
93
|
-
const outcome = classifyCodexGateOutcome(codexResult({ clean: false, down: false }));
|
|
94
|
-
assert.equal(outcome.kind, 'retry');
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
test('skipped with an unknown skipReason retries rather than silently advancing', () => {
|
|
98
|
-
const outcome = classifyCodexGateOutcome(
|
|
99
|
-
codexResult({ skipped: true, skipReason: 'mystery' }),
|
|
100
|
-
);
|
|
101
|
-
assert.equal(outcome.kind, 'retry');
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test('CODEX_SCHEMA requires skipped and skipReason drawn from the skip-reason constant', () => {
|
|
105
|
-
const schemaStart = convergeSource.indexOf('const CODEX_SCHEMA =');
|
|
106
|
-
const schemaEnd = convergeSource.indexOf('const COPILOT_VERIFY_VERDICTS =');
|
|
107
|
-
assert.notEqual(schemaStart, -1, 'expected CODEX_SCHEMA to exist');
|
|
108
|
-
const schemaSource = convergeSource.slice(schemaStart, schemaEnd);
|
|
109
|
-
assert.match(schemaSource, /skipped:\s*\{\s*type:\s*'boolean'/);
|
|
110
|
-
assert.match(schemaSource, /enum:\s*CODEX_SKIP_REASONS/);
|
|
111
|
-
assert.match(schemaSource, /required:\s*\[[^\]]*'skipped'[^\]]*\]/);
|
|
112
|
-
assert.match(schemaSource, /required:\s*\[[^\]]*'skipReason'[^\]]*\]/);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test('the Codex gate prompt honors reviews_disabled.py with only --reviewer codex', () => {
|
|
116
|
-
const codexPrompt = functionBody('runCodexGate');
|
|
117
|
-
const reviewsDisabledIndex = codexPrompt.indexOf('reviews_disabled.py');
|
|
118
|
-
assert.notEqual(reviewsDisabledIndex, -1, 'expected reviews_disabled.py invocation');
|
|
119
|
-
const invocationLineEnd = codexPrompt.indexOf('\\n', reviewsDisabledIndex);
|
|
120
|
-
const invocationLine = codexPrompt.slice(reviewsDisabledIndex, invocationLineEnd);
|
|
121
|
-
assert.match(invocationLine, /--reviewer codex/);
|
|
122
|
-
assert.doesNotMatch(invocationLine, /--owner|--repo/);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test('the Codex gate prompt decides required vs skip only through is_codex_review_required', () => {
|
|
126
|
-
const codexPrompt = functionBody('runCodexGate');
|
|
127
|
-
assert.match(codexPrompt, /codex_usage_probe\.py/, 'expected the usage probe CLI');
|
|
128
|
-
assert.match(
|
|
129
|
-
codexPrompt,
|
|
130
|
-
/is_codex_review_required/,
|
|
131
|
-
'expected the shared probe helper rather than an inline percent threshold',
|
|
132
|
-
);
|
|
133
|
-
assert.doesNotMatch(
|
|
134
|
-
codexPrompt,
|
|
135
|
-
/WEEKLY_USAGE_GATE_THRESHOLD_PERCENT|\b10\s*%|\bpercent_left\s*[><=]/,
|
|
136
|
-
'expected no duplicate threshold literal or inline comparison in the gate prompt',
|
|
137
|
-
);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
test('the Codex gate prompt treats a non-zero usage-probe exit as codex_down not usage skip', () => {
|
|
141
|
-
const codexPrompt = functionBody('runCodexGate');
|
|
142
|
-
assert.match(
|
|
143
|
-
codexPrompt,
|
|
144
|
-
/Non-zero exit means the Codex CLI or probe is broken/,
|
|
145
|
-
'expected an explicit non-zero probe exit branch',
|
|
146
|
-
);
|
|
147
|
-
assert.match(
|
|
148
|
-
codexPrompt,
|
|
149
|
-
/Never map a failed probe to skipReason:'usage'/,
|
|
150
|
-
'expected failed probe to be barred from the usage-skip path',
|
|
151
|
-
);
|
|
152
|
-
assert.match(
|
|
153
|
-
codexPrompt,
|
|
154
|
-
/clean:false, down:true, skipped:false, skipReason:''/,
|
|
155
|
-
'expected failed probe to return the codex_down schema shape',
|
|
156
|
-
);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
test('the Codex gate prompt sys.path insert uses CONFIG.codexScripts not a literal $HOME token', () => {
|
|
160
|
-
const codexPrompt = functionBody('runCodexGate');
|
|
161
|
-
assert.match(
|
|
162
|
-
codexPrompt,
|
|
163
|
-
/sys\.path\.insert\(0, r'\$\{CONFIG\.codexScripts\}'\)/,
|
|
164
|
-
'expected sys.path.insert to interpolate CONFIG.codexScripts (home-expanded at CONFIG build)',
|
|
165
|
-
);
|
|
166
|
-
assert.doesNotMatch(
|
|
167
|
-
codexPrompt,
|
|
168
|
-
/sys\.path\.insert\(0, r'\$HOME/,
|
|
169
|
-
'expected no unexpanded $HOME inside the Python raw-string path',
|
|
170
|
-
);
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
test('the Codex gate prompt runs the review wrapper against the PR base branch', () => {
|
|
174
|
-
const codexPrompt = functionBody('runCodexGate');
|
|
175
|
-
assert.match(codexPrompt, /run_codex_review/, 'expected the codex-review wrapper');
|
|
176
|
-
assert.match(codexPrompt, /base_branch/, 'expected the wrapper to receive the PR base');
|
|
177
|
-
assert.match(codexPrompt, /\.base\.ref/, 'expected the agent to resolve the PR base ref');
|
|
178
|
-
assert.match(codexPrompt, /parse_codex_findings/, 'expected findings to come from the shared parser');
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
test('the Codex gate prompt passes an explicit generous timeout so a slow-but-healthy review is not cut short', () => {
|
|
182
|
-
const codexPrompt = functionBody('runCodexGate');
|
|
183
|
-
assert.match(
|
|
184
|
-
codexPrompt,
|
|
185
|
-
/timeout_seconds=\$\{CONFIG\.codexReviewTimeoutSeconds\}/,
|
|
186
|
-
'expected the driver to pass CONFIG.codexReviewTimeoutSeconds, overriding the too-small wrapper default',
|
|
187
|
-
);
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
test('CONFIG.codexReviewTimeoutSeconds exceeds the observed real Codex runtime', () => {
|
|
191
|
-
const match = convergeSource.match(/codexReviewTimeoutSeconds:\s*(\d+)/);
|
|
192
|
-
assert.notEqual(match, null, 'expected CONFIG.codexReviewTimeoutSeconds to be defined');
|
|
193
|
-
const budgetSeconds = Number(match[1]);
|
|
194
|
-
assert.ok(
|
|
195
|
-
budgetSeconds >= 900,
|
|
196
|
-
`expected a budget comfortably above the ~683s measured Codex runtime, got ${budgetSeconds}`,
|
|
197
|
-
);
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
test('the Codex gate prompt runs the review in the background rather than the default foreground timeout', () => {
|
|
201
|
-
const codexPrompt = functionBody('runCodexGate');
|
|
202
|
-
assert.match(
|
|
203
|
-
codexPrompt,
|
|
204
|
-
/run_in_background/i,
|
|
205
|
-
'expected the review driver to run in the background — the foreground tool timeout caps below the review runtime and would kill a healthy review',
|
|
206
|
-
);
|
|
207
|
-
assert.match(
|
|
208
|
-
codexPrompt,
|
|
209
|
-
/result file/i,
|
|
210
|
-
'expected the driver to persist its outcome to a result file the agent reads once the background run finishes',
|
|
211
|
-
);
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
test('the Codex gate prompt waits for the background review inside the same turn with a bounded Monitor loop', () => {
|
|
215
|
-
const codexPrompt = functionBody('runCodexGate');
|
|
216
|
-
assert.match(codexPrompt, /Monitor tool/, 'expected a same-turn Monitor wait for the background review');
|
|
217
|
-
assert.match(codexPrompt, /until-loop/, 'expected a bounded until-loop that re-checks the result file');
|
|
218
|
-
assert.match(codexPrompt, /codexReviewPollIntervalSeconds/, 'expected a named poll interval in the wait protocol');
|
|
219
|
-
assert.match(codexPrompt, /codexReviewMaxPolls/, 'expected a named attempt budget in the wait protocol');
|
|
220
|
-
assert.match(
|
|
221
|
-
codexPrompt,
|
|
222
|
-
/Do NOT end this turn/i,
|
|
223
|
-
'expected an explicit instruction against ending the schema-bearing turn to await the background run',
|
|
224
|
-
);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
test('the Codex background-review poll budget covers the review timeout', () => {
|
|
228
|
-
const intervalMatch = convergeSource.match(/codexReviewPollIntervalSeconds:\s*(\d+)/);
|
|
229
|
-
const pollsMatch = convergeSource.match(/codexReviewMaxPolls:\s*(\d+)/);
|
|
230
|
-
const timeoutMatch = convergeSource.match(/codexReviewTimeoutSeconds:\s*(\d+)/);
|
|
231
|
-
assert.notEqual(intervalMatch, null, 'expected codexReviewPollIntervalSeconds in CONFIG');
|
|
232
|
-
assert.notEqual(pollsMatch, null, 'expected codexReviewMaxPolls in CONFIG');
|
|
233
|
-
assert.notEqual(timeoutMatch, null, 'expected codexReviewTimeoutSeconds in CONFIG');
|
|
234
|
-
const intervalSeconds = Number(intervalMatch[1]);
|
|
235
|
-
const maxPolls = Number(pollsMatch[1]);
|
|
236
|
-
const timeoutSeconds = Number(timeoutMatch[1]);
|
|
237
|
-
assert.ok(
|
|
238
|
-
intervalSeconds * maxPolls >= timeoutSeconds,
|
|
239
|
-
`expected the poll budget ${intervalSeconds * maxPolls}s to cover the ${timeoutSeconds}s review timeout`,
|
|
240
|
-
);
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
test('the Codex gate prompt returns codex_down when the background poll budget is spent', () => {
|
|
244
|
-
const codexPrompt = functionBody('runCodexGate');
|
|
245
|
-
assert.match(
|
|
246
|
-
codexPrompt,
|
|
247
|
-
/full poll budget is spent with no result file[\s\S]*?down:true/,
|
|
248
|
-
'expected a spent-budget branch that returns the codex_down schema shape',
|
|
249
|
-
);
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
test('meta Codex gate phase describes codex_down as a non-blocking skip that logs a note', () => {
|
|
253
|
-
const phaseStart = convergeSource.indexOf("title: 'Codex gate'");
|
|
254
|
-
const phaseEnd = convergeSource.indexOf("title: 'Finalize'");
|
|
255
|
-
assert.notEqual(phaseStart, -1, 'expected a Codex gate phase in meta');
|
|
256
|
-
const codexPhaseDetail = convergeSource.slice(phaseStart, phaseEnd);
|
|
257
|
-
assert.match(codexPhaseDetail, /codex_down/, 'expected the phase detail to name codex_down');
|
|
258
|
-
assert.match(
|
|
259
|
-
codexPhaseDetail,
|
|
260
|
-
/codex_down, the gate skips without blocking/,
|
|
261
|
-
'expected codex_down to be described as a non-blocking skip',
|
|
262
|
-
);
|
|
263
|
-
assert.match(
|
|
264
|
-
codexPhaseDetail,
|
|
265
|
-
/logged as a note/,
|
|
266
|
-
'expected the codex_down skip to be logged as a note so the bypass stays visible',
|
|
267
|
-
);
|
|
268
|
-
assert.doesNotMatch(
|
|
269
|
-
codexPhaseDetail,
|
|
270
|
-
/holds the PR as a draft with a codex-down blocker/,
|
|
271
|
-
'codex_down must not be described as holding the PR draft with a blocker',
|
|
272
|
-
);
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
test('CODEX_SCHEMA down.description describes codex_down as a skipped, bypassed gate recorded as a note', () => {
|
|
276
|
-
const schemaStart = convergeSource.indexOf('const CODEX_SCHEMA =');
|
|
277
|
-
const schemaEnd = convergeSource.indexOf('const COPILOT_VERIFY_VERDICTS =');
|
|
278
|
-
assert.notEqual(schemaStart, -1, 'expected CODEX_SCHEMA to exist');
|
|
279
|
-
const schemaSource = convergeSource.slice(schemaStart, schemaEnd);
|
|
280
|
-
const downLineStart = schemaSource.indexOf('down: {');
|
|
281
|
-
const downLine = schemaSource.slice(downLineStart, schemaSource.indexOf('\n', downLineStart));
|
|
282
|
-
assert.match(downLine, /codex_down/, 'expected the down description to name codex_down');
|
|
283
|
-
assert.match(downLine, /skipped and bypassed/, 'expected codex_down to be described as a skipped, bypassed gate');
|
|
284
|
-
assert.match(downLine, /recorded as a note/, 'expected codex_down to be recorded as a note');
|
|
285
|
-
assert.doesNotMatch(
|
|
286
|
-
downLine,
|
|
287
|
-
/holds the PR as a draft with a codex-down blocker/,
|
|
288
|
-
'the down description must not claim codex_down holds the PR draft as a blocker',
|
|
289
|
-
);
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
test('the CODEX phase stamps codexCleanAt on clean and clears it on an opt-out, usage, or down skip', () => {
|
|
293
|
-
const codexPhaseStart = convergeSource.indexOf("if (phase === 'CODEX') {");
|
|
294
|
-
const finalizePhaseStart = convergeSource.indexOf("if (phase === 'FINALIZE') {", codexPhaseStart);
|
|
295
|
-
assert.notEqual(codexPhaseStart, -1, 'expected a CODEX phase block');
|
|
296
|
-
assert.notEqual(finalizePhaseStart, -1, 'expected FINALIZE after CODEX');
|
|
297
|
-
const codexPhase = convergeSource.slice(codexPhaseStart, finalizePhaseStart);
|
|
298
|
-
assert.match(codexPhase, /await runCodexGate\(head\)/);
|
|
299
|
-
assert.match(codexPhase, /classifyCodexGateOutcome\(codex\)/);
|
|
300
|
-
assert.match(codexPhase, /codexCleanAt = head/, 'expected a clean pass to stamp the HEAD');
|
|
301
|
-
assert.match(codexPhase, /skip-token[\s\S]*codexDown = true[\s\S]*codexCleanAt = null/);
|
|
302
|
-
assert.match(codexPhase, /skip-usage[\s\S]*codexDown = false[\s\S]*codexCleanAt = null/);
|
|
303
|
-
assert.match(codexPhase, /kind === 'down'[\s\S]*codexDown = true[\s\S]*codexCleanAt = null/);
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
test('the CODEX phase skips a genuine codex_down gracefully, recording a note and advancing to mark-ready', () => {
|
|
307
|
-
const codexPhaseStart = convergeSource.indexOf("if (phase === 'CODEX') {");
|
|
308
|
-
const finalizePhaseStart = convergeSource.indexOf("if (phase === 'FINALIZE') {", codexPhaseStart);
|
|
309
|
-
const codexPhase = convergeSource.slice(codexPhaseStart, finalizePhaseStart);
|
|
310
|
-
const downBranchStart = codexPhase.indexOf("codexOutcome.kind === 'down'");
|
|
311
|
-
const downBranchEnd = codexPhase.indexOf("codexOutcome.kind === 'fix'", downBranchStart);
|
|
312
|
-
assert.notEqual(downBranchStart, -1, 'expected a codex_down branch in the CODEX phase');
|
|
313
|
-
assert.notEqual(downBranchEnd, -1, 'expected a fix branch after the down branch');
|
|
314
|
-
const downBranch = codexPhase.slice(downBranchStart, downBranchEnd);
|
|
315
|
-
assert.match(downBranch, /codexNote =/, 'a genuine codex_down records a transparent note');
|
|
316
|
-
assert.match(downBranch, /codexDown = true/, 'the down branch sets the bypass flag so mark-ready skips the required Codex gate');
|
|
317
|
-
assert.match(downBranch, /codexCleanAt = null/, 'the down branch clears any codex-clean stamp');
|
|
318
|
-
assert.match(
|
|
319
|
-
downBranch,
|
|
320
|
-
/phase = 'FINALIZE'/,
|
|
321
|
-
'a genuine codex_down advances to mark-ready with the gate bypassed rather than blocking the run',
|
|
322
|
-
);
|
|
323
|
-
assert.doesNotMatch(downBranch, /blocker =/, 'a genuine codex_down must not record a blocker');
|
|
324
|
-
assert.doesNotMatch(downBranch, /\bbreak\b/, 'the down branch continues rather than breaking the loop');
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
test('the workflow return object includes codexNote so the final report surfaces the Codex skip', () => {
|
|
328
|
-
const assembleStart = convergeSource.indexOf('const assembleResult =');
|
|
329
|
-
assert.notEqual(assembleStart, -1, 'expected assembleResult to exist');
|
|
330
|
-
const assembleEnd = convergeSource.indexOf('})', assembleStart);
|
|
331
|
-
assert.notEqual(assembleEnd, -1, 'expected the assembleResult object to close');
|
|
332
|
-
const assembleSource = convergeSource.slice(assembleStart, assembleEnd);
|
|
333
|
-
assert.match(assembleSource, /codexNote,/, 'expected codexNote among the returned notes');
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
test('the CODEX phase routes findings through applyFixes and re-enters CONVERGE', () => {
|
|
337
|
-
const codexPhaseStart = convergeSource.indexOf("if (phase === 'CODEX') {");
|
|
338
|
-
const finalizePhaseStart = convergeSource.indexOf("if (phase === 'FINALIZE') {", codexPhaseStart);
|
|
339
|
-
const codexPhase = convergeSource.slice(codexPhaseStart, finalizePhaseStart);
|
|
340
|
-
assert.match(codexPhase, /applyFixes\(head, codexOutcome\.findings, 'codex'\)/);
|
|
341
|
-
assert.match(codexPhase, /phase = 'CONVERGE'/);
|
|
342
|
-
assert.match(codexPhase, /head = null/);
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
test('the COPILOT phase advances to CODEX rather than FINALIZE on a clean or bypass path', () => {
|
|
346
|
-
const copilotPhaseStart = convergeSource.indexOf("if (phase === 'COPILOT') {");
|
|
347
|
-
const codexPhaseStart = convergeSource.indexOf("if (phase === 'CODEX') {", copilotPhaseStart);
|
|
348
|
-
assert.notEqual(copilotPhaseStart, -1, 'expected a COPILOT phase block');
|
|
349
|
-
assert.notEqual(codexPhaseStart, -1, 'expected a CODEX phase after COPILOT');
|
|
350
|
-
const copilotPhase = convergeSource.slice(copilotPhaseStart, codexPhaseStart);
|
|
351
|
-
assert.match(copilotPhase, /phase = 'CODEX'/);
|
|
352
|
-
assert.doesNotMatch(copilotPhase, /phase = 'FINALIZE'/);
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
test('the merged FINALIZE check receives codexDown and codexCleanAt and passes matching flags', () => {
|
|
356
|
-
const finalizeStart = convergeSource.indexOf("if (phase === 'FINALIZE') {");
|
|
357
|
-
assert.notEqual(finalizeStart, -1, 'expected a FINALIZE phase block');
|
|
358
|
-
const checkCall = convergeSource.indexOf('runConvergenceCheck(', finalizeStart);
|
|
359
|
-
assert.notEqual(checkCall, -1, 'expected the FINALIZE phase to call runConvergenceCheck');
|
|
360
|
-
const checkLine = convergeSource.slice(checkCall, convergeSource.indexOf('\n', checkCall));
|
|
361
|
-
assert.match(checkLine, /codexDown/);
|
|
362
|
-
assert.match(checkLine, /codexCleanAt/);
|
|
363
|
-
const checkBody = functionBody('runConvergenceCheck');
|
|
364
|
-
assert.match(checkBody, /context\.codexDown \? ' --codex-down' : ''/);
|
|
365
|
-
assert.match(checkBody, /context\.codexCleanAt \? ` --codex-clean-at \$\{context\.codexCleanAt\}` : ''/);
|
|
366
|
-
assert.match(checkBody, /if \(context\.codexDown\) reviewerOptOutTokens\.push\('codex'\)/);
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
test('runConvergenceCheck tells the finalize agent to persist codex_clean_at for the flagless mark-ready re-check', () => {
|
|
370
|
-
const checkBody = functionBody('runConvergenceCheck');
|
|
371
|
-
assert.match(
|
|
372
|
-
checkBody,
|
|
373
|
-
/codexCleanAtNote/,
|
|
374
|
-
'expected a dedicated note when the Codex gate stamped clean on HEAD',
|
|
375
|
-
);
|
|
376
|
-
assert.match(
|
|
377
|
-
checkBody,
|
|
378
|
-
/pr-converge-state\.json/,
|
|
379
|
-
'expected the note to name the job-dir state file the mark-ready blocker re-reads',
|
|
380
|
-
);
|
|
381
|
-
assert.match(
|
|
382
|
-
checkBody,
|
|
383
|
-
/codex_clean_at/,
|
|
384
|
-
'expected the note to write the codex_clean_at key the flagless re-check resolves',
|
|
385
|
-
);
|
|
386
|
-
assert.match(
|
|
387
|
-
checkBody,
|
|
388
|
-
/CLAUDE_JOB_DIR/,
|
|
389
|
-
'expected the note to locate the state file under CLAUDE_JOB_DIR',
|
|
390
|
-
);
|
|
391
|
-
assert.match(
|
|
392
|
-
checkBody,
|
|
393
|
-
/needsCodexCleanExportFallback/,
|
|
394
|
-
'expected a single-export fallback path when CLAUDE_JOB_DIR is unset',
|
|
395
|
-
);
|
|
396
|
-
assert.match(
|
|
397
|
-
checkBody,
|
|
398
|
-
/Emit exactly one export/,
|
|
399
|
-
'expected the opt-out instruction to forbid a second overwriting export',
|
|
400
|
-
);
|
|
401
|
-
assert.doesNotMatch(
|
|
402
|
-
checkBody,
|
|
403
|
-
/export CLAUDE_REVIEWS_DISABLED="codex"/,
|
|
404
|
-
'expected no second solo codex export that would overwrite other tokens',
|
|
405
|
-
);
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
test('CONFIG expands home for the codex-review scripts directory', () => {
|
|
409
|
-
assert.match(
|
|
410
|
-
convergeSource,
|
|
411
|
-
/const homeDirectory = \([\s\S]*?homeDirectoryPayload\.homeDirectory/,
|
|
412
|
-
'expected home to be sourced from the workflow args payload (the sandbox exposes no process)',
|
|
413
|
-
);
|
|
414
|
-
assert.match(
|
|
415
|
-
convergeSource,
|
|
416
|
-
/codexScripts:\s*`\$\{homeDirectory\}\/\.claude\/_shared\/pr-loop\/scripts`/,
|
|
417
|
-
'expected codexScripts to expand homeDirectory rather than embed $HOME',
|
|
418
|
-
);
|
|
419
|
-
assert.doesNotMatch(
|
|
420
|
-
convergeSource,
|
|
421
|
-
/codexScripts:\s*'\$HOME\//,
|
|
422
|
-
'expected no literal $HOME token in codexScripts (Python raw strings do not expand it)',
|
|
423
|
-
);
|
|
424
|
-
});
|
|
425
|
-
|
|
426
|
-
test('no process global is read unguarded (the workflow sandbox exposes no process)', () => {
|
|
427
|
-
const unguardedProcessLines = convergeSource
|
|
428
|
-
.split('\n')
|
|
429
|
-
.filter((sourceLine) => sourceLine.includes('process.') && !sourceLine.includes('typeof process'));
|
|
430
|
-
assert.deepEqual(
|
|
431
|
-
unguardedProcessLines,
|
|
432
|
-
[],
|
|
433
|
-
'every process.* read must sit on a line guarded by typeof process; the Bun workflow sandbox has no process global',
|
|
434
|
-
);
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
test('the home directory is sourced from the workflow args payload', () => {
|
|
438
|
-
assert.match(
|
|
439
|
-
convergeSource,
|
|
440
|
-
/const homeDirectoryPayload = normalizeRunInput\(args\)/,
|
|
441
|
-
'expected homeDirectory to read args (the only channel into the process-less sandbox), parsed via normalizeRunInput',
|
|
442
|
-
);
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
test('meta lists the Codex gate phase between Copilot and Finalize', () => {
|
|
446
|
-
const copilotPhaseIndex = convergeSource.indexOf("title: 'Copilot gate'");
|
|
447
|
-
const codexPhaseIndex = convergeSource.indexOf("title: 'Codex gate'");
|
|
448
|
-
const finalizePhaseIndex = convergeSource.indexOf("title: 'Finalize'");
|
|
449
|
-
assert.notEqual(codexPhaseIndex, -1, 'expected a Codex gate phase in meta');
|
|
450
|
-
assert.ok(copilotPhaseIndex < codexPhaseIndex && codexPhaseIndex < finalizePhaseIndex);
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
test('SKILL.md names Codex among the terminal confirmation gates', () => {
|
|
454
|
-
assert.match(skillSource, /Codex/i);
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
test('SKILL.md documents codexNote in the workflow return shape', () => {
|
|
458
|
-
const returnShapeMatch = skillSource.match(/`\{ converged, rounds, finalSha, blocker,[^`]*\}`/);
|
|
459
|
-
assert.ok(returnShapeMatch, 'expected the documented return shape literal in SKILL.md');
|
|
460
|
-
assert.match(returnShapeMatch[0], /codexNote/);
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
test('SKILL.md final-report template surfaces the codexNote so a bypassed Codex gate stays visible', () => {
|
|
464
|
-
const reportBlockMatch = skillSource.match(/\/autoconverge exit:[\s\S]*?```/);
|
|
465
|
-
assert.ok(reportBlockMatch, 'expected the final-report template block in SKILL.md');
|
|
466
|
-
assert.match(reportBlockMatch[0], /Codex: <codexNote>/);
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
test('stop-conditions.md documents the Codex gate skip paths as non-blockers', () => {
|
|
470
|
-
assert.match(stopConditionsSource, /Codex/i);
|
|
471
|
-
assert.match(stopConditionsSource, /codex/i);
|
|
472
|
-
});
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict'
|
|
2
|
-
import { readFileSync } from 'node:fs'
|
|
3
|
-
import test from 'node:test'
|
|
4
|
-
import vm from 'node:vm'
|
|
5
|
-
|
|
6
|
-
const sourcePath = new URL('./converge.mjs', import.meta.url)
|
|
7
|
-
const workflowSource = readFileSync(sourcePath, 'utf8')
|
|
8
|
-
|
|
9
|
-
function loadReviewResultParser() {
|
|
10
|
-
const passMarker = workflowSource.match(/const REVIEW_RESULT_PASS_MARKER = .+/)[0]
|
|
11
|
-
const findingsMarker = workflowSource.match(/const REVIEW_RESULT_FINDINGS_MARKER = .+/)[0]
|
|
12
|
-
const parserStart = workflowSource.indexOf('function parseReviewResult')
|
|
13
|
-
const parserEnd = workflowSource.indexOf('\n}\n', parserStart) + 2
|
|
14
|
-
const parserSource = `${passMarker}\n${findingsMarker}\n${workflowSource.slice(parserStart, parserEnd)}`
|
|
15
|
-
const parserContext = {}
|
|
16
|
-
vm.runInNewContext(`${parserSource}\nglobalThis.parseReviewResult = parseReviewResult`, parserContext)
|
|
17
|
-
return parserContext.parseReviewResult
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
test('review tasks apply the canonical review guide and return a review result', () => {
|
|
21
|
-
assert.match(workflowSource, /function runReviewTask\(task, context\)/)
|
|
22
|
-
assert.match(workflowSource, /_shared\/pr-loop\/precatch-rubric\.md/)
|
|
23
|
-
assert.match(workflowSource, /REVIEW_RESULT: PASS/)
|
|
24
|
-
assert.match(workflowSource, /REVIEW_RESULT: FINDINGS/)
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
test('review tasks use code-quality-agent and keep the working tree read-only', () => {
|
|
28
|
-
const reviewTaskStart = workflowSource.indexOf('function runReviewTask')
|
|
29
|
-
const reviewTaskEnd = workflowSource.indexOf('function serializeOneLineJson', reviewTaskStart)
|
|
30
|
-
const reviewTaskSource = workflowSource.slice(reviewTaskStart, reviewTaskEnd)
|
|
31
|
-
assert.match(reviewTaskSource, /agentType: 'code-quality-agent'/)
|
|
32
|
-
assert.match(reviewTaskSource, /Make no edit to the tree under verification/)
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
test('workflow agents receive the selected GitHub transport before role instructions', () => {
|
|
36
|
-
assert.match(
|
|
37
|
-
workflowSource,
|
|
38
|
-
/HEADLESS_EDIT_PREAMBLE\}\$\{githubTransportDirective\(activeGithubTransport\)\}\$\{worktreeDirective/,
|
|
39
|
-
)
|
|
40
|
-
assert.match(
|
|
41
|
-
workflowSource,
|
|
42
|
-
/HEADLESS_READONLY_PREAMBLE\}\$\{githubTransportDirective\(activeGithubTransport\)\}\$\{worktreeDirective/,
|
|
43
|
-
)
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
test('review result parser accepts one standalone terminal disposition only', () => {
|
|
47
|
-
const parseReviewResult = loadReviewResultParser()
|
|
48
|
-
|
|
49
|
-
assert.equal(parseReviewResult('review complete\nREVIEW_RESULT: PASS'), 'PASS')
|
|
50
|
-
assert.equal(parseReviewResult('finding: missing test\nREVIEW_RESULT: FINDINGS'), 'FINDINGS')
|
|
51
|
-
assert.equal(parseReviewResult('REVIEW_RESULT: PASSING'), null)
|
|
52
|
-
assert.equal(parseReviewResult('REVIEW_RESULT: PASS\nREVIEW_RESULT: FINDINGS'), null)
|
|
53
|
-
assert.equal(parseReviewResult('REVIEW_RESULT: PASS\n> REVIEW_RESULT: FINDINGS'), null)
|
|
54
|
-
assert.equal(parseReviewResult('review complete'), null)
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
test('the fix flow keeps edit, review, and commit as distinct operational steps', () => {
|
|
58
|
-
const fixerStart = workflowSource.indexOf('async function fixerWithRecovery')
|
|
59
|
-
const fixerEnd = workflowSource.indexOf('function runCodeEditorTask', fixerStart)
|
|
60
|
-
const fixerSource = workflowSource.slice(fixerStart, fixerEnd)
|
|
61
|
-
assert.match(fixerSource, /runReviewTask\('fix-verify'/)
|
|
62
|
-
assert.match(fixerSource, /runFixerTask\('commit'/)
|
|
63
|
-
assert.match(fixerSource, /reviewPassed\(verifyTranscript\)/)
|
|
64
|
-
})
|