claude-dev-env 2.8.0 → 2.10.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 (323) hide show
  1. package/CLAUDE.md +8 -2
  2. package/_shared/pr-loop/CLAUDE.md +1 -1
  3. package/_shared/pr-loop/audit-contract.md +17 -6
  4. package/_shared/pr-loop/audit-reply-template.md +4 -4
  5. package/_shared/pr-loop/code-rules-gate.md +3 -5
  6. package/_shared/pr-loop/fix-protocol.md +2 -3
  7. package/_shared/pr-loop/gh-payloads.md +1 -1
  8. package/_shared/pr-loop/scripts/CLAUDE.md +1 -1
  9. package/_shared/pr-loop/scripts/README.md +1 -1
  10. package/_shared/pr-loop/scripts/code_rules_gate.py +2 -0
  11. package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_running.py +16 -1
  12. package/_shared/pr-loop/scripts/code_rules_gate_parts/git_blob_readers.py +11 -5
  13. package/_shared/pr-loop/scripts/preflight.py +9 -4
  14. package/_shared/pr-loop/scripts/reviews_disabled.py +50 -22
  15. package/_shared/pr-loop/scripts/tests/conftest.py +20 -0
  16. package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +6 -6
  17. package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +50 -6
  18. package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +1 -1
  19. package/_shared/pr-loop/state-schema.md +5 -14
  20. package/agents/CLAUDE.md +2 -2
  21. package/agents/clean-coder.md +62 -562
  22. package/agents/code-quality-agent.md +10 -2
  23. package/agents/code-verifier.md +1 -1
  24. package/agents/test_agent_frontmatter.py +58 -40
  25. package/audit-rubrics/CLAUDE.md +2 -1
  26. package/audit-rubrics/audit-categories.json +704 -0
  27. package/audit-rubrics/prompts/category-i-concurrency.md +1 -1
  28. package/bin/CLAUDE.md +16 -5
  29. package/bin/ever-shipped-skills.mjs +2 -0
  30. package/bin/install-plan.mjs +402 -0
  31. package/bin/install-transaction.mjs +455 -0
  32. package/bin/install.mjs +593 -147
  33. package/bin/install.plan.test.mjs +194 -0
  34. package/bin/install.profile-root.test.mjs +154 -0
  35. package/bin/install.profiles.test.mjs +253 -0
  36. package/bin/install.settings-defaults.test.mjs +200 -0
  37. package/bin/install.transaction.test.mjs +400 -0
  38. package/bin/install.uninstall-transaction.test.mjs +418 -0
  39. package/bin/merge_managed_permissions.mjs +130 -0
  40. package/bin/resolve-install-root.mjs +181 -0
  41. package/bin/select-install-targets.mjs +401 -0
  42. package/commands/CLAUDE.md +0 -2
  43. package/docs/CODE_RULES.md +4 -2
  44. package/docs/references/CLAUDE.md +3 -2
  45. package/docs/references/advisor-tool.md +44 -6
  46. package/docs/references/prose-style-enforcement.md +25 -0
  47. package/docs/references/team-advisor-skill.md +14 -8
  48. package/hooks/blocking/CLAUDE.md +6 -6
  49. package/hooks/blocking/_path_setup.py +9 -5
  50. package/hooks/blocking/code_rules_docstrings.py +124 -30
  51. package/hooks/blocking/code_rules_enforcer.py +161 -16
  52. package/hooks/blocking/code_rules_shared.py +40 -23
  53. package/hooks/blocking/config/CLAUDE.md +3 -5
  54. package/hooks/blocking/config/prose_style_enforcement_constants.py +38 -0
  55. package/hooks/blocking/config/test_prose_style_enforcement_constants.py +45 -0
  56. package/hooks/blocking/eli11_reply_enforcer.py +70 -113
  57. package/hooks/blocking/hedging_language_blocker.py +103 -20
  58. package/hooks/blocking/hook_prose_detector_consistency.py +6 -0
  59. package/hooks/blocking/intent_only_ending_blocker.py +6 -0
  60. package/hooks/blocking/plain_language_blocker.py +139 -20
  61. package/hooks/blocking/pre_tool_use_dispatcher.py +102 -20
  62. package/hooks/blocking/state_description_blocker.py +7 -1
  63. package/hooks/blocking/tdd_enforcer.py +8 -0
  64. package/hooks/blocking/test__path_setup.py +28 -0
  65. package/hooks/blocking/test_code_rules_enforcer_agent_home_tooling.py +99 -0
  66. package/hooks/blocking/test_code_rules_enforcer_docstring_args_span_scope.py +232 -10
  67. package/hooks/blocking/test_code_rules_enforcer_ephemeral.py +1 -1
  68. package/hooks/blocking/test_code_rules_enforcer_join_separator_magic.py +41 -0
  69. package/hooks/blocking/test_code_rules_enforcer_string_magic.py +98 -0
  70. package/hooks/blocking/test_eli11_reply_enforcer.py +98 -165
  71. package/hooks/blocking/test_hedging_language_blocker.py +120 -1
  72. package/hooks/blocking/test_hook_prose_detector_consistency.py +28 -8
  73. package/hooks/blocking/test_intent_only_ending_blocker.py +27 -2
  74. package/hooks/blocking/test_package_inventory_stale_blocker.py +11 -4
  75. package/hooks/blocking/test_plain_language_blocker.py +129 -19
  76. package/hooks/blocking/test_plain_language_blocker_allowlist.py +70 -26
  77. package/hooks/blocking/test_pre_tool_use_dispatcher.py +99 -26
  78. package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +87 -50
  79. package/hooks/blocking/test_state_description_blocker.py +45 -2
  80. package/hooks/blocking/test_stop_dispatcher.py +11 -7
  81. package/hooks/blocking/test_volatile_path_in_post_blocker.py +12 -12
  82. package/hooks/blocking/volatile_path_in_post_blocker.py +2 -2
  83. package/hooks/hooks.json +15 -0
  84. package/hooks/hooks_constants/CLAUDE.md +14 -3
  85. package/hooks/hooks_constants/ask_user_question_shape.py +281 -0
  86. package/hooks/hooks_constants/code_rules_enforcer_constants.py +2 -1
  87. package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
  88. package/hooks/hooks_constants/eli11_reply_enforcer_constants.py +5 -12
  89. package/hooks/hooks_constants/hedging_uncertainty_constants.py +42 -0
  90. package/hooks/hooks_constants/issue_tracker_session_starter_constants.py +23 -0
  91. package/hooks/hooks_constants/orchestrator_auto_starter_constants.py +23 -0
  92. package/hooks/hooks_constants/piped_pytest_blocker_constants.py +4 -1
  93. package/hooks/hooks_constants/plain_language_blocker_constants.py +4 -1
  94. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +6 -0
  95. package/hooks/hooks_constants/project_paths_reader.py +31 -4
  96. package/hooks/hooks_constants/prose_matcher_precision_constants.py +40 -0
  97. package/hooks/hooks_constants/pytest_invocation.py +354 -0
  98. package/hooks/hooks_constants/session_start_injector.py +163 -0
  99. package/hooks/hooks_constants/session_start_injector_constants.py +46 -0
  100. package/hooks/hooks_constants/shell_command_pipeline.py +397 -0
  101. package/hooks/hooks_constants/shell_command_segments.py +5 -0
  102. package/hooks/hooks_constants/test_ask_user_question_shape.py +167 -0
  103. package/hooks/hooks_constants/test_project_paths_reader.py +29 -0
  104. package/hooks/hooks_constants/test_prose_metrics_parity.py +8 -0
  105. package/hooks/hooks_constants/test_pytest_invocation.py +130 -0
  106. package/hooks/hooks_constants/test_session_start_injector.py +168 -0
  107. package/hooks/hooks_constants/test_shell_command_pipeline.py +135 -0
  108. package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +1 -1
  109. package/hooks/hooks_constants/working_style_prompt_constants.py +30 -0
  110. package/hooks/observability/CLAUDE.md +2 -0
  111. package/hooks/observability/prose_matcher_advisory.py +237 -0
  112. package/hooks/observability/test_prose_matcher_advisory.py +143 -0
  113. package/hooks/session/CLAUDE.md +9 -1
  114. package/hooks/session/_path_setup.py +13 -0
  115. package/hooks/session/issue_tracker_session_starter.py +135 -0
  116. package/hooks/session/orchestrator_auto_starter.py +100 -0
  117. package/hooks/session/test__path_setup.py +28 -0
  118. package/hooks/session/test_issue_tracker_session_starter.py +104 -0
  119. package/hooks/session/test_orchestrator_auto_starter.py +99 -0
  120. package/hooks/session/test_working_style_prompt.py +47 -0
  121. package/hooks/session/untracked_repo_detector.py +1 -24
  122. package/hooks/session/working_style_prompt.py +36 -0
  123. package/hooks/validators/_path_setup.py +19 -0
  124. package/hooks/validators/run_all_validators.py +8 -13
  125. package/installable-surfaces.manifest.json +21 -0
  126. package/output-styles/CLAUDE.md +15 -0
  127. package/package.json +5 -2
  128. package/rules/CLAUDE.md +1 -0
  129. package/rules/code-standards.md +33 -7
  130. package/rules/durable-post-artifacts.md +2 -2
  131. package/rules/eli11-replies.md +7 -2
  132. package/rules/hedging-claims.md +4 -2
  133. package/rules/long-horizon-autonomy.md +3 -1
  134. package/rules/opus5-communication-contract.md +45 -0
  135. package/rules/plain-language.md +2 -2
  136. package/rules/research-mode.md +1 -1
  137. package/scripts/CLAUDE.md +13 -2
  138. package/scripts/Sync-RepoMain.ps1 +215 -0
  139. package/scripts/active_capability_references.py +218 -0
  140. package/scripts/ci/windows-installer-lifecycle.ps1 +78 -0
  141. package/scripts/claude_chain_runner.py +394 -6
  142. package/scripts/claude_chain_usage.py +1 -1
  143. package/scripts/codex_compat_materializer.py +105 -85
  144. package/scripts/dev_env_scripts_constants/CLAUDE.md +3 -1
  145. package/scripts/dev_env_scripts_constants/active_capability_constants.py +46 -0
  146. package/scripts/dev_env_scripts_constants/claude_chain_constants.py +74 -0
  147. package/scripts/dev_env_scripts_constants/grok_run_ledger_constants.py +50 -0
  148. package/scripts/dev_env_scripts_constants/grok_worker_constants.py +104 -0
  149. package/scripts/dev_env_scripts_constants/verify_installable_package_constants.py +116 -0
  150. package/scripts/grok_patch_artifacts.py +123 -0
  151. package/scripts/grok_run_ledger.py +318 -0
  152. package/scripts/profile-isolation-launchers/config/mcp-bundles.json +25 -0
  153. package/scripts/profile-isolation-launchers/config/profile-isolation-constants.mjs +60 -0
  154. package/scripts/profile-isolation-launchers/config/profiles.manifest.json +54 -0
  155. package/scripts/profile-isolation-launchers/config/shared-allowlist.json +64 -0
  156. package/scripts/profile-isolation-launchers/launcher-runtime.mjs +180 -0
  157. package/scripts/profile-isolation-launchers/lib/profile-manifest.mjs +288 -0
  158. package/scripts/profile-isolation-launchers/mcp-bundles.mjs +275 -0
  159. package/scripts/profile-isolation-launchers/profile-isolation-contract.test.mjs +221 -0
  160. package/scripts/profile-isolation-launchers/tests/launcher-runtime.test.mjs +108 -0
  161. package/scripts/profile-isolation-launchers/tests/mcp-bundles.test.mjs +147 -0
  162. package/scripts/profile-isolation-launchers/tests/shortcut-contract.test.ps1 +102 -0
  163. package/scripts/profile-isolation-launchers/tests/version-compatibility.test.mjs +210 -0
  164. package/scripts/profile-isolation-launchers/version-compatibility.mjs +299 -0
  165. package/scripts/profile-isolation-launchers/windows/shortcut-inventory.ps1 +127 -0
  166. package/scripts/profile-isolation-launchers/windows/shortcut-manifest.json +51 -0
  167. package/scripts/profile-isolation-launchers/windows/shortcut-reconcile.ps1 +77 -0
  168. package/scripts/spawn_grok_batch.py +556 -9
  169. package/scripts/test_active_capability_references.py +108 -0
  170. package/scripts/test_claude_chain_runner.py +414 -82
  171. package/scripts/test_claude_chain_usage.py +12 -12
  172. package/scripts/test_grok_patch_artifacts.py +82 -0
  173. package/scripts/test_grok_run_ledger.py +116 -0
  174. package/scripts/test_resolve_worker_spawn.py +2 -2
  175. package/scripts/test_spawn_grok_batch.py +295 -0
  176. package/scripts/test_verify_installable_package.py +208 -0
  177. package/scripts/tests/test_codex_compat_materializer.py +33 -0
  178. package/scripts/verify_installable_package.py +612 -0
  179. package/settings.json +10 -0
  180. package/skills/CLAUDE.md +6 -2
  181. package/skills/_shared/CLAUDE.md +37 -4
  182. package/skills/_shared/advisor/CLAUDE.md +9 -0
  183. package/skills/_shared/advisor/advisor-protocol.md +5 -0
  184. package/skills/_shared/advisor/scripts/README.md +9 -0
  185. package/skills/_shared/end-of-run-gotcha-recommendations.md +156 -0
  186. package/skills/_shared/pr-loop/CLAUDE.md +18 -1
  187. package/skills/_shared/pr-loop/audit-contract.md +5 -0
  188. package/skills/_shared/pr-loop/audit-reply-template.md +5 -0
  189. package/skills/_shared/pr-loop/code-rules-gate.md +5 -0
  190. package/skills/_shared/pr-loop/fix-protocol.md +5 -0
  191. package/skills/_shared/pr-loop/gh-payloads.md +5 -0
  192. package/skills/_shared/pr-loop/post-audit-thread-contract.md +5 -0
  193. package/skills/_shared/pr-loop/precatch-rubric.md +5 -0
  194. package/skills/_shared/pr-loop/scripts/CLAUDE.md +9 -1
  195. package/skills/_shared/pr-loop/scripts/RUNTIME_SCRIPTS.md +29 -0
  196. package/skills/_shared/pr-loop/scripts/audit_category_schema.py +355 -0
  197. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +1 -0
  198. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py +32 -0
  199. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +7 -19
  200. package/skills/_shared/pr-loop/scripts/test_audit_category_schema.py +94 -0
  201. package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +21 -0
  202. package/skills/_shared/pr-loop/state-schema.md +5 -0
  203. package/skills/_shared/pr-loop/worker-spawn.md +5 -0
  204. package/skills/autoconverge/reference/convergence.md +2 -1
  205. package/skills/autoconverge/reference/stop-conditions.md +5 -3
  206. package/skills/beat-sheet/SKILL.md +54 -0
  207. package/skills/beat-sheet/reference/visual-beats.md +29 -0
  208. package/skills/bugteam/CONSTRAINTS.md +4 -4
  209. package/skills/bugteam/EXAMPLES.md +1 -1
  210. package/skills/bugteam/reference/README.md +1 -1
  211. package/skills/e-code-review/SKILL.md +29 -3
  212. package/skills/e-code-review/reference/effort-evaluation.md +35 -0
  213. package/skills/e-code-review/reference/medium.md +15 -4
  214. package/skills/e-code-review/reference/runner-selection.md +40 -0
  215. package/skills/e-code-review/scripts/config/e_code_review_effort_constants/__init__.py +41 -0
  216. package/skills/e-code-review/scripts/config/e_code_review_effort_constants/effort_constants.py +40 -0
  217. package/skills/e-code-review/scripts/e_code_review_scripts_constants/__init__.py +1 -0
  218. package/skills/e-code-review/scripts/e_code_review_scripts_constants/finding_pipeline_constants.py +49 -0
  219. package/skills/e-code-review/scripts/e_code_review_scripts_constants/grok_code_review_constants.py +55 -0
  220. package/skills/e-code-review/scripts/effort_defaults_evidence.json +186 -0
  221. package/skills/e-code-review/scripts/effort_evaluation.py +362 -0
  222. package/skills/e-code-review/scripts/finding_pipeline.py +140 -0
  223. package/skills/e-code-review/scripts/fixtures/demanding.json +26 -0
  224. package/skills/e-code-review/scripts/fixtures/easy.json +14 -0
  225. package/skills/e-code-review/scripts/fixtures/medium.json +20 -0
  226. package/skills/e-code-review/scripts/grok_code_review.py +230 -0
  227. package/skills/e-code-review/scripts/test_effort_evaluation.py +180 -0
  228. package/skills/e-code-review/scripts/test_finding_pipeline.py +197 -0
  229. package/skills/e-code-review/scripts/test_grok_code_review.py +289 -0
  230. package/skills/grok-spawn/SKILL.md +5 -0
  231. package/skills/orchestrator/SKILL.md +5 -0
  232. package/skills/plan-to-pr/scripts/create_packet.py +4 -4
  233. package/skills/plan-to-pr/scripts/load_skill_constants.py +41 -0
  234. package/skills/plan-to-pr/scripts/validate_packet.py +4 -4
  235. package/skills/plan-to-pr/scripts/validate_protocol.py +4 -1
  236. package/skills/plan-to-pr/scripts/validate_run.py +4 -1
  237. package/skills/pr-converge/scripts/check_convergence.py +21 -19
  238. package/skills/pr-converge/scripts/check_convergence_availability.py +50 -7
  239. package/skills/pr-converge/scripts/conftest.py +35 -0
  240. package/skills/pr-converge/scripts/test_check_convergence_availability.py +65 -0
  241. package/skills/pr-converge/scripts/test_check_convergence_codex.py +11 -1
  242. package/skills/pr-converge/scripts/test_check_convergence_contract.py +9 -2
  243. package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
  244. package/skills/rebase/SKILL.md +15 -3
  245. package/skills/reviewer-gates/SKILL.md +2 -2
  246. package/skills/show/SKILL.md +51 -0
  247. package/skills/show/references/accessibility.md +7 -0
  248. package/skills/show/references/art.md +3 -0
  249. package/skills/show/references/charts.md +3 -0
  250. package/skills/show/references/core-design.md +14 -0
  251. package/skills/show/references/erds.md +3 -0
  252. package/skills/show/references/flowcharts.md +3 -0
  253. package/skills/show/references/host-and-html.md +3 -0
  254. package/skills/show/references/illustrative-diagrams.md +10 -0
  255. package/skills/show/references/interaction.md +3 -0
  256. package/skills/show/references/mockups.md +3 -0
  257. package/skills/show/references/quality-gates.md +7 -0
  258. package/skills/show/references/structural-diagrams.md +3 -0
  259. package/skills/show/references/subject-inventory.md +21 -0
  260. package/skills/show/references/svg-contract.md +22 -0
  261. package/skills/show/routing.yaml +30 -0
  262. package/skills/show/samples/pr1262-v2.svg +222 -0
  263. package/skills/show/scripts/README.md +6 -0
  264. package/skills/show/scripts/validate-artifact.py +91 -0
  265. package/skills/show/scripts/validate-package.py +18 -0
  266. package/skills/show/templates/html-widget.html +4 -0
  267. package/skills/show/templates/svg-base.svg +19 -0
  268. package/skills/show/tests/fixtures/css-var.svg +6 -0
  269. package/skills/show/tests/fixtures/dead-ref.svg +7 -0
  270. package/skills/show/tests/fixtures/filled-glyph.svg +8 -0
  271. package/skills/show/tests/fixtures/inherited-fill.svg +18 -0
  272. package/skills/show/tests/fixtures/invalid.svg +1 -0
  273. package/skills/show/tests/fixtures/large-canvas.svg +21 -0
  274. package/skills/show/tests/fixtures/unfilled-connector.svg +15 -0
  275. package/skills/show/tests/fixtures/valid.html +1 -0
  276. package/skills/show/tests/test_validate-artifact.py +74 -0
  277. package/skills/show/tests/test_validators.py +59 -0
  278. package/skills/show/workflows/create-visual.md +13 -0
  279. package/skills/show/workflows/review-visual.md +20 -0
  280. package/skills/split-pr/SKILL.md +85 -0
  281. package/skills/split-pr/reference/path-layers.md +16 -0
  282. package/skills/split-pr/reference/proposal-format.md +15 -0
  283. package/skills/split-pr/reference/split-further-loop.md +10 -0
  284. package/skills/split-pr/reference/splitting-principles.md +26 -0
  285. package/skills/split-pr/scripts/analyze_pr.py +279 -0
  286. package/skills/split-pr/scripts/categorize_files.py +106 -0
  287. package/skills/split-pr/scripts/config/__init__.py +1 -0
  288. package/skills/split-pr/scripts/config/dependency_constants.py +14 -0
  289. package/skills/split-pr/scripts/config/git_operations_constants.py +36 -0
  290. package/skills/split-pr/scripts/config/packing_constants.py +61 -0
  291. package/skills/split-pr/scripts/config/plan_constants.py +49 -0
  292. package/skills/split-pr/scripts/config/split_pr_constants.py +110 -0
  293. package/skills/split-pr/scripts/execute_split_slices.py +82 -0
  294. package/skills/split-pr/scripts/pack_files_into_slices.py +212 -0
  295. package/skills/split-pr/scripts/split_pr_dependency_graph.py +70 -0
  296. package/skills/split-pr/scripts/split_pr_git_operations.py +184 -0
  297. package/skills/split-pr/scripts/split_pr_layer_order.py +58 -0
  298. package/skills/split-pr/scripts/split_pr_paginate.py +119 -0
  299. package/skills/split-pr/scripts/split_pr_process_runner.py +52 -0
  300. package/skills/split-pr/scripts/split_pr_script_types.py +126 -0
  301. package/skills/split-pr/scripts/split_pr_title.py +41 -0
  302. package/skills/split-pr/scripts/test_analyze_pr.py +228 -0
  303. package/skills/split-pr/scripts/test_categorize_files.py +55 -0
  304. package/skills/split-pr/scripts/test_categorize_files_packing.py +59 -0
  305. package/skills/split-pr/scripts/test_execute_split_slices.py +99 -0
  306. package/skills/split-pr/scripts/test_split_pr_dependency_graph.py +47 -0
  307. package/skills/split-pr/scripts/test_split_pr_git_operations.py +125 -0
  308. package/skills/split-pr/scripts/test_split_pr_layer_order.py +36 -0
  309. package/skills/split-pr/scripts/test_split_pr_paginate.py +65 -0
  310. package/skills/split-pr/scripts/test_split_pr_script_types.py +73 -0
  311. package/skills/split-pr/scripts/test_split_pr_title.py +28 -0
  312. package/skills/split-pr/scripts/test_verify_dependency_graph.py +46 -0
  313. package/skills/split-pr/scripts/test_verify_plan.py +56 -0
  314. package/skills/split-pr/scripts/test_verify_plan_contract.py +50 -0
  315. package/skills/split-pr/scripts/test_verify_plan_path_normalization.py +45 -0
  316. package/skills/split-pr/scripts/verify_dependency_graph.py +111 -0
  317. package/skills/split-pr/scripts/verify_plan.py +139 -0
  318. package/skills/task-build/reference/tool-routing.md +3 -0
  319. package/skills/team-advisor/SKILL.md +23 -44
  320. package/system-prompts/software-engineer.xml +17 -5
  321. package/commands/initialize.md +0 -90
  322. package/commands/stubcheck.md +0 -88
  323. package/skills/test_markdown_link_integrity.py +0 -107
@@ -0,0 +1,208 @@
1
+ """Unit tests for verify_installable_package helpers.
2
+
3
+ Covers path extraction, manifest loading, tarball surface matching, and
4
+ command walking without requiring a full npm pack on every case.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import sys
10
+ import tarfile
11
+ from pathlib import Path
12
+
13
+ _SCRIPTS_DIR = Path(__file__).resolve().parent
14
+ if str(_SCRIPTS_DIR) not in sys.path:
15
+ sys.path.insert(0, str(_SCRIPTS_DIR))
16
+
17
+ import verify_installable_package as mod
18
+ from dev_env_scripts_constants.verify_installable_package_constants import (
19
+ CLASS_PACKAGED,
20
+ MANIFEST_FILENAME,
21
+ PLUGIN_ROOT_TOKEN,
22
+ TARBALL_PACKAGE_PREFIX,
23
+ )
24
+
25
+ PACKAGE_ROOT = Path(__file__).resolve().parent.parent
26
+
27
+
28
+ def test_load_manifest_lists_hooks_and_claude_md() -> None:
29
+ manifest = mod.load_installable_surfaces_manifest(from_package_root=PACKAGE_ROOT)
30
+ all_directories = mod.required_directory_names(manifest)
31
+ all_root_files = mod.required_root_file_names(manifest)
32
+ assert "hooks" in all_directories
33
+ assert "output-styles" in all_directories
34
+ assert "CLAUDE.md" in all_root_files
35
+ assert MANIFEST_FILENAME in all_root_files
36
+
37
+
38
+ def test_extract_python_script_token_from_plugin_root_command() -> None:
39
+ command = (
40
+ f"python3 {PLUGIN_ROOT_TOKEN}hooks/blocking/pre_tool_use_dispatcher.py"
41
+ )
42
+ script_token = mod.extract_python_script_token(command)
43
+ assert script_token == f"{PLUGIN_ROOT_TOKEN}hooks/blocking/pre_tool_use_dispatcher.py"
44
+
45
+
46
+ def test_package_relative_hook_script_path_strips_plugin_root() -> None:
47
+ script_token = f"{PLUGIN_ROOT_TOKEN}hooks/blocking/stop_dispatcher.py"
48
+ relative_path = mod.package_relative_hook_script_path(script_token)
49
+ assert relative_path == "hooks/blocking/stop_dispatcher.py"
50
+
51
+
52
+ def test_extract_python_script_token_skips_inline_c_command() -> None:
53
+ command = 'python3 -c "import sys; print(1)"'
54
+ assert mod.extract_python_script_token(command) is None
55
+
56
+
57
+ def test_surface_appears_in_tarball_members_for_directory_children() -> None:
58
+ all_members = frozenset(
59
+ {
60
+ f"{TARBALL_PACKAGE_PREFIX}hooks/hooks.json",
61
+ f"{TARBALL_PACKAGE_PREFIX}CLAUDE.md",
62
+ }
63
+ )
64
+ assert mod.surface_appears_in_tarball_members("hooks", all_members)
65
+ assert mod.surface_appears_in_tarball_members("CLAUDE.md", all_members)
66
+ assert not mod.surface_appears_in_tarball_members("missing-dir", all_members)
67
+
68
+
69
+ def test_missing_manifest_surfaces_in_tarball_reports_absent_root_file() -> None:
70
+ manifest = {
71
+ "directories": ["hooks"],
72
+ "root_files": ["CLAUDE.md", "codex-capability-map.json"],
73
+ }
74
+ all_members = frozenset(
75
+ {
76
+ f"{TARBALL_PACKAGE_PREFIX}hooks/hooks.json",
77
+ f"{TARBALL_PACKAGE_PREFIX}CLAUDE.md",
78
+ }
79
+ )
80
+ missing = mod.missing_manifest_surfaces_in_tarball(manifest, all_members)
81
+ assert missing == ["codex-capability-map.json"]
82
+
83
+
84
+ def test_walk_json_command_fields_collects_nested_commands() -> None:
85
+ payload = {
86
+ "hooks": {
87
+ "PreToolUse": [
88
+ {"hooks": [{"type": "command", "command": "python3 a.py"}]},
89
+ ],
90
+ "Stop": [{"hooks": [{"command": "python3 b.py"}]}],
91
+ }
92
+ }
93
+ all_commands = mod.walk_json_command_fields(payload)
94
+ assert all_commands == ["python3 a.py", "python3 b.py"]
95
+
96
+
97
+ def test_list_npm_pack_tarball_members_reads_archive(tmp_path: Path) -> None:
98
+ tarball_path = tmp_path / "sample.tgz"
99
+ with tarfile.open(tarball_path, mode="w:gz") as archive:
100
+ member_path = tmp_path / "member.txt"
101
+ member_path.write_text("ok", encoding="utf-8")
102
+ archive.add(member_path, arcname=f"{TARBALL_PACKAGE_PREFIX}hooks/hooks.json")
103
+ all_members = mod.list_npm_pack_tarball_members(from_tarball_path=tarball_path)
104
+ assert f"{TARBALL_PACKAGE_PREFIX}hooks/hooks.json" in all_members
105
+
106
+
107
+ def test_classify_surface_marks_hooks_packaged() -> None:
108
+ packaged_names = mod.load_package_json_file_entries(from_package_root=PACKAGE_ROOT)
109
+ classification = mod.classify_surface(
110
+ "hooks",
111
+ packaged_names,
112
+ from_package_root=PACKAGE_ROOT,
113
+ )
114
+ assert classification == CLASS_PACKAGED
115
+
116
+
117
+ def test_load_hook_python_script_paths_returns_committed_hooks_paths() -> None:
118
+ all_paths = mod.load_hook_python_script_paths(from_package_root=PACKAGE_ROOT)
119
+ assert all_paths
120
+ assert all(each_path.startswith("hooks/") for each_path in all_paths)
121
+ assert all(each_path.endswith(".py") for each_path in all_paths)
122
+
123
+
124
+ def test_package_root_path_points_at_package_json() -> None:
125
+ package_root = mod.package_root_path()
126
+ assert (package_root / "package.json").is_file()
127
+ assert package_root.name == "claude-dev-env"
128
+
129
+
130
+ def test_repository_root_path_contains_packages_directory() -> None:
131
+ repository_root = mod.repository_root_path()
132
+ assert (repository_root / "packages" / "claude-dev-env").is_dir()
133
+
134
+
135
+ def test_resolve_npm_executable_returns_existing_path() -> None:
136
+ npm_path = mod.resolve_npm_executable()
137
+ assert Path(npm_path).exists()
138
+ assert "npm" in Path(npm_path).name.lower()
139
+
140
+
141
+ def test_tarball_member_prefix_for_surface_uses_package_prefix() -> None:
142
+ member_prefix = mod.tarball_member_prefix_for_surface("hooks")
143
+ assert member_prefix == f"{TARBALL_PACKAGE_PREFIX}hooks"
144
+
145
+
146
+ def test_run_npm_pack_writes_tarball(tmp_path: Path) -> None:
147
+ tarball_path = mod.run_npm_pack(
148
+ into_directory=tmp_path,
149
+ from_package_root=PACKAGE_ROOT,
150
+ )
151
+ assert tarball_path.is_file()
152
+ assert tarball_path.suffix == ".tgz"
153
+ all_members = mod.list_npm_pack_tarball_members(from_tarball_path=tarball_path)
154
+ assert any(each_member.startswith(f"{TARBALL_PACKAGE_PREFIX}hooks/") for each_member in all_members)
155
+
156
+
157
+ def test_list_git_tracked_paths_under_includes_hooks_json() -> None:
158
+ all_tracked = mod.list_git_tracked_paths_under(
159
+ under_relative_path="packages/claude-dev-env/hooks",
160
+ from_repository_root=mod.repository_root_path(),
161
+ )
162
+ assert "packages/claude-dev-env/hooks/hooks.json" in all_tracked
163
+
164
+
165
+ def test_untracked_or_missing_hook_scripts_empty_for_live_hooks() -> None:
166
+ all_hook_scripts = mod.load_hook_python_script_paths(from_package_root=PACKAGE_ROOT)
167
+ missing_scripts = mod.untracked_or_missing_hook_scripts(
168
+ all_package_relative_scripts=all_hook_scripts,
169
+ from_package_root=PACKAGE_ROOT,
170
+ from_repository_root=mod.repository_root_path(),
171
+ )
172
+ assert missing_scripts == []
173
+
174
+
175
+ def test_untracked_or_missing_hook_scripts_flags_unknown_path() -> None:
176
+ missing_scripts = mod.untracked_or_missing_hook_scripts(
177
+ all_package_relative_scripts=["hooks/does_not_exist_anywhere.py"],
178
+ from_package_root=PACKAGE_ROOT,
179
+ from_repository_root=mod.repository_root_path(),
180
+ )
181
+ assert missing_scripts == ["hooks/does_not_exist_anywhere.py"]
182
+
183
+
184
+ def test_smoke_compile_python_scripts_accepts_live_hooks() -> None:
185
+ all_hook_scripts = mod.load_hook_python_script_paths(from_package_root=PACKAGE_ROOT)
186
+ compile_failures = mod.smoke_compile_python_scripts(
187
+ all_package_relative_scripts=all_hook_scripts,
188
+ from_package_root=PACKAGE_ROOT,
189
+ )
190
+ assert compile_failures == []
191
+
192
+
193
+ def test_smoke_check_install_entrypoint_passes() -> None:
194
+ entrypoint_failure = mod.smoke_check_install_entrypoint(from_package_root=PACKAGE_ROOT)
195
+ assert entrypoint_failure is None
196
+
197
+
198
+ def test_verify_packed_manifest_surfaces_finds_no_gaps() -> None:
199
+ missing_surfaces = mod.verify_packed_manifest_surfaces(from_package_root=PACKAGE_ROOT)
200
+ assert missing_surfaces == []
201
+
202
+
203
+ def test_run_all_installable_package_checks_passes_on_live_package() -> None:
204
+ all_failures = mod.run_all_installable_package_checks(
205
+ from_package_root=PACKAGE_ROOT,
206
+ from_repository_root=mod.repository_root_path(),
207
+ )
208
+ assert all_failures == []
@@ -630,3 +630,36 @@ def test_cli_reports_publication_and_rollback_failure_as_json(
630
630
  assert report["reconcile_required"] is True
631
631
  assert report["errors"] == 2
632
632
  assert all(str(tmp_path) not in message for message in report["error_details"])
633
+
634
+ def test_frontmatter_accepts_empty_tools_list() -> None:
635
+ agent = parse_frontmatter(
636
+ Path("agent.md"),
637
+ "---\nname: x\ndescription: y\ntools: []\n---\n",
638
+ "agent.md",
639
+ )
640
+ assert agent.tools == ()
641
+
642
+
643
+ def test_frontmatter_accepts_block_scalar_description() -> None:
644
+ source_text = (
645
+ "---\n"
646
+ "name: x\n"
647
+ "description: |\n"
648
+ " line one\n"
649
+ " line two\n"
650
+ "---\n"
651
+ "body\n"
652
+ )
653
+ agent = parse_frontmatter(Path("agent.md"), source_text, "agent.md")
654
+ assert "line one" in agent.description
655
+ assert "line two" in agent.description
656
+
657
+
658
+ def test_frontmatter_rejects_non_string_name() -> None:
659
+ with pytest.raises(MaterializerError):
660
+ parse_frontmatter(
661
+ Path("bad.md"),
662
+ "---\nname: 1\ndescription: y\n---\n",
663
+ "bad.md",
664
+ )
665
+