@aigne/doc-smith 0.5.1 → 0.7.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.
Files changed (42) hide show
  1. package/.github/workflows/ci.yml +46 -0
  2. package/.github/workflows/reviewer.yml +2 -1
  3. package/CHANGELOG.md +17 -0
  4. package/agents/chat.yaml +30 -0
  5. package/agents/check-detail-result.mjs +2 -1
  6. package/agents/check-detail.mjs +1 -0
  7. package/agents/check-structure-plan.mjs +1 -1
  8. package/agents/docs-fs.yaml +25 -0
  9. package/agents/exit.mjs +6 -0
  10. package/agents/feedback-refiner.yaml +5 -1
  11. package/agents/find-items-by-paths.mjs +10 -4
  12. package/agents/fs.mjs +60 -0
  13. package/agents/input-generator.mjs +159 -90
  14. package/agents/load-config.mjs +0 -5
  15. package/agents/load-sources.mjs +119 -12
  16. package/agents/publish-docs.mjs +28 -11
  17. package/agents/retranslate.yaml +1 -1
  18. package/agents/team-publish-docs.yaml +2 -2
  19. package/aigne.yaml +1 -0
  20. package/package.json +13 -10
  21. package/prompts/content-detail-generator.md +12 -4
  22. package/prompts/document/custom-components.md +80 -0
  23. package/prompts/document/d2-chart/diy-examples.md +44 -0
  24. package/prompts/document/d2-chart/official-examples.md +708 -0
  25. package/prompts/document/d2-chart/rules.md +48 -0
  26. package/prompts/document/detail-generator.md +13 -15
  27. package/prompts/document/structure-planning.md +1 -3
  28. package/prompts/feedback-refiner.md +81 -60
  29. package/prompts/structure-planning.md +20 -3
  30. package/tests/check-detail-result.test.mjs +50 -2
  31. package/tests/conflict-resolution.test.mjs +237 -0
  32. package/tests/input-generator.test.mjs +940 -0
  33. package/tests/load-sources.test.mjs +627 -3
  34. package/tests/preferences-utils.test.mjs +94 -0
  35. package/tests/save-value-to-config.test.mjs +182 -5
  36. package/tests/utils.test.mjs +49 -0
  37. package/utils/auth-utils.mjs +1 -1
  38. package/utils/conflict-detector.mjs +72 -1
  39. package/utils/constants.mjs +139 -126
  40. package/utils/kroki-utils.mjs +162 -0
  41. package/utils/markdown-checker.mjs +175 -67
  42. package/utils/utils.mjs +97 -29
@@ -86,15 +86,27 @@ export const DEFAULT_INCLUDE_PATTERNS = [
86
86
  "*.yml",
87
87
  "*Dockerfile",
88
88
  "*Makefile",
89
+ // Media files
90
+ "*.jpg",
91
+ "*.jpeg",
92
+ "*.png",
93
+ "*.gif",
94
+ "*.bmp",
95
+ "*.webp",
96
+ "*.svg",
97
+ "*.mp4",
98
+ "*.mov",
99
+ "*.avi",
100
+ "*.mkv",
101
+ "*.webm",
102
+ "*.m4v",
89
103
  ];
90
104
 
91
105
  export const DEFAULT_EXCLUDE_PATTERNS = [
92
106
  "**/aigne-docs/**",
93
107
  "**/doc-smith/**",
94
108
  "**/.aigne/**",
95
- "**/assets/**",
96
109
  "**/data/**",
97
- "**/images/**",
98
110
  "**/public/**",
99
111
  "**/static/**",
100
112
  "**/vendor/**",
@@ -331,79 +343,8 @@ export const SUPPORTED_FILE_EXTENSIONS = [".txt", ".md", ".json", ".yaml", ".yml
331
343
  export const CONFLICT_RULES = {
332
344
  // Internal conflicts within the same question (multi-select conflicts)
333
345
  internalConflicts: {
334
- documentPurpose: [
335
- {
336
- conflictItems: ["getStarted", "findAnswers"],
337
- severity: "severe",
338
- reason:
339
- "Quick start guide (skips complex cases) conflicts with comprehensive API reference (skips beginner explanations)",
340
- suggestion: "Choose one as primary goal, or consider creating layered documentation",
341
- },
342
- {
343
- conflictItems: ["getStarted", "understandSystem"],
344
- severity: "severe",
345
- reason: "30-minute quick start conflicts with deep architectural concept explanations",
346
- suggestion: "Consider creating separate quick start and architecture design docs",
347
- },
348
- {
349
- conflictItems: ["completeTasks", "understandSystem"],
350
- severity: "moderate",
351
- reason:
352
- "Practical task guidance and theoretical concept explanation have different focuses",
353
- suggestion:
354
- "Can be handled through layered document structure: concepts first, then practice",
355
- },
356
- {
357
- conflictItems: ["getStarted", "solveProblems"],
358
- severity: "moderate",
359
- reason:
360
- "Quick start (success cases) and troubleshooting (error scenarios) have different focuses",
361
- suggestion: "Create separate tutorial and troubleshooting guides",
362
- },
363
- {
364
- conflictItems: ["findAnswers", "solveProblems"],
365
- severity: "moderate",
366
- reason: "API reference and diagnostic documentation have different organizational logic",
367
- suggestion: "Add troubleshooting section to reference documentation",
368
- },
369
- ],
370
- targetAudienceTypes: [
371
- {
372
- conflictItems: ["endUsers", "developers"],
373
- severity: "severe",
374
- reason:
375
- "Non-technical users (avoid technical terms) conflict with developers (code-first approach)",
376
- suggestion:
377
- "Create separate documentation for different audiences or use layered content design",
378
- },
379
- {
380
- conflictItems: ["endUsers", "devops"],
381
- severity: "severe",
382
- reason: "Non-technical users and operations technical personnel have very different needs",
383
- suggestion: "Consider creating separate user guides and operations documentation",
384
- },
385
- {
386
- conflictItems: ["endUsers", "decisionMakers"],
387
- severity: "severe",
388
- reason:
389
- "Non-technical users (simple language) and decision makers (architecture diagrams) have different needs",
390
- suggestion: "Create high-level overview for management and user operation manuals",
391
- },
392
- {
393
- conflictItems: ["developers", "decisionMakers"],
394
- severity: "moderate",
395
- reason:
396
- "Developers (code details) and decision makers (high-level overview) have different focus areas",
397
- suggestion: "Use progressive disclosure: high-level first, then details",
398
- },
399
- {
400
- conflictItems: ["supportTeams", "decisionMakers"],
401
- severity: "moderate",
402
- reason:
403
- "Support teams (problem diagnosis) and decision makers (architecture decisions) have different focus areas",
404
- suggestion: "Include operational considerations in decision documentation",
405
- },
406
- ],
346
+ // Note: Most conflicts can be resolved through intelligent document structure planning
347
+ // Only keeping conflicts that represent fundamental incompatibilities
407
348
  },
408
349
 
409
350
  // Cross-question conflicts (conflicts between different questions)
@@ -471,66 +412,138 @@ export const CONFLICT_RULES = {
471
412
  readerKnowledgeLevel: ["emergencyTroubleshooting"],
472
413
  },
473
414
  },
415
+ ],
416
+ };
417
+
418
+ // Conflict resolution rules - defines how to handle conflicts when users select conflicting options
419
+ export const CONFLICT_RESOLUTION_RULES = {
420
+ // Document purpose conflicts that can be resolved through structure planning
421
+ documentPurpose: [
474
422
  {
475
- conditions: {
476
- documentPurpose: ["getStarted"],
477
- documentationDepth: ["comprehensive"],
478
- },
479
- severity: "severe",
480
- reason: "Quick start tutorials contradict comprehensive coverage documentation",
481
- action: "filter",
482
- conflictingOptions: {
483
- documentationDepth: ["comprehensive"],
484
- },
423
+ conflictItems: ["getStarted", "findAnswers"],
424
+ strategy: "layered_structure",
425
+ description: "Quick start and API reference conflict, resolved through layered structure",
485
426
  },
486
427
  {
487
- conditions: {
488
- documentPurpose: ["solveProblems"],
489
- documentationDepth: ["essentialOnly"],
490
- },
491
- severity: "moderate",
492
- reason:
493
- "Troubleshooting usually needs to cover edge cases, basic content alone may not be sufficient",
494
- action: "filter",
495
- conflictingOptions: {
496
- documentationDepth: ["essentialOnly"],
497
- },
428
+ conflictItems: ["getStarted", "understandSystem"],
429
+ strategy: "separate_sections",
430
+ description:
431
+ "Quick start and system understanding conflict, resolved through separate sections",
498
432
  },
499
433
  {
500
- conditions: {
501
- targetAudienceTypes: ["endUsers"],
502
- documentationDepth: ["comprehensive"],
503
- },
504
- severity: "moderate",
505
- reason: "Non-technical users typically do not need comprehensive technical coverage",
506
- action: "filter",
507
- conflictingOptions: {
508
- documentationDepth: ["comprehensive"],
509
- },
434
+ conflictItems: ["completeTasks", "understandSystem"],
435
+ strategy: "concepts_then_practice",
436
+ description:
437
+ "Task guidance and system understanding conflict, resolved through concepts-then-practice structure",
510
438
  },
511
439
  {
512
- conditions: {
513
- targetAudienceTypes: ["decisionMakers"],
514
- documentationDepth: ["essentialOnly"],
515
- },
516
- severity: "moderate",
517
- reason: "Decision makers may need more comprehensive information to make decisions",
518
- action: "filter",
519
- conflictingOptions: {
520
- documentationDepth: ["essentialOnly"],
521
- },
440
+ conflictItems: ["findAnswers", "solveProblems"],
441
+ strategy: "reference_with_troubleshooting",
442
+ description:
443
+ "API reference and problem solving conflict, resolved through reference with troubleshooting",
522
444
  },
445
+ ],
446
+
447
+ // Target audience conflicts that can be resolved through structure planning
448
+ targetAudienceTypes: [
523
449
  {
524
- conditions: {
525
- readerKnowledgeLevel: ["completeBeginners"],
526
- documentationDepth: ["essentialOnly"],
527
- },
528
- severity: "moderate",
529
- reason: "Complete beginners may need more explanations, not just core content",
530
- action: "filter",
531
- conflictingOptions: {
532
- documentationDepth: ["essentialOnly"],
533
- },
450
+ conflictItems: ["endUsers", "developers"],
451
+ strategy: "separate_user_paths",
452
+ description: "End users and developers conflict, resolved through separate user paths",
453
+ },
454
+ {
455
+ conflictItems: ["endUsers", "devops"],
456
+ strategy: "role_based_sections",
457
+ description: "End users and DevOps conflict, resolved through role-based sections",
458
+ },
459
+ {
460
+ conflictItems: ["developers", "decisionMakers"],
461
+ strategy: "progressive_disclosure",
462
+ description:
463
+ "Developers and decision makers conflict, resolved through progressive disclosure",
534
464
  },
535
465
  ],
536
466
  };
467
+
468
+ // Resolution strategy descriptions
469
+ export const RESOLUTION_STRATEGIES = {
470
+ layered_structure: (items) =>
471
+ `Detected "${items.join('" and "')}" purpose conflict. Resolution strategy: Create layered document structure
472
+ - Quick start section: Uses "get started" style - optimizes for speed, key steps, working examples, skips complex edge cases
473
+ - API reference section: Uses "find answers" style - comprehensive coverage, searchability, rich examples, skips narrative flow
474
+ - Ensure sections complement rather than conflict with each other`,
475
+
476
+ separate_sections: (items) =>
477
+ `Detected "${items.join('" and "')}" purpose conflict. Resolution strategy: Create separate sections
478
+ - Quick start section: Uses "get started" style - focuses on practical operations, completable within 30 minutes
479
+ - System understanding section: Uses "understand system" style - dedicated to explaining architecture, concepts, design decision rationale
480
+ - Meet different depth needs through clear section separation`,
481
+
482
+ concepts_then_practice: (items) =>
483
+ `Detected "${items.join('" and "')}" purpose conflict. Resolution strategy: Use progressive "concepts-then-practice" structure
484
+ - Concepts section: Uses "understand system" style - first explains core concepts and architecture principles
485
+ - Practice section: Uses "complete tasks" style - then provides specific step guidance and practical scenarios
486
+ - Ensure smooth transition between theory and practice`,
487
+
488
+ reference_with_troubleshooting: (items) =>
489
+ `Detected "${items.join('" and "')}" purpose conflict. Resolution strategy: Integrate troubleshooting into API reference
490
+ - API reference section: Uses "find answers" style - comprehensive feature documentation and parameter descriptions
491
+ - Troubleshooting section: Uses "solve problems" style - add common issues and diagnostic methods for each feature
492
+ - Create dedicated problem diagnosis index for quick location`,
493
+
494
+ separate_user_paths: (items) =>
495
+ `Detected "${items.join('" and "')}" audience conflict. Resolution strategy: Create separate user paths
496
+ - User guide path: Uses "end users" style - simple language, UI operations, screenshot instructions, business outcome oriented
497
+ - Developer guide path: Uses "developers" style - code-first, technical precision, SDK examples, configuration snippets
498
+ - Provide clear path navigation for users to choose appropriate entry point`,
499
+
500
+ role_based_sections: (items) =>
501
+ `Detected "${items.join('" and "')}" audience conflict. Resolution strategy: Organize content by role
502
+ - Create dedicated sections for different roles, each section uses corresponding audience style
503
+ - Ensure content depth and expression precisely match the needs and background of corresponding audience
504
+ - Provide cross-references between sections to facilitate collaborative understanding between roles`,
505
+
506
+ progressive_disclosure: (items) =>
507
+ `Detected "${items.join('" and "')}" audience conflict. Resolution strategy: Use progressive information disclosure
508
+ - Overview level: Uses "decision makers" style - high-level architecture diagrams, decision points, business value
509
+ - Detail level: Uses "developers" style - technical implementation details, code examples, best practices
510
+ - Ensure smooth transition from strategic to tactical`,
511
+ };
512
+
513
+ export const D2_CONFIG = `vars: {
514
+ d2-config: {
515
+ layout-engine: elk
516
+ theme-id: 0
517
+ theme-overrides: {
518
+ N1: "#2AA7A1"
519
+ N2: "#73808C"
520
+
521
+ N4: "#FFFFFF"
522
+ N5: "#FAFBFC"
523
+
524
+ N7: "#ffffff"
525
+
526
+ B1: "#8EDDD9"
527
+ B2: "#C9DCE6"
528
+ B3: "#EEF9F9"
529
+ B4: "#F7F8FA"
530
+ B5: "#FCFDFD"
531
+ B6: "#E3E9F0"
532
+
533
+
534
+ AA2: "#9EB7C5"
535
+ AA4: "#E3EBF2"
536
+ AA5: "#F6FAFC"
537
+
538
+ AB4: "#B8F1F6"
539
+ AB5: "#E3F8FA"
540
+ }
541
+ }
542
+ }`;
543
+
544
+ export const KROKI_CONCURRENCY = 5;
545
+ export const FILE_CONCURRENCY = 3;
546
+ export const TMP_DIR = ".tmp";
547
+ export const TMP_DOCS_DIR = "docs";
548
+
549
+ export const TMP_ASSETS_DIR = "assets";
@@ -0,0 +1,162 @@
1
+ import path from "node:path";
2
+
3
+ import fs from "fs-extra";
4
+ import { glob } from "glob";
5
+ import pMap from "p-map";
6
+ import { joinURL } from "ufo";
7
+
8
+ import {
9
+ D2_CONFIG,
10
+ FILE_CONCURRENCY,
11
+ KROKI_CONCURRENCY,
12
+ TMP_ASSETS_DIR,
13
+ TMP_DIR,
14
+ } from "./constants.mjs";
15
+ import { getContentHash } from "./utils.mjs";
16
+
17
+ export async function getChart({ chart = "d2", format = "svg", content, strict }) {
18
+ const baseUrl = "https://chart.abtnet.io";
19
+
20
+ try {
21
+ const res = await fetch(joinURL(baseUrl, chart, format), {
22
+ method: "POST",
23
+ body: content,
24
+ headers: {
25
+ Accept: "image/svg+xml",
26
+ "Content-Type": "text/plain",
27
+ },
28
+ });
29
+ if (strict && !res.ok) {
30
+ throw new Error(`Failed to fetch chart: ${res.status} ${res.statusText}`);
31
+ }
32
+
33
+ const data = await res.text();
34
+ return data;
35
+ } catch (err) {
36
+ if (strict) throw err;
37
+
38
+ console.error("Failed to generate chart from:", baseUrl, err);
39
+ return null;
40
+ }
41
+ }
42
+
43
+ export async function getD2Svg({ content, strict = false }) {
44
+ const svgContent = await getChart({
45
+ chart: "d2",
46
+ format: "svg",
47
+ content,
48
+ strict,
49
+ });
50
+ return svgContent;
51
+ }
52
+
53
+ // Helper: save d2 svg assets alongside document
54
+ export async function saveD2Assets({ markdown, docsDir }) {
55
+ const codeBlockRegex = /```d2\n([\s\S]*?)```/g;
56
+
57
+ const { replaced } = await runIterator({
58
+ input: markdown,
59
+ regexp: codeBlockRegex,
60
+ replace: true,
61
+ fn: async ([_match, _code]) => {
62
+ const assetDir = path.join(docsDir, "../", TMP_ASSETS_DIR, "d2");
63
+ await fs.ensureDir(assetDir);
64
+ const d2Content = [D2_CONFIG, _code].join("\n");
65
+ const fileName = `${getContentHash(d2Content)}.svg`;
66
+ const svgPath = path.join(assetDir, fileName);
67
+
68
+ if (await fs.pathExists(svgPath)) {
69
+ if (process.env.DEBUG) {
70
+ console.log("Found assets cache, skipping generation", svgPath);
71
+ }
72
+ } else {
73
+ if (process.env.DEBUG) {
74
+ console.log("start generate d2 chart", svgPath);
75
+ }
76
+ try {
77
+ const svg = await getD2Svg({ content: d2Content });
78
+ if (svg) {
79
+ await fs.writeFile(svgPath, svg, { encoding: "utf8" });
80
+ }
81
+ } catch (error) {
82
+ if (process.env.DEBUG) {
83
+ console.warn("Failed to generate D2 chart:", error);
84
+ }
85
+ return _code;
86
+ }
87
+ }
88
+ return `![](../${TMP_ASSETS_DIR}/d2/${fileName})`;
89
+ },
90
+ options: { concurrency: KROKI_CONCURRENCY },
91
+ });
92
+
93
+ return replaced;
94
+ }
95
+
96
+ export async function beforePublishHook({ docsDir }) {
97
+ // Example: process each markdown file (replace with your logic)
98
+ const mdFilePaths = await glob("**/*.md", { cwd: docsDir });
99
+ await pMap(
100
+ mdFilePaths,
101
+ async (filePath) => {
102
+ let finalContent = await fs.readFile(path.join(docsDir, filePath), { encoding: "utf8" });
103
+ finalContent = await saveD2Assets({ markdown: finalContent, docsDir });
104
+
105
+ await fs.writeFile(path.join(docsDir, filePath), finalContent, { encoding: "utf8" });
106
+ },
107
+ { concurrency: FILE_CONCURRENCY },
108
+ );
109
+ }
110
+
111
+ async function runIterator({ input, regexp, fn = () => {}, options, replace = false }) {
112
+ if (!input) return input;
113
+ const matches = [...input.matchAll(regexp)];
114
+ const results = [];
115
+ await pMap(
116
+ matches,
117
+ async (...args) => {
118
+ const resultItem = await fn(...args);
119
+ results.push(resultItem);
120
+ },
121
+ options,
122
+ );
123
+
124
+ let replaced = input;
125
+ if (replace) {
126
+ let index = 0;
127
+ replaced = replaced.replace(regexp, () => {
128
+ return results[index++];
129
+ });
130
+ }
131
+
132
+ return {
133
+ results,
134
+ replaced,
135
+ };
136
+ }
137
+
138
+ export async function checkD2Content({ content }) {
139
+ await ensureTmpDir();
140
+ const assetDir = path.join(".aigne", "doc-smith", TMP_DIR, TMP_ASSETS_DIR, "d2");
141
+ await fs.ensureDir(assetDir);
142
+ const d2Content = [D2_CONFIG, content].join("\n");
143
+ const fileName = `${getContentHash(d2Content)}.svg`;
144
+ const svgPath = path.join(assetDir, fileName);
145
+ if (await fs.pathExists(svgPath)) {
146
+ if (process.env.DEBUG) {
147
+ console.log("Found assets cache, skipping generation", svgPath);
148
+ }
149
+ return;
150
+ }
151
+
152
+ const svg = await getD2Svg({ content: d2Content, strict: true });
153
+ await fs.writeFile(svgPath, svg, { encoding: "utf8" });
154
+ }
155
+
156
+ export async function ensureTmpDir() {
157
+ const tmpDir = path.join(".aigne", "doc-smith", TMP_DIR);
158
+ if (!(await fs.pathExists(path.join(tmpDir, ".gitignore")))) {
159
+ await fs.ensureDir(tmpDir);
160
+ await fs.writeFile(path.join(tmpDir, ".gitignore"), "**/*", { encoding: "utf8" });
161
+ }
162
+ }