@aigne/doc-smith 0.8.12-beta → 0.8.12-beta.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.
Files changed (54) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +20 -0
  3. package/agents/generate/check-diagram.mjs +40 -0
  4. package/agents/generate/draw-diagram.yaml +23 -0
  5. package/agents/generate/generate-structure.yaml +5 -1
  6. package/agents/generate/merge-d2-diagram.yaml +3 -3
  7. package/agents/generate/update-document-structure.yaml +5 -2
  8. package/agents/generate/user-review-document-structure.mjs +7 -0
  9. package/agents/generate/wrap-diagram-code.mjs +35 -0
  10. package/agents/history/index.yaml +6 -0
  11. package/agents/history/view.mjs +75 -0
  12. package/agents/translate/index.yaml +3 -2
  13. package/agents/translate/record-translation-history.mjs +19 -0
  14. package/agents/translate/translate-multilingual.yaml +2 -1
  15. package/agents/update/batch-update-document.yaml +1 -1
  16. package/agents/update/check-document.mjs +5 -1
  17. package/agents/update/generate-document.yaml +31 -25
  18. package/agents/update/{generate-and-translate-document.yaml → handle-document-update.yaml} +2 -11
  19. package/agents/update/index.yaml +1 -0
  20. package/agents/update/save-and-translate-document.mjs +106 -0
  21. package/agents/update/update-document-detail.yaml +5 -1
  22. package/agents/update/update-single-document.yaml +1 -10
  23. package/agents/update/user-review-document.mjs +10 -1
  24. package/aigne.yaml +8 -1
  25. package/package.json +1 -1
  26. package/prompts/detail/d2-diagram/guide.md +19 -0
  27. package/prompts/detail/d2-diagram/role-and-personality.md +2 -0
  28. package/prompts/detail/d2-diagram/rules.md +24 -0
  29. package/prompts/detail/d2-diagram/{rules-system.md → system-prompt.md} +3 -9
  30. package/prompts/detail/{document-rules.md → generate/document-rules.md} +1 -1
  31. package/prompts/detail/generate/system-prompt.md +72 -0
  32. package/prompts/detail/generate/user-prompt.md +54 -0
  33. package/prompts/detail/{update-document.md → update/system-prompt.md} +43 -67
  34. package/prompts/detail/update/user-prompt.md +33 -0
  35. package/prompts/structure/{generate-structure-system.md → generate/system-prompt.md} +7 -40
  36. package/prompts/structure/{generate-structure-user.md → generate/user-prompt.md} +17 -13
  37. package/prompts/structure/{update-document-structure.md → update/system-prompt.md} +16 -27
  38. package/prompts/structure/update/user-prompt.md +19 -0
  39. package/tests/agents/generate/user-review-document-structure.test.mjs +2 -0
  40. package/tests/agents/update/check-document.test.mjs +1 -1
  41. package/tests/agents/update/save-and-translate-document.test.mjs +369 -0
  42. package/tests/agents/utils/check-detail-result.test.mjs +13 -0
  43. package/tests/utils/d2-utils.test.mjs +14 -0
  44. package/tests/utils/docs-finder-utils.test.mjs +13 -0
  45. package/tests/utils/history-utils.test.mjs +178 -0
  46. package/utils/d2-utils.mjs +9 -0
  47. package/utils/docs-finder-utils.mjs +10 -1
  48. package/utils/history-utils.mjs +191 -0
  49. package/utils/markdown-checker.mjs +20 -0
  50. package/agents/generate/check-d2-diagram-valid.mjs +0 -26
  51. package/agents/generate/generate-d2-diagram.yaml +0 -23
  52. package/prompts/detail/generate-document.md +0 -125
  53. /package/prompts/detail/d2-diagram/{rules-user.md → user-prompt.md} +0 -0
  54. /package/prompts/detail/{detail-example.md → generate/detail-example.md} +0 -0
@@ -0,0 +1,191 @@
1
+ import { execSync } from "node:child_process";
2
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { parse, stringify } from "yaml";
5
+ import { DOC_SMITH_DIR } from "./constants/index.mjs";
6
+
7
+ const HISTORY_FILE = "history.yaml";
8
+
9
+ /**
10
+ * Check if git is available in the system
11
+ */
12
+ export function isGitAvailable() {
13
+ try {
14
+ execSync("git --version", { stdio: "ignore" });
15
+ return true;
16
+ } catch {
17
+ return false;
18
+ }
19
+ }
20
+
21
+ /**
22
+ * Initialize git repo in DOC_SMITH_DIR if not exists
23
+ */
24
+ export function ensureGitRepo() {
25
+ if (!isGitAvailable()) return false;
26
+
27
+ const gitDir = join(process.cwd(), DOC_SMITH_DIR, ".git");
28
+
29
+ if (!existsSync(gitDir)) {
30
+ try {
31
+ const cwd = join(process.cwd(), DOC_SMITH_DIR);
32
+
33
+ execSync("git init", { cwd, stdio: "ignore" });
34
+
35
+ // Create .gitignore to exclude temporary files
36
+ const gitignore = "*.tmp\n";
37
+ writeFileSync(join(cwd, ".gitignore"), gitignore);
38
+
39
+ // Initial commit
40
+ execSync('git add .gitignore && git commit -m "Initialize doc-smith history"', {
41
+ cwd,
42
+ stdio: "ignore",
43
+ });
44
+
45
+ console.log("✔ Git history tracking initialized");
46
+ return true;
47
+ } catch (error) {
48
+ console.warn("Failed to initialize git history:", error.message);
49
+ return false;
50
+ }
51
+ }
52
+
53
+ return true;
54
+ }
55
+
56
+ /**
57
+ * Record update using git commit (if available)
58
+ */
59
+ function recordUpdateGit({ feedback }) {
60
+ try {
61
+ const cwd = join(process.cwd(), DOC_SMITH_DIR);
62
+
63
+ // Stage changed files (only if they exist)
64
+ const filesToAdd = ["docs/", "config.yaml", "preferences.yml", "history.yaml"]
65
+ .filter((file) => existsSync(join(cwd, file)))
66
+ .join(" ");
67
+
68
+ if (filesToAdd) {
69
+ execSync(`git add ${filesToAdd}`, {
70
+ cwd,
71
+ stdio: "ignore",
72
+ });
73
+ }
74
+
75
+ // Check if there are changes to commit
76
+ try {
77
+ execSync("git diff --cached --quiet", { cwd, stdio: "ignore" });
78
+ console.log("✔ No update history changes to commit");
79
+ return; // No changes
80
+ } catch {
81
+ // Has changes, continue
82
+ }
83
+
84
+ // Build commit message (only user feedback)
85
+ const message = feedback;
86
+
87
+ // Commit
88
+ execSync(`git commit -m ${JSON.stringify(message)}`, {
89
+ cwd,
90
+ stdio: "ignore",
91
+ });
92
+ console.log("✔ Update history committed successfully");
93
+ } catch (error) {
94
+ console.warn("Update history commit failed:", error.message);
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Record update in YAML file (always)
100
+ */
101
+ function recordUpdateYaml({ operation, feedback, documentPath = null }) {
102
+ try {
103
+ const docSmithDir = join(process.cwd(), DOC_SMITH_DIR);
104
+ if (!existsSync(docSmithDir)) {
105
+ mkdirSync(docSmithDir, { recursive: true });
106
+ }
107
+ const historyPath = join(docSmithDir, HISTORY_FILE);
108
+
109
+ // Read existing history
110
+ let history = { entries: [] };
111
+ if (existsSync(historyPath)) {
112
+ try {
113
+ const content = readFileSync(historyPath, "utf8");
114
+ history = parse(content) || { entries: [] };
115
+ } catch (error) {
116
+ console.warn("Failed to read history file:", error.message);
117
+ }
118
+ }
119
+
120
+ // Create new entry
121
+ const entry = {
122
+ timestamp: new Date().toISOString(),
123
+ operation,
124
+ feedback,
125
+ };
126
+
127
+ // Add document path if provided
128
+ if (documentPath) {
129
+ entry.documentPath = documentPath;
130
+ }
131
+
132
+ // Add to beginning (newest first)
133
+ history.entries = history.entries || [];
134
+ history.entries.unshift(entry);
135
+
136
+ // Write back
137
+ const yamlContent = stringify(history, {
138
+ indent: 2,
139
+ lineWidth: 100,
140
+ });
141
+
142
+ writeFileSync(historyPath, yamlContent, "utf8");
143
+ } catch (error) {
144
+ console.warn("YAML history tracking failed:", error.message);
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Record an update after user feedback
150
+ * - Always writes to YAML
151
+ * - Also commits to git if available
152
+ * @param {Object} params
153
+ * @param {string} params.operation - Type of operation (e.g., 'document_update', 'structure_update', 'translation_update')
154
+ * @param {string} params.feedback - User feedback text
155
+ * @param {string} params.documentPath - Document path - should be a string
156
+ */
157
+ export function recordUpdate({ operation, feedback, documentPath = null }) {
158
+ // Skip if no feedback
159
+ if (!feedback?.trim()) return;
160
+
161
+ // Always record in YAML
162
+ recordUpdateYaml({ operation, feedback, documentPath });
163
+
164
+ // Also record in git if available
165
+ if (isGitAvailable()) {
166
+ // Initialize git repo on first update if not exists
167
+ ensureGitRepo();
168
+ recordUpdateGit({ feedback });
169
+ } else {
170
+ console.warn("Git is not available, skipping git based update history");
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Get history entries from YAML
176
+ */
177
+ export function getHistory() {
178
+ const historyPath = join(process.cwd(), DOC_SMITH_DIR, HISTORY_FILE);
179
+
180
+ if (!existsSync(historyPath)) {
181
+ return { entries: [] };
182
+ }
183
+
184
+ try {
185
+ const content = readFileSync(historyPath, "utf8");
186
+ return parse(content) || { entries: [] };
187
+ } catch (error) {
188
+ console.warn("Failed to read history:", error.message);
189
+ return { entries: [] };
190
+ }
191
+ }
@@ -1,11 +1,14 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import pMap from "p-map";
3
4
  import remarkGfm from "remark-gfm";
4
5
  import remarkLint from "remark-lint";
5
6
  import remarkParse from "remark-parse";
6
7
  import { unified } from "unified";
7
8
  import { visit } from "unist-util-visit";
8
9
  import { VFile } from "vfile";
10
+ import { KROKI_CONCURRENCY } from "./constants/index.mjs";
11
+ import { checkContent, isValidCode } from "./d2-utils.mjs";
9
12
  import { validateMermaidSyntax } from "./mermaid-validator.mjs";
10
13
 
11
14
  /**
@@ -375,6 +378,7 @@ export async function checkMarkdown(markdown, source = "content", options = {})
375
378
 
376
379
  // Check mermaid code blocks and other custom validations
377
380
  const mermaidChecks = [];
381
+ const d2ChecksList = [];
378
382
  visit(ast, "code", (node) => {
379
383
  if (node.lang) {
380
384
  const line = node.position?.start?.line || "unknown";
@@ -463,6 +467,12 @@ export async function checkMarkdown(markdown, source = "content", options = {})
463
467
  specialCharMatch = nodeWithSpecialCharsRegex.exec(mermaidContent);
464
468
  }
465
469
  }
470
+ if (isValidCode(node.lang)) {
471
+ d2ChecksList.push({
472
+ content: node.value,
473
+ line,
474
+ });
475
+ }
466
476
  }
467
477
  });
468
478
 
@@ -514,6 +524,16 @@ export async function checkMarkdown(markdown, source = "content", options = {})
514
524
  // Wait for all mermaid checks to complete
515
525
  await Promise.all(mermaidChecks);
516
526
 
527
+ await pMap(
528
+ d2ChecksList,
529
+ async ({ content, line }) =>
530
+ checkContent({ content }).catch((err) => {
531
+ const errorMessage = err?.message || String(err) || "Unknown d2 syntax error";
532
+ errorMessages.push(`Found D2 syntax error in ${source} at line ${line}: ${errorMessage}`);
533
+ }),
534
+ { concurrency: KROKI_CONCURRENCY },
535
+ );
536
+
517
537
  // Run markdown linting rules
518
538
  await processor.run(ast, file);
519
539
 
@@ -1,26 +0,0 @@
1
- import { checkContent } from "../../utils/d2-utils.mjs";
2
-
3
- export default async function checkD2DiagramIsValid({ d2DiagramSourceCode }) {
4
- try {
5
- await checkContent({ content: d2DiagramSourceCode });
6
- return {
7
- isValid: true,
8
- };
9
- } catch (err) {
10
- return {
11
- isValid: false,
12
- error: err.message,
13
- };
14
- }
15
- }
16
-
17
- checkD2DiagramIsValid.input_schema = {
18
- type: "object",
19
- properties: {
20
- d2DiagramSourceCode: {
21
- type: "string",
22
- description: "Source code of d2 diagram",
23
- },
24
- },
25
- required: ["d2DiagramSourceCode"],
26
- };
@@ -1,23 +0,0 @@
1
- name: drawD2Diagram
2
- description: Generate a D2 diagram from document content.
3
- instructions:
4
- - role: system
5
- url: ../../prompts/detail/d2-diagram/rules-system.md
6
- - role: user
7
- url: ../../prompts/detail/d2-diagram/rules-user.md
8
- input_schema:
9
- type: object
10
- properties:
11
- documentContent:
12
- type: string
13
- description: Source code of current document (without the D2 diagram)
14
- required:
15
- - documentContent
16
- output_schema:
17
- type: object
18
- properties:
19
- d2DiagramSourceCode:
20
- type: string
21
- description: Source code to draw D2 diagram
22
- required:
23
- - d2DiagramSourceCode
@@ -1,125 +0,0 @@
1
- <role_and_goal>
2
- {% include "../common/document/role-and-personality.md" %}
3
-
4
- Your task is to generate detailed content for the current {{nodeName}} based on user-provided information: current {{nodeName}} details (including title, description, path), DataSources, documentStructure (overall structural planning), and other relevant information.
5
- </role_and_goal>
6
-
7
- <user_locale>
8
- {{ locale }}
9
- </user_locale>
10
-
11
- <user_rules>
12
- {{ rules }}
13
-
14
- ** Output content in {{ locale }} language **
15
- </user_rules>
16
-
17
- {% set operation_type = "generating" %}
18
- {% include "../common/document/user-preferences.md" %}
19
-
20
- {% if detailFeedback %}
21
- <content_review_feedback>
22
- {{ detailFeedback }}
23
- </content_review_feedback>
24
- {% endif %}
25
-
26
- <content_generation_rules>
27
-
28
- {% include "../common/document/content-rules-core.md" %}
29
-
30
-
31
- Documentation content generation rules:
32
- {% include "./document-rules.md" %}
33
-
34
- Custom component generation rules:
35
- {% include "custom/custom-components.md" %}
36
-
37
- Custom code block generation rules:
38
- {% include "custom/custom-code-block.md" %}
39
-
40
- </content_generation_rules>
41
-
42
- {% if glossary %}
43
- <terms>
44
- Glossary of specialized terms. Please ensure correct spelling when using these terms.
45
-
46
- {{glossary}}
47
- </terms>
48
- {% endif %}
49
-
50
- <document_structure>
51
- {{ documentStructureYaml }}
52
- </document_structure>
53
-
54
- <current_document>
55
- Current {{nodeName}} information:
56
- title: {{title}}
57
- description: {{description}}
58
- path: {{path}}
59
- parentId: {{parentId}}
60
- </current_document>
61
-
62
- {% if content %}
63
- Content from previous generation:
64
- <last_content>
65
- {{content}}
66
- </last_content>
67
- {% endif %}
68
-
69
- {% if feedback %}
70
- User feedback on previous generation:
71
- <feedback>
72
- {{feedback}}
73
- </feedback>
74
- {% endif %}
75
-
76
- {% if detailFeedback %}
77
- <content_review_feedback>
78
- {{ detailFeedback }}
79
- </content_review_feedback>
80
- {% endif %}
81
-
82
- <datasources>
83
- {{ detailDataSources }}
84
-
85
- {{ additionalInformation }}
86
-
87
- <media_list>
88
- {{ assetsContent }}
89
- </media_list>
90
-
91
- {% include "../common/document/media-handling-rules.md" %}
92
-
93
- </datasources>
94
-
95
-
96
- {% include "./detail-example.md" %}
97
-
98
- <output_constraints>
99
-
100
- 1. Output detailed text content for {{nodeName}}.
101
- 2. Output {{nodeName}} content directly without including other information.
102
- 3. Reference the style from examples only, **output content in {{locale}} language**
103
-
104
- </output_constraints>
105
-
106
-
107
- <tool-usage>
108
- 1. generateD2DiagramContent: Generate D2 diagram for the given document content
109
- - Use diagrams to clarify complex concepts and diversify the presentation of the page.
110
- - The document overview page must include an architecture diagram that illustrates the entire documentation structure.
111
- - For the first page of each section, include a structural diagram of the current module when it adds clarity.
112
- - For individual article pages, consider detailed flowcharts when the content or overall architecture warrants them.
113
- - The number of diagrams is flexible, but aim for 0-3 diagrams as a practical range.
114
-
115
- 2. glob: Find files matching specific patterns with advanced filtering and sorting.
116
-
117
- 3. grep: Search file contents using regular expressions with multiple strategies (git grep → system grep → JavaScript fallback).
118
-
119
- 4. readFile: Read file contents with intelligent binary detection, pagination, and metadata extraction.
120
-
121
- When to use Tools:
122
- - For each document, evaluate whether D2 diagrams are needed. If so, always use generateD2DiagramContent to add diagrams to the document
123
- - During document generation, if the given context is missing or lacks referenced content, use glob/grep/readFile to obtain more context
124
- - Code examples in generated documents must use APIs and packages defined in the input data sources. Do not generate non-existent code out of thin air. Use glob/grep/readFile to query related code or references
125
- </tool-usage>