@dzhechkov/keysarium 1.0.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.
- package/README.md +289 -0
- package/bin/cli.js +5 -0
- package/package.json +43 -0
- package/src/cli.js +157 -0
- package/src/commands/doctor.js +469 -0
- package/src/commands/init.js +171 -0
- package/src/commands/list.js +147 -0
- package/src/commands/remove.js +197 -0
- package/src/commands/update.js +191 -0
- package/src/utils.js +274 -0
- package/templates/.claude/agents/cjm-variant-worker.md +60 -0
- package/templates/.claude/agents/discovery-worker.md +45 -0
- package/templates/.claude/agents/parallel-research-orchestrator.md +70 -0
- package/templates/.claude/agents/presentation-worker.md +55 -0
- package/templates/.claude/agents/research-worker.md +43 -0
- package/templates/.claude/commands/architecture-phase.md +37 -0
- package/templates/.claude/commands/casarium.md +163 -0
- package/templates/.claude/commands/cjm-prototype.md +42 -0
- package/templates/.claude/commands/discovery.md +34 -0
- package/templates/.claude/commands/explore-case.md +22 -0
- package/templates/.claude/commands/harvest.md +67 -0
- package/templates/.claude/commands/new-research.md +69 -0
- package/templates/.claude/commands/parallel-research.md +45 -0
- package/templates/.claude/commands/presentation.md +49 -0
- package/templates/.claude/commands/research.md +32 -0
- package/templates/.claude/commands/solve.md +36 -0
- package/templates/.claude/rules/agent-swarm.md +31 -0
- package/templates/.claude/rules/anti-patterns.md +19 -0
- package/templates/.claude/rules/checkpoint-protocol.md +23 -0
- package/templates/.claude/rules/domain-specific.md +32 -0
- package/templates/.claude/rules/file-conventions.md +38 -0
- package/templates/.claude/rules/modular-reuse.md +35 -0
- package/templates/.claude/rules/research-quality.md +20 -0
- package/templates/.claude/settings.json +37 -0
- package/templates/.claude/skills/explore/SKILL.md +215 -0
- package/templates/.claude/skills/explore/references/questioning-techniques.md +151 -0
- package/templates/.claude/skills/explore/references/task-brief-templates.md +355 -0
- package/templates/.claude/skills/frontend-design/LICENSE.txt +177 -0
- package/templates/.claude/skills/frontend-design/SKILL.md +42 -0
- package/templates/.claude/skills/goap-research-ed25519/SKILL.md +415 -0
- package/templates/.claude/skills/goap-research-ed25519/references/ed25519-verification.md +658 -0
- package/templates/.claude/skills/goap-research-ed25519/references/research-actions.md +544 -0
- package/templates/.claude/skills/goap-research-ed25519/references/source-evaluation.md +560 -0
- package/templates/.claude/skills/goap-research-ed25519/scripts/ed25519_verifier.py +662 -0
- package/templates/.claude/skills/goap-research-ed25519/scripts/goap_planner.py +720 -0
- package/templates/.claude/skills/presentation-storyteller/SKILL.md +371 -0
- package/templates/.claude/skills/presentation-storyteller/references/example-presentation.md +273 -0
- package/templates/.claude/skills/presentation-storyteller/references/slide-types.md +426 -0
- package/templates/.claude/skills/presentation-storyteller/references/sources-index-template.md +213 -0
- package/templates/.claude/skills/presentation-storyteller/references/speaker-script-patterns.md +324 -0
- package/templates/.claude/skills/presentation-storyteller/references/storytelling-frameworks.md +270 -0
- package/templates/.claude/skills/problem-solver-enhanced/SKILL.md +562 -0
- package/templates/.claude/skills/reverse-engineering-unicorn/SKILL.md +194 -0
- package/templates/.claude/skills/reverse-engineering-unicorn/examples/noom-cjm-example.md +100 -0
- package/templates/.claude/skills/reverse-engineering-unicorn/examples/noom-module1-example.md +88 -0
- package/templates/.claude/skills/reverse-engineering-unicorn/modules/01-intelligence.md +328 -0
- package/templates/.claude/skills/reverse-engineering-unicorn/modules/02-product-customers.md +221 -0
- package/templates/.claude/skills/reverse-engineering-unicorn/modules/025-cjm-prototype.md +206 -0
- package/templates/.claude/skills/reverse-engineering-unicorn/modules/03-market-competition.md +402 -0
- package/templates/.claude/skills/reverse-engineering-unicorn/modules/04-business-finance.md +287 -0
- package/templates/.claude/skills/reverse-engineering-unicorn/modules/05-growth-engine.md +290 -0
- package/templates/.claude/skills/reverse-engineering-unicorn/modules/06-playbook-synthesis.md +286 -0
- package/templates/.claude/skills/reverse-engineering-unicorn/references/blue-ocean-canvas.md +39 -0
- package/templates/.claude/skills/reverse-engineering-unicorn/references/industry-benchmarks.md +67 -0
- package/templates/.claude/skills/reverse-engineering-unicorn/references/jtbd-canvas.md +48 -0
- package/templates/CLAUDE.md +185 -0
- package/templates/TOOLKIT_HARVEST.md +29 -0
- package/templates/docs/keysarium/01-deployment-guide.md +768 -0
- package/templates/docs/keysarium/02-admin-guide.md +1170 -0
- package/templates/docs/keysarium/03-user-guide.md +884 -0
- package/templates/docs/keysarium/04-infrastructure-requirements.md +967 -0
- package/templates/docs/keysarium/05-architecture.md +706 -0
- package/templates/docs/keysarium/06-user-flows.md +925 -0
- package/templates/docs/skills-bto/01-deployment-guide.md +828 -0
- package/templates/docs/skills-bto/02-admin-guide.md +1052 -0
- package/templates/docs/skills-bto/03-user-guide.md +960 -0
- package/templates/docs/skills-bto/04-infrastructure-requirements.md +610 -0
- package/templates/docs/skills-bto/05-architecture.md +1112 -0
- package/templates/docs/skills-bto/06-user-flows.md +977 -0
- package/templates/lib/agent-patterns.md +115 -0
- package/templates/lib/domain-templates.md +172 -0
- package/templates/lib/phase-utils.md +107 -0
- package/templates/lib/skill-loader.md +78 -0
- package/templates/researches/.gitkeep +0 -0
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const {
|
|
6
|
+
green, red, yellow, cyan, bold, dim,
|
|
7
|
+
info, success, warn, error: logError,
|
|
8
|
+
fileExists, readJSON, readManifest,
|
|
9
|
+
MANIFEST_FILE,
|
|
10
|
+
} = require('../utils');
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Check definitions
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Run a single check. Returns { name, passed, detail, fix }.
|
|
18
|
+
*/
|
|
19
|
+
function check(name, fn) {
|
|
20
|
+
try {
|
|
21
|
+
const result = fn();
|
|
22
|
+
return {
|
|
23
|
+
name,
|
|
24
|
+
passed: result.passed,
|
|
25
|
+
detail: result.detail || '',
|
|
26
|
+
fix: result.fix || '',
|
|
27
|
+
};
|
|
28
|
+
} catch (err) {
|
|
29
|
+
return {
|
|
30
|
+
name,
|
|
31
|
+
passed: false,
|
|
32
|
+
detail: `Exception: ${err.message}`,
|
|
33
|
+
fix: 'Investigate the error above.',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check 1: Verify every file in manifest exists on disk.
|
|
40
|
+
*/
|
|
41
|
+
function checkFilesExist(manifest, targetDir) {
|
|
42
|
+
return check('Files exist', () => {
|
|
43
|
+
const files = manifest.files || [];
|
|
44
|
+
const missing = [];
|
|
45
|
+
|
|
46
|
+
for (const relPath of files) {
|
|
47
|
+
const absPath = path.join(targetDir, relPath);
|
|
48
|
+
if (!fileExists(absPath)) {
|
|
49
|
+
missing.push(relPath);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (missing.length === 0) {
|
|
54
|
+
return {
|
|
55
|
+
passed: true,
|
|
56
|
+
detail: `All ${files.length} manifest files present`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
passed: false,
|
|
62
|
+
detail: `${missing.length} file(s) missing: ${missing.slice(0, 5).join(', ')}${missing.length > 5 ? '...' : ''}`,
|
|
63
|
+
fix: `Run ${cyan('@dzhechko/keysarium update')} to restore missing files.`,
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Check 2: CLAUDE.md contains key sections.
|
|
70
|
+
*/
|
|
71
|
+
function checkClaudeMd(targetDir) {
|
|
72
|
+
return check('CLAUDE.md valid', () => {
|
|
73
|
+
const claudePath = path.join(targetDir, 'CLAUDE.md');
|
|
74
|
+
|
|
75
|
+
if (!fileExists(claudePath)) {
|
|
76
|
+
return {
|
|
77
|
+
passed: false,
|
|
78
|
+
detail: 'CLAUDE.md not found',
|
|
79
|
+
fix: `Run ${cyan('@dzhechko/keysarium update')} or ${cyan('@dzhechko/keysarium init --force')} to restore.`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const content = fs.readFileSync(claudePath, 'utf8');
|
|
84
|
+
const requiredSections = ['Pipeline', 'Skills'];
|
|
85
|
+
const missingSections = [];
|
|
86
|
+
|
|
87
|
+
for (const section of requiredSections) {
|
|
88
|
+
// Case-insensitive search for the section heading or keyword
|
|
89
|
+
if (!content.toLowerCase().includes(section.toLowerCase())) {
|
|
90
|
+
missingSections.push(section);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (missingSections.length === 0) {
|
|
95
|
+
return {
|
|
96
|
+
passed: true,
|
|
97
|
+
detail: 'Contains required sections (Pipeline, Skills)',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
passed: false,
|
|
103
|
+
detail: `Missing sections: ${missingSections.join(', ')}`,
|
|
104
|
+
fix: 'Restore CLAUDE.md from the template or add the missing sections manually.',
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Check 3: Each skill directory has SKILL.md.
|
|
111
|
+
*/
|
|
112
|
+
function checkSkillsComplete(targetDir) {
|
|
113
|
+
return check('Skills complete', () => {
|
|
114
|
+
const skillsDir = path.join(targetDir, '.claude', 'skills');
|
|
115
|
+
|
|
116
|
+
if (!fileExists(skillsDir)) {
|
|
117
|
+
return {
|
|
118
|
+
passed: false,
|
|
119
|
+
detail: '.claude/skills/ directory not found',
|
|
120
|
+
fix: `Run ${cyan('@dzhechko/keysarium update')} to restore skills.`,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let entries;
|
|
125
|
+
try {
|
|
126
|
+
entries = fs.readdirSync(skillsDir);
|
|
127
|
+
} catch {
|
|
128
|
+
return {
|
|
129
|
+
passed: false,
|
|
130
|
+
detail: 'Cannot read .claude/skills/ directory',
|
|
131
|
+
fix: 'Check directory permissions.',
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const skillDirs = entries.filter((entry) => {
|
|
136
|
+
const entryPath = path.join(skillsDir, entry);
|
|
137
|
+
try {
|
|
138
|
+
return fs.statSync(entryPath).isDirectory();
|
|
139
|
+
} catch {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const missingSkillMd = [];
|
|
145
|
+
for (const dir of skillDirs) {
|
|
146
|
+
const skillMdPath = path.join(skillsDir, dir, 'SKILL.md');
|
|
147
|
+
if (!fileExists(skillMdPath)) {
|
|
148
|
+
missingSkillMd.push(dir);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (missingSkillMd.length === 0) {
|
|
153
|
+
return {
|
|
154
|
+
passed: true,
|
|
155
|
+
detail: `${skillDirs.length} skill(s), all have SKILL.md`,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
passed: false,
|
|
161
|
+
detail: `Missing SKILL.md in: ${missingSkillMd.join(', ')}`,
|
|
162
|
+
fix: 'Each skill directory must contain a SKILL.md file. Run update to restore.',
|
|
163
|
+
};
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Check 4: All 11 command .md files are present.
|
|
169
|
+
*/
|
|
170
|
+
function checkCommandsPresent(targetDir) {
|
|
171
|
+
return check('Commands present', () => {
|
|
172
|
+
const commandsDir = path.join(targetDir, '.claude', 'commands');
|
|
173
|
+
const expectedCommands = [
|
|
174
|
+
'casarium.md',
|
|
175
|
+
'new-research.md',
|
|
176
|
+
'parallel-research.md',
|
|
177
|
+
'discovery.md',
|
|
178
|
+
'explore-case.md',
|
|
179
|
+
'research.md',
|
|
180
|
+
'cjm-prototype.md',
|
|
181
|
+
'solve.md',
|
|
182
|
+
'architecture-phase.md',
|
|
183
|
+
'presentation.md',
|
|
184
|
+
'harvest.md',
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
if (!fileExists(commandsDir)) {
|
|
188
|
+
return {
|
|
189
|
+
passed: false,
|
|
190
|
+
detail: '.claude/commands/ directory not found',
|
|
191
|
+
fix: `Run ${cyan('@dzhechko/keysarium update')} to restore commands.`,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const missing = [];
|
|
196
|
+
for (const cmd of expectedCommands) {
|
|
197
|
+
if (!fileExists(path.join(commandsDir, cmd))) {
|
|
198
|
+
missing.push(cmd);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (missing.length === 0) {
|
|
203
|
+
return {
|
|
204
|
+
passed: true,
|
|
205
|
+
detail: `All ${expectedCommands.length} command files present`,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
passed: false,
|
|
211
|
+
detail: `Missing ${missing.length} command(s): ${missing.join(', ')}`,
|
|
212
|
+
fix: `Run ${cyan('@dzhechko/keysarium update')} to restore missing commands.`,
|
|
213
|
+
};
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Check 5: All 7 rule .md files are present.
|
|
219
|
+
*/
|
|
220
|
+
function checkRulesPresent(targetDir) {
|
|
221
|
+
return check('Rules present', () => {
|
|
222
|
+
const rulesDir = path.join(targetDir, '.claude', 'rules');
|
|
223
|
+
const expectedRules = [
|
|
224
|
+
'agent-swarm.md',
|
|
225
|
+
'anti-patterns.md',
|
|
226
|
+
'checkpoint-protocol.md',
|
|
227
|
+
'domain-specific.md',
|
|
228
|
+
'file-conventions.md',
|
|
229
|
+
'modular-reuse.md',
|
|
230
|
+
'research-quality.md',
|
|
231
|
+
];
|
|
232
|
+
|
|
233
|
+
if (!fileExists(rulesDir)) {
|
|
234
|
+
return {
|
|
235
|
+
passed: false,
|
|
236
|
+
detail: '.claude/rules/ directory not found',
|
|
237
|
+
fix: `Run ${cyan('@dzhechko/keysarium update')} to restore rules.`,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const missing = [];
|
|
242
|
+
for (const rule of expectedRules) {
|
|
243
|
+
if (!fileExists(path.join(rulesDir, rule))) {
|
|
244
|
+
missing.push(rule);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (missing.length === 0) {
|
|
249
|
+
return {
|
|
250
|
+
passed: true,
|
|
251
|
+
detail: `All ${expectedRules.length} rule files present`,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
passed: false,
|
|
257
|
+
detail: `Missing ${missing.length} rule(s): ${missing.join(', ')}`,
|
|
258
|
+
fix: `Run ${cyan('@dzhechko/keysarium update')} to restore missing rules.`,
|
|
259
|
+
};
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Check 6: All 5 agent .md files are present.
|
|
265
|
+
*/
|
|
266
|
+
function checkAgentsPresent(targetDir) {
|
|
267
|
+
return check('Agents present', () => {
|
|
268
|
+
const agentsDir = path.join(targetDir, '.claude', 'agents');
|
|
269
|
+
|
|
270
|
+
if (!fileExists(agentsDir)) {
|
|
271
|
+
return {
|
|
272
|
+
passed: false,
|
|
273
|
+
detail: '.claude/agents/ directory not found',
|
|
274
|
+
fix: `Run ${cyan('@dzhechko/keysarium update')} to restore agents.`,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
let entries;
|
|
279
|
+
try {
|
|
280
|
+
entries = fs.readdirSync(agentsDir);
|
|
281
|
+
} catch {
|
|
282
|
+
return {
|
|
283
|
+
passed: false,
|
|
284
|
+
detail: 'Cannot read .claude/agents/ directory',
|
|
285
|
+
fix: 'Check directory permissions.',
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const mdFiles = entries.filter((e) => e.endsWith('.md'));
|
|
290
|
+
|
|
291
|
+
if (mdFiles.length >= 5) {
|
|
292
|
+
return {
|
|
293
|
+
passed: true,
|
|
294
|
+
detail: `${mdFiles.length} agent file(s) present`,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
passed: false,
|
|
300
|
+
detail: `Only ${mdFiles.length}/5 agent files found`,
|
|
301
|
+
fix: `Run ${cyan('@dzhechko/keysarium update')} to restore missing agent templates.`,
|
|
302
|
+
};
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Check 7: .claude/settings.json is valid JSON.
|
|
308
|
+
*/
|
|
309
|
+
function checkSettingsValid(targetDir) {
|
|
310
|
+
return check('Settings valid', () => {
|
|
311
|
+
const settingsPath = path.join(targetDir, '.claude', 'settings.json');
|
|
312
|
+
|
|
313
|
+
if (!fileExists(settingsPath)) {
|
|
314
|
+
return {
|
|
315
|
+
passed: false,
|
|
316
|
+
detail: '.claude/settings.json not found',
|
|
317
|
+
fix: `Run ${cyan('@dzhechko/keysarium update')} to restore settings.`,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const data = readJSON(settingsPath);
|
|
322
|
+
if (data === null) {
|
|
323
|
+
return {
|
|
324
|
+
passed: false,
|
|
325
|
+
detail: '.claude/settings.json contains invalid JSON',
|
|
326
|
+
fix: 'Fix the JSON syntax in .claude/settings.json or restore from template.',
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
passed: true,
|
|
332
|
+
detail: 'Valid JSON',
|
|
333
|
+
};
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Check 8: researches/ directory exists.
|
|
339
|
+
*/
|
|
340
|
+
function checkResearchDir(targetDir) {
|
|
341
|
+
return check('Research dir', () => {
|
|
342
|
+
const researchesDir = path.join(targetDir, 'researches');
|
|
343
|
+
|
|
344
|
+
if (!fileExists(researchesDir)) {
|
|
345
|
+
return {
|
|
346
|
+
passed: false,
|
|
347
|
+
detail: 'researches/ directory not found',
|
|
348
|
+
fix: 'Create the directory: mkdir researches',
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
const stat = fs.statSync(researchesDir);
|
|
354
|
+
if (!stat.isDirectory()) {
|
|
355
|
+
return {
|
|
356
|
+
passed: false,
|
|
357
|
+
detail: 'researches exists but is not a directory',
|
|
358
|
+
fix: 'Remove the file and create a directory: rm researches && mkdir researches',
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
} catch {
|
|
362
|
+
return {
|
|
363
|
+
passed: false,
|
|
364
|
+
detail: 'Cannot stat researches/',
|
|
365
|
+
fix: 'Check filesystem permissions.',
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return {
|
|
370
|
+
passed: true,
|
|
371
|
+
detail: 'Directory exists',
|
|
372
|
+
};
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// ---------------------------------------------------------------------------
|
|
377
|
+
// Main command
|
|
378
|
+
// ---------------------------------------------------------------------------
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* `@dzhechko/keysarium doctor` — Health check for the toolkit installation.
|
|
382
|
+
*
|
|
383
|
+
* @param {object} options
|
|
384
|
+
* @param {string} options.targetDir — Project root directory
|
|
385
|
+
*/
|
|
386
|
+
async function run(options) {
|
|
387
|
+
const { targetDir } = options;
|
|
388
|
+
const manifestPath = path.join(targetDir, MANIFEST_FILE);
|
|
389
|
+
|
|
390
|
+
// ── a) Check manifest exists ───────────────────────────────────────────
|
|
391
|
+
if (!fileExists(manifestPath)) {
|
|
392
|
+
logError('Keysarium is not installed in this directory.');
|
|
393
|
+
info(`Run ${cyan('@dzhechko/keysarium init')} to install.`);
|
|
394
|
+
process.exit(1);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const manifest = readManifest(targetDir);
|
|
398
|
+
if (!manifest) {
|
|
399
|
+
logError('Failed to read .keysarium.json — file may be corrupted.');
|
|
400
|
+
process.exit(1);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
console.log('');
|
|
404
|
+
info(bold(`Running health checks for @dzhechko/keysarium v${manifest.version}...`));
|
|
405
|
+
console.log('');
|
|
406
|
+
|
|
407
|
+
// ── b) Run all checks ─────────────────────────────────────────────────
|
|
408
|
+
const results = [
|
|
409
|
+
checkFilesExist(manifest, targetDir),
|
|
410
|
+
checkClaudeMd(targetDir),
|
|
411
|
+
checkSkillsComplete(targetDir),
|
|
412
|
+
checkCommandsPresent(targetDir),
|
|
413
|
+
checkRulesPresent(targetDir),
|
|
414
|
+
checkAgentsPresent(targetDir),
|
|
415
|
+
checkSettingsValid(targetDir),
|
|
416
|
+
checkResearchDir(targetDir),
|
|
417
|
+
];
|
|
418
|
+
|
|
419
|
+
// ── c) Display results ─────────────────────────────────────────────────
|
|
420
|
+
let passedCount = 0;
|
|
421
|
+
const failures = [];
|
|
422
|
+
|
|
423
|
+
for (const result of results) {
|
|
424
|
+
const icon = result.passed ? green('\u2713') : red('\u2717');
|
|
425
|
+
const statusColor = result.passed ? green : red;
|
|
426
|
+
const detailStr = result.detail ? dim(` — ${result.detail}`) : '';
|
|
427
|
+
|
|
428
|
+
console.log(` ${icon} ${statusColor(result.name)}${detailStr}`);
|
|
429
|
+
|
|
430
|
+
if (result.passed) {
|
|
431
|
+
passedCount++;
|
|
432
|
+
} else {
|
|
433
|
+
failures.push(result);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// ── d) Summary ─────────────────────────────────────────────────────────
|
|
438
|
+
console.log('');
|
|
439
|
+
const total = results.length;
|
|
440
|
+
|
|
441
|
+
if (passedCount === total) {
|
|
442
|
+
success(bold(`${passedCount}/${total} checks passed — installation is healthy!`));
|
|
443
|
+
console.log('');
|
|
444
|
+
process.exit(0);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
warn(bold(`${passedCount}/${total} checks passed`));
|
|
448
|
+
console.log('');
|
|
449
|
+
|
|
450
|
+
// ── e) Show fix suggestions ────────────────────────────────────────────
|
|
451
|
+
console.log(bold('Fix suggestions:'));
|
|
452
|
+
console.log('');
|
|
453
|
+
|
|
454
|
+
for (const failure of failures) {
|
|
455
|
+
console.log(` ${red('\u2717')} ${bold(failure.name)}`);
|
|
456
|
+
if (failure.detail) {
|
|
457
|
+
console.log(` ${dim(failure.detail)}`);
|
|
458
|
+
}
|
|
459
|
+
if (failure.fix) {
|
|
460
|
+
console.log(` ${yellow('Fix:')} ${failure.fix}`);
|
|
461
|
+
}
|
|
462
|
+
console.log('');
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
process.exit(1);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
module.exports = run;
|
|
469
|
+
module.exports.run = run;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const {
|
|
6
|
+
green, yellow, cyan, bold, dim,
|
|
7
|
+
info, success, warn, error: logError, step,
|
|
8
|
+
copyDirRecursive, fileExists, readJSON,
|
|
9
|
+
ensureDir, getRelativePaths,
|
|
10
|
+
createManifest, writeManifest, getTemplatesDir,
|
|
11
|
+
COMPONENTS, MANIFEST_FILE,
|
|
12
|
+
} = require('../utils');
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Helpers
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Determine which component keys to install based on CLI flags.
|
|
20
|
+
* Default: core group + lib
|
|
21
|
+
* --minimal: core group only (no lib)
|
|
22
|
+
* --with-docs: add docs group
|
|
23
|
+
*/
|
|
24
|
+
function resolveComponents(options) {
|
|
25
|
+
const { minimal, withDocs } = options;
|
|
26
|
+
const coreKeys = ['skills', 'commands', 'rules', 'agents', 'hooks', 'claude_md', 'harvest'];
|
|
27
|
+
const keys = [...coreKeys];
|
|
28
|
+
|
|
29
|
+
if (!minimal) {
|
|
30
|
+
keys.push('lib');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (withDocs) {
|
|
34
|
+
keys.push('docs');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return keys.filter((k) => COMPONENTS[k]);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Copy a single component from templates to the target directory.
|
|
42
|
+
* Returns array of relative file paths that were installed.
|
|
43
|
+
*/
|
|
44
|
+
function installComponent(comp, templatesDir, targetDir) {
|
|
45
|
+
const src = path.join(templatesDir, comp.src);
|
|
46
|
+
const dest = path.join(targetDir, comp.src);
|
|
47
|
+
|
|
48
|
+
if (!fileExists(src)) {
|
|
49
|
+
warn(`Template source not found: ${comp.src} — skipping.`);
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (comp.isFile) {
|
|
54
|
+
ensureDir(path.dirname(dest));
|
|
55
|
+
fs.copyFileSync(src, dest);
|
|
56
|
+
return [comp.src];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
copyDirRecursive(src, dest);
|
|
60
|
+
return getRelativePaths(dest).map((rel) => path.join(comp.src, rel));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Main command
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* `@dzhechko/keysarium init` — Install the toolkit into the target project.
|
|
69
|
+
*
|
|
70
|
+
* @param {object} options
|
|
71
|
+
* @param {boolean} options.minimal — Install only core (no lib)
|
|
72
|
+
* @param {boolean} options.force — Overwrite existing installation
|
|
73
|
+
* @param {boolean} options.withDocs — Include docs/ directory
|
|
74
|
+
* @param {boolean} options.dryRun — Preview without writing anything
|
|
75
|
+
* @param {string} options.targetDir — Destination project root
|
|
76
|
+
*/
|
|
77
|
+
async function run(options) {
|
|
78
|
+
const { force, dryRun, targetDir } = options;
|
|
79
|
+
const manifestPath = path.join(targetDir, MANIFEST_FILE);
|
|
80
|
+
|
|
81
|
+
// ── a) Check for existing installation ──────────────────────────────────
|
|
82
|
+
if (fileExists(manifestPath)) {
|
|
83
|
+
if (!force) {
|
|
84
|
+
warn('Keysarium is already installed in this directory.');
|
|
85
|
+
info(`Run ${cyan('@dzhechko/keysarium update')} to update, or use ${yellow('--force')} to overwrite.`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
warn('Existing installation found — overwriting (--force).');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ── b) Determine components ─────────────────────────────────────────────
|
|
92
|
+
const componentKeys = resolveComponents(options);
|
|
93
|
+
const templatesDir = getTemplatesDir();
|
|
94
|
+
|
|
95
|
+
// ── c) Show plan ────────────────────────────────────────────────────────
|
|
96
|
+
console.log('');
|
|
97
|
+
info(bold('Installation plan:'));
|
|
98
|
+
console.log('');
|
|
99
|
+
|
|
100
|
+
for (const key of componentKeys) {
|
|
101
|
+
const comp = COMPONENTS[key];
|
|
102
|
+
console.log(` ${green('+')} ${comp.label}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log('');
|
|
106
|
+
|
|
107
|
+
if (dryRun) {
|
|
108
|
+
warn('Dry run — no files were written.');
|
|
109
|
+
process.exit(0);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── d) Install components ───────────────────────────────────────────────
|
|
113
|
+
const totalComponents = componentKeys.length;
|
|
114
|
+
const installedFiles = [];
|
|
115
|
+
|
|
116
|
+
for (let i = 0; i < totalComponents; i++) {
|
|
117
|
+
const key = componentKeys[i];
|
|
118
|
+
const comp = COMPONENTS[key];
|
|
119
|
+
step(i + 1, totalComponents, `Installing ${comp.label}...`);
|
|
120
|
+
|
|
121
|
+
const files = installComponent(comp, templatesDir, targetDir);
|
|
122
|
+
installedFiles.push(...files);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── e) Create researches/ directory ─────────────────────────────────────
|
|
126
|
+
const researchesDir = path.join(targetDir, 'researches');
|
|
127
|
+
ensureDir(researchesDir);
|
|
128
|
+
const gitkeepPath = path.join(researchesDir, '.gitkeep');
|
|
129
|
+
if (!fileExists(gitkeepPath)) {
|
|
130
|
+
fs.writeFileSync(gitkeepPath, '', 'utf8');
|
|
131
|
+
}
|
|
132
|
+
info('Created researches/ directory with .gitkeep');
|
|
133
|
+
|
|
134
|
+
// ── f) Write manifest ──────────────────────────────────────────────────
|
|
135
|
+
const pkgPath = path.resolve(__dirname, '../../package.json');
|
|
136
|
+
const pkg = readJSON(pkgPath);
|
|
137
|
+
const version = pkg ? pkg.version : '0.0.0';
|
|
138
|
+
|
|
139
|
+
const manifest = createManifest(version, componentKeys, installedFiles.sort());
|
|
140
|
+
writeManifest(targetDir, manifest);
|
|
141
|
+
info('Created .keysarium.json manifest');
|
|
142
|
+
|
|
143
|
+
// ── g) Success summary ─────────────────────────────────────────────────
|
|
144
|
+
console.log('');
|
|
145
|
+
success(bold('Installation complete!'));
|
|
146
|
+
console.log('');
|
|
147
|
+
|
|
148
|
+
console.log(bold('Installed components:'));
|
|
149
|
+
for (const key of componentKeys) {
|
|
150
|
+
const comp = COMPONENTS[key];
|
|
151
|
+
console.log(` ${green('\u2713')} ${comp.label}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
console.log('');
|
|
155
|
+
console.log(bold('Next steps:'));
|
|
156
|
+
console.log(` 1. Open ${cyan('Claude Code')} in this directory`);
|
|
157
|
+
console.log(` 2. Run ${cyan('/casarium [your case description]')}`);
|
|
158
|
+
console.log(` 3. Follow the 7-phase pipeline`);
|
|
159
|
+
console.log('');
|
|
160
|
+
console.log(bold('Available commands:'));
|
|
161
|
+
console.log(` ${cyan('/casarium')} ${dim('\u2014 Full pipeline')}`);
|
|
162
|
+
console.log(` ${cyan('/new-research')} ${dim('\u2014 New research')}`);
|
|
163
|
+
console.log(` ${cyan('/parallel-research')} ${dim('\u2014 Multiple cases in parallel')}`);
|
|
164
|
+
console.log(` ${cyan('/harvest')} ${dim('\u2014 Extract knowledge')}`);
|
|
165
|
+
console.log('');
|
|
166
|
+
|
|
167
|
+
process.exit(0);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
module.exports = run;
|
|
171
|
+
module.exports.run = run;
|