@aigne/doc-smith 0.9.6-beta → 0.9.6-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 (41) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/agents/create/document-structure-tools/delete-document.mjs +53 -9
  3. package/agents/create/update-document-structure.yaml +1 -1
  4. package/agents/create/user-add-document/add-documents-to-structure.mjs +96 -0
  5. package/agents/create/user-add-document/find-documents-to-add-links.yaml +47 -0
  6. package/agents/create/user-add-document/index.yaml +46 -0
  7. package/agents/create/user-add-document/prepare-documents-to-translate.mjs +22 -0
  8. package/agents/create/user-add-document/print-add-document-summary.mjs +63 -0
  9. package/agents/create/user-add-document/review-documents-with-new-links.mjs +110 -0
  10. package/agents/create/user-remove-document/find-documents-with-invalid-links.mjs +78 -0
  11. package/agents/create/user-remove-document/index.yaml +41 -0
  12. package/agents/create/user-remove-document/prepare-documents-to-translate.mjs +22 -0
  13. package/agents/create/user-remove-document/print-remove-document-summary.mjs +53 -0
  14. package/agents/create/user-remove-document/remove-documents-from-structure.mjs +99 -0
  15. package/agents/create/user-remove-document/review-documents-with-invalid-links.mjs +119 -0
  16. package/agents/create/user-review-document-structure.mjs +1 -40
  17. package/agents/create/utils/init-current-content.mjs +38 -0
  18. package/agents/init/index.mjs +3 -4
  19. package/agents/update/document-tools/update-document-content.mjs +12 -12
  20. package/agents/update/update-document-detail.yaml +5 -1
  21. package/agents/update/update-single/update-single-document-detail.mjs +21 -6
  22. package/agents/update/user-review-document.mjs +10 -13
  23. package/agents/utils/add-translates-to-structure.mjs +29 -0
  24. package/agents/utils/{analyze-feedback-intent.yaml → analyze-document-feedback-intent.yaml} +5 -2
  25. package/agents/utils/analyze-structure-feedback-intent.yaml +29 -0
  26. package/agents/utils/check-detail-result.mjs +2 -14
  27. package/agents/utils/load-sources.mjs +36 -46
  28. package/aigne.yaml +10 -1
  29. package/package.json +1 -1
  30. package/prompts/detail/custom/custom-components/x-cards-usage-rules.md +18 -10
  31. package/prompts/structure/find-documents-to-add-links.md +52 -0
  32. package/prompts/utils/analyze-document-feedback-intent.md +54 -0
  33. package/prompts/utils/analyze-structure-feedback-intent.md +43 -0
  34. package/types/document-schema.mjs +2 -0
  35. package/types/document-structure-schema.mjs +6 -2
  36. package/utils/docs-finder-utils.mjs +161 -0
  37. package/utils/file-utils.mjs +9 -7
  38. package/utils/load-config.mjs +21 -4
  39. package/utils/markdown-checker.mjs +50 -5
  40. package/utils/utils.mjs +103 -0
  41. package/prompts/utils/analyze-feedback-intent.md +0 -55
@@ -900,17 +900,19 @@ export function isDirExcluded(dir, excludePatterns) {
900
900
 
901
901
  /**
902
902
  * Return source paths that would be excluded by exclude patterns (files are skipped, directories use minimatch, glob patterns use path prefix heuristic)
903
+ * @returns {{excluded: string[], notFound: string[]}} Object with excluded and notFound arrays
903
904
  */
904
905
  export async function findInvalidSourcePaths(sourcePaths, excludePatterns) {
905
906
  if (!Array.isArray(sourcePaths) || sourcePaths.length === 0) {
906
- return [];
907
+ return { excluded: [], notFound: [] };
907
908
  }
908
909
 
909
910
  if (!Array.isArray(excludePatterns) || excludePatterns.length === 0) {
910
- return [];
911
+ return { excluded: [], notFound: [] };
911
912
  }
912
913
 
913
- const invalidPaths = [];
914
+ const excluded = [];
915
+ const notFound = [];
914
916
 
915
917
  for (const sourcePath of sourcePaths) {
916
918
  if (typeof sourcePath !== "string" || !sourcePath) {
@@ -931,7 +933,7 @@ export async function findInvalidSourcePaths(sourcePaths, excludePatterns) {
931
933
  if (isGlobPattern(sourcePath)) {
932
934
  const representativePath = getPathPrefix(sourcePath);
933
935
  if (isDirExcluded(representativePath, excludePatterns)) {
934
- invalidPaths.push(sourcePath);
936
+ excluded.push(sourcePath);
935
937
  }
936
938
  continue;
937
939
  }
@@ -945,14 +947,14 @@ export async function findInvalidSourcePaths(sourcePaths, excludePatterns) {
945
947
  // Check dir with minimatch
946
948
  if (stats.isDirectory()) {
947
949
  if (isDirExcluded(sourcePath, excludePatterns)) {
948
- invalidPaths.push(sourcePath);
950
+ excluded.push(sourcePath);
949
951
  }
950
952
  }
951
953
  } catch {
952
954
  // Path doesn't exist
953
- invalidPaths.push(sourcePath);
955
+ notFound.push(sourcePath);
954
956
  }
955
957
  }
956
958
 
957
- return invalidPaths;
959
+ return { excluded, notFound };
958
960
  }
@@ -41,11 +41,28 @@ export default async function loadConfig({ config, appUrl }) {
41
41
  ...(processedConfig.excludePatterns || parsedConfig.excludePatterns || []),
42
42
  ];
43
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`,
44
+ const { excluded, notFound } = await findInvalidSourcePaths(sourcesPath, excludePatterns);
45
+
46
+ if (excluded.length > 0 || notFound.length > 0) {
47
+ const warnings = [];
48
+
49
+ if (excluded.length > 0) {
50
+ warnings.push(
51
+ `⚠️ These paths were excluded (ignored by config):\n${excluded.map((p) => ` - ${chalk.yellow(p)}`).join("\n")}`,
52
+ );
53
+ }
54
+
55
+ if (notFound.length > 0) {
56
+ warnings.push(
57
+ `🚫 These paths were skipped because they do not exist:\n${notFound.map((p) => ` - ${chalk.red(p)}`).join("\n")}`,
58
+ );
59
+ }
60
+
61
+ warnings.push(
62
+ `💡 Tip: You can remove these paths in ${chalk.cyan(toDisplayPath(configPath))}`,
48
63
  );
64
+
65
+ console.warn(`${warnings.join("\n\n")}\n`);
49
66
  }
50
67
  }
51
68
 
@@ -59,6 +59,9 @@ function countTableColumns(line) {
59
59
  return columns.length;
60
60
  }
61
61
 
62
+ const linkPattern = /(?<!!)\[([^\]]+)\]\(([^)]+)\)/g;
63
+ const hrefPattern = /<x-card[^>]*\s+data-href\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s>]+))/gi;
64
+
62
65
  /**
63
66
  * Check for dead links in markdown content
64
67
  * @param {string} markdown - The markdown content
@@ -67,16 +70,33 @@ function countTableColumns(line) {
67
70
  * @param {Array} errorMessages - Array to push error messages to
68
71
  */
69
72
  function checkDeadLinks(markdown, source, allowedLinks, errorMessages) {
70
- const linkRegex = /(?<!!)\[([^\]]+)\]\(([^)]+)\)/g;
71
- let match;
73
+ const links = [];
72
74
 
75
+ // Collect Markdown format links: [text](link)
76
+ const linkRegex = new RegExp(linkPattern.source, linkPattern.flags);
77
+ let match;
73
78
  while (true) {
74
79
  match = linkRegex.exec(markdown);
75
80
  if (match === null) break;
81
+ links.push({ trimLink: match[2].trim(), display: `[${match[1]}](${match[2].trim()})` });
82
+ }
76
83
 
77
- const link = match[2];
78
- const trimLink = link.trim();
84
+ // Collect data-href attribute values from <x-card> elements
85
+ const hrefRegex = new RegExp(hrefPattern.source, hrefPattern.flags);
86
+ let attrMatch;
87
+ while (true) {
88
+ attrMatch = hrefRegex.exec(markdown);
89
+ if (attrMatch === null) break;
90
+ const attrValue = (attrMatch[1] || attrMatch[2] || attrMatch[3] || "").trim();
91
+ if (attrValue) {
92
+ // Preserve original format with quotes if present
93
+ const originalMatch = attrMatch[0];
94
+ links.push({ trimLink: attrValue, display: originalMatch });
95
+ }
96
+ }
79
97
 
98
+ // Process all collected links with the same logic
99
+ for (const { trimLink, display } of links) {
80
100
  // Only check links that processContent would process
81
101
  // Exclude external links and mailto
82
102
  if (/^(https?:\/\/|mailto:)/.test(trimLink)) continue;
@@ -90,12 +110,37 @@ function checkDeadLinks(markdown, source, allowedLinks, errorMessages) {
90
110
  // Check if this link is in the allowed links set
91
111
  if (!allowedLinks.has(path)) {
92
112
  errorMessages.push(
93
- `Found a dead link in ${source}: [${match[1]}](${trimLink}), ensure the link exists in the documentation structure path`,
113
+ `Found a dead link in ${source}: ${display}, ensure the link exists in the documentation structure path`,
94
114
  );
95
115
  }
96
116
  }
97
117
  }
98
118
 
119
+ /**
120
+ * Extract link from error message
121
+ * @param {string} error - The error message
122
+ * @returns {string} - The link
123
+ */
124
+ export function getLinkFromError(error) {
125
+ if (!error || !error.includes("Found a dead link in")) {
126
+ return "";
127
+ }
128
+
129
+ const linkRegex = new RegExp(linkPattern.source, linkPattern.flags);
130
+ let match = linkRegex.exec(error);
131
+ if (match) {
132
+ return match[2].trim();
133
+ }
134
+
135
+ const hrefRegex = new RegExp(hrefPattern.source, hrefPattern.flags);
136
+ match = hrefRegex.exec(error);
137
+ if (match) {
138
+ return (match[1] || match[2] || match[3] || "").trim();
139
+ }
140
+
141
+ return "";
142
+ }
143
+
99
144
  /**
100
145
  * Check code block content for indentation consistency issues
101
146
  * @param {Array} codeBlockContent - Array of {line, lineNumber} objects from the code block
package/utils/utils.mjs CHANGED
@@ -1225,3 +1225,106 @@ export function getContentHash(str, { trim = true } = {}) {
1225
1225
  const input = trim && typeof str === "string" ? str.trim() : str;
1226
1226
  return crypto.createHash("sha256").update(input).digest("hex");
1227
1227
  }
1228
+
1229
+ function toPath(path) {
1230
+ if (Array.isArray(path)) return path;
1231
+
1232
+ const result = [];
1233
+ path.replace(/[^.[\]]+|\[(\d+|(["'])(.*?)\2)\]/g, (match, bracketContent, quote, quotedKey) => {
1234
+ if (quote) {
1235
+ // ["key"] or ['key']
1236
+ result.push(quotedKey);
1237
+ } else if (bracketContent !== undefined) {
1238
+ // [123]
1239
+ result.push(bracketContent);
1240
+ } else {
1241
+ // dot notation
1242
+ result.push(match);
1243
+ }
1244
+ });
1245
+ return result;
1246
+ }
1247
+
1248
+ /**
1249
+ * Deeply get the value at a given path from an object, or return a default value if missing.
1250
+ * @param {object} obj - The object to query.
1251
+ * @param {string|Array<string|number>} path - The path to get, as a string or array.
1252
+ * @param {*} defaultValue - The value returned if the resolved value is undefined.
1253
+ * @returns {*} The value at the path or defaultValue.
1254
+ */
1255
+ export function dget(obj, path, defaultValue) {
1256
+ const parts = toPath(path);
1257
+
1258
+ let current = obj;
1259
+ for (const key of parts) {
1260
+ if (current == null || !(key in current)) return defaultValue;
1261
+ current = current[key];
1262
+ }
1263
+ return current;
1264
+ }
1265
+
1266
+ /**
1267
+ * Deeply set the value at a given path in an object.
1268
+ * @param {object} obj - The object to modify.
1269
+ * @param {string|Array<string|number>} path - The path to set, as a string or array.
1270
+ * @param {*} value - The value to set.
1271
+ * @returns {object} The modified object.
1272
+ */
1273
+ export function dset(obj, path, value) {
1274
+ const parts = toPath(path);
1275
+
1276
+ let current = obj;
1277
+ for (let i = 0; i < parts.length; i++) {
1278
+ const key = parts[i];
1279
+
1280
+ if (i === parts.length - 1) {
1281
+ current[key] = value;
1282
+ } else {
1283
+ if (current[key] == null || typeof current[key] !== "object") {
1284
+ current[key] = String(parts[i + 1]).match(/^\d+$/) ? [] : {};
1285
+ }
1286
+ current = current[key];
1287
+ }
1288
+ }
1289
+ return obj;
1290
+ }
1291
+
1292
+ /**
1293
+ * Create a context path manager that provides get/set/clear operations
1294
+ * @param {object} options - The options object containing user context
1295
+ * @param {string} path - The context path (e.g., 'currentPageDetails./about' or 'lastToolInputs./about')
1296
+ * @returns {object} An object with { get, set } methods and a contextPath method for sub-paths
1297
+ */
1298
+ export function userContextAt(options, path) {
1299
+ const userContext = options?.context?.userContext || null;
1300
+ if (!userContext) {
1301
+ throw new Error("userContext is not available");
1302
+ }
1303
+
1304
+ return {
1305
+ /**
1306
+ * Get a value from the context path
1307
+ * @param {string} [key] - Optional key for nested access (e.g., 'updateMeta' for lastToolInputs)
1308
+ * @returns {*} The value at the path, or undefined if not found
1309
+ */
1310
+ get(key) {
1311
+ if (key !== undefined) {
1312
+ return dget(userContext, `${path}.${key}`);
1313
+ }
1314
+ return dget(userContext, path);
1315
+ },
1316
+
1317
+ /**
1318
+ * Set a value in the context path
1319
+ * @param {string|*} key - If key is provided, this is the key; otherwise this is the value
1320
+ * @param {*} [value] - The value to set (required if first param is a key)
1321
+ */
1322
+ set(key, value) {
1323
+ if (value !== undefined) {
1324
+ dset(userContext, `${path}.${key}`, value);
1325
+ } else {
1326
+ dset(userContext, path, key);
1327
+ }
1328
+ },
1329
+ };
1330
+ }
@@ -1,55 +0,0 @@
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>