claude-dev-env 4.2.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/agents/pr-description-writer.md +2 -2
- package/.agents/agents/test_agent_frontmatter.py +2 -2
- package/.agents/agents/test_pr_description_writer_style.py +1 -3
- package/.agents/skills/e-code-review/SKILL.md +12 -12
- package/.agents/skills/e-code-review/preflight-proposal.contract.test.mjs +20 -0
- package/.agents/skills/e-code-review/reference/fix.md +15 -17
- package/.agents/skills/e-code-review/reference/loop.md +35 -39
- package/.agents/skills/e-code-review/reference/mode-contract.test.mjs +2 -1
- package/.agents/skills/e-code-review/reference/preflight-proposal.md +44 -9
- package/.agents/skills/e-code-review/reference/runner-selection.md +13 -14
- package/.agents/skills/issue-tracker/SKILL.md +7 -0
- package/.agents/skills/pull-request/SKILL.md +216 -0
- package/.agents/skills/pull-request/reference/publication-tasks.md +18 -0
- package/.agents/skills/pull-request/scripts/github_pr_command_constants/__init__.py +1 -0
- package/.agents/skills/pull-request/scripts/github_pr_command_constants/config/__init__.py +1 -0
- package/.agents/skills/pull-request/scripts/github_pr_command_constants/config/constants.py +69 -0
- package/.agents/skills/pull-request/scripts/pull_request.py +265 -0
- package/.agents/skills/pull-request/scripts/recover_legacy_author.py +251 -0
- package/.agents/skills/pull-request/scripts/test_github_pr_command_constants.py +58 -0
- package/.agents/skills/pull-request/scripts/test_pull_request.py +396 -0
- package/.agents/skills/pull-request/scripts/test_recover_legacy_author.py +221 -0
- package/.agents/skills/skill-builder/SKILL.md +3 -196
- package/.agents/skills/test-runner/SKILL.md +69 -0
- package/.agents/skills/test-runner/scripts/preflight_checks.py +365 -0
- package/.agents/skills/test-runner/scripts/run_tests.py +316 -0
- package/.agents/skills/test-runner/scripts/test_preflight_checks.py +185 -0
- package/.agents/skills/test-runner/scripts/test_run_tests.py +281 -0
- package/.agents/skills/test-runner/scripts/test_runner_constants/__init__.py +1 -0
- package/.agents/skills/test-runner/scripts/test_runner_constants/config/__init__.py +1 -0
- package/.agents/skills/test-runner/scripts/test_runner_constants/config/constants.py +96 -0
- package/.agents/skills-archived/pr-loop-cloud-transport/reference/identity-and-hooks.md +1 -1
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +6 -0
- package/_shared/pr-loop/scripts/terminology_sweep.py +64 -19
- package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +59 -0
- package/bin/ever-shipped-skills.mjs +2 -0
- package/bin/ever-shipped-skills.test.mjs +8 -0
- package/bin/install.agents-home.test.mjs +15 -13
- package/bin/install.mjs +37 -1
- package/bin/install.policy-lint.test.mjs +78 -0
- package/bin/install.prune.test.mjs +149 -20
- package/bin/install.pstack-plugin.test.mjs +114 -0
- package/bin/install.test.mjs +61 -7
- package/bin/refresh-pstack-plugin-skills.test.mjs +211 -0
- package/hooks/advisory/pr_done_reminder.py +342 -0
- package/hooks/advisory/test_pr_done_reminder.py +249 -0
- package/hooks/blocking/bash_post_call_dispatcher.py +53 -6
- package/hooks/blocking/code_rules_paired_test.py +22 -15
- package/hooks/blocking/pii_prevention_blocker.py +32 -51
- package/hooks/blocking/test_bash_post_call_dispatcher.py +55 -17
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +1 -4
- package/hooks/blocking/test_code_rules_command_dispatch.py +2 -2
- package/hooks/blocking/test_code_rules_enforcer_paired_test.py +36 -29
- package/hooks/blocking/test_code_rules_js_resume_task_enumeration.py +4 -4
- package/hooks/blocking/test_code_rules_js_returns_object_schemaless.py +5 -5
- package/hooks/blocking/test_pii_prevention_blocker.py +37 -89
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +68 -133
- package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +20 -51
- package/hooks/blocking/test_precommit_code_rules_gate_native_owner.py +18 -18
- package/hooks/gh_post_body_texts.py +195 -0
- package/hooks/git-hooks/git_hooks_constants/staged_policy_lint.py +12 -0
- package/hooks/git-hooks/pre_commit.py +52 -11
- package/hooks/git-hooks/test_installed_policy_lint.py +159 -0
- package/hooks/git-hooks/test_native_policy_commit.py +87 -0
- package/hooks/git-hooks/test_pre_commit.py +246 -0
- package/hooks/hooks.json +1 -16
- package/hooks/hooks_constants/bash_post_call_dispatcher_constants.py +14 -2
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +0 -8
- package/hooks/hooks_constants/gh_post_body_texts_constants.py +12 -0
- package/hooks/hooks_constants/pr_done_reminder_constants.py +98 -0
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +10 -43
- package/hooks/hooks_constants/test_bash_post_call_dispatcher_constants.py +17 -7
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +0 -7
- package/hooks/hooks_constants/test_failure_recorder_constants.py +3 -3
- package/hooks/hooks_constants/test_gh_post_body_texts_constants.py +19 -0
- package/hooks/hooks_constants/test_pr_done_reminder_constants.py +40 -0
- package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +20 -32
- package/hooks/observability/test_failure_recorder.py +1 -1
- package/hooks/pyproject.toml +1 -1
- package/hooks/test_gh_post_body_texts.py +79 -0
- package/package.json +2 -2
- package/rules/durable-post-artifacts.md +32 -20
- package/rules/gh-cli-conventions.md +23 -13
- package/rules/git-workflow.md +21 -8
- package/scripts/check.ps1 +0 -1
- package/{.agents/skills/run-claude-dev-env/driver.mjs → scripts/ci/installer-lifecycle.mjs} +3 -28
- package/scripts/ci/installer-lifecycle.test.mjs +26 -0
- package/scripts/ci/windows-installer-lifecycle.ps1 +1 -11
- package/scripts/dev_env_scripts_constants/shared_tree_constants.py +4 -5
- package/scripts/durable_post_lint.py +206 -0
- package/scripts/durable_post_lint_config/config/constants.py +96 -0
- package/scripts/durable_post_lint_config/config/test_constants.py +25 -0
- package/scripts/policy_lint/adapter_configuration.py +53 -12
- package/scripts/policy_lint/adapter_detectors.py +13 -6
- package/scripts/policy_lint/adapters.py +7 -4
- package/scripts/policy_lint/config/constants.py +5 -0
- package/scripts/policy_lint/engine.py +48 -4
- package/scripts/policy_lint/model.py +1 -0
- package/scripts/policy_lint/registry.py +1 -1
- package/scripts/policy_lint/selection.py +2 -1
- package/scripts/policy_lint/selection_git.py +2 -0
- package/scripts/refresh_pstack_plugin_skills.mjs +200 -0
- package/scripts/repository_checks/config/constants.py +0 -5
- package/scripts/shared_tree_paths.py +32 -3
- package/scripts/test_durable_post_lint.py +332 -0
- package/scripts/test_shared_tree_paths.py +33 -0
- package/scripts/tests/test_adapter_configuration.py +54 -0
- package/scripts/tests/test_policy_lint_rules_archive.py +120 -0
- package/scripts/tests/test_policy_lint_rules_engine.py +25 -4
- package/scripts/tests/test_policy_lint_rules_installed_loader.py +29 -0
- package/scripts/tests/test_policy_lint_rules_registry.py +227 -13
- package/scripts/tests/test_policy_lint_selection.py +25 -2
- package/scripts/tests/test_policy_lint_selection_native_index.py +30 -0
- package/scripts/tests/test_rules.py +3 -7
- package/.agents/skills/_shared/pr-loop/.claude/CLAUDE.md +0 -1
- package/.agents/skills/_shared/pr-loop/AGENTS.md +0 -1
- package/.agents/skills/_shared/pr-loop/audit-contract.md +0 -5
- package/.agents/skills/_shared/pr-loop/audit-reply-template.md +0 -5
- package/.agents/skills/_shared/pr-loop/code-rules-gate.md +0 -5
- package/.agents/skills/_shared/pr-loop/fix-protocol.md +0 -5
- package/.agents/skills/_shared/pr-loop/gh-payloads.md +0 -5
- package/.agents/skills/_shared/pr-loop/portable-driver.md +0 -149
- package/.agents/skills/_shared/pr-loop/post-audit-thread-contract.md +0 -5
- package/.agents/skills/_shared/pr-loop/precatch-rubric.md +0 -5
- package/.agents/skills/_shared/pr-loop/preflight-proposal.contract.test.mjs +0 -119
- package/.agents/skills/_shared/pr-loop/preflight-proposal.md +0 -5
- package/.agents/skills/_shared/pr-loop/prompts/.claude/CLAUDE.md +0 -1
- package/.agents/skills/_shared/pr-loop/prompts/AGENTS.md +0 -1
- package/.agents/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +0 -324
- package/.agents/skills/_shared/pr-loop/scripts/.claude/CLAUDE.md +0 -1
- package/.agents/skills/_shared/pr-loop/scripts/AGENTS.md +0 -1
- package/.agents/skills/_shared/pr-loop/scripts/RUNTIME_SCRIPTS.md +0 -29
- package/.agents/skills/_shared/pr-loop/scripts/_cli_utils.py +0 -22
- package/.agents/skills/_shared/pr-loop/scripts/_path_resolver.py +0 -186
- package/.agents/skills/_shared/pr-loop/scripts/_xml_utils.py +0 -20
- package/.agents/skills/_shared/pr-loop/scripts/audit_category_schema.py +0 -355
- package/.agents/skills/_shared/pr-loop/scripts/build_audit_prompt.py +0 -289
- package/.agents/skills/_shared/pr-loop/scripts/build_converge_task_list.py +0 -310
- package/.agents/skills/_shared/pr-loop/scripts/build_fix_prompt.py +0 -292
- package/.agents/skills/_shared/pr-loop/scripts/init_loop_state.py +0 -134
- package/.agents/skills/_shared/pr-loop/scripts/portable_converge_driver.py +0 -1646
- package/.agents/skills/_shared/pr-loop/scripts/preflight_worktree.py +0 -392
- package/.agents/skills/_shared/pr-loop/scripts/select_converge_pacer.py +0 -215
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/.claude/CLAUDE.md +0 -1
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/AGENTS.md +0 -1
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/__init__.py +0 -0
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py +0 -37
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/converge_task_list_constants.py +0 -56
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/handoff_constants.py +0 -45
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/pacer_constants.py +0 -47
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +0 -157
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/portable_driver_constants.py +0 -177
- package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/preflight_constants.py +0 -70
- package/.agents/skills/_shared/pr-loop/scripts/teardown_worktrees.py +0 -172
- package/.agents/skills/_shared/pr-loop/scripts/test__path_resolver.py +0 -106
- package/.agents/skills/_shared/pr-loop/scripts/test_audit_category_schema.py +0 -94
- package/.agents/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +0 -499
- package/.agents/skills/_shared/pr-loop/scripts/test_build_converge_task_list.py +0 -140
- package/.agents/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +0 -262
- package/.agents/skills/_shared/pr-loop/scripts/test_init_loop_state.py +0 -71
- package/.agents/skills/_shared/pr-loop/scripts/test_portable_converge_driver.py +0 -1119
- package/.agents/skills/_shared/pr-loop/scripts/test_preflight_worktree.py +0 -287
- package/.agents/skills/_shared/pr-loop/scripts/test_select_converge_pacer.py +0 -207
- package/.agents/skills/_shared/pr-loop/scripts/test_teardown_worktrees.py +0 -59
- package/.agents/skills/_shared/pr-loop/scripts/test_write_handoff.py +0 -201
- package/.agents/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +0 -182
- package/.agents/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +0 -206
- package/.agents/skills/_shared/pr-loop/scripts/write_handoff.py +0 -309
- package/.agents/skills/_shared/pr-loop/state-schema.md +0 -5
- package/.agents/skills/_shared/pr-loop/worker-spawn.md +0 -5
- package/.agents/skills/autoconverge/.claude/CLAUDE.md +0 -1
- package/.agents/skills/autoconverge/AGENTS.md +0 -1
- package/.agents/skills/autoconverge/SKILL.md +0 -431
- package/.agents/skills/autoconverge/reference/.claude/CLAUDE.md +0 -1
- package/.agents/skills/autoconverge/reference/AGENTS.md +0 -1
- package/.agents/skills/autoconverge/reference/closing-report.md +0 -93
- package/.agents/skills/autoconverge/reference/convergence-gates.md +0 -31
- package/.agents/skills/autoconverge/reference/convergence.md +0 -219
- package/.agents/skills/autoconverge/reference/copilot-findings.md +0 -51
- package/.agents/skills/autoconverge/reference/gotchas.md +0 -66
- package/.agents/skills/autoconverge/reference/headless-safety.md +0 -44
- package/.agents/skills/autoconverge/reference/multi-pr.md +0 -128
- package/.agents/skills/autoconverge/reference/self-closing-loop.md +0 -84
- package/.agents/skills/autoconverge/reference/stop-conditions.md +0 -127
- package/.agents/skills/autoconverge/test_portable_pacer_gate.py +0 -54
- package/.agents/skills/autoconverge/workflow/.claude/CLAUDE.md +0 -1
- package/.agents/skills/autoconverge/workflow/AGENTS.md +0 -1
- package/.agents/skills/autoconverge/workflow/aggregate_runs.py +0 -371
- package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/.claude/CLAUDE.md +0 -1
- package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/AGENTS.md +0 -1
- package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/__init__.py +0 -0
- package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +0 -250
- package/.agents/skills/autoconverge/workflow/converge.clean-audit.test.mjs +0 -665
- package/.agents/skills/autoconverge/workflow/converge.codex-gate.test.mjs +0 -472
- package/.agents/skills/autoconverge/workflow/converge.contract.test.mjs +0 -64
- package/.agents/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +0 -1009
- package/.agents/skills/autoconverge/workflow/converge.fix-progress.test.mjs +0 -107
- package/.agents/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +0 -33
- package/.agents/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +0 -15
- package/.agents/skills/autoconverge/workflow/converge.mjs +0 -3132
- package/.agents/skills/autoconverge/workflow/converge.p2-advance.test.mjs +0 -202
- package/.agents/skills/autoconverge/workflow/converge.path-aware.test.mjs +0 -47
- package/.agents/skills/autoconverge/workflow/converge.precatch.test.mjs +0 -152
- package/.agents/skills/autoconverge/workflow/converge.run-input.test.mjs +0 -116
- package/.agents/skills/autoconverge/workflow/converge_multi.mjs +0 -169
- package/.agents/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +0 -173
- package/.agents/skills/autoconverge/workflow/convergence_summary.py +0 -110
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a11d903476b803493.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a26213978adeef6fb.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a3def0d15ed9d9110.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a41f41b1b708ee3b7.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a758b880abecc3ff7.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a8897b89656b1bd16.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ab1c2d3e4f5a6b7c8.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-abd463d744a1437bc.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ad19d027ae8ee1816.jsonl +0 -2
- package/.agents/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +0 -266
- package/.agents/skills/autoconverge/workflow/render_report.py +0 -1024
- package/.agents/skills/autoconverge/workflow/test_aggregate_runs.py +0 -134
- package/.agents/skills/autoconverge/workflow/test_convergence_summary.py +0 -149
- package/.agents/skills/autoconverge/workflow/test_render_report.py +0 -812
- package/.agents/skills/hitl/SKILL.md +0 -45
- package/.agents/skills/pr-cleanup/SKILL.md +0 -140
- package/.agents/skills/pr-cleanup/reference/process-inventory.md +0 -17
- package/.agents/skills/pr-cleanup/reference/task-seeds.md +0 -22
- package/.agents/skills/pr-name-by-capability/SKILL.md +0 -73
- package/.agents/skills/pr-name-by-capability/reference/fetch-commands.md +0 -29
- package/.agents/skills/pr-name-by-capability/reference/offense-examples.md +0 -32
- package/.agents/skills/pr-name-by-capability/reference/report-template.md +0 -29
- package/.agents/skills/pr-name-by-capability/reference/rule-checklist.md +0 -38
- package/.agents/skills/pr-name-by-capability/reference/task-seeds.md +0 -10
- package/.agents/skills/pr-plain-language-cleanup/SKILL.md +0 -93
- package/.agents/skills/pr-refinement/SKILL.md +0 -31
- package/.agents/skills/pr-shared-extraction/SKILL.md +0 -177
- package/.agents/skills/pr-shared-extraction/reference/examples.md +0 -46
- package/.agents/skills/pr-shared-extraction/reference/offense-taxonomy.md +0 -100
- package/.agents/skills/pr-small-cl/SKILL.md +0 -52
- package/.agents/skills/pr-title-description/SKILL.md +0 -96
- package/.agents/skills/prototype/SKILL.md +0 -85
- package/.agents/skills/prototype/reference/honest-limitations.md +0 -23
- package/.agents/skills/prototype/reference/promotion-tasks.md +0 -23
- package/.agents/skills/prototype/scripts/build_sandbox_settings.py +0 -249
- package/.agents/skills/prototype/scripts/conftest.py +0 -15
- package/.agents/skills/prototype/scripts/launch_sandbox.py +0 -205
- package/.agents/skills/prototype/scripts/probe_sandbox_safety.py +0 -311
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/__init__.py +0 -1
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/__init__.py +0 -0
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/build_sandbox_settings_constants.py +0 -41
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/launch_sandbox_constants.py +0 -23
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/probe_sandbox_safety_constants.py +0 -45
- package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/prototype_common_constants.py +0 -10
- package/.agents/skills/prototype/scripts/test_build_sandbox_settings.py +0 -275
- package/.agents/skills/prototype/scripts/test_launch_sandbox.py +0 -303
- package/.agents/skills/prototype/scripts/test_probe_sandbox_safety.py +0 -312
- package/.agents/skills/prototype/workflows/promotion.md +0 -25
- package/.agents/skills/prototype/workflows/sandbox.md +0 -35
- package/.agents/skills/rebase/.claude/CLAUDE.md +0 -1
- package/.agents/skills/rebase/AGENTS.md +0 -1
- package/.agents/skills/rebase/SKILL.md +0 -178
- package/.agents/skills/review-router/SKILL.md +0 -51
- package/.agents/skills/review-router/reference/route-policy.json +0 -7
- package/.agents/skills/review-router/reference/task-seeds.md +0 -1
- package/.agents/skills/review-router/scripts/conftest.py +0 -4
- package/.agents/skills/review-router/scripts/review_router.py +0 -50
- package/.agents/skills/review-router/scripts/review_router_cli.py +0 -211
- package/.agents/skills/review-router/scripts/review_router_constants/__init__.py +0 -1
- package/.agents/skills/review-router/scripts/review_router_constants/config/__init__.py +0 -0
- package/.agents/skills/review-router/scripts/review_router_constants/config/constants.py +0 -13
- package/.agents/skills/review-router/scripts/test_review_router.py +0 -23
- package/.agents/skills/review-router/scripts/test_review_router_cli.py +0 -47
- package/.agents/skills/review-tier/SKILL.md +0 -56
- package/.agents/skills/review-tier/reference/task-seeds.md +0 -1
- package/.agents/skills/review-tier/reference/tier-policy.json +0 -9
- package/.agents/skills/review-tier/scripts/conftest.py +0 -4
- package/.agents/skills/review-tier/scripts/review_tier.py +0 -359
- package/.agents/skills/review-tier/scripts/review_tier_constants/__init__.py +0 -1
- package/.agents/skills/review-tier/scripts/review_tier_constants/config/__init__.py +0 -0
- package/.agents/skills/review-tier/scripts/review_tier_constants/config/constants.py +0 -29
- package/.agents/skills/review-tier/scripts/test_generation.py +0 -159
- package/.agents/skills/review-tier/scripts/test_review_tier.py +0 -33
- package/.agents/skills/run-claude-dev-env/SKILL.md +0 -84
- package/.agents/skills/session-log/.claude/CLAUDE.md +0 -1
- package/.agents/skills/session-log/AGENTS.md +0 -1
- package/.agents/skills/session-log/SKILL.md +0 -170
- package/.agents/skills/session-log/templates/frontmatter.md +0 -40
- package/.agents/skills/session-tidy/.claude/CLAUDE.md +0 -1
- package/.agents/skills/session-tidy/AGENTS.md +0 -1
- package/.agents/skills/session-tidy/SKILL.md +0 -191
- package/.agents/skills/skill-builder/references/.claude/CLAUDE.md +0 -1
- package/.agents/skills/skill-builder/references/AGENTS.md +0 -1
- package/.agents/skills/skill-builder/references/delegation-map.md +0 -130
- package/.agents/skills/skill-builder/references/description-field.md +0 -111
- package/.agents/skills/skill-builder/references/deterministic-elements.md +0 -218
- package/.agents/skills/skill-builder/references/progressive-disclosure.md +0 -122
- package/.agents/skills/skill-builder/references/self-audit-checklist.md +0 -126
- package/.agents/skills/skill-builder/references/skill-modularity.md +0 -111
- package/.agents/skills/skill-builder/references/skill-types.md +0 -232
- package/.agents/skills/skill-builder/references/thariq-x-post-skills.json +0 -33
- package/.agents/skills/skill-builder/templates/.claude/CLAUDE.md +0 -1
- package/.agents/skills/skill-builder/templates/AGENTS.md +0 -1
- package/.agents/skills/skill-builder/templates/gap-analysis.md +0 -87
- package/.agents/skills/skill-builder/workflows/.claude/CLAUDE.md +0 -1
- package/.agents/skills/skill-builder/workflows/AGENTS.md +0 -1
- package/.agents/skills/skill-builder/workflows/improve-skill.md +0 -136
- package/.agents/skills/skill-builder/workflows/new-skill.md +0 -179
- package/.agents/skills/skill-builder/workflows/polish-skill.md +0 -139
- package/.agents/skills/source-command-sr-loop/SKILL.md +0 -82
- package/.agents/skills/update/.claude/CLAUDE.md +0 -1
- package/.agents/skills/update/AGENTS.md +0 -1
- package/.agents/skills/update/SKILL.md +0 -190
- package/_shared/pr-loop/AGENTS.md +0 -1
- package/_shared/pr-loop/CLAUDE.md +0 -1
- package/_shared/pr-loop/audit-contract.md +0 -183
- package/_shared/pr-loop/audit-reply-template.md +0 -151
- package/_shared/pr-loop/code-rules-gate.md +0 -65
- package/_shared/pr-loop/fix-protocol.md +0 -60
- package/_shared/pr-loop/gh-payloads.md +0 -75
- package/_shared/pr-loop/post-audit-thread-contract.md +0 -51
- package/_shared/pr-loop/precatch-rubric.md +0 -66
- package/_shared/pr-loop/preflight-proposal.md +0 -56
- package/_shared/pr-loop/scripts/post_audit_thread.py +0 -1242
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/post_audit_thread_constants.py +0 -199
- package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +0 -1115
- package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +0 -129
- package/_shared/pr-loop/state-schema.md +0 -45
- package/_shared/pr-loop/worker-spawn.md +0 -189
- package/hooks/_gh_pr_author_swap_utils.py +0 -1225
- package/hooks/blocking/conventional_pr_title_gate.py +0 -443
- package/hooks/blocking/gh_body_arg_blocker.py +0 -176
- package/hooks/blocking/gh_pr_author_enforcer.py +0 -487
- package/hooks/blocking/gh_pr_author_restore.py +0 -105
- package/hooks/blocking/pr_description_writer_gate.py +0 -256
- package/hooks/blocking/test_conventional_pr_title_gate.py +0 -640
- package/hooks/blocking/test_gh_body_arg_blocker.py +0 -441
- package/hooks/blocking/test_gh_pr_author_enforcer.py +0 -1166
- package/hooks/blocking/test_gh_pr_author_restore.py +0 -513
- package/hooks/blocking/test_gh_pr_author_swap_utils.py +0 -910
- package/hooks/blocking/test_pr_description_writer_gate.py +0 -234
- package/hooks/blocking/test_preflight_check.py +0 -343
- package/hooks/blocking/test_test_preflight_check.py +0 -80
- package/hooks/blocking/test_volatile_path_in_post_blocker.py +0 -322
- package/hooks/blocking/volatile_path_in_post_blocker.py +0 -461
- package/hooks/hooks_constants/conventional_pr_title_gate_constants.py +0 -58
- package/hooks/hooks_constants/gh_pr_author_swap_constants.py +0 -76
- package/hooks/hooks_constants/pr_description_writer_gate_constants.py +0 -73
- package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +0 -55
- package/hooks/observability/pr_description_writer_spawn_tracker.py +0 -113
- package/hooks/observability/test_pr_description_writer_spawn_tracker.py +0 -150
- package/hooks/session/gh_pr_author_session_cleanup.py +0 -175
- package/hooks/session/test_gh_pr_author_session_cleanup.py +0 -576
- package/hooks/test__gh_pr_author_swap_utils.py +0 -333
|
@@ -25,16 +25,43 @@ const RETIRED_SKILL_DIRECTORIES = [
|
|
|
25
25
|
'post-audit-findings',
|
|
26
26
|
'pr-consistency-audit',
|
|
27
27
|
'bdd-protocol',
|
|
28
|
+
'hitl',
|
|
29
|
+
'autoconverge',
|
|
30
|
+
'pr-cleanup',
|
|
31
|
+
'pr-name-by-capability',
|
|
32
|
+
'pr-plain-language-cleanup',
|
|
33
|
+
'pr-refinement',
|
|
34
|
+
'pr-shared-extraction',
|
|
35
|
+
'pr-small-cl',
|
|
36
|
+
'pr-title-description',
|
|
37
|
+
'prototype',
|
|
38
|
+
'rebase',
|
|
39
|
+
'review-router',
|
|
40
|
+
'review-tier',
|
|
41
|
+
'run-claude-dev-env',
|
|
42
|
+
'session-log',
|
|
43
|
+
'session-tidy',
|
|
44
|
+
'source-command-sr-loop',
|
|
45
|
+
'update',
|
|
28
46
|
];
|
|
29
47
|
const PERSONAL_SKILL_DIRECTORIES = ['credit-card-picker', 'midjourney-sref'];
|
|
30
|
-
const SHIPPED_SKILL_DIRECTORY = '
|
|
48
|
+
const SHIPPED_SKILL_DIRECTORY = 'e-code-review';
|
|
31
49
|
const PRUNED_BACKUP_DIRECTORY_NAME = '.claude-dev-env-pruned';
|
|
32
50
|
const SKIP_PRUNE_NOTICE_MARKER = 'Skipping retired-skill and stale-file prune';
|
|
33
51
|
const STALE_SKILL_FILE_RELATIVE_SEGMENTS = ['scripts', 'retired_module.py'];
|
|
34
52
|
const RUNTIME_ARTIFACT_RELATIVE_SEGMENTS = ['scripts', '__pycache__', 'helper.cpython-312.pyc'];
|
|
35
53
|
const SCOPED_GROUP_SKILL_DIRECTORY = 'orchestrator';
|
|
36
|
-
const
|
|
37
|
-
'
|
|
54
|
+
const CORE_SKILL_DIRECTORIES = [
|
|
55
|
+
'orchestrator',
|
|
56
|
+
'orchestrator-refresh',
|
|
57
|
+
'team-advisor',
|
|
58
|
+
'grok-spawn',
|
|
59
|
+
'everything-search',
|
|
60
|
+
'test-runner',
|
|
61
|
+
'privacy-hygiene',
|
|
62
|
+
'issue-tracker',
|
|
63
|
+
'task-build',
|
|
64
|
+
'eli5',
|
|
38
65
|
];
|
|
39
66
|
const PRIOR_RUN_BACKUP_DIRECTORY_NAMES = [
|
|
40
67
|
'2020-01-01T00-00-00-000Z',
|
|
@@ -66,6 +93,41 @@ const NESTED_SKILL_DIRECTORY = 'foo';
|
|
|
66
93
|
const NESTED_SKILL_FILE_SEGMENTS = [NESTED_SKILL_DIRECTORY, 'scripts', 'a.py'];
|
|
67
94
|
const MYPY_INI_FILE_NAME = '.mypy.ini';
|
|
68
95
|
const SKIPPED_RECORD_SUMMARY_MARKER = 'manifest record(s) skipped';
|
|
96
|
+
const RETIRED_PULL_REQUEST_HOOK_RELATIVE_PATHS = [
|
|
97
|
+
'_gh_pr_author_swap_utils.py',
|
|
98
|
+
'test__gh_pr_author_swap_utils.py',
|
|
99
|
+
'blocking/conventional_pr_title_gate.py',
|
|
100
|
+
'blocking/gh_body_arg_blocker.py',
|
|
101
|
+
'blocking/gh_pr_author_enforcer.py',
|
|
102
|
+
'blocking/gh_pr_author_restore.py',
|
|
103
|
+
'blocking/pr_description_writer_gate.py',
|
|
104
|
+
'blocking/test_conventional_pr_title_gate.py',
|
|
105
|
+
'blocking/test_gh_body_arg_blocker.py',
|
|
106
|
+
'blocking/test_gh_pr_author_enforcer.py',
|
|
107
|
+
'blocking/test_gh_pr_author_restore.py',
|
|
108
|
+
'blocking/test_gh_pr_author_swap_utils.py',
|
|
109
|
+
'blocking/test_pr_description_writer_gate.py',
|
|
110
|
+
'blocking/test_volatile_path_in_post_blocker.py',
|
|
111
|
+
'blocking/volatile_path_in_post_blocker.py',
|
|
112
|
+
'hooks_constants/conventional_pr_title_gate_constants.py',
|
|
113
|
+
'hooks_constants/gh_pr_author_swap_constants.py',
|
|
114
|
+
'hooks_constants/pr_description_writer_gate_constants.py',
|
|
115
|
+
'hooks_constants/volatile_path_in_post_blocker_constants.py',
|
|
116
|
+
'observability/pr_description_writer_spawn_tracker.py',
|
|
117
|
+
'observability/test_pr_description_writer_spawn_tracker.py',
|
|
118
|
+
'session/gh_pr_author_session_cleanup.py',
|
|
119
|
+
'session/test_gh_pr_author_session_cleanup.py',
|
|
120
|
+
];
|
|
121
|
+
const RETIRED_PULL_REQUEST_REGISTRATION_PATHS = [
|
|
122
|
+
'blocking/conventional_pr_title_gate.py',
|
|
123
|
+
'blocking/gh_body_arg_blocker.py',
|
|
124
|
+
'blocking/gh_pr_author_enforcer.py',
|
|
125
|
+
'blocking/gh_pr_author_restore.py',
|
|
126
|
+
'blocking/pr_description_writer_gate.py',
|
|
127
|
+
'blocking/volatile_path_in_post_blocker.py',
|
|
128
|
+
'observability/pr_description_writer_spawn_tracker.py',
|
|
129
|
+
'session/gh_pr_author_session_cleanup.py',
|
|
130
|
+
];
|
|
69
131
|
|
|
70
132
|
/**
|
|
71
133
|
* Report whether a path under one managed root landed in a run backup.
|
|
@@ -658,25 +720,33 @@ test('a scoped --only install leaves retired skills in place because prune runs
|
|
|
658
720
|
}
|
|
659
721
|
});
|
|
660
722
|
|
|
661
|
-
test('a full reinstall keeps
|
|
723
|
+
test('a full reinstall keeps the skill-builder stub and archives its obsolete support files', () => {
|
|
662
724
|
const sandbox = createSandbox();
|
|
663
725
|
try {
|
|
664
|
-
|
|
665
|
-
|
|
726
|
+
const skillName = 'skill-builder';
|
|
727
|
+
plantSkillDirectory(sandbox.skillsDirectory, skillName, true);
|
|
728
|
+
const stubPath = join(sandbox.skillsDirectory, skillName, 'SKILL.md');
|
|
729
|
+
const obsoleteReferencePath = join(sandbox.skillsDirectory, skillName, 'references', 'retired.md');
|
|
730
|
+
writeFileWithParents(obsoleteReferencePath, 'obsolete skill-builder guidance\n');
|
|
731
|
+
writeFileSync(sandbox.manifestPath, JSON.stringify({
|
|
732
|
+
package: 'claude-dev-env',
|
|
733
|
+
version: '0.0.0',
|
|
734
|
+
installedAt: new Date().toISOString(),
|
|
735
|
+
files: [stubPath, obsoleteReferencePath],
|
|
736
|
+
skills: [skillName],
|
|
737
|
+
}, null, 2) + '\n');
|
|
666
738
|
|
|
667
739
|
const installerOutput = runInstaller(sandbox.homeDirectory, []);
|
|
668
740
|
|
|
741
|
+
assert.equal(installerOutput.includes(SKIP_PRUNE_NOTICE_MARKER), false);
|
|
742
|
+
assert.equal(existsSync(stubPath), true, 'the requested stub stays installed');
|
|
743
|
+
assert.match(readFileSync(stubPath, 'utf8'), /TODO: Rework to follow pstack philosophy\./);
|
|
744
|
+
assert.ok(readManifest(sandbox.manifestPath).skills.includes(skillName));
|
|
745
|
+
assert.equal(existsSync(obsoleteReferencePath), false);
|
|
669
746
|
assert.equal(
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
'
|
|
673
|
-
);
|
|
674
|
-
const restoredSkillPath = join(sandbox.skillsDirectory, 'autoconverge', 'SKILL.md');
|
|
675
|
-
assert.equal(existsSync(restoredSkillPath), true, 'autoconverge survives and is reinstalled');
|
|
676
|
-
assert.notEqual(
|
|
677
|
-
readFileSync(restoredSkillPath, 'utf8'),
|
|
678
|
-
'stale seeded copy\n',
|
|
679
|
-
'the shipped autoconverge overwrites the stale seeded copy',
|
|
747
|
+
prunedSkillBackupContains(sandbox.claudeDirectory, join(skillName, 'references', 'retired.md')),
|
|
748
|
+
true,
|
|
749
|
+
'old implementation files move to the recovery backup while the stub stays',
|
|
680
750
|
);
|
|
681
751
|
} finally {
|
|
682
752
|
rmSync(sandbox.homeDirectory, { recursive: true, force: true });
|
|
@@ -704,20 +774,24 @@ test('a scoped --only install records the skill names it installed', () => {
|
|
|
704
774
|
}
|
|
705
775
|
});
|
|
706
776
|
|
|
707
|
-
test('a scoped core install ships
|
|
777
|
+
test('a fresh scoped core install ships active skills and omits retired skills', () => {
|
|
708
778
|
const sandbox = createSandbox();
|
|
709
779
|
try {
|
|
710
780
|
runInstaller(sandbox.homeDirectory, ['--only', 'core']);
|
|
711
781
|
|
|
712
|
-
const
|
|
782
|
+
const missingSkillNames = CORE_SKILL_DIRECTORIES.filter(
|
|
713
783
|
eachSkillName => !existsSync(join(sandbox.skillsDirectory, eachSkillName, 'SKILL.md')),
|
|
714
784
|
);
|
|
715
|
-
assert.deepEqual(
|
|
785
|
+
assert.deepEqual(missingSkillNames, []);
|
|
716
786
|
|
|
717
787
|
const recordedSkillNames = new Set(readManifest(sandbox.manifestPath).skills);
|
|
718
|
-
for (const eachSkillName of
|
|
788
|
+
for (const eachSkillName of CORE_SKILL_DIRECTORIES) {
|
|
719
789
|
assert.equal(recordedSkillNames.has(eachSkillName), true);
|
|
720
790
|
}
|
|
791
|
+
for (const eachSkillName of RETIRED_SKILL_DIRECTORIES) {
|
|
792
|
+
assert.equal(existsSync(join(sandbox.skillsDirectory, eachSkillName)), false);
|
|
793
|
+
assert.equal(recordedSkillNames.has(eachSkillName), false);
|
|
794
|
+
}
|
|
721
795
|
} finally {
|
|
722
796
|
rmSync(sandbox.homeDirectory, { recursive: true, force: true });
|
|
723
797
|
}
|
|
@@ -1113,6 +1187,61 @@ test('a full reinstall removes the retired Everything path rewriter and keeps a
|
|
|
1113
1187
|
}
|
|
1114
1188
|
});
|
|
1115
1189
|
|
|
1190
|
+
test('a full reinstall removes retired pull request hooks and preserves foreign and recovery state', () => {
|
|
1191
|
+
const sandbox = createSandbox();
|
|
1192
|
+
try {
|
|
1193
|
+
runInstaller(sandbox.homeDirectory, []);
|
|
1194
|
+
const retiredPaths = RETIRED_PULL_REQUEST_HOOK_RELATIVE_PATHS.map(relativePath => (
|
|
1195
|
+
join(sandbox.claudeDirectory, HOOKS_DIRECTORY_NAME, ...relativePath.split('/'))
|
|
1196
|
+
));
|
|
1197
|
+
for (const retiredPath of retiredPaths) {
|
|
1198
|
+
writeFileWithParents(retiredPath, 'an earlier install managed this file\n');
|
|
1199
|
+
}
|
|
1200
|
+
appendManifestFiles(sandbox.manifestPath, retiredPaths);
|
|
1201
|
+
const foreignHookPath = join(
|
|
1202
|
+
sandbox.claudeDirectory, HOOKS_DIRECTORY_NAME, 'blocking', 'foreign_pr_hook.py',
|
|
1203
|
+
);
|
|
1204
|
+
const foreignHookBytes = Buffer.from('a user hook the installer never wrote\n', 'utf8');
|
|
1205
|
+
writeFileWithParents(foreignHookPath, foreignHookBytes);
|
|
1206
|
+
const recoveryStatePath = join(
|
|
1207
|
+
sandbox.homeDirectory, 'pending', 'gh_pr_author_swap_session-a.json',
|
|
1208
|
+
);
|
|
1209
|
+
const recoveryStateBytes = Buffer.from('{"original_account":"prior"}\n', 'utf8');
|
|
1210
|
+
writeFileWithParents(recoveryStatePath, recoveryStateBytes);
|
|
1211
|
+
const settings = readSettings(sandbox.claudeDirectory);
|
|
1212
|
+
settings.hooks.PreToolUse.push({
|
|
1213
|
+
matcher: 'Bash',
|
|
1214
|
+
hooks: [
|
|
1215
|
+
...RETIRED_PULL_REQUEST_REGISTRATION_PATHS.map(relativePath => ({
|
|
1216
|
+
type: 'command',
|
|
1217
|
+
command: `python3 "${join(sandbox.claudeDirectory, HOOKS_DIRECTORY_NAME, ...relativePath.split('/')).replace(/\\/g, '/')}"`,
|
|
1218
|
+
})),
|
|
1219
|
+
{ type: 'command', command: `python3 "${foreignHookPath.replace(/\\/g, '/')}"` },
|
|
1220
|
+
],
|
|
1221
|
+
});
|
|
1222
|
+
writeFileSync(
|
|
1223
|
+
join(sandbox.claudeDirectory, SETTINGS_FILE_NAME),
|
|
1224
|
+
JSON.stringify(settings, null, 4) + '\n',
|
|
1225
|
+
);
|
|
1226
|
+
|
|
1227
|
+
runInstaller(sandbox.homeDirectory, []);
|
|
1228
|
+
|
|
1229
|
+
const freshManifest = readManifest(sandbox.manifestPath);
|
|
1230
|
+
const allCommands = allHookCommands(readSettings(sandbox.claudeDirectory));
|
|
1231
|
+
for (const retiredPath of retiredPaths) {
|
|
1232
|
+
assert.equal(existsSync(retiredPath), false, `${retiredPath} leaves installed hooks`);
|
|
1233
|
+
assert.equal(freshManifest.files.includes(retiredPath), false);
|
|
1234
|
+
}
|
|
1235
|
+
for (const relativePath of RETIRED_PULL_REQUEST_REGISTRATION_PATHS) {
|
|
1236
|
+
assert.ok(allCommands.every(command => !command.includes(relativePath)));
|
|
1237
|
+
}
|
|
1238
|
+
assert.deepEqual(readFileSync(foreignHookPath), foreignHookBytes);
|
|
1239
|
+
assert.deepEqual(readFileSync(recoveryStatePath), recoveryStateBytes);
|
|
1240
|
+
} finally {
|
|
1241
|
+
rmSync(sandbox.homeDirectory, { recursive: true, force: true });
|
|
1242
|
+
}
|
|
1243
|
+
});
|
|
1244
|
+
|
|
1116
1245
|
test('a full reinstall removes a retired hook entry under an event type the current config leaves out', () => {
|
|
1117
1246
|
const sandbox = createSandbox();
|
|
1118
1247
|
try {
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import { strict as assert } from 'node:assert';
|
|
3
|
+
import {
|
|
4
|
+
mkdtempSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
existsSync,
|
|
9
|
+
rmSync,
|
|
10
|
+
} from 'node:fs';
|
|
11
|
+
import { tmpdir } from 'node:os';
|
|
12
|
+
import { dirname, join } from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
import { execFileSync } from 'node:child_process';
|
|
15
|
+
import { resolveInstallRoot } from './resolve-install-root.mjs';
|
|
16
|
+
import { PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH } from '../scripts/refresh_pstack_plugin_skills.mjs';
|
|
17
|
+
|
|
18
|
+
const THIS_DIRECTORY = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const INSTALLER_PATH = join(THIS_DIRECTORY, 'install.mjs');
|
|
20
|
+
const PACKAGE_DIRECTORY = dirname(THIS_DIRECTORY);
|
|
21
|
+
|
|
22
|
+
function runInstaller(homeDirectory, extraArguments) {
|
|
23
|
+
return execFileSync('node', [INSTALLER_PATH, ...extraArguments], {
|
|
24
|
+
cwd: PACKAGE_DIRECTORY,
|
|
25
|
+
encoding: 'utf8',
|
|
26
|
+
env: {
|
|
27
|
+
...process.env,
|
|
28
|
+
HOME: homeDirectory,
|
|
29
|
+
USERPROFILE: homeDirectory,
|
|
30
|
+
CODEX_HOME: join(homeDirectory, '.codex'),
|
|
31
|
+
CLAUDE_CONFIG_DIR: join(homeDirectory, '.claude'),
|
|
32
|
+
GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'),
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function seedPstackSkills(agentsHome, skillNames) {
|
|
38
|
+
const pluginRoot = join(agentsHome, 'skills', 'pstack');
|
|
39
|
+
for (const eachSkillName of skillNames) {
|
|
40
|
+
const skillDirectory = join(pluginRoot, eachSkillName);
|
|
41
|
+
mkdirSync(skillDirectory, { recursive: true });
|
|
42
|
+
writeFileSync(join(skillDirectory, 'SKILL.md'), '---\nname: ' + eachSkillName + '\n---\n');
|
|
43
|
+
}
|
|
44
|
+
return pluginRoot;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function withTemporaryHome(runAssertions) {
|
|
48
|
+
const homeDirectory = mkdtempSync(join(tmpdir(), 'cdev-pstack-plugin-'));
|
|
49
|
+
try {
|
|
50
|
+
const resolution = resolveInstallRoot({
|
|
51
|
+
homeDirectory,
|
|
52
|
+
environment: {},
|
|
53
|
+
explicitTarget: null,
|
|
54
|
+
});
|
|
55
|
+
runAssertions(homeDirectory, resolution);
|
|
56
|
+
} finally {
|
|
57
|
+
rmSync(homeDirectory, { recursive: true, force: true });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
test('a pstack folder the user installed gains a plugin manifest', () => {
|
|
62
|
+
withTemporaryHome((homeDirectory, resolution) => {
|
|
63
|
+
const pluginRoot = seedPstackSkills(resolution.agentsHome, ['how', 'why']);
|
|
64
|
+
|
|
65
|
+
runInstaller(homeDirectory, ['--only', 'core']);
|
|
66
|
+
|
|
67
|
+
const manifestPath = join(pluginRoot, PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH);
|
|
68
|
+
assert.equal(existsSync(manifestPath), true);
|
|
69
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
70
|
+
assert.equal(manifest.name, 'pstack');
|
|
71
|
+
assert.deepEqual(manifest.skills, ['./how', './why']);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('a second install leaves an unchanged manifest byte for byte', () => {
|
|
76
|
+
withTemporaryHome((homeDirectory, resolution) => {
|
|
77
|
+
const pluginRoot = seedPstackSkills(resolution.agentsHome, ['how']);
|
|
78
|
+
|
|
79
|
+
runInstaller(homeDirectory, ['--only', 'core']);
|
|
80
|
+
const manifestPath = join(pluginRoot, PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH);
|
|
81
|
+
const afterFirstInstall = readFileSync(manifestPath, 'utf8');
|
|
82
|
+
runInstaller(homeDirectory, ['--only', 'core']);
|
|
83
|
+
|
|
84
|
+
assert.equal(readFileSync(manifestPath, 'utf8'), afterFirstInstall);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('a later install lists a skill a pstack update added', () => {
|
|
89
|
+
withTemporaryHome((homeDirectory, resolution) => {
|
|
90
|
+
const pluginRoot = seedPstackSkills(resolution.agentsHome, ['how']);
|
|
91
|
+
runInstaller(homeDirectory, ['--only', 'core']);
|
|
92
|
+
|
|
93
|
+
seedPstackSkills(resolution.agentsHome, ['why']);
|
|
94
|
+
runInstaller(homeDirectory, ['--only', 'core']);
|
|
95
|
+
|
|
96
|
+
const manifestPath = join(pluginRoot, PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH);
|
|
97
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
98
|
+
assert.deepEqual(manifest.skills, ['./how', './why']);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('an install with no pstack folder writes no manifest and does not fail', () => {
|
|
103
|
+
withTemporaryHome((homeDirectory, resolution) => {
|
|
104
|
+
runInstaller(homeDirectory, ['--only', 'core']);
|
|
105
|
+
|
|
106
|
+
const manifestPath = join(
|
|
107
|
+
resolution.agentsHome,
|
|
108
|
+
'skills',
|
|
109
|
+
'pstack',
|
|
110
|
+
PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH,
|
|
111
|
+
);
|
|
112
|
+
assert.equal(existsSync(manifestPath), false);
|
|
113
|
+
});
|
|
114
|
+
});
|
package/bin/install.test.mjs
CHANGED
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
retiredManagedHookRelativePaths,
|
|
44
44
|
pruneRetiredHookEntriesFromSettings,
|
|
45
45
|
retainNewestRunBackupOnly,
|
|
46
|
+
refreshInstalledPstackPluginManifest,
|
|
46
47
|
} from './install.mjs';
|
|
47
48
|
import { EVER_SHIPPED_SKILL_NAMES } from './ever-shipped-skills.mjs';
|
|
48
49
|
import {
|
|
@@ -228,6 +229,11 @@ test('CORE_SKILLS ships ELI5 as the leaf presentation skill', () => {
|
|
|
228
229
|
assert.equal(INSTALL_GROUPS.core.skills.includes('eli5'), true);
|
|
229
230
|
});
|
|
230
231
|
|
|
232
|
+
test('CORE_SKILLS ships test-runner for explicit Django and Playwright readiness checks', () => {
|
|
233
|
+
assert.ok(CORE_SKILLS.includes('test-runner'));
|
|
234
|
+
assert.equal(INSTALL_GROUPS.core.skills.includes('test-runner'), true);
|
|
235
|
+
});
|
|
236
|
+
|
|
231
237
|
|
|
232
238
|
test('EVER_SHIPPED_SKILL_NAMES retains imagegen so reinstall prunes the retired skill', () => {
|
|
233
239
|
assert.ok(
|
|
@@ -1246,7 +1252,7 @@ const OLD_FOLDED_HOOKS_SETTINGS = {
|
|
|
1246
1252
|
|
|
1247
1253
|
|
|
1248
1254
|
test('FOLDED_HOOK_RELATIVE_PATHS contains folded hooks plus retired entries', () => {
|
|
1249
|
-
assert.equal(FOLDED_HOOK_RELATIVE_PATHS.size,
|
|
1255
|
+
assert.equal(FOLDED_HOOK_RELATIVE_PATHS.size, 21);
|
|
1250
1256
|
assert.ok(FOLDED_HOOK_RELATIVE_PATHS.has('blocking/write_existing_file_blocker.py'));
|
|
1251
1257
|
assert.ok(FOLDED_HOOK_RELATIVE_PATHS.has('blocking/code_rules_enforcer.py'));
|
|
1252
1258
|
assert.ok(FOLDED_HOOK_RELATIVE_PATHS.has('blocking/pytest_testpaths_orphan_blocker.py'));
|
|
@@ -1256,16 +1262,22 @@ test('FOLDED_HOOK_RELATIVE_PATHS contains folded hooks plus retired entries', ()
|
|
|
1256
1262
|
|
|
1257
1263
|
|
|
1258
1264
|
test('retired hook registrations stay managed so reinstall removes them', () => {
|
|
1259
|
-
const
|
|
1265
|
+
const retiredPaths = new Set([
|
|
1266
|
+
'session/untracked_repo_detector.py',
|
|
1267
|
+
'session/gh_pr_author_session_cleanup.py',
|
|
1268
|
+
'observability/pr_description_writer_spawn_tracker.py',
|
|
1269
|
+
]);
|
|
1260
1270
|
const shippedHooks = JSON.parse(
|
|
1261
1271
|
readFileSync(new URL('../hooks/hooks.json', import.meta.url), 'utf8')
|
|
1262
1272
|
);
|
|
1263
1273
|
const shippedCommands = Object.values(shippedHooks.hooks).flatMap(matcherGroups => (
|
|
1264
1274
|
matcherGroups.flatMap(matcherGroup => matcherGroup.hooks.map(hook => hook.command))
|
|
1265
1275
|
));
|
|
1266
|
-
assert.deepEqual(RETIRED_HOOK_REGISTRATION_RELATIVE_PATHS,
|
|
1267
|
-
|
|
1268
|
-
|
|
1276
|
+
assert.deepEqual(RETIRED_HOOK_REGISTRATION_RELATIVE_PATHS, retiredPaths);
|
|
1277
|
+
for (const retiredPath of retiredPaths) {
|
|
1278
|
+
assert.ok(managedHookScriptRelativePaths({ hooks: {} }).has(retiredPath));
|
|
1279
|
+
assert.ok(shippedCommands.every(command => !command.includes(retiredPath)));
|
|
1280
|
+
}
|
|
1269
1281
|
});
|
|
1270
1282
|
|
|
1271
1283
|
|
|
@@ -1288,6 +1300,11 @@ test('FOLDED_HOOK_RELATIVE_PATHS lists every hook the PreToolUse dispatcher host
|
|
|
1288
1300
|
];
|
|
1289
1301
|
const retiredHooks = [
|
|
1290
1302
|
'blocking/md_to_html_blocker.py',
|
|
1303
|
+
'blocking/gh_body_arg_blocker.py',
|
|
1304
|
+
'blocking/volatile_path_in_post_blocker.py',
|
|
1305
|
+
'blocking/conventional_pr_title_gate.py',
|
|
1306
|
+
'blocking/gh_pr_author_enforcer.py',
|
|
1307
|
+
'blocking/pr_description_writer_gate.py',
|
|
1291
1308
|
'session/untracked_repo_detector.py',
|
|
1292
1309
|
];
|
|
1293
1310
|
for (const hostedPath of dispatcherHostedHooks) {
|
|
@@ -1480,12 +1497,13 @@ const OLD_POST_FOLDED_HOOKS_SETTINGS = {
|
|
|
1480
1497
|
};
|
|
1481
1498
|
|
|
1482
1499
|
|
|
1483
|
-
test('POST_FOLDED_HOOK_RELATIVE_PATHS contains
|
|
1484
|
-
assert.equal(POST_FOLDED_HOOK_RELATIVE_PATHS.size,
|
|
1500
|
+
test('POST_FOLDED_HOOK_RELATIVE_PATHS contains hosted and retired post-tool hooks', () => {
|
|
1501
|
+
assert.equal(POST_FOLDED_HOOK_RELATIVE_PATHS.size, 5);
|
|
1485
1502
|
assert.ok(POST_FOLDED_HOOK_RELATIVE_PATHS.has('validation/mypy_validator.py'));
|
|
1486
1503
|
assert.ok(POST_FOLDED_HOOK_RELATIVE_PATHS.has('workflow/auto_formatter.py'));
|
|
1487
1504
|
assert.ok(POST_FOLDED_HOOK_RELATIVE_PATHS.has('workflow/doc_gist_auto_publish.py'));
|
|
1488
1505
|
assert.ok(POST_FOLDED_HOOK_RELATIVE_PATHS.has('workflow/md_to_html_companion.py'));
|
|
1506
|
+
assert.ok(POST_FOLDED_HOOK_RELATIVE_PATHS.has('blocking/gh_pr_author_restore.py'));
|
|
1489
1507
|
});
|
|
1490
1508
|
|
|
1491
1509
|
|
|
@@ -2524,3 +2542,39 @@ test('pruneStaleInstalledFiles leaves the run backup root standing when the rena
|
|
|
2524
2542
|
rmSync(sandbox.root, { recursive: true, force: true });
|
|
2525
2543
|
}
|
|
2526
2544
|
});
|
|
2545
|
+
|
|
2546
|
+
test('the pstack step writes a manifest for a skills root that carries pstack', () => {
|
|
2547
|
+
const skillsRoot = mkdtempSync(join(tmpdir(), 'cdev-pstack-step-'));
|
|
2548
|
+
try {
|
|
2549
|
+
const skillDirectory = join(skillsRoot, 'pstack', 'how');
|
|
2550
|
+
mkdirSync(skillDirectory, { recursive: true });
|
|
2551
|
+
writeFileSync(join(skillDirectory, 'SKILL.md'), '---\nname: how\n---\n');
|
|
2552
|
+
|
|
2553
|
+
const manifestPath = refreshInstalledPstackPluginManifest(skillsRoot);
|
|
2554
|
+
|
|
2555
|
+
assert.equal(
|
|
2556
|
+
manifestPath,
|
|
2557
|
+
join(skillsRoot, 'pstack', '.claude-plugin', 'plugin.json'),
|
|
2558
|
+
'the step reports the manifest it wrote so the run records the path',
|
|
2559
|
+
);
|
|
2560
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
2561
|
+
assert.deepEqual(manifest.skills, ['./how']);
|
|
2562
|
+
assert.equal(
|
|
2563
|
+
refreshInstalledPstackPluginManifest(skillsRoot),
|
|
2564
|
+
null,
|
|
2565
|
+
'a second run writes nothing and reports no path',
|
|
2566
|
+
);
|
|
2567
|
+
} finally {
|
|
2568
|
+
rmSync(skillsRoot, { recursive: true, force: true });
|
|
2569
|
+
}
|
|
2570
|
+
});
|
|
2571
|
+
|
|
2572
|
+
test('the pstack step reports no path for a skills root without pstack', () => {
|
|
2573
|
+
const skillsRoot = mkdtempSync(join(tmpdir(), 'cdev-pstack-absent-'));
|
|
2574
|
+
try {
|
|
2575
|
+
assert.equal(refreshInstalledPstackPluginManifest(skillsRoot), null);
|
|
2576
|
+
assert.equal(existsSync(join(skillsRoot, 'pstack')), false);
|
|
2577
|
+
} finally {
|
|
2578
|
+
rmSync(skillsRoot, { recursive: true, force: true });
|
|
2579
|
+
}
|
|
2580
|
+
});
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import { strict as assert } from 'node:assert';
|
|
3
|
+
import {
|
|
4
|
+
mkdtempSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
rmSync,
|
|
8
|
+
symlinkSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
} from 'node:fs';
|
|
11
|
+
import { tmpdir } from 'node:os';
|
|
12
|
+
import { dirname, join } from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
import { execFileSync } from 'node:child_process';
|
|
15
|
+
import {
|
|
16
|
+
PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH,
|
|
17
|
+
findPstackSkillDirectoryNames,
|
|
18
|
+
refreshPstackPluginManifest,
|
|
19
|
+
} from '../scripts/refresh_pstack_plugin_skills.mjs';
|
|
20
|
+
|
|
21
|
+
function buildPluginRoot(homeDirectory, skillNames, manifest) {
|
|
22
|
+
const pluginRoot = join(homeDirectory, 'pstack');
|
|
23
|
+
for (const eachSkillName of skillNames) {
|
|
24
|
+
const skillDirectory = join(pluginRoot, eachSkillName);
|
|
25
|
+
mkdirSync(skillDirectory, { recursive: true });
|
|
26
|
+
writeFileSync(join(skillDirectory, 'SKILL.md'), '---\n');
|
|
27
|
+
}
|
|
28
|
+
const manifestPath = join(pluginRoot, PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH);
|
|
29
|
+
mkdirSync(join(pluginRoot, '.claude-plugin'), { recursive: true });
|
|
30
|
+
if (manifest !== null) {
|
|
31
|
+
writeFileSync(manifestPath, JSON.stringify(manifest));
|
|
32
|
+
}
|
|
33
|
+
return pluginRoot;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function withTemporaryHome(runAssertions) {
|
|
37
|
+
const homeDirectory = mkdtempSync(join(tmpdir(), 'cdev-pstack-manifest-'));
|
|
38
|
+
try {
|
|
39
|
+
runAssertions(homeDirectory);
|
|
40
|
+
} finally {
|
|
41
|
+
rmSync(homeDirectory, { recursive: true, force: true });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
test('finds only sub-folders that hold a skill entry file', () => {
|
|
46
|
+
withTemporaryHome((homeDirectory) => {
|
|
47
|
+
const pluginRoot = buildPluginRoot(homeDirectory, ['how', 'why'], { skills: [] });
|
|
48
|
+
mkdirSync(join(pluginRoot, 'docs'), { recursive: true });
|
|
49
|
+
writeFileSync(join(pluginRoot, '.claude-plugin', 'SKILL.md'), '---\n');
|
|
50
|
+
|
|
51
|
+
assert.deepEqual(findPstackSkillDirectoryNames(pluginRoot), ['how', 'why']);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('writes a manifest when the plugin folder has none', () => {
|
|
56
|
+
withTemporaryHome((homeDirectory) => {
|
|
57
|
+
const pluginRoot = buildPluginRoot(homeDirectory, ['how'], null);
|
|
58
|
+
|
|
59
|
+
const outcome = refreshPstackPluginManifest(pluginRoot);
|
|
60
|
+
|
|
61
|
+
assert.equal(outcome.didWrite, true);
|
|
62
|
+
const manifestPath = join(pluginRoot, PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH);
|
|
63
|
+
const written = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
64
|
+
assert.equal(written.name, 'pstack');
|
|
65
|
+
assert.deepEqual(written.skills, ['./how']);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('adds a skill folder the manifest does not list', () => {
|
|
70
|
+
withTemporaryHome((homeDirectory) => {
|
|
71
|
+
const pluginRoot = buildPluginRoot(homeDirectory, ['how', 'why'], {
|
|
72
|
+
name: 'pstack',
|
|
73
|
+
skills: ['./how'],
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const outcome = refreshPstackPluginManifest(pluginRoot);
|
|
77
|
+
|
|
78
|
+
assert.equal(outcome.didWrite, true);
|
|
79
|
+
assert.deepEqual(outcome.previousSkillPaths, ['./how']);
|
|
80
|
+
assert.deepEqual(outcome.currentSkillPaths, ['./how', './why']);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('leaves the manifest byte for byte when nothing changed', () => {
|
|
85
|
+
withTemporaryHome((homeDirectory) => {
|
|
86
|
+
const pluginRoot = buildPluginRoot(homeDirectory, ['how'], {
|
|
87
|
+
name: 'pstack',
|
|
88
|
+
skills: ['./how'],
|
|
89
|
+
});
|
|
90
|
+
const manifestPath = join(pluginRoot, PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH);
|
|
91
|
+
writeFileSync(manifestPath, JSON.stringify({ name: 'pstack', skills: ['./how'] }));
|
|
92
|
+
const before = readFileSync(manifestPath, 'utf8');
|
|
93
|
+
|
|
94
|
+
const outcome = refreshPstackPluginManifest(pluginRoot);
|
|
95
|
+
|
|
96
|
+
assert.equal(outcome.didWrite, false);
|
|
97
|
+
assert.equal(readFileSync(manifestPath, 'utf8'), before);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('reads a string skills value as a one-entry list', () => {
|
|
102
|
+
withTemporaryHome((homeDirectory) => {
|
|
103
|
+
const pluginRoot = buildPluginRoot(homeDirectory, ['how'], {
|
|
104
|
+
name: 'pstack',
|
|
105
|
+
skills: './',
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const outcome = refreshPstackPluginManifest(pluginRoot);
|
|
109
|
+
|
|
110
|
+
assert.deepEqual(outcome.previousSkillPaths, ['./']);
|
|
111
|
+
assert.deepEqual(outcome.currentSkillPaths, ['./how']);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('keeps manifest fields the refresher does not own', () => {
|
|
116
|
+
withTemporaryHome((homeDirectory) => {
|
|
117
|
+
const pluginRoot = buildPluginRoot(homeDirectory, ['how'], {
|
|
118
|
+
name: 'pstack',
|
|
119
|
+
version: '9.9.9',
|
|
120
|
+
description: 'a description the user edited',
|
|
121
|
+
skills: [],
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
refreshPstackPluginManifest(pluginRoot);
|
|
125
|
+
|
|
126
|
+
const manifestPath = join(pluginRoot, PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH);
|
|
127
|
+
const written = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
128
|
+
assert.equal(written.version, '9.9.9');
|
|
129
|
+
assert.equal(written.description, 'a description the user edited');
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('refuses a folder that is itself one skill', () => {
|
|
134
|
+
withTemporaryHome((homeDirectory) => {
|
|
135
|
+
const pluginRoot = buildPluginRoot(homeDirectory, ['how'], null);
|
|
136
|
+
writeFileSync(join(pluginRoot, 'SKILL.md'), '---\n');
|
|
137
|
+
|
|
138
|
+
const outcome = refreshPstackPluginManifest(pluginRoot);
|
|
139
|
+
|
|
140
|
+
assert.equal(outcome.didWrite, false);
|
|
141
|
+
assert.equal(outcome.reason, 'plugin root is itself a skill');
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('running the file directly reports what it did', () => {
|
|
146
|
+
withTemporaryHome((homeDirectory) => {
|
|
147
|
+
const pluginRoot = buildPluginRoot(homeDirectory, ['how'], null);
|
|
148
|
+
const modulePath = join(
|
|
149
|
+
dirname(dirname(fileURLToPath(import.meta.url))),
|
|
150
|
+
'scripts',
|
|
151
|
+
'refresh_pstack_plugin_skills.mjs',
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const firstRun = execFileSync('node', [modulePath, pluginRoot], { encoding: 'utf8' });
|
|
155
|
+
const secondRun = execFileSync('node', [modulePath, pluginRoot], { encoding: 'utf8' });
|
|
156
|
+
|
|
157
|
+
assert.match(firstRun, /Updated\. 1 skills listed\./);
|
|
158
|
+
assert.match(secondRun, /No change\. 1 skills listed\./);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('running the file through a directory link still reports what it did', () => {
|
|
163
|
+
withTemporaryHome((homeDirectory) => {
|
|
164
|
+
const pluginRoot = buildPluginRoot(homeDirectory, ['how'], null);
|
|
165
|
+
const scriptsDirectory = join(
|
|
166
|
+
dirname(dirname(fileURLToPath(import.meta.url))),
|
|
167
|
+
'scripts',
|
|
168
|
+
);
|
|
169
|
+
const linkedScriptsDirectory = join(homeDirectory, 'linked-scripts');
|
|
170
|
+
symlinkSync(scriptsDirectory, linkedScriptsDirectory, 'junction');
|
|
171
|
+
const linkedModulePath = join(linkedScriptsDirectory, 'refresh_pstack_plugin_skills.mjs');
|
|
172
|
+
|
|
173
|
+
const output = execFileSync('node', [linkedModulePath, pluginRoot], { encoding: 'utf8' });
|
|
174
|
+
|
|
175
|
+
assert.match(output, /Updated\. 1 skills listed\./);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test('with no folder argument it follows CLAUDE_CONFIG_DIR', () => {
|
|
180
|
+
withTemporaryHome((homeDirectory) => {
|
|
181
|
+
const claudeConfigDirectory = join(homeDirectory, 'profile', '.claude');
|
|
182
|
+
const pluginRoot = join(claudeConfigDirectory, 'skills', 'pstack');
|
|
183
|
+
mkdirSync(join(pluginRoot, 'how'), { recursive: true });
|
|
184
|
+
writeFileSync(join(pluginRoot, 'how', 'SKILL.md'), '---\n');
|
|
185
|
+
const modulePath = join(
|
|
186
|
+
dirname(dirname(fileURLToPath(import.meta.url))),
|
|
187
|
+
'scripts',
|
|
188
|
+
'refresh_pstack_plugin_skills.mjs',
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const output = execFileSync('node', [modulePath], {
|
|
192
|
+
encoding: 'utf8',
|
|
193
|
+
env: { ...process.env, CLAUDE_CONFIG_DIR: claudeConfigDirectory },
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
assert.match(output, /Updated\. 1 skills listed\./);
|
|
197
|
+
const manifestPath = join(pluginRoot, PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH);
|
|
198
|
+
assert.deepEqual(JSON.parse(readFileSync(manifestPath, 'utf8')).skills, ['./how']);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test('refuses a folder that holds no skills', () => {
|
|
203
|
+
withTemporaryHome((homeDirectory) => {
|
|
204
|
+
const pluginRoot = buildPluginRoot(homeDirectory, [], null);
|
|
205
|
+
|
|
206
|
+
const outcome = refreshPstackPluginManifest(pluginRoot);
|
|
207
|
+
|
|
208
|
+
assert.equal(outcome.didWrite, false);
|
|
209
|
+
assert.equal(outcome.reason, 'plugin root holds no skills');
|
|
210
|
+
});
|
|
211
|
+
});
|