@aigne/doc-smith 0.6.0 → 0.7.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.
- package/.aigne/doc-smith/config.yaml +70 -0
- package/.aigne/doc-smith/output/structure-plan.json +152 -0
- package/.aigne/doc-smith/preferences.yml +31 -0
- package/.aigne/doc-smith/upload-cache.yaml +288 -0
- package/.github/workflows/ci.yml +46 -0
- package/.github/workflows/reviewer.yml +2 -1
- package/CHANGELOG.md +17 -0
- package/README.md +33 -15
- package/agents/chat.yaml +30 -0
- package/agents/check-structure-plan.mjs +1 -1
- package/agents/docs-fs.yaml +25 -0
- package/agents/exit.mjs +6 -0
- package/agents/feedback-refiner.yaml +5 -1
- package/agents/find-items-by-paths.mjs +10 -4
- package/agents/fs.mjs +60 -0
- package/agents/input-generator.mjs +150 -91
- package/agents/load-config.mjs +0 -5
- package/agents/load-sources.mjs +61 -8
- package/agents/publish-docs.mjs +27 -12
- package/agents/retranslate.yaml +1 -1
- package/agents/team-publish-docs.yaml +2 -2
- package/aigne.yaml +1 -0
- package/docs/_sidebar.md +17 -0
- package/docs/advanced-how-it-works.md +104 -0
- package/docs/advanced-how-it-works.zh.md +104 -0
- package/docs/advanced-quality-assurance.md +64 -0
- package/docs/advanced-quality-assurance.zh.md +64 -0
- package/docs/advanced.md +28 -0
- package/docs/advanced.zh.md +28 -0
- package/docs/changelog.md +272 -0
- package/docs/changelog.zh.md +272 -0
- package/docs/cli-reference.md +185 -0
- package/docs/cli-reference.zh.md +185 -0
- package/docs/configuration-interactive-setup.md +82 -0
- package/docs/configuration-interactive-setup.zh.md +82 -0
- package/docs/configuration-language-support.md +64 -0
- package/docs/configuration-language-support.zh.md +64 -0
- package/docs/configuration-llm-setup.md +90 -0
- package/docs/configuration-llm-setup.zh.md +90 -0
- package/docs/configuration-preferences.md +122 -0
- package/docs/configuration-preferences.zh.md +123 -0
- package/docs/configuration.md +173 -0
- package/docs/configuration.zh.md +173 -0
- package/docs/features-generate-documentation.md +82 -0
- package/docs/features-generate-documentation.zh.md +82 -0
- package/docs/features-publish-your-docs.md +98 -0
- package/docs/features-publish-your-docs.zh.md +98 -0
- package/docs/features-translate-documentation.md +83 -0
- package/docs/features-translate-documentation.zh.md +83 -0
- package/docs/features-update-and-refine.md +86 -0
- package/docs/features-update-and-refine.zh.md +86 -0
- package/docs/features.md +56 -0
- package/docs/features.zh.md +56 -0
- package/docs/getting-started.md +74 -0
- package/docs/getting-started.zh.md +74 -0
- package/docs/overview.md +48 -0
- package/docs/overview.zh.md +48 -0
- package/media.md +19 -0
- package/package.json +13 -10
- package/prompts/content-detail-generator.md +7 -3
- package/prompts/document/custom-components.md +80 -0
- package/prompts/document/d2-chart/diy-examples.md +44 -0
- package/prompts/document/d2-chart/official-examples.md +708 -0
- package/prompts/document/d2-chart/rules.md +48 -0
- package/prompts/document/detail-generator.md +12 -15
- package/prompts/document/structure-planning.md +1 -3
- package/prompts/feedback-refiner.md +81 -60
- package/prompts/structure-planning.md +20 -3
- package/tests/check-detail-result.test.mjs +3 -4
- package/tests/conflict-resolution.test.mjs +237 -0
- package/tests/input-generator.test.mjs +940 -0
- package/tests/load-sources.test.mjs +627 -3
- package/tests/preferences-utils.test.mjs +94 -0
- package/tests/save-value-to-config.test.mjs +182 -5
- package/tests/utils.test.mjs +49 -0
- package/utils/conflict-detector.mjs +72 -1
- package/utils/constants.mjs +125 -124
- package/utils/kroki-utils.mjs +162 -0
- package/utils/markdown-checker.mjs +98 -70
- package/utils/utils.mjs +96 -28
package/utils/utils.mjs
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
|
+
import crypto from "node:crypto";
|
|
2
3
|
import { accessSync, constants, existsSync, mkdirSync, readdirSync, statSync } from "node:fs";
|
|
3
4
|
import fs from "node:fs/promises";
|
|
4
5
|
import path from "node:path";
|
|
5
|
-
import { parse } from "yaml";
|
|
6
|
+
import { parse, stringify as yamlStringify } from "yaml";
|
|
7
|
+
import {
|
|
8
|
+
detectResolvableConflicts,
|
|
9
|
+
generateConflictResolutionRules,
|
|
10
|
+
} from "./conflict-detector.mjs";
|
|
6
11
|
import {
|
|
7
12
|
DEFAULT_EXCLUDE_PATTERNS,
|
|
8
13
|
DEFAULT_INCLUDE_PATTERNS,
|
|
@@ -32,6 +37,16 @@ export function toRelativePath(filePath) {
|
|
|
32
37
|
return path.isAbsolute(filePath) ? path.relative(process.cwd(), filePath) : filePath;
|
|
33
38
|
}
|
|
34
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Check if a string looks like a glob pattern
|
|
42
|
+
* @param {string} pattern - The string to check
|
|
43
|
+
* @returns {boolean} - True if the string contains glob pattern characters
|
|
44
|
+
*/
|
|
45
|
+
export function isGlobPattern(pattern) {
|
|
46
|
+
if (pattern == null) return false;
|
|
47
|
+
return /[*?[\]]|(\*\*)/.test(pattern);
|
|
48
|
+
}
|
|
49
|
+
|
|
35
50
|
export function processContent({ content }) {
|
|
36
51
|
// Match markdown regular links [text](link), exclude images 
|
|
37
52
|
return content.replace(/(?<!!)\[([^\]]+)\]\(([^)]+)\)/g, (match, text, link) => {
|
|
@@ -95,6 +110,7 @@ export async function saveDocWithTranslations({
|
|
|
95
110
|
|
|
96
111
|
// Add labels front matter if labels are provided
|
|
97
112
|
let finalContent = processContent({ content });
|
|
113
|
+
|
|
98
114
|
if (labels && labels.length > 0) {
|
|
99
115
|
const frontMatter = `---\nlabels: ${JSON.stringify(labels)}\n---\n\n`;
|
|
100
116
|
finalContent = frontMatter + finalContent;
|
|
@@ -113,6 +129,7 @@ export async function saveDocWithTranslations({
|
|
|
113
129
|
let finalTranslationContent = processContent({
|
|
114
130
|
content: translate.translation,
|
|
115
131
|
});
|
|
132
|
+
|
|
116
133
|
if (labels && labels.length > 0) {
|
|
117
134
|
const frontMatter = `---\nlabels: ${JSON.stringify(labels)}\n---\n\n`;
|
|
118
135
|
finalTranslationContent = frontMatter + finalTranslationContent;
|
|
@@ -149,7 +166,7 @@ export function getCurrentGitHead() {
|
|
|
149
166
|
* @param {string} gitHead - The current git HEAD commit hash
|
|
150
167
|
*/
|
|
151
168
|
export async function saveGitHeadToConfig(gitHead) {
|
|
152
|
-
if (!gitHead || process.env.NODE_ENV ===
|
|
169
|
+
if (!gitHead || process.env.NODE_ENV === "test" || process.env.BUN_TEST) {
|
|
153
170
|
return; // Skip if no git HEAD available or in test environment
|
|
154
171
|
}
|
|
155
172
|
|
|
@@ -169,7 +186,9 @@ export async function saveGitHeadToConfig(gitHead) {
|
|
|
169
186
|
|
|
170
187
|
// Check if lastGitHead already exists in the file
|
|
171
188
|
const lastGitHeadRegex = /^lastGitHead:\s*.*$/m;
|
|
172
|
-
|
|
189
|
+
// Use yaml library to safely serialize the git head value
|
|
190
|
+
const yamlContent = yamlStringify({ lastGitHead: gitHead }).trim();
|
|
191
|
+
const newLastGitHeadLine = yamlContent;
|
|
173
192
|
|
|
174
193
|
if (lastGitHeadRegex.test(fileContent)) {
|
|
175
194
|
// Replace existing lastGitHead line
|
|
@@ -287,8 +306,10 @@ export function hasFileChangesBetweenCommits(
|
|
|
287
306
|
return addedOrDeletedFiles.some((filePath) => {
|
|
288
307
|
// Check if file matches any include pattern
|
|
289
308
|
const matchesInclude = includePatterns.some((pattern) => {
|
|
290
|
-
//
|
|
291
|
-
const
|
|
309
|
+
// First escape all regex special characters except * and ?
|
|
310
|
+
const escapedPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
311
|
+
// Then convert glob wildcards to regex
|
|
312
|
+
const regexPattern = escapedPattern.replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
292
313
|
const regex = new RegExp(regexPattern);
|
|
293
314
|
return regex.test(filePath);
|
|
294
315
|
});
|
|
@@ -299,8 +320,10 @@ export function hasFileChangesBetweenCommits(
|
|
|
299
320
|
|
|
300
321
|
// Check if file matches any exclude pattern
|
|
301
322
|
const matchesExclude = excludePatterns.some((pattern) => {
|
|
302
|
-
//
|
|
303
|
-
const
|
|
323
|
+
// First escape all regex special characters except * and ?
|
|
324
|
+
const escapedPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
325
|
+
// Then convert glob wildcards to regex
|
|
326
|
+
const regexPattern = escapedPattern.replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
304
327
|
const regex = new RegExp(regexPattern);
|
|
305
328
|
return regex.test(filePath);
|
|
306
329
|
});
|
|
@@ -351,9 +374,10 @@ export async function loadConfigFromFile() {
|
|
|
351
374
|
* @returns {string} Updated file content
|
|
352
375
|
*/
|
|
353
376
|
function handleArrayValueUpdate(key, value, comment, fileContent) {
|
|
354
|
-
//
|
|
355
|
-
const
|
|
356
|
-
|
|
377
|
+
// Use yaml library to safely serialize the key-value pair
|
|
378
|
+
const yamlObject = { [key]: value };
|
|
379
|
+
const yamlContent = yamlStringify(yamlObject).trim();
|
|
380
|
+
const formattedValue = yamlContent;
|
|
357
381
|
|
|
358
382
|
const lines = fileContent.split("\n");
|
|
359
383
|
|
|
@@ -435,7 +459,10 @@ function handleArrayValueUpdate(key, value, comment, fileContent) {
|
|
|
435
459
|
* @returns {string} Updated file content
|
|
436
460
|
*/
|
|
437
461
|
function handleStringValueUpdate(key, value, comment, fileContent) {
|
|
438
|
-
|
|
462
|
+
// Use yaml library to safely serialize the key-value pair
|
|
463
|
+
const yamlObject = { [key]: value };
|
|
464
|
+
const yamlContent = yamlStringify(yamlObject).trim();
|
|
465
|
+
const formattedValue = yamlContent;
|
|
439
466
|
const lines = fileContent.split("\n");
|
|
440
467
|
|
|
441
468
|
// Handle string values (original logic)
|
|
@@ -849,6 +876,29 @@ export function processConfigFields(config) {
|
|
|
849
876
|
const processed = {};
|
|
850
877
|
const allRulesContent = [];
|
|
851
878
|
|
|
879
|
+
// Set default values for missing or empty fields
|
|
880
|
+
const defaults = {
|
|
881
|
+
nodeName: "Section",
|
|
882
|
+
locale: "en",
|
|
883
|
+
sourcesPath: ["./"],
|
|
884
|
+
docsDir: "./.aigne/doc-smith/docs",
|
|
885
|
+
outputDir: "./.aigne/doc-smith/output",
|
|
886
|
+
translateLanguages: [],
|
|
887
|
+
rules: "",
|
|
888
|
+
targetAudience: "",
|
|
889
|
+
};
|
|
890
|
+
|
|
891
|
+
// Apply defaults for missing or empty fields
|
|
892
|
+
for (const [key, defaultValue] of Object.entries(defaults)) {
|
|
893
|
+
if (
|
|
894
|
+
!config[key] ||
|
|
895
|
+
(Array.isArray(defaultValue) && (!config[key] || config[key].length === 0)) ||
|
|
896
|
+
(typeof defaultValue === "string" && (!config[key] || config[key].trim() === ""))
|
|
897
|
+
) {
|
|
898
|
+
processed[key] = defaultValue;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
852
902
|
// Check if original rules field has content
|
|
853
903
|
if (config.rules) {
|
|
854
904
|
if (typeof config.rules === "string") {
|
|
@@ -868,27 +918,35 @@ export function processConfigFields(config) {
|
|
|
868
918
|
}
|
|
869
919
|
|
|
870
920
|
// Process document purpose (array)
|
|
871
|
-
let purposeContents = "";
|
|
872
921
|
if (config.documentPurpose && Array.isArray(config.documentPurpose)) {
|
|
873
|
-
|
|
874
|
-
.map((key) =>
|
|
875
|
-
|
|
876
|
-
|
|
922
|
+
const purposeRules = config.documentPurpose
|
|
923
|
+
.map((key) => {
|
|
924
|
+
const style = DOCUMENT_STYLES[key];
|
|
925
|
+
if (!style) return null;
|
|
926
|
+
return `Document Purpose - ${style.name}:\n${style.description}\n${style.content}`;
|
|
927
|
+
})
|
|
928
|
+
.filter(Boolean);
|
|
877
929
|
|
|
878
|
-
if (
|
|
879
|
-
allRulesContent.push(
|
|
930
|
+
if (purposeRules.length > 0) {
|
|
931
|
+
allRulesContent.push(purposeRules.join("\n\n"));
|
|
880
932
|
}
|
|
881
933
|
}
|
|
882
934
|
|
|
883
935
|
// Process target audience types (array)
|
|
884
|
-
let audienceContents = "";
|
|
885
936
|
let audienceNames = "";
|
|
886
937
|
if (config.targetAudienceTypes && Array.isArray(config.targetAudienceTypes)) {
|
|
887
|
-
// Get content for rules
|
|
888
|
-
|
|
889
|
-
.map((key) =>
|
|
890
|
-
|
|
891
|
-
|
|
938
|
+
// Get structured content for rules
|
|
939
|
+
const audienceRules = config.targetAudienceTypes
|
|
940
|
+
.map((key) => {
|
|
941
|
+
const audience = TARGET_AUDIENCES[key];
|
|
942
|
+
if (!audience) return null;
|
|
943
|
+
return `Target Audience - ${audience.name}:\n${audience.description}\n${audience.content}`;
|
|
944
|
+
})
|
|
945
|
+
.filter(Boolean);
|
|
946
|
+
|
|
947
|
+
if (audienceRules.length > 0) {
|
|
948
|
+
allRulesContent.push(audienceRules.join("\n\n"));
|
|
949
|
+
}
|
|
892
950
|
|
|
893
951
|
// Get names for targetAudience field
|
|
894
952
|
audienceNames = config.targetAudienceTypes
|
|
@@ -896,10 +954,6 @@ export function processConfigFields(config) {
|
|
|
896
954
|
.filter(Boolean)
|
|
897
955
|
.join(", ");
|
|
898
956
|
|
|
899
|
-
if (audienceContents) {
|
|
900
|
-
allRulesContent.push(audienceContents);
|
|
901
|
-
}
|
|
902
|
-
|
|
903
957
|
if (audienceNames) {
|
|
904
958
|
// Check if original targetAudience field has content
|
|
905
959
|
const existingTargetAudience = config.targetAudience?.trim();
|
|
@@ -933,6 +987,16 @@ export function processConfigFields(config) {
|
|
|
933
987
|
}
|
|
934
988
|
}
|
|
935
989
|
|
|
990
|
+
// Detect and handle conflicts in user selections
|
|
991
|
+
const conflicts = detectResolvableConflicts(config);
|
|
992
|
+
if (conflicts.length > 0) {
|
|
993
|
+
const conflictResolutionRules = generateConflictResolutionRules(conflicts);
|
|
994
|
+
allRulesContent.push(conflictResolutionRules);
|
|
995
|
+
|
|
996
|
+
// Store conflict information for debugging/logging
|
|
997
|
+
processed.detectedConflicts = conflicts;
|
|
998
|
+
}
|
|
999
|
+
|
|
936
1000
|
// Combine all content into rules field
|
|
937
1001
|
if (allRulesContent.length > 0) {
|
|
938
1002
|
processed.rules = allRulesContent.join("\n\n");
|
|
@@ -1087,3 +1151,7 @@ export function detectSystemLanguage() {
|
|
|
1087
1151
|
return "en";
|
|
1088
1152
|
}
|
|
1089
1153
|
}
|
|
1154
|
+
|
|
1155
|
+
export function getContentHash(str) {
|
|
1156
|
+
return crypto.createHash("sha256").update(str).digest("hex");
|
|
1157
|
+
}
|