@aigne/doc-smith 0.9.6-beta → 0.9.6-beta.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/agents/create/document-structure-tools/delete-document.mjs +32 -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 +56 -0
  9. package/agents/create/user-add-document/review-documents-with-new-links.mjs +73 -0
  10. package/agents/create/user-remove-document/find-documents-with-invalid-links.mjs +67 -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 +56 -0
  14. package/agents/create/user-remove-document/remove-documents-from-structure.mjs +96 -0
  15. package/agents/create/user-remove-document/review-documents-with-invalid-links.mjs +116 -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/update/document-tools/update-document-content.mjs +12 -12
  19. package/agents/update/update-document-detail.yaml +5 -1
  20. package/agents/update/update-single/update-single-document-detail.mjs +21 -6
  21. package/agents/update/user-review-document.mjs +10 -13
  22. package/agents/utils/add-translates-to-structure.mjs +29 -0
  23. package/agents/utils/{analyze-feedback-intent.yaml → analyze-document-feedback-intent.yaml} +5 -2
  24. package/agents/utils/analyze-structure-feedback-intent.yaml +29 -0
  25. package/agents/utils/check-detail-result.mjs +2 -14
  26. package/aigne.yaml +10 -1
  27. package/package.json +1 -1
  28. package/prompts/structure/find-documents-to-add-links.md +52 -0
  29. package/prompts/utils/analyze-document-feedback-intent.md +54 -0
  30. package/prompts/utils/analyze-structure-feedback-intent.md +43 -0
  31. package/types/document-schema.mjs +2 -0
  32. package/types/document-structure-schema.mjs +6 -2
  33. package/utils/docs-finder-utils.mjs +82 -0
  34. package/utils/markdown-checker.mjs +50 -5
  35. package/utils/utils.mjs +103 -0
  36. package/prompts/utils/analyze-feedback-intent.md +0 -55
@@ -0,0 +1,52 @@
1
+ # Task: Find Documents to Add Links
2
+
3
+ Determine which existing documents should link to newly added documents.
4
+
5
+ ## Input
6
+
7
+ <documentStructure>
8
+ {{originalDocumentStructure}}
9
+ </documentStructure>
10
+
11
+ <newDocuments>
12
+ {{newDocuments}}
13
+ </newDocuments>
14
+
15
+ <userFeedback>
16
+ {{allFeedback}}
17
+ </userFeedback>
18
+
19
+ ## Steps
20
+
21
+ 1. **Check <userFeedback> first.**
22
+ - If users explicitly specify linking (e.g., “link FAQ from About”), follow exactly.
23
+ 2. **Analyze <documentStructure>.**
24
+ Identify existing documents that should link to those in <newDocuments> using the rules below.
25
+ 3. For each qualifying document, add a non-empty `newLinks` array containing new document paths.
26
+ 4. Output only these updated documents (subset of <documentStructure>) as `documentsWithNewLinks`.
27
+
28
+ Each item in `documentsWithNewLinks` must:
29
+ - Be an existing document from <documentStructure>
30
+ - Retain all original properties (`path`, `title`, `description`, `parentId`, `icon`, `sourceIds`)
31
+ - Include `newLinks: string[]`
32
+
33
+ ## Linking Rules (in priority order)
34
+
35
+ 1. **User Instructions** — Follow explicit <userFeedback>.
36
+ 2. **Parent–Child** — If a new document’s `parentId` equals a document’s `path`, the parent links to it.
37
+ 3. **Semantic Similarity** — Link thematically related documents (e.g., “About” ↔ “Team”).
38
+ 4. **Navigation Context** — Documents in the same navigation group may link.
39
+ 5. **Hierarchy** — Sibling or section documents may cross-link.
40
+ 6. **Relevance** — Add links only when it improves navigation logically.
41
+
42
+ ## Output Format
43
+
44
+ ```json
45
+ {
46
+ "documentsWithNewLinks": [
47
+ {
48
+ "path": "/existing-document",
49
+ "newLinks": ["/new-document-1", "/new-document-2"]
50
+ }
51
+ ]
52
+ }
@@ -0,0 +1,54 @@
1
+ <role>
2
+ You are a feedback intent analyzer for **document content modifications**. Your task is to determine the intent type of user feedback regarding content-level operations inside a document, and whether external data sources are needed.
3
+ </role>
4
+
5
+ <input>
6
+ feedback: {{feedback}}
7
+ </input>
8
+
9
+ <analysis_rules>
10
+ If the feedback contains any document-level (structure) operations, return an error (document content edits cannot include structure changes).
11
+
12
+ Scope: Only analyze feedback related to document content (e.g. sections, text, images).
13
+
14
+ **Intent types:**
15
+
16
+ 1. add - Adding new sections or content inside a document
17
+ 2. edit - Modifying existing content, titles, descriptions, components
18
+ 3. delete - Removing sections or content
19
+ 4. move - Moving sections to different positions within the document
20
+ 5. reorder - Changing the order of sections at the same level
21
+ 6. mixed - Combination of multiple intent types
22
+
23
+ **Data source rules:**
24
+
25
+ - add/edit -> needDataSources = true
26
+ - delete/move/reorder -> needDataSources = false
27
+ - mixed -> needDataSources = true if any add/edit is included
28
+
29
+ **Decision logic:**
30
+
31
+ - Only consider document content operations.
32
+ - If any add or edit operation exists -> needDataSources = true
33
+ - If only delete, move, or reorder operations exist -> needDataSources = false
34
+ - When uncertain, default to needDataSources = true
35
+ </analysis_rules>
36
+
37
+ <output_rules>
38
+ Normal output:
39
+
40
+ {
41
+ "error": false,
42
+ "needDataSources": boolean,
43
+ "intentType": "add" | "edit" | "delete" | "move" | "reorder" | "mixed",
44
+ "reason": "Explanation of why data sources are or aren't needed based on page content operations."
45
+ }
46
+
47
+ Error output (if document-level operations are detected):
48
+
49
+ {
50
+ "error": true,
51
+ "needDataSources": false,
52
+ "reason": "Feedback mixes document-content edits with document-structure operations. When analyzing document content, structure changes are not allowed. Please split into separate feedback items."
53
+ }
54
+ </output_rules>
@@ -0,0 +1,43 @@
1
+ <role>
2
+ You are a feedback intent analyzer for **document structure modifications**. Your task is to determine the intent type of user feedback regarding document-level operations and whether external data sources are needed.
3
+ </role>
4
+
5
+ <input>
6
+ feedback: {{feedback}}
7
+ </input>
8
+
9
+ <analysis_rules>
10
+ Scope: Only analyze feedback related to document structure. Ignore any content-level operations inside document (e.g. sections, text, images).
11
+
12
+ **intent types:**
13
+
14
+ 1. add - Adding new documents
15
+ 2. edit - Modifying document-level properties (e.g., path, parentId, title of the document itself)
16
+ 3. delete - Removing documents
17
+ 4. move - Moving documents to different positions or parent sections
18
+ 5. reorder - Changing the order of documents
19
+ 6. mixed - Combination of multiple intent types
20
+
21
+ **Data source rules:**
22
+
23
+ - add/edit -> needDataSources = true
24
+ - delete/move/reorder -> needDataSources = false
25
+ - mixed -> needDataSources = true if any add/edit is included
26
+
27
+ **Decision logic:**
28
+
29
+ - Only consider document-level operations in the feedback.
30
+ - If any add or edit operation exists -> needDataSources = true
31
+ - If only delete, move, or reorder operations exist -> needDataSources = false
32
+ - When uncertain, default to needDataSources = true
33
+ </analysis_rules>
34
+
35
+ <output_rules>
36
+ Return a JSON object:
37
+
38
+ {
39
+ "needDataSources": boolean,
40
+ "intentType": "add" | "edit" | "delete" | "move" | "reorder" | "mixed",
41
+ "reason": "Explanation of why data sources are or aren't needed based on document-level operations."
42
+ }
43
+ </output_rules>
@@ -4,6 +4,7 @@ import { zodToJsonSchema } from "zod-to-json-schema";
4
4
  // Update document content schemas
5
5
  export const updateDocumentContentInputSchema = z.object({
6
6
  diffPatch: z.string().min(1, "Diff patch is required"),
7
+ path: z.string().min(1, "Path is required for concurrent document updates"),
7
8
  });
8
9
 
9
10
  export const updateDocumentContentOutputSchema = z.object({
@@ -18,6 +19,7 @@ export const getUpdateDocumentContentInputJsonSchema = () => {
18
19
  const schema = zodToJsonSchema(updateDocumentContentInputSchema);
19
20
  if (schema.properties) {
20
21
  schema.properties.diffPatch.description = "Diff patch string to apply to the original content";
22
+ schema.properties.path.description = "Document path";
21
23
  }
22
24
  return schema;
23
25
  };
@@ -33,12 +33,13 @@ export const addDocumentOutputSchema = z.object({
33
33
  // Delete document schemas
34
34
  export const deleteDocumentInputSchema = z.object({
35
35
  path: z.string().min(1, "Path is required"),
36
+ recursive: z.boolean().optional(),
36
37
  });
37
38
 
38
39
  export const deleteDocumentOutputSchema = z.object({
39
40
  documentStructure: documentStructureSchema,
40
41
  message: z.string().optional(),
41
- deletedDocument: documentItemSchema.optional(),
42
+ deletedDocuments: z.array(documentItemSchema).optional(),
42
43
  error: z.object({ message: z.string() }).optional(),
43
44
  });
44
45
 
@@ -113,6 +114,8 @@ export const getDeleteDocumentInputJsonSchema = () => {
113
114
  const schema = zodToJsonSchema(deleteDocumentInputSchema);
114
115
  if (schema.properties) {
115
116
  schema.properties.path.description = "URL path of the document to delete";
117
+ schema.properties.recursive.description =
118
+ "If true, recursively delete all child documents. If false or not provided, deletion will fail if child documents exist.";
116
119
  }
117
120
  return schema;
118
121
  };
@@ -123,7 +126,8 @@ export const getDeleteDocumentOutputJsonSchema = () => {
123
126
  schema.properties.documentStructure.description =
124
127
  "Updated documentation structure array with the document removed";
125
128
  schema.properties.message.description = "Success message describing the operation result";
126
- schema.properties.deletedDocument.description = "The deleted document object";
129
+ schema.properties.deletedDocuments.description =
130
+ "Array of deleted document objects (includes all recursively deleted child documents if recursive=true)";
127
131
  schema.properties.error.description =
128
132
  "Error object containing error message if operation failed";
129
133
  }
@@ -324,6 +324,40 @@ export async function loadDocumentStructure(outputDir) {
324
324
  }
325
325
  }
326
326
 
327
+ /**
328
+ * Build allowed links set from document structure
329
+ * Includes both original paths and processed .md paths for link validation
330
+ * @param {Array} documentStructure - Array of documentation structure items with path property
331
+ * @returns {Set<string>} Set of allowed link paths
332
+ */
333
+ export function buildAllowedLinksFromStructure(documentStructure) {
334
+ const allowedLinks = new Set();
335
+
336
+ if (!Array.isArray(documentStructure)) {
337
+ return allowedLinks;
338
+ }
339
+
340
+ documentStructure.forEach((item) => {
341
+ if (!item?.path) {
342
+ return;
343
+ }
344
+
345
+ // Add original path
346
+ allowedLinks.add(item.path);
347
+
348
+ // Add processed .md path (same logic as processContent in utils.mjs)
349
+ let processedPath = item.path;
350
+ if (processedPath.startsWith(".")) {
351
+ processedPath = processedPath.replace(/^\./, "");
352
+ }
353
+ let flatPath = processedPath.replace(/^\//, "").replace(/\//g, "-");
354
+ flatPath = `./${flatPath}.md`;
355
+ allowedLinks.add(flatPath);
356
+ });
357
+
358
+ return allowedLinks;
359
+ }
360
+
327
361
  /**
328
362
  * Build a tree structure from a flat document structure array using parentId
329
363
  * @param {Array} documentStructure - Flat array of document structure items with path and parentId
@@ -358,3 +392,51 @@ export function buildDocumentTree(documentStructure) {
358
392
 
359
393
  return { rootNodes, nodeMap };
360
394
  }
395
+
396
+ /**
397
+ * Format document structure for printing
398
+ * @param {Array} structure - Document structure array
399
+ * @returns {Object} Object containing rootNodes and printNode function
400
+ */
401
+ function formatDocumentStructure(structure) {
402
+ const { rootNodes } = buildDocumentTree(structure);
403
+
404
+ function printNode(node, depth = 0) {
405
+ const INDENT_SPACES = " ";
406
+ const FOLDER_ICON = " 📁";
407
+ const FILE_ICON = " 📄";
408
+ const indent = INDENT_SPACES.repeat(depth);
409
+ const prefix = depth === 0 ? FOLDER_ICON : FILE_ICON;
410
+
411
+ console.log(`${indent}${prefix} ${node.title}`);
412
+
413
+ if (node.children && node.children.length > 0) {
414
+ node.children.forEach((child) => {
415
+ printNode(child, depth + 1);
416
+ });
417
+ }
418
+ }
419
+
420
+ return { rootNodes, printNode };
421
+ }
422
+
423
+ /**
424
+ * Print document structure in a user-friendly format
425
+ * @param {Array} structure - Document structure array
426
+ */
427
+ export function printDocumentStructure(structure) {
428
+ console.log(`\n ${"-".repeat(50)}`);
429
+ console.log(" Current Documentation Structure");
430
+ console.log(` ${"-".repeat(50)}`);
431
+
432
+ const { rootNodes, printNode } = formatDocumentStructure(structure);
433
+
434
+ if (rootNodes.length === 0) {
435
+ console.log(" No documentation structure found.");
436
+ } else {
437
+ rootNodes.forEach((node) => {
438
+ printNode(node);
439
+ });
440
+ }
441
+ console.log();
442
+ }
@@ -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>