claude-dev-env 4.3.0 → 7.1.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 +35 -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 +35 -3
- package/.agents/skills/session-continuity/README.md +189 -0
- package/.agents/skills/session-continuity/SKILL.md +114 -0
- package/.agents/skills/session-continuity/continuity.mjs +375 -0
- package/.agents/skills/session-continuity/continuity.test.mjs +303 -0
- package/.agents/skills/skill-builder/SKILL.md +3 -196
- package/.agents/skills/syncing-submodules/scripts/test_sync_parent_pointer.py +0 -7
- package/.agents/skills/windows-scheduled-task/SKILL.md +153 -0
- 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/cde.mjs +36 -8
- package/bin/cde.test.mjs +48 -0
- package/bin/ever-shipped-skills.mjs +2 -20
- package/bin/ever-shipped-skills.test.mjs +8 -0
- package/bin/expand_home_directory_tokens.mjs +4 -5
- package/bin/expand_home_directory_tokens.test.mjs +128 -0
- package/bin/git_hooks_installer.mjs +22 -3
- package/bin/git_hooks_installer.test.mjs +159 -5
- package/bin/install-session-continuity.mjs +78 -0
- package/bin/install-session-continuity.test.mjs +42 -0
- package/bin/install.agents-home.test.mjs +15 -13
- package/bin/install.codex-rules.test.mjs +5 -2
- package/bin/install.cursor-rules.test.mjs +5 -2
- package/bin/install.mjs +506 -63
- package/bin/install.policy-lint.test.mjs +130 -0
- package/bin/install.profiles.test.mjs +10 -0
- package/bin/install.prune.test.mjs +286 -37
- package/bin/install.pstack-plugin.test.mjs +114 -0
- package/bin/install.test.mjs +537 -120
- package/bin/install.transaction.test.mjs +3 -3
- package/bin/refresh-pstack-plugin-skills.test.mjs +211 -0
- package/docs/CODE_RULES.md +1 -1
- 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_banned_identifiers.py +3 -0
- package/hooks/blocking/code_rules_paired_test.py +22 -15
- package/hooks/blocking/precommit_code_rules_gate.py +5 -8
- package/hooks/blocking/test_bash_dispatcher_interpreter_starts.py +7 -52
- package/hooks/blocking/test_bash_post_call_dispatcher.py +65 -4
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +0 -69
- package/hooks/blocking/test_code_rules_banned_identifiers.py +64 -0
- 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 +43 -342
- package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +20 -51
- package/hooks/blocking/test_shared_stdin_adoption.py +1 -47
- package/hooks/git-hooks/git_hooks_constants/staged_policy_lint.py +12 -0
- package/hooks/git-hooks/git_hooks_constants/verification_notice_constants.py +116 -0
- package/hooks/git-hooks/post_commit.py +33 -0
- package/hooks/git-hooks/pre_commit.py +18 -49
- package/hooks/git-hooks/pre_push.py +45 -541
- package/hooks/git-hooks/test_installed_policy_lint.py +208 -0
- package/hooks/git-hooks/test_native_hook_support.py +129 -0
- package/hooks/git-hooks/test_native_policy_commit.py +120 -0
- package/hooks/git-hooks/test_post_commit.py +43 -0
- package/hooks/git-hooks/test_pre_commit.py +38 -148
- package/hooks/git-hooks/test_pre_push.py +48 -1631
- package/hooks/git-hooks/test_verification_notice.py +60 -0
- package/hooks/git-hooks/test_verification_start.py +173 -0
- package/hooks/git-hooks/verification_notice.py +192 -0
- package/hooks/git-hooks/verification_notice_context.py +253 -0
- package/hooks/git-hooks/verification_notice_state.py +237 -0
- package/hooks/git-hooks/verification_start.py +100 -0
- package/hooks/hooks.json +1 -120
- package/hooks/hooks_constants/banned_identifiers_constants.py +4 -0
- package/hooks/hooks_constants/bash_post_call_dispatcher_constants.py +14 -1
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +1 -15
- package/hooks/hooks_constants/post_tool_use_dispatcher_constants.py +0 -4
- package/hooks/hooks_constants/pr_done_reminder_constants.py +98 -0
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +10 -93
- package/hooks/hooks_constants/stop_dispatcher_constants.py +1 -4
- package/hooks/hooks_constants/test_bash_post_call_dispatcher_constants.py +19 -2
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +3 -66
- package/hooks/hooks_constants/test_pr_done_reminder_constants.py +40 -0
- package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +46 -201
- package/hooks/hooks_constants/test_stop_dispatcher_constants.py +3 -25
- package/hooks/validation/test_post_tool_use_dispatcher.py +35 -298
- package/hooks/validators/run_all_validators.py +5 -1
- package/hooks/validators/security_checks.py +57 -2
- package/hooks/validators/test_hook_timing_harness.py +2 -5
- package/hooks/validators/test_run_all_validators_mutation_tool_parity.py +3 -11
- package/hooks/validators/test_run_all_validators_policy_lint.py +36 -0
- package/hooks/validators/test_security_checks.py +57 -0
- package/package.json +2 -2
- package/rules/confirm-implementation-forks.md +2 -0
- package/rules/durable-post-artifacts.md +2 -1
- package/rules/git-workflow.md +18 -6
- package/rules/pstack-host-mapping.md +48 -0
- package/rules/vault-context.md +3 -5
- package/rules/verify-runtime-state.md +7 -0
- package/scripts/audit-hooks-cli.mjs +5 -0
- package/scripts/audit-hooks.mjs +72 -16
- package/scripts/automatic_advisory/__init__.py +1 -0
- package/scripts/automatic_advisory/checkout.py +51 -0
- package/scripts/automatic_advisory/cli.py +334 -0
- package/scripts/automatic_advisory/config/__init__.py +1 -0
- package/scripts/automatic_advisory/config/constants.py +93 -0
- package/scripts/automatic_advisory/config/timing.py +4 -0
- package/scripts/automatic_advisory/configuration.py +197 -0
- package/scripts/automatic_advisory/execution.py +350 -0
- package/scripts/automatic_advisory/git.py +69 -0
- package/scripts/automatic_advisory/model.py +107 -0
- package/scripts/automatic_advisory/process_host.py +32 -0
- package/scripts/automatic_advisory/publisher.py +84 -0
- package/scripts/automatic_advisory/runner.py +399 -0
- package/scripts/automatic_advisory/state.py +116 -0
- package/scripts/automatic_advisory/window_flags.py +41 -0
- package/scripts/automatic_advisory/windows_job.py +122 -0
- 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/codex_compat_materializer.py +99 -13
- package/scripts/dev_env_scripts_constants/shared_tree_constants.py +4 -5
- package/{.agents/skills/_shared/pr-loop/scripts → scripts}/durable_post_lint.py +35 -43
- package/{.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/durable_post_lint_constants.py → scripts/durable_post_lint_config/config/constants.py} +10 -10
- package/{.agents/skills/_shared/pr-loop/scripts/test_durable_post_lint_constants.py → scripts/durable_post_lint_config/config/test_constants.py} +14 -1
- package/scripts/local_report_cli.py +184 -0
- package/scripts/local_report_core.py +247 -0
- package/scripts/local_report_publisher.py +12 -0
- package/scripts/local_report_validation.py +357 -0
- package/scripts/local_verification/__init__.py +23 -0
- package/scripts/local_verification/check_runner.py +364 -0
- package/scripts/local_verification/check_support.py +99 -0
- package/scripts/local_verification/cli.py +158 -0
- package/scripts/local_verification/command_runner.py +84 -0
- package/scripts/local_verification/config/__init__.py +1 -0
- package/scripts/local_verification/config/constants.py +89 -0
- package/scripts/local_verification/config/timing.py +1 -0
- package/scripts/local_verification/git_state.py +146 -0
- package/scripts/local_verification/manifest.py +224 -0
- package/scripts/local_verification/model.py +174 -0
- package/scripts/local_verification/runner.py +234 -0
- package/scripts/policy_lint/adapter_detectors.py +13 -6
- package/scripts/policy_lint/adapter_pairing.py +58 -3
- package/scripts/policy_lint/adapters.py +7 -4
- package/scripts/policy_lint/config/approved_test_pairs.py +323 -0
- 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/pr_verification/__init__.py +1 -0
- package/scripts/pr_verification/branch_refs.py +114 -0
- package/scripts/pr_verification/config/__init__.py +1 -0
- package/scripts/pr_verification/config/constants.py +84 -0
- package/scripts/pr_verification/config/timing.py +5 -0
- package/scripts/pr_verification/github.py +137 -0
- package/scripts/pr_verification/github_api.py +376 -0
- package/scripts/pr_verification/github_labels.py +88 -0
- package/scripts/pr_verification/github_parsing.py +182 -0
- package/scripts/pr_verification/github_transport.py +91 -0
- package/scripts/pr_verification/lock.py +122 -0
- package/scripts/pr_verification/model.py +43 -0
- package/scripts/pr_verification/requirements.txt +1 -0
- package/scripts/refresh_pstack_plugin_skills.mjs +200 -0
- package/scripts/shared_tree_paths.py +32 -3
- package/{.agents/skills/_shared/pr-loop/scripts → scripts}/test_durable_post_lint.py +97 -0
- package/scripts/test_shared_tree_paths.py +33 -0
- package/scripts/tests/test-audit-hooks-cli.mjs +150 -0
- package/scripts/tests/test-audit-hooks.mjs +94 -2
- package/scripts/tests/test_automatic_advisory.py +376 -0
- package/scripts/tests/test_automatic_advisory_job_cleanup.py +71 -0
- package/scripts/tests/test_automatic_advisory_polling.py +216 -0
- package/scripts/tests/test_automatic_advisory_recovery.py +320 -0
- package/scripts/tests/test_automatic_advisory_repairs.py +400 -0
- package/scripts/tests/test_automatic_advisory_settings.py +48 -0
- package/scripts/tests/test_automatic_advisory_window_flags.py +163 -0
- package/scripts/tests/test_closed_pr_label.py +157 -0
- package/scripts/tests/test_codex_compat_materializer.py +189 -6
- package/scripts/tests/test_github_labels.py +101 -0
- package/scripts/tests/test_github_transport.py +125 -0
- package/scripts/tests/test_local_report_publisher.py +396 -0
- package/scripts/tests/test_local_report_publisher_cli.py +85 -0
- package/scripts/tests/test_local_verification.py +348 -0
- package/scripts/tests/test_local_verification_cli.py +98 -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_pairing.py +163 -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_pr_verification_github.py +392 -0
- package/scripts/tests/test_pr_verification_lock.py +168 -0
- package/scripts/tests/test_rules.py +3 -7
- package/scripts/tests/test_verification_notice.py +347 -0
- package/.agents/skills/_shared/pr-loop/.claude/CLAUDE.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/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/blocking/ask_user_question_shape_blocker.py +0 -83
- package/hooks/blocking/bot_mention_comment_blocker.py +0 -76
- package/hooks/blocking/claude_md_orphan_file_blocker.py +0 -129
- package/hooks/blocking/cursor_cli_python_misfire_blocker.py +0 -108
- package/hooks/blocking/destructive_command_blocker.py +0 -2073
- package/hooks/blocking/fable_spawn_gate.py +0 -187
- package/hooks/blocking/luna_fast_mode_gate.py +0 -160
- package/hooks/blocking/nas_ssh_binary_enforcer.py +0 -163
- package/hooks/blocking/orchestrator_refresh_reschedule_gate.py +0 -260
- package/hooks/blocking/package_inventory_stale_blocker.py +0 -107
- package/hooks/blocking/pii_prevention_blocker.py +0 -365
- package/hooks/blocking/piped_pytest_blocker.py +0 -1223
- package/hooks/blocking/question_to_user_enforcer.py +0 -130
- package/hooks/blocking/send_user_file_open_locally_blocker.py +0 -71
- package/hooks/blocking/sensitive_file_protector.py +0 -167
- package/hooks/blocking/session_edit_stage_gate.py +0 -684
- package/hooks/blocking/session_handoff_blocker.py +0 -182
- package/hooks/blocking/shell_substitution_blocker.py +0 -350
- package/hooks/blocking/test_ask_user_question_shape.py +0 -69
- package/hooks/blocking/test_bot_mention_comment_blocker.py +0 -131
- package/hooks/blocking/test_claude_md_orphan_file_blocker.py +0 -1277
- package/hooks/blocking/test_cursor_cli_python_misfire_blocker.py +0 -208
- package/hooks/blocking/test_destructive_command_blocker.py +0 -2024
- package/hooks/blocking/test_destructive_command_blocker_deny_mode.py +0 -105
- package/hooks/blocking/test_destructive_command_blocker_no_verify.py +0 -122
- package/hooks/blocking/test_destructive_command_blocker_patterns.py +0 -133
- package/hooks/blocking/test_fable_spawn_gate.py +0 -381
- package/hooks/blocking/test_inventory_deadlock_resolution.py +0 -154
- package/hooks/blocking/test_luna_fast_mode_gate.py +0 -211
- package/hooks/blocking/test_nas_ssh_binary_enforcer.py +0 -225
- package/hooks/blocking/test_orchestrator_refresh_reschedule_gate.py +0 -231
- package/hooks/blocking/test_package_inventory_stale_blocker.py +0 -382
- package/hooks/blocking/test_pii_prevention_blocker.py +0 -1222
- package/hooks/blocking/test_pii_prevention_windows_git_path.py +0 -41
- package/hooks/blocking/test_pii_write_surface_ephemeral.py +0 -221
- package/hooks/blocking/test_piped_pytest_blocker.py +0 -587
- package/hooks/blocking/test_precommit_code_rules_gate_native_owner.py +0 -233
- package/hooks/blocking/test_question_to_user_enforcer.py +0 -169
- package/hooks/blocking/test_send_user_file_open_locally_blocker.py +0 -118
- package/hooks/blocking/test_sensitive_file_protector.py +0 -365
- package/hooks/blocking/test_session_edit_stage_gate.py +0 -537
- package/hooks/blocking/test_session_edit_stage_gate_staging.py +0 -64
- package/hooks/blocking/test_session_edit_stage_gate_support.py +0 -204
- package/hooks/blocking/test_session_edit_stage_gate_tracker.py +0 -116
- package/hooks/blocking/test_session_handoff_blocker.py +0 -318
- package/hooks/blocking/test_shell_substitution_blocker.py +0 -298
- package/hooks/blocking/test_unscoped_search_blocker.py +0 -102
- package/hooks/blocking/test_write_existing_file_blocker.py +0 -122
- package/hooks/blocking/tests/test_pii_prevention_blocker.py +0 -259
- package/hooks/blocking/unscoped_search_blocker.py +0 -391
- package/hooks/blocking/write_existing_file_blocker.py +0 -124
- package/hooks/hooks_constants/bot_mention_comment_blocker_constants.py +0 -19
- package/hooks/hooks_constants/cursor_cli_python_misfire_blocker_constants.py +0 -79
- package/hooks/hooks_constants/fable_spawn_gate_constants.py +0 -61
- package/hooks/hooks_constants/luna_fast_mode_gate_constants.py +0 -45
- package/hooks/hooks_constants/nas_ssh_binary_enforcer_constants.py +0 -20
- package/hooks/hooks_constants/orchestrator_refresh_reschedule_gate_constants.py +0 -57
- package/hooks/hooks_constants/send_user_file_open_locally_blocker_constants.py +0 -17
- package/hooks/hooks_constants/sensitive_file_protector_constants.py +0 -40
- package/hooks/hooks_constants/session_handoff_blocker_constants.py +0 -10
- package/hooks/hooks_constants/shell_substitution_blocker_constants.py +0 -80
- /package/.agents/skills/{_shared/pr-loop → session-continuity}/AGENTS.md +0 -0
- /package/{_shared/pr-loop → .agents/skills/session-continuity}/CLAUDE.md +0 -0
package/bin/install.test.mjs
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from 'node:fs';
|
|
15
15
|
import { tmpdir } from 'node:os';
|
|
16
16
|
import { join, dirname } from 'node:path';
|
|
17
|
-
import { pathToFileURL } from 'node:url';
|
|
17
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
18
18
|
|
|
19
19
|
import {
|
|
20
20
|
collectPackageSourceConflicts,
|
|
@@ -42,13 +42,17 @@ import {
|
|
|
42
42
|
caseOnlyRenameSourceName,
|
|
43
43
|
retiredManagedHookRelativePaths,
|
|
44
44
|
pruneRetiredHookEntriesFromSettings,
|
|
45
|
+
settingsHookCommandsAtPath,
|
|
46
|
+
retiredHookPathsNamedByCommands,
|
|
47
|
+
writeRetiredHookStandIns,
|
|
48
|
+
ownedHookRootComparisonKeys,
|
|
49
|
+
commandNamesOwnedHookScript,
|
|
50
|
+
hookCommandComparisonSpelling,
|
|
51
|
+
detectPython,
|
|
45
52
|
retainNewestRunBackupOnly,
|
|
53
|
+
refreshInstalledPstackPluginManifest,
|
|
46
54
|
} from './install.mjs';
|
|
47
55
|
import { EVER_SHIPPED_SKILL_NAMES } from './ever-shipped-skills.mjs';
|
|
48
|
-
import {
|
|
49
|
-
expandHomeDirectoryTokens,
|
|
50
|
-
expandHomeDirectoryTokensInSettings,
|
|
51
|
-
} from './expand_home_directory_tokens.mjs';
|
|
52
56
|
|
|
53
57
|
|
|
54
58
|
test('installer reports the authoritative Git hook names', () => {
|
|
@@ -347,115 +351,6 @@ test('interpreterCommandFromPath quotes an interpreter path that contains a spac
|
|
|
347
351
|
});
|
|
348
352
|
|
|
349
353
|
|
|
350
|
-
test('expandHomeDirectoryTokens expands $HOME, ${HOME}, and ~/', () => {
|
|
351
|
-
assert.equal(
|
|
352
|
-
expandHomeDirectoryTokens(
|
|
353
|
-
'python $HOME/.claude/hooks/session/fix_worktree_hookspath.py',
|
|
354
|
-
'C:\\Users\\x',
|
|
355
|
-
),
|
|
356
|
-
'python C:/Users/x/.claude/hooks/session/fix_worktree_hookspath.py',
|
|
357
|
-
);
|
|
358
|
-
assert.equal(
|
|
359
|
-
expandHomeDirectoryTokens('python ${HOME}/.claude/hooks/a.py', '/home/x'),
|
|
360
|
-
'python /home/x/.claude/hooks/a.py',
|
|
361
|
-
);
|
|
362
|
-
assert.equal(
|
|
363
|
-
expandHomeDirectoryTokens('python ~/.claude/hooks/a.py', '/home/x'),
|
|
364
|
-
'python /home/x/.claude/hooks/a.py',
|
|
365
|
-
);
|
|
366
|
-
assert.equal(
|
|
367
|
-
expandHomeDirectoryTokens('echo $HOMEPATH', 'C:/Users/x'),
|
|
368
|
-
'echo $HOMEPATH',
|
|
369
|
-
);
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
test('expandHomeDirectoryTokens inserts dollar characters in home paths literally', () => {
|
|
374
|
-
const homeWithReplaceMetacharacters = 'C:/Users/$&evil$1';
|
|
375
|
-
assert.equal(
|
|
376
|
-
expandHomeDirectoryTokens('python $HOME/.claude/a.py', homeWithReplaceMetacharacters),
|
|
377
|
-
'python C:/Users/$&evil$1/.claude/a.py',
|
|
378
|
-
);
|
|
379
|
-
assert.equal(
|
|
380
|
-
expandHomeDirectoryTokens('python ${HOME}/.claude/a.py', homeWithReplaceMetacharacters),
|
|
381
|
-
'python C:/Users/$&evil$1/.claude/a.py',
|
|
382
|
-
);
|
|
383
|
-
assert.equal(
|
|
384
|
-
expandHomeDirectoryTokens('python ~/.claude/a.py', homeWithReplaceMetacharacters),
|
|
385
|
-
'python C:/Users/$&evil$1/.claude/a.py',
|
|
386
|
-
);
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
test('expandHomeDirectoryTokens expands ${HOME} before $HOME so braces stay intact', () => {
|
|
391
|
-
assert.equal(
|
|
392
|
-
expandHomeDirectoryTokens('python ${HOME}/.claude/a.py', '/home/x'),
|
|
393
|
-
'python /home/x/.claude/a.py',
|
|
394
|
-
);
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
test('expandHomeDirectoryTokens strips trailing slashes from the home directory', () => {
|
|
399
|
-
assert.equal(
|
|
400
|
-
expandHomeDirectoryTokens('python $HOME/.claude/a.py', 'C:/Users/x/'),
|
|
401
|
-
'python C:/Users/x/.claude/a.py',
|
|
402
|
-
);
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
test('expandHomeDirectoryTokensInSettings skips non-array hook event values', () => {
|
|
407
|
-
const settings = {
|
|
408
|
-
hooks: {
|
|
409
|
-
SessionStart: 'not-an-array',
|
|
410
|
-
PreToolUse: [
|
|
411
|
-
{
|
|
412
|
-
matcher: 'Write',
|
|
413
|
-
hooks: [{ type: 'command', command: 'python $HOME/.claude/hooks/a.py' }],
|
|
414
|
-
},
|
|
415
|
-
],
|
|
416
|
-
},
|
|
417
|
-
};
|
|
418
|
-
expandHomeDirectoryTokensInSettings(settings, 'C:/Users/x');
|
|
419
|
-
assert.equal(settings.hooks.SessionStart, 'not-an-array');
|
|
420
|
-
assert.equal(
|
|
421
|
-
settings.hooks.PreToolUse[0].hooks[0].command,
|
|
422
|
-
'python C:/Users/x/.claude/hooks/a.py',
|
|
423
|
-
);
|
|
424
|
-
});
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
test('expandHomeDirectoryTokensInSettings rewrites hooks and statusLine', () => {
|
|
428
|
-
const settings = {
|
|
429
|
-
hooks: {
|
|
430
|
-
SessionStart: [
|
|
431
|
-
{
|
|
432
|
-
matcher: '',
|
|
433
|
-
hooks: [
|
|
434
|
-
{
|
|
435
|
-
type: 'command',
|
|
436
|
-
command: 'python $HOME/.claude/hooks/session/fix_worktree_hookspath.py',
|
|
437
|
-
},
|
|
438
|
-
],
|
|
439
|
-
},
|
|
440
|
-
],
|
|
441
|
-
},
|
|
442
|
-
statusLine: {
|
|
443
|
-
type: 'command',
|
|
444
|
-
command: 'python "$HOME/.claude/statusline-command.py"',
|
|
445
|
-
},
|
|
446
|
-
};
|
|
447
|
-
expandHomeDirectoryTokensInSettings(settings, 'C:/Users/x');
|
|
448
|
-
assert.equal(
|
|
449
|
-
settings.hooks.SessionStart[0].hooks[0].command,
|
|
450
|
-
'python C:/Users/x/.claude/hooks/session/fix_worktree_hookspath.py',
|
|
451
|
-
);
|
|
452
|
-
assert.equal(
|
|
453
|
-
settings.statusLine.command,
|
|
454
|
-
'python "C:/Users/x/.claude/statusline-command.py"',
|
|
455
|
-
);
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
|
|
459
354
|
test('mergeHooksIntoSettings expands residual $HOME in preserved user hooks', () => {
|
|
460
355
|
const hooksConfig = {
|
|
461
356
|
hooks: {
|
|
@@ -919,6 +814,19 @@ test('commandReferencesManagedHook matches a managed script followed by a whites
|
|
|
919
814
|
});
|
|
920
815
|
|
|
921
816
|
|
|
817
|
+
test('commandReferencesManagedHook matches a shipped script name under a hooks root this installer never writes', () => {
|
|
818
|
+
const managedPaths = new Set(['blocking/code_rules_enforcer.py']);
|
|
819
|
+
|
|
820
|
+
assert.ok(
|
|
821
|
+
commandReferencesManagedHook(
|
|
822
|
+
'python3 "/other-tool/.claude/hooks/blocking/code_rules_enforcer.py"',
|
|
823
|
+
managedPaths,
|
|
824
|
+
),
|
|
825
|
+
'the merge matcher reads the /.claude/hooks/ tail alone, so another tool\'s copy of a shipped script name is inside the managed set',
|
|
826
|
+
);
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
|
|
922
830
|
test('commandReferencesManagedHook matches the rewritten inline validators-runner hook that carries no script tail', () => {
|
|
923
831
|
const managedPaths = new Set(['blocking/code_rules_enforcer.py']);
|
|
924
832
|
const rewrittenInlineCommand =
|
|
@@ -1265,6 +1173,35 @@ test('retired hook registrations stay managed so reinstall removes them', () =>
|
|
|
1265
1173
|
'session/untracked_repo_detector.py',
|
|
1266
1174
|
'session/gh_pr_author_session_cleanup.py',
|
|
1267
1175
|
'observability/pr_description_writer_spawn_tracker.py',
|
|
1176
|
+
'blocking/plain_language_blocker.py',
|
|
1177
|
+
'lifecycle/config_change_guard.py',
|
|
1178
|
+
'blocking/docstring_rule_gate_count_blocker.py',
|
|
1179
|
+
'blocking/write_existing_file_blocker.py',
|
|
1180
|
+
'blocking/sensitive_file_protector.py',
|
|
1181
|
+
'blocking/pii_prevention_blocker.py',
|
|
1182
|
+
'blocking/duplicate_rmtree_helper_blocker.py',
|
|
1183
|
+
'blocking/claude_md_orphan_file_blocker.py',
|
|
1184
|
+
'blocking/package_inventory_stale_blocker.py',
|
|
1185
|
+
'blocking/env_var_table_code_drift_blocker.py',
|
|
1186
|
+
'blocking/pytest_testpaths_orphan_blocker.py',
|
|
1187
|
+
'blocking/destructive_command_blocker.py',
|
|
1188
|
+
'blocking/shell_substitution_blocker.py',
|
|
1189
|
+
'blocking/piped_pytest_blocker.py',
|
|
1190
|
+
'blocking/cursor_cli_python_misfire_blocker.py',
|
|
1191
|
+
'blocking/unscoped_search_blocker.py',
|
|
1192
|
+
'blocking/nas_ssh_binary_enforcer.py',
|
|
1193
|
+
'blocking/block_main_commit.py',
|
|
1194
|
+
'blocking/session_edit_stage_gate.py',
|
|
1195
|
+
'blocking/bash_pre_tool_use_dispatcher.py',
|
|
1196
|
+
'blocking/stop_dispatcher.py',
|
|
1197
|
+
'blocking/bot_mention_comment_blocker.py',
|
|
1198
|
+
'blocking/fable_spawn_gate.py',
|
|
1199
|
+
'blocking/luna_fast_mode_gate.py',
|
|
1200
|
+
'blocking/orchestrator_refresh_reschedule_gate.py',
|
|
1201
|
+
'blocking/ask_user_question_shape_blocker.py',
|
|
1202
|
+
'blocking/send_user_file_open_locally_blocker.py',
|
|
1203
|
+
'blocking/question_to_user_enforcer.py',
|
|
1204
|
+
'blocking/session_handoff_blocker.py',
|
|
1268
1205
|
]);
|
|
1269
1206
|
const shippedHooks = JSON.parse(
|
|
1270
1207
|
readFileSync(new URL('../hooks/hooks.json', import.meta.url), 'utf8')
|
|
@@ -1416,7 +1353,7 @@ test('mergeHooksIntoSettings is idempotent when run twice against an already-upd
|
|
|
1416
1353
|
});
|
|
1417
1354
|
|
|
1418
1355
|
|
|
1419
|
-
test('shipped hooks.json
|
|
1356
|
+
test('shipped hooks.json keeps only nonblocking policy handlers and lifecycle hooks', () => {
|
|
1420
1357
|
const shippedHooksConfig = JSON.parse(
|
|
1421
1358
|
readFileSync(new URL('../hooks/hooks.json', import.meta.url), 'utf8')
|
|
1422
1359
|
);
|
|
@@ -1428,8 +1365,8 @@ test('shipped hooks.json matches the dispatcher design: dispatchers registered,
|
|
|
1428
1365
|
|
|
1429
1366
|
assert.equal(
|
|
1430
1367
|
countManagedRunAllValidatorsHooks(shippedHooksConfig),
|
|
1431
|
-
|
|
1432
|
-
'shipped hooks.json must
|
|
1368
|
+
0,
|
|
1369
|
+
'shipped hooks.json must retire the inline run_all_validators runner from the write path',
|
|
1433
1370
|
);
|
|
1434
1371
|
|
|
1435
1372
|
const allPostToolUseGroups = shippedHooksConfig.hooks.PostToolUse || [];
|
|
@@ -1437,6 +1374,12 @@ test('shipped hooks.json matches the dispatcher design: dispatchers registered,
|
|
|
1437
1374
|
.flatMap(group => group.hooks.map(hook => hook.command))
|
|
1438
1375
|
.filter(cmd => cmd.includes('post_tool_use_dispatcher.py'));
|
|
1439
1376
|
assert.equal(postDispatcherCommands.length, 1, 'shipped hooks.json must register the PostToolUse dispatcher exactly once');
|
|
1377
|
+
const allCommands = Object.values(shippedHooksConfig.hooks).flatMap(matcherGroups => (
|
|
1378
|
+
matcherGroups.flatMap(matcherGroup => matcherGroup.hooks.map(hook => hook.command))
|
|
1379
|
+
));
|
|
1380
|
+
for (const retiredPath of RETIRED_HOOK_REGISTRATION_RELATIVE_PATHS) {
|
|
1381
|
+
assert.ok(allCommands.every(command => !command.includes(retiredPath)), retiredPath);
|
|
1382
|
+
}
|
|
1440
1383
|
|
|
1441
1384
|
const allSessionStartCommands = (shippedHooksConfig.hooks.SessionStart || [])
|
|
1442
1385
|
.flatMap(group => group.hooks.map(hook => hook.command));
|
|
@@ -2183,6 +2126,22 @@ function createSettingsFixture(settings, indentWidth) {
|
|
|
2183
2126
|
}
|
|
2184
2127
|
|
|
2185
2128
|
|
|
2129
|
+
/**
|
|
2130
|
+
* Build the ownership one sandbox's own ~/.claude/hooks root supplies.
|
|
2131
|
+
*
|
|
2132
|
+
* A retirement walk that falls back to its own defaults reads the installed hook
|
|
2133
|
+
* roots and the home directory of the machine running the test, so the answer
|
|
2134
|
+
* changes with the host. Handing every walk a sandbox ownership keeps the test
|
|
2135
|
+
* reading the fixture it wrote and nothing else.
|
|
2136
|
+
*
|
|
2137
|
+
* @param {string} sandboxRoot The sandbox home the `$HOME` and `~` spellings expand to.
|
|
2138
|
+
* @returns {{allOwnedHookRootKeys: Set<string>, homeDirectory: string}} The ownership.
|
|
2139
|
+
*/
|
|
2140
|
+
function sandboxClaudeHooksOwnership(sandboxRoot) {
|
|
2141
|
+
return sandboxHookOwnership(sandboxRoot, [join(sandboxRoot, '.claude', 'hooks')]);
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
|
|
2186
2145
|
test('pruneRetiredHookEntriesFromSettings removes the retired entry and keeps the user-authored one', () => {
|
|
2187
2146
|
const retiredCommand = `python3 "$HOME/.claude/hooks/${RETIRED_HOOK_RELATIVE_PATH}"`;
|
|
2188
2147
|
const userCommand = 'python3 my_own_gate.py --user-authored';
|
|
@@ -2205,7 +2164,9 @@ test('pruneRetiredHookEntriesFromSettings removes the retired entry and keeps th
|
|
|
2205
2164
|
}, 4);
|
|
2206
2165
|
try {
|
|
2207
2166
|
const removedCount = pruneRetiredHookEntriesFromSettings(
|
|
2208
|
-
settingsPath,
|
|
2167
|
+
settingsPath,
|
|
2168
|
+
new Set([RETIRED_HOOK_RELATIVE_PATH]),
|
|
2169
|
+
sandboxClaudeHooksOwnership(sandboxRoot),
|
|
2209
2170
|
);
|
|
2210
2171
|
|
|
2211
2172
|
assert.equal(removedCount, 2, 'both entries running the retired script are removed');
|
|
@@ -2250,7 +2211,9 @@ test('pruneRetiredHookEntriesFromSettings keeps every settings shape the install
|
|
|
2250
2211
|
}, 4);
|
|
2251
2212
|
try {
|
|
2252
2213
|
const removedCount = pruneRetiredHookEntriesFromSettings(
|
|
2253
|
-
settingsPath,
|
|
2214
|
+
settingsPath,
|
|
2215
|
+
new Set([RETIRED_HOOK_RELATIVE_PATH]),
|
|
2216
|
+
sandboxClaudeHooksOwnership(sandboxRoot),
|
|
2254
2217
|
);
|
|
2255
2218
|
|
|
2256
2219
|
assert.equal(removedCount, 1, 'the one entry running the retired script is the only removal');
|
|
@@ -2391,7 +2354,9 @@ test('pruneRetiredHookEntriesFromSettings leaves settings.json byte-identical wh
|
|
|
2391
2354
|
const bytesBefore = readFileSync(settingsPath, 'utf8');
|
|
2392
2355
|
|
|
2393
2356
|
const removedCount = pruneRetiredHookEntriesFromSettings(
|
|
2394
|
-
settingsPath,
|
|
2357
|
+
settingsPath,
|
|
2358
|
+
new Set([RETIRED_HOOK_RELATIVE_PATH]),
|
|
2359
|
+
sandboxClaudeHooksOwnership(sandboxRoot),
|
|
2395
2360
|
);
|
|
2396
2361
|
|
|
2397
2362
|
assert.equal(removedCount, 0, 'no entry runs a retired script');
|
|
@@ -2406,6 +2371,361 @@ test('pruneRetiredHookEntriesFromSettings leaves settings.json byte-identical wh
|
|
|
2406
2371
|
});
|
|
2407
2372
|
|
|
2408
2373
|
|
|
2374
|
+
const ALL_HOST_CONFIGURATION_INDENT_WIDTHS = [2, 4];
|
|
2375
|
+
|
|
2376
|
+
|
|
2377
|
+
/**
|
|
2378
|
+
* Build a settings fixture whose one matcher group registers a retired hook
|
|
2379
|
+
* beside a user-authored entry, so a prune removes one entry and rewrites the file.
|
|
2380
|
+
*
|
|
2381
|
+
* @param {string} relativePath The retired hook path the registration runs.
|
|
2382
|
+
* @returns {object} A settings object holding both entries.
|
|
2383
|
+
*/
|
|
2384
|
+
function settingsRegisteringRetiredAndUserHooks(relativePath) {
|
|
2385
|
+
return {
|
|
2386
|
+
hooks: {
|
|
2387
|
+
PreToolUse: [{
|
|
2388
|
+
matcher: 'Write|Edit',
|
|
2389
|
+
hooks: [
|
|
2390
|
+
{ type: 'command', command: `python3 "$HOME/.claude/hooks/${relativePath}"` },
|
|
2391
|
+
{ type: 'command', command: 'python3 my_own_gate.py --user-authored' },
|
|
2392
|
+
],
|
|
2393
|
+
}],
|
|
2394
|
+
},
|
|
2395
|
+
};
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
|
|
2399
|
+
test('pruneRetiredHookEntriesFromSettings writes back the indentation the host configuration file already used', () => {
|
|
2400
|
+
for (const indentWidth of ALL_HOST_CONFIGURATION_INDENT_WIDTHS) {
|
|
2401
|
+
const { settingsPath, sandboxRoot } = createSettingsFixture(
|
|
2402
|
+
settingsRegisteringRetiredAndUserHooks(RETIRED_HOOK_RELATIVE_PATH), indentWidth,
|
|
2403
|
+
);
|
|
2404
|
+
try {
|
|
2405
|
+
const removedCount = pruneRetiredHookEntriesFromSettings(
|
|
2406
|
+
settingsPath,
|
|
2407
|
+
new Set([RETIRED_HOOK_RELATIVE_PATH]),
|
|
2408
|
+
sandboxClaudeHooksOwnership(sandboxRoot),
|
|
2409
|
+
);
|
|
2410
|
+
|
|
2411
|
+
assert.equal(removedCount, 1, `the retired entry leaves the ${indentWidth}-space file`);
|
|
2412
|
+
const settingsText = readFileSync(settingsPath, 'utf8');
|
|
2413
|
+
assert.equal(
|
|
2414
|
+
settingsText,
|
|
2415
|
+
JSON.stringify(JSON.parse(settingsText), null, indentWidth) + '\n',
|
|
2416
|
+
`a file written with ${indentWidth} spaces is handed back with ${indentWidth} spaces`,
|
|
2417
|
+
);
|
|
2418
|
+
} finally {
|
|
2419
|
+
rmSync(sandboxRoot, { recursive: true, force: true });
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
});
|
|
2423
|
+
|
|
2424
|
+
|
|
2425
|
+
const SECOND_RETIRED_HOOK_RELATIVE_PATH = 'blocking/retired_second_gate.py';
|
|
2426
|
+
|
|
2427
|
+
|
|
2428
|
+
/**
|
|
2429
|
+
* Build a sandbox holding a ~/.claude/hooks root and a settings.json beside it.
|
|
2430
|
+
*
|
|
2431
|
+
* @param {object} settings The settings object to serialize.
|
|
2432
|
+
* @returns {{sandboxRoot: string, hooksRoot: string, settingsPath: string}} The sandbox paths.
|
|
2433
|
+
*/
|
|
2434
|
+
function createRetiredHookSandbox(settings) {
|
|
2435
|
+
const sandboxRoot = mkdtempSync(join(tmpdir(), 'cdev-retired-hook-stand-in-'));
|
|
2436
|
+
const hooksRoot = join(sandboxRoot, '.claude', 'hooks');
|
|
2437
|
+
mkdirSync(hooksRoot, { recursive: true });
|
|
2438
|
+
const settingsPath = join(sandboxRoot, 'settings.json');
|
|
2439
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 4) + '\n');
|
|
2440
|
+
return { sandboxRoot, hooksRoot, settingsPath };
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
|
|
2444
|
+
/**
|
|
2445
|
+
* Build a settings fixture registering one retired hook command.
|
|
2446
|
+
*
|
|
2447
|
+
* @param {string} relativePath The retired hook path the registration runs.
|
|
2448
|
+
* @returns {object} A settings object holding that registration and a user entry.
|
|
2449
|
+
*/
|
|
2450
|
+
function settingsRegistering(relativePath) {
|
|
2451
|
+
return {
|
|
2452
|
+
hooks: {
|
|
2453
|
+
PreToolUse: [{
|
|
2454
|
+
matcher: 'AskUserQuestion',
|
|
2455
|
+
hooks: [
|
|
2456
|
+
{ type: 'command', command: `python3 "$HOME/.claude/hooks/${relativePath}"` },
|
|
2457
|
+
{ type: 'command', command: 'python3 my_own_gate.py --user-authored' },
|
|
2458
|
+
],
|
|
2459
|
+
}],
|
|
2460
|
+
},
|
|
2461
|
+
};
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
|
|
2465
|
+
test('retiredHookPathsNamedByCommands names only the retired scripts a live registration runs', () => {
|
|
2466
|
+
const { sandboxRoot, settingsPath } = createRetiredHookSandbox(
|
|
2467
|
+
settingsRegistering(RETIRED_HOOK_RELATIVE_PATH),
|
|
2468
|
+
);
|
|
2469
|
+
try {
|
|
2470
|
+
const namedPaths = retiredHookPathsNamedByCommands(
|
|
2471
|
+
settingsHookCommandsAtPath(settingsPath),
|
|
2472
|
+
new Set([RETIRED_HOOK_RELATIVE_PATH, SECOND_RETIRED_HOOK_RELATIVE_PATH]),
|
|
2473
|
+
sandboxClaudeHooksOwnership(sandboxRoot),
|
|
2474
|
+
);
|
|
2475
|
+
|
|
2476
|
+
assert.deepEqual(
|
|
2477
|
+
[...namedPaths],
|
|
2478
|
+
[RETIRED_HOOK_RELATIVE_PATH],
|
|
2479
|
+
'the registered retired script is named and the unregistered one is left out',
|
|
2480
|
+
);
|
|
2481
|
+
} finally {
|
|
2482
|
+
rmSync(sandboxRoot, { recursive: true, force: true });
|
|
2483
|
+
}
|
|
2484
|
+
});
|
|
2485
|
+
|
|
2486
|
+
|
|
2487
|
+
test('settingsHookCommandsAtPath reads no command from a settings file it cannot use', () => {
|
|
2488
|
+
const { sandboxRoot, settingsPath } = createRetiredHookSandbox({ hooks: {} });
|
|
2489
|
+
try {
|
|
2490
|
+
writeFileSync(settingsPath, '{ this is not JSON');
|
|
2491
|
+
|
|
2492
|
+
assert.deepEqual(
|
|
2493
|
+
settingsHookCommandsAtPath(settingsPath),
|
|
2494
|
+
[],
|
|
2495
|
+
'settings the installer cannot parse hold no command',
|
|
2496
|
+
);
|
|
2497
|
+
assert.deepEqual(
|
|
2498
|
+
settingsHookCommandsAtPath(join(sandboxRoot, 'absent.json')),
|
|
2499
|
+
[],
|
|
2500
|
+
'a settings file that is not there holds no command',
|
|
2501
|
+
);
|
|
2502
|
+
assert.equal(
|
|
2503
|
+
retiredHookPathsNamedByCommands(
|
|
2504
|
+
[],
|
|
2505
|
+
new Set([RETIRED_HOOK_RELATIVE_PATH]),
|
|
2506
|
+
sandboxClaudeHooksOwnership(sandboxRoot),
|
|
2507
|
+
).size,
|
|
2508
|
+
0,
|
|
2509
|
+
'with no command to read, no retired script is named',
|
|
2510
|
+
);
|
|
2511
|
+
} finally {
|
|
2512
|
+
rmSync(sandboxRoot, { recursive: true, force: true });
|
|
2513
|
+
}
|
|
2514
|
+
});
|
|
2515
|
+
|
|
2516
|
+
|
|
2517
|
+
test('a stale registration keeps working because the retired hook leaves an inert stand-in behind', () => {
|
|
2518
|
+
const { sandboxRoot, hooksRoot, settingsPath } = createRetiredHookSandbox(
|
|
2519
|
+
settingsRegistering(RETIRED_HOOK_RELATIVE_PATH),
|
|
2520
|
+
);
|
|
2521
|
+
try {
|
|
2522
|
+
const namedPaths = retiredHookPathsNamedByCommands(
|
|
2523
|
+
settingsHookCommandsAtPath(settingsPath),
|
|
2524
|
+
new Set([RETIRED_HOOK_RELATIVE_PATH, SECOND_RETIRED_HOOK_RELATIVE_PATH]),
|
|
2525
|
+
sandboxClaudeHooksOwnership(sandboxRoot),
|
|
2526
|
+
);
|
|
2527
|
+
|
|
2528
|
+
const standInPaths = writeRetiredHookStandIns(hooksRoot, namedPaths);
|
|
2529
|
+
|
|
2530
|
+
const standInPath = join(hooksRoot, ...RETIRED_HOOK_RELATIVE_PATH.split('/'));
|
|
2531
|
+
assert.deepEqual(standInPaths, [standInPath], 'one stand-in is written, for the registered path');
|
|
2532
|
+
assert.equal(
|
|
2533
|
+
existsSync(join(hooksRoot, ...SECOND_RETIRED_HOOK_RELATIVE_PATH.split('/'))),
|
|
2534
|
+
false,
|
|
2535
|
+
'a retired path no registration names gets no stand-in',
|
|
2536
|
+
);
|
|
2537
|
+
|
|
2538
|
+
const standInRun = spawnSync(detectPython(), [standInPath], { encoding: 'utf8' });
|
|
2539
|
+
|
|
2540
|
+
assert.equal(standInRun.status, 0, 'the stand-in exits 0, so a stale registration allows the tool');
|
|
2541
|
+
assert.equal(standInRun.stdout, '', 'the stand-in writes nothing to stdout');
|
|
2542
|
+
assert.equal(standInRun.stderr, '', 'the stand-in writes nothing to stderr');
|
|
2543
|
+
} finally {
|
|
2544
|
+
rmSync(sandboxRoot, { recursive: true, force: true });
|
|
2545
|
+
}
|
|
2546
|
+
});
|
|
2547
|
+
|
|
2548
|
+
|
|
2549
|
+
test('writeRetiredHookStandIns leaves a hook file the prune could not move exactly as it stands', () => {
|
|
2550
|
+
const { sandboxRoot, hooksRoot } = createRetiredHookSandbox({ hooks: {} });
|
|
2551
|
+
try {
|
|
2552
|
+
const survivingPath = join(hooksRoot, ...RETIRED_HOOK_RELATIVE_PATH.split('/'));
|
|
2553
|
+
mkdirSync(dirname(survivingPath), { recursive: true });
|
|
2554
|
+
const survivingContents = 'raise SystemExit(2)\n';
|
|
2555
|
+
writeFileSync(survivingPath, survivingContents);
|
|
2556
|
+
|
|
2557
|
+
const standInPaths = writeRetiredHookStandIns(
|
|
2558
|
+
hooksRoot, new Set([RETIRED_HOOK_RELATIVE_PATH]),
|
|
2559
|
+
);
|
|
2560
|
+
|
|
2561
|
+
assert.deepEqual(standInPaths, [], 'a path still holding a file gets no stand-in');
|
|
2562
|
+
assert.equal(
|
|
2563
|
+
readFileSync(survivingPath, 'utf8'),
|
|
2564
|
+
survivingContents,
|
|
2565
|
+
'the file a failed prune left behind keeps its own bytes',
|
|
2566
|
+
);
|
|
2567
|
+
} finally {
|
|
2568
|
+
rmSync(sandboxRoot, { recursive: true, force: true });
|
|
2569
|
+
}
|
|
2570
|
+
});
|
|
2571
|
+
|
|
2572
|
+
|
|
2573
|
+
const AGENTS_HOOKS_ROOT_SEGMENTS = ['.agents', 'hooks'];
|
|
2574
|
+
const CODEX_HOOKS_ROOT_SEGMENTS = ['.codex', 'hooks'];
|
|
2575
|
+
const FOREIGN_HOOKS_ROOT_SEGMENTS = ['other-tool', '.claude', 'hooks'];
|
|
2576
|
+
|
|
2577
|
+
|
|
2578
|
+
/**
|
|
2579
|
+
* Build the ownership one retirement walk reads for one sandbox.
|
|
2580
|
+
*
|
|
2581
|
+
* @param {string} sandboxRoot The sandbox home the `$HOME` spelling expands to.
|
|
2582
|
+
* @param {string[]} allHookRootPaths The hooks directories the installer owns there.
|
|
2583
|
+
* @returns {{allOwnedHookRootKeys: Set<string>, homeDirectory: string}} The ownership.
|
|
2584
|
+
*/
|
|
2585
|
+
function sandboxHookOwnership(sandboxRoot, allHookRootPaths) {
|
|
2586
|
+
return {
|
|
2587
|
+
allOwnedHookRootKeys: ownedHookRootComparisonKeys(allHookRootPaths),
|
|
2588
|
+
homeDirectory: sandboxRoot,
|
|
2589
|
+
};
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
|
|
2593
|
+
/**
|
|
2594
|
+
* Build the command one hook registration runs for a script under a hooks root.
|
|
2595
|
+
*
|
|
2596
|
+
* @param {string} hooksRoot The hooks directory the registration names.
|
|
2597
|
+
* @param {string} relativePath The script path under that directory.
|
|
2598
|
+
* @returns {string} The registration command.
|
|
2599
|
+
*/
|
|
2600
|
+
function hookCommandUnder(hooksRoot, relativePath) {
|
|
2601
|
+
const scriptPath = join(hooksRoot, ...relativePath.split('/')).replace(/\\/g, '/');
|
|
2602
|
+
return `python3 "${scriptPath}"`;
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
|
|
2606
|
+
test('a retired registration spelled under the agents hooks root is named, because both roots are owned', () => {
|
|
2607
|
+
const sandboxRoot = mkdtempSync(join(tmpdir(), 'cdev-owned-hook-roots-'));
|
|
2608
|
+
try {
|
|
2609
|
+
const claudeHooksRoot = join(sandboxRoot, '.claude', 'hooks');
|
|
2610
|
+
const agentsHooksRoot = join(sandboxRoot, ...AGENTS_HOOKS_ROOT_SEGMENTS);
|
|
2611
|
+
const codexHooksRoot = join(sandboxRoot, ...CODEX_HOOKS_ROOT_SEGMENTS);
|
|
2612
|
+
|
|
2613
|
+
const namedPaths = retiredHookPathsNamedByCommands(
|
|
2614
|
+
[
|
|
2615
|
+
hookCommandUnder(agentsHooksRoot, RETIRED_HOOK_RELATIVE_PATH),
|
|
2616
|
+
hookCommandUnder(codexHooksRoot, SECOND_RETIRED_HOOK_RELATIVE_PATH),
|
|
2617
|
+
],
|
|
2618
|
+
new Set([RETIRED_HOOK_RELATIVE_PATH, SECOND_RETIRED_HOOK_RELATIVE_PATH]),
|
|
2619
|
+
sandboxHookOwnership(
|
|
2620
|
+
sandboxRoot, [claudeHooksRoot, agentsHooksRoot, codexHooksRoot],
|
|
2621
|
+
),
|
|
2622
|
+
);
|
|
2623
|
+
|
|
2624
|
+
assert.deepEqual(
|
|
2625
|
+
[...namedPaths].sort(),
|
|
2626
|
+
[RETIRED_HOOK_RELATIVE_PATH, SECOND_RETIRED_HOOK_RELATIVE_PATH].sort(),
|
|
2627
|
+
'a registration under any owned hooks root names its retired script',
|
|
2628
|
+
);
|
|
2629
|
+
} finally {
|
|
2630
|
+
rmSync(sandboxRoot, { recursive: true, force: true });
|
|
2631
|
+
}
|
|
2632
|
+
});
|
|
2633
|
+
|
|
2634
|
+
|
|
2635
|
+
test('a retired registration reaching an owned root through a junction is named', () => {
|
|
2636
|
+
const sandboxRoot = mkdtempSync(join(tmpdir(), 'cdev-owned-hook-junction-'));
|
|
2637
|
+
try {
|
|
2638
|
+
const agentsHooksRoot = join(sandboxRoot, ...AGENTS_HOOKS_ROOT_SEGMENTS);
|
|
2639
|
+
const claudeHooksRoot = join(sandboxRoot, '.claude', 'hooks');
|
|
2640
|
+
mkdirSync(agentsHooksRoot, { recursive: true });
|
|
2641
|
+
mkdirSync(dirname(claudeHooksRoot), { recursive: true });
|
|
2642
|
+
symlinkSync(agentsHooksRoot, claudeHooksRoot, 'junction');
|
|
2643
|
+
|
|
2644
|
+
const namedPaths = retiredHookPathsNamedByCommands(
|
|
2645
|
+
[hookCommandUnder(agentsHooksRoot, RETIRED_HOOK_RELATIVE_PATH)],
|
|
2646
|
+
new Set([RETIRED_HOOK_RELATIVE_PATH]),
|
|
2647
|
+
sandboxHookOwnership(sandboxRoot, [claudeHooksRoot]),
|
|
2648
|
+
);
|
|
2649
|
+
|
|
2650
|
+
assert.deepEqual(
|
|
2651
|
+
[...namedPaths],
|
|
2652
|
+
[RETIRED_HOOK_RELATIVE_PATH],
|
|
2653
|
+
'the junction and the directory it reaches are one owned hooks root',
|
|
2654
|
+
);
|
|
2655
|
+
} finally {
|
|
2656
|
+
rmSync(sandboxRoot, { recursive: true, force: true });
|
|
2657
|
+
}
|
|
2658
|
+
});
|
|
2659
|
+
|
|
2660
|
+
|
|
2661
|
+
test('a registration under a hooks root the installer does not own is never named or pruned', () => {
|
|
2662
|
+
const sandboxRoot = mkdtempSync(join(tmpdir(), 'cdev-foreign-hook-root-'));
|
|
2663
|
+
try {
|
|
2664
|
+
const claudeHooksRoot = join(sandboxRoot, '.claude', 'hooks');
|
|
2665
|
+
const foreignHooksRoot = join(sandboxRoot, ...FOREIGN_HOOKS_ROOT_SEGMENTS);
|
|
2666
|
+
const foreignCommand = hookCommandUnder(foreignHooksRoot, RETIRED_HOOK_RELATIVE_PATH);
|
|
2667
|
+
const settingsPath = join(sandboxRoot, 'settings.json');
|
|
2668
|
+
writeFileSync(settingsPath, JSON.stringify({
|
|
2669
|
+
hooks: {
|
|
2670
|
+
PreToolUse: [{
|
|
2671
|
+
matcher: '*',
|
|
2672
|
+
hooks: [{ type: 'command', command: foreignCommand }],
|
|
2673
|
+
}],
|
|
2674
|
+
},
|
|
2675
|
+
}, null, 4) + '\n');
|
|
2676
|
+
const ownership = sandboxHookOwnership(sandboxRoot, [claudeHooksRoot]);
|
|
2677
|
+
|
|
2678
|
+
const namedPaths = retiredHookPathsNamedByCommands(
|
|
2679
|
+
[foreignCommand], new Set([RETIRED_HOOK_RELATIVE_PATH]), ownership,
|
|
2680
|
+
);
|
|
2681
|
+
const removedCount = pruneRetiredHookEntriesFromSettings(
|
|
2682
|
+
settingsPath, new Set([RETIRED_HOOK_RELATIVE_PATH]), ownership,
|
|
2683
|
+
);
|
|
2684
|
+
|
|
2685
|
+
assert.equal(namedPaths.size, 0, 'a foreign registration earns no stand-in');
|
|
2686
|
+
assert.equal(removedCount, 0, 'a foreign registration is never pruned');
|
|
2687
|
+
assert.equal(
|
|
2688
|
+
JSON.parse(readFileSync(settingsPath, 'utf8')).hooks.PreToolUse[0].hooks[0].command,
|
|
2689
|
+
foreignCommand,
|
|
2690
|
+
'the foreign entry stays exactly as the other tool wrote it',
|
|
2691
|
+
);
|
|
2692
|
+
} finally {
|
|
2693
|
+
rmSync(sandboxRoot, { recursive: true, force: true });
|
|
2694
|
+
}
|
|
2695
|
+
});
|
|
2696
|
+
|
|
2697
|
+
|
|
2698
|
+
const SANDBOX_HOME_DIRECTORY = process.platform === 'win32'
|
|
2699
|
+
? 'c:/cdev-sandbox-home'
|
|
2700
|
+
: '/cdev-sandbox-home';
|
|
2701
|
+
|
|
2702
|
+
|
|
2703
|
+
test('a hook command spelled with a leading tilde expands to the home directory the tilde stands for', () => {
|
|
2704
|
+
const tildeCommand = `python3 ~/.claude/hooks/${RETIRED_HOOK_RELATIVE_PATH}`;
|
|
2705
|
+
|
|
2706
|
+
const comparisonSpelling = hookCommandComparisonSpelling(
|
|
2707
|
+
tildeCommand, SANDBOX_HOME_DIRECTORY,
|
|
2708
|
+
);
|
|
2709
|
+
|
|
2710
|
+
assert.equal(
|
|
2711
|
+
comparisonSpelling,
|
|
2712
|
+
`python3 ${SANDBOX_HOME_DIRECTORY}/.claude/hooks/${RETIRED_HOOK_RELATIVE_PATH}`,
|
|
2713
|
+
'the home directory replaces the tilde rather than following it',
|
|
2714
|
+
);
|
|
2715
|
+
assert.ok(
|
|
2716
|
+
commandNamesOwnedHookScript(
|
|
2717
|
+
tildeCommand,
|
|
2718
|
+
RETIRED_HOOK_RELATIVE_PATH,
|
|
2719
|
+
sandboxHookOwnership(
|
|
2720
|
+
SANDBOX_HOME_DIRECTORY,
|
|
2721
|
+
[join(SANDBOX_HOME_DIRECTORY, '.claude', 'hooks')],
|
|
2722
|
+
),
|
|
2723
|
+
),
|
|
2724
|
+
'the expanded command reaches the owned hooks root',
|
|
2725
|
+
);
|
|
2726
|
+
});
|
|
2727
|
+
|
|
2728
|
+
|
|
2409
2729
|
const PRIOR_RUN_BACKUP_NAMES = ['2020-01-01T00-00-00-000Z', '2021-06-15T12-30-45-123Z'];
|
|
2410
2730
|
const THIS_RUN_BACKUP_NAME = '2026-07-25T18-04-11-923Z';
|
|
2411
2731
|
const MOVED_SKILL_BACKUP_SEGMENTS = ['skills', 'demo'];
|
|
@@ -2541,3 +2861,100 @@ test('pruneStaleInstalledFiles leaves the run backup root standing when the rena
|
|
|
2541
2861
|
rmSync(sandbox.root, { recursive: true, force: true });
|
|
2542
2862
|
}
|
|
2543
2863
|
});
|
|
2864
|
+
|
|
2865
|
+
test('the pstack step writes a manifest for a skills root that carries pstack', () => {
|
|
2866
|
+
const skillsRoot = mkdtempSync(join(tmpdir(), 'cdev-pstack-step-'));
|
|
2867
|
+
try {
|
|
2868
|
+
const skillDirectory = join(skillsRoot, 'pstack', 'how');
|
|
2869
|
+
mkdirSync(skillDirectory, { recursive: true });
|
|
2870
|
+
writeFileSync(join(skillDirectory, 'SKILL.md'), '---\nname: how\n---\n');
|
|
2871
|
+
|
|
2872
|
+
const manifestPath = refreshInstalledPstackPluginManifest(skillsRoot);
|
|
2873
|
+
|
|
2874
|
+
assert.equal(
|
|
2875
|
+
manifestPath,
|
|
2876
|
+
join(skillsRoot, 'pstack', '.claude-plugin', 'plugin.json'),
|
|
2877
|
+
'the step reports the manifest it wrote so the run records the path',
|
|
2878
|
+
);
|
|
2879
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
2880
|
+
assert.deepEqual(manifest.skills, ['./how']);
|
|
2881
|
+
assert.equal(
|
|
2882
|
+
refreshInstalledPstackPluginManifest(skillsRoot),
|
|
2883
|
+
null,
|
|
2884
|
+
'a second run writes nothing and reports no path',
|
|
2885
|
+
);
|
|
2886
|
+
} finally {
|
|
2887
|
+
rmSync(skillsRoot, { recursive: true, force: true });
|
|
2888
|
+
}
|
|
2889
|
+
});
|
|
2890
|
+
|
|
2891
|
+
test('the pstack step reports no path for a skills root without pstack', () => {
|
|
2892
|
+
const skillsRoot = mkdtempSync(join(tmpdir(), 'cdev-pstack-absent-'));
|
|
2893
|
+
try {
|
|
2894
|
+
assert.equal(refreshInstalledPstackPluginManifest(skillsRoot), null);
|
|
2895
|
+
assert.equal(existsSync(join(skillsRoot, 'pstack')), false);
|
|
2896
|
+
} finally {
|
|
2897
|
+
rmSync(skillsRoot, { recursive: true, force: true });
|
|
2898
|
+
}
|
|
2899
|
+
});
|
|
2900
|
+
|
|
2901
|
+
const RETIRED_SKILLS = ['pr-small-cl', 'session-log', 'session-tidy'];
|
|
2902
|
+
|
|
2903
|
+
test('each core skill has a shipped entry point', () => {
|
|
2904
|
+
for (const skillName of CORE_SKILLS) {
|
|
2905
|
+
assert.ok(existsSync(new URL(`../.agents/skills/${skillName}/SKILL.md`, import.meta.url)), skillName);
|
|
2906
|
+
}
|
|
2907
|
+
});
|
|
2908
|
+
|
|
2909
|
+
test('retired skills leave install groups and stay in the cleanup registry', () => {
|
|
2910
|
+
const selectedSkills = Object.values(INSTALL_GROUPS).flatMap(group => group.skills ?? []);
|
|
2911
|
+
assert.equal(Object.hasOwn(INSTALL_GROUPS, 'journal'), false);
|
|
2912
|
+
for (const skillName of RETIRED_SKILLS) {
|
|
2913
|
+
assert.equal(selectedSkills.includes(skillName), false, skillName);
|
|
2914
|
+
assert.ok(EVER_SHIPPED_SKILL_NAMES.has(skillName), skillName);
|
|
2915
|
+
}
|
|
2916
|
+
});
|
|
2917
|
+
|
|
2918
|
+
test('README and vault guidance use current session tools', () => {
|
|
2919
|
+
const readme = readFileSync(new URL('../../../README.md', import.meta.url), 'utf8');
|
|
2920
|
+
const vaultRule = readFileSync(new URL('../rules/vault-context.md', import.meta.url), 'utf8');
|
|
2921
|
+
assert.doesNotMatch(readme, /--only journal|session-log|session-tidy/);
|
|
2922
|
+
assert.doesNotMatch(vaultRule, /`session-log`|`recall`/);
|
|
2923
|
+
assert.match(vaultRule, /obsidian MCP tools/);
|
|
2924
|
+
assert.match(vaultRule, /project.*frontmatter/);
|
|
2925
|
+
});
|
|
2926
|
+
|
|
2927
|
+
test('the help output states that the installer reads only flags', () => {
|
|
2928
|
+
const helpRun = spawnSync(
|
|
2929
|
+
process.execPath,
|
|
2930
|
+
[fileURLToPath(new URL('./install.mjs', import.meta.url)), '--help'],
|
|
2931
|
+
{ encoding: 'utf8', env: process.env },
|
|
2932
|
+
);
|
|
2933
|
+
|
|
2934
|
+
assert.equal(helpRun.status, 0, helpRun.stderr);
|
|
2935
|
+
assert.match(helpRun.stdout, /reads only flags/i);
|
|
2936
|
+
assert.match(helpRun.stdout, /bare path argument carries no meaning/i);
|
|
2937
|
+
});
|
|
2938
|
+
|
|
2939
|
+
|
|
2940
|
+
|
|
2941
|
+
test('pruneRetiredHookEntriesFromSettings warns and returns 0 when the host configuration file cannot be read', () => {
|
|
2942
|
+
const sandboxRoot = mkdtempSync(join(tmpdir(), 'cdev-unreadable-host-configuration-'));
|
|
2943
|
+
const unreadableConfigurationPath = join(sandboxRoot, 'hooks.json');
|
|
2944
|
+
mkdirSync(unreadableConfigurationPath);
|
|
2945
|
+
try {
|
|
2946
|
+
const { returnedValue, allWarnings } = captureWarnings(() => (
|
|
2947
|
+
pruneRetiredHookEntriesFromSettings(
|
|
2948
|
+
unreadableConfigurationPath,
|
|
2949
|
+
new Set([RETIRED_HOOK_RELATIVE_PATH]),
|
|
2950
|
+
sandboxClaudeHooksOwnership(sandboxRoot),
|
|
2951
|
+
)
|
|
2952
|
+
));
|
|
2953
|
+
|
|
2954
|
+
assert.equal(returnedValue, 0, 'an unreadable file removes no entry and ends the walk of that file');
|
|
2955
|
+
assert.equal(allWarnings.length, 1, 'the unreadable file is reported once');
|
|
2956
|
+
assert.match(allWarnings[0], /hooks\.json/, 'the warning names the file it left alone');
|
|
2957
|
+
} finally {
|
|
2958
|
+
rmSync(sandboxRoot, { recursive: true, force: true });
|
|
2959
|
+
}
|
|
2960
|
+
});
|