@dzhechkov/p-replicator 1.3.0 β†’ 1.5.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 (35) hide show
  1. package/README.md +55 -0
  2. package/bin/cli.js +0 -0
  3. package/package.json +7 -2
  4. package/src/cli.js +23 -6
  5. package/src/commands/doctor.js +46 -29
  6. package/src/commands/init.js +61 -8
  7. package/src/commands/list.js +5 -26
  8. package/src/commands/update.js +73 -7
  9. package/src/commands/verify.js +111 -0
  10. package/src/utils.js +275 -4
  11. package/templates/.claude/commands/deploy.md +100 -0
  12. package/templates/.claude/commands/docs.md +79 -0
  13. package/templates/.claude/commands/feature.md +134 -0
  14. package/templates/.claude/commands/go.md +115 -0
  15. package/templates/.claude/commands/myinsights.md +72 -0
  16. package/templates/.claude/commands/next.md +110 -0
  17. package/templates/.claude/commands/plan.md +88 -0
  18. package/templates/.claude/commands/replicate.md +103 -17
  19. package/templates/.claude/commands/run.md +151 -0
  20. package/templates/.claude/commands/start.md +88 -0
  21. package/templates/.claude/hooks/autocommit-insights.cjs +39 -0
  22. package/templates/.claude/hooks/autocommit-plans.cjs +39 -0
  23. package/templates/.claude/hooks/autocommit-roadmap.cjs +44 -0
  24. package/templates/.claude/hooks/session-insights.cjs +28 -0
  25. package/templates/.claude/hooks/state-update.cjs +79 -0
  26. package/templates/.claude/hooks/statusline.cjs +399 -0
  27. package/templates/.claude/rules/feature-lifecycle.md +145 -0
  28. package/templates/.claude/rules/git-workflow.md +74 -0
  29. package/templates/.claude/rules/insights-capture.md +77 -0
  30. package/templates/.claude/rules/replicate-pipeline.md +92 -19
  31. package/templates/.claude/settings.json +44 -0
  32. package/templates/.claude/skills/brutal-honesty-review/scripts/assess-code.sh +0 -0
  33. package/templates/.claude/skills/brutal-honesty-review/scripts/assess-tests.sh +0 -0
  34. package/templates/.claude/skills/goap-research-ed25519/scripts/ed25519_verifier.py +0 -0
  35. package/templates/.claude/skills/goap-research-ed25519/scripts/goap_planner.py +0 -0
package/README.md CHANGED
@@ -4,6 +4,14 @@
4
4
 
5
5
  Transform a product idea into a fully documented, validated, toolkit-equipped project ready for development.
6
6
 
7
+ ## Documentation
8
+
9
+ - πŸ‡·πŸ‡Ί [ДокумСнтация Π½Π° русском](./README/ru/README.md)
10
+ - πŸ‡¬πŸ‡§ [English documentation](./README/eng/README.md)
11
+
12
+ Each language pack includes 7 sections: Quick start, User guide, Admin guide,
13
+ API reference, Architecture, Troubleshooting, Changelog.
14
+
7
15
  ## Quick Start
8
16
 
9
17
  ```bash
@@ -19,6 +27,53 @@ claude
19
27
 
20
28
  The system walks you through 4-5 phases with interactive checkpoints. At each checkpoint you review the output and confirm before proceeding. Estimated time: 45-90 minutes for the full pipeline.
21
29
 
30
+ ### Already have technical documentation?
31
+
32
+ If you already have tech specs, architecture notes, or API docs for the project,
33
+ you can skip Phase 0 (Product Discovery) and feed your existing docs into Phase 1
34
+ as input:
35
+
36
+ ```bash
37
+ mkdir -p docs/existing
38
+ cp your-tech-doc-*.md docs/existing/ # place your existing docs here
39
+
40
+ claude
41
+ /replicate "Use my existing docs in docs/existing/, skip Phase 0"
42
+ ```
43
+
44
+ Three sub-paths are supported:
45
+ - **A. Full pipeline** β€” `/replicate` reads your docs, generates the 11 SPARC docs + validation + toolkit + scaffold
46
+ - **B. SPARC docs only** β€” invoke the `sparc-prd-mini` skill directly, no toolkit/scaffold
47
+ - **C. Validation-only** β€” rename existing docs to SPARC slot names + invoke the `requirements-validator` skill
48
+
49
+ Full recipe with caveats: see [User Guide](./README/eng/02_user_guide.md#starting-from-existing-tech-docs)
50
+ ([RU](./README/ru/02_user_guide.md#Π°Π»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹ΠΉ-Π²Ρ…ΠΎΠ΄-Ρƒ-вас-ΡƒΠΆΠ΅-Π΅ΡΡ‚ΡŒ-тСхдокумСнтация)).
51
+
52
+ ### Adding features to an existing project?
53
+
54
+ If you already have a working project (stack, PRD, CLAUDE.md, Specification
55
+ defined) and want to **add new features** with the same SPARC-mini validation
56
+ cycle that `/replicate` provides β€” use `/feature` (Mode 2):
57
+
58
+ ```bash
59
+ cd existing-project
60
+ npx @dzhechkov/p-replicator init # idempotent β€” preserves CLAUDE.md
61
+ claude
62
+ /feature add-stripe-payments # 4-phase: PLAN β†’ VALIDATE β†’ IMPLEMENT β†’ REVIEW
63
+ ```
64
+
65
+ `/feature` runs the same validation pipeline as `/replicate` Phase 2, scoped
66
+ to a single feature. Same verdicts: 🟒 READY (β‰₯70) / 🟑 CAVEATS (50-69) /
67
+ πŸ”΄ NEEDS WORK (<50). Same retry logic. Same `brutal-honesty-review` post-implementation.
68
+
69
+ Three sub-paths:
70
+ - **A. /feature directly** β€” single feature, full 4-phase lifecycle
71
+ - **B. /go auto-router** β€” picks /plan vs /feature based on complexity
72
+ - **C. Direct skill invocation** β€” only the validation cycle, no full lifecycle
73
+
74
+ Full recipe: see [User Guide](./README/eng/02_user_guide.md#feature-workflow-in-an-existing-project-mode-2)
75
+ ([RU](./README/ru/02_user_guide.md#feature-workflow-Π²-ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰Π΅ΠΌ-ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π΅-mode-2)).
76
+
22
77
  ## What Gets Installed
23
78
 
24
79
  | Component | Count | Description |
package/bin/cli.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dzhechkov/p-replicator",
3
- "version": "1.3.0",
3
+ "version": "1.5.2",
4
4
  "description": "P-Replicator β€” Claude Code toolkit for AI-assisted product development (Vibe Coding). Full /replicate pipeline, 10 modular skills (194K+ chars), /harvest knowledge extraction, swarm agents, quality gates, security patterns, cross-project learning.",
5
5
  "main": "src/cli.js",
6
6
  "bin": {
@@ -12,7 +12,12 @@
12
12
  "templates/"
13
13
  ],
14
14
  "scripts": {
15
- "test": "node bin/cli.js doctor",
15
+ "test": "node --test tests/unit/utils.test.js tests/e2e/lifecycle.test.js tests/snapshot/templates.test.js",
16
+ "test:unit": "node --test tests/unit/utils.test.js",
17
+ "test:e2e": "node --test tests/e2e/lifecycle.test.js",
18
+ "test:snapshot": "node --test tests/snapshot/templates.test.js",
19
+ "test:doctor": "node bin/cli.js doctor",
20
+ "snapshot:baseline": "node tests/snapshot/update-baseline.js",
16
21
  "prepublishOnly": "node scripts/sync-templates.js"
17
22
  },
18
23
  "keywords": [
package/src/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { bold, cyan, dim, green, yellow, error } = require('./utils');
3
+ const { bold, cyan, dim, green, yellow, error, COMPONENTS } = require('./utils');
4
4
 
5
5
  // ---------------------------------------------------------------------------
6
6
  // Banner
@@ -33,11 +33,13 @@ function showHelp() {
33
33
  console.log(' ' + green('update') + ' Update to latest version');
34
34
  console.log(' ' + green('remove') + ' Remove P-Replicator from project');
35
35
  console.log(' ' + green('list') + ' List installed components');
36
- console.log(' ' + green('doctor') + ' Check installation health');
36
+ console.log(' ' + green('doctor') + ' Check installation health (pre-shipped artifacts)');
37
+ console.log(' ' + green('verify') + ' Verify pre-shipped + post-/replicate artifacts');
37
38
  console.log('');
38
39
  console.log(bold('Options:'));
39
40
  console.log(' ' + yellow('--force') + ' Overwrite existing files without prompting');
40
41
  console.log(' ' + yellow('--dry-run') + ' Show what would be done without making changes');
42
+ console.log(' ' + yellow('--reset-settings') + ' Force-overwrite settings.json (default: merge with user customizations)');
41
43
  console.log(' ' + yellow('--help, -h') + ' Show this help message');
42
44
  console.log(' ' + yellow('--version, -v') + ' Show version number');
43
45
  console.log('');
@@ -47,14 +49,21 @@ function showHelp() {
47
49
  console.log(dim(' $ ') + 'npx @dzhechkov/p-replicator update --dry-run');
48
50
  console.log(dim(' $ ') + 'npx @dzhechkov/p-replicator doctor');
49
51
  console.log('');
52
+ // Counts derived from utils.COMPONENTS.items (SSOT) β€” fixes prior "1 rule" drift.
53
+ const c = {
54
+ skills: Object.keys(COMPONENTS.skills.items).length,
55
+ commands: Object.keys(COMPONENTS.commands.items).length,
56
+ agents: Object.keys(COMPONENTS.agents.items).length,
57
+ rules: Object.keys(COMPONENTS.rules.items).length,
58
+ };
50
59
  console.log(bold('What gets installed:'));
51
- console.log(' 10 skills SPARC docs, research, problem-solving, validation, review,');
60
+ console.log(` ${c.skills} skills SPARC docs, research, problem-solving, validation, review,`);
52
61
  console.log(' reverse engineering, toolkit generation, pipeline forge,');
53
62
  console.log(' knowledge extraction, task exploration');
54
- console.log(' 2 commands /replicate (full pipeline), /harvest (knowledge extraction)');
55
- console.log(' 4 agents replicate-coordinator, product-discoverer, doc-validator,');
63
+ console.log(` ${c.commands} commands /replicate (full pipeline), /harvest (knowledge extraction)`);
64
+ console.log(` ${c.agents} agents replicate-coordinator, product-discoverer, doc-validator,`);
56
65
  console.log(' harvest-coordinator');
57
- console.log(' 1 rule replicate-pipeline (phase constraints)');
66
+ console.log(` ${c.rules} rule${c.rules === 1 ? '' : 's'} replicate-pipeline, skill-interface-protocol`);
58
67
  console.log('');
59
68
  console.log(bold('After installation:'));
60
69
  console.log(' 1. Open ' + cyan('Claude Code') + ' in this directory');
@@ -75,6 +84,7 @@ function parseArgs(argv) {
75
84
  const flags = {
76
85
  force: false,
77
86
  dryRun: false,
87
+ resetSettings: false,
78
88
  targetDir: process.cwd(),
79
89
  };
80
90
 
@@ -88,6 +98,9 @@ function parseArgs(argv) {
88
98
  case '--dry-run':
89
99
  flags.dryRun = true;
90
100
  break;
101
+ case '--reset-settings':
102
+ flags.resetSettings = true;
103
+ break;
91
104
  case '--help':
92
105
  case '-h':
93
106
  command = 'help';
@@ -148,6 +161,10 @@ function main() {
148
161
  require('./commands/doctor')(flags);
149
162
  break;
150
163
 
164
+ case 'verify':
165
+ require('./commands/verify')(flags);
166
+ break;
167
+
151
168
  default:
152
169
  error(`Unknown command: "${resolvedCommand}"`);
153
170
  console.log('');
@@ -2,40 +2,22 @@
2
2
 
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
+ const { execFileSync } = require('child_process');
5
6
  const {
6
7
  green, red, yellow, cyan, bold, dim,
7
8
  info, success, warn, error: logError,
8
9
  readManifest, fileExists,
9
- MANIFEST_FILE,
10
+ MANIFEST_FILE, COMPONENTS,
10
11
  } = require('../utils');
11
12
 
12
13
  // ---------------------------------------------------------------------------
13
- // Expected components
14
+ // Expected components β€” derived from utils.COMPONENTS.items (SSOT)
14
15
  // ---------------------------------------------------------------------------
15
16
 
16
- const EXPECTED_SKILLS = [
17
- 'explore',
18
- 'sparc-prd-mini',
19
- 'goap-research-ed25519',
20
- 'problem-solver-enhanced',
21
- 'requirements-validator',
22
- 'brutal-honesty-review',
23
- 'cc-toolkit-generator-enhanced',
24
- 'reverse-engineering-unicorn',
25
- 'pipeline-forge',
26
- 'knowledge-extractor',
27
- ];
28
-
29
- const EXPECTED_COMMANDS = ['replicate', 'harvest'];
30
-
31
- const EXPECTED_AGENTS = [
32
- 'replicate-coordinator',
33
- 'product-discoverer',
34
- 'doc-validator',
35
- 'harvest-coordinator',
36
- ];
37
-
38
- const EXPECTED_RULES = ['replicate-pipeline', 'skill-interface-protocol'];
17
+ const EXPECTED_SKILLS = Object.keys(COMPONENTS.skills.items);
18
+ const EXPECTED_COMMANDS = Object.keys(COMPONENTS.commands.items);
19
+ const EXPECTED_AGENTS = Object.keys(COMPONENTS.agents.items);
20
+ const EXPECTED_RULES = Object.keys(COMPONENTS.rules.items);
39
21
 
40
22
  function run(options) {
41
23
  const { targetDir } = options;
@@ -74,7 +56,7 @@ function run(options) {
74
56
  console.log('');
75
57
 
76
58
  // ── 3) Skills ───────────────────────────────────────────────────────────
77
- console.log(bold('Skills (expected 10):'));
59
+ console.log(bold(`Skills (expected ${EXPECTED_SKILLS.length}):`));
78
60
  for (const skill of EXPECTED_SKILLS) {
79
61
  const skillPath = path.join(claudeDir, 'skills', skill, 'SKILL.md');
80
62
  if (fileExists(skillPath)) {
@@ -86,7 +68,7 @@ function run(options) {
86
68
  console.log('');
87
69
 
88
70
  // ── 4) Commands ─────────────────────────────────────────────────────────
89
- console.log(bold('Commands (expected 2):'));
71
+ console.log(bold(`Commands (expected ${EXPECTED_COMMANDS.length}):`));
90
72
  for (const cmd of EXPECTED_COMMANDS) {
91
73
  const cmdPath = path.join(claudeDir, 'commands', `${cmd}.md`);
92
74
  if (fileExists(cmdPath)) {
@@ -98,7 +80,7 @@ function run(options) {
98
80
  console.log('');
99
81
 
100
82
  // ── 5) Agents ───────────────────────────────────────────────────────────
101
- console.log(bold('Agents (expected 4):'));
83
+ console.log(bold(`Agents (expected ${EXPECTED_AGENTS.length}):`));
102
84
  for (const agent of EXPECTED_AGENTS) {
103
85
  const agentPath = path.join(claudeDir, 'agents', `${agent}.md`);
104
86
  if (fileExists(agentPath)) {
@@ -110,7 +92,7 @@ function run(options) {
110
92
  console.log('');
111
93
 
112
94
  // ── 6) Rules ────────────────────────────────────────────────────────────
113
- console.log(bold('Rules (expected 2):'));
95
+ console.log(bold(`Rules (expected ${EXPECTED_RULES.length}):`));
114
96
  for (const rule of EXPECTED_RULES) {
115
97
  const rulePath = path.join(claudeDir, 'rules', `${rule}.md`);
116
98
  if (fileExists(rulePath)) {
@@ -121,6 +103,41 @@ function run(options) {
121
103
  }
122
104
  console.log('');
123
105
 
106
+ // ── 6b) Settings (hooks config) ─────────────────────────────────────────
107
+ console.log(bold('Hooks (settings.json):'));
108
+ const settingsPath = path.join(claudeDir, 'settings.json');
109
+ if (fileExists(settingsPath)) {
110
+ pass('settings.json');
111
+ } else {
112
+ fail('settings.json β€” missing (SessionStart + Stop hooks not configured)');
113
+ }
114
+ console.log('');
115
+
116
+ // ── 6c) Hook scripts (cross-platform Node, v1.4.1+) ─────────────────────
117
+ if (COMPONENTS.hooks) {
118
+ const hookKeys = Object.keys(COMPONENTS.hooks.items);
119
+ console.log(bold(`Hook scripts (expected ${hookKeys.length}):`));
120
+ for (const hook of hookKeys) {
121
+ const hookPath = path.join(claudeDir, 'hooks', `${hook}.cjs`);
122
+ if (fileExists(hookPath)) {
123
+ pass(`${hook}.cjs`);
124
+ } else {
125
+ fail(`${hook}.cjs β€” missing (referenced by settings.json)`);
126
+ }
127
+ }
128
+ console.log('');
129
+ }
130
+
131
+ // ── 6d) Prerequisites (system tools the package relies on) ──────────────
132
+ console.log(bold('Prerequisites:'));
133
+ try {
134
+ execFileSync('git', ['--version'], { stdio: 'pipe' });
135
+ pass('git on PATH');
136
+ } catch {
137
+ fail('git NOT on PATH β€” autocommit hooks (roadmap, insights, plans) will silently no-op');
138
+ }
139
+ console.log('');
140
+
124
141
  // ── 7) Keysarium integration ────────────────────────────────────────────
125
142
  console.log(bold('Integration:'));
126
143
  const keysariumManifest = path.join(targetDir, '.keysarium.json');
@@ -3,11 +3,12 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const {
6
- green, cyan, bold, dim,
6
+ green, cyan, bold, dim, yellow,
7
7
  info, success, warn, error: logError, step,
8
- copyDirRecursive, fileExists, readJSON,
9
- createManifest, writeManifest, getTemplatesDir, getRelativePaths,
8
+ copyDirRecursive, fileExists, readJSON, writeJSON, ensureDir,
9
+ createManifest, writeManifest, readManifest, getTemplatesDir, getRelativePaths,
10
10
  COMPONENTS, MANIFEST_FILE,
11
+ mergeSettingsJson, removeOrphanHooks,
11
12
  } = require('../utils');
12
13
 
13
14
  // ---------------------------------------------------------------------------
@@ -37,7 +38,7 @@ function showKeysariumIntegration(keysariumManifest) {
37
38
  // Helpers
38
39
  // ---------------------------------------------------------------------------
39
40
 
40
- function installComponent(key, comp, templatesDir, targetDir) {
41
+ function installComponent(key, comp, templatesDir, targetDir, options) {
41
42
  const src = path.join(templatesDir, comp.src);
42
43
  const dest = path.join(targetDir, comp.src);
43
44
 
@@ -46,7 +47,44 @@ function installComponent(key, comp, templatesDir, targetDir) {
46
47
  return [];
47
48
  }
48
49
 
50
+ // settings.json merge (v1.4.2 + v1.4.3 orphan detection): on --force, run
51
+ // orphan cleanup (v1.4.3) using shippedDefaults from previous manifest, then
52
+ // merge new template into the cleaned user settings. Use --reset-settings to
53
+ // force full overwrite.
54
+ if (
55
+ comp.isFile &&
56
+ comp.src.endsWith('.json') &&
57
+ fileExists(dest) &&
58
+ !(options && options.resetSettings)
59
+ ) {
60
+ try {
61
+ const tplJson = readJSON(src);
62
+ const existingJson = readJSON(dest);
63
+ const fileKey = comp.src.split(/[/\\]/).pop();
64
+ const oldTplJson =
65
+ (options && options.previousManifest && options.previousManifest.shippedDefaults &&
66
+ options.previousManifest.shippedDefaults[fileKey]) || null;
67
+
68
+ // v1.4.3: remove hooks that were defaults in the old shipped template
69
+ // but no longer exist in the new template (true orphans).
70
+ const cleaned = removeOrphanHooks(existingJson, oldTplJson, tplJson);
71
+ const mergedJson = mergeSettingsJson(cleaned, tplJson);
72
+ writeJSON(dest, mergedJson);
73
+ info(`Merged user customizations into ${comp.src} ${dim('(use --reset-settings to overwrite)')}`);
74
+ return [comp.src];
75
+ } catch (err) {
76
+ warn(`settings merge failed (${err.message}) \u2014 falling back to overwrite`);
77
+ }
78
+ }
79
+
49
80
  copyDirRecursive(src, dest);
81
+
82
+ // Single-file components (e.g., settings.json): manifest tracks the file directly,
83
+ // since getRelativePaths(file) returns [] (it expects a directory).
84
+ if (fs.statSync(src).isFile()) {
85
+ return [comp.src];
86
+ }
87
+
50
88
  return getRelativePaths(dest).map((rel) => path.join(comp.src, rel));
51
89
  }
52
90
 
@@ -55,10 +93,13 @@ function installComponent(key, comp, templatesDir, targetDir) {
55
93
  // ---------------------------------------------------------------------------
56
94
 
57
95
  function run(options) {
58
- const { force, dryRun, targetDir } = options;
96
+ const { force, dryRun, targetDir, resetSettings } = options;
59
97
  const manifestPath = path.join(targetDir, MANIFEST_FILE);
60
98
 
61
99
  // ── a) Check for existing installation ──────────────────────────────────
100
+ // Read previous manifest BEFORE we overwrite it. Used by installComponent
101
+ // for orphan-hook detection in settings.json (v1.4.3).
102
+ const previousManifest = readManifest(targetDir);
62
103
  if (fileExists(manifestPath)) {
63
104
  if (!force) {
64
105
  warn('P-Replicator is already installed in this directory.');
@@ -75,7 +116,11 @@ function run(options) {
75
116
  }
76
117
 
77
118
  // ── c) Show plan ────────────────────────────────────────────────────────
78
- const componentKeys = Object.keys(COMPONENTS);
119
+ // Install only pre-shipped groups; project-generated groups have no `src` β€”
120
+ // they describe artifacts created by /replicate at a later phase.
121
+ const componentKeys = Object.keys(COMPONENTS).filter(
122
+ (k) => COMPONENTS[k].kind === 'pre-shipped'
123
+ );
79
124
  const templatesDir = getTemplatesDir();
80
125
 
81
126
  console.log('');
@@ -103,7 +148,7 @@ function run(options) {
103
148
  const comp = COMPONENTS[key];
104
149
  step(i + 1, totalComponents, `Installing ${comp.label}...`);
105
150
 
106
- const files = installComponent(key, comp, templatesDir, targetDir);
151
+ const files = installComponent(key, comp, templatesDir, targetDir, { resetSettings, previousManifest });
107
152
  installedFiles.push(...files);
108
153
  }
109
154
 
@@ -112,7 +157,15 @@ function run(options) {
112
157
  const pkg = readJSON(pkgPath);
113
158
  const version = pkg ? pkg.version : '0.0.0';
114
159
 
115
- const manifest = createManifest(version, componentKeys, installedFiles.sort());
160
+ // v1.4.3: snapshot the settings.json template we just shipped, so the NEXT
161
+ // upgrade can detect orphan hooks (hooks we used to ship but no longer do).
162
+ const shippedDefaults = {};
163
+ const settingsTemplatePath = path.join(templatesDir, '.claude', 'settings.json');
164
+ if (fileExists(settingsTemplatePath)) {
165
+ const tplContent = readJSON(settingsTemplatePath);
166
+ if (tplContent) shippedDefaults['settings.json'] = tplContent;
167
+ }
168
+ const manifest = createManifest(version, componentKeys, installedFiles.sort(), shippedDefaults);
116
169
  writeManifest(targetDir, manifest);
117
170
  info(`Created ${MANIFEST_FILE} manifest`);
118
171
 
@@ -6,37 +6,16 @@ const {
6
6
  green, cyan, bold, dim, yellow,
7
7
  info, warn,
8
8
  readManifest, fileExists,
9
- MANIFEST_FILE,
9
+ MANIFEST_FILE, COMPONENTS,
10
10
  } = require('../utils');
11
11
 
12
12
  // ---------------------------------------------------------------------------
13
- // Skill details for rich listing
13
+ // Display details \u2014 pulled from utils.COMPONENTS.items (SSOT)
14
14
  // ---------------------------------------------------------------------------
15
15
 
16
- const SKILL_DETAILS = {
17
- 'explore': 'Socratic task clarification',
18
- 'sparc-prd-mini': 'SPARC documentation generator (11 docs)',
19
- 'goap-research-ed25519': 'Verified research with Ed25519 anti-hallucination',
20
- 'problem-solver-enhanced': 'First principles + TRIZ (9 modules)',
21
- 'requirements-validator': 'INVEST/SMART validation + BDD scenarios',
22
- 'brutal-honesty-review': 'Unvarnished technical criticism',
23
- 'cc-toolkit-generator-enhanced': 'Modular toolkit generator (9 modules, ~165K chars)',
24
- 'reverse-engineering-unicorn': 'Company reverse engineering + playbook',
25
- 'pipeline-forge': 'Meta-skill: build AI pipelines from patterns',
26
- 'knowledge-extractor': 'Extract reusable knowledge from projects',
27
- };
28
-
29
- const COMMAND_DETAILS = {
30
- 'replicate': 'Full pipeline: idea \u2192 validated docs \u2192 toolkit',
31
- 'harvest': 'Knowledge extraction from projects',
32
- };
33
-
34
- const AGENT_DETAILS = {
35
- 'replicate-coordinator': 'Pipeline orchestration (Phases 0-4)',
36
- 'product-discoverer': 'Market research (Phase 0)',
37
- 'doc-validator': 'Documentation validation swarm (Phase 2)',
38
- 'harvest-coordinator': 'Knowledge extraction swarm',
39
- };
16
+ const SKILL_DETAILS = COMPONENTS.skills.items;
17
+ const COMMAND_DETAILS = COMPONENTS.commands.items;
18
+ const AGENT_DETAILS = COMPONENTS.agents.items;
40
19
 
41
20
  function run(options) {
42
21
  const { targetDir } = options;
@@ -1,17 +1,19 @@
1
1
  'use strict';
2
2
 
3
+ const fs = require('fs');
3
4
  const path = require('path');
4
5
  const {
5
6
  green, yellow, cyan, bold, dim,
6
7
  info, success, warn, error: logError, step,
7
- copyDirRecursive, fileExists, readJSON,
8
+ copyDirRecursive, fileExists, readJSON, writeJSON,
8
9
  diffFiles, getRelativePaths,
9
10
  readManifest, writeManifest, createManifest, getTemplatesDir,
10
11
  COMPONENTS, MANIFEST_FILE,
12
+ mergeSettingsJson, removeOrphanHooks,
11
13
  } = require('../utils');
12
14
 
13
15
  function run(options) {
14
- const { force, dryRun, targetDir } = options;
16
+ const { force, dryRun, targetDir, resetSettings } = options;
15
17
 
16
18
  // ── a) Check for existing installation ──────────────────────────────────
17
19
  const manifest = readManifest(targetDir);
@@ -75,24 +77,88 @@ function run(options) {
75
77
  }
76
78
 
77
79
  // ── d) Apply updates ───────────────────────────────────────────────────
80
+ // For each modified/added file: regular copy, EXCEPT settings.json β€” which
81
+ // gets the same merge + orphan-cleanup treatment as init --force (v1.4.3).
78
82
  const filesToUpdate = [...added, ...modified];
79
83
  let updated = 0;
84
+ const oldSettingsTpl = manifest.shippedDefaults && manifest.shippedDefaults['settings.json'];
80
85
 
81
86
  for (const rel of filesToUpdate) {
82
87
  const src = path.join(templateClaude, rel);
83
88
  const dest = path.join(projectClaude, rel);
84
- copyDirRecursive(src, dest);
89
+ const normalized = rel.replace(/\\/g, '/');
90
+ const isSettings = normalized === 'settings.json' || normalized.endsWith('/settings.json');
91
+
92
+ if (isSettings && fileExists(dest) && !resetSettings) {
93
+ try {
94
+ const tplJson = readJSON(src);
95
+ const existingJson = readJSON(dest);
96
+ const cleaned = removeOrphanHooks(existingJson, oldSettingsTpl || null, tplJson);
97
+ const mergedJson = mergeSettingsJson(cleaned, tplJson);
98
+ writeJSON(dest, mergedJson);
99
+ info(`Merged settings.json with user customizations ${dim('(use --reset-settings to overwrite)')}`);
100
+ } catch (err) {
101
+ warn(`settings merge failed (${err.message}) β€” falling back to overwrite`);
102
+ copyDirRecursive(src, dest);
103
+ }
104
+ } else {
105
+ copyDirRecursive(src, dest);
106
+ }
85
107
  updated++;
86
108
  }
87
109
 
88
110
  // ── e) Update manifest ─────────────────────────────────────────────────
89
- const allFiles = getRelativePaths(projectClaude).map((f) => '.claude/' + f);
90
- const componentKeys = Object.keys(COMPONENTS);
91
- const newManifest = createManifest(newVersion, componentKeys, allFiles.sort());
111
+ // Track ONLY files shipped by the package's templates β€” never walk the user's
112
+ // .claude/ tree. Walking projectClaude would capture project-generated files
113
+ // (e.g. /replicate output: start.md, feature.md, plan.md) into manifest.files,
114
+ // and a subsequent `remove` would then delete them, contradicting remove.js's
115
+ // own "project-specific files were NOT removed" guarantee.
116
+ const newTrackedFiles = getRelativePaths(templateClaude).map((f) => '.claude/' + f);
117
+ const newTrackedSet = new Set(newTrackedFiles);
118
+
119
+ // Orphan cleanup: files previously tracked by the package but no longer
120
+ // shipped (skill removed in a new package version, etc.). Safe to delete
121
+ // because they were package-owned. Project-generated files are never in the
122
+ // old manifest, so they are never considered orphans.
123
+ const oldTrackedFiles = manifest.files || [];
124
+ const orphans = oldTrackedFiles.filter(
125
+ (f) => f.startsWith('.claude/') && !newTrackedSet.has(f)
126
+ );
127
+
128
+ let removedOrphans = 0;
129
+ for (const orphan of orphans) {
130
+ const fullPath = path.join(targetDir, orphan);
131
+ if (fileExists(fullPath)) {
132
+ try {
133
+ fs.unlinkSync(fullPath);
134
+ removedOrphans++;
135
+ } catch (err) {
136
+ warn(`Could not remove orphan: ${orphan} (${err.message})`);
137
+ }
138
+ }
139
+ }
140
+
141
+ // Manifest tracks only pre-shipped groups (project-generated groups describe
142
+ // /replicate Phase 3 outputs, not files this package owns).
143
+ const componentKeys = Object.keys(COMPONENTS).filter(
144
+ (k) => COMPONENTS[k].kind === 'pre-shipped'
145
+ );
146
+ // v1.4.3: refresh shippedDefaults snapshot for the next upgrade to use.
147
+ const shippedDefaults = {};
148
+ const settingsTemplatePath = path.join(templateClaude, 'settings.json');
149
+ if (fileExists(settingsTemplatePath)) {
150
+ const tplContent = readJSON(settingsTemplatePath);
151
+ if (tplContent) shippedDefaults['settings.json'] = tplContent;
152
+ }
153
+ const newManifest = createManifest(newVersion, componentKeys, newTrackedFiles.sort(), shippedDefaults);
92
154
  writeManifest(targetDir, newManifest);
93
155
 
94
156
  console.log('');
95
- success(`Updated ${updated} files to version ${bold(newVersion)}`);
157
+ let summary = `Updated ${updated} files to version ${bold(newVersion)}`;
158
+ if (removedOrphans > 0) {
159
+ summary += dim(` (+ ${removedOrphans} orphan${removedOrphans === 1 ? '' : 's'} removed)`);
160
+ }
161
+ success(summary);
96
162
  console.log('');
97
163
 
98
164
  process.exit(0);