@dzhechkov/keysarium 1.4.0 → 1.5.1

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 (80) hide show
  1. package/README.md +52 -5
  2. package/README.ru.md +541 -0
  3. package/bin/cli.js +0 -0
  4. package/package.json +11 -9
  5. package/templates/.claude/commands/analyst-manual.md +38 -0
  6. package/templates/.claude/commands/casarium.md +55 -4
  7. package/templates/.claude/commands/feature-adr.md +8 -0
  8. package/templates/.claude/rules/trust-tiers.md +5 -0
  9. package/templates/.claude/shards/feature-adr.shard.md +64 -10
  10. package/templates/.claude/shards/phase-ai-factory.shard.md +109 -0
  11. package/templates/.claude/skills/ai-factory-mapper/README.md +90 -0
  12. package/templates/.claude/skills/ai-factory-mapper/SKILL.md +309 -0
  13. package/templates/.claude/skills/ai-factory-mapper/assets/report-template.md +123 -0
  14. package/templates/.claude/skills/ai-factory-mapper/references/catalog.md +230 -0
  15. package/templates/.claude/skills/ai-factory-mapper/references/coverage-formula.md +219 -0
  16. package/templates/.claude/skills/ai-factory-mapper/references/decomposition-checklist.md +183 -0
  17. package/templates/.claude/skills/ai-factory-mapper/references/pattern-library.md +312 -0
  18. package/templates/.claude/skills/ai-factory-mapper/scripts/build_docx_report.js +423 -0
  19. package/templates/.claude/skills/analyst-manual-full/SKILL.md +286 -0
  20. package/templates/.claude/skills/edu-site-generator/SKILL.md +254 -0
  21. package/templates/.claude/skills/edu-site-generator/examples/sample-course-structure.md +116 -0
  22. package/templates/.claude/skills/edu-site-generator/modules/00-content-analysis.md +84 -0
  23. package/templates/.claude/skills/edu-site-generator/modules/01-course-structure.md +75 -0
  24. package/templates/.claude/skills/edu-site-generator/modules/02-data-generation.md +186 -0
  25. package/templates/.claude/skills/edu-site-generator/modules/03-scaffold.md +249 -0
  26. package/templates/.claude/skills/edu-site-generator/modules/04-components.md +130 -0
  27. package/templates/.claude/skills/edu-site-generator/modules/05-gamification.md +163 -0
  28. package/templates/.claude/skills/edu-site-generator/modules/06-deploy.md +88 -0
  29. package/templates/.claude/skills/edu-site-generator/modules/07-verification.md +97 -0
  30. package/templates/.claude/skills/edu-site-generator/references/component-templates.md +436 -0
  31. package/templates/.claude/skills/edu-site-generator/references/data-schemas.md +164 -0
  32. package/templates/.claude/skills/edu-site-generator/references/exercise-catalog.md +110 -0
  33. package/templates/.claude/skills/edu-site-generator/references/tech-stack.md +69 -0
  34. package/templates/.claude/skills/feature-adr/SKILL.md +245 -59
  35. package/templates/.claude/skills/feature-adr/modules/03-adr.md +43 -0
  36. package/templates/.claude/skills/feature-adr/modules/03.5-ideation-swarm.md +180 -0
  37. package/templates/.claude/skills/feature-adr/modules/06-implementation-plan.md +56 -7
  38. package/templates/.claude/skills/feature-adr/modules/08-qe.md +87 -23
  39. package/templates/.claude/skills/feature-adr/modules/09-fleet-qe.md +305 -0
  40. package/templates/.claude/skills/feature-adr/references/agentic-qe/README.md +143 -0
  41. package/templates/.claude/skills/feature-adr/references/agentic-qe/brutal-honesty-review.md +240 -0
  42. package/templates/.claude/skills/feature-adr/references/agentic-qe/chaos-engineering-resilience.md +163 -0
  43. package/templates/.claude/skills/feature-adr/references/agentic-qe/code-goal-planner.md +446 -0
  44. package/templates/.claude/skills/feature-adr/references/agentic-qe/enterprise-integration-testing.md +111 -0
  45. package/templates/.claude/skills/feature-adr/references/agentic-qe/mutation-testing.md +236 -0
  46. package/templates/.claude/skills/feature-adr/references/agentic-qe/performance-testing.md +315 -0
  47. package/templates/.claude/skills/feature-adr/references/agentic-qe/qcsd-ideation-swarm.md +126 -0
  48. package/templates/.claude/skills/feature-adr/references/agentic-qe/qcsd-production-swarm.md +108 -0
  49. package/templates/.claude/skills/feature-adr/references/agentic-qe/qe-coverage-analysis.md +193 -0
  50. package/templates/.claude/skills/feature-adr/references/agentic-qe/qe-requirements-validation.md +254 -0
  51. package/templates/.claude/skills/feature-adr/references/agentic-qe/regression-testing.md +233 -0
  52. package/templates/.claude/skills/feature-adr/references/agentic-qe/risk-based-testing.md +212 -0
  53. package/templates/.claude/skills/feature-adr/references/agentic-qe/security-testing.md +311 -0
  54. package/templates/.claude/skills/feature-adr/references/agentic-qe/shift-left-testing.md +231 -0
  55. package/templates/.claude/skills/feature-adr/references/agentic-qe/tdd-london-chicago.md +249 -0
  56. package/templates/.claude/skills/transcript-site-generator/SKILL.md +287 -0
  57. package/templates/.claude/skills/transcript-site-generator/examples/sample-text-only-site.md +123 -0
  58. package/templates/.claude/skills/transcript-site-generator/examples/sample-transcript-site.md +134 -0
  59. package/templates/.claude/skills/transcript-site-generator/modules/00-input-analysis.md +132 -0
  60. package/templates/.claude/skills/transcript-site-generator/modules/01-content-parsing.md +117 -0
  61. package/templates/.claude/skills/transcript-site-generator/modules/02-site-generation.md +293 -0
  62. package/templates/.claude/skills/transcript-site-generator/modules/03-interactivity.md +406 -0
  63. package/templates/.claude/skills/transcript-site-generator/modules/04-deploy.md +130 -0
  64. package/templates/.claude/skills/transcript-site-generator/modules/05-verification.md +155 -0
  65. package/templates/.claude/skills/transcript-site-generator/references/component-catalog.md +86 -0
  66. package/templates/.claude/skills/transcript-site-generator/references/data-schemas.md +123 -0
  67. package/templates/.claude/skills/transcript-site-generator/references/tech-stack.md +132 -0
  68. package/templates/CLAUDE.md +9 -2
  69. package/templates/docs/edu-site-generator/01-deployment-guide.md +911 -0
  70. package/templates/docs/edu-site-generator/02-admin-guide.md +901 -0
  71. package/templates/docs/edu-site-generator/03-user-guide.md +1008 -0
  72. package/templates/docs/edu-site-generator/04-infrastructure-requirements.md +489 -0
  73. package/templates/docs/edu-site-generator/05-architecture.md +902 -0
  74. package/templates/docs/edu-site-generator/06-user-flows.md +759 -0
  75. package/templates/docs/transcript-site-generator/01-deployment-guide.md +1042 -0
  76. package/templates/docs/transcript-site-generator/02-admin-guide.md +918 -0
  77. package/templates/docs/transcript-site-generator/03-user-guide.md +925 -0
  78. package/templates/docs/transcript-site-generator/04-infrastructure-requirements.md +482 -0
  79. package/templates/docs/transcript-site-generator/05-architecture.md +1068 -0
  80. package/templates/docs/transcript-site-generator/06-user-flows.md +855 -0
@@ -0,0 +1,423 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * build_docx_report.js — генератор финального DOCX-отчёта для ai-factory-mapper.
4
+ *
5
+ * Использование:
6
+ * node build_docx_report.js <input.json> <output.docx>
7
+ *
8
+ * Ожидаемая структура input.json:
9
+ * {
10
+ * "title": "Анализ покрытия [...]",
11
+ * "subtitle": "опционально",
12
+ * "date": "16 апреля 2026",
13
+ * "catalog_version": "2026-04-16 + web_search",
14
+ * "executive_summary": {
15
+ * "tldr": "...",
16
+ * "key_findings": ["...", "..."]
17
+ * },
18
+ * "ranking": [ // опционально, если сценариев > 1
19
+ * { "rank": 1, "name": "...", "coverage": "~90%", "gaps": "..." },
20
+ * ...
21
+ * ],
22
+ * "catalog_snapshot": {
23
+ * "core": ["Foundation Models", "ML Inference", ...],
24
+ * "agents_count": 21,
25
+ * "mcp_count": 50,
26
+ * "fresh_announcements": [] // новинки с web_search, опционально
27
+ * },
28
+ * "scenarios": [
29
+ * {
30
+ * "name": "Сценарий 1. ...",
31
+ * "intro": "...",
32
+ * "steps": [
33
+ * "1. **Ingest** — ...",
34
+ * ...
35
+ * ],
36
+ * "mapping": [
37
+ * ["1", "Ingest", "AI Workflows + ...", "✅ Полное"],
38
+ * ...
39
+ * ],
40
+ * "gaps": ["**Gap 1**: ...", ...],
41
+ * "coverage_calc": {
42
+ * "n_full": 9, "n_partial": 0, "n_none": 1,
43
+ * "raw_pct": 90, "modifiers": ["—"],
44
+ * "final_pct": "~90%"
45
+ * },
46
+ * "mermaid": "flowchart LR\n A[...]..." // опционально
47
+ * },
48
+ * ...
49
+ * ],
50
+ * "conclusions": {
51
+ * "pattern": "...",
52
+ * "priority_gaps": ["...", "..."],
53
+ * "recommendations": ["...", "..."]
54
+ * }
55
+ * }
56
+ */
57
+
58
+ const {
59
+ Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell,
60
+ AlignmentType, LevelFormat, HeadingLevel,
61
+ BorderStyle, WidthType, ShadingType, PageBreak
62
+ } = require('docx');
63
+ const fs = require('fs');
64
+ const path = require('path');
65
+
66
+ // ---------- Константы стилей ----------
67
+ const FONT = "Arial";
68
+ const CLR_PRIMARY = "1F4E79"; // Cloud.ru-style тёмно-синий
69
+ const CLR_ACCENT = "2E75B6";
70
+ const CLR_GOOD_BG = "E2EFDA"; // зелёный
71
+ const CLR_WARN_BG = "FFF2CC"; // жёлтый
72
+ const CLR_BAD_BG = "FBE5D6"; // оранжевый
73
+ const CLR_ALT_BG = "F2F2F2"; // zebra
74
+
75
+ const border = { style: BorderStyle.SINGLE, size: 6, color: "CCCCCC" };
76
+ const borders = { top: border, bottom: border, left: border, right: border };
77
+
78
+ // ---------- Утилиты ----------
79
+ function parseInline(text) {
80
+ if (typeof text !== 'string') text = String(text);
81
+ const runs = [];
82
+ const regex = /(\*\*[^*]+\*\*)|(`[^`]+`)|([^*`]+)/g;
83
+ let m;
84
+ while ((m = regex.exec(text)) !== null) {
85
+ if (m[1]) runs.push(new TextRun({ text: m[1].slice(2, -2), bold: true, font: FONT }));
86
+ else if (m[2]) runs.push(new TextRun({ text: m[2].slice(1, -1), font: "Consolas", size: 20 }));
87
+ else runs.push(new TextRun({ text: m[3], font: FONT }));
88
+ }
89
+ return runs.length ? runs : [new TextRun({ text, font: FONT })];
90
+ }
91
+
92
+ const P = (text, opts = {}) => new Paragraph({
93
+ children: parseInline(text),
94
+ spacing: { after: 100, ...(opts.spacing || {}) },
95
+ alignment: opts.align
96
+ });
97
+
98
+ const H = (text, level) => new Paragraph({
99
+ heading: level,
100
+ children: [new TextRun({ text, font: FONT })],
101
+ spacing: { before: 300, after: 150 }
102
+ });
103
+
104
+ const Bullet = (text, level = 0) => new Paragraph({
105
+ numbering: { reference: "bullets", level },
106
+ children: parseInline(text),
107
+ spacing: { after: 60 }
108
+ });
109
+
110
+ // ---------- Цветовой фон по статусу покрытия ----------
111
+ function bgForCoverage(text) {
112
+ if (!text) return null;
113
+ const s = String(text);
114
+ if (s.includes("Полное") || s.includes("✅")) return CLR_GOOD_BG;
115
+ if (s.includes("Частичное") || s.includes("⚠️")) return CLR_WARN_BG;
116
+ if (s.includes("Нет") || s.includes("❌") || s.includes("GAP")) return CLR_BAD_BG;
117
+ return null;
118
+ }
119
+
120
+ function bgForPct(text) {
121
+ if (!text) return null;
122
+ const pct = parseInt(String(text).replace(/[^\d]/g, ""));
123
+ if (isNaN(pct)) return null;
124
+ if (pct >= 85) return CLR_GOOD_BG;
125
+ if (pct >= 70) return CLR_WARN_BG;
126
+ return CLR_BAD_BG;
127
+ }
128
+
129
+ // ---------- Ячейка таблицы ----------
130
+ function cell(text, opts = {}) {
131
+ const shading = opts.bg ? { fill: opts.bg, type: ShadingType.CLEAR } : undefined;
132
+ const align = opts.align || AlignmentType.LEFT;
133
+ const runs = opts.isHeader
134
+ ? [new TextRun({ text: String(text), bold: true, font: FONT, color: "FFFFFF" })]
135
+ : parseInline(String(text));
136
+ return new TableCell({
137
+ borders,
138
+ width: { size: opts.width, type: WidthType.DXA },
139
+ shading,
140
+ margins: { top: 80, bottom: 80, left: 120, right: 120 },
141
+ children: [new Paragraph({ alignment: align, children: runs })]
142
+ });
143
+ }
144
+
145
+ // ---------- Построение таблицы из 2D-массива ----------
146
+ function buildTable(rows, columnWidths, opts = {}) {
147
+ const tableWidth = columnWidths.reduce((a, b) => a + b, 0);
148
+ return new Table({
149
+ width: { size: tableWidth, type: WidthType.DXA },
150
+ columnWidths,
151
+ rows: rows.map((row, i) => new TableRow({
152
+ children: row.map((text, j) => {
153
+ const isHeader = i === 0;
154
+ let bg;
155
+ if (isHeader) bg = CLR_PRIMARY;
156
+ else if (opts.coverageCol !== undefined && j === opts.coverageCol) {
157
+ bg = bgForCoverage(text) || (i % 2 === 0 ? CLR_ALT_BG : "FFFFFF");
158
+ } else if (opts.pctCol !== undefined && j === opts.pctCol) {
159
+ bg = bgForPct(text) || (i % 2 === 0 ? CLR_ALT_BG : "FFFFFF");
160
+ } else {
161
+ bg = i % 2 === 0 ? CLR_ALT_BG : "FFFFFF";
162
+ }
163
+ const isCenter = opts.centerCols && opts.centerCols.includes(j);
164
+ return cell(text, {
165
+ width: columnWidths[j],
166
+ bg,
167
+ isHeader,
168
+ align: isCenter ? AlignmentType.CENTER : AlignmentType.LEFT
169
+ });
170
+ })
171
+ }))
172
+ });
173
+ }
174
+
175
+ // ---------- Блок для одного сценария ----------
176
+ function scenarioBlock(scenario, children) {
177
+ children.push(H(scenario.name, HeadingLevel.HEADING_1));
178
+ if (scenario.intro) {
179
+ children.push(P(scenario.intro, { spacing: { after: 200 } }));
180
+ }
181
+
182
+ // Декомпозиция
183
+ children.push(H("Декомпозиция workflow", HeadingLevel.HEADING_2));
184
+ scenario.steps.forEach(s => children.push(Bullet(s)));
185
+
186
+ // Маппинг
187
+ children.push(H("Маппинг на сервисы AI Factory", HeadingLevel.HEADING_2));
188
+ const mapHeader = ["#", "Шаг", "Сервис AI Factory", "Покрытие"];
189
+ const mapCols = [800, 3000, 4060, 1500];
190
+ children.push(buildTable(
191
+ [mapHeader, ...scenario.mapping],
192
+ mapCols,
193
+ { coverageCol: 3, centerCols: [0, 3] }
194
+ ));
195
+
196
+ // Mermaid-диаграмма (если есть) — вставляем как code block
197
+ if (scenario.mermaid) {
198
+ children.push(H("Mermaid-диаграмма пайплайна", HeadingLevel.HEADING_2));
199
+ children.push(P("Для рендеринга используй MCP Mermaid Server (из каталога Cloud.ru) или внешний Mermaid Live Editor.",
200
+ { spacing: { after: 120 } }));
201
+ // Вставляем mermaid-код как блок
202
+ const lines = scenario.mermaid.split('\n');
203
+ lines.forEach(line => {
204
+ children.push(new Paragraph({
205
+ children: [new TextRun({ text: line, font: "Consolas", size: 18 })],
206
+ spacing: { after: 0 },
207
+ shading: { fill: "F5F5F5", type: ShadingType.CLEAR }
208
+ }));
209
+ });
210
+ }
211
+
212
+ // Gaps
213
+ children.push(H("Что нельзя сделать «из коробки»", HeadingLevel.HEADING_2));
214
+ if (scenario.gaps && scenario.gaps.length) {
215
+ scenario.gaps.forEach(g => children.push(Bullet(g)));
216
+ } else {
217
+ children.push(P("Нет критичных gap'ов — сценарий полностью покрывается платформой."));
218
+ }
219
+
220
+ // Расчёт покрытия
221
+ children.push(H("Расчёт покрытия", HeadingLevel.HEADING_2));
222
+ const calc = scenario.coverage_calc;
223
+ if (calc) {
224
+ const calcRows = [
225
+ ["Категория", "Count", "Вес", "Вклад"],
226
+ ["✅ Полное", String(calc.n_full), "1.0", String(calc.n_full)],
227
+ ["⚠️ Частичное", String(calc.n_partial), "0.5", String(calc.n_partial * 0.5)],
228
+ ["❌ Нет", String(calc.n_none), "0.0", "0"]
229
+ ];
230
+ children.push(buildTable(calcRows, [2400, 1600, 1200, 1200], { centerCols: [1, 2, 3] }));
231
+
232
+ const total = calc.n_full + calc.n_partial + calc.n_none;
233
+ children.push(P(`Общее число шагов: ${total}`, { spacing: { before: 120 } }));
234
+ children.push(P(`coverage_raw = (${calc.n_full} + ${calc.n_partial * 0.5}) / ${total} = ${((calc.n_full + calc.n_partial * 0.5) / total).toFixed(3)}`));
235
+ children.push(P(`coverage_pct = ${calc.raw_pct}%`));
236
+ if (calc.modifiers && calc.modifiers.length && calc.modifiers[0] !== "—") {
237
+ children.push(P(`Применены модификаторы: ${calc.modifiers.join(", ")}`));
238
+ }
239
+ }
240
+ children.push(new Paragraph({
241
+ spacing: { before: 200, after: 200 },
242
+ children: [
243
+ new TextRun({ text: "Итоговое покрытие: ", font: FONT, bold: true }),
244
+ new TextRun({
245
+ text: scenario.coverage_calc?.final_pct || "—",
246
+ font: FONT, bold: true, color: CLR_PRIMARY, size: 28
247
+ })
248
+ ]
249
+ }));
250
+ children.push(new Paragraph({ children: [new PageBreak()] }));
251
+ }
252
+
253
+ // ---------- Главная функция ----------
254
+ function buildReport(data) {
255
+ const children = [];
256
+
257
+ // Титул
258
+ children.push(new Paragraph({
259
+ alignment: AlignmentType.CENTER,
260
+ spacing: { before: 1200, after: 200 },
261
+ children: [new TextRun({
262
+ text: data.title || "Анализ покрытия сценариев сервисами AI Factory",
263
+ bold: true, font: FONT, size: 40, color: CLR_PRIMARY
264
+ })]
265
+ }));
266
+ if (data.subtitle) {
267
+ children.push(new Paragraph({
268
+ alignment: AlignmentType.CENTER,
269
+ spacing: { after: 400 },
270
+ children: [new TextRun({
271
+ text: data.subtitle, italics: true, font: FONT, size: 28, color: "595959"
272
+ })]
273
+ }));
274
+ }
275
+ children.push(P(`Дата: ${data.date || "—"}`,
276
+ { align: AlignmentType.CENTER }));
277
+ children.push(P(`Версия каталога: ${data.catalog_version || "—"}`,
278
+ { align: AlignmentType.CENTER }));
279
+ children.push(P("Методология: First Principles + AI Factory Coverage Framework",
280
+ { align: AlignmentType.CENTER }));
281
+ children.push(new Paragraph({ children: [new PageBreak()] }));
282
+
283
+ // Executive Summary
284
+ children.push(H("Executive Summary", HeadingLevel.HEADING_1));
285
+ if (data.executive_summary) {
286
+ if (data.executive_summary.tldr) {
287
+ children.push(P(`**TL;DR:** ${data.executive_summary.tldr}`));
288
+ }
289
+ if (data.executive_summary.key_findings && data.executive_summary.key_findings.length) {
290
+ children.push(H("Ключевые выводы", HeadingLevel.HEADING_2));
291
+ data.executive_summary.key_findings.forEach(f => children.push(Bullet(f)));
292
+ }
293
+ }
294
+
295
+ // Ранжирование (если есть)
296
+ if (data.ranking && data.ranking.length > 0) {
297
+ children.push(H("Сводное ранжирование", HeadingLevel.HEADING_2));
298
+ const rankHeader = ["Ранг", "Сценарий", "Покрытие", "Главные gaps"];
299
+ const rankRows = data.ranking.map(r => [
300
+ String(r.rank), r.name, r.coverage, r.gaps
301
+ ]);
302
+ children.push(buildTable(
303
+ [rankHeader, ...rankRows],
304
+ [900, 3800, 1400, 3260],
305
+ { pctCol: 2, centerCols: [0, 2] }
306
+ ));
307
+ }
308
+
309
+ children.push(new Paragraph({ children: [new PageBreak()] }));
310
+
311
+ // Справочник
312
+ if (data.catalog_snapshot) {
313
+ children.push(H("Справочник AI Factory (на момент анализа)", HeadingLevel.HEADING_1));
314
+ const c = data.catalog_snapshot;
315
+ if (c.core && c.core.length) {
316
+ children.push(H("Платформенные сервисы (core)", HeadingLevel.HEADING_2));
317
+ c.core.forEach(s => children.push(Bullet(s)));
318
+ }
319
+ if (c.agents_count !== undefined) {
320
+ children.push(P(`**Готовых AI-агентов в каталоге:** ${c.agents_count}`));
321
+ }
322
+ if (c.mcp_count !== undefined) {
323
+ children.push(P(`**MCP-серверов в каталоге:** ${c.mcp_count}`));
324
+ }
325
+ if (c.fresh_announcements && c.fresh_announcements.length) {
326
+ children.push(H("Свежие анонсы (с web_search)", HeadingLevel.HEADING_2));
327
+ c.fresh_announcements.forEach(a => children.push(Bullet(a)));
328
+ }
329
+ children.push(new Paragraph({ children: [new PageBreak()] }));
330
+ }
331
+
332
+ // Сценарии
333
+ if (data.scenarios && data.scenarios.length) {
334
+ data.scenarios.forEach(s => scenarioBlock(s, children));
335
+ }
336
+
337
+ // Выводы
338
+ if (data.conclusions) {
339
+ children.push(H("Выводы и рекомендации", HeadingLevel.HEADING_1));
340
+ if (data.conclusions.pattern) {
341
+ children.push(H("Общий паттерн", HeadingLevel.HEADING_2));
342
+ children.push(P(data.conclusions.pattern));
343
+ }
344
+ if (data.conclusions.priority_gaps && data.conclusions.priority_gaps.length) {
345
+ children.push(H("Приоритетные gaps для развития платформы", HeadingLevel.HEADING_2));
346
+ data.conclusions.priority_gaps.forEach(g => children.push(Bullet(g)));
347
+ }
348
+ if (data.conclusions.recommendations && data.conclusions.recommendations.length) {
349
+ children.push(H("Рекомендации по реализации", HeadingLevel.HEADING_2));
350
+ data.conclusions.recommendations.forEach(r => children.push(Bullet(r)));
351
+ }
352
+ }
353
+
354
+ // Сборка документа
355
+ return new Document({
356
+ styles: {
357
+ default: { document: { run: { font: FONT, size: 22 } } },
358
+ paragraphStyles: [
359
+ {
360
+ id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true,
361
+ run: { size: 36, bold: true, font: FONT, color: CLR_PRIMARY },
362
+ paragraph: { spacing: { before: 360, after: 180 }, outlineLevel: 0 }
363
+ },
364
+ {
365
+ id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true,
366
+ run: { size: 28, bold: true, font: FONT, color: CLR_ACCENT },
367
+ paragraph: { spacing: { before: 240, after: 120 }, outlineLevel: 1 }
368
+ }
369
+ ]
370
+ },
371
+ numbering: {
372
+ config: [{
373
+ reference: "bullets",
374
+ levels: [{
375
+ level: 0, format: LevelFormat.BULLET, text: "•", alignment: AlignmentType.LEFT,
376
+ style: { paragraph: { indent: { left: 540, hanging: 270 } } }
377
+ }]
378
+ }]
379
+ },
380
+ sections: [{
381
+ properties: {
382
+ page: {
383
+ size: { width: 11906, height: 16838 }, // A4
384
+ margin: { top: 1134, right: 1134, bottom: 1134, left: 1134 }
385
+ }
386
+ },
387
+ children
388
+ }]
389
+ });
390
+ }
391
+
392
+ // ---------- CLI ----------
393
+ function main() {
394
+ const args = process.argv.slice(2);
395
+ if (args.length < 2) {
396
+ console.error("Usage: node build_docx_report.js <input.json> <output.docx>");
397
+ process.exit(1);
398
+ }
399
+ const [inputPath, outputPath] = args;
400
+
401
+ let data;
402
+ try {
403
+ data = JSON.parse(fs.readFileSync(inputPath, 'utf-8'));
404
+ } catch (e) {
405
+ console.error(`Failed to read/parse ${inputPath}:`, e.message);
406
+ process.exit(1);
407
+ }
408
+
409
+ const doc = buildReport(data);
410
+
411
+ Packer.toBuffer(doc).then(buf => {
412
+ fs.writeFileSync(outputPath, buf);
413
+ console.log(`✅ DOCX создан: ${outputPath}`);
414
+ console.log(`Размер: ${(buf.length / 1024).toFixed(1)} KB`);
415
+ }).catch(e => {
416
+ console.error("❌ Ошибка сборки:", e);
417
+ process.exit(1);
418
+ });
419
+ }
420
+
421
+ if (require.main === module) main();
422
+
423
+ module.exports = { buildReport };