@dzhechkov/p-replicator 1.5.6 → 1.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README/eng/01_quickstart.md +244 -0
- package/README/eng/02_user_guide.md +634 -0
- package/README/eng/03_admin_guide.md +333 -0
- package/README/eng/04_api_reference.md +302 -0
- package/README/eng/05_architecture.md +370 -0
- package/README/eng/06_troubleshooting.md +353 -0
- package/README/eng/07_changelog.md +134 -0
- package/README/eng/README.md +60 -0
- package/README/ru/01_quickstart.md +244 -0
- package/README/ru/02_user_guide.md +633 -0
- package/README/ru/03_admin_guide.md +337 -0
- package/README/ru/04_api_reference.md +333 -0
- package/README/ru/05_architecture.md +372 -0
- package/README/ru/06_troubleshooting.md +355 -0
- package/README/ru/07_changelog.md +146 -0
- package/README/ru/README.md +60 -0
- package/README/ru/html/build.js +553 -0
- package/README/ru/html/index.html +1312 -0
- package/README/ru/html/script.js +496 -0
- package/README/ru/html/style.css +804 -0
- package/bin/cli.js +0 -0
- package/package.json +10 -10
- package/src/cli.js +7 -1
- package/src/commands/init.js +4 -1
- package/templates/.claude/agents/replicate-coordinator.md +23 -21
- package/templates/.claude/commands/replicate.md +4 -2
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/03-generate-p0.md +15 -0
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/04-generate-p1.md +20 -0
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/README.md +11 -0
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* build.js — generates index.html from sibling .md files.
|
|
6
|
+
*
|
|
7
|
+
* Usage: node build.js
|
|
8
|
+
* Reads: ../{README,01_quickstart,02_user_guide,...}.md
|
|
9
|
+
* Writes: ./index.html
|
|
10
|
+
*
|
|
11
|
+
* Zero dependencies — uses only Node built-ins. Inlines a minimal
|
|
12
|
+
* Markdown → HTML parser sufficient for the documentation features
|
|
13
|
+
* actually used in our .md files.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('node:fs');
|
|
17
|
+
const path = require('node:path');
|
|
18
|
+
|
|
19
|
+
const SOURCE_DIR = path.resolve(__dirname, '..');
|
|
20
|
+
const OUTPUT = path.join(__dirname, 'index.html');
|
|
21
|
+
|
|
22
|
+
const FILES = [
|
|
23
|
+
{ id: 'index', source: 'README.md', title: 'Главная', nav: 'Главная' },
|
|
24
|
+
{ id: 'quickstart', source: '01_quickstart.md', title: '01. Быстрый старт', nav: '01. Быстрый старт' },
|
|
25
|
+
{ id: 'user-guide', source: '02_user_guide.md', title: '02. Руководство пользователя', nav: '02. Руководство' },
|
|
26
|
+
{ id: 'admin-guide', source: '03_admin_guide.md', title: '03. Admin Guide', nav: '03. Admin Guide' },
|
|
27
|
+
{ id: 'api-reference', source: '04_api_reference.md', title: '04. API Reference', nav: '04. API Reference' },
|
|
28
|
+
{ id: 'architecture', source: '05_architecture.md', title: '05. Архитектура', nav: '05. Архитектура' },
|
|
29
|
+
{ id: 'troubleshooting',source: '06_troubleshooting.md',title: '06. Troubleshooting', nav: '06. Troubleshooting' },
|
|
30
|
+
{ id: 'changelog', source: '07_changelog.md', title: '07. Changelog', nav: '07. Changelog' },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
// ─── Cyrillic → Latin transliteration for slugs ────────────────────────────
|
|
34
|
+
const TRANS = {
|
|
35
|
+
'а':'a','б':'b','в':'v','г':'g','д':'d','е':'e','ё':'yo','ж':'zh','з':'z',
|
|
36
|
+
'и':'i','й':'y','к':'k','л':'l','м':'m','н':'n','о':'o','п':'p','р':'r',
|
|
37
|
+
'с':'s','т':'t','у':'u','ф':'f','х':'h','ц':'ts','ч':'ch','ш':'sh','щ':'sch',
|
|
38
|
+
'ъ':'','ы':'y','ь':'','э':'e','ю':'yu','я':'ya',
|
|
39
|
+
};
|
|
40
|
+
function transliterate(s) {
|
|
41
|
+
return s.toLowerCase().split('').map((c) => TRANS[c] !== undefined ? TRANS[c] : c).join('');
|
|
42
|
+
}
|
|
43
|
+
function slugify(text) {
|
|
44
|
+
return transliterate(text)
|
|
45
|
+
.toLowerCase()
|
|
46
|
+
.replace(/[^a-z0-9\s-]/g, '')
|
|
47
|
+
.replace(/\s+/g, '-')
|
|
48
|
+
.replace(/-+/g, '-')
|
|
49
|
+
.replace(/^-|-$/g, '')
|
|
50
|
+
.slice(0, 80);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ─── Section map: source filename → section id (built lazily after FILES) ─
|
|
54
|
+
const SECTION_MAP = new Map();
|
|
55
|
+
function buildSectionMap() {
|
|
56
|
+
if (SECTION_MAP.size > 0) return;
|
|
57
|
+
for (const f of FILES) {
|
|
58
|
+
SECTION_MAP.set(f.source, f.id);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ─── Rewrite cross-section .md links to in-page anchors ──────────────────
|
|
63
|
+
// Returns rewritten URL, or null if URL should be left untouched.
|
|
64
|
+
function rewriteInternalLink(url) {
|
|
65
|
+
// External / mailto / pure-anchor URLs — leave alone
|
|
66
|
+
if (/^https?:/i.test(url) || /^mailto:/i.test(url) || url.startsWith('#')) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
buildSectionMap();
|
|
70
|
+
|
|
71
|
+
// Strip leading ./
|
|
72
|
+
let p = url.startsWith('./') ? url.slice(2) : url;
|
|
73
|
+
|
|
74
|
+
// Cross-language link to English README (no English HTML exists yet) → GitHub blob URL
|
|
75
|
+
if (p.startsWith('../eng/')) {
|
|
76
|
+
const tail = p.slice('../'.length); // "eng/README.md"
|
|
77
|
+
return `https://github.com/dzhechko/pu-unicorn-replicate/blob/main/packages/p-replicator/README/${tail}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Same-folder .md reference: "<filename>.md" or "<filename>.md#<fragment>"
|
|
81
|
+
const m = p.match(/^([^#?]+\.md)(?:#(.*))?$/);
|
|
82
|
+
if (!m) return null;
|
|
83
|
+
|
|
84
|
+
const filename = m[1];
|
|
85
|
+
const sectionId = SECTION_MAP.get(filename);
|
|
86
|
+
if (!sectionId) {
|
|
87
|
+
// .md file outside the section index (e.g., KNOWN_LIMITATIONS.md, CHANGELOG.md,
|
|
88
|
+
// .claude/commands/replicate.md) — point at the GitHub source.
|
|
89
|
+
if (filename.match(/^\.claude\//) || /^[A-Z_]+\.md$/.test(filename) || filename === 'CHANGELOG.md') {
|
|
90
|
+
return `https://github.com/dzhechko/pu-unicorn-replicate/blob/main/packages/p-replicator/${p}`;
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (m[2]) {
|
|
96
|
+
// Anchor present — fragment may be cyrillic; slugify to match heading id
|
|
97
|
+
const fragment = decodeURIComponent(m[2]);
|
|
98
|
+
const slug = slugify(fragment);
|
|
99
|
+
return `#${sectionId}-${slug}`;
|
|
100
|
+
}
|
|
101
|
+
return `#${sectionId}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ─── HTML escape ──────────────────────────────────────────────────────────
|
|
105
|
+
function escapeHtml(text) {
|
|
106
|
+
return String(text).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
107
|
+
}
|
|
108
|
+
function escapeAttr(text) {
|
|
109
|
+
return escapeHtml(text).replace(/"/g, '"');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ─── Inline markdown ──────────────────────────────────────────────────────
|
|
113
|
+
function processInline(text) {
|
|
114
|
+
// 1. Stash inline code so its content isn't processed further
|
|
115
|
+
const codes = [];
|
|
116
|
+
text = text.replace(/`([^`]+)`/g, (_, c) => {
|
|
117
|
+
codes.push(escapeHtml(c));
|
|
118
|
+
return `\x00CODE${codes.length - 1}\x00`;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// 2. Stash links (so brackets aren't escaped)
|
|
122
|
+
const links = [];
|
|
123
|
+
text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, label, url) => {
|
|
124
|
+
links.push({ label, url });
|
|
125
|
+
return `\x00LINK${links.length - 1}\x00`;
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// 3. Escape remaining HTML entities
|
|
129
|
+
text = escapeHtml(text);
|
|
130
|
+
|
|
131
|
+
// 4. Bold + italic + strikethrough on the escaped text
|
|
132
|
+
text = text.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
|
133
|
+
text = text.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, '<em>$1</em>');
|
|
134
|
+
text = text.replace(/~~([^~]+)~~/g, '<del>$1</del>');
|
|
135
|
+
|
|
136
|
+
// 5. Restore links (label needs inline processing too — re-run bold/italic/code on it)
|
|
137
|
+
text = text.replace(/\x00LINK(\d+)\x00/g, (_, idx) => {
|
|
138
|
+
const { label, url } = links[idx];
|
|
139
|
+
const safeLabel = escapeHtml(label)
|
|
140
|
+
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
|
|
141
|
+
.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, '<em>$1</em>');
|
|
142
|
+
// Cross-section .md links → in-page anchors. External URLs stay as-is.
|
|
143
|
+
const rewritten = rewriteInternalLink(url);
|
|
144
|
+
const finalUrl = rewritten !== null ? rewritten : url;
|
|
145
|
+
const isExternal = /^https?:/i.test(finalUrl);
|
|
146
|
+
const attrs = isExternal ? ' target="_blank" rel="noopener noreferrer"' : '';
|
|
147
|
+
return `<a href="${escapeAttr(finalUrl)}"${attrs}>${safeLabel}</a>`;
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// 6. Restore code
|
|
151
|
+
text = text.replace(/\x00CODE(\d+)\x00/g, (_, idx) => `<code>${codes[idx]}</code>`);
|
|
152
|
+
|
|
153
|
+
return text;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ─── Block parsers ────────────────────────────────────────────────────────
|
|
157
|
+
function renderTable(lines) {
|
|
158
|
+
if (lines.length < 2) return '';
|
|
159
|
+
const splitRow = (l) => l.replace(/^\||\|$/g, '').split('|').map((s) => s.trim());
|
|
160
|
+
const header = splitRow(lines[0]);
|
|
161
|
+
const rows = lines.slice(2).map(splitRow);
|
|
162
|
+
|
|
163
|
+
let html = '<div class="table-wrap"><table><thead><tr>';
|
|
164
|
+
for (const h of header) html += `<th>${processInline(h)}</th>`;
|
|
165
|
+
html += '</tr></thead><tbody>';
|
|
166
|
+
for (const row of rows) {
|
|
167
|
+
html += '<tr>';
|
|
168
|
+
for (const cell of row) html += `<td>${processInline(cell)}</td>`;
|
|
169
|
+
html += '</tr>';
|
|
170
|
+
}
|
|
171
|
+
html += '</tbody></table></div>';
|
|
172
|
+
return html;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function renderList(items, tag) {
|
|
176
|
+
let html = `<${tag}>`;
|
|
177
|
+
for (const item of items) {
|
|
178
|
+
html += `<li>${processInline(item)}</li>`;
|
|
179
|
+
}
|
|
180
|
+
html += `</${tag}>`;
|
|
181
|
+
return html;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function renderCode(code, lang) {
|
|
185
|
+
return `<pre data-lang="${escapeAttr(lang)}"><code class="language-${escapeAttr(lang || 'text')}">${escapeHtml(code)}</code></pre>`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ─── Main parser ──────────────────────────────────────────────────────────
|
|
189
|
+
function parseMarkdown(md, idPrefix = '') {
|
|
190
|
+
// Normalize line endings
|
|
191
|
+
const lines = md.replace(/\r\n/g, '\n').split('\n');
|
|
192
|
+
const out = [];
|
|
193
|
+
let i = 0;
|
|
194
|
+
|
|
195
|
+
function isBlockBoundary(line) {
|
|
196
|
+
return (
|
|
197
|
+
/^#{1,6}\s/.test(line) ||
|
|
198
|
+
/^---+\s*$/.test(line) ||
|
|
199
|
+
/^[-*+]\s+/.test(line) ||
|
|
200
|
+
/^\d+\.\s+/.test(line) ||
|
|
201
|
+
/^```/.test(line) ||
|
|
202
|
+
/^\|/.test(line) ||
|
|
203
|
+
/^>\s/.test(line) ||
|
|
204
|
+
line.trim() === ''
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
while (i < lines.length) {
|
|
209
|
+
const line = lines[i];
|
|
210
|
+
|
|
211
|
+
// ─── Code block (```)
|
|
212
|
+
if (/^```/.test(line)) {
|
|
213
|
+
const lang = line.slice(3).trim();
|
|
214
|
+
const codeLines = [];
|
|
215
|
+
i++;
|
|
216
|
+
while (i < lines.length && !/^```\s*$/.test(lines[i])) {
|
|
217
|
+
codeLines.push(lines[i]);
|
|
218
|
+
i++;
|
|
219
|
+
}
|
|
220
|
+
i++; // skip closing ```
|
|
221
|
+
out.push(renderCode(codeLines.join('\n'), lang));
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ─── Heading
|
|
226
|
+
const h = line.match(/^(#{1,6})\s+(.+?)\s*#*\s*$/);
|
|
227
|
+
if (h) {
|
|
228
|
+
const level = h[1].length;
|
|
229
|
+
const text = h[2].trim();
|
|
230
|
+
const baseId = slugify(text);
|
|
231
|
+
const id = idPrefix && level > 1 ? `${idPrefix}-${baseId}` : baseId || idPrefix || 'section';
|
|
232
|
+
out.push(`<h${level} id="${escapeAttr(id)}">${processInline(text)}</h${level}>`);
|
|
233
|
+
i++;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ─── HR
|
|
238
|
+
if (/^---+\s*$/.test(line)) {
|
|
239
|
+
out.push('<hr>');
|
|
240
|
+
i++;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ─── Blockquote
|
|
245
|
+
if (/^>\s?/.test(line)) {
|
|
246
|
+
const qLines = [];
|
|
247
|
+
while (i < lines.length && /^>\s?/.test(lines[i])) {
|
|
248
|
+
qLines.push(lines[i].replace(/^>\s?/, ''));
|
|
249
|
+
i++;
|
|
250
|
+
}
|
|
251
|
+
out.push(`<blockquote>${parseMarkdown(qLines.join('\n'), idPrefix)}</blockquote>`);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ─── Table
|
|
256
|
+
if (/^\|.*\|/.test(line) && i + 1 < lines.length && /^\|[\s:|-]+\|/.test(lines[i + 1])) {
|
|
257
|
+
const tableLines = [];
|
|
258
|
+
while (i < lines.length && /^\|/.test(lines[i])) {
|
|
259
|
+
tableLines.push(lines[i]);
|
|
260
|
+
i++;
|
|
261
|
+
}
|
|
262
|
+
out.push(renderTable(tableLines));
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ─── Unordered list
|
|
267
|
+
if (/^[-*+]\s+/.test(line)) {
|
|
268
|
+
const items = [];
|
|
269
|
+
while (i < lines.length && /^[-*+]\s+/.test(lines[i])) {
|
|
270
|
+
items.push(lines[i].replace(/^[-*+]\s+/, ''));
|
|
271
|
+
i++;
|
|
272
|
+
// collect indented continuation lines into the previous item
|
|
273
|
+
while (i < lines.length && /^\s{2,}\S/.test(lines[i])) {
|
|
274
|
+
items[items.length - 1] += ' ' + lines[i].replace(/^\s+/, '');
|
|
275
|
+
i++;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
out.push(renderList(items, 'ul'));
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ─── Ordered list
|
|
283
|
+
if (/^\d+\.\s+/.test(line)) {
|
|
284
|
+
const items = [];
|
|
285
|
+
while (i < lines.length && /^\d+\.\s+/.test(lines[i])) {
|
|
286
|
+
items.push(lines[i].replace(/^\d+\.\s+/, ''));
|
|
287
|
+
i++;
|
|
288
|
+
while (i < lines.length && /^\s{2,}\S/.test(lines[i])) {
|
|
289
|
+
items[items.length - 1] += ' ' + lines[i].replace(/^\s+/, '');
|
|
290
|
+
i++;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
out.push(renderList(items, 'ol'));
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ─── Empty line
|
|
298
|
+
if (line.trim() === '') {
|
|
299
|
+
i++;
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ─── Paragraph (collect until block boundary)
|
|
304
|
+
const para = [line];
|
|
305
|
+
i++;
|
|
306
|
+
while (i < lines.length && !isBlockBoundary(lines[i])) {
|
|
307
|
+
para.push(lines[i]);
|
|
308
|
+
i++;
|
|
309
|
+
}
|
|
310
|
+
out.push(`<p>${processInline(para.join(' ').replace(/\s+/g, ' ').trim())}</p>`);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return out.join('\n');
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// ─── TOC extraction (h2 + h3) ─────────────────────────────────────────────
|
|
317
|
+
function extractToc(html) {
|
|
318
|
+
const items = [];
|
|
319
|
+
const re = /<h(2|3)\s+id="([^"]+)">([\s\S]*?)<\/h\1>/g;
|
|
320
|
+
let m;
|
|
321
|
+
while ((m = re.exec(html)) !== null) {
|
|
322
|
+
items.push({
|
|
323
|
+
level: parseInt(m[1], 10),
|
|
324
|
+
id: m[2],
|
|
325
|
+
// Strip any inline tags from the heading text for TOC display
|
|
326
|
+
text: m[3].replace(/<[^>]+>/g, '').trim(),
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
return items;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// ─── Build sidebar TOC ────────────────────────────────────────────────────
|
|
333
|
+
function renderSidebarToc(sections) {
|
|
334
|
+
let html = '<ul>';
|
|
335
|
+
for (const s of sections) {
|
|
336
|
+
const subItems = (s.toc || []).filter((t) => t.level === 2);
|
|
337
|
+
html += `<li><a href="#${escapeAttr(s.id)}" class="toc-section">${escapeHtml(s.nav)}</a>`;
|
|
338
|
+
if (subItems.length > 0) {
|
|
339
|
+
html += '<ul>';
|
|
340
|
+
for (const sub of subItems.slice(0, 12)) {
|
|
341
|
+
html += `<li><a href="#${escapeAttr(sub.id)}">${escapeHtml(sub.text)}</a></li>`;
|
|
342
|
+
}
|
|
343
|
+
html += '</ul>';
|
|
344
|
+
}
|
|
345
|
+
html += '</li>';
|
|
346
|
+
}
|
|
347
|
+
html += '</ul>';
|
|
348
|
+
return html;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ─── Render section nav (prev/next) ───────────────────────────────────────
|
|
352
|
+
function renderSectionNav(sections, idx) {
|
|
353
|
+
const prev = idx > 0 ? sections[idx - 1] : null;
|
|
354
|
+
const next = idx < sections.length - 1 ? sections[idx + 1] : null;
|
|
355
|
+
let html = '<nav class="section-nav" aria-label="Навигация по секциям">';
|
|
356
|
+
if (prev) {
|
|
357
|
+
html += `<a href="#${escapeAttr(prev.id)}" class="prev"><span class="nav-arrow">←</span> <span class="nav-text"><span class="nav-label">Предыдущее</span><span class="nav-title">${escapeHtml(prev.nav)}</span></span></a>`;
|
|
358
|
+
} else {
|
|
359
|
+
html += '<span></span>';
|
|
360
|
+
}
|
|
361
|
+
if (next) {
|
|
362
|
+
html += `<a href="#${escapeAttr(next.id)}" class="next"><span class="nav-text"><span class="nav-label">Далее</span><span class="nav-title">${escapeHtml(next.nav)}</span></span> <span class="nav-arrow">→</span></a>`;
|
|
363
|
+
} else {
|
|
364
|
+
html += '<span></span>';
|
|
365
|
+
}
|
|
366
|
+
html += '</nav>';
|
|
367
|
+
return html;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// ─── Build sections HTML ──────────────────────────────────────────────────
|
|
371
|
+
function renderSection(s) {
|
|
372
|
+
return `<article id="${escapeAttr(s.id)}" class="section" data-section-title="${escapeAttr(s.title)}">
|
|
373
|
+
${s.html}
|
|
374
|
+
${s.nav_html}
|
|
375
|
+
</article>`;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// ─── Read + parse all sources ─────────────────────────────────────────────
|
|
379
|
+
console.log('[build] Reading source files from', SOURCE_DIR);
|
|
380
|
+
|
|
381
|
+
const sections = FILES.map((f) => {
|
|
382
|
+
const srcPath = path.join(SOURCE_DIR, f.source);
|
|
383
|
+
const md = fs.readFileSync(srcPath, 'utf8');
|
|
384
|
+
const html = parseMarkdown(md, f.id);
|
|
385
|
+
return {
|
|
386
|
+
...f,
|
|
387
|
+
html,
|
|
388
|
+
toc: extractToc(html),
|
|
389
|
+
};
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
console.log(`[build] Parsed ${sections.length} sections`);
|
|
393
|
+
|
|
394
|
+
// Add prev/next nav HTML to each section
|
|
395
|
+
sections.forEach((s, idx) => {
|
|
396
|
+
s.nav_html = renderSectionNav(sections, idx);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// ─── Final HTML template ──────────────────────────────────────────────────
|
|
400
|
+
const tocHtml = renderSidebarToc(sections);
|
|
401
|
+
const sectionsHtml = sections.map(renderSection).join('\n');
|
|
402
|
+
|
|
403
|
+
const META_DESC = '@dzhechkov/p-replicator — toolkit для AI-assisted разработки в Claude Code (Vibe Coding). 11 slash-команд, 10 skills, hooks, statusline, --feature-branches workflow для обучения. v1.5.0.';
|
|
404
|
+
const META_KEYWORDS = 'p-replicator, claude code, vibe coding, sparc, ai-assisted, npm, dzhechkov, /replicate, /run, /feature, statusline, claude-code-toolkit, prd, sparc-mini, requirements-validator';
|
|
405
|
+
|
|
406
|
+
const STRUCTURED_DATA = JSON.stringify({
|
|
407
|
+
'@context': 'https://schema.org',
|
|
408
|
+
'@type': 'TechArticle',
|
|
409
|
+
'headline': '@dzhechkov/p-replicator v1.5.0 — Документация',
|
|
410
|
+
'description': META_DESC,
|
|
411
|
+
'author': { '@type': 'Person', 'name': 'dzhechko' },
|
|
412
|
+
'datePublished': '2026-05-07',
|
|
413
|
+
'inLanguage': 'ru',
|
|
414
|
+
'about': {
|
|
415
|
+
'@type': 'SoftwareSourceCode',
|
|
416
|
+
'name': '@dzhechkov/p-replicator',
|
|
417
|
+
'codeRepository': 'https://github.com/dzhechko/pu-unicorn-replicate',
|
|
418
|
+
'programmingLanguage': 'JavaScript',
|
|
419
|
+
'softwareVersion': '1.5.0',
|
|
420
|
+
'license': 'https://opensource.org/licenses/MIT',
|
|
421
|
+
},
|
|
422
|
+
'isPartOf': {
|
|
423
|
+
'@type': 'WebSite',
|
|
424
|
+
'name': '@dzhechkov/p-replicator Documentation',
|
|
425
|
+
'url': 'https://github.com/dzhechko/pu-unicorn-replicate',
|
|
426
|
+
},
|
|
427
|
+
}, null, 2);
|
|
428
|
+
|
|
429
|
+
const html = `<!DOCTYPE html>
|
|
430
|
+
<html lang="ru" data-theme="auto">
|
|
431
|
+
<head>
|
|
432
|
+
<meta charset="UTF-8">
|
|
433
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
|
434
|
+
<title>@dzhechkov/p-replicator v1.5.0 — Документация</title>
|
|
435
|
+
<meta name="description" content="${escapeAttr(META_DESC)}">
|
|
436
|
+
<meta name="keywords" content="${escapeAttr(META_KEYWORDS)}">
|
|
437
|
+
<meta name="author" content="dzhechko">
|
|
438
|
+
<meta name="robots" content="index, follow">
|
|
439
|
+
|
|
440
|
+
<!-- Open Graph -->
|
|
441
|
+
<meta property="og:title" content="@dzhechkov/p-replicator v1.5.0">
|
|
442
|
+
<meta property="og:description" content="${escapeAttr(META_DESC)}">
|
|
443
|
+
<meta property="og:type" content="website">
|
|
444
|
+
<meta property="og:locale" content="ru_RU">
|
|
445
|
+
<meta property="og:site_name" content="P-Replicator Docs">
|
|
446
|
+
|
|
447
|
+
<!-- Twitter -->
|
|
448
|
+
<meta name="twitter:card" content="summary">
|
|
449
|
+
<meta name="twitter:title" content="@dzhechkov/p-replicator v1.5.0">
|
|
450
|
+
<meta name="twitter:description" content="${escapeAttr(META_DESC)}">
|
|
451
|
+
|
|
452
|
+
<!-- Theme color (browser chrome) -->
|
|
453
|
+
<meta name="theme-color" content="#0969da" media="(prefers-color-scheme: light)">
|
|
454
|
+
<meta name="theme-color" content="#0d1117" media="(prefers-color-scheme: dark)">
|
|
455
|
+
|
|
456
|
+
<!-- Canonical -->
|
|
457
|
+
<link rel="canonical" href="https://github.com/dzhechko/pu-unicorn-replicate#readme">
|
|
458
|
+
|
|
459
|
+
<!-- Structured data -->
|
|
460
|
+
<script type="application/ld+json">${STRUCTURED_DATA}</script>
|
|
461
|
+
|
|
462
|
+
<!-- Inline critical CSS for instant first paint (avoid FOUC) -->
|
|
463
|
+
<script>
|
|
464
|
+
// Apply theme BEFORE first paint to prevent flash
|
|
465
|
+
(function() {
|
|
466
|
+
try {
|
|
467
|
+
var t = localStorage.getItem('p-replicator-theme') || 'auto';
|
|
468
|
+
if (t !== 'auto') document.documentElement.setAttribute('data-theme', t);
|
|
469
|
+
} catch (_) {}
|
|
470
|
+
})();
|
|
471
|
+
</script>
|
|
472
|
+
|
|
473
|
+
<link rel="stylesheet" href="style.css">
|
|
474
|
+
</head>
|
|
475
|
+
<body>
|
|
476
|
+
<a href="#main" class="skip-to-content">Перейти к содержимому</a>
|
|
477
|
+
|
|
478
|
+
<div class="progress-bar" id="progressBar" aria-hidden="true"></div>
|
|
479
|
+
|
|
480
|
+
<header class="site-header" role="banner">
|
|
481
|
+
<button class="menu-toggle" id="menuToggle" aria-label="Открыть меню" aria-expanded="false">
|
|
482
|
+
<span aria-hidden="true">☰</span>
|
|
483
|
+
</button>
|
|
484
|
+
<a href="#index" class="site-title">
|
|
485
|
+
<strong>P-Replicator</strong>
|
|
486
|
+
<span class="version">v1.5.0</span>
|
|
487
|
+
</a>
|
|
488
|
+
<nav class="header-nav" aria-label="Внешние ссылки">
|
|
489
|
+
<a href="https://www.npmjs.com/package/@dzhechkov/p-replicator" target="_blank" rel="noopener noreferrer">npm</a>
|
|
490
|
+
<a href="https://github.com/dzhechko/pu-unicorn-replicate" target="_blank" rel="noopener noreferrer">GitHub</a>
|
|
491
|
+
<a href="https://t.me/llm_notes" target="_blank" rel="noopener noreferrer">Telegram</a>
|
|
492
|
+
</nav>
|
|
493
|
+
<button class="theme-toggle" id="themeToggle" aria-label="Переключить тему">
|
|
494
|
+
<span aria-hidden="true">🌙</span>
|
|
495
|
+
</button>
|
|
496
|
+
</header>
|
|
497
|
+
|
|
498
|
+
<div class="layout">
|
|
499
|
+
<aside class="sidebar" id="sidebar" aria-label="Содержание">
|
|
500
|
+
<div class="search-wrap">
|
|
501
|
+
<input type="search" class="search-input" id="searchInput"
|
|
502
|
+
placeholder="🔍 Поиск по документации"
|
|
503
|
+
aria-label="Поиск по документации"
|
|
504
|
+
autocomplete="off"
|
|
505
|
+
spellcheck="false">
|
|
506
|
+
<kbd class="search-kbd">/</kbd>
|
|
507
|
+
</div>
|
|
508
|
+
<div class="search-results" id="searchResults" role="listbox" hidden></div>
|
|
509
|
+
<nav class="toc" id="toc" aria-label="Содержание документации">
|
|
510
|
+
${tocHtml}
|
|
511
|
+
</nav>
|
|
512
|
+
</aside>
|
|
513
|
+
|
|
514
|
+
<main id="main" class="main-content" role="main">
|
|
515
|
+
${sectionsHtml}
|
|
516
|
+
|
|
517
|
+
<footer class="site-footer">
|
|
518
|
+
<div class="footer-row">
|
|
519
|
+
<strong>@dzhechkov/p-replicator</strong> v1.5.0 · MIT License
|
|
520
|
+
</div>
|
|
521
|
+
<div class="footer-row footer-muted">
|
|
522
|
+
Сгенерировано из <code>README/ru/*.md</code> командой <code>/docs</code>.
|
|
523
|
+
</div>
|
|
524
|
+
<div class="footer-row footer-muted">
|
|
525
|
+
Re-build: <code>cd packages/p-replicator/README/ru/html && node build.js</code>
|
|
526
|
+
</div>
|
|
527
|
+
<div class="footer-row footer-links">
|
|
528
|
+
<a href="https://github.com/dzhechko/pu-unicorn-replicate" target="_blank" rel="noopener noreferrer">GitHub</a>
|
|
529
|
+
·
|
|
530
|
+
<a href="https://www.npmjs.com/package/@dzhechkov/p-replicator" target="_blank" rel="noopener noreferrer">npm</a>
|
|
531
|
+
·
|
|
532
|
+
<a href="https://t.me/llm_notes" target="_blank" rel="noopener noreferrer">Telegram</a>
|
|
533
|
+
</div>
|
|
534
|
+
</footer>
|
|
535
|
+
</main>
|
|
536
|
+
</div>
|
|
537
|
+
|
|
538
|
+
<div class="sidebar-overlay" id="sidebarOverlay" aria-hidden="true"></div>
|
|
539
|
+
|
|
540
|
+
<button class="back-to-top" id="backToTop" aria-label="Наверх" hidden>
|
|
541
|
+
<span aria-hidden="true">↑</span>
|
|
542
|
+
</button>
|
|
543
|
+
|
|
544
|
+
<script src="script.js"></script>
|
|
545
|
+
</body>
|
|
546
|
+
</html>
|
|
547
|
+
`;
|
|
548
|
+
|
|
549
|
+
// ─── Write output ─────────────────────────────────────────────────────────
|
|
550
|
+
fs.writeFileSync(OUTPUT, html, 'utf8');
|
|
551
|
+
const sizeKb = (Buffer.byteLength(html, 'utf8') / 1024).toFixed(1);
|
|
552
|
+
console.log(`[build] Wrote ${OUTPUT} (${sizeKb} KB, ${sections.length} sections)`);
|
|
553
|
+
console.log(`[build] Sections: ${sections.map((s) => s.id).join(', ')}`);
|