claude-dev-env 4.3.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) hide show
  1. package/.agents/agents/pr-description-writer.md +2 -2
  2. package/.agents/agents/test_agent_frontmatter.py +2 -2
  3. package/.agents/agents/test_pr_description_writer_style.py +1 -3
  4. package/.agents/skills/e-code-review/SKILL.md +12 -12
  5. package/.agents/skills/e-code-review/preflight-proposal.contract.test.mjs +20 -0
  6. package/.agents/skills/e-code-review/reference/fix.md +15 -17
  7. package/.agents/skills/e-code-review/reference/loop.md +35 -39
  8. package/.agents/skills/e-code-review/reference/mode-contract.test.mjs +2 -1
  9. package/.agents/skills/e-code-review/reference/preflight-proposal.md +44 -9
  10. package/.agents/skills/e-code-review/reference/runner-selection.md +13 -14
  11. package/.agents/skills/issue-tracker/SKILL.md +5 -4
  12. package/.agents/skills/pull-request/SKILL.md +11 -13
  13. package/.agents/skills/pull-request/reference/publication-tasks.md +3 -3
  14. package/.agents/skills/pull-request/scripts/github_pr_command_constants/config/constants.py +9 -1
  15. package/.agents/skills/pull-request/scripts/pull_request.py +33 -44
  16. package/.agents/skills/pull-request/scripts/test_github_pr_command_constants.py +9 -0
  17. package/.agents/skills/pull-request/scripts/test_pull_request.py +1 -3
  18. package/.agents/skills/skill-builder/SKILL.md +3 -196
  19. package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +6 -0
  20. package/_shared/pr-loop/scripts/terminology_sweep.py +64 -19
  21. package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +59 -0
  22. package/bin/install.agents-home.test.mjs +15 -13
  23. package/bin/install.mjs +28 -0
  24. package/bin/install.policy-lint.test.mjs +78 -0
  25. package/bin/install.prune.test.mjs +59 -20
  26. package/bin/install.pstack-plugin.test.mjs +114 -0
  27. package/bin/install.test.mjs +37 -0
  28. package/bin/refresh-pstack-plugin-skills.test.mjs +211 -0
  29. package/hooks/advisory/pr_done_reminder.py +342 -0
  30. package/hooks/advisory/test_pr_done_reminder.py +249 -0
  31. package/hooks/blocking/bash_post_call_dispatcher.py +53 -6
  32. package/hooks/blocking/code_rules_paired_test.py +22 -15
  33. package/hooks/blocking/test_bash_post_call_dispatcher.py +65 -4
  34. package/hooks/blocking/test_code_rules_enforcer_paired_test.py +36 -29
  35. package/hooks/blocking/test_code_rules_js_resume_task_enumeration.py +4 -4
  36. package/hooks/blocking/test_code_rules_js_returns_object_schemaless.py +5 -5
  37. package/hooks/blocking/test_pre_tool_use_dispatcher.py +68 -133
  38. package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +20 -51
  39. package/hooks/blocking/test_precommit_code_rules_gate_native_owner.py +18 -18
  40. package/hooks/git-hooks/git_hooks_constants/staged_policy_lint.py +12 -0
  41. package/hooks/git-hooks/pre_commit.py +52 -11
  42. package/hooks/git-hooks/test_installed_policy_lint.py +159 -0
  43. package/hooks/git-hooks/test_native_policy_commit.py +87 -0
  44. package/hooks/git-hooks/test_pre_commit.py +246 -0
  45. package/hooks/hooks.json +1 -1
  46. package/hooks/hooks_constants/bash_post_call_dispatcher_constants.py +14 -1
  47. package/hooks/hooks_constants/pr_done_reminder_constants.py +98 -0
  48. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +10 -43
  49. package/hooks/hooks_constants/test_bash_post_call_dispatcher_constants.py +19 -2
  50. package/hooks/hooks_constants/test_pr_done_reminder_constants.py +40 -0
  51. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +20 -32
  52. package/package.json +2 -2
  53. package/rules/durable-post-artifacts.md +2 -1
  54. package/rules/git-workflow.md +6 -6
  55. package/scripts/check.ps1 +0 -1
  56. package/{.agents/skills/run-claude-dev-env/driver.mjs → scripts/ci/installer-lifecycle.mjs} +3 -28
  57. package/scripts/ci/installer-lifecycle.test.mjs +26 -0
  58. package/scripts/ci/windows-installer-lifecycle.ps1 +1 -11
  59. package/scripts/dev_env_scripts_constants/shared_tree_constants.py +4 -5
  60. package/{.agents/skills/_shared/pr-loop/scripts → scripts}/durable_post_lint.py +5 -41
  61. package/{.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/durable_post_lint_constants.py → scripts/durable_post_lint_config/config/constants.py} +0 -10
  62. package/{.agents/skills/_shared/pr-loop/scripts/test_durable_post_lint_constants.py → scripts/durable_post_lint_config/config/test_constants.py} +1 -1
  63. package/scripts/policy_lint/adapter_detectors.py +13 -6
  64. package/scripts/policy_lint/adapters.py +7 -4
  65. package/scripts/policy_lint/config/constants.py +5 -0
  66. package/scripts/policy_lint/engine.py +48 -4
  67. package/scripts/policy_lint/model.py +1 -0
  68. package/scripts/policy_lint/registry.py +1 -1
  69. package/scripts/policy_lint/selection.py +2 -1
  70. package/scripts/policy_lint/selection_git.py +2 -0
  71. package/scripts/refresh_pstack_plugin_skills.mjs +200 -0
  72. package/scripts/shared_tree_paths.py +32 -3
  73. package/scripts/test_shared_tree_paths.py +33 -0
  74. package/scripts/tests/test_policy_lint_rules_archive.py +120 -0
  75. package/scripts/tests/test_policy_lint_rules_engine.py +25 -4
  76. package/scripts/tests/test_policy_lint_rules_installed_loader.py +29 -0
  77. package/scripts/tests/test_policy_lint_rules_registry.py +227 -13
  78. package/scripts/tests/test_policy_lint_selection.py +25 -2
  79. package/scripts/tests/test_policy_lint_selection_native_index.py +30 -0
  80. package/scripts/tests/test_rules.py +3 -7
  81. package/.agents/skills/_shared/pr-loop/.claude/CLAUDE.md +0 -1
  82. package/.agents/skills/_shared/pr-loop/AGENTS.md +0 -1
  83. package/.agents/skills/_shared/pr-loop/audit-contract.md +0 -5
  84. package/.agents/skills/_shared/pr-loop/audit-reply-template.md +0 -5
  85. package/.agents/skills/_shared/pr-loop/code-rules-gate.md +0 -5
  86. package/.agents/skills/_shared/pr-loop/fix-protocol.md +0 -5
  87. package/.agents/skills/_shared/pr-loop/gh-payloads.md +0 -5
  88. package/.agents/skills/_shared/pr-loop/portable-driver.md +0 -149
  89. package/.agents/skills/_shared/pr-loop/post-audit-thread-contract.md +0 -5
  90. package/.agents/skills/_shared/pr-loop/precatch-rubric.md +0 -5
  91. package/.agents/skills/_shared/pr-loop/preflight-proposal.contract.test.mjs +0 -119
  92. package/.agents/skills/_shared/pr-loop/preflight-proposal.md +0 -5
  93. package/.agents/skills/_shared/pr-loop/prompts/.claude/CLAUDE.md +0 -1
  94. package/.agents/skills/_shared/pr-loop/prompts/AGENTS.md +0 -1
  95. package/.agents/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +0 -324
  96. package/.agents/skills/_shared/pr-loop/scripts/.claude/CLAUDE.md +0 -1
  97. package/.agents/skills/_shared/pr-loop/scripts/AGENTS.md +0 -1
  98. package/.agents/skills/_shared/pr-loop/scripts/RUNTIME_SCRIPTS.md +0 -29
  99. package/.agents/skills/_shared/pr-loop/scripts/_cli_utils.py +0 -22
  100. package/.agents/skills/_shared/pr-loop/scripts/_path_resolver.py +0 -186
  101. package/.agents/skills/_shared/pr-loop/scripts/_xml_utils.py +0 -20
  102. package/.agents/skills/_shared/pr-loop/scripts/audit_category_schema.py +0 -355
  103. package/.agents/skills/_shared/pr-loop/scripts/build_audit_prompt.py +0 -289
  104. package/.agents/skills/_shared/pr-loop/scripts/build_converge_task_list.py +0 -310
  105. package/.agents/skills/_shared/pr-loop/scripts/build_fix_prompt.py +0 -292
  106. package/.agents/skills/_shared/pr-loop/scripts/init_loop_state.py +0 -134
  107. package/.agents/skills/_shared/pr-loop/scripts/portable_converge_driver.py +0 -1646
  108. package/.agents/skills/_shared/pr-loop/scripts/preflight_worktree.py +0 -392
  109. package/.agents/skills/_shared/pr-loop/scripts/select_converge_pacer.py +0 -215
  110. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/.claude/CLAUDE.md +0 -1
  111. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/AGENTS.md +0 -1
  112. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/__init__.py +0 -0
  113. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py +0 -37
  114. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/converge_task_list_constants.py +0 -56
  115. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/handoff_constants.py +0 -45
  116. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/pacer_constants.py +0 -47
  117. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +0 -157
  118. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/portable_driver_constants.py +0 -177
  119. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/preflight_constants.py +0 -70
  120. package/.agents/skills/_shared/pr-loop/scripts/teardown_worktrees.py +0 -172
  121. package/.agents/skills/_shared/pr-loop/scripts/test__path_resolver.py +0 -106
  122. package/.agents/skills/_shared/pr-loop/scripts/test_audit_category_schema.py +0 -94
  123. package/.agents/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +0 -499
  124. package/.agents/skills/_shared/pr-loop/scripts/test_build_converge_task_list.py +0 -140
  125. package/.agents/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +0 -262
  126. package/.agents/skills/_shared/pr-loop/scripts/test_init_loop_state.py +0 -71
  127. package/.agents/skills/_shared/pr-loop/scripts/test_portable_converge_driver.py +0 -1119
  128. package/.agents/skills/_shared/pr-loop/scripts/test_preflight_worktree.py +0 -287
  129. package/.agents/skills/_shared/pr-loop/scripts/test_select_converge_pacer.py +0 -207
  130. package/.agents/skills/_shared/pr-loop/scripts/test_teardown_worktrees.py +0 -59
  131. package/.agents/skills/_shared/pr-loop/scripts/test_write_handoff.py +0 -201
  132. package/.agents/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +0 -182
  133. package/.agents/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +0 -206
  134. package/.agents/skills/_shared/pr-loop/scripts/write_handoff.py +0 -309
  135. package/.agents/skills/_shared/pr-loop/state-schema.md +0 -5
  136. package/.agents/skills/_shared/pr-loop/worker-spawn.md +0 -5
  137. package/.agents/skills/autoconverge/.claude/CLAUDE.md +0 -1
  138. package/.agents/skills/autoconverge/AGENTS.md +0 -1
  139. package/.agents/skills/autoconverge/SKILL.md +0 -431
  140. package/.agents/skills/autoconverge/reference/.claude/CLAUDE.md +0 -1
  141. package/.agents/skills/autoconverge/reference/AGENTS.md +0 -1
  142. package/.agents/skills/autoconverge/reference/closing-report.md +0 -93
  143. package/.agents/skills/autoconverge/reference/convergence-gates.md +0 -31
  144. package/.agents/skills/autoconverge/reference/convergence.md +0 -219
  145. package/.agents/skills/autoconverge/reference/copilot-findings.md +0 -51
  146. package/.agents/skills/autoconverge/reference/gotchas.md +0 -66
  147. package/.agents/skills/autoconverge/reference/headless-safety.md +0 -44
  148. package/.agents/skills/autoconverge/reference/multi-pr.md +0 -128
  149. package/.agents/skills/autoconverge/reference/self-closing-loop.md +0 -84
  150. package/.agents/skills/autoconverge/reference/stop-conditions.md +0 -127
  151. package/.agents/skills/autoconverge/test_portable_pacer_gate.py +0 -54
  152. package/.agents/skills/autoconverge/workflow/.claude/CLAUDE.md +0 -1
  153. package/.agents/skills/autoconverge/workflow/AGENTS.md +0 -1
  154. package/.agents/skills/autoconverge/workflow/aggregate_runs.py +0 -371
  155. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/.claude/CLAUDE.md +0 -1
  156. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/AGENTS.md +0 -1
  157. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/__init__.py +0 -0
  158. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +0 -250
  159. package/.agents/skills/autoconverge/workflow/converge.clean-audit.test.mjs +0 -665
  160. package/.agents/skills/autoconverge/workflow/converge.codex-gate.test.mjs +0 -472
  161. package/.agents/skills/autoconverge/workflow/converge.contract.test.mjs +0 -64
  162. package/.agents/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +0 -1009
  163. package/.agents/skills/autoconverge/workflow/converge.fix-progress.test.mjs +0 -107
  164. package/.agents/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +0 -33
  165. package/.agents/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +0 -15
  166. package/.agents/skills/autoconverge/workflow/converge.mjs +0 -3132
  167. package/.agents/skills/autoconverge/workflow/converge.p2-advance.test.mjs +0 -202
  168. package/.agents/skills/autoconverge/workflow/converge.path-aware.test.mjs +0 -47
  169. package/.agents/skills/autoconverge/workflow/converge.precatch.test.mjs +0 -152
  170. package/.agents/skills/autoconverge/workflow/converge.run-input.test.mjs +0 -116
  171. package/.agents/skills/autoconverge/workflow/converge_multi.mjs +0 -169
  172. package/.agents/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +0 -173
  173. package/.agents/skills/autoconverge/workflow/convergence_summary.py +0 -110
  174. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a11d903476b803493.jsonl +0 -2
  175. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a26213978adeef6fb.jsonl +0 -2
  176. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a3def0d15ed9d9110.jsonl +0 -2
  177. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a41f41b1b708ee3b7.jsonl +0 -2
  178. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a758b880abecc3ff7.jsonl +0 -2
  179. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a8897b89656b1bd16.jsonl +0 -2
  180. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ab1c2d3e4f5a6b7c8.jsonl +0 -2
  181. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-abd463d744a1437bc.jsonl +0 -2
  182. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ad19d027ae8ee1816.jsonl +0 -2
  183. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +0 -266
  184. package/.agents/skills/autoconverge/workflow/render_report.py +0 -1024
  185. package/.agents/skills/autoconverge/workflow/test_aggregate_runs.py +0 -134
  186. package/.agents/skills/autoconverge/workflow/test_convergence_summary.py +0 -149
  187. package/.agents/skills/autoconverge/workflow/test_render_report.py +0 -812
  188. package/.agents/skills/hitl/SKILL.md +0 -45
  189. package/.agents/skills/pr-cleanup/SKILL.md +0 -140
  190. package/.agents/skills/pr-cleanup/reference/process-inventory.md +0 -17
  191. package/.agents/skills/pr-cleanup/reference/task-seeds.md +0 -22
  192. package/.agents/skills/pr-name-by-capability/SKILL.md +0 -73
  193. package/.agents/skills/pr-name-by-capability/reference/fetch-commands.md +0 -29
  194. package/.agents/skills/pr-name-by-capability/reference/offense-examples.md +0 -32
  195. package/.agents/skills/pr-name-by-capability/reference/report-template.md +0 -29
  196. package/.agents/skills/pr-name-by-capability/reference/rule-checklist.md +0 -38
  197. package/.agents/skills/pr-name-by-capability/reference/task-seeds.md +0 -10
  198. package/.agents/skills/pr-plain-language-cleanup/SKILL.md +0 -93
  199. package/.agents/skills/pr-refinement/SKILL.md +0 -31
  200. package/.agents/skills/pr-shared-extraction/SKILL.md +0 -177
  201. package/.agents/skills/pr-shared-extraction/reference/examples.md +0 -46
  202. package/.agents/skills/pr-shared-extraction/reference/offense-taxonomy.md +0 -100
  203. package/.agents/skills/pr-small-cl/SKILL.md +0 -52
  204. package/.agents/skills/pr-title-description/SKILL.md +0 -96
  205. package/.agents/skills/prototype/SKILL.md +0 -85
  206. package/.agents/skills/prototype/reference/honest-limitations.md +0 -23
  207. package/.agents/skills/prototype/reference/promotion-tasks.md +0 -23
  208. package/.agents/skills/prototype/scripts/build_sandbox_settings.py +0 -249
  209. package/.agents/skills/prototype/scripts/conftest.py +0 -15
  210. package/.agents/skills/prototype/scripts/launch_sandbox.py +0 -205
  211. package/.agents/skills/prototype/scripts/probe_sandbox_safety.py +0 -311
  212. package/.agents/skills/prototype/scripts/prototype_scripts_constants/__init__.py +0 -1
  213. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/__init__.py +0 -0
  214. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/build_sandbox_settings_constants.py +0 -41
  215. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/launch_sandbox_constants.py +0 -23
  216. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/probe_sandbox_safety_constants.py +0 -45
  217. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/prototype_common_constants.py +0 -10
  218. package/.agents/skills/prototype/scripts/test_build_sandbox_settings.py +0 -275
  219. package/.agents/skills/prototype/scripts/test_launch_sandbox.py +0 -303
  220. package/.agents/skills/prototype/scripts/test_probe_sandbox_safety.py +0 -312
  221. package/.agents/skills/prototype/workflows/promotion.md +0 -25
  222. package/.agents/skills/prototype/workflows/sandbox.md +0 -35
  223. package/.agents/skills/rebase/.claude/CLAUDE.md +0 -1
  224. package/.agents/skills/rebase/AGENTS.md +0 -1
  225. package/.agents/skills/rebase/SKILL.md +0 -178
  226. package/.agents/skills/review-router/SKILL.md +0 -51
  227. package/.agents/skills/review-router/reference/route-policy.json +0 -7
  228. package/.agents/skills/review-router/reference/task-seeds.md +0 -1
  229. package/.agents/skills/review-router/scripts/conftest.py +0 -4
  230. package/.agents/skills/review-router/scripts/review_router.py +0 -50
  231. package/.agents/skills/review-router/scripts/review_router_cli.py +0 -211
  232. package/.agents/skills/review-router/scripts/review_router_constants/__init__.py +0 -1
  233. package/.agents/skills/review-router/scripts/review_router_constants/config/__init__.py +0 -0
  234. package/.agents/skills/review-router/scripts/review_router_constants/config/constants.py +0 -13
  235. package/.agents/skills/review-router/scripts/test_review_router.py +0 -23
  236. package/.agents/skills/review-router/scripts/test_review_router_cli.py +0 -47
  237. package/.agents/skills/review-tier/SKILL.md +0 -56
  238. package/.agents/skills/review-tier/reference/task-seeds.md +0 -1
  239. package/.agents/skills/review-tier/reference/tier-policy.json +0 -9
  240. package/.agents/skills/review-tier/scripts/conftest.py +0 -4
  241. package/.agents/skills/review-tier/scripts/review_tier.py +0 -359
  242. package/.agents/skills/review-tier/scripts/review_tier_constants/__init__.py +0 -1
  243. package/.agents/skills/review-tier/scripts/review_tier_constants/config/__init__.py +0 -0
  244. package/.agents/skills/review-tier/scripts/review_tier_constants/config/constants.py +0 -29
  245. package/.agents/skills/review-tier/scripts/test_generation.py +0 -159
  246. package/.agents/skills/review-tier/scripts/test_review_tier.py +0 -33
  247. package/.agents/skills/run-claude-dev-env/SKILL.md +0 -84
  248. package/.agents/skills/session-log/.claude/CLAUDE.md +0 -1
  249. package/.agents/skills/session-log/AGENTS.md +0 -1
  250. package/.agents/skills/session-log/SKILL.md +0 -170
  251. package/.agents/skills/session-log/templates/frontmatter.md +0 -40
  252. package/.agents/skills/session-tidy/.claude/CLAUDE.md +0 -1
  253. package/.agents/skills/session-tidy/AGENTS.md +0 -1
  254. package/.agents/skills/session-tidy/SKILL.md +0 -191
  255. package/.agents/skills/skill-builder/references/.claude/CLAUDE.md +0 -1
  256. package/.agents/skills/skill-builder/references/AGENTS.md +0 -1
  257. package/.agents/skills/skill-builder/references/delegation-map.md +0 -130
  258. package/.agents/skills/skill-builder/references/description-field.md +0 -111
  259. package/.agents/skills/skill-builder/references/deterministic-elements.md +0 -218
  260. package/.agents/skills/skill-builder/references/progressive-disclosure.md +0 -122
  261. package/.agents/skills/skill-builder/references/self-audit-checklist.md +0 -126
  262. package/.agents/skills/skill-builder/references/skill-modularity.md +0 -111
  263. package/.agents/skills/skill-builder/references/skill-types.md +0 -232
  264. package/.agents/skills/skill-builder/references/thariq-x-post-skills.json +0 -33
  265. package/.agents/skills/skill-builder/templates/.claude/CLAUDE.md +0 -1
  266. package/.agents/skills/skill-builder/templates/AGENTS.md +0 -1
  267. package/.agents/skills/skill-builder/templates/gap-analysis.md +0 -87
  268. package/.agents/skills/skill-builder/workflows/.claude/CLAUDE.md +0 -1
  269. package/.agents/skills/skill-builder/workflows/AGENTS.md +0 -1
  270. package/.agents/skills/skill-builder/workflows/improve-skill.md +0 -136
  271. package/.agents/skills/skill-builder/workflows/new-skill.md +0 -179
  272. package/.agents/skills/skill-builder/workflows/polish-skill.md +0 -139
  273. package/.agents/skills/source-command-sr-loop/SKILL.md +0 -82
  274. package/.agents/skills/update/.claude/CLAUDE.md +0 -1
  275. package/.agents/skills/update/AGENTS.md +0 -1
  276. package/.agents/skills/update/SKILL.md +0 -190
  277. package/_shared/pr-loop/AGENTS.md +0 -1
  278. package/_shared/pr-loop/CLAUDE.md +0 -1
  279. package/_shared/pr-loop/audit-contract.md +0 -183
  280. package/_shared/pr-loop/audit-reply-template.md +0 -151
  281. package/_shared/pr-loop/code-rules-gate.md +0 -65
  282. package/_shared/pr-loop/fix-protocol.md +0 -60
  283. package/_shared/pr-loop/gh-payloads.md +0 -75
  284. package/_shared/pr-loop/post-audit-thread-contract.md +0 -51
  285. package/_shared/pr-loop/precatch-rubric.md +0 -66
  286. package/_shared/pr-loop/preflight-proposal.md +0 -56
  287. package/_shared/pr-loop/scripts/post_audit_thread.py +0 -1242
  288. package/_shared/pr-loop/scripts/pr_loop_shared_constants/post_audit_thread_constants.py +0 -199
  289. package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +0 -1115
  290. package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +0 -129
  291. package/_shared/pr-loop/state-schema.md +0 -45
  292. package/_shared/pr-loop/worker-spawn.md +0 -189
  293. /package/{.agents/skills/_shared/pr-loop/scripts → scripts}/test_durable_post_lint.py +0 -0
@@ -1,10 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import importlib
4
+ import os
4
5
  import sys
5
6
  from pathlib import Path
6
7
  from types import ModuleType
7
8
 
9
+ from shared_tree_paths import resolve_shared_scripts_directory
10
+
8
11
  from . import adapter_configuration, adapter_detectors, adapter_pairing, adapter_support
9
12
  from .config import constants
10
13
  from .model import Diagnostic, Document, DocumentSet
@@ -24,10 +27,10 @@ def _hooks_module(module_name: str) -> ModuleType:
24
27
 
25
28
 
26
29
  def _pr_loop_script_module(module_name: str) -> ModuleType:
27
- package_directory = Path(__file__).resolve().parent.parent.parent
28
- scripts_directory = str(
29
- package_directory.joinpath(*constants.ALL_PR_LOOP_SCRIPTS_PATH_SEGMENTS)
30
- )
30
+ scripts_directory = str(resolve_shared_scripts_directory(
31
+ __file__, os.environ, constants.PR_LOOP_DIRECTORY_NAME,
32
+ f"{module_name}{constants.PYTHON_SUFFIX}", constants.SHARED_ROOT_PARENT_INDEX
33
+ ))
31
34
  if scripts_directory not in sys.path:
32
35
  sys.path.insert(0, scripts_directory)
33
36
  return importlib.import_module(module_name)
@@ -5,6 +5,7 @@ INVALID_INPUT_EXIT_CODE = 2
5
5
  GIT_EXECUTABLE = "git"
6
6
  UTF8_ENCODING = "utf-8"
7
7
  PATH_SEPARATOR = "/"
8
+ ARCHIVED_SKILLS_DIRECTORY_NAME = "skill-archive"
8
9
  NUL_BYTE = b"\0"
9
10
  ALL_GIT_ROOT_ARGUMENTS = ("rev-parse", "--show-toplevel")
10
11
  ALL_GIT_HEAD_ARGUMENTS = ("rev-parse", "--verify", "HEAD")
@@ -13,6 +14,7 @@ GIT_MERGE_BASE_ARGUMENT = "merge-base"
13
14
  GIT_HEAD_REFERENCE = "HEAD"
14
15
  GIT_INDEX_REFERENCE_PREFIX = ""
15
16
  GIT_ENVIRONMENT_VARIABLE_PREFIX = "GIT_"
17
+ GIT_INDEX_ENVIRONMENT_VARIABLE = "GIT_INDEX_FILE"
16
18
  ALL_GIT_BLOB_ARGUMENTS = ("cat-file", "blob")
17
19
  ALL_MISSING_BLOB_ERROR_FRAGMENTS = (
18
20
  "does not exist in",
@@ -75,3 +77,6 @@ TERMINOLOGY_FINDING_PATTERN = re.compile(
75
77
  TERMINOLOGY_PATH_GROUP = "path"
76
78
  TERMINOLOGY_LINE_NUMBER_GROUP = "line_number"
77
79
  TERMINOLOGY_MESSAGE_GROUP = "message"
80
+
81
+ PR_LOOP_DIRECTORY_NAME = "pr-loop"
82
+ SHARED_ROOT_PARENT_INDEX = 2
@@ -1,8 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from collections.abc import Iterable, Sequence
4
- from pathlib import Path
4
+ from pathlib import Path, PurePosixPath
5
5
 
6
+ from .config.constants import ARCHIVED_SKILLS_DIRECTORY_NAME
6
7
  from .model import (
7
8
  ChangeSetRule,
8
9
  Diagnostic,
@@ -25,6 +26,9 @@ def lint(
25
26
  ) -> LintReport:
26
27
  """Run the selected rules on one source selection.
27
28
 
29
+ Repository and diff selections check active code. File and editor
30
+ selections include archived skills.
31
+
28
32
  Args:
29
33
  request: Repository root, source, and rule sets.
30
34
  all_registry: Optional registry replacement.
@@ -32,10 +36,14 @@ def lint(
32
36
  Returns:
33
37
  A report of diagnostics and rule execution state.
34
38
  """
35
- document_set = select_documents(request)
36
- candidate_rules = tuple(all_registry) if all_registry is not None else default_registry()
39
+ document_set = _runtime_document_set(select_documents(request))
40
+ candidate_rules = (
41
+ tuple(all_registry) if all_registry is not None else default_registry()
42
+ )
37
43
  selected = selected_rules(candidate_rules, request.rule_sets)
38
- all_diagnostics, executed, failed, skipped = _run_selected_rules(selected, document_set)
44
+ all_diagnostics, executed, failed, skipped = _run_selected_rules(
45
+ selected, document_set
46
+ )
39
47
  return LintReport(
40
48
  1,
41
49
  tuple(sorted(all_diagnostics, key=_diagnostic_sort_key)),
@@ -46,6 +54,42 @@ def lint(
46
54
  )
47
55
 
48
56
 
57
+ def _is_runtime_path(path: PurePosixPath) -> bool:
58
+ return path.parts[0] != ARCHIVED_SKILLS_DIRECTORY_NAME
59
+
60
+
61
+ def _runtime_document_set(document_set: DocumentSet) -> DocumentSet:
62
+ if document_set.selection in {SelectionKind.FILES, SelectionKind.TEXT}:
63
+ return document_set
64
+ all_documents = tuple(
65
+ each_document
66
+ for each_document in document_set.documents
67
+ if _is_runtime_path(each_document.path)
68
+ )
69
+ all_deleted_paths = tuple(
70
+ each_path
71
+ for each_path in document_set.deleted_paths
72
+ if _is_runtime_path(each_path)
73
+ ) + tuple(
74
+ old_path
75
+ for old_path, new_path in document_set.renamed_paths
76
+ if _is_runtime_path(old_path) and not _is_runtime_path(new_path)
77
+ )
78
+ all_renamed_paths = tuple(
79
+ each_pair
80
+ for each_pair in document_set.renamed_paths
81
+ if _is_runtime_path(each_pair[1])
82
+ )
83
+ return DocumentSet(
84
+ all_documents,
85
+ document_set.selection,
86
+ document_set.repository_root,
87
+ all_deleted_paths,
88
+ all_renamed_paths,
89
+ document_set.base_revision,
90
+ )
91
+
92
+
49
93
  def _run_selected_rules(
50
94
  all_rules: tuple[Rule, ...], document_set: DocumentSet
51
95
  ) -> tuple[list[Diagnostic], tuple[str, ...], tuple[str, ...], tuple[str, ...]]:
@@ -106,6 +106,7 @@ class DocumentSet:
106
106
  repository_root: Path
107
107
  deleted_paths: tuple[PurePosixPath, ...] = ()
108
108
  renamed_paths: tuple[tuple[PurePosixPath, PurePosixPath], ...] = ()
109
+ base_revision: str | None = None
109
110
 
110
111
 
111
112
  @dataclass(frozen=True)
@@ -141,7 +141,7 @@ def default_registry() -> tuple[model.Rule, ...]:
141
141
  model.ChangeSetRule(
142
142
  "terminology-sweep",
143
143
  frozenset({"changed"}),
144
- frozenset({model.SelectionKind.STAGED}),
144
+ frozenset({model.SelectionKind.STAGED, model.SelectionKind.BASE}),
145
145
  adapters.terminology_diagnostics,
146
146
  ),
147
147
  model.RepositoryRule(
@@ -172,7 +172,7 @@ def _documents_from_changes(
172
172
  selection_kind = SelectionKind.STAGED if is_staged else SelectionKind.BASE
173
173
  return DocumentSet(
174
174
  tuple(all_documents), selection_kind, repository_root,
175
- tuple(all_deleted_paths), tuple(all_renamed_paths),
175
+ tuple(all_deleted_paths), tuple(all_renamed_paths), base_revision,
176
176
  )
177
177
 
178
178
 
@@ -354,6 +354,7 @@ def _normalize_document_set(repository_root: Path, document_set: DocumentSet) ->
354
354
  repository_root,
355
355
  all_deleted_paths,
356
356
  all_renamed_paths,
357
+ document_set.base_revision,
357
358
  )
358
359
 
359
360
 
@@ -10,6 +10,7 @@ from .config.constants import (
10
10
  ALL_MISSING_BLOB_ERROR_FRAGMENTS,
11
11
  ALL_UNBORN_HEAD_ERROR_FRAGMENTS,
12
12
  GIT_ENVIRONMENT_VARIABLE_PREFIX,
13
+ GIT_INDEX_ENVIRONMENT_VARIABLE,
13
14
  GIT_EXECUTABLE,
14
15
  NUL_BYTE,
15
16
  UTF8_ENCODING,
@@ -105,6 +106,7 @@ def _git_subprocess_environment() -> dict[str, str]:
105
106
  each_name: each_environment_text
106
107
  for each_name, each_environment_text in os.environ.items()
107
108
  if not each_name.upper().startswith(git_prefix)
109
+ or each_name.upper() == GIT_INDEX_ENVIRONMENT_VARIABLE
108
110
  }
109
111
 
110
112
 
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Keep a pstack skills-directory plugin manifest listing every skill folder.
3
+ *
4
+ * Claude Code loads a folder under `~/.claude/skills` as a plugin when the
5
+ * folder holds `.claude-plugin/plugin.json`, and it namespaces that plugin's
6
+ * skills as `pstack:how`. It does not scan a plugin root for skill folders, so
7
+ * the manifest names each sub-skill path:
8
+ *
9
+ * plugin root holds: how/SKILL.md, why/SKILL.md, docs/, .claude-plugin/
10
+ * manifest before: "skills": ["./how"]
11
+ * manifest after: "skills": ["./how", "./why"] <- why/ picked up
12
+ * second run: no write <- the list matches
13
+ * docs/, .claude-plugin/ <- skipped, no SKILL.md
14
+ *
15
+ * The installer calls `refreshPstackPluginManifest` for a pstack folder the
16
+ * user installed. Running this file directly refreshes the same manifest after
17
+ * a pstack update.
18
+ */
19
+
20
+ import {
21
+ existsSync,
22
+ mkdirSync,
23
+ readdirSync,
24
+ readFileSync,
25
+ realpathSync,
26
+ writeFileSync,
27
+ } from 'node:fs';
28
+ import { homedir } from 'node:os';
29
+ import { join } from 'node:path';
30
+ import { fileURLToPath } from 'node:url';
31
+
32
+ export const PSTACK_PLUGIN_DIRECTORY_NAME = 'pstack';
33
+ export const PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH = join('.claude-plugin', 'plugin.json');
34
+ export const SKILL_MANIFEST_FILE_NAME = 'SKILL.md';
35
+ export const SKILL_PATH_PREFIX = './';
36
+
37
+ const CLAUDE_HOME_DIRECTORY_NAME = '.claude';
38
+ const MANIFEST_SKILLS_KEY = 'skills';
39
+ const MANIFEST_INDENT_SPACES = 2;
40
+ const FILE_ENCODING = 'utf8';
41
+ const HIDDEN_NAME_PREFIX = '.';
42
+
43
+ /**
44
+ * Fields a first write seeds from the published pstack plugin. An existing
45
+ * manifest keeps its own values. This refresher changes only the skills list.
46
+ */
47
+ const SEEDED_MANIFEST_FIELDS = Object.freeze({
48
+ $schema: 'https://anthropic.com/claude-code/plugin.schema.json',
49
+ name: PSTACK_PLUGIN_DIRECTORY_NAME,
50
+ version: '0.14.8',
51
+ description:
52
+ 'if you want to go fast, go deep first. pstack helps you write less, but higher '
53
+ + 'quality code. rigorous agent workflows you can parallelize with confidence.',
54
+ author: Object.freeze({ name: 'Lauren Tan' }),
55
+ homepage: 'https://github.com/cursor/plugins/tree/main/pstack',
56
+ repository: 'https://github.com/cursor/plugins',
57
+ license: 'MIT',
58
+ });
59
+
60
+ /**
61
+ * Name every sub-folder of a plugin root that Claude Code loads as a skill.
62
+ *
63
+ * @param {string} pluginRoot Plugin folder holding one sub-folder per skill.
64
+ * @returns {string[]} The sorted names of each visible sub-folder holding a
65
+ * skill manifest. A hidden folder and a folder without `SKILL.md` are left out.
66
+ */
67
+ export function findPstackSkillDirectoryNames(pluginRoot) {
68
+ return readdirSync(pluginRoot, { withFileTypes: true })
69
+ .filter((eachEntry) => eachEntry.isDirectory())
70
+ .map((eachEntry) => eachEntry.name)
71
+ .filter((eachName) => !eachName.startsWith(HIDDEN_NAME_PREFIX))
72
+ .filter((eachName) => existsSync(join(pluginRoot, eachName, SKILL_MANIFEST_FILE_NAME)))
73
+ .sort();
74
+ }
75
+
76
+ function readManifestSkillPaths(manifestPath) {
77
+ if (!existsSync(manifestPath)) return { manifest: null, previousSkillPaths: [] };
78
+ const manifest = JSON.parse(readFileSync(manifestPath, FILE_ENCODING));
79
+ const listedSkillPaths = manifest[MANIFEST_SKILLS_KEY];
80
+ if (typeof listedSkillPaths === 'string') {
81
+ return { manifest, previousSkillPaths: [listedSkillPaths] };
82
+ }
83
+ if (Array.isArray(listedSkillPaths)) {
84
+ return { manifest, previousSkillPaths: listedSkillPaths };
85
+ }
86
+ return { manifest, previousSkillPaths: [] };
87
+ }
88
+
89
+ /**
90
+ * Rewrite a pstack plugin manifest's skills list to match its folder.
91
+ *
92
+ * Writes only when the list changes. A second call on an unchanged folder
93
+ * leaves the file byte for byte. An existing manifest keeps every field but
94
+ * the skills list.
95
+ *
96
+ * @param {string} pluginRoot Plugin folder holding the skill sub-folders.
97
+ * @returns {{didWrite: boolean, reason: string, previousSkillPaths: string[],
98
+ * currentSkillPaths: string[]}} Whether this call wrote, and the paths
99
+ * before and after. `reason` names a skip, and is empty on a write or an
100
+ * unchanged list.
101
+ */
102
+ export function refreshPstackPluginManifest(pluginRoot) {
103
+ const declined = { didWrite: false, previousSkillPaths: [], currentSkillPaths: [] };
104
+ if (!existsSync(pluginRoot)) {
105
+ return { ...declined, reason: 'plugin root is absent' };
106
+ }
107
+ if (existsSync(join(pluginRoot, SKILL_MANIFEST_FILE_NAME))) {
108
+ return { ...declined, reason: 'plugin root is itself a skill' };
109
+ }
110
+ const allSkillNames = findPstackSkillDirectoryNames(pluginRoot);
111
+ if (allSkillNames.length === 0) {
112
+ return { ...declined, reason: 'plugin root holds no skills' };
113
+ }
114
+ const manifestPath = join(pluginRoot, PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH);
115
+ const { manifest, previousSkillPaths } = readManifestSkillPaths(manifestPath);
116
+ const currentSkillPaths = allSkillNames.map((eachName) => SKILL_PATH_PREFIX + eachName);
117
+ const outcome = { reason: '', previousSkillPaths, currentSkillPaths };
118
+ const isUnchanged = manifest !== null
119
+ && previousSkillPaths.length === currentSkillPaths.length
120
+ && previousSkillPaths.every((eachPath, eachIndex) => eachPath === currentSkillPaths[eachIndex]);
121
+ if (isUnchanged) {
122
+ return { ...outcome, didWrite: false };
123
+ }
124
+ const refreshedManifest = {
125
+ ...(manifest === null ? SEEDED_MANIFEST_FIELDS : manifest),
126
+ [MANIFEST_SKILLS_KEY]: currentSkillPaths,
127
+ };
128
+ mkdirSync(join(pluginRoot, '.claude-plugin'), { recursive: true });
129
+ writeFileSync(
130
+ manifestPath,
131
+ JSON.stringify(refreshedManifest, null, MANIFEST_INDENT_SPACES) + '\n',
132
+ FILE_ENCODING,
133
+ );
134
+ return { ...outcome, didWrite: true };
135
+ }
136
+
137
+ /**
138
+ * Return the pstack plugin folder under a Claude home.
139
+ *
140
+ * @param {string} claudeHome The Claude home directory holding `skills`.
141
+ * @returns {string} The pstack plugin folder path, present or not.
142
+ */
143
+ export function pstackPluginRootFor(claudeHome) {
144
+ return join(claudeHome, 'skills', PSTACK_PLUGIN_DIRECTORY_NAME);
145
+ }
146
+
147
+ /**
148
+ * Return the Claude home used when no folder argument is given.
149
+ *
150
+ * A named profile sets `CLAUDE_CONFIG_DIR`. The installer writes into that
151
+ * root. The command-line form reads the same variable.
152
+ *
153
+ * @param {Record<string, string|undefined>} environment The process environment.
154
+ * @returns {string} The Claude home directory holding `skills`.
155
+ */
156
+ export function claudeHomeFrom(environment) {
157
+ return environment.CLAUDE_CONFIG_DIR || join(homedir(), CLAUDE_HOME_DIRECTORY_NAME);
158
+ }
159
+
160
+ function main() {
161
+ const explicitRoot = process.argv[2];
162
+ const pluginRoot = explicitRoot || pstackPluginRootFor(claudeHomeFrom(process.env));
163
+ const outcome = refreshPstackPluginManifest(pluginRoot);
164
+ if (!outcome.didWrite && outcome.reason) {
165
+ console.log(`No manifest written: ${outcome.reason}.`);
166
+ return;
167
+ }
168
+ if (!outcome.didWrite) {
169
+ console.log(`No change. ${outcome.currentSkillPaths.length} skills listed.`);
170
+ return;
171
+ }
172
+ console.log(`Updated. ${outcome.currentSkillPaths.length} skills listed.`);
173
+ for (const eachPath of outcome.currentSkillPaths) {
174
+ if (!outcome.previousSkillPaths.includes(eachPath)) console.log(` added ${eachPath}`);
175
+ }
176
+ for (const eachPath of outcome.previousSkillPaths) {
177
+ if (!outcome.currentSkillPaths.includes(eachPath)) console.log(` removed ${eachPath}`);
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Return whether node ran this module as its entry point.
183
+ *
184
+ * A directory link to this script gives node an invoked path that differs from
185
+ * this module's own path. Both sides resolve to their real location before
186
+ * the comparison.
187
+ *
188
+ * @param {string|undefined} invokedPath The path node reports as `argv[1]`.
189
+ * @returns {boolean} True when node ran this module as its entry point.
190
+ */
191
+ function isEntryPoint(invokedPath) {
192
+ if (!invokedPath) return false;
193
+ try {
194
+ return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(invokedPath);
195
+ } catch {
196
+ return fileURLToPath(import.meta.url) === invokedPath;
197
+ }
198
+ }
199
+
200
+ if (isEntryPoint(process.argv[1])) main();
@@ -11,7 +11,9 @@ from collections.abc import Mapping
11
11
  from pathlib import Path
12
12
 
13
13
  from dev_env_scripts_constants.shared_tree_constants import (
14
+ AGENTS_DIRECTORY_SUFFIX,
14
15
  CLAUDE_CONFIG_DIR_ENV_VAR,
16
+ DEFAULT_MANAGED_ROOT_NAME,
15
17
  PROCESS_TREE_DIRECTORY_NAME,
16
18
  PROCESS_TREE_KILL_MODULE_FILENAME,
17
19
  SCRIPTS_DIRECTORY_NAME,
@@ -29,6 +31,29 @@ def _shared_scripts_directory(from_root: Path, shared_subpackage_name: str) -> P
29
31
  )
30
32
 
31
33
 
34
+ def _linked_managed_root(module_path: Path, anchor_depth: int) -> Path | None:
35
+ """Resolve the managed root whose scripts pointer targets this installed tree.
36
+
37
+ Args:
38
+ module_path: The module path inside the managed scripts tree.
39
+ anchor_depth: Parent index of the package or agents root.
40
+
41
+ Returns:
42
+ The Claude managed root when its scripts pointer resolves to this tree.
43
+ """
44
+ package_root = module_path.resolve().parents[anchor_depth]
45
+ if not package_root.name.endswith(AGENTS_DIRECTORY_SUFFIX):
46
+ return None
47
+ managed_name = package_root.name.removesuffix(AGENTS_DIRECTORY_SUFFIX) or DEFAULT_MANAGED_ROOT_NAME
48
+ managed_root = package_root.parent / managed_name
49
+ linked_scripts = managed_root / SCRIPTS_DIRECTORY_NAME
50
+ if not linked_scripts.is_dir():
51
+ return None
52
+ if linked_scripts.resolve() != package_root / SCRIPTS_DIRECTORY_NAME:
53
+ return None
54
+ return managed_root
55
+
56
+
32
57
  def resolve_shared_scripts_directory(
33
58
  module_file: str | Path,
34
59
  all_environment: Mapping[str, str],
@@ -44,9 +69,10 @@ def resolve_shared_scripts_directory(
44
69
  ok: an un-resolved parent keeps the link side, where _shared sits
45
70
  flag: a resolved parent walks the link and misses _shared entirely
46
71
 
47
- Tries the un-resolved parent first, then the configured managed root,
48
- then the resolved parent. When none holds the marker, returns the
49
- un-resolved candidate, so an import failure names a readable path.
72
+ Tries the un-resolved parent first, then a verified installed scripts
73
+ pointer, then the configured managed root, then the resolved parent.
74
+ If none holds the marker, returns the un-resolved candidate so an
75
+ import failure names a readable path.
50
76
 
51
77
  Args:
52
78
  module_file: Path of the importing module (``__file__``).
@@ -63,6 +89,9 @@ def resolve_shared_scripts_directory(
63
89
  module_path.absolute().parents[anchor_depth], shared_subpackage_name
64
90
  )
65
91
  all_candidates = [un_resolved_candidate]
92
+ linked_root = _linked_managed_root(module_path, anchor_depth)
93
+ if linked_root is not None:
94
+ all_candidates.append(_shared_scripts_directory(linked_root, shared_subpackage_name))
66
95
  configured_root_text = all_environment.get(CLAUDE_CONFIG_DIR_ENV_VAR, "").strip()
67
96
  if configured_root_text:
68
97
  all_candidates.append(
@@ -171,3 +171,36 @@ def test_generalized_resolver_serves_a_deeper_anchor(tmp_path: Path) -> None:
171
171
  3,
172
172
  )
173
173
  assert resolved_scripts_directory == expected_scripts_directory
174
+
175
+
176
+ @pytest.mark.parametrize("managed_name", (".claude", "profile-a"))
177
+ def test_resolved_agents_scripts_find_the_verified_managed_root(
178
+ tmp_path: Path, managed_name: str,
179
+ ) -> None:
180
+ agents_name = ".agents" if managed_name == ".claude" else f"{managed_name}.agents"
181
+ agents_root = tmp_path / agents_name
182
+ scripts_directory = agents_root / "scripts"
183
+ scripts_directory.mkdir(parents=True)
184
+ module_file = scripts_directory / "runner.py"
185
+ module_file.write_text("", encoding="utf-8")
186
+ managed_root = tmp_path / managed_name
187
+ managed_root.mkdir()
188
+ _create_directory_link(from_link=managed_root / "scripts", to_target=scripts_directory)
189
+ expected = _write_process_tree_kill_stub(managed_root)
190
+ resolved_directory = shared_tree_paths.resolve_shared_process_tree_scripts_directory(module_file, {})
191
+ assert resolved_directory == expected
192
+
193
+
194
+ def test_neighboring_managed_root_cannot_supply_a_foreign_shared_tree(tmp_path: Path) -> None:
195
+ agents_root = tmp_path / ".agents"
196
+ scripts_directory = agents_root / "scripts"
197
+ scripts_directory.mkdir(parents=True)
198
+ module_file = scripts_directory / "runner.py"
199
+ module_file.write_text("", encoding="utf-8")
200
+ managed_root = tmp_path / ".claude"
201
+ foreign_scripts = managed_root / "scripts"
202
+ foreign_scripts.mkdir(parents=True)
203
+ _write_process_tree_kill_stub(managed_root)
204
+ resolved_directory = shared_tree_paths.resolve_shared_process_tree_scripts_directory(module_file, {})
205
+ expected = agents_root / "_shared" / "process-tree" / "scripts"
206
+ assert resolved_directory == expected
@@ -0,0 +1,120 @@
1
+ """Archive boundaries for automatic and explicitly requested policy checks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import subprocess
7
+ from pathlib import Path, PurePosixPath
8
+
9
+ import pytest
10
+ from policy_lint.engine import _runtime_document_set, lint
11
+ from policy_lint.model import (
12
+ Diagnostic,
13
+ Document,
14
+ DocumentRule,
15
+ DocumentSet,
16
+ LintRequest,
17
+ Location,
18
+ SelectionKind,
19
+ Severity,
20
+ TextDocument,
21
+ )
22
+
23
+
24
+ def _git(repository_root: Path, *arguments: str) -> None:
25
+ environment = {
26
+ variable_name: variable_text
27
+ for variable_name, variable_text in os.environ.items()
28
+ if not variable_name.upper().startswith("GIT_")
29
+ }
30
+ subprocess.run(
31
+ ["git", *arguments], cwd=repository_root, env=environment,
32
+ check=True, capture_output=True, text=True,
33
+ )
34
+
35
+
36
+ @pytest.fixture
37
+ def archive_repository(tmp_path: Path) -> Path:
38
+ _git(tmp_path, "init", "--quiet", "-b", "main")
39
+ _git(tmp_path, "config", "user.name", "Archive Tests")
40
+ _git(tmp_path, "config", "user.email", "archive@example.invalid")
41
+ _git(tmp_path, "config", "commit.gpgsign", "false")
42
+ for relative_path in ("active.py", "skill-archive/old.py", "src/skill-archive/live.py"):
43
+ file_path = tmp_path / relative_path
44
+ file_path.parent.mkdir(parents=True, exist_ok=True)
45
+ file_path.write_text("first\n", encoding="utf-8")
46
+ _git(tmp_path, "add", ".")
47
+ _git(tmp_path, "commit", "--quiet", "-m", "archive fixture")
48
+ for file_path in tmp_path.rglob("*.py"):
49
+ file_path.write_text("second\n", encoding="utf-8")
50
+ _git(tmp_path, "add", ".")
51
+ return tmp_path
52
+
53
+
54
+ def _marker_diagnostics(document: Document, repository_root: Path) -> tuple[Diagnostic, ...]:
55
+ assert (repository_root / document.path).is_file()
56
+ return (Diagnostic("marker", Severity.ERROR, "Checked document", Location(document.path, 1)),)
57
+
58
+
59
+ def _accept_document(document: Document) -> bool:
60
+ return bool(document.path.parts)
61
+
62
+
63
+ def _marker_rule() -> DocumentRule:
64
+ return DocumentRule(
65
+ "marker", frozenset({"changed", "repository"}),
66
+ _accept_document, _marker_diagnostics,
67
+ )
68
+
69
+
70
+ @pytest.mark.parametrize("selection", (SelectionKind.STAGED, SelectionKind.BASE, SelectionKind.REPOSITORY))
71
+ def test_automatic_checks_keep_live_code_and_exclude_only_root_archive(
72
+ archive_repository: Path, selection: SelectionKind,
73
+ ) -> None:
74
+ requests = {
75
+ SelectionKind.STAGED: LintRequest.staged(archive_repository),
76
+ SelectionKind.BASE: LintRequest.base(archive_repository, "HEAD"),
77
+ SelectionKind.REPOSITORY: LintRequest.repository(archive_repository),
78
+ }
79
+ report = lint(requests[selection], all_registry=(_marker_rule(),))
80
+ assert report.checked_documents == (
81
+ PurePosixPath("active.py"), PurePosixPath("src/skill-archive/live.py"),
82
+ )
83
+ assert len(report.diagnostics) == 2
84
+ assert report.executed_rules == ("marker",)
85
+ assert report.exit_code == 1
86
+
87
+
88
+ @pytest.mark.parametrize("selection", (SelectionKind.FILES, SelectionKind.TEXT))
89
+ def test_explicit_archive_checks_still_execute_rules(
90
+ archive_repository: Path, selection: SelectionKind,
91
+ ) -> None:
92
+ archive_path = Path("skill-archive/old.py")
93
+ request = (
94
+ LintRequest.files(archive_repository, [archive_path])
95
+ if selection is SelectionKind.FILES
96
+ else LintRequest(archive_repository, TextDocument(archive_path, "editor text\n"))
97
+ )
98
+ report = lint(request, all_registry=(_marker_rule(),))
99
+ assert report.checked_documents == (PurePosixPath("skill-archive/old.py"),)
100
+ assert len(report.diagnostics) == 1
101
+ assert report.exit_code == 1
102
+
103
+
104
+ def test_retirement_and_restoration_preserve_live_change_metadata(tmp_path: Path) -> None:
105
+ retired_path = PurePosixPath("active/retired.py")
106
+ archive_path = PurePosixPath("skill-archive/retired.py")
107
+ restored_source = PurePosixPath("skill-archive/restored.py")
108
+ restored_path = PurePosixPath("active/restored.py")
109
+ deleted_path = PurePosixPath("active/deleted.py")
110
+ document_set = DocumentSet(
111
+ (Document.from_text(archive_path, "archived"), Document.from_text(restored_path, "restored")),
112
+ SelectionKind.BASE,
113
+ tmp_path,
114
+ (deleted_path, PurePosixPath("skill-archive/deleted.py")),
115
+ ((retired_path, archive_path), (restored_source, restored_path)),
116
+ )
117
+ runtime_set = _runtime_document_set(document_set)
118
+ assert tuple(document.path for document in runtime_set.documents) == (restored_path,)
119
+ assert runtime_set.deleted_paths == (deleted_path, retired_path)
120
+ assert runtime_set.renamed_paths == ((restored_source, restored_path),)
@@ -10,7 +10,7 @@ from pathlib import Path, PurePosixPath
10
10
  import pytest
11
11
  from policy_lint import adapters
12
12
  from policy_lint.config.constants import INCOMPLETE_EXIT_CODE
13
- from policy_lint.engine import lint
13
+ from policy_lint.engine import _runtime_document_set, lint
14
14
  from policy_lint.model import (
15
15
  ContentOrigin,
16
16
  Diagnostic,
@@ -309,7 +309,11 @@ def test_state_description_diagnostics_maps_and_scopes_phrases(
309
309
  lint_report = _lint_documents(
310
310
  tmp_path,
311
311
  (source_document,),
312
- (_changed_document_rule("state-description", adapters.state_description_diagnostics),),
312
+ (
313
+ _changed_document_rule(
314
+ "state-description", adapters.state_description_diagnostics
315
+ ),
316
+ ),
313
317
  )
314
318
  assert len(lint_report.diagnostics) == 1
315
319
  assert lint_report.diagnostics[0].location is not None
@@ -351,8 +355,12 @@ def test_failed_adapter_rolls_back_continues_and_exits_incomplete(
351
355
  _source_document("pkg/second.py"),
352
356
  )
353
357
  all_rules = (
354
- _changed_document_rule("flaky", lambda document, _root: _flaky_diagnostics(document)),
355
- _changed_document_rule("stable", lambda document, _root: _stable_diagnostics(document)),
358
+ _changed_document_rule(
359
+ "flaky", lambda document, _root: _flaky_diagnostics(document)
360
+ ),
361
+ _changed_document_rule(
362
+ "stable", lambda document, _root: _stable_diagnostics(document)
363
+ ),
356
364
  )
357
365
  lint_report = _lint_documents(repository_root, all_documents, all_rules)
358
366
  assert lint_report.failed_rules == ("flaky",)
@@ -379,3 +387,16 @@ def test_process_interrupt_is_not_swallowed(tmp_path: Path) -> None:
379
387
  (_source_document("pkg/mod.py"),),
380
388
  (_changed_document_rule("interrupt", interrupt_diagnostics),),
381
389
  )
390
+
391
+
392
+ def test_runtime_document_set_keeps_base_revision_for_base_selection(
393
+ tmp_path: Path,
394
+ ) -> None:
395
+ document_set = DocumentSet(
396
+ (_source_document("pkg/mod.py"),),
397
+ SelectionKind.BASE,
398
+ tmp_path,
399
+ base_revision="abc123def",
400
+ )
401
+ runtime_document_set = _runtime_document_set(document_set)
402
+ assert runtime_document_set.base_revision == "abc123def"