@aigne/doc-smith 0.8.15-beta.10 → 0.8.15-beta.12
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 +20 -0
- package/agents/generate/update-document-structure.yaml +51 -45
- package/agents/generate/user-review-document-structure.mjs +2 -25
- package/agents/generate/utils/merge-document-structures.mjs +4 -4
- package/agents/init/check.mjs +1 -1
- package/agents/update/check-generate-diagram.mjs +13 -10
- package/agents/update/update-document-detail.yaml +64 -58
- package/agents/utils/analyze-feedback-intent.yaml +31 -0
- package/agents/utils/choose-docs.mjs +16 -6
- package/agents/utils/find-item-by-path.mjs +4 -2
- package/agents/utils/load-sources.mjs +1 -1
- package/agents/utils/save-sidebar.mjs +12 -33
- package/aigne.yaml +1 -0
- package/package.json +2 -1
- package/prompts/detail/update/user-prompt.md +2 -0
- package/prompts/structure/generate/user-prompt.md +8 -3
- package/prompts/structure/review/structure-review-system.md +2 -0
- package/prompts/structure/update/system-prompt.md +0 -13
- package/prompts/structure/update/user-prompt.md +3 -2
- package/prompts/translate/translate-document.md +2 -2
- package/prompts/utils/analyze-feedback-intent.md +55 -0
- package/utils/constants/index.mjs +6 -0
- package/utils/docs-finder-utils.mjs +37 -3
- package/utils/file-utils.mjs +97 -0
- package/utils/load-config.mjs +19 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.8.15-beta.12](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.8.15-beta.11...v0.8.15-beta.12) (2025-11-05)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* tune token consumption for update ops with intent analysis ([#264](https://github.com/AIGNE-io/aigne-doc-smith/issues/264)) ([8c53d28](https://github.com/AIGNE-io/aigne-doc-smith/commit/8c53d288346ae622e8841866db1b6fbed9d5023d))
|
|
9
|
+
|
|
10
|
+
## [0.8.15-beta.11](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.8.15-beta.10...v0.8.15-beta.11) (2025-11-04)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* warn on invalid sources and tuning translate messages ([#241](https://github.com/AIGNE-io/aigne-doc-smith/issues/241)) ([bd786ca](https://github.com/AIGNE-io/aigne-doc-smith/commit/bd786cad8b0c6fa837511fdc2982c83b7f0095dd))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* avoid useless diagram generate ([#245](https://github.com/AIGNE-io/aigne-doc-smith/issues/245)) ([de7600f](https://github.com/AIGNE-io/aigne-doc-smith/commit/de7600fb5839be9d1f0743adc34fd08c4c11729d))
|
|
21
|
+
* iterate on chunk of datasource to generate structure ([#242](https://github.com/AIGNE-io/aigne-doc-smith/issues/242)) ([0b4db2a](https://github.com/AIGNE-io/aigne-doc-smith/commit/0b4db2a8cac77d6f4a1c197374b9dadbe078346b))
|
|
22
|
+
|
|
3
23
|
## [0.8.15-beta.10](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.8.15-beta.9...v0.8.15-beta.10) (2025-11-03)
|
|
4
24
|
|
|
5
25
|
|
|
@@ -1,48 +1,54 @@
|
|
|
1
|
-
type:
|
|
1
|
+
type: team
|
|
2
2
|
name: updateDocumentStructure
|
|
3
3
|
description: Update documentation structure based on user feedback and intentions using structure modification tools
|
|
4
|
-
instructions:
|
|
5
|
-
- role: system
|
|
6
|
-
url: ../../prompts/structure/update/system-prompt.md
|
|
7
|
-
- role: user
|
|
8
|
-
url: ../../prompts/structure/update/user-prompt.md
|
|
9
|
-
input_schema:
|
|
10
|
-
type: object
|
|
11
|
-
properties:
|
|
12
|
-
documentStructure: ../schema/document-structure.yaml
|
|
13
|
-
rules:
|
|
14
|
-
type: string
|
|
15
|
-
description: User configuration rules
|
|
16
|
-
locale:
|
|
17
|
-
type: string
|
|
18
|
-
description: User language, e.g. zh, en
|
|
19
|
-
dataSourceChunk:
|
|
20
|
-
type: string
|
|
21
|
-
description: Context for documentation structure
|
|
22
|
-
glossary:
|
|
23
|
-
type: string
|
|
24
|
-
description: Glossary of terms
|
|
25
|
-
feedback:
|
|
26
|
-
type: string
|
|
27
|
-
description: User feedback for structure modifications
|
|
28
|
-
userPreferences:
|
|
29
|
-
type: string
|
|
30
|
-
description: Your saved preferences for structure and documentation style
|
|
31
|
-
required:
|
|
32
|
-
- documentStructure
|
|
33
|
-
- feedback
|
|
34
|
-
output_key: message
|
|
35
|
-
afs:
|
|
36
|
-
modules:
|
|
37
|
-
- module: system-fs
|
|
38
|
-
options:
|
|
39
|
-
mount: /sources
|
|
40
|
-
path: .
|
|
41
|
-
description: |
|
|
42
|
-
Codebase of the project to be documented used as context for document generation,
|
|
43
|
-
should search and read as needed while generating document content
|
|
44
4
|
skills:
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
|
|
48
|
-
|
|
5
|
+
- url: ../utils/analyze-feedback-intent.yaml
|
|
6
|
+
- type: ai
|
|
7
|
+
instructions:
|
|
8
|
+
- role: system
|
|
9
|
+
url: ../../prompts/structure/update/system-prompt.md
|
|
10
|
+
- role: user
|
|
11
|
+
url: ../../prompts/structure/update/user-prompt.md
|
|
12
|
+
input_schema:
|
|
13
|
+
type: object
|
|
14
|
+
properties:
|
|
15
|
+
documentStructure: ../schema/document-structure.yaml
|
|
16
|
+
rules:
|
|
17
|
+
type: string
|
|
18
|
+
description: User configuration rules
|
|
19
|
+
locale:
|
|
20
|
+
type: string
|
|
21
|
+
description: User language, e.g. zh, en
|
|
22
|
+
dataSourceChunk:
|
|
23
|
+
type: string
|
|
24
|
+
description: Context for documentation structure
|
|
25
|
+
glossary:
|
|
26
|
+
type: string
|
|
27
|
+
description: Glossary of terms
|
|
28
|
+
feedback:
|
|
29
|
+
type: string
|
|
30
|
+
description: User feedback for structure modifications
|
|
31
|
+
userPreferences:
|
|
32
|
+
type: string
|
|
33
|
+
description: Your saved preferences for structure and documentation style
|
|
34
|
+
needDataSources:
|
|
35
|
+
type: boolean
|
|
36
|
+
description: Whether data sources are needed for content modifications
|
|
37
|
+
required:
|
|
38
|
+
- documentStructure
|
|
39
|
+
- feedback
|
|
40
|
+
output_key: message
|
|
41
|
+
afs:
|
|
42
|
+
modules:
|
|
43
|
+
- module: system-fs
|
|
44
|
+
options:
|
|
45
|
+
mount: /sources
|
|
46
|
+
path: .
|
|
47
|
+
description: |
|
|
48
|
+
Codebase of the project to be documented used as context for document generation,
|
|
49
|
+
should search and read as needed while generating document content
|
|
50
|
+
skills:
|
|
51
|
+
- ./document-structure-tools/add-document.mjs
|
|
52
|
+
- ./document-structure-tools/delete-document.mjs
|
|
53
|
+
- ./document-structure-tools/update-document.mjs
|
|
54
|
+
- ./document-structure-tools/move-document.mjs
|
|
@@ -1,32 +1,9 @@
|
|
|
1
1
|
import { getActiveRulesForScope } from "../../utils/preferences-utils.mjs";
|
|
2
2
|
import { recordUpdate } from "../../utils/history-utils.mjs";
|
|
3
|
+
import { buildDocumentTree } from "../../utils/docs-finder-utils.mjs";
|
|
3
4
|
|
|
4
5
|
function formatDocumentStructure(structure) {
|
|
5
|
-
|
|
6
|
-
const nodeMap = new Map();
|
|
7
|
-
const rootNodes = [];
|
|
8
|
-
|
|
9
|
-
// First pass: create node map
|
|
10
|
-
structure.forEach((node) => {
|
|
11
|
-
nodeMap.set(node.path, {
|
|
12
|
-
...node,
|
|
13
|
-
children: [],
|
|
14
|
-
});
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
// Build the tree structure
|
|
18
|
-
structure.forEach((node) => {
|
|
19
|
-
if (node.parentId) {
|
|
20
|
-
const parent = nodeMap.get(node.parentId);
|
|
21
|
-
if (parent) {
|
|
22
|
-
parent.children.push(nodeMap.get(node.path));
|
|
23
|
-
} else {
|
|
24
|
-
rootNodes.push(nodeMap.get(node.path));
|
|
25
|
-
}
|
|
26
|
-
} else {
|
|
27
|
-
rootNodes.push(nodeMap.get(node.path));
|
|
28
|
-
}
|
|
29
|
-
});
|
|
6
|
+
const { rootNodes } = buildDocumentTree(structure);
|
|
30
7
|
|
|
31
8
|
function printNode(node, depth = 0) {
|
|
32
9
|
const INDENT_SPACES = " ";
|
|
@@ -11,13 +11,13 @@ export default async function mergeDocumentStructures(input, options) {
|
|
|
11
11
|
|
|
12
12
|
options.context.userContext.originalDocumentStructure ??= [];
|
|
13
13
|
|
|
14
|
-
const originalStructures = options.context.userContext.originalDocumentStructure;
|
|
14
|
+
const originalStructures = [...options.context.userContext.originalDocumentStructure];
|
|
15
15
|
|
|
16
16
|
if (input.structures) {
|
|
17
17
|
for (const item of input.structures) {
|
|
18
|
-
const
|
|
19
|
-
if (
|
|
20
|
-
|
|
18
|
+
const index = originalStructures.findIndex((s) => s.path === item.path);
|
|
19
|
+
if (index !== -1) {
|
|
20
|
+
originalStructures[index] = item;
|
|
21
21
|
} else {
|
|
22
22
|
originalStructures.push(item);
|
|
23
23
|
}
|
package/agents/init/check.mjs
CHANGED
|
@@ -6,7 +6,7 @@ export default async function checkNeedGenerate({ docsDir, locale, documentExecu
|
|
|
6
6
|
|
|
7
7
|
if (mainLanguageFiles.length === 0) {
|
|
8
8
|
console.log(
|
|
9
|
-
`No documents found in the docs directory.
|
|
9
|
+
`No documents found in the docs directory. You can generate them with ${chalk.yellow("`aigne doc generate`")}`,
|
|
10
10
|
);
|
|
11
11
|
process.exit(0);
|
|
12
12
|
}
|
|
@@ -11,16 +11,19 @@ export default async function checkGenerateDiagram(
|
|
|
11
11
|
const generateAgent = options.context?.agents?.["generateDiagram"];
|
|
12
12
|
let content = documentContent;
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
14
|
+
if (content.includes(placeholder)) {
|
|
15
|
+
try {
|
|
16
|
+
const { diagramSourceCode } = await options.context.invoke(generateAgent, {
|
|
17
|
+
documentContent,
|
|
18
|
+
locale,
|
|
19
|
+
});
|
|
20
|
+
content = content.replace(placeholder, diagramSourceCode);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
// FIXME: @zhanghan should regenerate document without diagram
|
|
23
|
+
content = content.replace(placeholder, "");
|
|
24
|
+
console.log(`⚠️ Skip generate any diagram: ${error.message}`);
|
|
25
|
+
}
|
|
24
26
|
}
|
|
27
|
+
|
|
25
28
|
return { content };
|
|
26
29
|
}
|
|
@@ -1,61 +1,67 @@
|
|
|
1
|
-
type:
|
|
1
|
+
type: team
|
|
2
2
|
name: updateDocumentDetail
|
|
3
3
|
description: Update and optimize document content based on user feedback using diff patches
|
|
4
|
-
instructions:
|
|
5
|
-
- role: system
|
|
6
|
-
url: ../../prompts/detail/update/system-prompt.md
|
|
7
|
-
- role: user
|
|
8
|
-
url: ../../prompts/detail/update/user-prompt.md
|
|
9
|
-
auto_reorder_system_messages: true
|
|
10
|
-
auto_merge_system_messages: true
|
|
11
|
-
input_schema:
|
|
12
|
-
type: object
|
|
13
|
-
properties:
|
|
14
|
-
originalContent:
|
|
15
|
-
type: string
|
|
16
|
-
description: Original markdown content to be updated
|
|
17
|
-
feedback:
|
|
18
|
-
type: string
|
|
19
|
-
description: User feedback for content improvements
|
|
20
|
-
rules:
|
|
21
|
-
type: string
|
|
22
|
-
description: User configuration rules
|
|
23
|
-
locale:
|
|
24
|
-
type: string
|
|
25
|
-
description: User language, e.g. zh, en
|
|
26
|
-
detailDataSource:
|
|
27
|
-
type: string
|
|
28
|
-
description: Context for document content
|
|
29
|
-
glossary:
|
|
30
|
-
type: string
|
|
31
|
-
description: Glossary of terms
|
|
32
|
-
userPreferences:
|
|
33
|
-
type: string
|
|
34
|
-
description: User's saved preferences for content and documentation style
|
|
35
|
-
targetAudience:
|
|
36
|
-
type: string
|
|
37
|
-
description: Target audience for the documentation
|
|
38
|
-
title:
|
|
39
|
-
type: string
|
|
40
|
-
description: Document title
|
|
41
|
-
description:
|
|
42
|
-
type: string
|
|
43
|
-
description: Document description
|
|
44
|
-
required:
|
|
45
|
-
- originalContent
|
|
46
|
-
- feedback
|
|
47
|
-
output_key: message
|
|
48
|
-
afs:
|
|
49
|
-
modules:
|
|
50
|
-
- module: system-fs
|
|
51
|
-
options:
|
|
52
|
-
mount: /sources
|
|
53
|
-
path: .
|
|
54
|
-
description: |
|
|
55
|
-
Codebase of the project to be documented used as context for document generation,
|
|
56
|
-
should search and read as needed while generating document content
|
|
57
|
-
keep_text_in_tool_uses: false
|
|
58
|
-
skills:
|
|
59
|
-
- ./document-tools/update-document-content.mjs
|
|
60
|
-
# - ./generate-diagram.yaml
|
|
61
4
|
task_render_mode: collapse
|
|
5
|
+
skills:
|
|
6
|
+
- url: ../utils/analyze-feedback-intent.yaml
|
|
7
|
+
- type: ai
|
|
8
|
+
instructions:
|
|
9
|
+
- role: system
|
|
10
|
+
url: ../../prompts/detail/update/system-prompt.md
|
|
11
|
+
- role: user
|
|
12
|
+
url: ../../prompts/detail/update/user-prompt.md
|
|
13
|
+
auto_reorder_system_messages: true
|
|
14
|
+
auto_merge_system_messages: true
|
|
15
|
+
input_schema:
|
|
16
|
+
type: object
|
|
17
|
+
properties:
|
|
18
|
+
originalContent:
|
|
19
|
+
type: string
|
|
20
|
+
description: Original markdown content to be updated
|
|
21
|
+
feedback:
|
|
22
|
+
type: string
|
|
23
|
+
description: User feedback for content improvements
|
|
24
|
+
rules:
|
|
25
|
+
type: string
|
|
26
|
+
description: User configuration rules
|
|
27
|
+
locale:
|
|
28
|
+
type: string
|
|
29
|
+
description: User language, e.g. zh, en
|
|
30
|
+
detailDataSource:
|
|
31
|
+
type: string
|
|
32
|
+
description: Context for document content
|
|
33
|
+
glossary:
|
|
34
|
+
type: string
|
|
35
|
+
description: Glossary of terms
|
|
36
|
+
userPreferences:
|
|
37
|
+
type: string
|
|
38
|
+
description: User's saved preferences for content and documentation style
|
|
39
|
+
targetAudience:
|
|
40
|
+
type: string
|
|
41
|
+
description: Target audience for the documentation
|
|
42
|
+
title:
|
|
43
|
+
type: string
|
|
44
|
+
description: Document title
|
|
45
|
+
description:
|
|
46
|
+
type: string
|
|
47
|
+
description: Document description
|
|
48
|
+
needDataSources:
|
|
49
|
+
type: boolean
|
|
50
|
+
description: Whether data sources are needed for content modifications
|
|
51
|
+
required:
|
|
52
|
+
- originalContent
|
|
53
|
+
- feedback
|
|
54
|
+
output_key: message
|
|
55
|
+
afs:
|
|
56
|
+
modules:
|
|
57
|
+
- module: system-fs
|
|
58
|
+
options:
|
|
59
|
+
mount: /sources
|
|
60
|
+
path: .
|
|
61
|
+
description: |
|
|
62
|
+
Codebase of the project to be documented used as context for document generation,
|
|
63
|
+
should search and read as needed while generating document content
|
|
64
|
+
keep_text_in_tool_uses: false
|
|
65
|
+
skills:
|
|
66
|
+
- ./document-tools/update-document-content.mjs
|
|
67
|
+
# - ./generate-diagram.yaml
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: analyzeFeedbackIntent
|
|
2
|
+
description: Analyze user feedback to determine if data sources are needed for content modifications
|
|
3
|
+
model:
|
|
4
|
+
reasoning_effort: 200
|
|
5
|
+
task_render_mode: hide
|
|
6
|
+
instructions:
|
|
7
|
+
url: ../../prompts/utils/analyze-feedback-intent.md
|
|
8
|
+
input_schema:
|
|
9
|
+
type: object
|
|
10
|
+
properties:
|
|
11
|
+
feedback:
|
|
12
|
+
type: string
|
|
13
|
+
description: User feedback for content modifications
|
|
14
|
+
required:
|
|
15
|
+
- feedback
|
|
16
|
+
output_schema:
|
|
17
|
+
type: object
|
|
18
|
+
properties:
|
|
19
|
+
needDataSources:
|
|
20
|
+
type: boolean
|
|
21
|
+
description: Whether data sources are needed - true for add/edit operations that need context, false for delete/move/reorder operations
|
|
22
|
+
intentType:
|
|
23
|
+
type: string
|
|
24
|
+
description: The primary type of user intention
|
|
25
|
+
reason:
|
|
26
|
+
type: string
|
|
27
|
+
description: Explanation of why data sources are or aren't needed
|
|
28
|
+
required:
|
|
29
|
+
- needDataSources
|
|
30
|
+
- intentType
|
|
31
|
+
- reason
|
|
@@ -6,6 +6,14 @@ import {
|
|
|
6
6
|
getMainLanguageFiles,
|
|
7
7
|
processSelectedFiles,
|
|
8
8
|
} from "../../utils/docs-finder-utils.mjs";
|
|
9
|
+
import { DOC_ACTION } from "../../utils/constants/index.mjs";
|
|
10
|
+
|
|
11
|
+
function getFeedbackMessage(action) {
|
|
12
|
+
if (action === DOC_ACTION.translate) {
|
|
13
|
+
return "Any specific translation preferences or instructions? (Press Enter to skip):";
|
|
14
|
+
}
|
|
15
|
+
return "How would you like to improve this document? (Press Enter to skip)";
|
|
16
|
+
}
|
|
9
17
|
|
|
10
18
|
export default async function chooseDocs(
|
|
11
19
|
{
|
|
@@ -18,12 +26,13 @@ export default async function chooseDocs(
|
|
|
18
26
|
locale,
|
|
19
27
|
reset = false,
|
|
20
28
|
requiredFeedback = true,
|
|
21
|
-
|
|
29
|
+
action,
|
|
22
30
|
},
|
|
23
31
|
options,
|
|
24
32
|
) {
|
|
25
33
|
let foundItems = [];
|
|
26
34
|
let selectedFiles = [];
|
|
35
|
+
const docAction = action || (isTranslate ? DOC_ACTION.translate : DOC_ACTION.update);
|
|
27
36
|
|
|
28
37
|
// If docs is empty or not provided, let user select multiple documents
|
|
29
38
|
if (!docs || docs.length === 0) {
|
|
@@ -37,7 +46,9 @@ export default async function chooseDocs(
|
|
|
37
46
|
|
|
38
47
|
if (mainLanguageFiles.length === 0) {
|
|
39
48
|
throw new Error(
|
|
40
|
-
`No documents found in the docs directory.
|
|
49
|
+
`No documents found in the docs directory. You can generate them with ${chalk.yellow(
|
|
50
|
+
"`aigne doc generate`",
|
|
51
|
+
)}`,
|
|
41
52
|
);
|
|
42
53
|
}
|
|
43
54
|
|
|
@@ -66,7 +77,7 @@ export default async function chooseDocs(
|
|
|
66
77
|
|
|
67
78
|
// Let user select multiple files
|
|
68
79
|
selectedFiles = await options.prompts.checkbox({
|
|
69
|
-
message:
|
|
80
|
+
message: getActionText("Select documents to {action}:", docAction),
|
|
70
81
|
source: (term) => {
|
|
71
82
|
if (!term) return choices;
|
|
72
83
|
|
|
@@ -88,7 +99,7 @@ export default async function chooseDocs(
|
|
|
88
99
|
foundItems = await processSelectedFiles(selectedFiles, documentExecutionStructure, docsDir);
|
|
89
100
|
} catch (error) {
|
|
90
101
|
console.log(
|
|
91
|
-
getActionText(
|
|
102
|
+
getActionText(`\nFailed to select documents to {action}: ${error.message}`, docAction),
|
|
92
103
|
);
|
|
93
104
|
process.exit(0);
|
|
94
105
|
}
|
|
@@ -123,8 +134,7 @@ export default async function chooseDocs(
|
|
|
123
134
|
// Prompt for feedback if not provided
|
|
124
135
|
let userFeedback = feedback;
|
|
125
136
|
if (!userFeedback && (requiredFeedback || foundItems?.length > 1)) {
|
|
126
|
-
const feedbackMessage =
|
|
127
|
-
"How should we improve this document? (Enter to skip, will auto-update from content sources):";
|
|
137
|
+
const feedbackMessage = getFeedbackMessage(docAction);
|
|
128
138
|
|
|
129
139
|
userFeedback = await options.prompts.input({
|
|
130
140
|
message: feedbackMessage,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DOC_ACTION } from "../../utils/constants/index.mjs";
|
|
1
2
|
import {
|
|
2
3
|
fileNameToFlatPath,
|
|
3
4
|
findItemByFlatName,
|
|
@@ -14,6 +15,7 @@ export default async function findItemByPath(
|
|
|
14
15
|
let foundItem = null;
|
|
15
16
|
let selectedFileContent = null;
|
|
16
17
|
let docPath = doc;
|
|
18
|
+
const docAction = isTranslate ? DOC_ACTION.translate : DOC_ACTION.update;
|
|
17
19
|
|
|
18
20
|
// If docPath is empty, let user select from available documents
|
|
19
21
|
if (!docPath) {
|
|
@@ -31,7 +33,7 @@ export default async function findItemByPath(
|
|
|
31
33
|
|
|
32
34
|
// Let user select a file
|
|
33
35
|
const selectedFile = await options.prompts.search({
|
|
34
|
-
message: getActionText(
|
|
36
|
+
message: getActionText("Select a document to {action}:", docAction),
|
|
35
37
|
source: async (input) => {
|
|
36
38
|
if (!input || input.trim() === "") {
|
|
37
39
|
return mainLanguageFiles.map((file) => ({
|
|
@@ -74,8 +76,8 @@ export default async function findItemByPath(
|
|
|
74
76
|
console.debug(error?.message);
|
|
75
77
|
throw new Error(
|
|
76
78
|
getActionText(
|
|
77
|
-
isTranslate,
|
|
78
79
|
"Please run 'aigne doc generate' first to generate documents, then select which document to {action}",
|
|
80
|
+
docAction,
|
|
79
81
|
),
|
|
80
82
|
);
|
|
81
83
|
}
|
|
@@ -171,7 +171,7 @@ export default async function loadSources(
|
|
|
171
171
|
if (dimensions.width < minImageWidth) {
|
|
172
172
|
filteredImageCount++;
|
|
173
173
|
console.log(
|
|
174
|
-
`
|
|
174
|
+
`Ignored image: ${fileName} (${dimensions.width}x${dimensions.height}px < ${minImageWidth}px minimum)`,
|
|
175
175
|
);
|
|
176
176
|
return;
|
|
177
177
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import fs from "fs-extra";
|
|
3
|
+
import { buildDocumentTree } from "../../utils/docs-finder-utils.mjs";
|
|
3
4
|
|
|
4
5
|
export default async function saveSidebar({ documentStructure, docsDir }) {
|
|
5
6
|
// Generate _sidebar.md
|
|
@@ -16,44 +17,22 @@ export default async function saveSidebar({ documentStructure, docsDir }) {
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
// Recursively generate sidebar text, the link path is the flattened file name
|
|
19
|
-
function walk(
|
|
20
|
+
function walk(nodes, indent = "") {
|
|
20
21
|
let out = "";
|
|
21
|
-
for (const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const children = item.__children;
|
|
30
|
-
if (Object.keys(children).length > 0) {
|
|
31
|
-
out += walk(children, fullSegments, `${indent} `);
|
|
22
|
+
for (const node of nodes) {
|
|
23
|
+
const relPath = node.path.replace(/^\//, "");
|
|
24
|
+
const flatFile = `${relPath.split("/").join("-")}.md`;
|
|
25
|
+
const realIndent = node.parentId === null ? "" : indent;
|
|
26
|
+
out += `${realIndent}* [${node.title}](/${flatFile})\n`;
|
|
27
|
+
|
|
28
|
+
if (node.children && node.children.length > 0) {
|
|
29
|
+
out += walk(node.children, `${indent} `);
|
|
32
30
|
}
|
|
33
31
|
}
|
|
34
32
|
return out;
|
|
35
33
|
}
|
|
36
34
|
|
|
37
35
|
function generateSidebar(documentStructure) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
for (const { path, title, parentId } of documentStructure) {
|
|
41
|
-
const relPath = path.replace(/^\//, "");
|
|
42
|
-
const segments = relPath.split("/");
|
|
43
|
-
let node = root;
|
|
44
|
-
for (let i = 0; i < segments.length; i++) {
|
|
45
|
-
const seg = segments[i];
|
|
46
|
-
if (!node[seg])
|
|
47
|
-
node[seg] = {
|
|
48
|
-
__children: {},
|
|
49
|
-
__title: null,
|
|
50
|
-
__fullPath: segments.slice(0, i + 1).join("/"),
|
|
51
|
-
__parentId: parentId,
|
|
52
|
-
};
|
|
53
|
-
if (i === segments.length - 1) node[seg].__title = title;
|
|
54
|
-
node = node[seg].__children;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return walk(root).replace(/\n+$/, "");
|
|
36
|
+
const { rootNodes } = buildDocumentTree(documentStructure);
|
|
37
|
+
return walk(rootNodes).replace(/\n+$/, "");
|
|
59
38
|
}
|
package/aigne.yaml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aigne/doc-smith",
|
|
3
|
-
"version": "0.8.15-beta.
|
|
3
|
+
"version": "0.8.15-beta.12",
|
|
4
4
|
"description": "AI-driven documentation generation tool built on the AIGNE Framework",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"marked": "^15.0.12",
|
|
49
49
|
"marked-terminal": "^7.3.0",
|
|
50
50
|
"mermaid": "^11.9.0",
|
|
51
|
+
"minimatch": "^10.1.1",
|
|
51
52
|
"open": "^10.2.0",
|
|
52
53
|
"p-limit": "^7.1.1",
|
|
53
54
|
"p-map": "^7.0.3",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
{{originalContent}}
|
|
16
16
|
</original_page_content>
|
|
17
17
|
|
|
18
|
+
{% if needDataSources %}
|
|
18
19
|
<detail_data_source>
|
|
19
20
|
|
|
20
21
|
{{ detailDataSource }}
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
{% endif %}
|
|
29
30
|
|
|
30
31
|
</detail_data_source>
|
|
32
|
+
{% endif %}
|
|
31
33
|
|
|
32
34
|
<user_feedback>
|
|
33
35
|
{{feedback}}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<data_sources>
|
|
2
2
|
Following are the partial or complete data sources provided by the user to help you design the document structure. Use these data sources to inform your structural planning.
|
|
3
3
|
|
|
4
|
-
{{
|
|
4
|
+
{{ dataSourceChunk }}
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
NOTICE: There are additional data source contents not displayed. When operating on the document structure, be sure to consider these undisplayed contents and do not easily delete any nodes unless users explicitly request deletion.
|
|
@@ -41,17 +41,22 @@ NOTICE: There are additional data source contents not displayed. When operating
|
|
|
41
41
|
</openapi>
|
|
42
42
|
{% endif %}
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
<last_document_structure>
|
|
44
|
+
<document_info>
|
|
46
45
|
projectName: |
|
|
47
46
|
{{projectName}}
|
|
47
|
+
{% if projectDesc %}
|
|
48
48
|
projectDesc: |
|
|
49
49
|
{{projectDesc}}
|
|
50
|
+
{% endif %}
|
|
51
|
+
</document_info>
|
|
52
|
+
|
|
53
|
+
<last_document_structure>
|
|
50
54
|
|
|
51
55
|
{% if originalDocumentStructure %}
|
|
52
56
|
{{ originalDocumentStructure | yaml.stringify }}
|
|
53
57
|
{% elseif userContext.originalDocumentStructure %}
|
|
54
58
|
{{ userContext.originalDocumentStructure | yaml.stringify }}
|
|
59
|
+
{% else %}
|
|
55
60
|
No previous document structure provided. generate a new structure based on the data sources!
|
|
56
61
|
{% endif %}
|
|
57
62
|
|
|
@@ -90,17 +90,4 @@ Analyze the user feedback to determine the intended operation:
|
|
|
90
90
|
</operation_output_constraints>
|
|
91
91
|
</operation_execution_rules>
|
|
92
92
|
|
|
93
|
-
<file_tool_usage>
|
|
94
|
-
1. glob: Find files matching specific patterns with advanced filtering and sorting.
|
|
95
|
-
|
|
96
|
-
2. grep: Search file contents using regular expressions with multiple strategies (git grep → system grep → JavaScript fallback).
|
|
97
|
-
|
|
98
|
-
3. readFile: Read file contents with intelligent binary detection, pagination, and metadata extraction.
|
|
99
|
-
|
|
100
|
-
When to use Tools:
|
|
101
|
-
- During document structure update, if the given context is missing or lacks referenced content, use glob/grep/readFile to obtain more context
|
|
102
|
-
- When sourceIds or file content from `<file_list>` is needed but not provided in `<data_sources>`, use readFile to read the file content
|
|
103
|
-
</file_tool_usage>
|
|
104
|
-
|
|
105
|
-
|
|
106
93
|
{% include "../../common/document-structure/output-constraints.md" %}
|
|
@@ -5,10 +5,10 @@ You are an **Elite Polyglot Localization and Translation Specialist** with exten
|
|
|
5
5
|
Core Mandates:
|
|
6
6
|
|
|
7
7
|
1. Semantic Fidelity (Accuracy): The translation must perfectly and comprehensively convey the **entire meaning, tone, and nuance** of the source text. **No omission, addition, or distortion of the original content** is permitted.
|
|
8
|
-
2. Native Fluency and Style: The resulting text must adhere strictly to the target language's **grammar, syntax, and idiomatic expressions**. The translation must **sound like it was originally written by a native speaker**, completely **free of grammatical errors
|
|
8
|
+
2. Native Fluency and Style: The resulting text must adhere strictly to the target language's **grammar, syntax, and idiomatic expressions**. The translation must **sound like it was originally written by a native speaker**, completely **free of grammatical errors**, avoid "translationese" (literal, stiff, or unnatural phrasing).
|
|
9
9
|
3. Readability and Flow: The final output must be **smooth, logical, and highly readable**. Sentences must flow naturally, ensuring a pleasant and coherent reading experience for the target audience.
|
|
10
10
|
4. Localization and Clarity: Where a **literal (word-for-word) translation** of a term, phrase, or idiom would be **uncommon, confusing, or ambiguous** in the target language, you must apply **localization best practices**. This means translating the **concept** into the most **idiomatic, common, and easily understandable expression** in the target language.
|
|
11
|
-
5. Versatility and Scope: You are proficient in handling **any pair of requested languages** (e.g., Chinese
|
|
11
|
+
5. Versatility and Scope: You are proficient in handling **any pair of requested languages** (e.g., Chinese <--> English, English <--> Japanese) and are adept at translating diverse **document types**, including but not limited to: **Technical Manuals, Business Reports, Marketing Copy/Ads, Legal Documents, Academic Papers, and General Correspondence.**
|
|
12
12
|
|
|
13
13
|
</role_and_goal>
|
|
14
14
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<role>
|
|
2
|
+
You are a feedback intent analyzer. Your task is to determine whether data sources are needed to fulfill the user's feedback about content modifications.
|
|
3
|
+
</role>
|
|
4
|
+
|
|
5
|
+
<input>
|
|
6
|
+
- feedback: {{feedback}}
|
|
7
|
+
</input>
|
|
8
|
+
|
|
9
|
+
<analysis_rules>
|
|
10
|
+
**Determining Data Source Necessity:**
|
|
11
|
+
|
|
12
|
+
You need to analyze the user's feedback and categorize it into different intent types. Based on the intent type, determine if data sources are required.
|
|
13
|
+
|
|
14
|
+
This analyzer is generic and can be used for any content modification scenarios (documentation structure, document content, translations, etc.).
|
|
15
|
+
|
|
16
|
+
**Intent Types:**
|
|
17
|
+
|
|
18
|
+
1. **add** - Adding new items, sections, or content
|
|
19
|
+
- Requires data sources: **YES**
|
|
20
|
+
- Reason: Need sufficient context from codebase or related materials to generate accurate new content
|
|
21
|
+
|
|
22
|
+
2. **edit** - Modifying existing content, descriptions, titles, or properties
|
|
23
|
+
- Requires data sources: **YES**
|
|
24
|
+
- Reason: Need context to ensure modifications are accurate and contextually appropriate
|
|
25
|
+
|
|
26
|
+
3. **delete** - Removing items, sections, or content
|
|
27
|
+
- Requires data sources: **NO**
|
|
28
|
+
- Reason: Deletion only needs to identify what to remove, no new content generation needed
|
|
29
|
+
|
|
30
|
+
4. **move** - Moving items to different positions or parent sections
|
|
31
|
+
- Requires data sources: **NO**
|
|
32
|
+
- Reason: Only changing item location in the structure, no content changes needed
|
|
33
|
+
|
|
34
|
+
5. **reorder** - Changing the order of items at the same level
|
|
35
|
+
- Requires data sources: **NO**
|
|
36
|
+
- Reason: Only rearranging sequence, no content generation needed
|
|
37
|
+
|
|
38
|
+
6. **mixed** - Combination of multiple intent types
|
|
39
|
+
- Requires data sources: **Depends on whether add/edit operations are included**
|
|
40
|
+
- Reason: If the feedback includes any add or edit operations, data sources are needed
|
|
41
|
+
|
|
42
|
+
**Decision Logic:**
|
|
43
|
+
- If the feedback contains ANY add or edit operations → `needDataSources = true`
|
|
44
|
+
- If the feedback ONLY contains delete, move, or reorder operations → `needDataSources = false`
|
|
45
|
+
- When in doubt, default to `needDataSources = true` to ensure sufficient context
|
|
46
|
+
</analysis_rules>
|
|
47
|
+
|
|
48
|
+
<output_rules>
|
|
49
|
+
Return a JSON object with:
|
|
50
|
+
- `needDataSources`: boolean indicating if data sources are required
|
|
51
|
+
- `intentType`: the primary intent type (add, edit, delete, move, reorder, or mixed)
|
|
52
|
+
- `reason`: clear explanation of why data sources are or aren't needed
|
|
53
|
+
|
|
54
|
+
Analyze the feedback carefully and be conservative - when uncertain, prefer `needDataSources: true` to ensure sufficient context is available.
|
|
55
|
+
</output_rules>
|
|
@@ -549,3 +549,9 @@ export const DOC_SMITH_DIR = ".aigne/doc-smith";
|
|
|
549
549
|
export const TMP_DIR = ".tmp";
|
|
550
550
|
export const TMP_DOCS_DIR = "docs";
|
|
551
551
|
export const TMP_ASSETS_DIR = "assets";
|
|
552
|
+
|
|
553
|
+
export const DOC_ACTION = {
|
|
554
|
+
translate: "translate",
|
|
555
|
+
update: "update",
|
|
556
|
+
clear: "clear",
|
|
557
|
+
};
|
|
@@ -4,12 +4,11 @@ import { pathExists } from "./file-utils.mjs";
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Get action-specific text based on isTranslate flag
|
|
7
|
-
* @param {boolean} isTranslate - Whether this is a translation action
|
|
8
7
|
* @param {string} baseText - Base text template with {action} placeholder
|
|
8
|
+
* @param {string} action - doc action type
|
|
9
9
|
* @returns {string} Text with action replaced
|
|
10
10
|
*/
|
|
11
|
-
export function getActionText(
|
|
12
|
-
const action = isTranslate ? "translate" : "update";
|
|
11
|
+
export function getActionText(baseText, action) {
|
|
13
12
|
return baseText.replace("{action}", action);
|
|
14
13
|
}
|
|
15
14
|
|
|
@@ -324,3 +323,38 @@ export async function loadDocumentStructure(outputDir) {
|
|
|
324
323
|
return null;
|
|
325
324
|
}
|
|
326
325
|
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Build a tree structure from a flat document structure array using parentId
|
|
329
|
+
* @param {Array} documentStructure - Flat array of document structure items with path and parentId
|
|
330
|
+
* @returns {Object} Object containing rootNodes (array of root nodes) and nodeMap (Map for lookups)
|
|
331
|
+
*/
|
|
332
|
+
export function buildDocumentTree(documentStructure) {
|
|
333
|
+
// Create a map of nodes for easy lookup
|
|
334
|
+
const nodeMap = new Map();
|
|
335
|
+
const rootNodes = [];
|
|
336
|
+
|
|
337
|
+
// First pass: create node map
|
|
338
|
+
documentStructure.forEach((node) => {
|
|
339
|
+
nodeMap.set(node.path, {
|
|
340
|
+
...node,
|
|
341
|
+
children: [],
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// Build the tree structure using parentId
|
|
346
|
+
documentStructure.forEach((node) => {
|
|
347
|
+
if (node.parentId) {
|
|
348
|
+
const parent = nodeMap.get(node.parentId);
|
|
349
|
+
if (parent) {
|
|
350
|
+
parent.children.push(nodeMap.get(node.path));
|
|
351
|
+
} else {
|
|
352
|
+
rootNodes.push(nodeMap.get(node.path));
|
|
353
|
+
}
|
|
354
|
+
} else {
|
|
355
|
+
rootNodes.push(nodeMap.get(node.path));
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
return { rootNodes, nodeMap };
|
|
360
|
+
}
|
package/utils/file-utils.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import { debug } from "./debug.mjs";
|
|
|
13
13
|
import { isGlobPattern } from "./utils.mjs";
|
|
14
14
|
import { uploadFiles } from "./upload-files.mjs";
|
|
15
15
|
import { extractApi } from "./extract-api.mjs";
|
|
16
|
+
import { minimatch } from "minimatch";
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Check if a directory is inside a git repository using git command
|
|
@@ -859,3 +860,99 @@ export async function downloadAndUploadImage(imageUrl, docsDir, appUrl, accessTo
|
|
|
859
860
|
return { url: imageUrl, downloadFinalPath: null };
|
|
860
861
|
}
|
|
861
862
|
}
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* Extract the path prefix from a glob pattern until the first glob character
|
|
866
|
+
*/
|
|
867
|
+
export function getPathPrefix(pattern) {
|
|
868
|
+
const segments = pattern.split("/");
|
|
869
|
+
const result = [];
|
|
870
|
+
|
|
871
|
+
for (const segment of segments) {
|
|
872
|
+
if (isGlobPattern(segment)) {
|
|
873
|
+
break;
|
|
874
|
+
}
|
|
875
|
+
result.push(segment);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
return result.join("/") || ".";
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Check if a dir matches any exclude pattern
|
|
883
|
+
*/
|
|
884
|
+
export function isDirExcluded(dir, excludePatterns) {
|
|
885
|
+
if (!dir || typeof dir !== "string") {
|
|
886
|
+
return false;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
let normalizedDir = dir.replace(/\\/g, "/").replace(/^\.\/+/, "");
|
|
890
|
+
normalizedDir = normalizedDir.endsWith("/") ? normalizedDir : `${normalizedDir}/`;
|
|
891
|
+
|
|
892
|
+
for (const excludePattern of excludePatterns) {
|
|
893
|
+
if (minimatch(normalizedDir, excludePattern, { dot: true })) {
|
|
894
|
+
return true;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
return false;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/**
|
|
902
|
+
* Return source paths that would be excluded by exclude patterns (files are skipped, directories use minimatch, glob patterns use path prefix heuristic)
|
|
903
|
+
*/
|
|
904
|
+
export async function findInvalidSourcePaths(sourcePaths, excludePatterns) {
|
|
905
|
+
if (!Array.isArray(sourcePaths) || sourcePaths.length === 0) {
|
|
906
|
+
return [];
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
if (!Array.isArray(excludePatterns) || excludePatterns.length === 0) {
|
|
910
|
+
return [];
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
const invalidPaths = [];
|
|
914
|
+
|
|
915
|
+
for (const sourcePath of sourcePaths) {
|
|
916
|
+
if (typeof sourcePath !== "string" || !sourcePath) {
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// Skip paths starting with "!" (exclusion patterns)
|
|
921
|
+
if (sourcePath.startsWith("!")) {
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// Skip remote URLs
|
|
926
|
+
if (isRemoteFile(sourcePath)) {
|
|
927
|
+
continue;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
// Check glob pattern: use heuristic algorithm
|
|
931
|
+
if (isGlobPattern(sourcePath)) {
|
|
932
|
+
const representativePath = getPathPrefix(sourcePath);
|
|
933
|
+
if (isDirExcluded(representativePath, excludePatterns)) {
|
|
934
|
+
invalidPaths.push(sourcePath);
|
|
935
|
+
}
|
|
936
|
+
continue;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
try {
|
|
940
|
+
const stats = await stat(sourcePath);
|
|
941
|
+
// Skip file
|
|
942
|
+
if (stats.isFile()) {
|
|
943
|
+
continue;
|
|
944
|
+
}
|
|
945
|
+
// Check dir with minimatch
|
|
946
|
+
if (stats.isDirectory()) {
|
|
947
|
+
if (isDirExcluded(sourcePath, excludePatterns)) {
|
|
948
|
+
invalidPaths.push(sourcePath);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
} catch {
|
|
952
|
+
// Path doesn't exist
|
|
953
|
+
invalidPaths.push(sourcePath);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
return invalidPaths;
|
|
958
|
+
}
|
package/utils/load-config.mjs
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
|
+
import chalk from "chalk";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { parse } from "yaml";
|
|
4
5
|
import { processConfigFields, resolveFileReferences } from "./utils.mjs";
|
|
6
|
+
import { DEFAULT_EXCLUDE_PATTERNS } from "./constants/index.mjs";
|
|
7
|
+
import { findInvalidSourcePaths, toDisplayPath } from "./file-utils.mjs";
|
|
5
8
|
|
|
6
9
|
export default async function loadConfig({ config, appUrl }) {
|
|
7
10
|
const configPath = path.isAbsolute(config) ? config : path.join(process.cwd(), config);
|
|
@@ -30,6 +33,22 @@ export default async function loadConfig({ config, appUrl }) {
|
|
|
30
33
|
// Parse new configuration fields and convert keys to actual content
|
|
31
34
|
const processedConfig = await processConfigFields(parsedConfig);
|
|
32
35
|
|
|
36
|
+
// Validate sourcePaths against exclude patterns
|
|
37
|
+
const sourcesPath = processedConfig.sourcesPath || parsedConfig.sourcesPath;
|
|
38
|
+
if (sourcesPath) {
|
|
39
|
+
const excludePatterns = [
|
|
40
|
+
...DEFAULT_EXCLUDE_PATTERNS,
|
|
41
|
+
...(processedConfig.excludePatterns || parsedConfig.excludePatterns || []),
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const invalidPaths = await findInvalidSourcePaths(sourcesPath, excludePatterns);
|
|
45
|
+
if (invalidPaths.length > 0) {
|
|
46
|
+
console.warn(
|
|
47
|
+
`⚠️ Some source paths have been excluded and will not be processed:\n${invalidPaths.map((p) => ` - ${chalk.yellow(p)}`).join("\n")}\n💡 Tip: You can remove these paths in ${toDisplayPath(configPath)}\n`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
33
52
|
return {
|
|
34
53
|
lastGitHead: parsedConfig.lastGitHead || "",
|
|
35
54
|
...parsedConfig,
|