@dzhechkov/p-replicator 1.5.18 → 1.9.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 (89) hide show
  1. package/.dz-manifest.json +173 -57
  2. package/CHANGELOG.md +262 -0
  3. package/MULTIPLATFORM_ROADMAP.md +1 -1
  4. package/README/eng/01_quickstart.md +2 -2
  5. package/README/eng/02_user_guide.md +1 -1
  6. package/README/eng/03_admin_guide.md +2 -2
  7. package/README/eng/05_architecture.md +6 -2
  8. package/README/eng/README.md +1 -1
  9. package/README/ru/01_quickstart.md +2 -2
  10. package/README/ru/02_user_guide.md +1 -1
  11. package/README/ru/03_admin_guide.md +2 -2
  12. package/README/ru/05_architecture.md +1 -1
  13. package/README/ru/README.md +1 -1
  14. package/README/ru/html/index.html +7 -7
  15. package/README.md +139 -16
  16. package/bin/cli.js +0 -0
  17. package/package.json +11 -10
  18. package/sbom.json +346 -56
  19. package/src/utils.js +4 -0
  20. package/templates/.claude/agents/doc-validator.md +2 -1
  21. package/templates/.claude/agents/product-discoverer.md +1 -1
  22. package/templates/.claude/commands/next.md +16 -0
  23. package/templates/.claude/commands/replicate.md +183 -9
  24. package/templates/.claude/commands/start.md +19 -1
  25. package/templates/.claude/hooks/autocommit-insights.cjs +95 -10
  26. package/templates/.claude/hooks/autocommit-plans.cjs +95 -10
  27. package/templates/.claude/hooks/autocommit-roadmap.cjs +96 -13
  28. package/templates/.claude/hooks/check-docs-complete.cjs +174 -0
  29. package/templates/.claude/hooks/check-growth-trace.cjs +191 -0
  30. package/templates/.claude/hooks/check-ports.cjs +232 -0
  31. package/templates/.claude/hooks/session-insights.cjs +13 -1
  32. package/templates/.claude/hooks/state-update.cjs +13 -1
  33. package/templates/.claude/hooks/statusline.cjs +145 -18
  34. package/templates/.claude/rules/docker-ports.md +123 -0
  35. package/templates/.claude/rules/replicate-pipeline.md +19 -6
  36. package/templates/.claude/rules/skill-interface-protocol.md +9 -0
  37. package/templates/.claude/settings.json +5 -5
  38. package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/01-detect-parse.md +57 -14
  39. package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/02-analyze-map.md +9 -7
  40. package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/03-generate-p0.md +6 -4
  41. package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/08-skill-composition.md +2 -2
  42. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/enhanced-recommendations.md +6 -4
  43. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/extended-mapping.md +2 -2
  44. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/ddd-agents.md +2 -2
  45. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/ddd-skills.md +1 -1
  46. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/enhanced-claude-md.md +2 -2
  47. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/feature-lifecycle.md +2 -2
  48. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/feature-suggestions.md +11 -2
  49. package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/start-command.md +1 -1
  50. package/templates/.claude/skills/requirements-validator/SKILL.md +59 -0
  51. package/templates/.claude/skills/requirements-validator/references/scoring-system.md +28 -0
  52. package/templates/.claude/skills/reverse-engineering-unicorn/modules/01-intelligence.md +4 -4
  53. package/templates/.claude/skills/reverse-engineering-unicorn/modules/02-product-customers.md +2 -2
  54. package/templates/.claude/skills/reverse-engineering-unicorn/modules/025-cjm-prototype.md +9 -1
  55. package/templates/.claude/skills/reverse-engineering-unicorn/modules/03-market-competition.md +3 -3
  56. package/templates/.claude/skills/reverse-engineering-unicorn/modules/04-business-finance.md +3 -3
  57. package/templates/.claude/skills/reverse-engineering-unicorn/modules/05-growth-engine.md +132 -12
  58. package/templates/.claude/skills/reverse-engineering-unicorn/modules/06-playbook-synthesis.md +1 -1
  59. package/templates/.claude/skills/sparc-prd-mini/SKILL.md +152 -10
  60. package/tests/e2e/lifecycle.test.js +21 -10
  61. package/tests/snapshot/baseline.json +42 -38
  62. package/tests/snapshot/update-baseline.js +2 -1
  63. package/tests/unit/adr-decision-coverage.test.js +137 -0
  64. package/tests/unit/adr-scanner-contract.test.js +108 -0
  65. package/tests/unit/autocommit-deletion.test.js +242 -0
  66. package/tests/unit/check-docs-complete.test.js +249 -0
  67. package/tests/unit/check-growth-trace.test.js +188 -0
  68. package/tests/unit/check-ports.test.js +184 -0
  69. package/tests/unit/db-port-rule.test.js +216 -0
  70. package/tests/unit/detect-parse-anchor.test.js +109 -0
  71. package/tests/unit/external-dependency-check.test.js +209 -0
  72. package/tests/unit/growth-axes-and-compliance.test.js +169 -0
  73. package/tests/unit/growth-gate-conditional.test.js +122 -0
  74. package/tests/unit/growth-module-b2b-gate.test.js +122 -0
  75. package/tests/unit/growth-requirements-bridge.test.js +127 -0
  76. package/tests/unit/hooks-project-anchored.test.js +223 -0
  77. package/tests/unit/hooks-report-failures.test.js +207 -0
  78. package/tests/unit/module-copy-identity.test.js +76 -0
  79. package/tests/unit/pipeline-file-ownership.test.js +95 -0
  80. package/tests/unit/roadmap-one-schema.test.js +179 -0
  81. package/tests/unit/skill-paths-prebaked.test.js +174 -0
  82. package/tests/unit/sparc-reconciliation.test.js +117 -0
  83. package/tests/unit/spec-pseudocode-traceability.test.js +146 -0
  84. package/tests/unit/statusline-honest-labels.test.js +178 -0
  85. package/tests/unit/statusline-two-roots.test.js +237 -0
  86. package/tests/unit/sync-templates-guard.test.js +209 -0
  87. package/tests/unit/utils.test.js +2 -2
  88. package/tests/unit/validation-gate-teeth.test.js +158 -0
  89. package/LICENSE +0 -21
package/src/utils.js CHANGED
@@ -308,6 +308,7 @@ const COMPONENTS = {
308
308
  'git-workflow': 'Commit/push discipline, branch strategy, semantic messages',
309
309
  'insights-capture': 'When and how to capture development insights to knowledge base',
310
310
  'feature-lifecycle': '/feature phases (PLAN → VALIDATE → IMPLEMENT → REVIEW), checkpoints, scoring',
311
+ 'docker-ports': 'Правило №0: storage ports are not published outward; loopback bind is the exception',
311
312
  },
312
313
  },
313
314
  settings: {
@@ -332,6 +333,9 @@ const COMPONENTS = {
332
333
  'autocommit-plans': 'Auto-commit docs/plans/ on Stop hook',
333
334
  'statusline': 'Multi-line dashboard (pipeline, roadmap, toolkit) for Claude Code statusLine',
334
335
  'state-update': 'Argv-driven helper for pipeline commands to publish current command + phase + progress',
336
+ 'check-ports': 'Enforce docker-ports Правило №0 against a real compose (invoke deliberately; exits 0/1/2)',
337
+ 'check-growth-trace': 'Did the M5 growth seed reach docs/Specification.md (invoke deliberately; exits 0/1/2)',
338
+ 'check-docs-complete': 'Are Phase-1 documents written and placeholder-free, before the Phase-2 swarm (invoke deliberately; exits 0/1/2)',
335
339
  },
336
340
  },
337
341
  // ─── Project-generated groups (created by /replicate Phase 3) ───────────
@@ -15,7 +15,7 @@ Read from: `.claude/skills/requirements-validator/SKILL.md`
15
15
 
16
16
  ## Swarm Strategy
17
17
 
18
- Launch 5 parallel validation agents using Task tool:
18
+ Launch 6 parallel validation agents using Task tool:
19
19
 
20
20
  | Agent | Scope | Criteria | Tool |
21
21
  |-------|-------|----------|------|
@@ -24,6 +24,7 @@ Launch 5 parallel validation agents using Task tool:
24
24
  | `validator-architecture` | Architecture.md | Target constraints compliance, completeness | Task |
25
25
  | `validator-pseudocode` | Pseudocode.md | Story coverage, implementability | Task |
26
26
  | `validator-coherence` | Cross-document | Consistency, no contradictions | Task |
27
+ | `validator-dependencies` | `Architecture.md` → `## External Dependencies` | Every external capability a requirement relies on has a verdict and, where CONFIRMED, evidence that names that capability | Task |
27
28
 
28
29
  ## Scoring System
29
30
 
@@ -19,7 +19,7 @@ Read from: `.claude/skills/reverse-engineering-unicorn/SKILL.md`
19
19
  | M2: Product & Customers | Always | JTBD, Value Prop, segments |
20
20
  | M3: Market & Competition | Always | TAM/SAM, competitors, Blue Ocean Canvas |
21
21
  | M4: Business & Finance | If monetization model needed | Unit economics |
22
- | M5: Growth Engine | If B2C/PLG | Channels, integrations, viral loops |
22
+ | M5: Growth Engine | If acquisition/adoption in scope (incl. B2B) | Channels, integrations, viral loops |
23
23
 
24
24
  ## Output Format
25
25
 
@@ -81,6 +81,22 @@ Blocked: <count> (run /next blocked to see)
81
81
 
82
82
  ### Schema fields (post v1.5.0)
83
83
 
84
+ **This table is THE schema for `.claude/feature-roadmap.json`.** It is the only one: any other
85
+ document that shows the file's fields must point here rather than restate them, because two
86
+ descriptions of one file are two chances to be wrong and only one of them can be right.
87
+
88
+ **Who creates the file** — both answers are true, in different modes, and this is the one place that
89
+ says so:
90
+
91
+ - after `/replicate`, Phase 3 generates it from the PRD MVP scope;
92
+ - in Mode 2 (an existing project where `init` added the toolkit and `/replicate` never ran), the user
93
+ writes it by hand if they want batch automation via `/run mvp`; ad-hoc `/feature <id>` needs no
94
+ roadmap at all.
95
+
96
+ `priority` is a CLOSED set — `mvp`, `high`, `medium`, `low`. A value outside it is not a new priority,
97
+ it is a roadmap this toolkit cannot read: the status line counts MVP by `priority === 'mvp'` and will
98
+ mark the file rather than quietly report zero.
99
+
84
100
  | Field | Required | Populated by | Purpose |
85
101
  |-------|----------|--------------|---------|
86
102
  | `id` | yes | initial roadmap generation | Stable kebab-case slug |
@@ -166,9 +166,37 @@ Read the skill: `.claude/skills/reverse-engineering-unicorn/SKILL.md`
166
166
  | M2: Product & Customers | Always | JTBD, Value Prop, segments |
167
167
  | M3: Market & Competition | Always | TAM/SAM, competitors, Blue Ocean |
168
168
  | M4: Business & Finance | If monetization | Unit economics |
169
- | M5: Growth Engine | If B2C/PLG | Channels, integrations |
169
+ | M5: Growth Engine | If acquisition/adoption in scope (incl. B2B) | Channels, integrations |
170
170
 
171
- **Output:** Product Discovery Brief passed as pre-filled context to Phase 1
171
+ M5 is no longer gated on PRODUCT TYPE. The module branches on type itself — *«Если B2B → sales-led
172
+ growth, не product-led»* — so gating it outside disabled the one branch it declares, and every B2B
173
+ project got an empty `### Growth Channels` slot.
174
+
175
+ The condition is APPLICABILITY, not type, and the difference matters in both directions: `Always`
176
+ would over-promise, because M5's outputs are CAC, channels and loops, and an internal tool with no
177
+ acquisition objective has nothing to put in them. So: run it whenever acquisition or adoption is in
178
+ scope — which includes B2B — and skip it when neither is. The cost is visible rather than
179
+ discovered: a B2B run now spends M5's time.
180
+
181
+ Honest limit: line 62 of the module says to *switch* to a sales-led framework; it does not define
182
+ one. A B2B run gets the type-appropriate instruction, not a type-appropriate playbook.
183
+
184
+ **Output — WRITE FIRST, then hand off. Both, in this order:**
185
+
186
+ 1. **Write** the full Product Discovery Brief to `docs/product-discovery-brief.md`.
187
+ 2. **Then** pass it to Phase 1 as pre-filled context, exactly as before.
188
+
189
+ The hand-off is unchanged; the file is an ADDITION. Until this was added the brief existed only in
190
+ the conversation — it made Phase 1 skip its own Phase 0 (`sparc-prd-mini/SKILL.md:993-999`) and then
191
+ vanished, so the M5 growth analysis had no artifact any later step could read. Nothing downstream was
192
+ ignoring it: there was nothing to ignore.
193
+
194
+ The file MUST include M5's `Growth Requirements Seed` table verbatim when M5 ran — that table is the
195
+ only place `FR-GROWTH-nnn` obligations exist before Phase 1 promotes them.
196
+
197
+ **When this file is absent it means Phase 0 did not run** — the `--from-docs` / `--skip-discovery`
198
+ entry skips Phase 0 entirely (see the alternative-entry section). Absence is NOT evidence that the
199
+ project has no growth requirements, and no consumer may read it that way.
172
200
 
173
201
  **Checkpoint:**
174
202
  ```
@@ -240,8 +268,49 @@ Created [N] documents in docs/
240
268
  ═══════════════════════════════════════════════════════════════
241
269
  ```
242
270
 
271
+ ### Прерванный прогон: как продолжить с того места
272
+
273
+ `/replicate` — интерактивный конвейер с четырьмя чекпоинтами, и продолжить его можно **уже сейчас,
274
+ без всякой новой машинерии**. Три сигнала, каждый существует независимо от этого раздела:
275
+
276
+ | Вопрос | Чем отвечается |
277
+ |---|---|
278
+ | До какой фазы дошли? | `git log --oneline` — после КАЖДОЙ фазы делается свой коммит (`docs: SPARC…`, `docs: validation report…`, `feat: Claude Code toolkit…`, `chore: initial project setup…`) |
279
+ | Документы Фазы 1 дописаны? | `node .claude/hooks/check-docs-complete.cjs .` — `0` дописаны, `1` названо, чего не хватает, `2` Фаза 1 не запускалась |
280
+ | Тулкит Фазы 3 сгенерирован? | `npx @dzhechkov/p-replicator verify` — раздел «Post-/replicate» |
281
+
282
+ **Как продолжить:** посмотрите последний коммит фазы, затем скажите `/replicate` прямым текстом:
283
+ *«продолжай с Фазы 3, Фазы 0-2 уже сделаны»*. Конвейер интерактивный — человек на чекпоинте и есть
284
+ механизм возобновления.
285
+
286
+ **Почему здесь нет автоматического определения фазы.** Оно рассматривалось (бэклог `58575b07`) и
287
+ сознательно НЕ реализовано: три сигнала выше уже дают ответ, а свежая логика ветвления в
288
+ интерактивном конвейере — это то, что может сработать неверно ровно тогда, когда прогон и так пошёл
289
+ не по плану. Запись решения важнее самого решения: если вы вернётесь к этому вопросу, начинайте с
290
+ того, что перечисленного выше оказалось недостаточно.
291
+
243
292
  ### Phase 2: VALIDATION
244
293
 
294
+ **Шаг 2.0 — ДЕТЕРМИНИРОВАННАЯ ПРОВЕРКА ПОЛНОТЫ. Выполняется ПЕРВОЙ, до запуска роя.**
295
+
296
+ ```bash
297
+ node .claude/hooks/check-docs-complete.cjs .
298
+ ```
299
+
300
+ | Код | Что делать |
301
+ |:---:|---|
302
+ | `0` | продолжайте — рой валидации запускается |
303
+ | `1` | **НЕ запускайте рой.** Вернитесь в Фазу 1 и допишите названные документы |
304
+ | `2` | проверка не выполнена — почините вызов и повторите; это НЕ «всё в порядке» |
305
+
306
+ Причина, по которой шаг стоит здесь, а не внутри роя: существование файла, его пустота и
307
+ незаполненный шаблон решаются сорока строками кода. Отправлять на этот вопрос рой агентов — значит
308
+ платить вероятностной проверкой за то, что решается детерминированно. Рою остаётся то, ради чего он
309
+ и нужен: тестируемость, полнота требований, реализуемость.
310
+
311
+ Ограничение, которое проверка печатает сама: она доказывает, что документы НАПИСАНЫ, а не что они
312
+ верны. Верность — работа роя.
313
+
245
314
  Read the skill: `.claude/skills/requirements-validator/SKILL.md`
246
315
 
247
316
  **Goal:** Verify all documentation for completeness, testability, and implementation readiness.
@@ -255,6 +324,10 @@ Read the skill: `.claude/skills/requirements-validator/SKILL.md`
255
324
  | `validator-architecture` | Architecture.md | Target constraints, completeness |
256
325
  | `validator-pseudocode` | Pseudocode.md | Story coverage, implementability |
257
326
  | `validator-coherence` | Cross-document | Consistency, no contradictions |
327
+ | `validator-dependencies` | `Architecture.md` → `## External Dependencies` | Every external capability a requirement relies on has a verdict and, where CONFIRMED, evidence that names that capability |
328
+
329
+ The sixth lens is the only one that looks OUTSIDE the documents. The other five compare our own
330
+ output with our own output, which cannot discover that a service does not do what we assumed.
258
331
 
259
332
  **Process (iterative, max 3 iterations):**
260
333
 
@@ -270,7 +343,16 @@ Read the skill: `.claude/skills/requirements-validator/SKILL.md`
270
343
  - Happy path (1-2), Error handling (2-3), Edge cases (1-2), Security
271
344
  - Save as `docs/test-scenarios.md`
272
345
 
273
- **Save validation report:** `docs/validation-report.md`
346
+ **Save validation report:** `docs/validation-report.md`. Its **first line** must be exactly one of
347
+
348
+ ```
349
+ **Verdict:** 🟢 READY
350
+ **Verdict:** 🟡 CAVEATS
351
+ **Verdict:** 🔴 NEEDS WORK
352
+ ```
353
+
354
+ and no other line in the file may begin with `**Verdict:**`. Phase 3 reads that one line and nothing
355
+ else — an unanchored verdict is a verdict Phase 3 can find in an example or a quoted history.
274
356
 
275
357
  Git commit: `docs: validation report and BDD scenarios`
276
358
 
@@ -278,9 +360,85 @@ Git commit: `docs: validation report and BDD scenarios`
278
360
 
279
361
  | Verdict | Conditions | Action |
280
362
  |---------|-----------|--------|
281
- | 🟢 READY | All scores ≥50, average ≥70, no contradictions | → Phase 3 |
282
- | 🟡 CAVEATS | Warnings exist, no blocked, limitations described | → Phase 3 with notes |
283
- | 🔴 NEEDS WORK | Blocked items exist | → Return to Phase 1 |
363
+ | 🟢 READY | All scores ≥50, average ≥70, no contradictions, **no item on the blocking floor**, **no external dependency `UNCONFIRMED` or `CONTRADICTED`** | → Phase 3 |
364
+ | 🟡 CAVEATS | Warnings exist, no blocked, limitations described, **every `UNCONFIRMED` dependency NAMED row by row** | → Phase 3 with notes |
365
+ | 🔴 NEEDS WORK | Blocked items exist, **or any item has `Testable = 0` or `Completeness = 0`**, **or any external dependency is `CONTRADICTED`** | → Return to Phase 1 |
366
+
367
+ **The blocking floor** (`skills/requirements-validator/references/scoring-system.md` → "Blocking
368
+ floor"): the weakest link decides, never the average. An item with no acceptance criteria totals
369
+ 72/100 and would otherwise read as READY.
370
+
371
+ **Шаг 2.9 — ПОКРЫТИЕ РЕШЕНИЙ (обязательный, до чекпойнта).**
372
+
373
+ Every decision in `docs/ADR.md` carries an id `ADR-<nnn>` — three digits, assigned in order, never
374
+ reused even after a decision is superseded.
375
+
376
+ **Where to look, named file by file.** «Across the docs» is not an instruction. Search EXACTLY these,
377
+ and no others:
378
+
379
+ ```
380
+ docs/PRD.md · docs/Solution_Strategy.md · docs/Specification.md · docs/Pseudocode.md
381
+ docs/Architecture.md · docs/Refinement.md · docs/Completion.md · docs/C4_Diagrams.md
382
+ ```
383
+
384
+ **Two files are EXCLUDED, and the first exclusion is the one that makes this check work at all:**
385
+
386
+ - `docs/ADR.md` itself. Its own headings contain every id, so counting them would make every decision
387
+ appear named and the check would pass by construction — always, on any project.
388
+ - `docs/validation-report.md`. This step WRITES into it. Counting it would let the previous run's
389
+ output satisfy the next run: the check would start proving itself.
390
+
391
+ **What counts as a mention.** The exact token `ADR-<nnn>`, case-sensitive, in one of the files above.
392
+ Not a title, not a paraphrase, not a link whose text merely resembles it. One occurrence is enough;
393
+ repeats are not counted twice.
394
+
395
+ **A superseded decision needs no current mention.** If a decision's own entry says it is superseded,
396
+ list it in a third column rather than as a gap — it was replaced, not forgotten.
397
+
398
+ Write a `## Decision Coverage` block into `docs/validation-report.md` — **in every case**, including
399
+ the one where everything is covered, because an absent block and a block saying "all covered" are
400
+ indistinguishable to the next reader:
401
+
402
+ ```
403
+ ## Decision Coverage
404
+
405
+ Decisions in docs/ADR.md: [N] · named downstream: [M] · superseded: [S]
406
+
407
+ Recorded but named nowhere:
408
+ | Decision | Title |
409
+ |---|---|
410
+ | ADR-… | … |
411
+
412
+ Named downstream but absent from docs/ADR.md:
413
+ | Reference | Where |
414
+ |---|---|
415
+ | ADR-… | docs/… |
416
+ ```
417
+
418
+ **Both tables are required, and both may be the single word `none`.** A one-way check is half a
419
+ check: without the second table, a document referring to `ADR-009` that nobody ever wrote reads
420
+ exactly like coverage. `none` is written out rather than left blank, because an empty table and a
421
+ forgotten table look identical.
422
+
423
+ **Three states of the ADR file, and each has its own line — the block is written in all three:**
424
+
425
+ | State | What to write in the block |
426
+ |---|---|
427
+ | `docs/ADR.md` absent | *"docs/ADR.md is absent, so no decision ids were collected from it. The second table below still applies."* |
428
+ | present but containing no `ADR-<nnn>` id | *"docs/ADR.md exists but records no decision ids."* |
429
+ | present with ids | the counts and the two tables above |
430
+
431
+ Note what the first line does NOT say. It says the FILE is absent — not that the project recorded no
432
+ architectural decisions. Decisions may live somewhere this pipeline does not look, and claiming
433
+ otherwise would be asserting something this step cannot see. **In all three states the second table
434
+ still runs**: a downstream reference to a decision that does not exist is a defect whether or not an
435
+ ADR file was ever written.
436
+
437
+ **What this establishes, and what it does not.** It establishes that a decision is NAMED somewhere
438
+ downstream. It does NOT establish that the decision was implemented — no comparison of identifiers
439
+ can. So it catches *"the decision was written down and then forgotten"*; it does not catch *"someone
440
+ mentioned it in a sentence and built something else"*. Say so here rather than letting a later reader
441
+ assume the stronger thing.
284
442
 
285
443
  **Checkpoint:**
286
444
  ```
@@ -295,6 +453,18 @@ Iterations: N/3
295
453
 
296
454
  ### Phase 3: TOOLKIT GENERATION
297
455
 
456
+ **Precondition — check it before reading anything else. The toolkit is built ON the validated docs,
457
+ so an unvalidated input is not a smaller toolkit, it is a wrong one:**
458
+
459
+ 1. `docs/validation-report.md` must EXIST. If it is absent, Phase 2 did not run or did not finish —
460
+ do NOT generate anything; return to Phase 2 and say so.
461
+ 2. Its **first line** must be `**Verdict:**` followed by 🟢 READY or 🟡 CAVEATS. Read ONLY that
462
+ line: a verdict word anywhere else in the document — an example, a quoted history, a summary —
463
+ is NOT the verdict. On 🔴 NEEDS WORK, on no such first line, or on more than one line starting
464
+ with `**Verdict:**`, do NOT generate anything; return to Phase 2 and say which of the four it was.
465
+ 3. On 🟡, carry the report's stated limitations into the toolkit's own notes — a caveat that stops at
466
+ the phase boundary was never recorded.
467
+
298
468
  Read the skill: `.claude/skills/cc-toolkit-generator-enhanced/SKILL.md`
299
469
 
300
470
  **Goal:** Generate project-specific Claude Code instruments IN-PLACE.
@@ -305,7 +475,7 @@ Read the skill: `.claude/skills/cc-toolkit-generator-enhanced/SKILL.md`
305
475
  - **Pre-shipped by `npx p-replicator init` — do NOT overwrite or regenerate:**
306
476
  - All 10 skills in `.claude/skills/`
307
477
  - All 11 commands: `/replicate`, `/harvest`, `/start`, `/plan`, `/feature`, `/go`, `/run`, `/next`, `/myinsights`, `/docs`, `/deploy`
308
- - All 5 rules: `replicate-pipeline`, `skill-interface-protocol`, `git-workflow`, `insights-capture`, `feature-lifecycle`
478
+ - All 6 rules: `replicate-pipeline`, `skill-interface-protocol`, `git-workflow`, `insights-capture`, `feature-lifecycle`, `docker-ports`
309
479
  - All 4 pipeline agents: `replicate-coordinator`, `product-discoverer`, `doc-validator`, `harvest-coordinator`
310
480
  - `.claude/settings.json` + cross-platform Node hook scripts in `.claude/hooks/`
311
481
  - Phase 3 generates ONLY project-specific artifacts derived from SPARC docs (see below).
@@ -369,11 +539,15 @@ Git commit: `feat: Claude Code toolkit for [project-name]`
369
539
 
370
540
  **Generate scaffold files:**
371
541
 
372
- 1. `docker-compose.yml` — from Architecture.md services
542
+ 1. `docker-compose.yml` — from Architecture.md services, **if not exists** (a run over a tree that already has one must not discard it either — the guard is symmetric)
373
543
  2. `Dockerfile` — from Architecture.md tech stack
374
544
  3. `.gitignore` — if not exists
375
545
  4. `docs/features/` — create empty directory for future features
376
546
 
547
+ > `docker-compose.yml`, `.gitignore` и `README.md` позже читает `/start` (Phase 1). Он обязан их СОХРАНИТЬ и
548
+ > менять только по названной причине — правило `if not exists` записано на его стороне. Оба конца
549
+ > рекомендованной последовательности договорены в тексте, а не совпадают по случайности.
550
+
377
551
  **Git operations:**
378
552
  ```bash
379
553
  git add .
@@ -467,7 +641,7 @@ PRINCIPLE: User enters keys via UI → stored encrypted in browser → NEVER sen
467
641
 
468
642
  ### NEVER
469
643
  - Don't duplicate explore/research phases — sparc-prd-mini does this internally
470
- - Never skip validation — toolkit is built on validated docs
644
+ - Never skip validation — toolkit is built on validated docs. The enforcing check is Phase 3's precondition, which reads the first `**Verdict:**` line of `docs/validation-report.md`
471
645
  - Never use base cc-toolkit-generator — only enhanced version
472
646
  - Don't overwrite template files (generic commands, rules, settings.json)
473
647
 
@@ -33,7 +33,25 @@ via `Task` tool, commits per logical change for safe error recovery.
33
33
  2. **Generate root configs:** `package.json` (monorepo workspaces),
34
34
  `docker-compose.yml`, `.env.example`, `.gitignore`, `tsconfig.base.json`.
35
35
 
36
- 3. **Git commit:** `chore: project root configuration`
36
+ **Два из них `/replicate` уже мог записать.** Штатная последовательность — `/replicate`, затем
37
+ `/start`, а Phase 4 команды `/replicate` создаёт `docker-compose.yml` и `.gitignore` из того же
38
+ `Architecture.md`. Значит это не два разных файла, а один, выведенный дважды, и слепая
39
+ перегенерация молча выбросит всё, что между двумя запусками правили руками.
40
+
41
+ Для `docker-compose.yml`, `.gitignore` и `README.md` действует правило `if not exists`:
42
+ - файла НЕТ → создать, как обычно;
43
+ - файл ЕСТЬ → **не перегенерировать**. Прочитать его и оставить; внести только МИНИМАЛЬНУЮ
44
+ точечную правку, которой требует названное требование из документов (появился пакет, которого
45
+ в файле нет; порт из `Architecture.md` не проброшен). Всё, что к этому требованию не относится,
46
+ сохраняется без изменений.
47
+ - Перед коммитом ПОСМОТРЕТЬ ДИФ и НАЗВАТЬ каждый изменённый фрагмент и его причину. «Добавил
48
+ недостающий порт», под которым лежит переписанный целиком файл, — это и есть та потеря,
49
+ которую правило предотвращает; диф отличает одно от другого, а обещание не отличает.
50
+
51
+ 3. **Git commit:** `chore: project root configuration`. Если на шаге 2 менялся уже существовавший
52
+ `docker-compose.yml`, `.gitignore` или `README.md` — тело коммита перечисляет каждый изменённый
53
+ фрагмент и причину. Фиксированный заголовок не отменяет этого перечня: заголовок говорит, ЧТО за
54
+ шаг, тело — что именно он тронул в чужом файле.
37
55
 
38
56
  ### Phase 2: Packages (PARALLEL via Task tool ⚡)
39
57
 
@@ -10,30 +10,115 @@ const fs = require('node:fs');
10
10
  const path = require('node:path');
11
11
  const { execFileSync } = require('node:child_process');
12
12
 
13
- const TARGET_DIR = path.resolve(process.cwd(), '.claude', 'insights');
14
- const RELATIVE = path.relative(process.cwd(), TARGET_DIR);
15
- const SILENT = { stdio: 'ignore' };
13
+ // The project root, never the process cwd: a `cd` inside any tool call moves cwd for the rest of
14
+ // the session, and these hooks are non-blocking, so a wrong anchor fails SILENTLY. CLAUDE_PROJECT_DIR
15
+ // first the host is authoritative about what the project is. `__dirname` second: a hook always
16
+ // lives at <project>/.claude/hooks/<x>.cjs, so its own location settles the root with no cooperation
17
+ // from anyone, which is what keeps this working when the variable is absent (hand-run, older host).
18
+ const ENV_ROOT = process.env.CLAUDE_PROJECT_DIR;
19
+ // isAbsolute, not just truthy: a RELATIVE value would still be resolved against the drifting
20
+ // cwd, which is the very bug this anchor exists to remove.
21
+ const ROOT = (ENV_ROOT && path.isAbsolute(ENV_ROOT))
22
+ ? ENV_ROOT
23
+ : path.resolve(__dirname, '..', '..');
24
+
25
+ const TARGET_DIR = path.resolve(ROOT, '.claude', 'insights');
26
+ const RELATIVE = path.relative(ROOT, TARGET_DIR);
27
+ // cwd: ROOT — the paths below are relative to ROOT, so git must run there too.
28
+ // stderr is PIPED, not ignored: on success nothing is printed anyway, and on failure git's
29
+ // own words are the only thing that tells a reader WHY. Discarding them leaves a report that
30
+ // names what failed and not why, which is half a report.
31
+ const SILENT = { stdio: ['ignore', 'pipe', 'pipe'], cwd: ROOT };
32
+
33
+ // What the hook was DOING when it failed. The outer catch also sees staging failures and a
34
+ // missing git binary, and reporting either of those as "could not commit" would send the reader
35
+ // looking in the wrong place.
36
+ let stage = 'start';
16
37
 
17
38
  function git(args) {
18
39
  return execFileSync('git', args, SILENT);
19
40
  }
20
41
 
21
42
  try {
22
- if (!fs.existsSync(TARGET_DIR)) process.exit(0);
23
- try { git(['rev-parse', '--git-dir']); } catch { process.exit(0); }
43
+ // The repository probe comes FIRST now, because the absence check below needs to ask git a
44
+ // question and there is no point asking outside a repository.
45
+ stage = 'inspect the repository';
46
+ try {
47
+ git(['rev-parse', '--git-dir']);
48
+ } catch (probeErr) {
49
+ // "Not a git repository" is the ordinary case and stays silent. Everything else — no git on
50
+ // PATH, dubious ownership, a permission error — is a real failure that used to look exactly
51
+ // like it, which is how a broken machine and an ordinary directory became indistinguishable.
52
+ const why = String((probeErr && probeErr.stderr) || '').trim();
53
+ if (!why || /not a git repository/i.test(why)) process.exit(0);
54
+ throw probeErr;
55
+ }
56
+
57
+ // Absent — but absent HOW? A path that never existed is nothing to do, as before. A path git
58
+ // still tracks is a DELETION, and a deletion is exactly the change this hook exists to record.
59
+ //
60
+ // `git ls-files` is the discriminator, and it must run BEFORE staging: staging a deletion
61
+ // removes the entry from the index, after which the same question answers "not tracked".
62
+ // CORRECTION, measured: `--error-unmatch` also discriminates correctly here, for files AND for
63
+ // directories — 0 for a deleted-but-tracked path, 1 for one that never existed. An earlier note
64
+ // here claimed otherwise; that claim came from measuring it AFTER staging, which is what made it
65
+ // fail, not the directory-ness. `ls-files --` is preferred only because it answers with DATA
66
+ // (empty or not) instead of by throwing, so the ordinary case needs no exception handling.
67
+ //
68
+ // Simply DELETING the existence guard would have been the naive fix and would have broken the
69
+ // reporting that shipped yesterday: `git add` on a path that never existed exits 128, so every
70
+ // session stop in every project without this artifact would print a failure line. MEASURED.
71
+ if (!fs.existsSync(TARGET_DIR)) {
72
+ // A bare `catch { tracked = '' }` here would turn a REAL git failure into "never existed"
73
+ // and skip a deletion that should have been recorded — a silent bypass of the very thing
74
+ // this feature adds. A failure is rethrown into the reporting path; only a genuinely
75
+ // empty answer means the artifact never lived here.
76
+ const tracked = String(git(['ls-files', '--', RELATIVE]) || '').trim();
77
+ if (!tracked) process.exit(0); // never existed here — silent, exactly as before
78
+ }
24
79
 
80
+ stage = 'stage the change';
25
81
  git(['add', '--', RELATIVE]);
26
82
 
27
- let hasDiff = false;
83
+ // Ask git WHAT it staged, not merely WHETHER something changed — same subprocess, strictly
84
+ // more information. Deriving "this is a removal" from whether the path still exists was
85
+ // wrong for a directory: deleting ONE file inside it, or deleting every tracked file while
86
+ // an ignored one keeps the directory present, would have been committed as an update and
87
+ // defeated the very search this feature promises.
88
+ let staged = '';
28
89
  try {
29
- git(['diff', '--cached', '--quiet', '--', RELATIVE]);
90
+ staged = String(git(['diff', '--cached', '--name-status', '--', RELATIVE]) || '').trim();
30
91
  } catch {
31
- hasDiff = true;
92
+ // No HEAD yet (an unborn repository): there is no history to record a removal against.
93
+ staged = '';
32
94
  }
95
+ const deleted = staged.split('\n').some((l) => /^D/.test(l.trim()));
96
+ const hasDiff = staged.length > 0;
33
97
 
34
98
  if (hasDiff) {
35
- git(['commit', '--only', '--', RELATIVE, '-m', 'docs(insights): auto-capture']);
99
+ // -m BEFORE the `--`: everything after `--` is a PATHSPEC, so the old order made git
100
+ // look for files literally named '-m' and 'docs(insights): auto-capture' — it failed every
101
+ // time, from every directory, and this hook exits 0 on failure, so nobody saw it.
102
+ stage = 'commit';
103
+ // A deletion gets its own subject, so the event is findable in `git log` without
104
+ // reading diffs — which is the whole point of recording it.
105
+ git(['commit', '-m', deleted ? 'docs(insights): auto-remove' : 'docs(insights): auto-capture', '--only', '--', RELATIVE]);
36
106
  }
37
- } catch (_err) {
107
+ } catch (err) {
108
+ // Best-effort — never break the Claude session on commit failures. But "not breaking the session"
109
+ // and "saying nothing" are different things, and only the first one is the contract: it is about
110
+ // the EXIT CODE. Silence is what let a permanent defect live here undetected — `-m` after `--`
111
+ // made every commit fail, from every directory, forever, and nothing said so.
112
+ //
113
+ // A held index.lock, a missing user.email, a repository pre-commit hook that rejected the commit,
114
+ // an ignored target: each now costs one line and still exits 0. The ordinary "nothing to commit"
115
+ // path never reaches here, deliberately — a notice that fires when nothing is wrong trains people
116
+ // to ignore notices, and the next real failure scrolls past with them.
117
+ const artifactName = '.claude/insights/';
118
+ const gitSaid = err && err.stderr ? String(err.stderr).trim() : '';
119
+ const detail = (gitSaid || String((err && err.message) || err)).split('\n')
120
+ .map((l) => l.trim()).filter(Boolean)[0] || 'unknown error';
121
+ process.stdout.write('[autocommit-insights] could not ' + stage + ' — ' + artifactName + ': '
122
+ + detail.slice(0, 200) + '\n');
38
123
  process.exit(0);
39
124
  }
@@ -10,30 +10,115 @@ const fs = require('node:fs');
10
10
  const path = require('node:path');
11
11
  const { execFileSync } = require('node:child_process');
12
12
 
13
- const TARGET_DIR = path.resolve(process.cwd(), 'docs', 'plans');
14
- const RELATIVE = path.relative(process.cwd(), TARGET_DIR);
15
- const SILENT = { stdio: 'ignore' };
13
+ // The project root, never the process cwd: a `cd` inside any tool call moves cwd for the rest of
14
+ // the session, and these hooks are non-blocking, so a wrong anchor fails SILENTLY. CLAUDE_PROJECT_DIR
15
+ // first the host is authoritative about what the project is. `__dirname` second: a hook always
16
+ // lives at <project>/.claude/hooks/<x>.cjs, so its own location settles the root with no cooperation
17
+ // from anyone, which is what keeps this working when the variable is absent (hand-run, older host).
18
+ const ENV_ROOT = process.env.CLAUDE_PROJECT_DIR;
19
+ // isAbsolute, not just truthy: a RELATIVE value would still be resolved against the drifting
20
+ // cwd, which is the very bug this anchor exists to remove.
21
+ const ROOT = (ENV_ROOT && path.isAbsolute(ENV_ROOT))
22
+ ? ENV_ROOT
23
+ : path.resolve(__dirname, '..', '..');
24
+
25
+ const TARGET_DIR = path.resolve(ROOT, 'docs', 'plans');
26
+ const RELATIVE = path.relative(ROOT, TARGET_DIR);
27
+ // cwd: ROOT — the paths below are relative to ROOT, so git must run there too.
28
+ // stderr is PIPED, not ignored: on success nothing is printed anyway, and on failure git's
29
+ // own words are the only thing that tells a reader WHY. Discarding them leaves a report that
30
+ // names what failed and not why, which is half a report.
31
+ const SILENT = { stdio: ['ignore', 'pipe', 'pipe'], cwd: ROOT };
32
+
33
+ // What the hook was DOING when it failed. The outer catch also sees staging failures and a
34
+ // missing git binary, and reporting either of those as "could not commit" would send the reader
35
+ // looking in the wrong place.
36
+ let stage = 'start';
16
37
 
17
38
  function git(args) {
18
39
  return execFileSync('git', args, SILENT);
19
40
  }
20
41
 
21
42
  try {
22
- if (!fs.existsSync(TARGET_DIR)) process.exit(0);
23
- try { git(['rev-parse', '--git-dir']); } catch { process.exit(0); }
43
+ // The repository probe comes FIRST now, because the absence check below needs to ask git a
44
+ // question and there is no point asking outside a repository.
45
+ stage = 'inspect the repository';
46
+ try {
47
+ git(['rev-parse', '--git-dir']);
48
+ } catch (probeErr) {
49
+ // "Not a git repository" is the ordinary case and stays silent. Everything else — no git on
50
+ // PATH, dubious ownership, a permission error — is a real failure that used to look exactly
51
+ // like it, which is how a broken machine and an ordinary directory became indistinguishable.
52
+ const why = String((probeErr && probeErr.stderr) || '').trim();
53
+ if (!why || /not a git repository/i.test(why)) process.exit(0);
54
+ throw probeErr;
55
+ }
56
+
57
+ // Absent — but absent HOW? A path that never existed is nothing to do, as before. A path git
58
+ // still tracks is a DELETION, and a deletion is exactly the change this hook exists to record.
59
+ //
60
+ // `git ls-files` is the discriminator, and it must run BEFORE staging: staging a deletion
61
+ // removes the entry from the index, after which the same question answers "not tracked".
62
+ // CORRECTION, measured: `--error-unmatch` also discriminates correctly here, for files AND for
63
+ // directories — 0 for a deleted-but-tracked path, 1 for one that never existed. An earlier note
64
+ // here claimed otherwise; that claim came from measuring it AFTER staging, which is what made it
65
+ // fail, not the directory-ness. `ls-files --` is preferred only because it answers with DATA
66
+ // (empty or not) instead of by throwing, so the ordinary case needs no exception handling.
67
+ //
68
+ // Simply DELETING the existence guard would have been the naive fix and would have broken the
69
+ // reporting that shipped yesterday: `git add` on a path that never existed exits 128, so every
70
+ // session stop in every project without this artifact would print a failure line. MEASURED.
71
+ if (!fs.existsSync(TARGET_DIR)) {
72
+ // A bare `catch { tracked = '' }` here would turn a REAL git failure into "never existed"
73
+ // and skip a deletion that should have been recorded — a silent bypass of the very thing
74
+ // this feature adds. A failure is rethrown into the reporting path; only a genuinely
75
+ // empty answer means the artifact never lived here.
76
+ const tracked = String(git(['ls-files', '--', RELATIVE]) || '').trim();
77
+ if (!tracked) process.exit(0); // never existed here — silent, exactly as before
78
+ }
24
79
 
80
+ stage = 'stage the change';
25
81
  git(['add', '--', RELATIVE]);
26
82
 
27
- let hasDiff = false;
83
+ // Ask git WHAT it staged, not merely WHETHER something changed — same subprocess, strictly
84
+ // more information. Deriving "this is a removal" from whether the path still exists was
85
+ // wrong for a directory: deleting ONE file inside it, or deleting every tracked file while
86
+ // an ignored one keeps the directory present, would have been committed as an update and
87
+ // defeated the very search this feature promises.
88
+ let staged = '';
28
89
  try {
29
- git(['diff', '--cached', '--quiet', '--', RELATIVE]);
90
+ staged = String(git(['diff', '--cached', '--name-status', '--', RELATIVE]) || '').trim();
30
91
  } catch {
31
- hasDiff = true;
92
+ // No HEAD yet (an unborn repository): there is no history to record a removal against.
93
+ staged = '';
32
94
  }
95
+ const deleted = staged.split('\n').some((l) => /^D/.test(l.trim()));
96
+ const hasDiff = staged.length > 0;
33
97
 
34
98
  if (hasDiff) {
35
- git(['commit', '--only', '--', RELATIVE, '-m', 'docs(plan): auto-save']);
99
+ // -m BEFORE the `--`: everything after `--` is a PATHSPEC, so the old order made git
100
+ // look for files literally named '-m' and 'docs(plan): auto-save' — it failed every
101
+ // time, from every directory, and this hook exits 0 on failure, so nobody saw it.
102
+ stage = 'commit';
103
+ // A deletion gets its own subject, so the event is findable in `git log` without
104
+ // reading diffs — which is the whole point of recording it.
105
+ git(['commit', '-m', deleted ? 'docs(plan): auto-remove' : 'docs(plan): auto-save', '--only', '--', RELATIVE]);
36
106
  }
37
- } catch (_err) {
107
+ } catch (err) {
108
+ // Best-effort — never break the Claude session on commit failures. But "not breaking the session"
109
+ // and "saying nothing" are different things, and only the first one is the contract: it is about
110
+ // the EXIT CODE. Silence is what let a permanent defect live here undetected — `-m` after `--`
111
+ // made every commit fail, from every directory, forever, and nothing said so.
112
+ //
113
+ // A held index.lock, a missing user.email, a repository pre-commit hook that rejected the commit,
114
+ // an ignored target: each now costs one line and still exits 0. The ordinary "nothing to commit"
115
+ // path never reaches here, deliberately — a notice that fires when nothing is wrong trains people
116
+ // to ignore notices, and the next real failure scrolls past with them.
117
+ const artifactName = 'docs/plans/';
118
+ const gitSaid = err && err.stderr ? String(err.stderr).trim() : '';
119
+ const detail = (gitSaid || String((err && err.message) || err)).split('\n')
120
+ .map((l) => l.trim()).filter(Boolean)[0] || 'unknown error';
121
+ process.stdout.write('[autocommit-plans] could not ' + stage + ' — ' + artifactName + ': '
122
+ + detail.slice(0, 200) + '\n');
38
123
  process.exit(0);
39
124
  }