@dzhechkov/p-replicator 1.5.5 → 1.5.7

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.
@@ -0,0 +1,372 @@
1
+ # 05. Архитектура
2
+
3
+ Внутреннее устройство `p-replicator` — как ставится, как обновляется,
4
+ как сосуществует с user customizations.
5
+
6
+ ## Двухуровневая модель: Pre-shipped vs Project-generated
7
+
8
+ Главное архитектурное разделение:
9
+
10
+ | Уровень | Кто создаёт | Где живёт | Изменяется |
11
+ |---|---|---|---|
12
+ | **Pre-shipped** | `npx p-replicator init` | `.claude/{skills,commands,agents,rules,hooks}/` + `settings.json` | На каждом upgrade пакета |
13
+ | **Project-generated** | `/replicate` Phase 3 (LLM execution) | Различные места: `CLAUDE.md`, `.claude/agents/planner.md`, `docs/`, и т.д. | Только при пересоздании |
14
+
15
+ Это **главный фикс** v1.4.0 — раньше `/replicate` Phase 3 пыталась генерировать
16
+ ВСЕ артефакты (включая generic команды как `/run`, `/feature`), что приводило
17
+ к flaky outputs (LLM compression, missed templates). После v1.4.0
18
+ generic-команды pre-shipped, Phase 3 генерирует ТОЛЬКО project-specific.
19
+
20
+ ---
21
+
22
+ ## SSOT: `utils.COMPONENTS`
23
+
24
+ Единый источник правды о том что shipped и что generated. Структура:
25
+
26
+ ```javascript
27
+ const COMPONENTS = {
28
+ // Pre-shipped (6 групп, install via npx init):
29
+ skills: { kind: 'pre-shipped', src: '.claude/skills', items: { /* 10 */ } },
30
+ commands: { kind: 'pre-shipped', src: '.claude/commands', items: { /* 11 */ } },
31
+ agents: { kind: 'pre-shipped', src: '.claude/agents', items: { /* 4 */ } },
32
+ rules: { kind: 'pre-shipped', src: '.claude/rules', items: { /* 5 */ } },
33
+ settings: { kind: 'pre-shipped', isFile: true, src: '.claude/settings.json' },
34
+ hooks: { kind: 'pre-shipped', src: '.claude/hooks', items: { /* 6 */ } },
35
+
36
+ // Project-generated (3 группы, created by /replicate Phase 3):
37
+ projectAgents: { kind: 'project-generated', items: { /* full paths */ } },
38
+ projectRules: { kind: 'project-generated', items: { /* full paths */ } },
39
+ projectFiles: { kind: 'project-generated', items: { /* full paths */ } },
40
+ };
41
+ ```
42
+
43
+ **Consumers:**
44
+ - `init.js` / `update.js` — итерируют только `kind === 'pre-shipped'` для install
45
+ - `doctor.js` — проверяет существование pre-shipped artifacts
46
+ - `list.js` — выводит metadata
47
+ - `verify.js` — проверяет ОБЕ группы (pre-shipped strict, project-generated
48
+ hints)
49
+ - `cli.js` showHelp — динамически считает items для отображения
50
+
51
+ **Любая будущая правка items автоматически обновляет все 5 поверхностей** —
52
+ устранены drift-проблемы которые были до v1.3.1.
53
+
54
+ ---
55
+
56
+ ## Path derivation: `getItemRelativePath()`
57
+
58
+ Один helper централизует derivation для всех групп:
59
+
60
+ ```javascript
61
+ function getItemRelativePath(comp, itemKey) {
62
+ if (comp.isFile) return comp.src; // settings.json
63
+ if (comp.kind === 'project-generated') return itemKey; // full paths
64
+ if (comp.src === '.claude/skills') return path.join(comp.src, itemKey, 'SKILL.md');
65
+ if (comp.src === '.claude/hooks') return path.join(comp.src, itemKey + '.cjs');
66
+ return path.join(comp.src, itemKey + '.md'); // commands/rules/agents
67
+ }
68
+ ```
69
+
70
+ ---
71
+
72
+ ## Cross-platform hooks (v1.4.1)
73
+
74
+ **Дизайн-принцип:** zero shell dependency.
75
+
76
+ Все 6 hook-скриптов написаны на pure Node, используют `execFileSync('git', [...])`
77
+ вместо shell-pipes. Это эквивалентно работает на:
78
+ - Windows cmd.exe (нет `2>/dev/null`, есть `2>nul` — не нужен ни тот ни другой)
79
+ - Bash / zsh / Git Bash на Windows
80
+ - PowerShell
81
+
82
+ **Pattern для autocommit-скрипта:**
83
+
84
+ ```javascript
85
+ const fs = require('node:fs');
86
+ const path = require('node:path');
87
+ const { execFileSync } = require('node:child_process');
88
+
89
+ const TARGET = path.resolve(process.cwd(), '.claude', 'feature-roadmap.json');
90
+ const SILENT = { stdio: 'ignore' };
91
+ const git = (args) => execFileSync('git', args, SILENT);
92
+
93
+ try {
94
+ if (!fs.existsSync(TARGET)) process.exit(0);
95
+ try { git(['rev-parse', '--git-dir']); } catch { process.exit(0); }
96
+ git(['add', '--', TARGET]);
97
+ let hasDiff = false;
98
+ try { git(['diff', '--cached', '--quiet', '--', TARGET]); }
99
+ catch { hasDiff = true; }
100
+ if (hasDiff) git(['commit', '--only', '--', TARGET, '-m', '...']);
101
+ } catch { process.exit(0); }
102
+ ```
103
+
104
+ **Defensive properties:**
105
+ - `if (!fs.existsSync) exit 0` — нет файла, нечего коммитить
106
+ - `try { rev-parse } catch { exit 0 }` — нет git репозитория, skip
107
+ - `try { diff } catch { hasDiff = true }` — `git diff --quiet` exits 1 if diff
108
+ - Outer `try/catch` гарантирует exit 0 при любых ошибках (best-effort)
109
+
110
+ ---
111
+
112
+ ## Sync-templates: MERGE mode (v1.4.1)
113
+
114
+ **Файл:** `scripts/sync-templates.js` — runs as `prepublishOnly` hook.
115
+
116
+ **Цель:** скопировать `.claude/` из source-repo в `templates/.claude/`
117
+ (который попадает в npm tarball).
118
+
119
+ **До v1.4.1 (BUG):** `cleanDir(target)` + `copyRecursive(source, target)` —
120
+ очищал target перед copy. Удалял файлы которые есть в `templates/` но нет в
121
+ source. Это **silently удалило все v1.4.0 pre-shipped команды** во время
122
+ `npm publish --dry-run`.
123
+
124
+ **После v1.4.1 (FIX):** `ensureDir(target)` + `copyRecursive(source, target)` —
125
+ копирует/перезаписывает source-файлы, но НЕ удаляет target-only. Pre-shipped
126
+ файлы выживают, source файлы overwrite'ят с правильным содержимым.
127
+
128
+ **Идемпотентность:** прогон 2 раза подряд → одинаковый result.
129
+
130
+ ---
131
+
132
+ ## Settings.json merge (v1.4.2)
133
+
134
+ `init --force` и `update` используют `mergeSettingsJson(existing, template)`
135
+ для preserve user customizations:
136
+
137
+ ### Алгоритм
138
+
139
+ ```
140
+ mergeSettingsJson(existing, template):
141
+ if !existing: return template (fresh install)
142
+ if !template: return existing (defensive)
143
+
144
+ merged = {...existing}
145
+
146
+ # Top-level: template добавляет только то чего нет у user
147
+ for each (key, value) in template:
148
+ if key not in merged: merged[key] = value
149
+
150
+ # Hooks: deep merge per event type
151
+ if template.hooks:
152
+ merged.hooks = mergeHookEvents(existing.hooks, template.hooks)
153
+
154
+ return merged
155
+
156
+ mergeHookEvents(existing, template):
157
+ for each eventType in template:
158
+ if !existing[eventType]: existing[eventType] = template[eventType]
159
+ else: mergeHookMatchers(existing[eventType], template[eventType])
160
+
161
+ mergeHookMatchers(existing[], template[]):
162
+ for each tplEntry in template:
163
+ target = existing.find(e => e.matcher === tplEntry.matcher)
164
+ if !target: existing.push(tplEntry)
165
+ else:
166
+ existingCmds = Set(target.hooks.map(h => h.command))
167
+ for each tplHook in tplEntry.hooks:
168
+ if !existingCmds.has(tplHook.command):
169
+ target.hooks.push(tplHook) # de-dup by command string
170
+ ```
171
+
172
+ ### Identity model
173
+
174
+ Hooks сравниваются по `command` string. Implications:
175
+
176
+ - User-added hook (отсутствует в template): **preserved**
177
+ - User-modified default (изменил command): treated как user-added → **preserved**
178
+ (старый default удаляется через orphan detection если был в shippedDefaults)
179
+ - Identical command в template и user: **de-duped** (только одна копия)
180
+ - New hook in template: **added** к user's settings
181
+
182
+ ### Override
183
+
184
+ `--reset-settings` flag отключает merge — full overwrite. Для случаев когда
185
+ user хочет clean-slate.
186
+
187
+ ---
188
+
189
+ ## Orphan detection (v1.4.3)
190
+
191
+ **Проблема merge-only логики:** если в новой версии package удаляет hook,
192
+ старый hook остаётся у user'а forever (user-added perspective).
193
+
194
+ **Решение:** `manifest.shippedDefaults` baseline.
195
+
196
+ ### Алгоритм
197
+
198
+ ```
199
+ init/update upgrade flow:
200
+ 1. previousManifest = read .p-replicator.json BEFORE overwrite
201
+ 2. oldTpl = previousManifest.shippedDefaults['settings.json']
202
+ 3. newTpl = read templates/.claude/settings.json (current)
203
+ 4. existing = read user's .claude/settings.json
204
+ 5. cleaned = removeOrphanHooks(existing, oldTpl, newTpl)
205
+ 6. merged = mergeSettingsJson(cleaned, newTpl)
206
+ 7. write merged to .claude/settings.json
207
+ 8. write new manifest with shippedDefaults = newTpl (для следующего upgrade)
208
+
209
+ removeOrphanHooks(existing, oldTpl, newTpl):
210
+ if !oldTpl: return existing (first upgrade, no baseline yet)
211
+ oldCmds = extractCommands(oldTpl)
212
+ newCmds = extractCommands(newTpl)
213
+ orphans = oldCmds.filter(c => !newCmds.has(c))
214
+ return existing with orphan commands filtered out
215
+ ```
216
+
217
+ ### Свойства
218
+
219
+ - **User-added** (никогда не было в `oldTpl`) → preserved
220
+ - **Removed default** (было в `oldTpl`, нет в `newTpl`, есть у user) → удалён
221
+ - **Unchanged default** (есть везде) → kept
222
+ - **Renamed/modified default** (cmd-string changed) → старый orphaned, новый
223
+ added через merge
224
+
225
+ ### Backward compat
226
+
227
+ Если manifest без `shippedDefaults` (pre-1.4.3 install) — orphan detection
228
+ skipped на первый upgrade. Manifest заполняется на текущем install для
229
+ будущих upgrade'ов.
230
+
231
+ ---
232
+
233
+ ## Statusline architecture (v1.5.0)
234
+
235
+ **Цель:** single-script multi-line dashboard.
236
+
237
+ ```
238
+ ┌─ statusline.cjs (entry, ~330 LOC) ─────────────────────────┐
239
+ │ │
240
+ │ 1. main() │
241
+ │ ├── parseManifest() ───────► .p-replicator.json │
242
+ │ ├── parseState() ──────────► .claude/.p-replicator-state.json (with stale-check) │
243
+ │ ├── parseRoadmap() ────────► .claude/feature-roadmap.json │
244
+ │ ├── parseSparcDocs() ──────► docs/PRD.md, ..., ADR.md │
245
+ │ ├── parseValidationScore() ► docs/validation-report.md (regex) │
246
+ │ ├── parseAdrs() ───────────► docs/ADR.md OR docs/adr/ OR docs/ddd/adr/ │
247
+ │ ├── parsePlans() ──────────► docs/plans/*.md │
248
+ │ ├── parseInsights() ───────► .claude/insights/index.md │
249
+ │ ├── parseToolkit() ────────► filesystem walks │
250
+ │ ├── parseSettingsStatus() ─► deep-equals current vs shippedDefaults │
251
+ │ ├── parseMcpServers() ─────► .mcp.json │
252
+ │ ├── parseKeysarium() ──────► .keysarium.json existence │
253
+ │ ├── parseDomain() ─────────► CLAUDE.md keyword grep │
254
+ │ ├── parseLastHarvest() ────► TOOLKIT_HARVEST.md mtime │
255
+ │ └── parseLastTest() ───────► .claude/.last-test.json (optional) │
256
+ │ │
257
+ │ 2. lines = [ │
258
+ │ buildHeader(manifest), │
259
+ │ buildPipeline(state), │
260
+ │ buildRoadmap(roadmap, domain), │
261
+ │ buildDocs(sparc, validation, plans, adrs, lastHarvest), │
262
+ │ buildToolkit(toolkit, expected), │
263
+ │ buildStatus(insights, lastTest, mcpServers, settingsStatus, keysarium), │
264
+ │ ] │
265
+ │ │
266
+ │ 3. process.stdout.write(lines.join('\n') + '\n') │
267
+ │ │
268
+ └─────────────────────────────────────────────────────────────┘
269
+ ```
270
+
271
+ **Defensive design:** каждая `parse*` функция wrapped в `safeRun()` с
272
+ fallback. Один parse error → fallback value, остальные секции работают.
273
+
274
+ **State-file flow:**
275
+
276
+ ```
277
+ команда (e.g., /run) ──Bash──► node .claude/hooks/state-update.cjs --command /run --phase loop --progress 0.4
278
+
279
+
280
+ .claude/.p-replicator-state.json (atomic write)
281
+
282
+
283
+ Claude Code prompt ────────► node .claude/hooks/statusline.cjs
284
+
285
+
286
+ читает state, считает heuristics, рендерит 6 строк
287
+ ```
288
+
289
+ **Stale check:** state старше 30 минут → ignore (показывается `idle` в Pipeline
290
+ секции).
291
+
292
+ ---
293
+
294
+ ## Test infrastructure
295
+
296
+ **Suite:** 105 tests, 36 suites, ~25 sec runtime.
297
+
298
+ | Layer | File | Coverage |
299
+ |---|---|---|
300
+ | **Unit** | `tests/unit/utils.test.js` (54 tests) | Pure functions: createManifest, mergeSettingsJson, removeOrphanHooks, getItemRelativePath, parseToolkit logic |
301
+ | **E2E** | `tests/e2e/lifecycle.test.js` (48 tests) | Full CLI lifecycle, hooks installation, settings merge edge cases, statusline output, --feature-branches docs |
302
+ | **Snapshot** | `tests/snapshot/templates.test.js` (3 tests) | SHA-256 baseline для всех 115 файлов в `templates/` |
303
+
304
+ **Меta-тесты:** проверяют consistency между документами. Например:
305
+ - `replicate-pipeline.md` упоминает все pre-shipped commands (no orphan in rule)
306
+ - `replicate.md` Phase 3 не утверждает «Generate `<pre-shipped>.md`» (no
307
+ drift в spec)
308
+
309
+ **Snapshot baseline** регенерируется через `npm run snapshot:baseline` после
310
+ intentional template changes.
311
+
312
+ ---
313
+
314
+ ## Module composition: `view()` syntax (Claude Code-specific)
315
+
316
+ Skills используют `view()` для cross-skill loading в runtime:
317
+
318
+ ```markdown
319
+ view() .claude/skills/explore/SKILL.md
320
+ view() .claude/skills/explore/references/questioning-techniques.md
321
+ ```
322
+
323
+ Claude Code разрешает эти ссылки динамически: при выполнении skill,
324
+ LLM читает referenced files в момент использования. Это позволяет skill A
325
+ делегировать в skill B без duplicate-копий контента.
326
+
327
+ **Ограничение:** только Claude Code поддерживает этот runtime-механизм. Для
328
+ других платформ (Codex, OpenCode) skill content должен быть **inlined**
329
+ (скомпилирован в command markdown) на install-time. См.
330
+ `MULTIPLATFORM_ROADMAP.md`.
331
+
332
+ ---
333
+
334
+ ## Pipeline: `/replicate` фазы
335
+
336
+ ```
337
+ INPUT (idea or company name)
338
+
339
+
340
+ Phase 0: PRODUCT DISCOVERY (опц.)
341
+ │ skill: reverse-engineering-unicorn
342
+ │ output: docs/00_product_discovery.md
343
+
344
+ Phase 1: PLANNING
345
+ │ skill: sparc-prd-mini (внутри: explore + research + solve + 5 SPARC phases)
346
+ │ output: docs/PRD.md, Architecture.md, Pseudocode.md, ... (11 docs)
347
+
348
+ Phase 2: VALIDATION (5-agent swarm)
349
+ │ skill: requirements-validator
350
+ │ output: docs/validation-report.md, docs/test-scenarios.md (BDD)
351
+ │ verdict: 🟢 READY / 🟡 CAVEATS / 🔴 NEEDS WORK (max 3 retries)
352
+
353
+ Phase 3: TOOLKIT GENERATION (project-specific only)
354
+ │ skill: cc-toolkit-generator-enhanced (9 modules)
355
+ │ output: project agents (planner, code-reviewer, architect),
356
+ │ project rules (security, coding-style, testing),
357
+ │ project skills (project-context, coding-standards),
358
+ │ CLAUDE.md, feature-roadmap.json, DEVELOPMENT_GUIDE.md
359
+
360
+ Phase 4: FINALIZE
361
+ │ output: docker-compose.yml, Dockerfile, .gitignore
362
+ │ action: git commit
363
+
364
+ DONE — project готов к /start или /run
365
+ ```
366
+
367
+ ---
368
+
369
+ ## Дальше
370
+
371
+ - [06_troubleshooting.md](./06_troubleshooting.md) — типичные проблемы
372
+ - [07_changelog.md](./07_changelog.md) — история эволюции