@dzhechkov/p-replicator 1.5.6 → 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.
- package/LICENSE +21 -0
- package/README/eng/01_quickstart.md +244 -0
- package/README/eng/02_user_guide.md +634 -0
- package/README/eng/03_admin_guide.md +333 -0
- package/README/eng/04_api_reference.md +302 -0
- package/README/eng/05_architecture.md +370 -0
- package/README/eng/06_troubleshooting.md +353 -0
- package/README/eng/07_changelog.md +134 -0
- package/README/eng/README.md +60 -0
- package/README/ru/01_quickstart.md +244 -0
- package/README/ru/02_user_guide.md +633 -0
- package/README/ru/03_admin_guide.md +337 -0
- package/README/ru/04_api_reference.md +333 -0
- package/README/ru/05_architecture.md +372 -0
- package/README/ru/06_troubleshooting.md +355 -0
- package/README/ru/07_changelog.md +146 -0
- package/README/ru/README.md +60 -0
- package/README/ru/html/build.js +553 -0
- package/README/ru/html/index.html +1312 -0
- package/README/ru/html/script.js +496 -0
- package/README/ru/html/style.css +804 -0
- package/bin/cli.js +0 -0
- package/package.json +10 -10
- package/src/cli.js +7 -1
- package/src/commands/init.js +4 -1
- package/templates/.claude/agents/replicate-coordinator.md +23 -21
- package/templates/.claude/commands/replicate.md +4 -2
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/03-generate-p0.md +15 -0
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/04-generate-p1.md +20 -0
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/README.md +11 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
# 05. Architecture
|
|
2
|
+
|
|
3
|
+
Internal design of `p-replicator` — how it installs, upgrades, coexists
|
|
4
|
+
with user customizations.
|
|
5
|
+
|
|
6
|
+
## Two-tier model: Pre-shipped vs Project-generated
|
|
7
|
+
|
|
8
|
+
The fundamental architectural split:
|
|
9
|
+
|
|
10
|
+
| Tier | Created by | Lives in | Updated |
|
|
11
|
+
|---|---|---|---|
|
|
12
|
+
| **Pre-shipped** | `npx p-replicator init` | `.claude/{skills,commands,agents,rules,hooks}/` + `settings.json` | On each package upgrade |
|
|
13
|
+
| **Project-generated** | `/replicate` Phase 3 (LLM execution) | Various: `CLAUDE.md`, `.claude/agents/planner.md`, `docs/`, etc. | Only on regeneration |
|
|
14
|
+
|
|
15
|
+
This is **the main fix** of v1.4.0 — previously `/replicate` Phase 3 tried
|
|
16
|
+
to generate ALL artifacts (including generic commands like `/run`,
|
|
17
|
+
`/feature`), which led to flaky outputs (LLM compression, missed templates).
|
|
18
|
+
Post-v1.4.0, generic commands are pre-shipped, Phase 3 generates ONLY
|
|
19
|
+
project-specific artifacts.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## SSOT: `utils.COMPONENTS`
|
|
24
|
+
|
|
25
|
+
Single source of truth for what's shipped and what's generated:
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
const COMPONENTS = {
|
|
29
|
+
// Pre-shipped (6 groups, install via npx init):
|
|
30
|
+
skills: { kind: 'pre-shipped', src: '.claude/skills', items: { /* 10 */ } },
|
|
31
|
+
commands: { kind: 'pre-shipped', src: '.claude/commands', items: { /* 11 */ } },
|
|
32
|
+
agents: { kind: 'pre-shipped', src: '.claude/agents', items: { /* 4 */ } },
|
|
33
|
+
rules: { kind: 'pre-shipped', src: '.claude/rules', items: { /* 5 */ } },
|
|
34
|
+
settings: { kind: 'pre-shipped', isFile: true, src: '.claude/settings.json' },
|
|
35
|
+
hooks: { kind: 'pre-shipped', src: '.claude/hooks', items: { /* 6 */ } },
|
|
36
|
+
|
|
37
|
+
// Project-generated (3 groups, created by /replicate Phase 3):
|
|
38
|
+
projectAgents: { kind: 'project-generated', items: { /* full paths */ } },
|
|
39
|
+
projectRules: { kind: 'project-generated', items: { /* full paths */ } },
|
|
40
|
+
projectFiles: { kind: 'project-generated', items: { /* full paths */ } },
|
|
41
|
+
};
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Consumers:**
|
|
45
|
+
- `init.js` / `update.js` iterate only `kind === 'pre-shipped'` for install
|
|
46
|
+
- `doctor.js` checks existence of pre-shipped artifacts
|
|
47
|
+
- `list.js` outputs metadata
|
|
48
|
+
- `verify.js` checks BOTH groups (pre-shipped strict, project-generated hints)
|
|
49
|
+
- `cli.js` showHelp dynamically counts items for display
|
|
50
|
+
|
|
51
|
+
**Any future edit to items automatically updates all 5 surfaces** —
|
|
52
|
+
eliminating drift issues that existed pre-v1.3.1.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Path derivation: `getItemRelativePath()`
|
|
57
|
+
|
|
58
|
+
A single helper centralizes path derivation across all groups:
|
|
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
|
+
**Design principle:** zero shell dependency.
|
|
75
|
+
|
|
76
|
+
All 6 hook scripts are pure Node, using `execFileSync('git', [...])`
|
|
77
|
+
instead of shell pipes. This works equivalently on:
|
|
78
|
+
- Windows cmd.exe (no `2>/dev/null`, no `2>nul` — neither needed)
|
|
79
|
+
- Bash / zsh / Git Bash on Windows
|
|
80
|
+
- PowerShell
|
|
81
|
+
|
|
82
|
+
**Pattern for autocommit script:**
|
|
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` — no file, nothing to commit
|
|
106
|
+
- `try { rev-parse } catch { exit 0 }` — no git repo, skip
|
|
107
|
+
- `try { diff } catch { hasDiff = true }` — `git diff --quiet` exits 1 if diff
|
|
108
|
+
- Outer `try/catch` ensures exit 0 on any error (best-effort)
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Sync-templates: MERGE mode (v1.4.1)
|
|
113
|
+
|
|
114
|
+
**File:** `scripts/sync-templates.js` — runs as `prepublishOnly` hook.
|
|
115
|
+
|
|
116
|
+
**Goal:** copy `.claude/` from source repo into `templates/.claude/` (which
|
|
117
|
+
ends up in the npm tarball).
|
|
118
|
+
|
|
119
|
+
**Pre-v1.4.1 (BUG):** `cleanDir(target)` + `copyRecursive(source, target)` —
|
|
120
|
+
cleared target before copy. Deleted files present in `templates/` but absent
|
|
121
|
+
in source. This **silently deleted all v1.4.0 pre-shipped commands** during
|
|
122
|
+
`npm publish --dry-run`.
|
|
123
|
+
|
|
124
|
+
**Post-v1.4.1 (FIX):** `ensureDir(target)` + `copyRecursive(source, target)` —
|
|
125
|
+
copies/overwrites source files but does NOT delete target-only ones.
|
|
126
|
+
Pre-shipped files survive; source files overwrite with correct content.
|
|
127
|
+
|
|
128
|
+
**Idempotent:** running twice consecutively → identical result.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Settings.json merge (v1.4.2)
|
|
133
|
+
|
|
134
|
+
`init --force` and `update` use `mergeSettingsJson(existing, template)` to
|
|
135
|
+
preserve user customizations.
|
|
136
|
+
|
|
137
|
+
### Algorithm
|
|
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 only fills what user lacks
|
|
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 are compared by `command` string. Implications:
|
|
175
|
+
|
|
176
|
+
- User-added hook (absent from template): **preserved**
|
|
177
|
+
- User-modified default (changed command): treated as user-added → **preserved**
|
|
178
|
+
(old default removed via orphan detection if it was in shippedDefaults)
|
|
179
|
+
- Identical command in template and user: **de-duped** (single copy)
|
|
180
|
+
- New hook in template: **added** to user's settings
|
|
181
|
+
|
|
182
|
+
### Override
|
|
183
|
+
|
|
184
|
+
`--reset-settings` flag disables merge — full overwrite. For when user wants
|
|
185
|
+
clean slate.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Orphan detection (v1.4.3)
|
|
190
|
+
|
|
191
|
+
**Problem with merge-only logic:** if a new package version removes a hook,
|
|
192
|
+
the old hook lingers forever in user's settings (looks user-added from
|
|
193
|
+
merge perspective).
|
|
194
|
+
|
|
195
|
+
**Solution:** `manifest.shippedDefaults` baseline.
|
|
196
|
+
|
|
197
|
+
### Algorithm
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
init/update upgrade flow:
|
|
201
|
+
1. previousManifest = read .p-replicator.json BEFORE overwrite
|
|
202
|
+
2. oldTpl = previousManifest.shippedDefaults['settings.json']
|
|
203
|
+
3. newTpl = read templates/.claude/settings.json (current)
|
|
204
|
+
4. existing = read user's .claude/settings.json
|
|
205
|
+
5. cleaned = removeOrphanHooks(existing, oldTpl, newTpl)
|
|
206
|
+
6. merged = mergeSettingsJson(cleaned, newTpl)
|
|
207
|
+
7. write merged to .claude/settings.json
|
|
208
|
+
8. write new manifest with shippedDefaults = newTpl (for next upgrade)
|
|
209
|
+
|
|
210
|
+
removeOrphanHooks(existing, oldTpl, newTpl):
|
|
211
|
+
if !oldTpl: return existing (first upgrade, no baseline yet)
|
|
212
|
+
oldCmds = extractCommands(oldTpl)
|
|
213
|
+
newCmds = extractCommands(newTpl)
|
|
214
|
+
orphans = oldCmds.filter(c => !newCmds.has(c))
|
|
215
|
+
return existing with orphan commands filtered out
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Properties
|
|
219
|
+
|
|
220
|
+
- **User-added** (never in `oldTpl`) → preserved
|
|
221
|
+
- **Removed default** (was in `oldTpl`, gone from `newTpl`, present in user's) → removed
|
|
222
|
+
- **Unchanged default** (in all three) → kept
|
|
223
|
+
- **Renamed/modified default** (cmd-string changed) → old orphaned, new added via merge
|
|
224
|
+
|
|
225
|
+
### Backward compat
|
|
226
|
+
|
|
227
|
+
If manifest has no `shippedDefaults` (pre-1.4.3 install) — orphan detection
|
|
228
|
+
skipped on first upgrade. Manifest gets populated for future upgrades.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Statusline architecture (v1.5.0)
|
|
233
|
+
|
|
234
|
+
**Goal:** single-script multi-line dashboard.
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
┌─ statusline.cjs (entry, ~330 LOC) ─────────────────────────┐
|
|
238
|
+
│ │
|
|
239
|
+
│ 1. main() │
|
|
240
|
+
│ ├── parseManifest() ───────► .p-replicator.json │
|
|
241
|
+
│ ├── parseState() ──────────► .claude/.p-replicator-state.json (with stale check) │
|
|
242
|
+
│ ├── parseRoadmap() ────────► .claude/feature-roadmap.json │
|
|
243
|
+
│ ├── parseSparcDocs() ──────► docs/PRD.md, ..., ADR.md │
|
|
244
|
+
│ ├── parseValidationScore() ► docs/validation-report.md (regex) │
|
|
245
|
+
│ ├── parseAdrs() ───────────► docs/ADR.md OR docs/adr/ OR docs/ddd/adr/ │
|
|
246
|
+
│ ├── parsePlans() ──────────► docs/plans/*.md │
|
|
247
|
+
│ ├── parseInsights() ───────► .claude/insights/index.md │
|
|
248
|
+
│ ├── parseToolkit() ────────► filesystem walks │
|
|
249
|
+
│ ├── parseSettingsStatus() ─► deep-equals current vs shippedDefaults │
|
|
250
|
+
│ ├── parseMcpServers() ─────► .mcp.json │
|
|
251
|
+
│ ├── parseKeysarium() ──────► .keysarium.json existence │
|
|
252
|
+
│ ├── parseDomain() ─────────► CLAUDE.md keyword grep │
|
|
253
|
+
│ ├── parseLastHarvest() ────► TOOLKIT_HARVEST.md mtime │
|
|
254
|
+
│ └── parseLastTest() ───────► .claude/.last-test.json (optional) │
|
|
255
|
+
│ │
|
|
256
|
+
│ 2. lines = [ │
|
|
257
|
+
│ buildHeader(manifest), │
|
|
258
|
+
│ buildPipeline(state), │
|
|
259
|
+
│ buildRoadmap(roadmap, domain), │
|
|
260
|
+
│ buildDocs(sparc, validation, plans, adrs, lastHarvest), │
|
|
261
|
+
│ buildToolkit(toolkit, expected), │
|
|
262
|
+
│ buildStatus(insights, lastTest, mcpServers, settingsStatus, keysarium), │
|
|
263
|
+
│ ] │
|
|
264
|
+
│ │
|
|
265
|
+
│ 3. process.stdout.write(lines.join('\n') + '\n') │
|
|
266
|
+
│ │
|
|
267
|
+
└─────────────────────────────────────────────────────────────┘
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**Defensive design:** every `parse*` function wrapped in `safeRun()` with
|
|
271
|
+
fallback. One parse error → fallback value, other sections work.
|
|
272
|
+
|
|
273
|
+
**State-file flow:**
|
|
274
|
+
|
|
275
|
+
```
|
|
276
|
+
command (e.g., /run) ──Bash──► node .claude/hooks/state-update.cjs --command /run --phase loop --progress 0.4
|
|
277
|
+
│
|
|
278
|
+
▼
|
|
279
|
+
.claude/.p-replicator-state.json (atomic write)
|
|
280
|
+
▲
|
|
281
|
+
│
|
|
282
|
+
Claude Code prompt ────────► node .claude/hooks/statusline.cjs
|
|
283
|
+
│
|
|
284
|
+
▼
|
|
285
|
+
reads state, computes heuristics, renders 6 lines
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**Stale check:** state older than 30 minutes → ignored (Pipeline section
|
|
289
|
+
shows `idle`).
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## Test infrastructure
|
|
294
|
+
|
|
295
|
+
**Suite:** 105 tests, 36 suites, ~25 sec runtime.
|
|
296
|
+
|
|
297
|
+
| Layer | File | Coverage |
|
|
298
|
+
|---|---|---|
|
|
299
|
+
| **Unit** | `tests/unit/utils.test.js` (54 tests) | Pure functions: createManifest, mergeSettingsJson, removeOrphanHooks, getItemRelativePath, parseToolkit logic |
|
|
300
|
+
| **E2E** | `tests/e2e/lifecycle.test.js` (48 tests) | Full CLI lifecycle, hooks installation, settings merge edge cases, statusline output, --feature-branches docs |
|
|
301
|
+
| **Snapshot** | `tests/snapshot/templates.test.js` (3 tests) | SHA-256 baseline of all 115 files in `templates/` |
|
|
302
|
+
|
|
303
|
+
**Meta-tests:** verify consistency between documents:
|
|
304
|
+
- `replicate-pipeline.md` mentions every pre-shipped command (no orphan in rule)
|
|
305
|
+
- `replicate.md` Phase 3 doesn't claim "Generate `<pre-shipped>.md`" (no spec drift)
|
|
306
|
+
|
|
307
|
+
**Snapshot baseline** regenerated via `npm run snapshot:baseline` after
|
|
308
|
+
intentional template changes.
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Module composition: `view()` syntax (Claude Code-specific)
|
|
313
|
+
|
|
314
|
+
Skills use `view()` for cross-skill loading at runtime:
|
|
315
|
+
|
|
316
|
+
```markdown
|
|
317
|
+
view() .claude/skills/explore/SKILL.md
|
|
318
|
+
view() .claude/skills/explore/references/questioning-techniques.md
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Claude Code resolves these references dynamically: when executing a skill,
|
|
322
|
+
the LLM reads referenced files at the moment of use. This lets skill A
|
|
323
|
+
delegate to skill B without duplicating content.
|
|
324
|
+
|
|
325
|
+
**Limitation:** only Claude Code supports this runtime mechanism. For
|
|
326
|
+
other platforms (Codex, OpenCode), skill content must be **inlined**
|
|
327
|
+
(compiled into command markdown) at install time. See
|
|
328
|
+
`MULTIPLATFORM_ROADMAP.md`.
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## Pipeline: `/replicate` phases
|
|
333
|
+
|
|
334
|
+
```
|
|
335
|
+
INPUT (idea or company name)
|
|
336
|
+
│
|
|
337
|
+
▼
|
|
338
|
+
Phase 0: PRODUCT DISCOVERY (optional)
|
|
339
|
+
│ skill: reverse-engineering-unicorn
|
|
340
|
+
│ output: docs/00_product_discovery.md
|
|
341
|
+
▼
|
|
342
|
+
Phase 1: PLANNING
|
|
343
|
+
│ skill: sparc-prd-mini (internally: explore + research + solve + 5 SPARC phases)
|
|
344
|
+
│ output: docs/PRD.md, Architecture.md, Pseudocode.md, ... (11 docs)
|
|
345
|
+
▼
|
|
346
|
+
Phase 2: VALIDATION (5-agent swarm)
|
|
347
|
+
│ skill: requirements-validator
|
|
348
|
+
│ output: docs/validation-report.md, docs/test-scenarios.md (BDD)
|
|
349
|
+
│ verdict: 🟢 READY / 🟡 CAVEATS / 🔴 NEEDS WORK (max 3 retries)
|
|
350
|
+
▼
|
|
351
|
+
Phase 3: TOOLKIT GENERATION (project-specific only)
|
|
352
|
+
│ skill: cc-toolkit-generator-enhanced (9 modules)
|
|
353
|
+
│ output: project agents (planner, code-reviewer, architect),
|
|
354
|
+
│ project rules (security, coding-style, testing),
|
|
355
|
+
│ project skills (project-context, coding-standards),
|
|
356
|
+
│ CLAUDE.md, feature-roadmap.json, DEVELOPMENT_GUIDE.md
|
|
357
|
+
▼
|
|
358
|
+
Phase 4: FINALIZE
|
|
359
|
+
│ output: docker-compose.yml, Dockerfile, .gitignore
|
|
360
|
+
│ action: git commit
|
|
361
|
+
▼
|
|
362
|
+
DONE — project ready for /start or /run
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## Next
|
|
368
|
+
|
|
369
|
+
- [06_troubleshooting.md](./06_troubleshooting.md) — common issues
|
|
370
|
+
- [07_changelog.md](./07_changelog.md) — version history
|