@aigne/doc-smith 0.2.4 → 0.2.6
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/CHANGELOG.md +14 -0
- package/README.md +1 -0
- package/agents/check-detail-result.mjs +15 -120
- package/agents/check-structure-plan.mjs +56 -7
- package/agents/detail-generator-and-translate.yaml +7 -1
- package/agents/detail-regenerator.yaml +6 -57
- package/agents/docs-generator.yaml +5 -61
- package/agents/find-item-by-path.mjs +63 -14
- package/agents/input-generator.mjs +31 -21
- package/agents/language-selector.mjs +101 -0
- package/agents/load-config.mjs +3 -3
- package/agents/load-sources.mjs +55 -40
- package/agents/publish-docs.mjs +44 -153
- package/agents/retranslate.yaml +74 -0
- package/agents/save-docs.mjs +12 -2
- package/agents/save-output.mjs +9 -3
- package/agents/save-single-doc.mjs +19 -0
- package/agents/structure-planning.yaml +6 -0
- package/agents/team-publish-docs.yaml +7 -7
- package/agents/translate.yaml +3 -0
- package/aigne.yaml +5 -1
- package/docs-mcp/docs-search.yaml +1 -1
- package/docs-mcp/get-docs-detail.mjs +1 -1
- package/docs-mcp/get-docs-structure.mjs +1 -1
- package/docs-mcp/read-doc-content.mjs +1 -1
- package/package.json +16 -7
- package/prompts/check-structure-planning-result.md +4 -7
- package/prompts/content-detail-generator.md +1 -2
- package/prompts/structure-planning.md +7 -2
- package/prompts/translator.md +4 -0
- package/tests/test-all-validation-cases.mjs +707 -0
- package/utils/constants.mjs +3 -2
- package/utils/markdown-checker.mjs +386 -0
- package/utils/mermaid-validator.mjs +158 -0
- package/utils/mermaid-worker-pool.mjs +254 -0
- package/utils/mermaid-worker.mjs +242 -0
- package/utils/utils.mjs +155 -44
package/utils/utils.mjs
CHANGED
|
@@ -83,6 +83,7 @@ export async function saveDocWithTranslations({
|
|
|
83
83
|
locale,
|
|
84
84
|
translates = [],
|
|
85
85
|
labels,
|
|
86
|
+
isTranslate = false,
|
|
86
87
|
}) {
|
|
87
88
|
const results = [];
|
|
88
89
|
try {
|
|
@@ -96,20 +97,22 @@ export async function saveDocWithTranslations({
|
|
|
96
97
|
return isEnglish ? `${flatName}.md` : `${flatName}.${language}.md`;
|
|
97
98
|
};
|
|
98
99
|
|
|
99
|
-
// Save main content with appropriate filename based on locale
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
// Save main content with appropriate filename based on locale (skip if isTranslate is true)
|
|
101
|
+
if (!isTranslate) {
|
|
102
|
+
const mainFileName = getFileName(locale);
|
|
103
|
+
const mainFilePath = path.join(docsDir, mainFileName);
|
|
102
104
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
// Add labels front matter if labels are provided
|
|
106
|
+
let finalContent = processContent({ content });
|
|
107
|
+
if (labels && labels.length > 0) {
|
|
108
|
+
const frontMatter = `---\nlabels: ${JSON.stringify(labels)}\n---\n\n`;
|
|
109
|
+
finalContent = frontMatter + finalContent;
|
|
110
|
+
}
|
|
109
111
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
112
|
+
await fs.writeFile(mainFilePath, finalContent, "utf8");
|
|
113
|
+
results.push({ path: mainFilePath, success: true });
|
|
114
|
+
console.log(chalk.green(`Saved: ${chalk.cyan(mainFilePath)}`));
|
|
115
|
+
}
|
|
113
116
|
|
|
114
117
|
// Process all translations
|
|
115
118
|
for (const translate of translates) {
|
|
@@ -162,7 +165,7 @@ export async function saveGitHeadToConfig(gitHead) {
|
|
|
162
165
|
}
|
|
163
166
|
|
|
164
167
|
try {
|
|
165
|
-
const docSmithDir = path.join(process.cwd(), "doc-smith");
|
|
168
|
+
const docSmithDir = path.join(process.cwd(), "./.aigne/doc-smith");
|
|
166
169
|
if (!existsSync(docSmithDir)) {
|
|
167
170
|
mkdirSync(docSmithDir, { recursive: true });
|
|
168
171
|
}
|
|
@@ -345,7 +348,11 @@ export function hasFileChangesBetweenCommits(
|
|
|
345
348
|
* @returns {Promise<Object|null>} - The config object or null if file doesn't exist
|
|
346
349
|
*/
|
|
347
350
|
export async function loadConfigFromFile() {
|
|
348
|
-
const configPath = path.join(
|
|
351
|
+
const configPath = path.join(
|
|
352
|
+
process.cwd(),
|
|
353
|
+
"./.aigne/doc-smith",
|
|
354
|
+
"config.yaml"
|
|
355
|
+
);
|
|
349
356
|
|
|
350
357
|
try {
|
|
351
358
|
if (!existsSync(configPath)) {
|
|
@@ -364,14 +371,15 @@ export async function loadConfigFromFile() {
|
|
|
364
371
|
* Save value to config.yaml file
|
|
365
372
|
* @param {string} key - The config key to save
|
|
366
373
|
* @param {string} value - The value to save
|
|
374
|
+
* @param {string} [comment] - Optional comment to add above the key
|
|
367
375
|
*/
|
|
368
|
-
export async function saveValueToConfig(key, value) {
|
|
369
|
-
if (
|
|
370
|
-
return; // Skip if
|
|
376
|
+
export async function saveValueToConfig(key, value, comment) {
|
|
377
|
+
if (value === undefined) {
|
|
378
|
+
return; // Skip if value is undefined
|
|
371
379
|
}
|
|
372
380
|
|
|
373
381
|
try {
|
|
374
|
-
const docSmithDir = path.join(process.cwd(), "doc-smith");
|
|
382
|
+
const docSmithDir = path.join(process.cwd(), "./.aigne/doc-smith");
|
|
375
383
|
if (!existsSync(docSmithDir)) {
|
|
376
384
|
mkdirSync(docSmithDir, { recursive: true });
|
|
377
385
|
}
|
|
@@ -385,17 +393,38 @@ export async function saveValueToConfig(key, value) {
|
|
|
385
393
|
}
|
|
386
394
|
|
|
387
395
|
// Check if key already exists in the file
|
|
388
|
-
const
|
|
389
|
-
const
|
|
396
|
+
const lines = fileContent.split("\n");
|
|
397
|
+
const keyRegex = new RegExp(`^${key}:\\s*.*$`);
|
|
398
|
+
const newKeyLine = `${key}: "${value}"`;
|
|
399
|
+
|
|
400
|
+
const keyIndex = lines.findIndex((line) => keyRegex.test(line));
|
|
390
401
|
|
|
391
|
-
if (
|
|
402
|
+
if (keyIndex !== -1) {
|
|
392
403
|
// Replace existing key line
|
|
393
|
-
|
|
404
|
+
lines[keyIndex] = newKeyLine;
|
|
405
|
+
fileContent = lines.join("\n");
|
|
406
|
+
|
|
407
|
+
// Add comment if provided and not already present
|
|
408
|
+
if (
|
|
409
|
+
comment &&
|
|
410
|
+
keyIndex > 0 &&
|
|
411
|
+
!lines[keyIndex - 1].trim().startsWith("# ")
|
|
412
|
+
) {
|
|
413
|
+
// Add comment above the key if it doesn't already have one
|
|
414
|
+
lines.splice(keyIndex, 0, `# ${comment}`);
|
|
415
|
+
fileContent = lines.join("\n");
|
|
416
|
+
}
|
|
394
417
|
} else {
|
|
395
418
|
// Add key to the end of file
|
|
396
419
|
if (fileContent && !fileContent.endsWith("\n")) {
|
|
397
420
|
fileContent += "\n";
|
|
398
421
|
}
|
|
422
|
+
|
|
423
|
+
// Add comment if provided
|
|
424
|
+
if (comment) {
|
|
425
|
+
fileContent += `# ${comment}\n`;
|
|
426
|
+
}
|
|
427
|
+
|
|
399
428
|
fileContent += newKeyLine + "\n";
|
|
400
429
|
}
|
|
401
430
|
|
|
@@ -472,20 +501,20 @@ export function validatePaths(paths) {
|
|
|
472
501
|
}
|
|
473
502
|
|
|
474
503
|
/**
|
|
475
|
-
* Check if input is a valid directory and add it to results if so
|
|
504
|
+
* Check if input is a valid directory or file and add it to results if so
|
|
476
505
|
* @param {string} searchTerm - The search term to check
|
|
477
506
|
* @param {Array} results - The results array to modify
|
|
478
507
|
*/
|
|
479
|
-
function
|
|
508
|
+
function addExactPathMatch(searchTerm, results) {
|
|
480
509
|
const inputValidation = validatePath(searchTerm);
|
|
481
510
|
if (inputValidation.isValid) {
|
|
482
511
|
const stats = statSync(normalizePath(searchTerm));
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
}
|
|
512
|
+
const isDirectory = stats.isDirectory();
|
|
513
|
+
results.unshift({
|
|
514
|
+
name: searchTerm,
|
|
515
|
+
value: searchTerm,
|
|
516
|
+
description: isDirectory ? "📁 Directory" : "📄 File",
|
|
517
|
+
});
|
|
489
518
|
}
|
|
490
519
|
}
|
|
491
520
|
|
|
@@ -510,7 +539,7 @@ export function getAvailablePaths(userInput = "") {
|
|
|
510
539
|
const dirPath = path.dirname(searchTerm);
|
|
511
540
|
const fileName = path.basename(searchTerm);
|
|
512
541
|
results = getDirectoryContents(dirPath, fileName);
|
|
513
|
-
|
|
542
|
+
addExactPathMatch(searchTerm, results);
|
|
514
543
|
}
|
|
515
544
|
// Handle relative paths
|
|
516
545
|
else if (searchTerm.startsWith("./") || searchTerm.startsWith("../")) {
|
|
@@ -519,7 +548,7 @@ export function getAvailablePaths(userInput = "") {
|
|
|
519
548
|
if (lastSlashIndex === -1) {
|
|
520
549
|
// No slash found, treat as current directory search
|
|
521
550
|
results = getDirectoryContents("./", searchTerm);
|
|
522
|
-
|
|
551
|
+
addExactPathMatch(searchTerm, results);
|
|
523
552
|
} else {
|
|
524
553
|
const dirPath = searchTerm.substring(0, lastSlashIndex + 1);
|
|
525
554
|
const fileName = searchTerm.substring(lastSlashIndex + 1);
|
|
@@ -537,13 +566,13 @@ export function getAvailablePaths(userInput = "") {
|
|
|
537
566
|
}
|
|
538
567
|
|
|
539
568
|
results = getDirectoryContents(dirPath, fileName);
|
|
540
|
-
|
|
569
|
+
addExactPathMatch(searchTerm, results);
|
|
541
570
|
}
|
|
542
571
|
}
|
|
543
572
|
// Handle simple file/directory names (search in current directory)
|
|
544
573
|
else {
|
|
545
574
|
results = getDirectoryContents("./", searchTerm);
|
|
546
|
-
|
|
575
|
+
addExactPathMatch(searchTerm, results);
|
|
547
576
|
}
|
|
548
577
|
|
|
549
578
|
// Remove duplicates based on value (path)
|
|
@@ -571,7 +600,7 @@ export function getAvailablePaths(userInput = "") {
|
|
|
571
600
|
* Get directory contents for a specific path
|
|
572
601
|
* @param {string} dirPath - Directory path to search in
|
|
573
602
|
* @param {string} searchTerm - Optional search term to filter results
|
|
574
|
-
* @returns {Array<Object>} - Array of path objects
|
|
603
|
+
* @returns {Array<Object>} - Array of path objects (both files and directories)
|
|
575
604
|
*/
|
|
576
605
|
function getDirectoryContents(dirPath, searchTerm = "") {
|
|
577
606
|
try {
|
|
@@ -623,17 +652,24 @@ function getDirectoryContents(dirPath, searchTerm = "") {
|
|
|
623
652
|
|
|
624
653
|
const isDirectory = entry.isDirectory();
|
|
625
654
|
|
|
626
|
-
//
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
}
|
|
655
|
+
// Include both directories and files
|
|
656
|
+
items.push({
|
|
657
|
+
name: relativePath,
|
|
658
|
+
value: relativePath,
|
|
659
|
+
description: isDirectory ? "📁 Directory" : "📄 File",
|
|
660
|
+
});
|
|
633
661
|
}
|
|
634
662
|
|
|
635
|
-
// Sort alphabetically (
|
|
636
|
-
items.sort((a, b) =>
|
|
663
|
+
// Sort alphabetically (directories first, then files)
|
|
664
|
+
items.sort((a, b) => {
|
|
665
|
+
const aIsDir = a.description === "📁 Directory";
|
|
666
|
+
const bIsDir = b.description === "📁 Directory";
|
|
667
|
+
|
|
668
|
+
if (aIsDir && !bIsDir) return -1;
|
|
669
|
+
if (!aIsDir && bIsDir) return 1;
|
|
670
|
+
|
|
671
|
+
return a.name.localeCompare(b.name);
|
|
672
|
+
});
|
|
637
673
|
|
|
638
674
|
return items;
|
|
639
675
|
} catch (error) {
|
|
@@ -644,3 +680,78 @@ function getDirectoryContents(dirPath, searchTerm = "") {
|
|
|
644
680
|
return [];
|
|
645
681
|
}
|
|
646
682
|
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Get GitHub repository information
|
|
686
|
+
* @param {string} repoUrl - The repository URL
|
|
687
|
+
* @returns {Promise<Object>} - Repository information
|
|
688
|
+
*/
|
|
689
|
+
export async function getGitHubRepoInfo(repoUrl) {
|
|
690
|
+
try {
|
|
691
|
+
// Extract owner and repo from GitHub URL
|
|
692
|
+
const match = repoUrl.match(
|
|
693
|
+
/github\.com[\/:]([^\/]+)\/([^\/]+?)(?:\.git)?$/
|
|
694
|
+
);
|
|
695
|
+
if (!match) return null;
|
|
696
|
+
|
|
697
|
+
const [, owner, repo] = match;
|
|
698
|
+
const apiUrl = `https://api.github.com/repos/${owner}/${repo}`;
|
|
699
|
+
|
|
700
|
+
const response = await fetch(apiUrl);
|
|
701
|
+
if (!response.ok) return null;
|
|
702
|
+
|
|
703
|
+
const data = await response.json();
|
|
704
|
+
return {
|
|
705
|
+
name: data.name,
|
|
706
|
+
description: data.description || "",
|
|
707
|
+
icon: data.owner?.avatar_url || "",
|
|
708
|
+
};
|
|
709
|
+
} catch (error) {
|
|
710
|
+
console.warn("Failed to fetch GitHub repository info:", error.message);
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Get project information automatically without user confirmation
|
|
717
|
+
* @returns {Promise<Object>} - Project information including name, description, icon, and fromGitHub flag
|
|
718
|
+
*/
|
|
719
|
+
export async function getProjectInfo() {
|
|
720
|
+
let repoInfo = null;
|
|
721
|
+
let defaultName = path.basename(process.cwd());
|
|
722
|
+
let defaultDescription = "";
|
|
723
|
+
let defaultIcon = "";
|
|
724
|
+
let fromGitHub = false;
|
|
725
|
+
|
|
726
|
+
// Check if we're in a git repository
|
|
727
|
+
try {
|
|
728
|
+
const gitRemote = execSync("git remote get-url origin", {
|
|
729
|
+
encoding: "utf8",
|
|
730
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
731
|
+
}).trim();
|
|
732
|
+
|
|
733
|
+
// Extract repository name from git remote URL
|
|
734
|
+
const repoName = gitRemote.split("/").pop().replace(".git", "");
|
|
735
|
+
defaultName = repoName;
|
|
736
|
+
|
|
737
|
+
// If it's a GitHub repository, try to get additional info
|
|
738
|
+
if (gitRemote.includes("github.com")) {
|
|
739
|
+
repoInfo = await getGitHubRepoInfo(gitRemote);
|
|
740
|
+
if (repoInfo) {
|
|
741
|
+
defaultDescription = repoInfo.description;
|
|
742
|
+
defaultIcon = repoInfo.icon;
|
|
743
|
+
fromGitHub = true;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
} catch (error) {
|
|
747
|
+
// Not in git repository or no origin remote, use current directory name
|
|
748
|
+
console.warn("No git repository found, using current directory name");
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
return {
|
|
752
|
+
name: defaultName,
|
|
753
|
+
description: defaultDescription,
|
|
754
|
+
icon: defaultIcon,
|
|
755
|
+
fromGitHub,
|
|
756
|
+
};
|
|
757
|
+
}
|