claude-dev-env 2.9.0 → 2.11.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 (318) hide show
  1. package/CLAUDE.md +2 -2
  2. package/_shared/advisor/CLAUDE.md +3 -2
  3. package/_shared/advisor/advisor-protocol.md +74 -108
  4. package/_shared/advisor/reference/advisor-block.md +37 -0
  5. package/_shared/advisor/reference/cli-chain.md +45 -0
  6. package/_shared/advisor/reference/consult-format.md +41 -0
  7. package/_shared/advisor/reference/lifecycle.md +21 -0
  8. package/_shared/advisor/reference/sol-rung.md +31 -0
  9. package/_shared/advisor/reference/spawn-walk-log.md +31 -0
  10. package/_shared/advisor/reference/third-party-bind.md +30 -0
  11. package/_shared/advisor/reference/warm-up.md +33 -0
  12. package/_shared/advisor/scripts/codex_sol_advisor.py +449 -0
  13. package/_shared/advisor/scripts/config/advisor_scripts_constants/advisor_route_constants.py +21 -0
  14. package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +19 -17
  15. package/_shared/advisor/scripts/config/advisor_scripts_constants/sol_advisor_constants.py +28 -0
  16. package/_shared/advisor/scripts/model_tier_run_validator.py +32 -9
  17. package/_shared/advisor/scripts/tests/test_codex_sol_advisor.py +474 -0
  18. package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +79 -0
  19. package/_shared/advisor/scripts/tests/test_tier_model_ids.py +39 -17
  20. package/_shared/advisor/scripts/tier_model_ids.py +24 -0
  21. package/_shared/pr-loop/CLAUDE.md +1 -1
  22. package/_shared/pr-loop/audit-contract.md +17 -6
  23. package/_shared/pr-loop/audit-reply-template.md +4 -4
  24. package/_shared/pr-loop/code-rules-gate.md +3 -5
  25. package/_shared/pr-loop/fix-protocol.md +2 -3
  26. package/_shared/pr-loop/gh-payloads.md +1 -1
  27. package/_shared/pr-loop/scripts/CLAUDE.md +1 -1
  28. package/_shared/pr-loop/scripts/README.md +1 -1
  29. package/_shared/pr-loop/scripts/code_rules_gate.py +2 -0
  30. package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_running.py +16 -1
  31. package/_shared/pr-loop/scripts/code_rules_gate_parts/git_blob_readers.py +11 -5
  32. package/_shared/pr-loop/scripts/preflight.py +9 -4
  33. package/_shared/pr-loop/scripts/reviews_disabled.py +50 -22
  34. package/_shared/pr-loop/scripts/tests/conftest.py +20 -0
  35. package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +6 -6
  36. package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +50 -6
  37. package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +1 -1
  38. package/_shared/pr-loop/state-schema.md +5 -14
  39. package/agents/CLAUDE.md +2 -2
  40. package/agents/clean-coder.md +58 -548
  41. package/agents/code-quality-agent.md +10 -2
  42. package/agents/code-verifier.md +1 -1
  43. package/agents/test_agent_frontmatter.py +32 -40
  44. package/audit-rubrics/CLAUDE.md +2 -1
  45. package/audit-rubrics/audit-categories.json +704 -0
  46. package/audit-rubrics/prompts/category-i-concurrency.md +1 -1
  47. package/bin/CLAUDE.md +16 -5
  48. package/bin/ever-shipped-skills.mjs +2 -0
  49. package/bin/install-plan.mjs +402 -0
  50. package/bin/install-transaction.mjs +455 -0
  51. package/bin/install.mjs +593 -147
  52. package/bin/install.plan.test.mjs +194 -0
  53. package/bin/install.profile-root.test.mjs +154 -0
  54. package/bin/install.profiles.test.mjs +253 -0
  55. package/bin/install.settings-defaults.test.mjs +200 -0
  56. package/bin/install.transaction.test.mjs +400 -0
  57. package/bin/install.uninstall-transaction.test.mjs +418 -0
  58. package/bin/merge_managed_permissions.mjs +130 -0
  59. package/bin/resolve-install-root.mjs +181 -0
  60. package/bin/select-install-targets.mjs +401 -0
  61. package/commands/CLAUDE.md +0 -2
  62. package/docs/references/CLAUDE.md +3 -1
  63. package/docs/references/advisor-tool.md +25 -7
  64. package/docs/references/prose-style-enforcement.md +25 -0
  65. package/docs/references/team-advisor-skill.md +3 -3
  66. package/docs/references/weak-executor-advisor.md +91 -0
  67. package/hooks/blocking/CLAUDE.md +6 -6
  68. package/hooks/blocking/_path_setup.py +9 -5
  69. package/hooks/blocking/code_rules_docstrings.py +124 -30
  70. package/hooks/blocking/code_rules_enforcer.py +161 -16
  71. package/hooks/blocking/code_rules_shared.py +40 -23
  72. package/hooks/blocking/config/CLAUDE.md +3 -5
  73. package/hooks/blocking/config/prose_style_enforcement_constants.py +38 -0
  74. package/hooks/blocking/config/test_prose_style_enforcement_constants.py +45 -0
  75. package/hooks/blocking/eli11_reply_enforcer.py +70 -113
  76. package/hooks/blocking/hedging_language_blocker.py +103 -20
  77. package/hooks/blocking/hook_prose_detector_consistency.py +6 -0
  78. package/hooks/blocking/intent_only_ending_blocker.py +6 -0
  79. package/hooks/blocking/plain_language_blocker.py +139 -20
  80. package/hooks/blocking/pre_tool_use_dispatcher.py +102 -20
  81. package/hooks/blocking/state_description_blocker.py +7 -1
  82. package/hooks/blocking/tdd_enforcer.py +8 -0
  83. package/hooks/blocking/test__path_setup.py +28 -0
  84. package/hooks/blocking/test_code_rules_enforcer_agent_home_tooling.py +99 -0
  85. package/hooks/blocking/test_code_rules_enforcer_docstring_args_span_scope.py +232 -10
  86. package/hooks/blocking/test_code_rules_enforcer_ephemeral.py +1 -1
  87. package/hooks/blocking/test_code_rules_enforcer_join_separator_magic.py +41 -0
  88. package/hooks/blocking/test_code_rules_enforcer_string_magic.py +98 -0
  89. package/hooks/blocking/test_eli11_reply_enforcer.py +98 -165
  90. package/hooks/blocking/test_fable_spawn_gate.py +18 -11
  91. package/hooks/blocking/test_hedging_language_blocker.py +120 -1
  92. package/hooks/blocking/test_hook_prose_detector_consistency.py +28 -8
  93. package/hooks/blocking/test_intent_only_ending_blocker.py +27 -2
  94. package/hooks/blocking/test_package_inventory_stale_blocker.py +11 -4
  95. package/hooks/blocking/test_plain_language_blocker.py +129 -19
  96. package/hooks/blocking/test_plain_language_blocker_allowlist.py +70 -26
  97. package/hooks/blocking/test_pre_tool_use_dispatcher.py +99 -26
  98. package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +87 -50
  99. package/hooks/blocking/test_state_description_blocker.py +45 -2
  100. package/hooks/blocking/test_stop_dispatcher.py +11 -7
  101. package/hooks/blocking/test_volatile_path_in_post_blocker.py +12 -12
  102. package/hooks/blocking/volatile_path_in_post_blocker.py +2 -2
  103. package/hooks/hooks.json +15 -0
  104. package/hooks/hooks_constants/CLAUDE.md +14 -3
  105. package/hooks/hooks_constants/ask_user_question_shape.py +281 -0
  106. package/hooks/hooks_constants/code_rules_enforcer_constants.py +2 -1
  107. package/hooks/hooks_constants/eli11_reply_enforcer_constants.py +5 -12
  108. package/hooks/hooks_constants/hedging_uncertainty_constants.py +42 -0
  109. package/hooks/hooks_constants/issue_tracker_session_starter_constants.py +23 -0
  110. package/hooks/hooks_constants/orchestrator_auto_starter_constants.py +23 -0
  111. package/hooks/hooks_constants/piped_pytest_blocker_constants.py +4 -1
  112. package/hooks/hooks_constants/plain_language_blocker_constants.py +4 -1
  113. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +6 -0
  114. package/hooks/hooks_constants/project_paths_reader.py +31 -4
  115. package/hooks/hooks_constants/prose_matcher_precision_constants.py +40 -0
  116. package/hooks/hooks_constants/pytest_invocation.py +354 -0
  117. package/hooks/hooks_constants/session_start_injector.py +163 -0
  118. package/hooks/hooks_constants/session_start_injector_constants.py +46 -0
  119. package/hooks/hooks_constants/shell_command_pipeline.py +397 -0
  120. package/hooks/hooks_constants/shell_command_segments.py +5 -0
  121. package/hooks/hooks_constants/test_ask_user_question_shape.py +167 -0
  122. package/hooks/hooks_constants/test_project_paths_reader.py +29 -0
  123. package/hooks/hooks_constants/test_prose_metrics_parity.py +8 -0
  124. package/hooks/hooks_constants/test_pytest_invocation.py +130 -0
  125. package/hooks/hooks_constants/test_session_start_injector.py +168 -0
  126. package/hooks/hooks_constants/test_shell_command_pipeline.py +135 -0
  127. package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +1 -1
  128. package/hooks/hooks_constants/working_style_prompt_constants.py +30 -0
  129. package/hooks/observability/CLAUDE.md +2 -0
  130. package/hooks/observability/prose_matcher_advisory.py +237 -0
  131. package/hooks/observability/test_prose_matcher_advisory.py +143 -0
  132. package/hooks/session/CLAUDE.md +9 -1
  133. package/hooks/session/_path_setup.py +13 -0
  134. package/hooks/session/issue_tracker_session_starter.py +135 -0
  135. package/hooks/session/orchestrator_auto_starter.py +100 -0
  136. package/hooks/session/test__path_setup.py +28 -0
  137. package/hooks/session/test_issue_tracker_session_starter.py +104 -0
  138. package/hooks/session/test_orchestrator_auto_starter.py +99 -0
  139. package/hooks/session/test_working_style_prompt.py +47 -0
  140. package/hooks/session/untracked_repo_detector.py +1 -24
  141. package/hooks/session/working_style_prompt.py +36 -0
  142. package/hooks/validators/_path_setup.py +19 -0
  143. package/hooks/validators/run_all_validators.py +8 -13
  144. package/installable-surfaces.manifest.json +21 -0
  145. package/output-styles/CLAUDE.md +1 -3
  146. package/package.json +4 -2
  147. package/rules/CLAUDE.md +1 -0
  148. package/rules/durable-post-artifacts.md +2 -2
  149. package/rules/eli11-replies.md +6 -1
  150. package/rules/hedging-claims.md +4 -2
  151. package/rules/long-horizon-autonomy.md +3 -1
  152. package/rules/opus5-communication-contract.md +45 -0
  153. package/rules/plain-language.md +2 -2
  154. package/rules/research-mode.md +1 -1
  155. package/scripts/CLAUDE.md +11 -0
  156. package/scripts/Sync-RepoMain.ps1 +215 -0
  157. package/scripts/active_capability_references.py +218 -0
  158. package/scripts/ci/windows-installer-lifecycle.ps1 +78 -0
  159. package/scripts/claude_chain_runner.py +394 -6
  160. package/scripts/claude_chain_usage.py +1 -1
  161. package/scripts/codex_compat_materializer.py +105 -85
  162. package/scripts/dev_env_scripts_constants/CLAUDE.md +2 -0
  163. package/scripts/dev_env_scripts_constants/active_capability_constants.py +46 -0
  164. package/scripts/dev_env_scripts_constants/claude_chain_constants.py +74 -0
  165. package/scripts/dev_env_scripts_constants/verify_installable_package_constants.py +116 -0
  166. package/scripts/profile-isolation-launchers/config/mcp-bundles.json +25 -0
  167. package/scripts/profile-isolation-launchers/config/profile-isolation-constants.mjs +60 -0
  168. package/scripts/profile-isolation-launchers/config/profiles.manifest.json +54 -0
  169. package/scripts/profile-isolation-launchers/config/shared-allowlist.json +64 -0
  170. package/scripts/profile-isolation-launchers/launcher-runtime.mjs +180 -0
  171. package/scripts/profile-isolation-launchers/lib/profile-manifest.mjs +288 -0
  172. package/scripts/profile-isolation-launchers/mcp-bundles.mjs +275 -0
  173. package/scripts/profile-isolation-launchers/profile-isolation-contract.test.mjs +221 -0
  174. package/scripts/profile-isolation-launchers/tests/launcher-runtime.test.mjs +108 -0
  175. package/scripts/profile-isolation-launchers/tests/mcp-bundles.test.mjs +147 -0
  176. package/scripts/profile-isolation-launchers/tests/shortcut-contract.test.ps1 +102 -0
  177. package/scripts/profile-isolation-launchers/tests/version-compatibility.test.mjs +210 -0
  178. package/scripts/profile-isolation-launchers/version-compatibility.mjs +299 -0
  179. package/scripts/profile-isolation-launchers/windows/shortcut-inventory.ps1 +127 -0
  180. package/scripts/profile-isolation-launchers/windows/shortcut-manifest.json +51 -0
  181. package/scripts/profile-isolation-launchers/windows/shortcut-reconcile.ps1 +77 -0
  182. package/scripts/spawn_grok_batch.py +3 -0
  183. package/scripts/test_active_capability_references.py +108 -0
  184. package/scripts/test_claude_chain_runner.py +414 -82
  185. package/scripts/test_claude_chain_usage.py +12 -12
  186. package/scripts/test_resolve_worker_spawn.py +2 -2
  187. package/scripts/test_verify_installable_package.py +208 -0
  188. package/scripts/tests/test_codex_compat_materializer.py +33 -0
  189. package/scripts/verify_installable_package.py +612 -0
  190. package/settings.json +10 -0
  191. package/skills/CLAUDE.md +2 -0
  192. package/skills/_shared/advisor/CLAUDE.md +1 -1
  193. package/skills/_shared/advisor/scripts/README.md +2 -0
  194. package/skills/_shared/pr-loop/scripts/CLAUDE.md +1 -0
  195. package/skills/_shared/pr-loop/scripts/audit_category_schema.py +355 -0
  196. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +1 -0
  197. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py +32 -0
  198. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +7 -19
  199. package/skills/_shared/pr-loop/scripts/test_audit_category_schema.py +94 -0
  200. package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +21 -0
  201. package/skills/autoconverge/reference/convergence.md +2 -1
  202. package/skills/autoconverge/reference/stop-conditions.md +5 -3
  203. package/skills/beat-sheet/SKILL.md +54 -0
  204. package/skills/beat-sheet/reference/visual-beats.md +29 -0
  205. package/skills/bugteam/CONSTRAINTS.md +4 -4
  206. package/skills/bugteam/EXAMPLES.md +1 -1
  207. package/skills/bugteam/reference/README.md +1 -1
  208. package/skills/e-code-review/SKILL.md +26 -5
  209. package/skills/e-code-review/reference/effort-evaluation.md +35 -0
  210. package/skills/e-code-review/reference/medium.md +15 -4
  211. package/skills/e-code-review/scripts/config/e_code_review_effort_constants/__init__.py +41 -0
  212. package/skills/e-code-review/scripts/config/e_code_review_effort_constants/effort_constants.py +40 -0
  213. package/skills/e-code-review/scripts/e_code_review_scripts_constants/finding_pipeline_constants.py +49 -0
  214. package/skills/e-code-review/scripts/effort_defaults_evidence.json +186 -0
  215. package/skills/e-code-review/scripts/effort_evaluation.py +362 -0
  216. package/skills/e-code-review/scripts/finding_pipeline.py +140 -0
  217. package/skills/e-code-review/scripts/fixtures/demanding.json +26 -0
  218. package/skills/e-code-review/scripts/fixtures/easy.json +14 -0
  219. package/skills/e-code-review/scripts/fixtures/medium.json +20 -0
  220. package/skills/e-code-review/scripts/grok_code_review.py +16 -7
  221. package/skills/e-code-review/scripts/test_effort_evaluation.py +180 -0
  222. package/skills/e-code-review/scripts/test_finding_pipeline.py +197 -0
  223. package/skills/e-code-review/scripts/test_grok_code_review.py +77 -0
  224. package/skills/grokify/SKILL.md +1 -1
  225. package/skills/grokify/templates/handoff-template.md +2 -2
  226. package/skills/orchestrator/SKILL.md +5 -4
  227. package/skills/plan-to-pr/scripts/create_packet.py +4 -4
  228. package/skills/plan-to-pr/scripts/load_skill_constants.py +41 -0
  229. package/skills/plan-to-pr/scripts/validate_packet.py +4 -4
  230. package/skills/plan-to-pr/scripts/validate_protocol.py +4 -1
  231. package/skills/plan-to-pr/scripts/validate_run.py +4 -1
  232. package/skills/pr-converge/scripts/check_convergence.py +21 -19
  233. package/skills/pr-converge/scripts/check_convergence_availability.py +50 -7
  234. package/skills/pr-converge/scripts/conftest.py +35 -0
  235. package/skills/pr-converge/scripts/test_check_convergence_availability.py +65 -0
  236. package/skills/pr-converge/scripts/test_check_convergence_codex.py +11 -1
  237. package/skills/pr-converge/scripts/test_check_convergence_contract.py +9 -2
  238. package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
  239. package/skills/rebase/SKILL.md +15 -3
  240. package/skills/reviewer-gates/SKILL.md +2 -2
  241. package/skills/show/SKILL.md +51 -0
  242. package/skills/show/references/accessibility.md +7 -0
  243. package/skills/show/references/art.md +3 -0
  244. package/skills/show/references/charts.md +3 -0
  245. package/skills/show/references/core-design.md +14 -0
  246. package/skills/show/references/erds.md +3 -0
  247. package/skills/show/references/flowcharts.md +3 -0
  248. package/skills/show/references/host-and-html.md +3 -0
  249. package/skills/show/references/illustrative-diagrams.md +10 -0
  250. package/skills/show/references/interaction.md +3 -0
  251. package/skills/show/references/mockups.md +3 -0
  252. package/skills/show/references/quality-gates.md +7 -0
  253. package/skills/show/references/structural-diagrams.md +3 -0
  254. package/skills/show/references/subject-inventory.md +21 -0
  255. package/skills/show/references/svg-contract.md +22 -0
  256. package/skills/show/routing.yaml +30 -0
  257. package/skills/show/samples/pr1262-v2.svg +222 -0
  258. package/skills/show/scripts/README.md +6 -0
  259. package/skills/show/scripts/validate-artifact.py +91 -0
  260. package/skills/show/scripts/validate-package.py +18 -0
  261. package/skills/show/templates/html-widget.html +4 -0
  262. package/skills/show/templates/svg-base.svg +19 -0
  263. package/skills/show/tests/fixtures/css-var.svg +6 -0
  264. package/skills/show/tests/fixtures/dead-ref.svg +7 -0
  265. package/skills/show/tests/fixtures/filled-glyph.svg +8 -0
  266. package/skills/show/tests/fixtures/inherited-fill.svg +18 -0
  267. package/skills/show/tests/fixtures/invalid.svg +1 -0
  268. package/skills/show/tests/fixtures/large-canvas.svg +21 -0
  269. package/skills/show/tests/fixtures/unfilled-connector.svg +15 -0
  270. package/skills/show/tests/fixtures/valid.html +1 -0
  271. package/skills/show/tests/test_validate-artifact.py +74 -0
  272. package/skills/show/tests/test_validators.py +59 -0
  273. package/skills/show/workflows/create-visual.md +13 -0
  274. package/skills/show/workflows/review-visual.md +20 -0
  275. package/skills/split-pr/SKILL.md +85 -0
  276. package/skills/split-pr/reference/path-layers.md +16 -0
  277. package/skills/split-pr/reference/proposal-format.md +15 -0
  278. package/skills/split-pr/reference/split-further-loop.md +10 -0
  279. package/skills/split-pr/reference/splitting-principles.md +26 -0
  280. package/skills/split-pr/scripts/analyze_pr.py +279 -0
  281. package/skills/split-pr/scripts/categorize_files.py +106 -0
  282. package/skills/split-pr/scripts/config/__init__.py +1 -0
  283. package/skills/split-pr/scripts/config/dependency_constants.py +14 -0
  284. package/skills/split-pr/scripts/config/git_operations_constants.py +36 -0
  285. package/skills/split-pr/scripts/config/packing_constants.py +61 -0
  286. package/skills/split-pr/scripts/config/plan_constants.py +49 -0
  287. package/skills/split-pr/scripts/config/split_pr_constants.py +110 -0
  288. package/skills/split-pr/scripts/execute_split_slices.py +82 -0
  289. package/skills/split-pr/scripts/pack_files_into_slices.py +212 -0
  290. package/skills/split-pr/scripts/split_pr_dependency_graph.py +70 -0
  291. package/skills/split-pr/scripts/split_pr_git_operations.py +184 -0
  292. package/skills/split-pr/scripts/split_pr_layer_order.py +58 -0
  293. package/skills/split-pr/scripts/split_pr_paginate.py +119 -0
  294. package/skills/split-pr/scripts/split_pr_process_runner.py +52 -0
  295. package/skills/split-pr/scripts/split_pr_script_types.py +126 -0
  296. package/skills/split-pr/scripts/split_pr_title.py +41 -0
  297. package/skills/split-pr/scripts/test_analyze_pr.py +228 -0
  298. package/skills/split-pr/scripts/test_categorize_files.py +55 -0
  299. package/skills/split-pr/scripts/test_categorize_files_packing.py +59 -0
  300. package/skills/split-pr/scripts/test_execute_split_slices.py +99 -0
  301. package/skills/split-pr/scripts/test_split_pr_dependency_graph.py +47 -0
  302. package/skills/split-pr/scripts/test_split_pr_git_operations.py +125 -0
  303. package/skills/split-pr/scripts/test_split_pr_layer_order.py +36 -0
  304. package/skills/split-pr/scripts/test_split_pr_paginate.py +65 -0
  305. package/skills/split-pr/scripts/test_split_pr_script_types.py +73 -0
  306. package/skills/split-pr/scripts/test_split_pr_title.py +28 -0
  307. package/skills/split-pr/scripts/test_verify_dependency_graph.py +46 -0
  308. package/skills/split-pr/scripts/test_verify_plan.py +56 -0
  309. package/skills/split-pr/scripts/test_verify_plan_contract.py +50 -0
  310. package/skills/split-pr/scripts/test_verify_plan_path_normalization.py +45 -0
  311. package/skills/split-pr/scripts/verify_dependency_graph.py +111 -0
  312. package/skills/split-pr/scripts/verify_plan.py +139 -0
  313. package/skills/team-advisor/SKILL.md +7 -4
  314. package/skills/team-advisor/reference/advisor-docs-review.md +207 -0
  315. package/system-prompts/software-engineer.xml +11 -2
  316. package/commands/initialize.md +0 -90
  317. package/commands/stubcheck.md +0 -88
  318. package/output-styles/caveman-agent.md +0 -37
@@ -3,7 +3,6 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import argparse
6
- import ast
7
6
  import hashlib
8
7
  import json
9
8
  import os
@@ -15,6 +14,8 @@ from dataclasses import dataclass, field
15
14
  from pathlib import Path
16
15
  from typing import Callable, Iterable
17
16
 
17
+ import yaml
18
+
18
19
 
19
20
  ManagedContent = str | bytes
20
21
 
@@ -24,7 +25,6 @@ reparse_point_attribute_name = "FILE_ATTRIBUTE_REPARSE_POINT"
24
25
  manifest_indentation_width = 2
25
26
  publish_plan_max_positional_arguments = 3
26
27
  publish_plan_failure_injector_position = 2
27
- frontmatter_required_fields = ("name", "description")
28
28
  frontmatter_unsupported_fields = ("tools", "model", "color")
29
29
  full_prune_opt_in_flag = "--allow-prune-all"
30
30
  unreadable_source_root_message = (
@@ -259,73 +259,67 @@ def validate_target_path(target_root: Path, relative_path: str) -> Path:
259
259
  return _validate_containment(root, candidate)
260
260
 
261
261
 
262
- def _parse_frontmatter_scalar(serialized_field_text: str) -> str | tuple[str, ...] | None:
263
- normalized_field_text = serialized_field_text.strip()
264
- if not normalized_field_text:
265
- return ""
266
- if normalized_field_text.startswith("["):
267
- return _parse_frontmatter_list(normalized_field_text)
268
- try:
269
- parsed = ast.literal_eval(normalized_field_text)
270
- except (SyntaxError, ValueError):
271
- if normalized_field_text[:1] in {'"', "'"} or normalized_field_text[:1] == "[":
272
- raise MaterializerError("malformed frontmatter value")
273
- return normalized_field_text
274
- if isinstance(parsed, str):
275
- return parsed
276
- if isinstance(parsed, list) and all(isinstance(each_entry, str) for each_entry in parsed):
277
- return tuple(parsed)
278
- raise MaterializerError("frontmatter value must be a string or string list")
279
-
280
-
281
- def _parse_frontmatter_list(serialized_list: str) -> tuple[str, ...]:
282
- if not serialized_list.endswith("]"):
283
- raise MaterializerError("malformed frontmatter value")
284
- all_entries: list[str] = []
285
- entry_start = 1
286
- quote: str | None = None
287
- has_escape_pending = False
288
- for each_index, each_character in enumerate(serialized_list[1:-1], 1):
289
- if has_escape_pending:
290
- has_escape_pending = False
291
- continue
292
- if quote == '"' and each_character == "\\":
293
- has_escape_pending = True
294
- continue
295
- if each_character in {'"', "'"}:
296
- if quote is None:
297
- quote = each_character
298
- elif quote == each_character:
299
- quote = None
300
- continue
301
- if each_character == "," and quote is None:
302
- all_entries.append(_parse_frontmatter_list_entry(serialized_list[entry_start:each_index]))
303
- entry_start = each_index + 1
304
- if quote is not None or has_escape_pending:
305
- raise MaterializerError("malformed frontmatter value")
306
- all_entries.append(_parse_frontmatter_list_entry(serialized_list[entry_start:-1]))
307
- return tuple(all_entries) if all_entries != [""] else ()
308
-
309
-
310
- def _parse_frontmatter_list_entry(raw_entry: str) -> str:
311
- entry = raw_entry.strip()
312
- if not entry:
313
- raise MaterializerError("malformed frontmatter value")
314
- try:
315
- parsed = ast.literal_eval(entry)
316
- except (SyntaxError, ValueError):
317
- if entry[0] in {'"', "'"}:
318
- raise MaterializerError("malformed frontmatter value")
319
- if any(character in entry for character in "[]{}:"):
320
- raise MaterializerError("malformed frontmatter value")
321
- return entry
322
- if not isinstance(parsed, str):
323
- raise MaterializerError("frontmatter list entries must be strings")
324
- return parsed
262
+ class _UniqueKeySafeLoader(yaml.SafeLoader):
263
+ """SafeLoader that rejects duplicate mapping keys (safe_load keeps last)."""
264
+
265
+
266
+ def _construct_mapping_rejecting_duplicates(
267
+ loader: yaml.SafeLoader,
268
+ node: yaml.nodes.MappingNode,
269
+ is_deep: bool = False,
270
+ ) -> dict:
271
+ mapping: dict = {}
272
+ for each_key_node, each_field_node in node.value:
273
+ key = loader.construct_object(each_key_node, deep=is_deep)
274
+ if key in mapping:
275
+ raise yaml.constructor.ConstructorError(
276
+ None,
277
+ None,
278
+ f"duplicate key {key!r}",
279
+ each_key_node.start_mark,
280
+ )
281
+ mapping[key] = loader.construct_object(each_field_node, deep=is_deep)
282
+ return mapping
283
+
284
+
285
+ _UniqueKeySafeLoader.add_constructor(
286
+ yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
287
+ _construct_mapping_rejecting_duplicates,
288
+ )
289
+
290
+
291
+ def _require_nonempty_string(field_content: object, source_path: Path) -> str:
292
+ if not isinstance(field_content, str):
293
+ raise MaterializerError(f"name and description are required: {source_path}")
294
+ if not field_content.strip():
295
+ raise MaterializerError(f"name and description are required: {source_path}")
296
+ return field_content
297
+
298
+
299
+ def _require_optional_string(
300
+ field_content: object, field_name: str, source_path: Path
301
+ ) -> str | None:
302
+ if field_content is None:
303
+ return None
304
+ if not isinstance(field_content, str):
305
+ raise MaterializerError(f"{field_name} must be a string: {source_path}")
306
+ return field_content
307
+
308
+
309
+ def _require_tools_list(tools_content: object, source_path: Path) -> tuple[str, ...]:
310
+ if tools_content is None:
311
+ return ()
312
+ if isinstance(tools_content, str):
313
+ return (tools_content,)
314
+ if not isinstance(tools_content, list):
315
+ raise MaterializerError(f"tools must be a list: {source_path}")
316
+ if not all(isinstance(each_entry, str) for each_entry in tools_content):
317
+ raise MaterializerError(f"tools must be a list: {source_path}")
318
+ return tuple(tools_content)
325
319
 
326
320
 
327
321
  def parse_frontmatter(source_path: Path, source_text: str, relative_source: str) -> ClaudeAgent:
328
- """Parse one Claude agent's frontmatter.
322
+ """Parse one Claude agent's frontmatter through validated YAML.
329
323
 
330
324
  Args:
331
325
  source_path: Path used in validation errors.
@@ -342,30 +336,56 @@ def parse_frontmatter(source_path: Path, source_text: str, relative_source: str)
342
336
  lines = source_text.splitlines()
343
337
  if len(lines) < 3 or lines[0].strip() != "---":
344
338
  raise MaterializerError(f"malformed frontmatter: {source_path}")
345
- delimiters = [each_index for each_index, line in enumerate(lines[1:], 1) if line.strip() == "---"]
339
+ delimiters = [
340
+ each_index for each_index, line in enumerate(lines[1:], 1) if line.strip() == "---"
341
+ ]
346
342
  if len(delimiters) != 1:
347
343
  raise MaterializerError(f"malformed frontmatter delimiters: {source_path}")
348
- all_fields: dict[str, str | tuple[str, ...] | None] = {}
349
- for each_line in lines[1 : delimiters[0]]:
350
- if not each_line.strip() or ":" not in each_line:
351
- raise MaterializerError(f"malformed frontmatter: {source_path}")
352
- key, serialized_field_text = each_line.split(":", 1)
353
- key = key.strip()
354
- if key in all_fields or not re.fullmatch(r"[A-Za-z][A-Za-z0-9_-]*", key):
344
+ frontmatter_text = line_separator.join(lines[1 : delimiters[0]]) + line_separator
345
+ try:
346
+ parsed_fields = yaml.load(frontmatter_text, Loader=_UniqueKeySafeLoader)
347
+ except yaml.YAMLError as yaml_error:
348
+ raise MaterializerError(f"malformed frontmatter: {source_path}") from yaml_error
349
+ if not isinstance(parsed_fields, dict):
350
+ raise MaterializerError(f"malformed frontmatter: {source_path}")
351
+ for each_key in parsed_fields:
352
+ if not isinstance(each_key, str) or not re.fullmatch(
353
+ r"[A-Za-z][A-Za-z0-9_-]*", each_key
354
+ ):
355
355
  raise MaterializerError(f"malformed frontmatter key: {source_path}")
356
- all_fields[key] = _parse_frontmatter_scalar(serialized_field_text)
357
- unknown = tuple(sorted(each_key for each_key in all_fields if each_key not in frontmatter_allowed_fields))
356
+ unknown = tuple(
357
+ sorted(
358
+ each_key
359
+ for each_key in parsed_fields
360
+ if each_key not in frontmatter_allowed_fields
361
+ )
362
+ )
358
363
  if unknown:
359
364
  raise MaterializerError(f"unknown frontmatter keys: {source_path}")
360
- unsupported = tuple(sorted(each_key for each_key in all_fields if each_key in frontmatter_unsupported_fields))
361
- if any(not isinstance(all_fields.get(each_key), str) or not all_fields[each_key] for each_key in frontmatter_required_fields):
362
- raise MaterializerError(f"name and description are required: {source_path}")
363
- tools = all_fields.get("tools", ())
364
- if isinstance(tools, str):
365
- tools = (tools,)
366
- if not isinstance(tools, tuple):
367
- raise MaterializerError(f"tools must be a list: {source_path}")
368
- return ClaudeAgent(source_path, source_identity, all_fields["name"], all_fields["description"], tools, all_fields.get("model"), all_fields.get("color"), unsupported)
365
+ unsupported = tuple(
366
+ sorted(
367
+ each_key
368
+ for each_key in parsed_fields
369
+ if each_key in frontmatter_unsupported_fields
370
+ )
371
+ )
372
+ name = _require_nonempty_string(parsed_fields.get("name"), source_path)
373
+ description = _require_nonempty_string(
374
+ parsed_fields.get("description"), source_path
375
+ )
376
+ tools = _require_tools_list(parsed_fields.get("tools"), source_path)
377
+ model = _require_optional_string(parsed_fields.get("model"), "model", source_path)
378
+ color = _require_optional_string(parsed_fields.get("color"), "color", source_path)
379
+ return ClaudeAgent(
380
+ source_path,
381
+ source_identity,
382
+ name,
383
+ description,
384
+ tools,
385
+ model,
386
+ color,
387
+ unsupported,
388
+ )
369
389
 
370
390
 
371
391
  def convert_agent(agent: ClaudeAgent) -> str:
@@ -12,6 +12,8 @@ Named constants for scripts in `scripts/`. Follows the project convention that t
12
12
  | `claude_chain_usage_constants.py` | `claude_chain_usage.py` - full weekly percent scale, usage-pause skill path segments, CLI config-path flag, JSON report keys, and probe error message templates |
13
13
  | `grok_worker_constants.py` | `grok_worker_preflight.py`, `grok_headless_runner.py`, `spawn_grok_batch.py`, and `resolve_worker_spawn.py` - the `grok` binary name and CLI flags, model and subcommand tokens, leader-socket and scratch-file name parts, auth and usage-limit signature lists, outcome classifications, fallthrough reasons, tool-profile names and prompt headers, worker timeouts (default, plus the floor and the `MAXIMUM_WORKER_TIMEOUT_SECONDS` ceiling that the batch spec parse, the headless runner, and the spawn dispatcher all enforce, each bound carrying one rejection template shared by those sites, plus launch-failure return code and post-kill grace), the process-tree kill attempt limit and kill-failed classification, the preflight ping's single-turn cap, ping-cache keys and TTL, batch-spec and summary JSON keys, worker-advisor placeholder launcher/model/effort tokens, four verdict signals, MAXIMUM_WORKER_ADVISOR_CORRECTIONS, advisor_blocked classification, and advisor_session_id report keys, the prompt-part and report-stream join separators, and the CLI launch-error stderr prefix |
14
14
  | `code_review_constants.py` | `invoke_code_review.py` - the `/code-review ultra --fix` prompt, opus model alias, permission-mode flag and value, result mode and JSON keys, session-model CLI flag, git dirty-check tokens, and in-session return markers; finding severity tokens (`blocker` / `high` / `medium` / `low` / `nit`), verification verdict tokens, loop terminal tokens (`clean` / `nits_fixed` / `advisor_blocked`), and pure helpers for retained-finding checks, head recording, terminal resolution, and terminal encoding |
15
+ | `active_capability_constants.py` | `active_capability_references.py` - package skills/agents/commands directory names, skill manifest filename, ban-listed capability names, inert fence languages, slash and backtick extract patterns, fence open/close patterns, banned-reason prefix, UTF-8 encoding, and newline join separator |
16
+ | `verify_installable_package_constants.py` | `verify_installable_package.py` - manifest filename and keys, npm pack / tarball / plugin-root tokens, git and node smoke argv pieces, surface classification labels, exit codes, and CLI status headers |
15
17
  | `__init__.py` | Empty package marker |
16
18
 
17
19
  ## Convention
@@ -0,0 +1,46 @@
1
+ """Constants for the active_capability_references script.
2
+
3
+ Script-level scalar constants live in dev_env_scripts_constants alongside
4
+ timing.py and the other per-script constant modules.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ PACKAGE_SKILLS_DIRECTORY: str = "skills"
10
+ PACKAGE_AGENTS_DIRECTORY: str = "agents"
11
+ PACKAGE_COMMANDS_DIRECTORY: str = "commands"
12
+ SKILL_MANIFEST_FILENAME: str = "SKILL.md"
13
+
14
+ # Slash-command and skill names that must never appear as active instructions.
15
+ ALL_BANNED_ACTIVE_CAPABILITY_NAMES: frozenset[str] = frozenset(
16
+ {
17
+ "stub-detector",
18
+ "superpowers",
19
+ "qbug",
20
+ "findbugs",
21
+ "fixbugs",
22
+ "monitor-open-prs",
23
+ }
24
+ )
25
+
26
+ # Fence languages treated as inert historical/example content.
27
+ ALL_INERT_FENCE_LANGUAGES: frozenset[str] = frozenset(
28
+ {
29
+ "example",
30
+ "history",
31
+ "historical",
32
+ "quote",
33
+ "diff",
34
+ }
35
+ )
36
+
37
+ SLASH_CAPABILITY_PATTERN: str = r"(?<![`\w])/([a-z][a-z0-9-]{2,})"
38
+ BACKTICK_CAPABILITY_PATTERN: str = (
39
+ r"`(?:skills/)?([a-z][a-z0-9-]{2,})(?:/SKILL\.md)?`"
40
+ )
41
+ FENCE_OPEN_PATTERN: str = r"^```([A-Za-z0-9_-]*)\s*$"
42
+ FENCE_CLOSE_PATTERN: str = r"^```\s*$"
43
+ BANNED_REASON_PREFIX: str = "banned_active_capability:"
44
+
45
+ UTF8_ENCODING: str = "utf-8"
46
+ NEWLINE_JOIN_SEPARATOR: str = "\n"
@@ -227,3 +227,77 @@ ATTEMPT_SUMMARY_ENTRY_TEMPLATE: str = "{command}={status}"
227
227
 
228
228
  ATTEMPT_SUMMARY_JOIN_SEPARATOR: str = ", "
229
229
  """Separator joining per-attempt fragments in the exhausted-chain summary."""
230
+
231
+ AFFINITY_STATE_SCHEMA_VERSION: int = 1
232
+ """Version field written into the session-to-binary affinity state document."""
233
+
234
+ AFFINITY_STATE_FILENAME: str = "claude-chain-affinity.json"
235
+ """Default affinity state file name under the Claude home directory."""
236
+
237
+ AFFINITY_MAXIMUM_ENTRIES: int = 64
238
+ """Hard cap on retained session-to-binary affinity rows (oldest drop first)."""
239
+
240
+ AFFINITY_KEY_SCHEMA_VERSION: str = "schema_version"
241
+ """JSON key for the affinity document schema version."""
242
+
243
+ AFFINITY_KEY_ALL_BINDINGS: str = "all_bindings"
244
+ """JSON key for the ordered list of session-to-command bindings."""
245
+
246
+ AFFINITY_KEY_SESSION_ID: str = "session_id"
247
+ """JSON key for a bound Claude session id."""
248
+
249
+ AFFINITY_KEY_COMMAND: str = "command"
250
+ """JSON key for the chain binary command bound to a session id."""
251
+
252
+ AFFINITY_TEMP_SUFFIX: str = ".tmp"
253
+ """Suffix for the temporary file used during atomic affinity replacement."""
254
+
255
+ AFFINITY_BINDING_NOT_OBJECT_REASON: str = "a binding entry is not an object"
256
+ """Reason when an affinity binding entry is not a JSON object."""
257
+
258
+ AFFINITY_BINDING_SESSION_ID_MISSING_REASON: str = (
259
+ "binding missing non-empty session_id"
260
+ )
261
+ """Reason when an affinity binding lacks a usable session_id."""
262
+
263
+ AFFINITY_BINDING_COMMAND_MISSING_REASON: str = "binding missing non-empty command"
264
+ """Reason when an affinity binding lacks a usable command."""
265
+
266
+ AFFINITY_UNSUPPORTED_SCHEMA_VERSION_REASON_TEMPLATE: str = (
267
+ "unsupported schema_version {schema_version!r}"
268
+ )
269
+ """Reason when the affinity document schema version is not supported."""
270
+
271
+ AFFINITY_BINDINGS_MISSING_OR_NOT_LIST_REASON: str = (
272
+ "all_bindings is missing or not a list"
273
+ )
274
+ """Reason when all_bindings is absent or the wrong type."""
275
+
276
+ AFFINITY_TOP_LEVEL_NOT_OBJECT_REASON: str = "top-level value is not a JSON object"
277
+ """Reason when the affinity document root is not an object."""
278
+
279
+ AFFINITY_SESSION_ID_AND_COMMAND_REQUIRED_MESSAGE: str = (
280
+ "session_id and command must be non-empty"
281
+ )
282
+ """ValueError message when record_affinity_binding receives empty ids."""
283
+
284
+ AFFINITY_MAXIMUM_ENTRIES_MINIMUM_MESSAGE: str = "maximum_entries must be at least 1"
285
+ """ValueError message when maximum_entries is below one."""
286
+
287
+ AFFINITY_CORRUPT_MESSAGE_TEMPLATE: str = (
288
+ "Affinity state at {state_path} is corrupt or unreadable: {error}. "
289
+ "Delete or repair the file before retrying."
290
+ )
291
+ """Actionable diagnostic when affinity state cannot be loaded."""
292
+
293
+ AFFINITY_WRITE_FAILED_MESSAGE_TEMPLATE: str = (
294
+ "Failed to write affinity state at {state_path}: {error}. "
295
+ "Check directory permissions and free space."
296
+ )
297
+ """Actionable diagnostic when atomic affinity replacement fails."""
298
+
299
+ AFFINITY_JSON_INDENT_SPACES: int = 2
300
+ """Indent width for the written affinity state JSON document."""
301
+
302
+ RESUME_SESSION_FLAG: str = "--resume"
303
+ """Claude CLI flag that continues a prior session by id."""
@@ -0,0 +1,116 @@
1
+ """Constants for verify_installable_package — npm pack surface checks.
2
+
3
+ Holds manifest keys, pack/tarball path tokens, hook-command path markers,
4
+ git and smoke-tool argv pieces, and CLI status messages.
5
+ """
6
+
7
+ MANIFEST_FILENAME: str = "installable-surfaces.manifest.json"
8
+ """Basename of the committed installable-surfaces manifest under the package root."""
9
+
10
+ PACKAGE_JSON_FILENAME: str = "package.json"
11
+ """Basename of the npm package manifest that lists the files field."""
12
+
13
+ PACKAGE_JSON_FILES_KEY: str = "files"
14
+ """JSON key on package.json that lists packaged path entries."""
15
+
16
+ PACKAGE_JSON_EXCLUDE_PREFIX: str = "!"
17
+ """Prefix on package.json files entries that exclude a path pattern."""
18
+
19
+ MANIFEST_DIRECTORIES_KEY: str = "directories"
20
+ """JSON key listing required top-level package directories."""
21
+
22
+ MANIFEST_ROOT_FILES_KEY: str = "root_files"
23
+ """JSON key listing required package-root files."""
24
+
25
+ TARBALL_PACKAGE_PREFIX: str = "package/"
26
+ """Path prefix npm pack applies to every member inside the generated tarball."""
27
+
28
+ PLUGIN_ROOT_TOKEN: str = "${CLAUDE_PLUGIN_ROOT}/"
29
+ """Placeholder prefix hooks.json uses for package-relative hook script paths."""
30
+
31
+ PYTHON_FILE_SUFFIX: str = ".py"
32
+ """Suffix that marks a hook command token as a Python script path."""
33
+
34
+ HOOKS_DIRECTORY_NAME: str = "hooks"
35
+ """Top-level package directory that holds hook scripts and hooks.json."""
36
+
37
+ HOOKS_DIRECTORY_PREFIX: str = "hooks/"
38
+ """Package-relative prefix that marks a hook script path under hooks/."""
39
+
40
+ HOOKS_JSON_RELATIVE_PATH: str = "hooks/hooks.json"
41
+ """Package-relative path of the hook registration map."""
42
+
43
+ JSON_COMMAND_KEY: str = "command"
44
+ """JSON object key whose string value is a hook command line."""
45
+
46
+ INSTALL_ENTRYPOINT_RELATIVE_PATH: str = "bin/install.mjs"
47
+ """Package-relative path of the primary install CLI entrypoint."""
48
+
49
+ PACKAGE_PATH_FROM_REPOSITORY: str = "packages/claude-dev-env"
50
+ """Repository-relative path of the published package directory."""
51
+
52
+ NPM_BINARY_NAME: str = "npm"
53
+ """npm CLI binary name used to build the pack tarball on POSIX hosts."""
54
+
55
+ NPM_CMD_BINARY_NAME: str = "npm.cmd"
56
+ """Windows npm launcher resolved before the extensionless npm name."""
57
+
58
+ NEWLINE_JOIN_SEPARATOR: str = "\n"
59
+ """Separator used when joining multi-line verification failure reports."""
60
+
61
+ NPM_PACK_SUBCOMMAND: str = "pack"
62
+ """npm subcommand that writes the installable tarball."""
63
+
64
+ NPM_PACK_JSON_FLAG: str = "--json"
65
+ """Flag that makes npm pack emit machine-readable filename metadata."""
66
+
67
+ NPM_PACK_DESTINATION_FLAG: str = "--pack-destination"
68
+ """Flag that directs npm pack to write the tarball into a chosen directory."""
69
+
70
+ NPM_PACK_FILENAME_KEY: str = "filename"
71
+ """JSON field on an npm pack --json record that holds the tarball basename."""
72
+
73
+ GIT_BINARY_NAME: str = "git"
74
+ """git CLI binary name used to confirm hook scripts are committed."""
75
+
76
+ GIT_LS_FILES_SUBCOMMAND: str = "ls-files"
77
+ """git subcommand that lists tracked paths in the index."""
78
+
79
+ NODE_BINARY_NAME: str = "node"
80
+ """Node.js binary name used for install-entrypoint syntax smoke checks."""
81
+
82
+ NODE_CHECK_FLAG: str = "--check"
83
+ """Flag that makes node parse a script without executing it."""
84
+
85
+ UTF8_ENCODING: str = "utf-8"
86
+ """Text encoding for manifest, hooks.json, and subprocess text mode."""
87
+
88
+ CLASS_PACKAGED: str = "packaged"
89
+ """Surface classification when a directory is on disk and listed for packaging."""
90
+
91
+ CLASS_SOURCE_ONLY: str = "source_only"
92
+ """Surface classification when a directory is on disk but not listed for packaging."""
93
+
94
+ CLASS_CONTRADICTORY: str = "contradictory"
95
+ """Surface classification when package.json lists a directory that is missing on disk."""
96
+
97
+ EXIT_CODE_SUCCESS: int = 0
98
+ """Process exit code when every installable-surface check passes."""
99
+
100
+ EXIT_CODE_FAILURE: int = 1
101
+ """Process exit code when one or more installable-surface checks fail."""
102
+
103
+ MISSING_MANIFEST_SURFACES_HEADER: str = "Missing surfaces in npm pack tarball:"
104
+ """Header printed when required manifest paths are absent from the packed tarball."""
105
+
106
+ UNTRACKED_HOOK_SCRIPTS_HEADER: str = "Hook scripts missing from git index:"
107
+ """Header printed when a hooks.json command resolves to an untracked or missing file."""
108
+
109
+ SMOKE_COMPILE_FAILURES_HEADER: str = "Hook scripts failed py_compile:"
110
+ """Header printed when a resolved hook script fails Python syntax compilation."""
111
+
112
+ INSTALL_ENTRYPOINT_SMOKE_FAILURE_HEADER: str = "Install entrypoint failed node --check:"
113
+ """Header printed when bin/install.mjs fails the Node syntax smoke check."""
114
+
115
+ VERIFICATION_PASSED_MESSAGE: str = "installable package verification passed"
116
+ """Status line printed when every check succeeds."""
@@ -0,0 +1,25 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "supportedActivationInterface": "claude-config-dir-mcp-json",
4
+ "mcpConfigFileName": "mcp.json",
5
+ "bundles": {
6
+ "lean": {
7
+ "id": "lean",
8
+ "allServerNames": ["filesystem-readonly"]
9
+ },
10
+ "full": {
11
+ "id": "full",
12
+ "allServerNames": ["filesystem-readonly", "github-readonly"]
13
+ }
14
+ },
15
+ "serverByName": {
16
+ "filesystem-readonly": {
17
+ "command": "npx",
18
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "${PROFILE_ROOT}"]
19
+ },
20
+ "github-readonly": {
21
+ "command": "npx",
22
+ "args": ["-y", "@modelcontextprotocol/server-github"]
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,60 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const CONFIG_DIRECTORY_PATH = dirname(fileURLToPath(import.meta.url));
6
+
7
+ /**
8
+ * @param {string} fileName
9
+ * @returns {unknown}
10
+ */
11
+ function readJsonConfigFile(fileName) {
12
+ const absolutePath = join(CONFIG_DIRECTORY_PATH, fileName);
13
+ return JSON.parse(readFileSync(absolutePath, 'utf8'));
14
+ }
15
+
16
+ export const PROFILES_MANIFEST_FILE_NAME = 'profiles.manifest.json';
17
+ export const SHARED_ALLOWLIST_FILE_NAME = 'shared-allowlist.json';
18
+
19
+ /**
20
+ * Sole authoritative profile-root environment variable for CLI isolation.
21
+ * CLAUDE_HOME is never honored as a profile root by this contract.
22
+ */
23
+ export const CLAUDE_CONFIG_DIR_ENVIRONMENT_VARIABLE = 'CLAUDE_CONFIG_DIR';
24
+ export const CLAUDE_HOME_ENVIRONMENT_VARIABLE = 'CLAUDE_HOME';
25
+ export const CLAUDE_CODE_TMPDIR_ENVIRONMENT_VARIABLE = 'CLAUDE_CODE_TMPDIR';
26
+ export const CLAUDE_CODE_PLUGIN_SEED_DIR_ENVIRONMENT_VARIABLE = 'CLAUDE_CODE_PLUGIN_SEED_DIR';
27
+
28
+ export const PROFILES_ROOT_ENVIRONMENT_VARIABLE = 'LLM_SETTINGS_PROFILES_ROOT';
29
+ export const SHARED_SOURCE_ROOT_ENVIRONMENT_VARIABLE = 'LLM_SETTINGS_SHARED_SOURCE_ROOT';
30
+ export const PLUGIN_SEED_ROOT_ENVIRONMENT_VARIABLE = 'LLM_SETTINGS_PLUGIN_SEED_ROOT';
31
+
32
+ export const DEFAULT_PROFILES_ROOT_DIRECTORY_NAME = '.claude-profiles';
33
+ export const DEFAULT_SHARED_SOURCE_DIRECTORY_NAME = 'shared-source';
34
+ export const DEFAULT_PLUGIN_SEED_DIRECTORY_NAME = 'plugin-seed';
35
+
36
+ export const MIGRATION_MODE_CLEAN_LOCAL_RUNTIME = 'clean-local-runtime';
37
+ export const MIGRATION_MODE_MATERIALIZE_FROM_LEGACY = 'materialize-from-legacy';
38
+
39
+ export const MCP_BUNDLE_LEAN = 'lean';
40
+ export const MCP_BUNDLE_FULL = 'full';
41
+
42
+ export const LAUNCHER_SCHEMA_VERSION = 1;
43
+ export const PROFILE_ISOLATION_CONTRACT_OWNER = 'profile-isolation-contract';
44
+ export const PACKAGE_FILES_WHITELIST_SCRIPTS_ENTRY = 'scripts/';
45
+ export const LIVE_DEPLOYMENT_RESERVED_FOR = 'L1';
46
+ export const INSTALL_DESTINATION_ROOT_RELATIVE_PATH = 'scripts/profile-isolation-launchers';
47
+
48
+ /**
49
+ * @returns {Record<string, unknown>}
50
+ */
51
+ export function loadProfilesManifestDocument() {
52
+ return /** @type {Record<string, unknown>} */ (readJsonConfigFile(PROFILES_MANIFEST_FILE_NAME));
53
+ }
54
+
55
+ /**
56
+ * @returns {Record<string, unknown>}
57
+ */
58
+ export function loadSharedAllowlistDocument() {
59
+ return /** @type {Record<string, unknown>} */ (readJsonConfigFile(SHARED_ALLOWLIST_FILE_NAME));
60
+ }
@@ -0,0 +1,54 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "profilesRootPlaceholder": "${PROFILES_ROOT}",
4
+ "sharedSourcePlaceholder": "${SHARED_SOURCE_ROOT}",
5
+ "pluginSeedPlaceholder": "${PLUGIN_SEED_ROOT}",
6
+ "migrationOrder": ["profile-c", "profile-b", "profile-a", "master", "profile-d"],
7
+ "profiles": {
8
+ "master": {
9
+ "id": "master",
10
+ "aliases": ["default"],
11
+ "directoryName": "master",
12
+ "launcherNames": ["claude"],
13
+ "fullLauncherNames": ["claude-full"],
14
+ "migrationMode": "materialize-from-legacy",
15
+ "mcpBundle": "lean"
16
+ },
17
+ "profile-a": {
18
+ "id": "profile-a",
19
+ "aliases": [],
20
+ "directoryName": "profile-a",
21
+ "launcherNames": ["claude-profile-a"],
22
+ "fullLauncherNames": ["claude-profile-a-full"],
23
+ "migrationMode": "materialize-from-legacy",
24
+ "mcpBundle": "lean"
25
+ },
26
+ "profile-c": {
27
+ "id": "profile-c",
28
+ "aliases": [],
29
+ "directoryName": "profile-c",
30
+ "launcherNames": ["claude-profile-c"],
31
+ "fullLauncherNames": ["claude-profile-c-full"],
32
+ "migrationMode": "clean-local-runtime",
33
+ "mcpBundle": "lean"
34
+ },
35
+ "profile-b": {
36
+ "id": "profile-b",
37
+ "aliases": [],
38
+ "directoryName": "profile-b",
39
+ "launcherNames": ["claude-profile-b"],
40
+ "fullLauncherNames": ["claude-profile-b-full"],
41
+ "migrationMode": "clean-local-runtime",
42
+ "mcpBundle": "lean"
43
+ },
44
+ "profile-d": {
45
+ "id": "profile-d",
46
+ "aliases": [],
47
+ "directoryName": "profile-d",
48
+ "launcherNames": ["claude-profile-d"],
49
+ "fullLauncherNames": ["claude-profile-d-full"],
50
+ "migrationMode": "clean-local-runtime",
51
+ "mcpBundle": "lean"
52
+ }
53
+ }
54
+ }