@dzhechkov/p-replicator 1.2.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.
- package/README.md +55 -0
- package/bin/cli.js +0 -0
- package/package.json +10 -3
- package/src/cli.js +23 -6
- package/src/commands/doctor.js +46 -29
- package/src/commands/init.js +61 -8
- package/src/commands/list.js +5 -26
- package/src/commands/update.js +73 -7
- package/src/commands/verify.js +111 -0
- package/src/utils.js +275 -4
- package/templates/.claude/commands/deploy.md +100 -0
- package/templates/.claude/commands/docs.md +79 -0
- package/templates/.claude/commands/feature.md +134 -0
- package/templates/.claude/commands/go.md +115 -0
- package/templates/.claude/commands/myinsights.md +72 -0
- package/templates/.claude/commands/next.md +110 -0
- package/templates/.claude/commands/plan.md +88 -0
- package/templates/.claude/commands/replicate.md +103 -17
- package/templates/.claude/commands/run.md +151 -0
- package/templates/.claude/commands/start.md +88 -0
- package/templates/.claude/hooks/autocommit-insights.cjs +39 -0
- package/templates/.claude/hooks/autocommit-plans.cjs +39 -0
- package/templates/.claude/hooks/autocommit-roadmap.cjs +44 -0
- package/templates/.claude/hooks/session-insights.cjs +28 -0
- package/templates/.claude/hooks/state-update.cjs +79 -0
- package/templates/.claude/hooks/statusline.cjs +399 -0
- package/templates/.claude/rules/feature-lifecycle.md +145 -0
- package/templates/.claude/rules/git-workflow.md +74 -0
- package/templates/.claude/rules/insights-capture.md +77 -0
- package/templates/.claude/rules/replicate-pipeline.md +97 -20
- package/templates/.claude/settings.json +44 -0
- package/templates/.claude/skills/brutal-honesty-review/SKILL.md +43 -0
- package/templates/.claude/skills/brutal-honesty-review/scripts/assess-code.sh +0 -0
- package/templates/.claude/skills/brutal-honesty-review/scripts/assess-tests.sh +0 -0
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/SKILL.md +8 -0
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/03-generate-p0.md +46 -0
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/04-generate-p1.md +9 -0
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/05-generate-p2p3.md +11 -0
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/06-package-deliver.md +46 -0
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/security-patterns-library.md +196 -0
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/automation-commands.md +15 -0
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/ddd-agents.md +39 -0
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/feature-lifecycle.md +8 -0
- package/templates/.claude/skills/goap-research-ed25519/scripts/ed25519_verifier.py +0 -0
- package/templates/.claude/skills/goap-research-ed25519/scripts/goap_planner.py +0 -0
- package/templates/.claude/skills/requirements-validator/SKILL.md +25 -1
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* P-Replicator statusline (v1.5.0)
|
|
6
|
+
*
|
|
7
|
+
* Invoked by Claude Code via settings.json `statusLine` config. Output to
|
|
8
|
+
* stdout becomes the status bar above the prompt. Multi-line + ANSI colors.
|
|
9
|
+
*
|
|
10
|
+
* Sources: filesystem heuristics + optional state-file (.claude/.p-replicator-state.json)
|
|
11
|
+
* written by /run, /feature, /replicate via .claude/hooks/state-update.cjs.
|
|
12
|
+
*
|
|
13
|
+
* Defensive: every section is wrapped in try/catch with sensible fallback,
|
|
14
|
+
* so a parse error in any single source never breaks the whole status bar.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('node:fs');
|
|
18
|
+
const path = require('node:path');
|
|
19
|
+
|
|
20
|
+
const CWD = process.cwd();
|
|
21
|
+
const NOW = Date.now();
|
|
22
|
+
|
|
23
|
+
// ─── ANSI helpers ─────────────────────────────────────────────────────────
|
|
24
|
+
const C = {
|
|
25
|
+
reset: '\x1b[0m',
|
|
26
|
+
bold: '\x1b[1m',
|
|
27
|
+
dim: '\x1b[2m',
|
|
28
|
+
red: '\x1b[31m',
|
|
29
|
+
green: '\x1b[32m',
|
|
30
|
+
yellow: '\x1b[33m',
|
|
31
|
+
blue: '\x1b[34m',
|
|
32
|
+
magenta:'\x1b[35m',
|
|
33
|
+
cyan: '\x1b[36m',
|
|
34
|
+
gray: '\x1b[90m',
|
|
35
|
+
};
|
|
36
|
+
function color(c, s) { return c + s + C.reset; }
|
|
37
|
+
function bold(s) { return C.bold + s + C.reset; }
|
|
38
|
+
function dim(s) { return C.dim + s + C.reset; }
|
|
39
|
+
function green(s) { return C.green + s + C.reset; }
|
|
40
|
+
function yellow(s){ return C.yellow + s + C.reset; }
|
|
41
|
+
function red(s) { return C.red + s + C.reset; }
|
|
42
|
+
function cyan(s) { return C.cyan + s + C.reset; }
|
|
43
|
+
function gray(s) { return C.gray + s + C.reset; }
|
|
44
|
+
|
|
45
|
+
// ─── safe wrappers ────────────────────────────────────────────────────────
|
|
46
|
+
function safeRun(fn, fallback) {
|
|
47
|
+
try { return fn(); } catch { return fallback; }
|
|
48
|
+
}
|
|
49
|
+
function safeReadJson(p) {
|
|
50
|
+
try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return null; }
|
|
51
|
+
}
|
|
52
|
+
function safeReadText(p) {
|
|
53
|
+
try { return fs.readFileSync(p, 'utf8'); } catch { return null; }
|
|
54
|
+
}
|
|
55
|
+
function safeListDir(p) {
|
|
56
|
+
try { return fs.readdirSync(p); } catch { return []; }
|
|
57
|
+
}
|
|
58
|
+
function exists(p) {
|
|
59
|
+
try { fs.accessSync(p); return true; } catch { return false; }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ─── parsers (heuristic) ──────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
function parseManifest() {
|
|
65
|
+
return safeReadJson(path.join(CWD, '.p-replicator.json'));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function parseState() {
|
|
69
|
+
const p = path.join(CWD, '.claude', '.p-replicator-state.json');
|
|
70
|
+
const state = safeReadJson(p);
|
|
71
|
+
if (!state) return null;
|
|
72
|
+
// Stale check: state older than 30 min — treat as not-running
|
|
73
|
+
if (state.updatedAt) {
|
|
74
|
+
const age = NOW - new Date(state.updatedAt).getTime();
|
|
75
|
+
if (age > 30 * 60 * 1000) return null;
|
|
76
|
+
}
|
|
77
|
+
return state;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function parseRoadmap() {
|
|
81
|
+
const r = safeReadJson(path.join(CWD, '.claude', 'feature-roadmap.json'));
|
|
82
|
+
if (!r || !Array.isArray(r.features)) return null;
|
|
83
|
+
const features = r.features;
|
|
84
|
+
const total = features.length;
|
|
85
|
+
const done = features.filter((f) => f.status === 'done').length;
|
|
86
|
+
const inProgress = features.find((f) => f.status === 'in_progress');
|
|
87
|
+
const blocked = features.filter((f) => f.status === 'blocked').length;
|
|
88
|
+
const mvp = features.filter((f) => f.priority === 'mvp');
|
|
89
|
+
const mvpDone = mvp.filter((f) => f.status === 'done').length;
|
|
90
|
+
return { total, done, inProgress, blocked, mvpTotal: mvp.length, mvpDone };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function parseSparcDocs() {
|
|
94
|
+
const docsDir = path.join(CWD, 'docs');
|
|
95
|
+
const expectedSparc = [
|
|
96
|
+
'PRD.md', 'Solution_Strategy.md', 'Specification.md', 'Pseudocode.md',
|
|
97
|
+
'Architecture.md', 'Refinement.md', 'Completion.md',
|
|
98
|
+
'Research_Findings.md', 'Final_Summary.md', 'C4_Diagrams.md', 'ADR.md',
|
|
99
|
+
];
|
|
100
|
+
const present = expectedSparc.filter((f) => exists(path.join(docsDir, f)));
|
|
101
|
+
return { present: present.length, total: expectedSparc.length };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function parseValidationScore() {
|
|
105
|
+
const p = path.join(CWD, 'docs', 'validation-report.md');
|
|
106
|
+
const text = safeReadText(p);
|
|
107
|
+
if (!text) return null;
|
|
108
|
+
// Look for "Average Score: XX/100" or "Score: XX" patterns
|
|
109
|
+
const m = text.match(/(?:average\s+)?score[:\s]+(\d{1,3})(?:\s*\/\s*100)?/i);
|
|
110
|
+
if (!m) return null;
|
|
111
|
+
const score = parseInt(m[1], 10);
|
|
112
|
+
let verdict;
|
|
113
|
+
if (score >= 70) verdict = 'READY';
|
|
114
|
+
else if (score >= 50) verdict = 'CAVEATS';
|
|
115
|
+
else verdict = 'NEEDS_WORK';
|
|
116
|
+
return { score, verdict };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function parseAdrs() {
|
|
120
|
+
// Prefer docs/ADR.md (single file with ## ADR-N headings)
|
|
121
|
+
const single = path.join(CWD, 'docs', 'ADR.md');
|
|
122
|
+
if (exists(single)) {
|
|
123
|
+
const text = safeReadText(single) || '';
|
|
124
|
+
const headings = text.match(/^#{2,3}\s+ADR/gm) || [];
|
|
125
|
+
if (headings.length > 0) return headings.length;
|
|
126
|
+
}
|
|
127
|
+
// Or docs/adr/*.md (per-ADR files)
|
|
128
|
+
const dir = path.join(CWD, 'docs', 'adr');
|
|
129
|
+
const files = safeListDir(dir).filter((f) => f.endsWith('.md'));
|
|
130
|
+
if (files.length > 0) return files.length;
|
|
131
|
+
// Or docs/ddd/adr/*.md (DDD pipeline)
|
|
132
|
+
const dddDir = path.join(CWD, 'docs', 'ddd', 'adr');
|
|
133
|
+
const dddFiles = safeListDir(dddDir).filter((f) => f.endsWith('.md'));
|
|
134
|
+
return dddFiles.length;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function parsePlans() {
|
|
138
|
+
const dir = path.join(CWD, 'docs', 'plans');
|
|
139
|
+
return safeListDir(dir).filter((f) => f.endsWith('.md')).length;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function parseInsights() {
|
|
143
|
+
const p = path.join(CWD, '.claude', 'insights', 'index.md');
|
|
144
|
+
const text = safeReadText(p);
|
|
145
|
+
if (!text) return { count: 0, lastDate: null };
|
|
146
|
+
const headings = text.match(/^##\s+\d{4}-\d{2}-\d{2}/gm) || [];
|
|
147
|
+
// Last date: extract from last heading
|
|
148
|
+
let lastDate = null;
|
|
149
|
+
if (headings.length > 0) {
|
|
150
|
+
const last = headings[headings.length - 1];
|
|
151
|
+
const m = last.match(/\d{4}-\d{2}-\d{2}/);
|
|
152
|
+
if (m) lastDate = m[0];
|
|
153
|
+
}
|
|
154
|
+
return { count: headings.length, lastDate };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function parseToolkit() {
|
|
158
|
+
const dir = path.join(CWD, '.claude');
|
|
159
|
+
const skills = safeListDir(path.join(dir, 'skills')).filter((d) => {
|
|
160
|
+
return safeRun(() => fs.statSync(path.join(dir, 'skills', d)).isDirectory(), false);
|
|
161
|
+
}).length;
|
|
162
|
+
const commands = safeListDir(path.join(dir, 'commands'))
|
|
163
|
+
.filter((f) => f.endsWith('.md')).length;
|
|
164
|
+
const agents = safeListDir(path.join(dir, 'agents'))
|
|
165
|
+
.filter((f) => f.endsWith('.md')).length;
|
|
166
|
+
const rules = safeListDir(path.join(dir, 'rules'))
|
|
167
|
+
.filter((f) => f.endsWith('.md')).length;
|
|
168
|
+
const hooks = safeListDir(path.join(dir, 'hooks'))
|
|
169
|
+
.filter((f) => f.endsWith('.cjs')).length;
|
|
170
|
+
return { skills, commands, agents, rules, hooks };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function parseExpectedToolkit() {
|
|
174
|
+
// From manifest's components if shippedDefaults available; otherwise fall back to baseline
|
|
175
|
+
return {
|
|
176
|
+
skillsExpected: 10,
|
|
177
|
+
commandsExpected: 11,
|
|
178
|
+
agentsExpected: 4, // pre-shipped only (project agents are extra)
|
|
179
|
+
rulesExpected: 5, // pre-shipped only (project rules are extra)
|
|
180
|
+
hooksExpected: 6, // 4 v1.4.1 hooks + statusline + state-update (v1.5.0)
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function parseSettingsStatus(manifest) {
|
|
185
|
+
const settingsPath = path.join(CWD, '.claude', 'settings.json');
|
|
186
|
+
if (!exists(settingsPath)) return 'missing';
|
|
187
|
+
const cur = safeReadJson(settingsPath);
|
|
188
|
+
if (!cur) return 'corrupt';
|
|
189
|
+
const shipped = manifest && manifest.shippedDefaults && manifest.shippedDefaults['settings.json'];
|
|
190
|
+
if (!shipped) return 'unknown';
|
|
191
|
+
// If exact match: defaults; else: merged (user customized)
|
|
192
|
+
try {
|
|
193
|
+
const sortKeys = (o) => JSON.stringify(o, Object.keys(o).sort());
|
|
194
|
+
return sortKeys(cur) === sortKeys(shipped) ? 'defaults' : 'merged';
|
|
195
|
+
} catch {
|
|
196
|
+
return 'unknown';
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function parseMcpServers() {
|
|
201
|
+
const mcpJson = safeReadJson(path.join(CWD, '.mcp.json'));
|
|
202
|
+
if (!mcpJson) return null;
|
|
203
|
+
const servers = mcpJson.mcpServers || mcpJson.servers || {};
|
|
204
|
+
return Object.keys(servers).length;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function parseKeysarium() {
|
|
208
|
+
return exists(path.join(CWD, '.keysarium.json'));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function parseDomain() {
|
|
212
|
+
const claudeMd = safeReadText(path.join(CWD, 'CLAUDE.md'));
|
|
213
|
+
if (!claudeMd) return null;
|
|
214
|
+
// Heuristic keyword search
|
|
215
|
+
const banking = /банк|bank|финт|fintech|gigachat|yandexgpt|ФЗ-152|ЦБ|ФСТЭК/i;
|
|
216
|
+
const retail = /retail|e-commerce|ecommerce|ритейл|рекоменд|conversion/i;
|
|
217
|
+
const enterprise = /enterprise|b2b|legacy|sla|change\s*management/i;
|
|
218
|
+
const healthcare = /health|medical|клиник|hipaa|ФЗ-323/i;
|
|
219
|
+
if (banking.test(claudeMd)) return 'banking';
|
|
220
|
+
if (retail.test(claudeMd)) return 'retail';
|
|
221
|
+
if (enterprise.test(claudeMd)) return 'enterprise';
|
|
222
|
+
if (healthcare.test(claudeMd)) return 'healthcare';
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function parseLastHarvest() {
|
|
227
|
+
// Heuristic: TOOLKIT_HARVEST.md mtime
|
|
228
|
+
const p = path.join(CWD, 'TOOLKIT_HARVEST.md');
|
|
229
|
+
try {
|
|
230
|
+
const stat = fs.statSync(p);
|
|
231
|
+
return stat.mtime.toISOString().slice(0, 10);
|
|
232
|
+
} catch { return null; }
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function parseLastTest() {
|
|
236
|
+
// Optional cache file written by users
|
|
237
|
+
const p = path.join(CWD, '.claude', '.last-test.json');
|
|
238
|
+
return safeReadJson(p);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ─── progress bar ─────────────────────────────────────────────────────────
|
|
242
|
+
function bar(progress, width = 8) {
|
|
243
|
+
const fill = Math.round(progress * width);
|
|
244
|
+
const empty = width - fill;
|
|
245
|
+
return '▓'.repeat(fill) + '░'.repeat(empty);
|
|
246
|
+
}
|
|
247
|
+
function dotBar(done, total, width = 8) {
|
|
248
|
+
const w = Math.min(width, Math.max(total, 1));
|
|
249
|
+
const fill = Math.round((done / Math.max(total, 1)) * w);
|
|
250
|
+
return '['+ '●'.repeat(fill) + '○'.repeat(w - fill) + ']';
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ─── line builders ───────────────────────────────────────────────────────
|
|
254
|
+
function buildHeader(manifest) {
|
|
255
|
+
const ver = (manifest && manifest.version) || '?';
|
|
256
|
+
const user = process.env.USER || process.env.USERNAME || 'user';
|
|
257
|
+
const model = process.env.CLAUDE_MODEL || process.env.MODEL || 'Claude';
|
|
258
|
+
return `${cyan(bold('P-Replicator'))} ${dim('V' + ver)} ${green('●')} ${user} ${dim('│')} ${model}`;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function buildPipeline(state) {
|
|
262
|
+
const parts = ['🚀 ' + bold('Pipeline')];
|
|
263
|
+
if (state && state.currentCommand) {
|
|
264
|
+
const cmd = state.currentCommand;
|
|
265
|
+
const phase = state.currentPhase;
|
|
266
|
+
if (phase) {
|
|
267
|
+
const progress = typeof phase.progress === 'number' ? phase.progress : 0;
|
|
268
|
+
const idx = phase.index ?? '?';
|
|
269
|
+
const total = phase.total ?? '?';
|
|
270
|
+
parts.push(`${cyan(cmd)} ${bar(progress)} ${Math.round(progress*100)}%`);
|
|
271
|
+
parts.push(`${dim('Phase:')} ${phase.name || ''} (${idx}/${total})`);
|
|
272
|
+
} else {
|
|
273
|
+
parts.push(cyan(cmd));
|
|
274
|
+
}
|
|
275
|
+
if (state.lastCommand && state.lastCommand !== cmd) {
|
|
276
|
+
parts.push(`${dim('Last:')} ${state.lastCommand}`);
|
|
277
|
+
}
|
|
278
|
+
} else {
|
|
279
|
+
parts.push(dim('idle'));
|
|
280
|
+
}
|
|
281
|
+
return parts.join(' ' + dim('│') + ' ');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function buildRoadmap(roadmap, domain) {
|
|
285
|
+
if (!roadmap) {
|
|
286
|
+
return `🎯 ${bold('Roadmap')} ${dim('— no roadmap yet (run /next or /replicate)')}`;
|
|
287
|
+
}
|
|
288
|
+
const { total, done, inProgress, blocked, mvpTotal, mvpDone } = roadmap;
|
|
289
|
+
const dotbar = dotBar(done, total, 8);
|
|
290
|
+
const parts = [
|
|
291
|
+
`🎯 ${bold('Roadmap')}`,
|
|
292
|
+
`${dotbar} mvp ${green(mvpDone + '/' + mvpTotal)}`,
|
|
293
|
+
`${dim('Done')} ${green(done + '/' + total)}`,
|
|
294
|
+
];
|
|
295
|
+
if (inProgress) {
|
|
296
|
+
parts.push(`${dim('▶')} ${cyan(inProgress.id)}`);
|
|
297
|
+
}
|
|
298
|
+
if (blocked > 0) {
|
|
299
|
+
parts.push(`${red('Blocked')} ${blocked}`);
|
|
300
|
+
}
|
|
301
|
+
if (domain) {
|
|
302
|
+
parts.push(`${dim('Domain:')} ${cyan(domain)}`);
|
|
303
|
+
}
|
|
304
|
+
return parts.join(' ' + dim('│') + ' ');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function buildDocs(sparc, validation, plans, adrs, lastHarvest) {
|
|
308
|
+
const parts = [`📊 ${bold('SPARC')} ${green('●' + sparc.present + '/' + sparc.total)}`];
|
|
309
|
+
if (validation) {
|
|
310
|
+
const verdictIcon = validation.verdict === 'READY' ? '🟢'
|
|
311
|
+
: validation.verdict === 'CAVEATS' ? '🟡' : '🔴';
|
|
312
|
+
parts.push(`${verdictIcon} ${validation.score}/100`);
|
|
313
|
+
}
|
|
314
|
+
parts.push(`${dim('Plans')} ${plans > 0 ? green('●' + plans) : '0'}`);
|
|
315
|
+
parts.push(`${dim('ADRs')} ${adrs > 0 ? green('●' + adrs) : '0'}`);
|
|
316
|
+
if (lastHarvest) {
|
|
317
|
+
parts.push(`${dim('Harvest')} ${lastHarvest}`);
|
|
318
|
+
}
|
|
319
|
+
return parts.join(' ' + dim('│') + ' ');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function buildToolkit(toolkit, expected) {
|
|
323
|
+
const dotIf = (v, e) => v >= e ? green('●' + v + '/' + e) : yellow('●' + v + '/' + e);
|
|
324
|
+
const extraAgents = Math.max(0, toolkit.agents - expected.agentsExpected);
|
|
325
|
+
const extraRules = Math.max(0, toolkit.rules - expected.rulesExpected);
|
|
326
|
+
const agentsLbl = toolkit.agents >= expected.agentsExpected
|
|
327
|
+
? green('●' + expected.agentsExpected + (extraAgents > 0 ? '+' + extraAgents : ''))
|
|
328
|
+
: yellow('●' + toolkit.agents + '/' + expected.agentsExpected);
|
|
329
|
+
const rulesLbl = toolkit.rules >= expected.rulesExpected
|
|
330
|
+
? green('●' + expected.rulesExpected + (extraRules > 0 ? '+' + extraRules : ''))
|
|
331
|
+
: yellow('●' + toolkit.rules + '/' + expected.rulesExpected);
|
|
332
|
+
return [
|
|
333
|
+
`🛠️ ${bold('Toolkit')}`,
|
|
334
|
+
`Skills ${dotIf(toolkit.skills, expected.skillsExpected)}`,
|
|
335
|
+
`Cmds ${dotIf(toolkit.commands, expected.commandsExpected)}`,
|
|
336
|
+
`Agents ${agentsLbl}`,
|
|
337
|
+
`Rules ${rulesLbl}`,
|
|
338
|
+
`Hooks ${dotIf(toolkit.hooks, expected.hooksExpected)}`,
|
|
339
|
+
].join(' ' + dim('│') + ' ');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function buildStatus(insights, lastTest, mcpServers, settingsStatus, keysarium) {
|
|
343
|
+
const parts = [];
|
|
344
|
+
parts.push(`💡 ${bold('Insights')} ${insights.count > 0 ? green('●' + insights.count) : '0'}` +
|
|
345
|
+
(insights.lastDate ? ` ${dim('(' + insights.lastDate + ')')}` : ''));
|
|
346
|
+
|
|
347
|
+
if (lastTest && typeof lastTest.passed === 'number') {
|
|
348
|
+
const total = lastTest.total ?? lastTest.passed;
|
|
349
|
+
const ok = lastTest.passed === total;
|
|
350
|
+
parts.push(`${ok ? '✅' : '❌'} ${dim('Tests')} ${ok ? green(lastTest.passed + '/' + total) : red(lastTest.passed + '/' + total)}`);
|
|
351
|
+
}
|
|
352
|
+
if (mcpServers !== null && mcpServers !== undefined) {
|
|
353
|
+
parts.push(`🔌 ${dim('MCP')} ${mcpServers > 0 ? green('●' + mcpServers) : '0'}`);
|
|
354
|
+
}
|
|
355
|
+
if (settingsStatus) {
|
|
356
|
+
const map = { defaults: '✓ defaults', merged: '⚠️ merged', corrupt: red('🔴 corrupt'), missing: red('missing'), unknown: '?' };
|
|
357
|
+
parts.push(`⚙️ ${dim('Settings')} ${map[settingsStatus] || settingsStatus}`);
|
|
358
|
+
}
|
|
359
|
+
if (keysarium) {
|
|
360
|
+
parts.push(`🧬 ${green('Keysarium ✓')}`);
|
|
361
|
+
}
|
|
362
|
+
return parts.join(' ' + dim('│') + ' ');
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ─── main ─────────────────────────────────────────────────────────────────
|
|
366
|
+
function main() {
|
|
367
|
+
const manifest = safeRun(() => parseManifest(), null);
|
|
368
|
+
const state = safeRun(() => parseState(), null);
|
|
369
|
+
const roadmap = safeRun(() => parseRoadmap(), null);
|
|
370
|
+
const sparc = safeRun(() => parseSparcDocs(), { present: 0, total: 11 });
|
|
371
|
+
const validation = safeRun(() => parseValidationScore(), null);
|
|
372
|
+
const adrs = safeRun(() => parseAdrs(), 0);
|
|
373
|
+
const plans = safeRun(() => parsePlans(), 0);
|
|
374
|
+
const insights = safeRun(() => parseInsights(), { count: 0, lastDate: null });
|
|
375
|
+
const toolkit = safeRun(() => parseToolkit(), { skills: 0, commands: 0, agents: 0, rules: 0, hooks: 0 });
|
|
376
|
+
const expected = parseExpectedToolkit();
|
|
377
|
+
const settingsStatus = safeRun(() => parseSettingsStatus(manifest), null);
|
|
378
|
+
const mcpServers = safeRun(() => parseMcpServers(), null);
|
|
379
|
+
const keysarium = safeRun(() => parseKeysarium(), false);
|
|
380
|
+
const domain = safeRun(() => parseDomain(), null);
|
|
381
|
+
const lastHarvest = safeRun(() => parseLastHarvest(), null);
|
|
382
|
+
const lastTest = safeRun(() => parseLastTest(), null);
|
|
383
|
+
|
|
384
|
+
const lines = [
|
|
385
|
+
buildHeader(manifest),
|
|
386
|
+
buildPipeline(state),
|
|
387
|
+
buildRoadmap(roadmap, domain),
|
|
388
|
+
buildDocs(sparc, validation, plans, adrs, lastHarvest),
|
|
389
|
+
buildToolkit(toolkit, expected),
|
|
390
|
+
buildStatus(insights, lastTest, mcpServers, settingsStatus, keysarium),
|
|
391
|
+
];
|
|
392
|
+
|
|
393
|
+
// Write to stdout, never throw
|
|
394
|
+
try {
|
|
395
|
+
process.stdout.write(lines.join('\n') + '\n');
|
|
396
|
+
} catch { /* ignore */ }
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
main();
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Feature Lifecycle Rules
|
|
2
|
+
|
|
3
|
+
Governance for the `/feature` command's 4-phase pipeline. Applies whenever
|
|
4
|
+
a feature touches ≥ 4 files or introduces new architecture (use `/plan` for
|
|
5
|
+
smaller changes).
|
|
6
|
+
|
|
7
|
+
## Entry modes
|
|
8
|
+
|
|
9
|
+
`/feature` officially supports two entry modes. The 4-phase pipeline behaves
|
|
10
|
+
**identically** in both — phase sequence, validation thresholds, retry logic,
|
|
11
|
+
and quality gates are the same.
|
|
12
|
+
|
|
13
|
+
### Mode 1: Post-/replicate (default)
|
|
14
|
+
|
|
15
|
+
Project was bootstrapped via `/replicate`. CLAUDE.md, `docs/` SPARC slots,
|
|
16
|
+
`.claude/` toolkit, and the Docker scaffold are all generated by /replicate.
|
|
17
|
+
`/feature` runs in standard 4-phase mode.
|
|
18
|
+
|
|
19
|
+
### Mode 2: Existing project (post v1.5.2)
|
|
20
|
+
|
|
21
|
+
Project has its own pre-existing CLAUDE.md, PRD, Specification, and stack.
|
|
22
|
+
The user ran `npx @dzhechkov/p-replicator init` to add the toolkit on top.
|
|
23
|
+
`/feature` runs in standard 4-phase mode.
|
|
24
|
+
|
|
25
|
+
#### Mode 2 prerequisites
|
|
26
|
+
|
|
27
|
+
- `init` completed and `verify` reports pre-shipped contract intact
|
|
28
|
+
- Standard SPARC paths populated: `docs/PRD.md`, `docs/Specification.md`,
|
|
29
|
+
`docs/Architecture.md` (Pseudocode/Refinement/Completion optional)
|
|
30
|
+
- Existing CLAUDE.md preserved at project root (init does NOT overwrite without `--force`)
|
|
31
|
+
|
|
32
|
+
#### Mode 2 detection
|
|
33
|
+
|
|
34
|
+
There is no automatic detection — `/feature` doesn't differentiate between
|
|
35
|
+
modes at runtime. Implicit signals (absence of `.p-replicator.json` "fresh"
|
|
36
|
+
markers, absence of Phase 3 toolkit artifacts) are advisory only. The
|
|
37
|
+
pipeline runs the same code path either way.
|
|
38
|
+
|
|
39
|
+
#### Mode 2 caveats
|
|
40
|
+
|
|
41
|
+
- `/feature` reads docs at standard SPARC paths — non-standard locations
|
|
42
|
+
require a one-time rename or symlink (no `--prd-path` flag yet)
|
|
43
|
+
- `/start` MUST NOT be used in Mode 2 (it's for fresh scaffolds only)
|
|
44
|
+
- `/feature-ent` is unavailable in Mode 2 unless DDD/ADR/C4 docs are added
|
|
45
|
+
manually (Phase 3 of /replicate normally generates `/feature-ent`
|
|
46
|
+
conditionally based on detected DDD docs)
|
|
47
|
+
- Auto-commit hooks (`Stop` hook → `autocommit-roadmap.cjs` /
|
|
48
|
+
`autocommit-insights.cjs` / `autocommit-plans.cjs`) may surprise users
|
|
49
|
+
with custom git workflows; `.claude/settings.json` can be edited after
|
|
50
|
+
`init` to disable them — the v1.4.2+ merge logic with `shippedDefaults`
|
|
51
|
+
baseline preserves user customizations across `update` runs
|
|
52
|
+
|
|
53
|
+
#### Mode 2 test contract
|
|
54
|
+
|
|
55
|
+
Same as Mode 1: pre-shipped contract intact, `verify` exit code 0, manifest
|
|
56
|
+
schema unchanged. No new flags or state-file fields are required.
|
|
57
|
+
|
|
58
|
+
## Phase Sequence (strict)
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
PLAN → VALIDATE → IMPLEMENT → REVIEW
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
NEVER skip phases. NEVER reorder.
|
|
65
|
+
|
|
66
|
+
## Phase 1: PLAN (sparc-prd-mini)
|
|
67
|
+
|
|
68
|
+
**Skill:** `.claude/skills/sparc-prd-mini/SKILL.md`
|
|
69
|
+
|
|
70
|
+
**Output:** `docs/features/<feature>/01-05_*.md` (5 SPARC docs).
|
|
71
|
+
**Quality gate:** all 5 docs created, no empty placeholders.
|
|
72
|
+
|
|
73
|
+
## Phase 2: VALIDATE (requirements-validator)
|
|
74
|
+
|
|
75
|
+
**Skill:** `.claude/skills/requirements-validator/SKILL.md`
|
|
76
|
+
|
|
77
|
+
**Output:** `docs/features/<feature>/validation-report.md`
|
|
78
|
+
|
|
79
|
+
| Verdict | Threshold | Next |
|
|
80
|
+
|---------|-----------|------|
|
|
81
|
+
| 🟢 READY | average ≥ 70, no blockers | Phase 3 |
|
|
82
|
+
| 🟡 CAVEATS | 50-69, no blockers | Phase 3 (AUTO retries once on 🟡) |
|
|
83
|
+
| 🔴 NEEDS WORK | < 50 OR any blocker | Phase 1 (max 3 retries) |
|
|
84
|
+
|
|
85
|
+
After 3 retries with 🔴, halt and surface to user.
|
|
86
|
+
|
|
87
|
+
## Phase 3: IMPLEMENT (parallel agents)
|
|
88
|
+
|
|
89
|
+
**Strategy:** maximum parallelism via `Task` tool.
|
|
90
|
+
|
|
91
|
+
1. Identify independent work units from Phase 1's Architecture
|
|
92
|
+
2. Spawn one Task per unit
|
|
93
|
+
3. Each Task: read SPARC sections + implement + test + commit
|
|
94
|
+
4. Coordinator merges/integrates after all Tasks complete
|
|
95
|
+
5. Run full test suite
|
|
96
|
+
|
|
97
|
+
**Quality gate:** tests pass, lint clean, build succeeds.
|
|
98
|
+
|
|
99
|
+
## Phase 4: REVIEW (brutal-honesty-review)
|
|
100
|
+
|
|
101
|
+
**Skill:** `.claude/skills/brutal-honesty-review/SKILL.md`
|
|
102
|
+
|
|
103
|
+
**Output:** `docs/features/<feature>/review-report.md`
|
|
104
|
+
|
|
105
|
+
| Severity | Action |
|
|
106
|
+
|----------|--------|
|
|
107
|
+
| `blocker` | MUST fix before merge |
|
|
108
|
+
| `high` | Fix in this feature unless explicit deferral |
|
|
109
|
+
| `medium` | Optional fix; create follow-up issue |
|
|
110
|
+
| `low` | Logged, no action required |
|
|
111
|
+
|
|
112
|
+
## Quality Gates Summary
|
|
113
|
+
|
|
114
|
+
| Gate | Phase | Failure Action |
|
|
115
|
+
|------|-------|----------------|
|
|
116
|
+
| All SPARC docs created | 1 | Halt — investigate sparc-prd-mini |
|
|
117
|
+
| Validator verdict ≠ 🔴 (after retries) | 2 | Halt — surface to user |
|
|
118
|
+
| Tests + lint + build | 3 | Re-run, max 3 attempts |
|
|
119
|
+
| No `blocker` findings | 4 | Loop Phase 4 until clean |
|
|
120
|
+
|
|
121
|
+
## Commit Discipline
|
|
122
|
+
|
|
123
|
+
- After Phase 1: `docs(<feature>): SPARC plan`
|
|
124
|
+
- After Phase 2: `docs(<feature>): validation report`
|
|
125
|
+
- After Phase 3: `feat(<feature>): implement [phase 3]`
|
|
126
|
+
- After Phase 4: `feat(<feature>): review fixes [phase 4]`
|
|
127
|
+
|
|
128
|
+
## AUTO Mode (called from /go or /run)
|
|
129
|
+
|
|
130
|
+
Skip per-phase confirmation prompts. Auto-decisions per Phase 1-4 above.
|
|
131
|
+
|
|
132
|
+
## When `/feature-ent` is the right choice
|
|
133
|
+
|
|
134
|
+
If `.claude/commands/feature-ent.md` exists, prefer it for: cross-bounded-context,
|
|
135
|
+
new ADRs, C4 updates. `/go` makes this decision automatically.
|
|
136
|
+
|
|
137
|
+
## Anti-Patterns
|
|
138
|
+
|
|
139
|
+
| Anti-Pattern | Why bad |
|
|
140
|
+
|--------------|---------|
|
|
141
|
+
| Skipping Phase 2 (validation) | Phase 3 builds on unvalidated requirements |
|
|
142
|
+
| Implementing in Phase 1 | "Plan" should not contain code |
|
|
143
|
+
| One giant commit at the end | Hard to bisect, hard to roll back phases |
|
|
144
|
+
| Ignoring Phase 4 findings | brutal-honesty-review has measurable defect-catch rate |
|
|
145
|
+
| Adding to scope mid-pipeline | Restart `/feature` if scope grew significantly |
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Git Workflow Rules
|
|
2
|
+
|
|
3
|
+
Project-wide commit, branch, and push discipline. Applies to all commands
|
|
4
|
+
that modify files.
|
|
5
|
+
|
|
6
|
+
## Branch Strategy
|
|
7
|
+
|
|
8
|
+
- `main` — production-ready, protected
|
|
9
|
+
- `feature/<id>-<slug>` — per-feature work
|
|
10
|
+
- `hotfix/<slug>` — emergency fixes
|
|
11
|
+
|
|
12
|
+
## Commit Discipline
|
|
13
|
+
|
|
14
|
+
### Cadence
|
|
15
|
+
|
|
16
|
+
Commit after each **logical change**, not at session end. NEVER batch unrelated changes.
|
|
17
|
+
|
|
18
|
+
### Message Format (Conventional Commits)
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
<type>(<scope>): <subject>
|
|
22
|
+
|
|
23
|
+
[body — what + why, NOT how]
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
| Type | Use for |
|
|
27
|
+
|------|---------|
|
|
28
|
+
| `feat` | New user-facing feature |
|
|
29
|
+
| `fix` | Bug fix |
|
|
30
|
+
| `docs` | Documentation only |
|
|
31
|
+
| `refactor` | Behavior unchanged |
|
|
32
|
+
| `test` | Tests only |
|
|
33
|
+
| `chore` | Build, deps, scaffolding |
|
|
34
|
+
| `perf` | Performance |
|
|
35
|
+
| `style` | Formatting |
|
|
36
|
+
| `ci` | CI/CD |
|
|
37
|
+
|
|
38
|
+
### Examples
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
feat(auth): JWT-based login with refresh tokens
|
|
42
|
+
fix(payment): handle Stripe webhook retry idempotency
|
|
43
|
+
docs(roadmap): mark auth-jwt as done
|
|
44
|
+
chore: bump @dzhechkov/p-replicator to 1.4.0
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Push Discipline
|
|
48
|
+
|
|
49
|
+
- Push after each successful phase completion
|
|
50
|
+
- `git push origin HEAD`
|
|
51
|
+
- Force-push (`--force-with-lease`) ONLY on personal feature branches
|
|
52
|
+
|
|
53
|
+
## Pre-Commit Hooks (auto-installed by `init` via `settings.json`)
|
|
54
|
+
|
|
55
|
+
| Hook | Purpose |
|
|
56
|
+
|------|---------|
|
|
57
|
+
| `Stop` autocommit-roadmap | Commits `.claude/feature-roadmap.json` if changed |
|
|
58
|
+
| `Stop` autocommit-insights | Commits `.claude/insights/` if changed |
|
|
59
|
+
| `Stop` autocommit-plans | Commits new `docs/plans/*.md` if added |
|
|
60
|
+
|
|
61
|
+
## Forbidden
|
|
62
|
+
|
|
63
|
+
- `git commit --no-verify` (without explicit user approval)
|
|
64
|
+
- `git push --force` to `main`
|
|
65
|
+
- Committing `.env`, `.env.*` (except `.env.example`)
|
|
66
|
+
- Committing secrets, API keys, passwords
|
|
67
|
+
- Committing `node_modules/`, `dist/`, `coverage/`
|
|
68
|
+
|
|
69
|
+
## Pre-Push Self-Check
|
|
70
|
+
|
|
71
|
+
- [ ] Tests pass
|
|
72
|
+
- [ ] Lint clean
|
|
73
|
+
- [ ] No secrets in diff
|
|
74
|
+
- [ ] Branch up-to-date with origin/main
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Insights Capture Rules
|
|
2
|
+
|
|
3
|
+
When and how to record development "грабли" (rakes) into the project knowledge
|
|
4
|
+
base. Used by `/myinsights` and the `SessionStart` hook
|
|
5
|
+
(`.claude/hooks/session-insights.cjs`).
|
|
6
|
+
|
|
7
|
+
## When to Capture
|
|
8
|
+
|
|
9
|
+
CAPTURE an insight whenever:
|
|
10
|
+
- A bug took > 15 min to root-cause
|
|
11
|
+
- A library / tool behaved differently than docs claimed
|
|
12
|
+
- A workaround was needed for a third-party limitation
|
|
13
|
+
- A non-obvious config or env var fixed a deploy issue
|
|
14
|
+
- An algorithm choice paid off (or didn't) in noticeable ways
|
|
15
|
+
|
|
16
|
+
DO NOT capture:
|
|
17
|
+
- One-line typos or simple syntax errors
|
|
18
|
+
- Information already in official docs that's easy to find
|
|
19
|
+
- Personal preference notes
|
|
20
|
+
|
|
21
|
+
## How to Capture (via /myinsights)
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
/myinsights "Prisma migrate dev fails silently if shadow DB unreachable. Workaround: set DATABASE_URL_SHADOW explicitly."
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The command parses problem + solution + tags, appends a structured entry to
|
|
28
|
+
`.claude/insights/index.md`, auto-commits via Stop hook.
|
|
29
|
+
|
|
30
|
+
## Entry Structure
|
|
31
|
+
|
|
32
|
+
```markdown
|
|
33
|
+
## <YYYY-MM-DD> — <short title>
|
|
34
|
+
|
|
35
|
+
**Tags:** <comma-separated keywords>
|
|
36
|
+
|
|
37
|
+
**Problem:**
|
|
38
|
+
<1-3 sentences>
|
|
39
|
+
|
|
40
|
+
**Solution:**
|
|
41
|
+
<1-5 sentences with code if relevant>
|
|
42
|
+
|
|
43
|
+
**References:** <file:line | commit hash | external link>
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Tag Conventions
|
|
49
|
+
|
|
50
|
+
Use lowercase, hyphenated, specific tags:
|
|
51
|
+
- ✅ `prisma-migration`, `postgres-timezone`, `docker-compose-network`
|
|
52
|
+
- ❌ `bug`, `fix`, `important` (too generic)
|
|
53
|
+
|
|
54
|
+
Aim for 2-5 tags per entry.
|
|
55
|
+
|
|
56
|
+
## Auto-Injection on SessionStart
|
|
57
|
+
|
|
58
|
+
The `SessionStart` hook runs `node .claude/hooks/session-insights.cjs` which
|
|
59
|
+
prints recent insights to stdout. Claude Code captures stdout and injects
|
|
60
|
+
it into the initial session context.
|
|
61
|
+
|
|
62
|
+
## Storage Lifecycle
|
|
63
|
+
|
|
64
|
+
- `<= 50 entries`: single `index.md`
|
|
65
|
+
- `> 50 entries`: split into archive files `<YYYY-MM>.md` with `index.md` as TOC
|
|
66
|
+
- Never delete entries unless factually wrong; mark superseded entries instead
|
|
67
|
+
|
|
68
|
+
## Anti-Patterns
|
|
69
|
+
|
|
70
|
+
| Anti-Pattern | Why bad |
|
|
71
|
+
|--------------|---------|
|
|
72
|
+
| Capturing every error | Noise drowns signal |
|
|
73
|
+
| Generic tags | Tag-based recall fails |
|
|
74
|
+
| Missing tags | Entry is unfindable |
|
|
75
|
+
| Long prose in problem field | Hard to scan |
|
|
76
|
+
| Solution without code | When code is the fix, include the snippet |
|
|
77
|
+
| Deleting outdated entries | Lose history; supersede instead |
|