claude-dev-env 2.9.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 (289) hide show
  1. package/CLAUDE.md +1 -1
  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 +58 -548
  22. package/agents/code-quality-agent.md +10 -2
  23. package/agents/code-verifier.md +1 -1
  24. package/agents/test_agent_frontmatter.py +32 -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/references/CLAUDE.md +1 -0
  44. package/docs/references/advisor-tool.md +1 -1
  45. package/docs/references/prose-style-enforcement.md +25 -0
  46. package/hooks/blocking/CLAUDE.md +6 -6
  47. package/hooks/blocking/_path_setup.py +9 -5
  48. package/hooks/blocking/code_rules_docstrings.py +124 -30
  49. package/hooks/blocking/code_rules_enforcer.py +161 -16
  50. package/hooks/blocking/code_rules_shared.py +40 -23
  51. package/hooks/blocking/config/CLAUDE.md +3 -5
  52. package/hooks/blocking/config/prose_style_enforcement_constants.py +38 -0
  53. package/hooks/blocking/config/test_prose_style_enforcement_constants.py +45 -0
  54. package/hooks/blocking/eli11_reply_enforcer.py +70 -113
  55. package/hooks/blocking/hedging_language_blocker.py +103 -20
  56. package/hooks/blocking/hook_prose_detector_consistency.py +6 -0
  57. package/hooks/blocking/intent_only_ending_blocker.py +6 -0
  58. package/hooks/blocking/plain_language_blocker.py +139 -20
  59. package/hooks/blocking/pre_tool_use_dispatcher.py +102 -20
  60. package/hooks/blocking/state_description_blocker.py +7 -1
  61. package/hooks/blocking/tdd_enforcer.py +8 -0
  62. package/hooks/blocking/test__path_setup.py +28 -0
  63. package/hooks/blocking/test_code_rules_enforcer_agent_home_tooling.py +99 -0
  64. package/hooks/blocking/test_code_rules_enforcer_docstring_args_span_scope.py +232 -10
  65. package/hooks/blocking/test_code_rules_enforcer_ephemeral.py +1 -1
  66. package/hooks/blocking/test_code_rules_enforcer_join_separator_magic.py +41 -0
  67. package/hooks/blocking/test_code_rules_enforcer_string_magic.py +98 -0
  68. package/hooks/blocking/test_eli11_reply_enforcer.py +98 -165
  69. package/hooks/blocking/test_hedging_language_blocker.py +120 -1
  70. package/hooks/blocking/test_hook_prose_detector_consistency.py +28 -8
  71. package/hooks/blocking/test_intent_only_ending_blocker.py +27 -2
  72. package/hooks/blocking/test_package_inventory_stale_blocker.py +11 -4
  73. package/hooks/blocking/test_plain_language_blocker.py +129 -19
  74. package/hooks/blocking/test_plain_language_blocker_allowlist.py +70 -26
  75. package/hooks/blocking/test_pre_tool_use_dispatcher.py +99 -26
  76. package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +87 -50
  77. package/hooks/blocking/test_state_description_blocker.py +45 -2
  78. package/hooks/blocking/test_stop_dispatcher.py +11 -7
  79. package/hooks/blocking/test_volatile_path_in_post_blocker.py +12 -12
  80. package/hooks/blocking/volatile_path_in_post_blocker.py +2 -2
  81. package/hooks/hooks.json +15 -0
  82. package/hooks/hooks_constants/CLAUDE.md +14 -3
  83. package/hooks/hooks_constants/ask_user_question_shape.py +281 -0
  84. package/hooks/hooks_constants/code_rules_enforcer_constants.py +2 -1
  85. package/hooks/hooks_constants/eli11_reply_enforcer_constants.py +5 -12
  86. package/hooks/hooks_constants/hedging_uncertainty_constants.py +42 -0
  87. package/hooks/hooks_constants/issue_tracker_session_starter_constants.py +23 -0
  88. package/hooks/hooks_constants/orchestrator_auto_starter_constants.py +23 -0
  89. package/hooks/hooks_constants/piped_pytest_blocker_constants.py +4 -1
  90. package/hooks/hooks_constants/plain_language_blocker_constants.py +4 -1
  91. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +6 -0
  92. package/hooks/hooks_constants/project_paths_reader.py +31 -4
  93. package/hooks/hooks_constants/prose_matcher_precision_constants.py +40 -0
  94. package/hooks/hooks_constants/pytest_invocation.py +354 -0
  95. package/hooks/hooks_constants/session_start_injector.py +163 -0
  96. package/hooks/hooks_constants/session_start_injector_constants.py +46 -0
  97. package/hooks/hooks_constants/shell_command_pipeline.py +397 -0
  98. package/hooks/hooks_constants/shell_command_segments.py +5 -0
  99. package/hooks/hooks_constants/test_ask_user_question_shape.py +167 -0
  100. package/hooks/hooks_constants/test_project_paths_reader.py +29 -0
  101. package/hooks/hooks_constants/test_prose_metrics_parity.py +8 -0
  102. package/hooks/hooks_constants/test_pytest_invocation.py +130 -0
  103. package/hooks/hooks_constants/test_session_start_injector.py +168 -0
  104. package/hooks/hooks_constants/test_shell_command_pipeline.py +135 -0
  105. package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +1 -1
  106. package/hooks/hooks_constants/working_style_prompt_constants.py +30 -0
  107. package/hooks/observability/CLAUDE.md +2 -0
  108. package/hooks/observability/prose_matcher_advisory.py +237 -0
  109. package/hooks/observability/test_prose_matcher_advisory.py +143 -0
  110. package/hooks/session/CLAUDE.md +9 -1
  111. package/hooks/session/_path_setup.py +13 -0
  112. package/hooks/session/issue_tracker_session_starter.py +135 -0
  113. package/hooks/session/orchestrator_auto_starter.py +100 -0
  114. package/hooks/session/test__path_setup.py +28 -0
  115. package/hooks/session/test_issue_tracker_session_starter.py +104 -0
  116. package/hooks/session/test_orchestrator_auto_starter.py +99 -0
  117. package/hooks/session/test_working_style_prompt.py +47 -0
  118. package/hooks/session/untracked_repo_detector.py +1 -24
  119. package/hooks/session/working_style_prompt.py +36 -0
  120. package/hooks/validators/_path_setup.py +19 -0
  121. package/hooks/validators/run_all_validators.py +8 -13
  122. package/installable-surfaces.manifest.json +21 -0
  123. package/output-styles/CLAUDE.md +1 -3
  124. package/package.json +4 -2
  125. package/rules/CLAUDE.md +1 -0
  126. package/rules/durable-post-artifacts.md +2 -2
  127. package/rules/eli11-replies.md +6 -1
  128. package/rules/hedging-claims.md +4 -2
  129. package/rules/long-horizon-autonomy.md +3 -1
  130. package/rules/opus5-communication-contract.md +45 -0
  131. package/rules/plain-language.md +2 -2
  132. package/rules/research-mode.md +1 -1
  133. package/scripts/CLAUDE.md +11 -0
  134. package/scripts/Sync-RepoMain.ps1 +215 -0
  135. package/scripts/active_capability_references.py +218 -0
  136. package/scripts/ci/windows-installer-lifecycle.ps1 +78 -0
  137. package/scripts/claude_chain_runner.py +394 -6
  138. package/scripts/claude_chain_usage.py +1 -1
  139. package/scripts/codex_compat_materializer.py +105 -85
  140. package/scripts/dev_env_scripts_constants/CLAUDE.md +2 -0
  141. package/scripts/dev_env_scripts_constants/active_capability_constants.py +46 -0
  142. package/scripts/dev_env_scripts_constants/claude_chain_constants.py +74 -0
  143. package/scripts/dev_env_scripts_constants/verify_installable_package_constants.py +116 -0
  144. package/scripts/profile-isolation-launchers/config/mcp-bundles.json +25 -0
  145. package/scripts/profile-isolation-launchers/config/profile-isolation-constants.mjs +60 -0
  146. package/scripts/profile-isolation-launchers/config/profiles.manifest.json +54 -0
  147. package/scripts/profile-isolation-launchers/config/shared-allowlist.json +64 -0
  148. package/scripts/profile-isolation-launchers/launcher-runtime.mjs +180 -0
  149. package/scripts/profile-isolation-launchers/lib/profile-manifest.mjs +288 -0
  150. package/scripts/profile-isolation-launchers/mcp-bundles.mjs +275 -0
  151. package/scripts/profile-isolation-launchers/profile-isolation-contract.test.mjs +221 -0
  152. package/scripts/profile-isolation-launchers/tests/launcher-runtime.test.mjs +108 -0
  153. package/scripts/profile-isolation-launchers/tests/mcp-bundles.test.mjs +147 -0
  154. package/scripts/profile-isolation-launchers/tests/shortcut-contract.test.ps1 +102 -0
  155. package/scripts/profile-isolation-launchers/tests/version-compatibility.test.mjs +210 -0
  156. package/scripts/profile-isolation-launchers/version-compatibility.mjs +299 -0
  157. package/scripts/profile-isolation-launchers/windows/shortcut-inventory.ps1 +127 -0
  158. package/scripts/profile-isolation-launchers/windows/shortcut-manifest.json +51 -0
  159. package/scripts/profile-isolation-launchers/windows/shortcut-reconcile.ps1 +77 -0
  160. package/scripts/spawn_grok_batch.py +3 -0
  161. package/scripts/test_active_capability_references.py +108 -0
  162. package/scripts/test_claude_chain_runner.py +414 -82
  163. package/scripts/test_claude_chain_usage.py +12 -12
  164. package/scripts/test_resolve_worker_spawn.py +2 -2
  165. package/scripts/test_verify_installable_package.py +208 -0
  166. package/scripts/tests/test_codex_compat_materializer.py +33 -0
  167. package/scripts/verify_installable_package.py +612 -0
  168. package/settings.json +10 -0
  169. package/skills/CLAUDE.md +2 -0
  170. package/skills/_shared/pr-loop/scripts/CLAUDE.md +1 -0
  171. package/skills/_shared/pr-loop/scripts/audit_category_schema.py +355 -0
  172. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +1 -0
  173. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py +32 -0
  174. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +7 -19
  175. package/skills/_shared/pr-loop/scripts/test_audit_category_schema.py +94 -0
  176. package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +21 -0
  177. package/skills/autoconverge/reference/convergence.md +2 -1
  178. package/skills/autoconverge/reference/stop-conditions.md +5 -3
  179. package/skills/beat-sheet/SKILL.md +54 -0
  180. package/skills/beat-sheet/reference/visual-beats.md +29 -0
  181. package/skills/bugteam/CONSTRAINTS.md +4 -4
  182. package/skills/bugteam/EXAMPLES.md +1 -1
  183. package/skills/bugteam/reference/README.md +1 -1
  184. package/skills/e-code-review/SKILL.md +26 -5
  185. package/skills/e-code-review/reference/effort-evaluation.md +35 -0
  186. package/skills/e-code-review/reference/medium.md +15 -4
  187. package/skills/e-code-review/scripts/config/e_code_review_effort_constants/__init__.py +41 -0
  188. package/skills/e-code-review/scripts/config/e_code_review_effort_constants/effort_constants.py +40 -0
  189. package/skills/e-code-review/scripts/e_code_review_scripts_constants/finding_pipeline_constants.py +49 -0
  190. package/skills/e-code-review/scripts/effort_defaults_evidence.json +186 -0
  191. package/skills/e-code-review/scripts/effort_evaluation.py +362 -0
  192. package/skills/e-code-review/scripts/finding_pipeline.py +140 -0
  193. package/skills/e-code-review/scripts/fixtures/demanding.json +26 -0
  194. package/skills/e-code-review/scripts/fixtures/easy.json +14 -0
  195. package/skills/e-code-review/scripts/fixtures/medium.json +20 -0
  196. package/skills/e-code-review/scripts/grok_code_review.py +16 -7
  197. package/skills/e-code-review/scripts/test_effort_evaluation.py +180 -0
  198. package/skills/e-code-review/scripts/test_finding_pipeline.py +197 -0
  199. package/skills/e-code-review/scripts/test_grok_code_review.py +77 -0
  200. package/skills/plan-to-pr/scripts/create_packet.py +4 -4
  201. package/skills/plan-to-pr/scripts/load_skill_constants.py +41 -0
  202. package/skills/plan-to-pr/scripts/validate_packet.py +4 -4
  203. package/skills/plan-to-pr/scripts/validate_protocol.py +4 -1
  204. package/skills/plan-to-pr/scripts/validate_run.py +4 -1
  205. package/skills/pr-converge/scripts/check_convergence.py +21 -19
  206. package/skills/pr-converge/scripts/check_convergence_availability.py +50 -7
  207. package/skills/pr-converge/scripts/conftest.py +35 -0
  208. package/skills/pr-converge/scripts/test_check_convergence_availability.py +65 -0
  209. package/skills/pr-converge/scripts/test_check_convergence_codex.py +11 -1
  210. package/skills/pr-converge/scripts/test_check_convergence_contract.py +9 -2
  211. package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
  212. package/skills/rebase/SKILL.md +15 -3
  213. package/skills/reviewer-gates/SKILL.md +2 -2
  214. package/skills/show/SKILL.md +51 -0
  215. package/skills/show/references/accessibility.md +7 -0
  216. package/skills/show/references/art.md +3 -0
  217. package/skills/show/references/charts.md +3 -0
  218. package/skills/show/references/core-design.md +14 -0
  219. package/skills/show/references/erds.md +3 -0
  220. package/skills/show/references/flowcharts.md +3 -0
  221. package/skills/show/references/host-and-html.md +3 -0
  222. package/skills/show/references/illustrative-diagrams.md +10 -0
  223. package/skills/show/references/interaction.md +3 -0
  224. package/skills/show/references/mockups.md +3 -0
  225. package/skills/show/references/quality-gates.md +7 -0
  226. package/skills/show/references/structural-diagrams.md +3 -0
  227. package/skills/show/references/subject-inventory.md +21 -0
  228. package/skills/show/references/svg-contract.md +22 -0
  229. package/skills/show/routing.yaml +30 -0
  230. package/skills/show/samples/pr1262-v2.svg +222 -0
  231. package/skills/show/scripts/README.md +6 -0
  232. package/skills/show/scripts/validate-artifact.py +91 -0
  233. package/skills/show/scripts/validate-package.py +18 -0
  234. package/skills/show/templates/html-widget.html +4 -0
  235. package/skills/show/templates/svg-base.svg +19 -0
  236. package/skills/show/tests/fixtures/css-var.svg +6 -0
  237. package/skills/show/tests/fixtures/dead-ref.svg +7 -0
  238. package/skills/show/tests/fixtures/filled-glyph.svg +8 -0
  239. package/skills/show/tests/fixtures/inherited-fill.svg +18 -0
  240. package/skills/show/tests/fixtures/invalid.svg +1 -0
  241. package/skills/show/tests/fixtures/large-canvas.svg +21 -0
  242. package/skills/show/tests/fixtures/unfilled-connector.svg +15 -0
  243. package/skills/show/tests/fixtures/valid.html +1 -0
  244. package/skills/show/tests/test_validate-artifact.py +74 -0
  245. package/skills/show/tests/test_validators.py +59 -0
  246. package/skills/show/workflows/create-visual.md +13 -0
  247. package/skills/show/workflows/review-visual.md +20 -0
  248. package/skills/split-pr/SKILL.md +85 -0
  249. package/skills/split-pr/reference/path-layers.md +16 -0
  250. package/skills/split-pr/reference/proposal-format.md +15 -0
  251. package/skills/split-pr/reference/split-further-loop.md +10 -0
  252. package/skills/split-pr/reference/splitting-principles.md +26 -0
  253. package/skills/split-pr/scripts/analyze_pr.py +279 -0
  254. package/skills/split-pr/scripts/categorize_files.py +106 -0
  255. package/skills/split-pr/scripts/config/__init__.py +1 -0
  256. package/skills/split-pr/scripts/config/dependency_constants.py +14 -0
  257. package/skills/split-pr/scripts/config/git_operations_constants.py +36 -0
  258. package/skills/split-pr/scripts/config/packing_constants.py +61 -0
  259. package/skills/split-pr/scripts/config/plan_constants.py +49 -0
  260. package/skills/split-pr/scripts/config/split_pr_constants.py +110 -0
  261. package/skills/split-pr/scripts/execute_split_slices.py +82 -0
  262. package/skills/split-pr/scripts/pack_files_into_slices.py +212 -0
  263. package/skills/split-pr/scripts/split_pr_dependency_graph.py +70 -0
  264. package/skills/split-pr/scripts/split_pr_git_operations.py +184 -0
  265. package/skills/split-pr/scripts/split_pr_layer_order.py +58 -0
  266. package/skills/split-pr/scripts/split_pr_paginate.py +119 -0
  267. package/skills/split-pr/scripts/split_pr_process_runner.py +52 -0
  268. package/skills/split-pr/scripts/split_pr_script_types.py +126 -0
  269. package/skills/split-pr/scripts/split_pr_title.py +41 -0
  270. package/skills/split-pr/scripts/test_analyze_pr.py +228 -0
  271. package/skills/split-pr/scripts/test_categorize_files.py +55 -0
  272. package/skills/split-pr/scripts/test_categorize_files_packing.py +59 -0
  273. package/skills/split-pr/scripts/test_execute_split_slices.py +99 -0
  274. package/skills/split-pr/scripts/test_split_pr_dependency_graph.py +47 -0
  275. package/skills/split-pr/scripts/test_split_pr_git_operations.py +125 -0
  276. package/skills/split-pr/scripts/test_split_pr_layer_order.py +36 -0
  277. package/skills/split-pr/scripts/test_split_pr_paginate.py +65 -0
  278. package/skills/split-pr/scripts/test_split_pr_script_types.py +73 -0
  279. package/skills/split-pr/scripts/test_split_pr_title.py +28 -0
  280. package/skills/split-pr/scripts/test_verify_dependency_graph.py +46 -0
  281. package/skills/split-pr/scripts/test_verify_plan.py +56 -0
  282. package/skills/split-pr/scripts/test_verify_plan_contract.py +50 -0
  283. package/skills/split-pr/scripts/test_verify_plan_path_normalization.py +45 -0
  284. package/skills/split-pr/scripts/verify_dependency_graph.py +111 -0
  285. package/skills/split-pr/scripts/verify_plan.py +139 -0
  286. package/system-prompts/software-engineer.xml +11 -2
  287. package/commands/initialize.md +0 -90
  288. package/commands/stubcheck.md +0 -88
  289. package/output-styles/caveman-agent.md +0 -37
@@ -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"