mova-claude-import 0.1.1 → 0.1.2

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 (188) hide show
  1. package/README.md +68 -4
  2. package/control_surface_exclusions_v0.json +8 -0
  3. package/dist/anthropic_profile_v0.d.ts +1 -1
  4. package/dist/anthropic_profile_v0.js +1 -0
  5. package/dist/cli.js +206 -23
  6. package/dist/control_apply_v0.d.ts +5 -1
  7. package/dist/control_apply_v0.js +125 -8
  8. package/dist/control_check_v0.d.ts +1 -0
  9. package/dist/control_check_v0.js +194 -23
  10. package/dist/control_prefill_v0.js +128 -9
  11. package/dist/control_surface_coverage_v0.d.ts +22 -0
  12. package/dist/control_surface_coverage_v0.js +128 -0
  13. package/dist/control_v0.d.ts +149 -0
  14. package/dist/control_v0.js +360 -0
  15. package/dist/control_v0_schema.d.ts +6 -0
  16. package/dist/control_v0_schema.js +19 -0
  17. package/dist/init_v0.d.ts +6 -1
  18. package/dist/init_v0.js +41 -1
  19. package/dist/observability_writer_v0.d.ts +1 -0
  20. package/dist/observability_writer_v0.js +157 -0
  21. package/dist/observe_v0.d.ts +8 -0
  22. package/dist/observe_v0.js +57 -0
  23. package/dist/presets_v0.d.ts +11 -0
  24. package/dist/presets_v0.js +49 -0
  25. package/dist/redaction.js +5 -1
  26. package/dist/run_import.js +111 -26
  27. package/docs/CLAUDE_CONTROL_SURFACE_MAP_v0.md +78 -0
  28. package/docs/OPERATOR_GUIDE_v0.md +11 -0
  29. package/fixtures/pos/basic/mova/control_v0.json +93 -0
  30. package/fixtures/pos/claude_code_demo_full/.claude/agents/code-reviewer.md +13 -0
  31. package/fixtures/pos/claude_code_demo_full/.claude/agents/github-workflow.md +10 -0
  32. package/fixtures/pos/claude_code_demo_full/.claude/commands/code-quality.md +8 -0
  33. package/fixtures/pos/claude_code_demo_full/.claude/commands/docs-sync.md +8 -0
  34. package/fixtures/pos/claude_code_demo_full/.claude/commands/onboard.md +8 -0
  35. package/fixtures/pos/claude_code_demo_full/.claude/commands/pr-review.md +10 -0
  36. package/fixtures/pos/claude_code_demo_full/.claude/commands/pr-summary.md +8 -0
  37. package/fixtures/pos/claude_code_demo_full/.claude/commands/ticket.md +10 -0
  38. package/fixtures/pos/claude_code_demo_full/.claude/hooks/skill-eval.js +13 -0
  39. package/fixtures/pos/claude_code_demo_full/.claude/hooks/skill-eval.sh +15 -0
  40. package/fixtures/pos/claude_code_demo_full/.claude/hooks/skill-rules.json +21 -0
  41. package/fixtures/pos/claude_code_demo_full/.claude/hooks/skill-rules.schema.json +24 -0
  42. package/fixtures/pos/claude_code_demo_full/.claude/rules/code-style.md +5 -0
  43. package/fixtures/pos/claude_code_demo_full/.claude/rules/security.md +5 -0
  44. package/fixtures/pos/claude_code_demo_full/.claude/settings.json +102 -0
  45. package/fixtures/pos/claude_code_demo_full/.claude/settings.md +6 -0
  46. package/fixtures/pos/claude_code_demo_full/.claude/skills/core-components/SKILL.md +9 -0
  47. package/fixtures/pos/claude_code_demo_full/.claude/skills/formik-patterns/SKILL.md +9 -0
  48. package/fixtures/pos/claude_code_demo_full/.claude/skills/graphql-schema/SKILL.md +10 -0
  49. package/fixtures/pos/claude_code_demo_full/.claude/skills/react-ui-patterns/SKILL.md +10 -0
  50. package/fixtures/pos/claude_code_demo_full/.claude/skills/systematic-debugging/SKILL.md +9 -0
  51. package/fixtures/pos/claude_code_demo_full/.claude/skills/testing-patterns/SKILL.md +10 -0
  52. package/fixtures/pos/claude_code_demo_full/.github/workflows/pr-claude-code-review.yml +14 -0
  53. package/fixtures/pos/claude_code_demo_full/.github/workflows/scheduled-claude-code-dependency-audit.yml +14 -0
  54. package/fixtures/pos/claude_code_demo_full/.github/workflows/scheduled-claude-code-docs-sync.yml +14 -0
  55. package/fixtures/pos/claude_code_demo_full/.github/workflows/scheduled-claude-code-quality.yml +14 -0
  56. package/fixtures/pos/claude_code_demo_full/.mcp.json +44 -0
  57. package/fixtures/pos/claude_code_demo_full/CLAUDE.md +28 -0
  58. package/fixtures/pos/control_basic_project/mova/control_v0.json +93 -0
  59. package/fixtures/pos/observability_basic/CLAUDE.md +3 -0
  60. package/{.tmp_test_zip/out1 → fixtures/pos/preset_safe_observable_v0}/.mcp.json +3 -3
  61. package/fixtures/pos/preset_safe_observable_v0/CLAUDE.md +3 -0
  62. package/package.json +2 -1
  63. package/presets/safe_observable_v0/assets/.claude/agents/code-reviewer.md +9 -0
  64. package/presets/safe_observable_v0/assets/.claude/commands/finish.md +9 -0
  65. package/presets/safe_observable_v0/assets/.claude/commands/start.md +9 -0
  66. package/presets/safe_observable_v0/assets/.claude/hooks/skill-eval.js +15 -0
  67. package/presets/safe_observable_v0/assets/.claude/hooks/skill-eval.sh +17 -0
  68. package/presets/safe_observable_v0/assets/.claude/hooks/skill-rules.json +26 -0
  69. package/presets/safe_observable_v0/assets/.claude/rules/code-style.md +6 -0
  70. package/presets/safe_observable_v0/assets/.claude/rules/security.md +6 -0
  71. package/presets/safe_observable_v0/assets/.claude/skills/git-workflow/SKILL.md +9 -0
  72. package/presets/safe_observable_v0/assets/.claude/skills/security-basics/SKILL.md +9 -0
  73. package/presets/safe_observable_v0/assets/.claude/skills/systematic-debugging/SKILL.md +9 -0
  74. package/presets/safe_observable_v0/assets/.claude/skills/testing-patterns/SKILL.md +9 -0
  75. package/presets/safe_observable_v0/control_v0.json +214 -0
  76. package/schemas/mova.control_v0.schema.json +252 -0
  77. package/src/anthropic_profile_v0.ts +1 -0
  78. package/src/cli.ts +194 -23
  79. package/src/control_apply_v0.ts +131 -8
  80. package/src/control_check_v0.ts +203 -23
  81. package/src/control_prefill_v0.ts +136 -8
  82. package/src/control_surface_coverage_v0.ts +164 -0
  83. package/src/control_v0.ts +808 -0
  84. package/src/control_v0_schema.ts +26 -0
  85. package/src/init_v0.ts +48 -1
  86. package/src/observability_writer_v0.ts +157 -0
  87. package/src/observe_v0.ts +64 -0
  88. package/src/presets_v0.ts +55 -0
  89. package/src/redaction.ts +6 -1
  90. package/src/run_import.ts +132 -26
  91. package/test/control_demo_full_roundtrip.test.js +92 -0
  92. package/test/control_surface_coverage_v0.test.js +36 -0
  93. package/test/control_v0_schema_validation.test.js +69 -0
  94. package/test/init_v0.test.js +9 -0
  95. package/test/observability_writer_v0.test.js +59 -0
  96. package/test/preset_safe_observable_v0.test.js +55 -0
  97. package/test/profile_v0_output.test.js +1 -0
  98. package/test/scaffold_v0_output.test.js +1 -0
  99. package/tools/control_surface_coverage_v0.mjs +27 -0
  100. package/tools/smoke_v0.mjs +33 -0
  101. package/.tmp_test_control_apply/proj/.claude/agents/example_agent.md +0 -3
  102. package/.tmp_test_control_apply/proj/.claude/commands/example_command.md +0 -3
  103. package/.tmp_test_control_apply/proj/.claude/hooks/example_hook.sh +0 -2
  104. package/.tmp_test_control_apply/proj/.claude/output-styles/example_style.md +0 -3
  105. package/.tmp_test_control_apply/proj/.claude/settings.json +0 -30
  106. package/.tmp_test_control_apply/proj/.claude/settings.local.example.json +0 -3
  107. package/.tmp_test_control_apply/proj/.mcp.json +0 -3
  108. package/.tmp_test_control_apply/proj/CLAUDE.md +0 -13
  109. package/.tmp_test_control_apply/proj/MOVA.md +0 -3
  110. package/.tmp_test_control_check/proj/.mcp.json +0 -1
  111. package/.tmp_test_control_check/proj/CLAUDE.md +0 -1
  112. package/.tmp_test_control_prefill/out1/claude_control_profile_v0.json +0 -114
  113. package/.tmp_test_control_prefill/out1/prefill_report_v0.json +0 -13
  114. package/.tmp_test_control_prefill/out2/claude_control_profile_v0.json +0 -114
  115. package/.tmp_test_control_prefill/out2/prefill_report_v0.json +0 -13
  116. package/.tmp_test_overlay/proj/.claude/skills/a.md +0 -1
  117. package/.tmp_test_overlay/proj/.mcp.json +0 -1
  118. package/.tmp_test_overlay/proj/CLAUDE.md +0 -1
  119. package/.tmp_test_profile/proj/.claude/skills/a.md +0 -1
  120. package/.tmp_test_profile/proj/.mcp.json +0 -1
  121. package/.tmp_test_profile/proj/CLAUDE.md +0 -1
  122. package/.tmp_test_scaffold_apply/proj/.claude/agents/example_agent.md +0 -3
  123. package/.tmp_test_scaffold_apply/proj/.claude/commands/example_command.md +0 -3
  124. package/.tmp_test_scaffold_apply/proj/.claude/hooks/example_hook.sh +0 -2
  125. package/.tmp_test_scaffold_apply/proj/.claude/output-styles/example_style.md +0 -3
  126. package/.tmp_test_scaffold_apply/proj/.claude/settings.json +0 -30
  127. package/.tmp_test_scaffold_apply/proj/.claude/settings.local.example.json +0 -3
  128. package/.tmp_test_scaffold_apply/proj/.mcp.json +0 -3
  129. package/.tmp_test_scaffold_apply/proj/CLAUDE.md +0 -13
  130. package/.tmp_test_scaffold_apply/proj/MOVA.md +0 -3
  131. package/.tmp_test_strict/mova/claude_import/v0/VERSION.json +0 -10
  132. package/.tmp_test_strict/mova/claude_import/v0/episode_import_run.json +0 -20
  133. package/.tmp_test_strict/mova/claude_import/v0/import_manifest.json +0 -20
  134. package/.tmp_test_strict/mova/claude_import/v0/input_policy_report_v0.json +0 -32
  135. package/.tmp_test_zip/out1/.claude/agents/example_agent.md +0 -3
  136. package/.tmp_test_zip/out1/.claude/commands/example_command.md +0 -3
  137. package/.tmp_test_zip/out1/.claude/commands/mova_context.md +0 -4
  138. package/.tmp_test_zip/out1/.claude/commands/mova_lint.md +0 -4
  139. package/.tmp_test_zip/out1/.claude/commands/mova_proof.md +0 -6
  140. package/.tmp_test_zip/out1/.claude/hooks/example_hook.sh +0 -2
  141. package/.tmp_test_zip/out1/.claude/output-styles/example_style.md +0 -3
  142. package/.tmp_test_zip/out1/.claude/settings.json +0 -30
  143. package/.tmp_test_zip/out1/.claude/settings.local.example.json +0 -3
  144. package/.tmp_test_zip/out1/.claude/skills/a/SKILL.md +0 -1
  145. package/.tmp_test_zip/out1/.claude/skills/mova-control-v0/SKILL.md +0 -11
  146. package/.tmp_test_zip/out1/.claude/skills/mova-layer-v0/SKILL.md +0 -8
  147. package/.tmp_test_zip/out1/CLAUDE.md +0 -4
  148. package/.tmp_test_zip/out1/MOVA.md +0 -10
  149. package/.tmp_test_zip/out1/export.zip +0 -0
  150. package/.tmp_test_zip/out1/mova/claude_import/v0/VERSION.json +0 -10
  151. package/.tmp_test_zip/out1/mova/claude_import/v0/contracts/instruction_profile_v0.json +0 -8
  152. package/.tmp_test_zip/out1/mova/claude_import/v0/contracts/mcp_servers_v0.json +0 -4
  153. package/.tmp_test_zip/out1/mova/claude_import/v0/contracts/skills_catalog_v0.json +0 -11
  154. package/.tmp_test_zip/out1/mova/claude_import/v0/episode_import_run.json +0 -80
  155. package/.tmp_test_zip/out1/mova/claude_import/v0/export_manifest_v0.json +0 -32
  156. package/.tmp_test_zip/out1/mova/claude_import/v0/import_manifest.json +0 -33
  157. package/.tmp_test_zip/out1/mova/claude_import/v0/input_policy_report_v0.json +0 -38
  158. package/.tmp_test_zip/out1/mova/claude_import/v0/lint_report_v0.json +0 -6
  159. package/.tmp_test_zip/out1/mova/claude_import/v0/redaction_report.json +0 -4
  160. package/.tmp_test_zip/out2/.claude/agents/example_agent.md +0 -3
  161. package/.tmp_test_zip/out2/.claude/commands/example_command.md +0 -3
  162. package/.tmp_test_zip/out2/.claude/commands/mova_context.md +0 -4
  163. package/.tmp_test_zip/out2/.claude/commands/mova_lint.md +0 -4
  164. package/.tmp_test_zip/out2/.claude/commands/mova_proof.md +0 -6
  165. package/.tmp_test_zip/out2/.claude/hooks/example_hook.sh +0 -2
  166. package/.tmp_test_zip/out2/.claude/output-styles/example_style.md +0 -3
  167. package/.tmp_test_zip/out2/.claude/settings.json +0 -30
  168. package/.tmp_test_zip/out2/.claude/settings.local.example.json +0 -3
  169. package/.tmp_test_zip/out2/.claude/skills/a/SKILL.md +0 -1
  170. package/.tmp_test_zip/out2/.claude/skills/mova-control-v0/SKILL.md +0 -11
  171. package/.tmp_test_zip/out2/.claude/skills/mova-layer-v0/SKILL.md +0 -8
  172. package/.tmp_test_zip/out2/.mcp.json +0 -3
  173. package/.tmp_test_zip/out2/CLAUDE.md +0 -4
  174. package/.tmp_test_zip/out2/MOVA.md +0 -10
  175. package/.tmp_test_zip/out2/export.zip +0 -0
  176. package/.tmp_test_zip/out2/mova/claude_import/v0/VERSION.json +0 -10
  177. package/.tmp_test_zip/out2/mova/claude_import/v0/contracts/instruction_profile_v0.json +0 -8
  178. package/.tmp_test_zip/out2/mova/claude_import/v0/contracts/mcp_servers_v0.json +0 -4
  179. package/.tmp_test_zip/out2/mova/claude_import/v0/contracts/skills_catalog_v0.json +0 -11
  180. package/.tmp_test_zip/out2/mova/claude_import/v0/episode_import_run.json +0 -80
  181. package/.tmp_test_zip/out2/mova/claude_import/v0/export_manifest_v0.json +0 -32
  182. package/.tmp_test_zip/out2/mova/claude_import/v0/import_manifest.json +0 -33
  183. package/.tmp_test_zip/out2/mova/claude_import/v0/input_policy_report_v0.json +0 -38
  184. package/.tmp_test_zip/out2/mova/claude_import/v0/lint_report_v0.json +0 -6
  185. package/.tmp_test_zip/out2/mova/claude_import/v0/redaction_report.json +0 -4
  186. package/.tmp_test_zip/proj/.claude/skills/a.md +0 -1
  187. package/.tmp_test_zip/proj/.mcp.json +0 -1
  188. package/.tmp_test_zip/proj/CLAUDE.md +0 -1
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  - создаёт полный “эталонный” скелет Claude Code‑проекта;
8
8
  - импортирует существующий проект в чистую структуру;
9
- - добавляет слой контроля и отчёты без “магии” и без LLM.
9
+ - добавляет слой контроля, наблюдаемости и отчёты без “магии” и без LLM.
10
10
 
11
11
  ## Что было → что стало
12
12
 
@@ -26,23 +26,40 @@
26
26
  hooks/
27
27
  .mcp.json
28
28
  mova/
29
+ control_v0.json
29
30
  claude_import/v0/...
30
31
  claude_control/v0/runs/...
31
32
  ```
32
33
 
33
34
  ## Быстрый старт
34
35
 
36
+ ### Recommended flow (control_v0)
37
+
38
+ ```
39
+ npx mova-claude-import init --out <dir>
40
+ npx mova-claude-import control prefill --project <dir> --out <dir>
41
+ npx mova-claude-import control apply --project <dir> --profile <dir>/mova/control_v0.json --mode apply
42
+ npx mova-claude-import control check --project <dir> --profile <dir>/mova/control_v0.json
43
+ ```
44
+
35
45
  ### У меня уже есть папка Claude Code‑проекта
36
46
 
37
47
  ```
38
48
  npx mova-claude-import --project <in> --out <out> --zip
39
49
  ```
40
50
 
41
- Дальше добавьте контрольный профиль и проверьте план:
51
+ ### Quick start (existing Claude folder + preset)
42
52
 
43
53
  ```
44
- npx mova-claude-import control prefill --project <in> --out <out>
45
- npx mova-claude-import control check --project <in> --profile <out>/claude_control_profile_v0.json
54
+ npx -y mova-claude-import@<version> preset list
55
+ npx -y mova-claude-import@<version> control apply --preset safe_observable_v0 --project . --mode overlay
56
+ ```
57
+
58
+ Дальше используйте единый контрольный файл и выполните rebuild/import:
59
+
60
+ ```
61
+ <out>/mova/control_v0.json
62
+ npx mova-claude-import --project <in> --out <out> --zip
46
63
  ```
47
64
 
48
65
  ### Я хочу создать эталонный профиль с нуля (init)
@@ -51,6 +68,18 @@ npx mova-claude-import control check --project <in> --profile <out>/claude_contr
51
68
  npx mova-claude-import init --out <dir> --zip
52
69
  ```
53
70
 
71
+ Заполните единый контрольный файл:
72
+
73
+ ```
74
+ <dir>/mova/control_v0.json
75
+ ```
76
+
77
+ Затем выполните rebuild/import:
78
+
79
+ ```
80
+ npx mova-claude-import --project <dir> --out <out> --zip
81
+ ```
82
+
54
83
  ### Контроль (preview по умолчанию)
55
84
 
56
85
  ```
@@ -69,17 +98,52 @@ npm run demo
69
98
  Руководство: `docs/CONTROL_PROFILE_GUIDE_v0.md`.
70
99
  Примеры: `examples/control_profile_min.json`, `examples/control_profile_standard.json`, `examples/control_profile_strict.json`.
71
100
 
101
+ Единый контрольный файл для rebuild/import: `mova/control_v0.json`.
102
+ Schema: `schemas/mova.control_v0.schema.json`.
103
+
72
104
  Канон схем control‑слоя: `schemas/claude_control/v0/{ds,env,global}`.
73
105
 
74
106
  ## Где смотреть отчёты/доказательства
75
107
 
76
108
  - `mova/claude_import/v0/*` — отчёты импорта и контроля качества
77
109
  - `mova/claude_control/v0/runs/*` — планы/отчёты control‑команд
110
+ - `.mova/episodes/index.jsonl` — индекс наблюдаемости
111
+ - `.mova/episodes/<run_id>/summary.json` — краткая сводка последнего прогона
112
+
113
+ ## Наблюдаемость (Observability Writer)
114
+
115
+ Writer включается из `mova/control_v0.json` и через hooks пишет эпизоды:
116
+
117
+ - события в `.mova/episodes/<run_id>/events.jsonl`
118
+ - сводка в `.mova/episodes/<run_id>/summary.json`
119
+ - индекс прогонов в `.mova/episodes/index.jsonl`
120
+
121
+ Отключить можно через `observability.enable=false` в `mova/control_v0.json`.
122
+
123
+ Минимальные команды:
124
+
125
+ ```
126
+ npx mova-claude-import observe list --project <dir>
127
+ npx mova-claude-import observe tail --project <dir> --run <id>
128
+ npx mova-claude-import observe summary --project <dir> --run <id>
129
+ ```
78
130
 
79
131
  ## Для автоматизации
80
132
 
81
133
  Подробности по `--strict`, кодам завершения и CI‑проверкам — в `docs/OPERATOR_GUIDE_v0.md`.
82
134
 
135
+ ## Preset: safe_observable_v0
136
+
137
+ Что делает:
138
+ - включает observability writer и hooks
139
+ - добавляет guardrails на опасные команды и изменения в `main`
140
+ - добавляет start/finish команды и базовые skills
141
+ - включает skill-eval hook и правила
142
+
143
+ Что не делает:
144
+ - не включает MCP (по умолчанию пусто)
145
+ - не трогает секреты или локальные креды
146
+
83
147
  ## Ссылки
84
148
 
85
149
  - `docs/COMPATIBILITY_MATRIX.md`
@@ -0,0 +1,8 @@
1
+ {
2
+ "exclusions": [
3
+ {
4
+ "pattern": ".claude/skills/README.md",
5
+ "reason": "quality_v0 disallows files directly under .claude/skills"
6
+ }
7
+ ]
8
+ }
@@ -1,2 +1,2 @@
1
- export declare const anthropicProfileV0RequiredFiles: readonly ["CLAUDE.md", "MOVA.md", ".claude/settings.json", ".claude/commands/mova_context.md", ".claude/commands/mova_lint.md", ".claude/skills/mova-layer-v0/SKILL.md"];
1
+ export declare const anthropicProfileV0RequiredFiles: readonly ["CLAUDE.md", "MOVA.md", ".claude/settings.json", ".claude/commands/mova_context.md", ".claude/commands/mova_lint.md", ".claude/skills/mova-layer-v0/SKILL.md", "mova/control_v0.json"];
2
2
  export declare function getAnthropicProfileV0Files(): Record<string, string>;
@@ -6,6 +6,7 @@ export const anthropicProfileV0RequiredFiles = [
6
6
  ".claude/commands/mova_context.md",
7
7
  ".claude/commands/mova_lint.md",
8
8
  ".claude/skills/mova-layer-v0/SKILL.md",
9
+ "mova/control_v0.json",
9
10
  ];
10
11
  export function getAnthropicProfileV0Files() {
11
12
  return {
package/dist/cli.js CHANGED
@@ -3,6 +3,8 @@ import { initProfileV0 } from "./init_v0.js";
3
3
  import { controlPrefillV0 } from "./control_prefill_v0.js";
4
4
  import { controlCheckV0 } from "./control_check_v0.js";
5
5
  import { controlApplyV0 } from "./control_apply_v0.js";
6
+ import { listObservabilityRuns, readObservabilitySummary, tailObservabilityEvents } from "./observe_v0.js";
7
+ import { listPresets, readPresetControlRaw, resolvePreset } from "./presets_v0.js";
6
8
  function getArg(name) {
7
9
  const idx = process.argv.indexOf(name);
8
10
  if (idx === -1)
@@ -18,10 +20,15 @@ function usage(exitCode = 0) {
18
20
  "",
19
21
  "Usage:",
20
22
  " mova-claude-import --project <dir> [--out <dir>] [--dry-run] [--strict] [--include-local] [--include-user-settings] [--no-emit-profile] [--no-emit-overlay] [--zip] [--zip-name <name>]",
21
- " mova-claude-import init --out <dir> [--zip]",
23
+ " mova-claude-import init --out <dir> [--zip] [--preset <name>]",
22
24
  " mova-claude-import control prefill --project <dir> --out <dir> [--include-local]",
23
25
  " mova-claude-import control check --project <dir> --profile <file>",
24
- " mova-claude-import control apply --project <dir> --profile <file> [--mode preview|apply]",
26
+ " mova-claude-import control apply --project <dir> --profile <file> [--mode preview|apply|overlay] [--preset <name>]",
27
+ " mova-claude-import preset list",
28
+ " mova-claude-import preset show <name>",
29
+ " mova-claude-import observe list --project <dir>",
30
+ " mova-claude-import observe tail --project <dir> --run <id> [--limit <n>]",
31
+ " mova-claude-import observe summary --project <dir> --run <id>",
25
32
  "",
26
33
  "Notes:",
27
34
  " - CLAUDE.local.md and *.local.* are excluded unless --include-local",
@@ -30,6 +37,7 @@ function usage(exitCode = 0) {
30
37
  " - overlay emission is enabled by default; use --no-emit-overlay to skip",
31
38
  " - zip export is disabled by default; use --zip to enable",
32
39
  " - init creates a clean Anthropic profile v0 scaffold",
40
+ " - init --preset uses preset control_v0.json + assets",
33
41
  " - control commands run in preview by default",
34
42
  ].join("\n"));
35
43
  process.exit(exitCode);
@@ -48,15 +56,42 @@ if (subcommand === "init") {
48
56
  process.exit(2);
49
57
  }
50
58
  const emitZip = hasFlag("--zip");
51
- initProfileV0(out, emitZip)
52
- .then((res) => {
53
- process.stdout.write(JSON.stringify(res, null, 2) + "\n");
54
- process.exit(0);
55
- })
56
- .catch((err) => {
57
- console.error(err);
58
- process.exit(1);
59
- });
59
+ const presetName = getArg("--preset");
60
+ if (presetName) {
61
+ resolvePreset(presetName)
62
+ .then(async (preset) => {
63
+ if (!preset) {
64
+ console.error(`Preset not found: ${presetName}`);
65
+ process.exit(2);
66
+ }
67
+ const raw = await readPresetControlRaw(presetName);
68
+ const control = raw ? JSON.parse(raw) : null;
69
+ if (!control) {
70
+ console.error(`Preset control missing: ${presetName}`);
71
+ process.exit(2);
72
+ }
73
+ return initProfileV0(out, emitZip, { controlOverride: control, assetsRoot: preset.assets_root });
74
+ })
75
+ .then((res) => {
76
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
77
+ process.exit(0);
78
+ })
79
+ .catch((err) => {
80
+ console.error(err);
81
+ process.exit(1);
82
+ });
83
+ }
84
+ else {
85
+ initProfileV0(out, emitZip)
86
+ .then((res) => {
87
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
88
+ process.exit(0);
89
+ })
90
+ .catch((err) => {
91
+ console.error(err);
92
+ process.exit(1);
93
+ });
94
+ }
60
95
  }
61
96
  else if (subcommand === "control") {
62
97
  const action = process.argv[3];
@@ -95,10 +130,13 @@ else if (subcommand === "control") {
95
130
  controlCheckV0(project, profile, out)
96
131
  .then((res) => {
97
132
  console.log([
98
- "control check: ok",
133
+ res.exit_code && res.exit_code !== 0 ? "control check: issues" : "control check: ok",
99
134
  `plan: ${res.plan_path}`,
100
135
  `summary: ${res.summary_path}`,
101
- ].join("\n"));
136
+ res.exit_code ? `exit_code: ${res.exit_code}` : null,
137
+ ].filter(Boolean).join("\n"));
138
+ if (typeof res.exit_code === "number")
139
+ process.exit(res.exit_code);
102
140
  process.exit(0);
103
141
  })
104
142
  .catch((err) => {
@@ -107,19 +145,164 @@ else if (subcommand === "control") {
107
145
  });
108
146
  }
109
147
  else if (action === "apply") {
110
- const profile = getArg("--profile");
111
- if (!profile) {
148
+ const out = getArg("--out") || project;
149
+ const mode = getArg("--mode");
150
+ const presetName = getArg("--preset");
151
+ if (presetName) {
152
+ resolvePreset(presetName)
153
+ .then(async (preset) => {
154
+ if (!preset) {
155
+ console.error(`Preset not found: ${presetName}`);
156
+ process.exit(2);
157
+ }
158
+ return controlApplyV0(project, preset.control_path, out, mode, {
159
+ assetSourceRoot: preset.assets_root,
160
+ });
161
+ })
162
+ .then((res) => {
163
+ console.log([
164
+ res.exit_code && res.exit_code !== 0 ? "control apply: issues" : "control apply: ok",
165
+ `report: ${res.report_path}`,
166
+ res.exit_code ? `exit_code: ${res.exit_code}` : null,
167
+ ].filter(Boolean).join("\n"));
168
+ if (typeof res.exit_code === "number")
169
+ process.exit(res.exit_code);
170
+ process.exit(0);
171
+ })
172
+ .catch((err) => {
173
+ console.error(err);
174
+ process.exit(1);
175
+ });
176
+ }
177
+ else {
178
+ const profile = getArg("--profile");
179
+ if (!profile) {
180
+ usage(2);
181
+ process.exit(2);
182
+ }
183
+ controlApplyV0(project, profile, out, mode)
184
+ .then((res) => {
185
+ console.log([
186
+ res.exit_code && res.exit_code !== 0 ? "control apply: issues" : "control apply: ok",
187
+ `report: ${res.report_path}`,
188
+ res.exit_code ? `exit_code: ${res.exit_code}` : null,
189
+ ].filter(Boolean).join("\n"));
190
+ if (typeof res.exit_code === "number")
191
+ process.exit(res.exit_code);
192
+ process.exit(0);
193
+ })
194
+ .catch((err) => {
195
+ console.error(err);
196
+ process.exit(1);
197
+ });
198
+ }
199
+ }
200
+ else {
201
+ usage(2);
202
+ process.exit(2);
203
+ }
204
+ }
205
+ else if (subcommand === "preset") {
206
+ const action = process.argv[3];
207
+ if (action === "list") {
208
+ listPresets()
209
+ .then((presets) => {
210
+ for (const name of presets) {
211
+ console.log(name);
212
+ }
213
+ process.exit(0);
214
+ })
215
+ .catch((err) => {
216
+ console.error(err);
217
+ process.exit(1);
218
+ });
219
+ }
220
+ else if (action === "show") {
221
+ const name = process.argv[4];
222
+ if (!name) {
112
223
  usage(2);
113
224
  process.exit(2);
114
225
  }
115
- const out = getArg("--out") || project;
116
- const mode = getArg("--mode");
117
- controlApplyV0(project, profile, out, mode)
118
- .then((res) => {
119
- console.log([
120
- "control apply: ok",
121
- `report: ${res.report_path}`,
122
- ].join("\n"));
226
+ readPresetControlRaw(name)
227
+ .then((raw) => {
228
+ if (!raw) {
229
+ console.error(`Preset not found: ${name}`);
230
+ process.exit(2);
231
+ }
232
+ process.stdout.write(raw + "\n");
233
+ process.exit(0);
234
+ })
235
+ .catch((err) => {
236
+ console.error(err);
237
+ process.exit(1);
238
+ });
239
+ }
240
+ else {
241
+ usage(2);
242
+ process.exit(2);
243
+ }
244
+ }
245
+ else if (subcommand === "observe") {
246
+ const action = process.argv[3];
247
+ const project = getArg("--project");
248
+ if (!project) {
249
+ usage(2);
250
+ process.exit(2);
251
+ }
252
+ if (action === "list") {
253
+ listObservabilityRuns(project)
254
+ .then((runs) => {
255
+ if (!runs.length) {
256
+ console.log("observe list: no runs");
257
+ process.exit(0);
258
+ }
259
+ for (const run of runs) {
260
+ console.log(JSON.stringify(run));
261
+ }
262
+ process.exit(0);
263
+ })
264
+ .catch((err) => {
265
+ console.error(err);
266
+ process.exit(1);
267
+ });
268
+ }
269
+ else if (action === "tail") {
270
+ const runId = getArg("--run");
271
+ if (!runId) {
272
+ usage(2);
273
+ process.exit(2);
274
+ }
275
+ const limitRaw = getArg("--limit");
276
+ const limit = limitRaw ? Number(limitRaw) : 20;
277
+ tailObservabilityEvents(project, runId, Number.isFinite(limit) ? limit : 20)
278
+ .then((lines) => {
279
+ if (!lines.length) {
280
+ console.log("observe tail: no events");
281
+ process.exit(0);
282
+ }
283
+ for (const line of lines) {
284
+ console.log(line);
285
+ }
286
+ process.exit(0);
287
+ })
288
+ .catch((err) => {
289
+ console.error(err);
290
+ process.exit(1);
291
+ });
292
+ }
293
+ else if (action === "summary") {
294
+ const runId = getArg("--run");
295
+ if (!runId) {
296
+ usage(2);
297
+ process.exit(2);
298
+ }
299
+ readObservabilitySummary(project, runId)
300
+ .then((summary) => {
301
+ if (!summary) {
302
+ console.log("observe summary: not found");
303
+ process.exit(0);
304
+ }
305
+ process.stdout.write(JSON.stringify(summary, null, 2) + "\n");
123
306
  process.exit(0);
124
307
  })
125
308
  .catch((err) => {
@@ -1,6 +1,10 @@
1
1
  type ApplyResult = {
2
2
  run_id: string;
3
3
  report_path: string;
4
+ exit_code?: number;
4
5
  };
5
- export declare function controlApplyV0(projectDir: string, profilePath: string, outDir: string, mode?: string): Promise<ApplyResult>;
6
+ type ApplyOptions = {
7
+ assetSourceRoot?: string;
8
+ };
9
+ export declare function controlApplyV0(projectDir: string, profilePath: string, outDir: string, mode?: string, options?: ApplyOptions): Promise<ApplyResult>;
6
10
  export {};
@@ -4,6 +4,9 @@ import { stableStringify } from "./stable_json.js";
4
4
  import { stableSha256 } from "./redaction.js";
5
5
  import { buildMovaControlEntryV0, MOVA_CONTROL_ENTRY_MARKER } from "./mova_overlay_v0.js";
6
6
  import { ensureClaudeControlSurfacesV0 } from "./claude_profile_scaffold_v0.js";
7
+ import { controlToMcpJson, controlToSettingsV0, normalizeControlV0 } from "./control_v0.js";
8
+ import { validateControlV0Schema } from "./control_v0_schema.js";
9
+ import { getMovaObserveScriptV0 } from "./observability_writer_v0.js";
7
10
  async function exists(p) {
8
11
  try {
9
12
  await fs.stat(p);
@@ -24,6 +27,37 @@ async function writeJson(p, obj) {
24
27
  function computeRunId(parts) {
25
28
  return stableSha256(parts.join("|")).slice(0, 16);
26
29
  }
30
+ function mergeOverlayValue(existing, incoming) {
31
+ if (existing === undefined)
32
+ return incoming;
33
+ const existingArr = Array.isArray(existing) ? existing : null;
34
+ const incomingArr = Array.isArray(incoming) ? incoming : null;
35
+ if (existingArr || incomingArr) {
36
+ const left = existingArr ?? (existing === undefined ? [] : [existing]);
37
+ const right = incomingArr ?? (incoming === undefined ? [] : [incoming]);
38
+ const seen = new Set(left.map((item) => stableStringify(item)));
39
+ const merged = left.slice();
40
+ for (const item of right) {
41
+ const key = stableStringify(item);
42
+ if (seen.has(key))
43
+ continue;
44
+ seen.add(key);
45
+ merged.push(item);
46
+ }
47
+ return merged;
48
+ }
49
+ if (existing && typeof existing === "object" && incoming && typeof incoming === "object") {
50
+ const out = { ...existing };
51
+ for (const [key, value] of Object.entries(incoming)) {
52
+ out[key] = mergeOverlayValue(out[key], value);
53
+ }
54
+ return out;
55
+ }
56
+ return existing;
57
+ }
58
+ function mergeSettingsOverlay(existing, incoming) {
59
+ return mergeOverlayValue(existing ?? {}, incoming ?? {});
60
+ }
27
61
  function updateClaude(content, marker, block) {
28
62
  if (content.includes(marker)) {
29
63
  const idx = content.indexOf(marker);
@@ -34,15 +68,38 @@ function updateClaude(content, marker, block) {
34
68
  }
35
69
  return `${block}\n${content}`;
36
70
  }
37
- export async function controlApplyV0(projectDir, profilePath, outDir, mode) {
71
+ export async function controlApplyV0(projectDir, profilePath, outDir, mode, options) {
38
72
  await ensureClaudeControlSurfacesV0(projectDir);
39
73
  const profile = await readJson(profilePath);
40
- const applyMode = mode ?? profile?.apply?.default_apply_mode ?? "preview";
74
+ const isControlV0 = profile?.version === "control_v0";
75
+ const control = isControlV0 ? normalizeControlV0(profile).control : null;
76
+ if (isControlV0) {
77
+ const validation = await validateControlV0Schema(profile);
78
+ if (!validation.ok) {
79
+ const runId = computeRunId([profilePath, "invalid_schema"]);
80
+ const runBase = path.join(outDir, "mova", "claude_control", "v0", "runs", runId);
81
+ const reportPath = path.join(runBase, "control_apply_report_v0.json");
82
+ const report = {
83
+ profile_version: "v0",
84
+ run_id: runId,
85
+ project_dir: projectDir,
86
+ profile_path: profilePath,
87
+ mode: mode ?? "preview",
88
+ outcome_code: "INVALID_SCHEMA",
89
+ errors: validation.errors ?? [],
90
+ };
91
+ await writeJson(reportPath, report);
92
+ return { run_id: runId, report_path: reportPath, exit_code: 2 };
93
+ }
94
+ }
95
+ const applyMode = mode ?? (isControlV0 && control?.policy?.mode === "report_only" ? "preview" : profile?.apply?.default_apply_mode) ?? "preview";
96
+ const isOverlay = applyMode === "overlay";
97
+ const assetSourceRoot = options?.assetSourceRoot ?? projectDir;
41
98
  const claudePath = path.join(projectDir, "CLAUDE.md");
42
99
  const mcpPath = path.join(projectDir, ".mcp.json");
43
100
  const claudeExists = await exists(claudePath);
44
101
  const mcpExists = await exists(mcpPath);
45
- const marker = profile?.anthropic?.claude_md?.marker ?? MOVA_CONTROL_ENTRY_MARKER;
102
+ const marker = control?.claude_md?.marker ?? profile?.anthropic?.claude_md?.marker ?? MOVA_CONTROL_ENTRY_MARKER;
46
103
  const runId = computeRunId([profilePath, claudeExists ? "claude" : "", mcpExists ? "mcp" : "", marker, applyMode]);
47
104
  const runBase = path.join(outDir, "mova", "claude_control", "v0", "runs", runId);
48
105
  const overlayParams = {
@@ -56,15 +113,75 @@ export async function controlApplyV0(projectDir, profilePath, outDir, mode) {
56
113
  exportManifestFile: "export_manifest_v0.json",
57
114
  };
58
115
  const controlEntry = buildMovaControlEntryV0(overlayParams);
59
- const applied = { claude_md: false, mcp_json: false, settings: false };
60
- if (applyMode === "apply") {
61
- if (profile?.anthropic?.claude_md?.inject_control_entry && claudeExists) {
116
+ const applied = { claude_md: false, mcp_json: false, settings: false, assets: false, lsp: false };
117
+ if (applyMode === "apply" || applyMode === "overlay") {
118
+ if ((control?.claude_md?.inject_control_entry ?? profile?.anthropic?.claude_md?.inject_control_entry) && claudeExists) {
62
119
  const raw = await fs.readFile(claudePath, "utf8");
63
120
  const updated = updateClaude(raw, marker, controlEntry);
64
121
  await fs.writeFile(claudePath, updated, "utf8");
65
122
  applied.claude_md = true;
66
123
  }
67
- if (profile?.anthropic?.mcp?.servers && mcpExists) {
124
+ if (control) {
125
+ const settingsPath = path.join(projectDir, ".claude", "settings.json");
126
+ const settingsGenerated = controlToSettingsV0(control);
127
+ if (isOverlay && (await exists(settingsPath))) {
128
+ const current = await readJson(settingsPath);
129
+ const merged = mergeSettingsOverlay(current, settingsGenerated);
130
+ await writeJson(settingsPath, merged);
131
+ }
132
+ else {
133
+ await writeJson(settingsPath, settingsGenerated);
134
+ }
135
+ applied.settings = true;
136
+ if (!(isOverlay && (await exists(mcpPath)))) {
137
+ await writeJson(path.join(projectDir, ".mcp.json"), controlToMcpJson(control));
138
+ applied.mcp_json = true;
139
+ }
140
+ const assets = [
141
+ ...control.assets.skills,
142
+ ...control.assets.agents,
143
+ ...control.assets.commands,
144
+ ...control.assets.rules,
145
+ ...control.assets.hooks,
146
+ ...control.assets.workflows,
147
+ ...control.assets.docs,
148
+ ...control.assets.dotfiles,
149
+ ...control.assets.schemas,
150
+ ];
151
+ for (const asset of assets) {
152
+ const target = path.join(projectDir, asset.path);
153
+ if (isOverlay && (await exists(target)))
154
+ continue;
155
+ const sourceRel = asset.source_path ?? asset.path;
156
+ const source = path.isAbsolute(sourceRel) ? sourceRel : path.join(assetSourceRoot, sourceRel);
157
+ try {
158
+ await fs.stat(source);
159
+ }
160
+ catch {
161
+ continue;
162
+ }
163
+ await fs.mkdir(path.dirname(target), { recursive: true });
164
+ if (source !== target) {
165
+ await fs.copyFile(source, target);
166
+ }
167
+ }
168
+ applied.assets = assets.length > 0;
169
+ if (control.lsp.managed && Array.isArray(control.lsp.enabled_plugins)) {
170
+ const lspPath = path.join(projectDir, control.lsp.config_path);
171
+ if (!(isOverlay && (await exists(lspPath)))) {
172
+ await writeJson(lspPath, { enabled_plugins: control.lsp.enabled_plugins });
173
+ }
174
+ applied.lsp = true;
175
+ }
176
+ if (control.observability.enable && control.observability.writer?.script_path) {
177
+ const scriptPath = path.join(projectDir, control.observability.writer.script_path);
178
+ if (!(isOverlay && (await exists(scriptPath)))) {
179
+ await fs.mkdir(path.dirname(scriptPath), { recursive: true });
180
+ await fs.writeFile(scriptPath, getMovaObserveScriptV0(), "utf8");
181
+ }
182
+ }
183
+ }
184
+ else if (profile?.anthropic?.mcp?.servers && mcpExists) {
68
185
  const mcp = await readJson(mcpPath);
69
186
  const merged = { ...mcp, servers: profile.anthropic.mcp.servers };
70
187
  await fs.writeFile(mcpPath, stableStringify(merged) + "\n", "utf8");
@@ -77,7 +194,7 @@ export async function controlApplyV0(projectDir, profilePath, outDir, mode) {
77
194
  project_dir: projectDir,
78
195
  profile_path: profilePath,
79
196
  mode: applyMode,
80
- outcome_code: applyMode === "apply" ? "APPLIED" : "PREVIEW",
197
+ outcome_code: applyMode === "apply" || applyMode === "overlay" ? "APPLIED" : "PREVIEW",
81
198
  applied,
82
199
  };
83
200
  const reportPath = path.join(runBase, "control_apply_report_v0.json");
@@ -2,6 +2,7 @@ type CheckResult = {
2
2
  run_id: string;
3
3
  plan_path: string;
4
4
  summary_path: string;
5
+ exit_code?: number;
5
6
  };
6
7
  export declare function controlCheckV0(projectDir: string, profilePath: string, outDir: string): Promise<CheckResult>;
7
8
  export {};