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
@@ -2,8 +2,8 @@
2
2
 
3
3
  All prose a person reads (chat, `AskUserQuestion`, docs, PR/issue bodies, commits): everyday words, short active sentences, lead with the answer, define jargon on first use, and give only the detail the reader needs to act (progressive disclosure). Aim for first-pass readability by a non-specialist. Exact identifiers, file paths, and API names stay exact; code is out of scope.
4
4
 
5
- The `plain_language_blocker` PreToolUse hook (AskUserQuestion + `.md` Write/Edit/MultiEdit) blocks a heavy word and names the everyday swap; code fences, inline code, blockquotes, URLs, and file paths are skipped.
5
+ The `plain_language_blocker` PreToolUse hook (AskUserQuestion + `.md` Write/Edit/MultiEdit) names an everyday swap for a heavy word when `CLAUDE_PROSE_STYLE_ENFORCEMENT` is on (default off). That path is **allow with a systemMessage advisory** — it does not deny the write. AskUserQuestion lean-block structure stays always on and still denies chat detail. When the flag is off, heavy-word hits only emit privacy-safe advisory candidates (see [`docs/references/prose-style-enforcement.md`](../docs/references/prose-style-enforcement.md)). Code fences, inline code, blockquotes, URLs, and file paths are skipped.
6
6
 
7
- [`eli11-replies`](eli11-replies.md) governs reply length and shape; this rule governs word choice.
7
+ [`eli11-replies`](eli11-replies.md) governs reply length and shape; [`opus5-communication-contract`](opus5-communication-contract.md) governs progress and finals; this rule governs word choice.
8
8
 
9
9
  A project can keep its own domain words out of the check with a `.claude/plain-language-allow.json` file: a JSON array of terms. An exact, case-insensitive, whole-word match on any term passes. The hook reads this file only from inside the project tree, up to the repository root, so each project's allowlist stays with its own code.
@@ -30,4 +30,4 @@ Creative thinking, brainstorming, and novel ideas don't require citation. You ca
30
30
 
31
31
  - [`explore-thoroughly.md`](explore-thoroughly.md) — grounds an implementation plan in the codebase, as this rule grounds a factual claim in a source.
32
32
  - [`verify-runtime-state.md`](verify-runtime-state.md) — a runtime verdict needs a live probe from this session, not a citation.
33
- - [`hedging-claims.md`](hedging-claims.md) — the Stop hook that catches an unverified claim wearing a hedge word.
33
+ - [`hedging-claims.md`](hedging-claims.md) — the Stop hook that catches a bare hedge word; an explicit uncertainty label in the same sentence is the allowed alternative to a citation.
package/scripts/CLAUDE.md CHANGED
@@ -12,10 +12,12 @@ Utility scripts installed into `~/.claude/scripts/` by `bin/install.mjs`. Each s
12
12
  | `grok_headless_runner.py` | Runs one worker as headless `grok`: builds argv with no turn cap (the timeout is the only bound), mints a unique leader socket, captures streams, refuses a timeout that is missing, below `MIN_WORKER_TIMEOUT_SECONDS`, or above the `MAXIMUM_WORKER_TIMEOUT_SECONDS` (5400) ceiling, kills the whole process tree on timeout with grace and retries the kill-and-drain round once, classifies ok/usage_limit/auth_failure/timeout/kill_failed/error; exports `require_timeout_within_bounds` so a dispatcher can apply the same bounds without launching; imported by `spawn_grok_batch.py` and `resolve_worker_spawn.py` |
13
13
  | `grok_worker_preflight.py` | Soft gate for the headless grok tier: binary on PATH, `grok models` auth, install manifest + role agents, opt-in cached live ping; non-zero exit is fallthrough, not failure |
14
14
  | `setup_project_paths.py` | One-time bootstrap: discovers git repos via `es.exe` (Everything) and writes `~/.claude/project-paths.json`; never hardcodes scan roots |
15
- | `spawn_grok_batch.py` | Launches a fleet of headless grok workers from a JSON batch spec: gates once through the preflight, refuses a spec whose `timeout_seconds` exceeds `MAXIMUM_WORKER_TIMEOUT_SECONDS` (5400) rather than clamping it, assembles each prompt from part files, staggers starts, runs each through `grok_headless_runner.py`, and emits one batch summary JSON |
15
+ | `spawn_grok_batch.py` | Launches a fleet of headless grok workers from a JSON batch spec: gates once through the preflight, refuses a spec whose `timeout_seconds` exceeds `MAXIMUM_WORKER_TIMEOUT_SECONDS` (5400) rather than clamping it, assembles each prompt from part files, optionally binds a unique worker advisor per role via the lead-supplied `advisor.launcher` (placeholder default in constants), injects `advisor_session_id`, requires the same session ENDORSE or bounded CORRECTION/PLAN then ENDORSE, classifies bind/verdict/timeout/missing-launcher failures as `advisor_blocked`, staggers starts, runs each through `grok_headless_runner.py`, and emits one batch summary JSON |
16
16
  | `sweep_empty_dirs.py` | Deletes empty directories older than a configurable age under a given root; runs once (`--once`) or in continuous-watch mode |
17
17
  | `sync_to_cursor.py` | Entry point for syncing Claude rules to Cursor `.mdc` files; delegates to the `sync_to_cursor/` package |
18
18
  | `resolve_worker_spawn.py` | Dispatches a worker role through grok then claude fallback tiers (preflight, headless grok, `claude_agent_required` handoff, optional claude headless); applies `require_timeout_within_bounds` before the preflight, so an out-of-bounds `--timeout-seconds` prints a `timeout_out_of_bounds` outcome and exits 3 on every tier; protocol: [`../_shared/pr-loop/worker-spawn.md`](../_shared/pr-loop/worker-spawn.md) |
19
+ | `active_capability_references.py` | Inventories committed skill, agent, and command names and scans package markdown for active slash or backticked capability references; fails ban-listed names outside inert historical/example fences |
20
+ | `verify_installable_package.py` | Verifies the published package: runs real `npm pack`, checks every surface in `installable-surfaces.manifest.json` appears in the tarball, requires each `hooks.json` `.py` command to be git-tracked, and smoke-compiles those scripts (`node --check` on `bin/install.mjs`) |
19
21
 
20
22
  ## PowerShell scripts
21
23
 
@@ -24,6 +26,7 @@ Utility scripts installed into `~/.claude/scripts/` by `bin/install.mjs`. Each s
24
26
  | `Audit-ShellPolicy.ps1` | Audits Bash tool calls in session transcripts against the `pwsh`-only shell policy |
25
27
  | `Migrate-ShellPolicy.ps1` | Applies automated fixes for common shell-policy violations found by the audit script |
26
28
  | `Install-SweepEmptyDirs.ps1` | Registers `sweep_empty_dirs.py` as a scheduled task on Windows |
29
+ | `Sync-RepoMain.ps1` | Fast-forwards a main-tracking mirror (`-RepoPath`, required) to its remote main using `merge --ff-only`; refuses a different checkout or a diverged branch, logs each run to `-LogPath`, and exits non-zero so a scheduled-task history row surfaces the problem. `-StashDirty` stashes tracked changes right before the fast-forward under a dated `pre-sync stash` message and leaves the stash in the list |
27
30
  | `check.ps1` | Runs the full code-quality check suite |
28
31
  | `Show-Asset.ps1` | Opens files on screen, sizing each image window to the image's pixel dimensions (scaled to fit the screen); non-image files open in their default application |
29
32
  | `Get-SessionAccount.ps1` | Reports which Claude account the current session is actually logged into by comparing `~/.claude.json`'s CLI login against a `CLAUDE_USER_DATA_DIR` desktop profile's `lastKnownAccountUuid`, recovering the desktop account's email from profile storage when the two accounts differ |
@@ -33,7 +36,9 @@ Utility scripts installed into `~/.claude/scripts/` by `bin/install.mjs`. Each s
33
36
 
34
37
  | Entry | Description |
35
38
  |---|---|
36
- | `dev_env_scripts_constants/` | Named constants (`timing.py`) for scripts in this directory |
39
+ | `ci/` | CI-only adapters; `windows-installer-lifecycle.ps1` runs the Node installer 16-check driver under isolated HOME/USERPROFILE/GIT_CONFIG_GLOBAL and writes bounded evidence |
40
+ | `dev_env_scripts_constants/` | Named constants (`timing.py`, `grok_worker_constants.py`, …) for scripts in this directory, including worker-advisor placeholder launcher/model/effort, four verdict signals, correction cap, and advisor timeout |
41
+ | `profile-isolation-launchers/` | Profile-launcher contract plus MCP activation, Windows shortcut source semantics, and version preflight: profiles manifest, shared-path allowlist, profile resolver, `CLAUDE_CONFIG_DIR` authority, lean/full MCP bundle materialization into profile `mcp.json`, pure CLI/Desktop version-compatibility classifier consumed by launcher preflight, read-only shortcut inventory and preview reconcile under `windows/`. Live launcher execution, provenance pin, live shortcut mutation, and L1 deploy stay residual |
37
42
  | `sync_to_cursor/` | Package that builds Cursor `.mdc` files from Claude rules and docs |
38
43
  | `tests/` | pytest suite for the Python scripts and Pester (`*.Tests.ps1`) suite for the PowerShell scripts in this directory |
39
44
 
@@ -45,6 +50,12 @@ Python scripts (pytest):
45
50
  python -m pytest packages/claude-dev-env/scripts/tests/
46
51
  ```
47
52
 
53
+ Profile-isolation launcher contract (node:test):
54
+
55
+ ```bash
56
+ node --test packages/claude-dev-env/scripts/profile-isolation-launchers/**/*.test.mjs
57
+ ```
58
+
48
59
  PowerShell scripts (Pester 5+, `*.Tests.ps1`):
49
60
 
50
61
  ```powershell
@@ -0,0 +1,215 @@
1
+ #Requires -Version 7.0
2
+ <#
3
+ .SYNOPSIS
4
+ Fast-forward a local main-tracking mirror to its remote main.
5
+
6
+ .DESCRIPTION
7
+ Runs against any working tree that is meant to stay on one branch and follow
8
+ a remote, such as a read-only mirror kept current by a scheduled task.
9
+
10
+ Safety rules:
11
+ - Only runs when the checked-out branch is the named branch
12
+ - Only runs when there are no tracked changes (untracked files are fine),
13
+ unless -StashDirty is passed
14
+ - Uses merge --ff-only only (never force, never hard reset, never clean)
15
+ - Logs and exits non-zero on skip, diverge, or failure so a Task Scheduler
16
+ history row can surface a problem instead of a silent no-op
17
+
18
+ Exit codes: 0 success, 1 git operation failed, 2 configuration or repo
19
+ problem, 3 skipped by a safety rule, 4 diverged from the remote.
20
+
21
+ .PARAMETER RepoPath
22
+ Absolute path to the git working tree. Required.
23
+
24
+ .PARAMETER Remote
25
+ Remote name whose main is the source of truth (default: origin).
26
+
27
+ .PARAMETER Branch
28
+ Local branch that must be checked out (default: main).
29
+
30
+ .PARAMETER StashDirty
31
+ Changes the tracked-changes branch from skip to stash. With the switch, a
32
+ tree carrying tracked modifications is stashed with the message
33
+ "pre-sync stash <yyyy-MM-dd>: uncommitted other-stream edits before ff to
34
+ <remote>/<branch>" right before the fast-forward, and the stash is left in
35
+ the stash list for a human to inspect. Untracked files are never stashed.
36
+ The stash runs only when the fast-forward is going to happen, so an
37
+ already-up-to-date or diverged run creates no stash entry. If the
38
+ fast-forward then fails, the stash is popped back so the edits do not go
39
+ missing; when that pop also fails, the log names the stash entry so the
40
+ operator can recover it by hand.
41
+
42
+ .PARAMETER LogPath
43
+ Absolute path to the run log (default: $HOME\.claude\logs\sync-repo-main.log).
44
+ #>
45
+ [CmdletBinding()]
46
+ param(
47
+ [Parameter(Mandatory)]
48
+ [string]$RepoPath,
49
+ [string]$Remote = 'origin',
50
+ [string]$Branch = 'main',
51
+ [switch]$StashDirty,
52
+ [string]$LogPath = (Join-Path $HOME '.claude\logs\sync-repo-main.log')
53
+ )
54
+
55
+ $ErrorActionPreference = 'Stop'
56
+
57
+ $logDirectory = Split-Path -Parent $LogPath
58
+ if (-not (Test-Path -LiteralPath $logDirectory)) {
59
+ New-Item -ItemType Directory -Force -Path $logDirectory | Out-Null
60
+ }
61
+
62
+ function Write-SyncLog {
63
+ param(
64
+ [Parameter(Mandatory)]
65
+ [string]$Level,
66
+ [Parameter(Mandatory)]
67
+ [string]$Message
68
+ )
69
+ $line = '{0} [{1}] {2}' -f (Get-Date -Format 'o'), $Level, $Message
70
+ Add-Content -LiteralPath $LogPath -Value $line -Encoding utf8
71
+ Write-Host $line
72
+ }
73
+
74
+ function Get-GitExecutable {
75
+ $command = Get-Command git -ErrorAction SilentlyContinue
76
+ if ($null -eq $command) {
77
+ throw 'git executable not found on PATH'
78
+ }
79
+ return $command.Source
80
+ }
81
+
82
+ function Invoke-Git {
83
+ param(
84
+ [Parameter(Mandatory)]
85
+ [string[]]$Arguments
86
+ )
87
+ $output = & $script:gitPath -C $RepoPath @Arguments 2>&1
88
+ $exitCode = $LASTEXITCODE
89
+ return [pscustomobject]@{
90
+ ExitCode = $exitCode
91
+ Output = ($output | Out-String).Trim()
92
+ }
93
+ }
94
+
95
+ function Get-GitDecoration {
96
+ <#
97
+ Reads a value used only to decorate a log line. The sync has already
98
+ succeeded by the time these run, so a failure here returns a placeholder
99
+ rather than turning a completed sync into a failed run.
100
+ #>
101
+ param(
102
+ [Parameter(Mandatory)]
103
+ [string[]]$Arguments,
104
+ [Parameter(Mandatory)]
105
+ [string]$Placeholder
106
+ )
107
+ $result = Invoke-Git -Arguments $Arguments
108
+ if ($result.ExitCode -ne 0 -or -not $result.Output.Trim()) {
109
+ return $Placeholder
110
+ }
111
+ return $result.Output
112
+ }
113
+
114
+ function Invoke-GitOrExit {
115
+ param(
116
+ [Parameter(Mandatory)]
117
+ [string[]]$Arguments,
118
+ [Parameter(Mandatory)]
119
+ [string]$FailureMessage,
120
+ [Parameter(Mandatory)]
121
+ [int]$FailureExitCode
122
+ )
123
+ $result = Invoke-Git -Arguments $Arguments
124
+ if ($result.ExitCode -ne 0) {
125
+ Write-SyncLog -Level 'ERROR' -Message "${FailureMessage}: $($result.Output)"
126
+ exit $FailureExitCode
127
+ }
128
+ return $result.Output
129
+ }
130
+
131
+ try {
132
+ if (-not (Test-Path -LiteralPath $RepoPath)) {
133
+ Write-SyncLog -Level 'ERROR' -Message "repo path missing: $RepoPath"
134
+ exit 2
135
+ }
136
+
137
+ $script:gitPath = Get-GitExecutable
138
+
139
+ $currentBranch = Invoke-GitOrExit -Arguments @('branch', '--show-current') `
140
+ -FailureMessage 'cannot read current branch' -FailureExitCode 2
141
+ if ($currentBranch -ne $Branch) {
142
+ Write-SyncLog -Level 'SKIP' -Message "checkout is '$currentBranch', not '$Branch' (remote=$Remote)"
143
+ exit 3
144
+ }
145
+
146
+ $statusOutput = Invoke-GitOrExit -Arguments @('status', '--porcelain') `
147
+ -FailureMessage 'status failed' -FailureExitCode 2
148
+ $trackedDirtyLines = @($statusOutput -split "`r?`n" | Where-Object { $_.Trim() -and -not $_.StartsWith('??') })
149
+ if ($trackedDirtyLines.Count -gt 0 -and -not $StashDirty) {
150
+ Write-SyncLog -Level 'SKIP' -Message ("tracked changes present; refusing sync: " + ($trackedDirtyLines -join '; '))
151
+ exit 3
152
+ }
153
+
154
+ $null = Invoke-GitOrExit -Arguments @('fetch', $Remote, $Branch) `
155
+ -FailureMessage "fetch $Remote $Branch failed" -FailureExitCode 1
156
+
157
+ $tipOutput = Invoke-GitOrExit -Arguments @('rev-parse', $Branch, "$Remote/$Branch") `
158
+ -FailureMessage 'rev-parse of branch tips failed' -FailureExitCode 2
159
+ $tipLines = @($tipOutput -split "`r?`n" | Where-Object { $_.Trim() })
160
+ if ($tipLines.Count -ne 2) {
161
+ Write-SyncLog -Level 'ERROR' -Message "rev-parse returned $($tipLines.Count) tip(s), expected 2: $tipOutput"
162
+ exit 2
163
+ }
164
+ $localTip = $tipLines[0].Trim()
165
+ $remoteTip = $tipLines[1].Trim()
166
+
167
+ if ($localTip -eq $remoteTip) {
168
+ $remoteUrl = Get-GitDecoration -Arguments @('remote', 'get-url', $Remote) -Placeholder '<url unavailable>'
169
+ Write-SyncLog -Level 'OK' -Message "already up to date at $localTip ($Remote/$Branch $remoteUrl)"
170
+ exit 0
171
+ }
172
+
173
+ $ancestorResult = Invoke-Git -Arguments @('merge-base', '--is-ancestor', $Branch, "$Remote/$Branch")
174
+ if ($ancestorResult.ExitCode -ne 0) {
175
+ $countResult = Invoke-Git -Arguments @('rev-list', '--left-right', '--count', "${Branch}...${Remote}/${Branch}")
176
+ Write-SyncLog -Level 'ERROR' -Message "diverged from $Remote/$Branch (counts=$($countResult.Output)); fast-forward refused. local=$localTip remote=$remoteTip"
177
+ exit 4
178
+ }
179
+
180
+ $didStash = $false
181
+ if ($trackedDirtyLines.Count -gt 0) {
182
+ $stashMessage = 'pre-sync stash {0}: uncommitted other-stream edits before ff to {1}/{2}' -f (Get-Date -Format 'yyyy-MM-dd'), $Remote, $Branch
183
+ $stashResult = Invoke-Git -Arguments @('stash', 'push', '-m', $stashMessage)
184
+ if ($stashResult.ExitCode -ne 0) {
185
+ Write-SyncLog -Level 'ERROR' -Message "stash push failed; fast-forward abandoned: $($stashResult.Output)"
186
+ exit 1
187
+ }
188
+ $didStash = $true
189
+ Write-SyncLog -Level 'STASH' -Message ("stashed tracked changes as '" + $stashMessage + "' (left in stash list): " + ($trackedDirtyLines -join '; '))
190
+ }
191
+
192
+ $mergeResult = Invoke-Git -Arguments @('merge', '--ff-only', "$Remote/$Branch")
193
+ if ($mergeResult.ExitCode -ne 0) {
194
+ Write-SyncLog -Level 'ERROR' -Message "merge --ff-only failed: $($mergeResult.Output)"
195
+ if ($didStash) {
196
+ $popResult = Invoke-Git -Arguments @('stash', 'pop')
197
+ if ($popResult.ExitCode -eq 0) {
198
+ Write-SyncLog -Level 'OK' -Message "restored the stashed edits to the working tree after the failed fast-forward"
199
+ }
200
+ else {
201
+ Write-SyncLog -Level 'ERROR' -Message ("stash pop failed after the failed fast-forward; the edits are still stashed as '" + $stashMessage + "' — recover them with git stash list and git stash pop: " + $popResult.Output)
202
+ }
203
+ }
204
+ exit 1
205
+ }
206
+
207
+ $subject = Get-GitDecoration -Arguments @('log', '-1', '--format=%h %s') -Placeholder '<subject unavailable>'
208
+ $remoteUrl = Get-GitDecoration -Arguments @('remote', 'get-url', $Remote) -Placeholder '<url unavailable>'
209
+ Write-SyncLog -Level 'OK' -Message "$Branch $localTip -> $remoteTip | $subject | $Remote/$Branch $remoteUrl"
210
+ exit 0
211
+ }
212
+ catch {
213
+ Write-SyncLog -Level 'ERROR' -Message $_.Exception.Message
214
+ exit 1
215
+ }
@@ -0,0 +1,218 @@
1
+ #!/usr/bin/env python3
2
+ """Resolve active skill, agent, command, and tool references in prompt surfaces.
3
+
4
+ Builds inventories from committed skill manifests, agent markdown files, and
5
+ commands. Scans active prompt text for slash and backticked capability names.
6
+ Fails when an active (non-inert) reference names a banned capability.
7
+
8
+ ::
9
+
10
+ unresolved = unresolved_active_capabilities(package_root)
11
+ ok: unresolved == []
12
+ flag: active text mentions /qbug or `stub-detector`
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import re
18
+ from dataclasses import dataclass
19
+ from pathlib import Path
20
+
21
+ from dev_env_scripts_constants.active_capability_constants import (
22
+ ALL_BANNED_ACTIVE_CAPABILITY_NAMES,
23
+ ALL_INERT_FENCE_LANGUAGES,
24
+ BACKTICK_CAPABILITY_PATTERN,
25
+ BANNED_REASON_PREFIX,
26
+ FENCE_CLOSE_PATTERN,
27
+ FENCE_OPEN_PATTERN,
28
+ NEWLINE_JOIN_SEPARATOR,
29
+ PACKAGE_AGENTS_DIRECTORY,
30
+ PACKAGE_COMMANDS_DIRECTORY,
31
+ PACKAGE_SKILLS_DIRECTORY,
32
+ SKILL_MANIFEST_FILENAME,
33
+ SLASH_CAPABILITY_PATTERN,
34
+ UTF8_ENCODING,
35
+ )
36
+
37
+
38
+ @dataclass(frozen=True)
39
+ class CapabilityInventory:
40
+ """Committed capability names by kind."""
41
+
42
+ all_skill_names: frozenset[str]
43
+ all_agent_names: frozenset[str]
44
+ all_command_names: frozenset[str]
45
+
46
+ def all_known_names(self) -> frozenset[str]:
47
+ """Return the union of skill, agent, and command names."""
48
+ return self.all_skill_names | self.all_agent_names | self.all_command_names
49
+
50
+
51
+ @dataclass(frozen=True)
52
+ class UnresolvedCapabilityReference:
53
+ """One active reference that names a banned capability."""
54
+
55
+ file_path: str
56
+ line_number: int
57
+ capability_name: str
58
+ reason: str
59
+
60
+
61
+ def build_capability_inventory(from_package_root: Path) -> CapabilityInventory:
62
+ """Build inventories from committed skill/agent/command files.
63
+
64
+ Args:
65
+ from_package_root: ``packages/claude-dev-env`` root.
66
+
67
+ Returns:
68
+ Inventory of basenames present on disk.
69
+ """
70
+ skills_root = from_package_root / PACKAGE_SKILLS_DIRECTORY
71
+ agents_root = from_package_root / PACKAGE_AGENTS_DIRECTORY
72
+ commands_root = from_package_root / PACKAGE_COMMANDS_DIRECTORY
73
+ all_skills: set[str] = set()
74
+ if skills_root.is_dir():
75
+ for each_skill_directory in skills_root.iterdir():
76
+ if each_skill_directory.is_dir() and (
77
+ each_skill_directory / SKILL_MANIFEST_FILENAME
78
+ ).is_file():
79
+ all_skills.add(each_skill_directory.name)
80
+ all_agents: set[str] = set()
81
+ if agents_root.is_dir():
82
+ for each_agent_file in agents_root.glob("*.md"):
83
+ all_agents.add(each_agent_file.stem)
84
+ all_commands: set[str] = set()
85
+ if commands_root.is_dir():
86
+ for each_command_file in commands_root.glob("*.md"):
87
+ all_commands.add(each_command_file.stem)
88
+ return CapabilityInventory(
89
+ all_skill_names=frozenset(all_skills),
90
+ all_agent_names=frozenset(all_agents),
91
+ all_command_names=frozenset(all_commands),
92
+ )
93
+
94
+
95
+ def strip_inert_fenced_blocks(markdown_text: str) -> str:
96
+ """Remove fenced blocks tagged as historical/example content.
97
+
98
+ Args:
99
+ markdown_text: Full markdown source.
100
+
101
+ Returns:
102
+ Text with inert fenced blocks replaced by blank lines.
103
+ """
104
+ fence_open_pattern = re.compile(FENCE_OPEN_PATTERN)
105
+ fence_close_pattern = re.compile(FENCE_CLOSE_PATTERN)
106
+ all_lines = markdown_text.splitlines()
107
+ all_kept: list[str] = []
108
+ is_inside_inert = False
109
+ for each_line in all_lines:
110
+ open_match = fence_open_pattern.match(each_line)
111
+ if open_match and not is_inside_inert:
112
+ language = (open_match.group(1) or "").lower()
113
+ if language in ALL_INERT_FENCE_LANGUAGES:
114
+ is_inside_inert = True
115
+ all_kept.append("")
116
+ continue
117
+ if is_inside_inert:
118
+ if fence_close_pattern.match(each_line):
119
+ is_inside_inert = False
120
+ all_kept.append("")
121
+ continue
122
+ all_kept.append(each_line)
123
+ return NEWLINE_JOIN_SEPARATOR.join(all_kept)
124
+
125
+
126
+ def extract_active_capability_names(markdown_text: str) -> list[tuple[int, str]]:
127
+ """Return (1-based line, capability name) for active references.
128
+
129
+ Args:
130
+ markdown_text: Prompt or skill markdown.
131
+
132
+ Returns:
133
+ Ordered list of line number and capability name pairs.
134
+ """
135
+ slash_pattern = re.compile(SLASH_CAPABILITY_PATTERN)
136
+ backtick_pattern = re.compile(BACKTICK_CAPABILITY_PATTERN)
137
+ active_text = strip_inert_fenced_blocks(markdown_text)
138
+ all_hits: list[tuple[int, str]] = []
139
+ for each_line_number, each_line in enumerate(active_text.splitlines(), start=1):
140
+ for each_match in slash_pattern.finditer(each_line):
141
+ all_hits.append((each_line_number, each_match.group(1)))
142
+ for each_match in backtick_pattern.finditer(each_line):
143
+ all_hits.append((each_line_number, each_match.group(1)))
144
+ return all_hits
145
+
146
+
147
+ def classify_capability_reference(capability_name: str) -> str | None:
148
+ """Return a failure reason when the capability is banned.
149
+
150
+ Extractors also match ordinary prose tokens, so unknown names pass.
151
+ Only the ban list fails a reference.
152
+
153
+ Args:
154
+ capability_name: Extracted skill/command-like name.
155
+
156
+ Returns:
157
+ Reason string, or None when the reference is allowed.
158
+ """
159
+ if capability_name in ALL_BANNED_ACTIVE_CAPABILITY_NAMES:
160
+ return f"{BANNED_REASON_PREFIX}{capability_name}"
161
+ return None
162
+
163
+
164
+ def unresolved_active_capabilities(
165
+ from_package_root: Path,
166
+ *,
167
+ all_relative_markdown_paths: list[str] | None = None,
168
+ ) -> list[UnresolvedCapabilityReference]:
169
+ """Scan package markdown for banned active capability references.
170
+
171
+ Args:
172
+ from_package_root: ``packages/claude-dev-env`` root.
173
+ all_relative_markdown_paths: Optional explicit relative paths to scan;
174
+ defaults to all skills, agents, and commands markdown.
175
+
176
+ Returns:
177
+ Banned references with file, line, name, and reason.
178
+ """
179
+ if all_relative_markdown_paths is None:
180
+ all_relative_markdown_paths = _default_markdown_paths(from_package_root)
181
+ all_unresolved: list[UnresolvedCapabilityReference] = []
182
+ for each_relative_path in all_relative_markdown_paths:
183
+ absolute_path = from_package_root / each_relative_path
184
+ if not absolute_path.is_file():
185
+ continue
186
+ markdown_text = absolute_path.read_text(encoding=UTF8_ENCODING)
187
+ for each_line_number, each_name in extract_active_capability_names(
188
+ markdown_text
189
+ ):
190
+ reason = classify_capability_reference(each_name)
191
+ if reason is None:
192
+ continue
193
+ all_unresolved.append(
194
+ UnresolvedCapabilityReference(
195
+ file_path=each_relative_path.replace("\\", "/"),
196
+ line_number=each_line_number,
197
+ capability_name=each_name,
198
+ reason=reason,
199
+ )
200
+ )
201
+ return all_unresolved
202
+
203
+
204
+ def _default_markdown_paths(from_package_root: Path) -> list[str]:
205
+ all_paths: list[str] = []
206
+ for each_directory_name in (
207
+ PACKAGE_SKILLS_DIRECTORY,
208
+ PACKAGE_AGENTS_DIRECTORY,
209
+ PACKAGE_COMMANDS_DIRECTORY,
210
+ ):
211
+ directory_path = from_package_root / each_directory_name
212
+ if not directory_path.is_dir():
213
+ continue
214
+ for each_markdown_file in directory_path.rglob("*.md"):
215
+ all_paths.append(
216
+ each_markdown_file.relative_to(from_package_root).as_posix()
217
+ )
218
+ return all_paths
@@ -0,0 +1,78 @@
1
+ # Windows CI adapter for the Node installer lifecycle driver.
2
+ #
3
+ # The driver already redirects HOME, USERPROFILE, and GIT_CONFIG_GLOBAL into a
4
+ # throwaway sandbox. This adapter records that isolation contract, runs the
5
+ # 16-check driver, and writes bounded evidence under -EvidenceRoot so a failed
6
+ # Windows runner leaves an actionable artifact.
7
+ #
8
+ # Usage (from packages/claude-dev-env):
9
+ # pwsh -NoProfile -File scripts/ci/windows-installer-lifecycle.ps1 -EvidenceRoot <dir>
10
+
11
+ [CmdletBinding()]
12
+ param(
13
+ [Parameter(Mandatory = $true)]
14
+ [string]$EvidenceRoot
15
+ )
16
+
17
+ $ErrorActionPreference = 'Stop'
18
+
19
+ $packageRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
20
+ $driverPath = Join-Path $packageRoot '.claude\skills\run-claude-dev-env\driver.mjs'
21
+ $allIsolationEnvNames = @(
22
+ 'HOME',
23
+ 'USERPROFILE',
24
+ 'GIT_CONFIG_GLOBAL'
25
+ )
26
+
27
+ if (-not (Test-Path -LiteralPath $driverPath)) {
28
+ throw "Lifecycle driver missing at $driverPath"
29
+ }
30
+
31
+ New-Item -ItemType Directory -Force -Path $EvidenceRoot | Out-Null
32
+
33
+ $preflightPath = Join-Path $EvidenceRoot 'preflight.json'
34
+ $driverLogPath = Join-Path $EvidenceRoot 'driver.log'
35
+ $summaryPath = Join-Path $EvidenceRoot 'summary.json'
36
+
37
+ $preflight = [ordered]@{
38
+ packageRoot = $packageRoot
39
+ driverPath = $driverPath
40
+ platform = [System.Environment]::OSVersion.VersionString
41
+ nodeVersion = (& node --version 2>&1 | Out-String).Trim()
42
+ pythonVersion = (& python --version 2>&1 | Out-String).Trim()
43
+ isolationContract = $allIsolationEnvNames
44
+ note = 'Driver owns isolation roots; adapter never points at the real user profile.'
45
+ }
46
+ $preflight | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $preflightPath -Encoding utf8
47
+
48
+ Write-Host "Evidence root: $EvidenceRoot"
49
+ Write-Host "Driver: $driverPath"
50
+ Write-Host ("Isolation contract: {0} (driver-owned sandbox)" -f ($allIsolationEnvNames -join ', '))
51
+
52
+ $driverOutput = & node $driverPath 2>&1
53
+ $driverExitCode = $LASTEXITCODE
54
+ $driverOutput | ForEach-Object { Write-Host $_ }
55
+ $driverOutputText = ($driverOutput | Out-String)
56
+ [System.IO.File]::WriteAllText($driverLogPath, $driverOutputText)
57
+
58
+ $passedCheckMatch = [regex]::Match($driverOutputText, '(\d+)/(\d+) checks passed')
59
+ $summary = [ordered]@{
60
+ exitCode = $driverExitCode
61
+ allChecksPassed = ($driverExitCode -eq 0 -and $driverOutputText -match 'ALL CHECKS PASSED')
62
+ checksLine = if ($passedCheckMatch.Success) { $passedCheckMatch.Value } else { 'unparsed' }
63
+ evidenceFiles = @(
64
+ 'preflight.json',
65
+ 'driver.log',
66
+ 'summary.json'
67
+ )
68
+ }
69
+ $summary | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $summaryPath -Encoding utf8
70
+
71
+ if ($driverExitCode -ne 0) {
72
+ throw "Lifecycle driver exited $driverExitCode (see $driverLogPath)"
73
+ }
74
+ if ($driverOutputText -notmatch 'ALL CHECKS PASSED') {
75
+ throw "Lifecycle driver output missing ALL CHECKS PASSED (see $driverLogPath)"
76
+ }
77
+
78
+ Write-Host "Windows installer lifecycle: OK"