@aigne/doc-smith 0.2.11 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/agents/batch-docs-detail-generator.yaml +1 -1
- package/agents/batch-translate.yaml +3 -1
- package/agents/check-structure-plan.mjs +1 -1
- package/agents/detail-generator-and-translate.yaml +3 -0
- package/agents/detail-regenerator.yaml +3 -6
- package/agents/docs-generator.yaml +2 -5
- package/agents/find-item-by-path.mjs +1 -1
- package/agents/format-structure-plan.mjs +2 -0
- package/agents/input-generator.mjs +220 -52
- package/agents/load-config.mjs +8 -1
- package/agents/load-sources.mjs +2 -0
- package/agents/publish-docs.mjs +11 -4
- package/agents/reflective-structure-planner.yaml +2 -1
- package/agents/save-output.mjs +2 -0
- package/agents/save-single-doc.mjs +2 -0
- package/agents/structure-planning.yaml +1 -1
- package/agents/transform-detail-datasources.mjs +2 -0
- package/package.json +7 -7
- package/prompts/content-detail-generator.md +3 -1
- package/tests/test-all-validation-cases.mjs +22 -0
- package/utils/auth-utils.mjs +34 -10
- package/utils/blocklet.mjs +46 -12
- package/utils/conflict-detector.mjs +131 -0
- package/utils/constants.mjs +388 -27
- package/utils/markdown-checker.mjs +14 -0
- package/utils/utils.mjs +150 -4
|
@@ -495,6 +495,28 @@ flowchart TD
|
|
|
495
495
|
OperationalExcellence --> "Maintainability & Scalability" --> L;
|
|
496
496
|
\`\`\`
|
|
497
497
|
|
|
498
|
+
This content ends properly.
|
|
499
|
+
`,
|
|
500
|
+
},
|
|
501
|
+
|
|
502
|
+
{
|
|
503
|
+
category: "🧩 MERMAID VALIDATION",
|
|
504
|
+
name: "Mermaid with numbered list format in node labels",
|
|
505
|
+
expectPass: false,
|
|
506
|
+
expectedErrors: ["numbered list format in Mermaid node label"],
|
|
507
|
+
content: `# Test Document
|
|
508
|
+
|
|
509
|
+
\`\`\`mermaid
|
|
510
|
+
flowchart TD
|
|
511
|
+
A["1. Create Backend Implementation<br>api/src/providers/"]
|
|
512
|
+
B["2. Add Backend Configuration<br>api/src/providers/models.ts"]
|
|
513
|
+
C["3. Update Frontend Selector<br>src/pages/config/ai-providers/"]
|
|
514
|
+
D["4. Add Provider Logo<br>public/logo/"]
|
|
515
|
+
E["5. Update Documentation"]
|
|
516
|
+
|
|
517
|
+
A --> B --> C --> D --> E
|
|
518
|
+
\`\`\`
|
|
519
|
+
|
|
498
520
|
This content ends properly.
|
|
499
521
|
`,
|
|
500
522
|
},
|
package/utils/auth-utils.mjs
CHANGED
|
@@ -3,11 +3,20 @@ import { readFile, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { createConnect } from "@aigne/aigne-hub";
|
|
6
|
+
import chalk from "chalk";
|
|
6
7
|
import open from "open";
|
|
7
8
|
import { joinURL } from "ufo";
|
|
8
9
|
import { parse, stringify } from "yaml";
|
|
9
|
-
import {
|
|
10
|
-
|
|
10
|
+
import {
|
|
11
|
+
getComponentMountPoint,
|
|
12
|
+
InvalidBlockletError,
|
|
13
|
+
ComponentNotFoundError,
|
|
14
|
+
} from "./blocklet.mjs";
|
|
15
|
+
import {
|
|
16
|
+
DISCUSS_KIT_DID,
|
|
17
|
+
DISCUSS_KIT_STORE_URL,
|
|
18
|
+
BLOCKLET_ADD_COMPONENT_DOCS,
|
|
19
|
+
} from "./constants.mjs";
|
|
11
20
|
|
|
12
21
|
const WELLKNOWN_SERVICE_PATH_PREFIX = "/.well-known/service";
|
|
13
22
|
|
|
@@ -47,14 +56,29 @@ export async function getAccessToken(appUrl) {
|
|
|
47
56
|
// Check if Discuss Kit is running at the provided URL
|
|
48
57
|
try {
|
|
49
58
|
await getComponentMountPoint(appUrl, DISCUSS_KIT_DID);
|
|
50
|
-
} catch {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
)
|
|
59
|
+
} catch (error) {
|
|
60
|
+
const storeLink = chalk.cyan(DISCUSS_KIT_STORE_URL);
|
|
61
|
+
if (error instanceof InvalidBlockletError) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`${chalk.yellow("⚠️ The provided URL is not a valid website on ArcBlock platform")}\n\n` +
|
|
64
|
+
`${chalk.bold("💡 Solution:")} Start here to run your own website that can host your docs:\n${storeLink}\n\n`,
|
|
65
|
+
);
|
|
66
|
+
} else if (error instanceof ComponentNotFoundError) {
|
|
67
|
+
const docsLink = chalk.cyan(BLOCKLET_ADD_COMPONENT_DOCS);
|
|
68
|
+
throw new Error(
|
|
69
|
+
`${chalk.yellow("⚠️ This website does not have required components for publishing")}\n\n` +
|
|
70
|
+
`${chalk.bold("💡 Solution:")} Please refer to the documentation to add Discuss Kit component:\n${docsLink}\n\n`,
|
|
71
|
+
);
|
|
72
|
+
} else {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`❌ Unable to connect to: ${chalk.cyan(appUrl)}\n\n` +
|
|
75
|
+
`${chalk.bold("Possible causes:")}\n` +
|
|
76
|
+
`• Network connection issues\n` +
|
|
77
|
+
`• Server temporarily unavailable\n` +
|
|
78
|
+
`• Incorrect URL address\n\n` +
|
|
79
|
+
`${chalk.green("Suggestion:")} Please check your network connection and URL address, then try again`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
58
82
|
}
|
|
59
83
|
|
|
60
84
|
const DISCUSS_KIT_URL = appUrl;
|
package/utils/blocklet.mjs
CHANGED
|
@@ -1,24 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error class for invalid blocklet application URLs
|
|
3
|
+
*/
|
|
4
|
+
export class InvalidBlockletError extends Error {
|
|
5
|
+
constructor(url, status, statusText) {
|
|
6
|
+
super(`Invalid application URL: "${url}". Unable to fetch configuration.`);
|
|
7
|
+
this.name = "InvalidBlockletError";
|
|
8
|
+
this.url = url;
|
|
9
|
+
this.status = status;
|
|
10
|
+
this.statusText = statusText;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Custom error class for missing component mount points
|
|
16
|
+
*/
|
|
17
|
+
export class ComponentNotFoundError extends Error {
|
|
18
|
+
constructor(did, appUrl) {
|
|
19
|
+
super(`Your website "${appUrl}" missing required component to host your docs.`);
|
|
20
|
+
this.name = "ComponentNotFoundError";
|
|
21
|
+
this.did = did;
|
|
22
|
+
this.appUrl = appUrl;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
1
26
|
export async function getComponentMountPoint(appUrl, did) {
|
|
2
27
|
const url = new URL(appUrl);
|
|
3
28
|
const blockletJsUrl = `${url.origin}/__blocklet__.js?type=json`;
|
|
4
29
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
30
|
+
let blockletJs;
|
|
31
|
+
try {
|
|
32
|
+
blockletJs = await fetch(blockletJsUrl, {
|
|
33
|
+
method: "GET",
|
|
34
|
+
headers: {
|
|
35
|
+
Accept: "application/json",
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
} catch (error) {
|
|
39
|
+
throw new InvalidBlockletError(appUrl, null, error.message);
|
|
40
|
+
}
|
|
11
41
|
|
|
12
42
|
if (!blockletJs.ok) {
|
|
13
|
-
throw new
|
|
14
|
-
|
|
15
|
-
|
|
43
|
+
throw new InvalidBlockletError(appUrl, blockletJs.status, blockletJs.statusText);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let config;
|
|
47
|
+
try {
|
|
48
|
+
config = await blockletJs.json();
|
|
49
|
+
} catch {
|
|
50
|
+
throw new InvalidBlockletError(appUrl, null, "Invalid JSON response");
|
|
16
51
|
}
|
|
17
52
|
|
|
18
|
-
const
|
|
19
|
-
const component = config.componentMountPoints.find((component) => component.did === did);
|
|
53
|
+
const component = config.componentMountPoints?.find((component) => component.did === did);
|
|
20
54
|
if (!component) {
|
|
21
|
-
throw new
|
|
55
|
+
throw new ComponentNotFoundError(did, appUrl);
|
|
22
56
|
}
|
|
23
57
|
|
|
24
58
|
return component.mountPoint;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { CONFLICT_RULES } from "./constants.mjs";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Detect internal conflicts within the same question (multi-select conflicts)
|
|
5
|
+
* @param {string} questionType - Question type (documentPurpose, targetAudienceTypes)
|
|
6
|
+
* @param {Array} selectedValues - User selected values
|
|
7
|
+
* @returns {Array} List of conflicts
|
|
8
|
+
*/
|
|
9
|
+
export function detectInternalConflicts(questionType, selectedValues) {
|
|
10
|
+
const rules = CONFLICT_RULES.internalConflicts[questionType] || [];
|
|
11
|
+
|
|
12
|
+
// Extract values from the selected items (handle both string arrays and object arrays)
|
|
13
|
+
const selectedValueStrings = selectedValues.map((item) =>
|
|
14
|
+
typeof item === "object" && item.value ? item.value : item,
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
const conflicts = [];
|
|
18
|
+
|
|
19
|
+
rules.forEach((rule) => {
|
|
20
|
+
// Check if all conflict items are selected
|
|
21
|
+
const hasConflict = rule.conflictItems.every((item) => selectedValueStrings.includes(item));
|
|
22
|
+
|
|
23
|
+
if (hasConflict) {
|
|
24
|
+
conflicts.push({
|
|
25
|
+
type: "internal",
|
|
26
|
+
questionType,
|
|
27
|
+
severity: rule.severity,
|
|
28
|
+
reason: rule.reason,
|
|
29
|
+
suggestion: rule.suggestion,
|
|
30
|
+
items: rule.conflictItems,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return conflicts;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get filtered options based on cross-question conflict rules
|
|
40
|
+
* @param {string} targetQuestion - Target question type to filter
|
|
41
|
+
* @param {Object} currentSelections - Current user selections across all questions
|
|
42
|
+
* @param {Object} allOptions - All available options for the target question
|
|
43
|
+
* @returns {Object} Filtered options
|
|
44
|
+
*/
|
|
45
|
+
export function getFilteredOptions(targetQuestion, currentSelections, allOptions) {
|
|
46
|
+
const crossRules = CONFLICT_RULES.crossConflicts;
|
|
47
|
+
const filteredOptions = { ...allOptions };
|
|
48
|
+
const appliedFilters = [];
|
|
49
|
+
|
|
50
|
+
crossRules.forEach((rule) => {
|
|
51
|
+
// Check if we should apply this rule for filtering the target question
|
|
52
|
+
const shouldApplyRule = (() => {
|
|
53
|
+
// Check if this rule applies to the target question
|
|
54
|
+
const conflictingForTarget = rule.conflictingOptions[targetQuestion];
|
|
55
|
+
if (!conflictingForTarget) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check if current selections match the conditions that would trigger filtering
|
|
60
|
+
const nonTargetConditions = Object.entries(rule.conditions).filter(
|
|
61
|
+
([key]) => key !== targetQuestion,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
if (nonTargetConditions.length === 0) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const matchesNonTargetConditions = nonTargetConditions.every(([key, values]) => {
|
|
69
|
+
const selection = currentSelections[key];
|
|
70
|
+
if (!selection) return false;
|
|
71
|
+
|
|
72
|
+
// Extract values if selection contains objects
|
|
73
|
+
const selectionArray = Array.isArray(selection) ? selection : [selection];
|
|
74
|
+
const selectionValues = selectionArray.map((item) =>
|
|
75
|
+
typeof item === "object" && item.value ? item.value : item,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
return values.some((value) => selectionValues.includes(value));
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return matchesNonTargetConditions;
|
|
82
|
+
})();
|
|
83
|
+
|
|
84
|
+
if (shouldApplyRule && rule.action === "filter") {
|
|
85
|
+
// Filter out conflicting options for the target question
|
|
86
|
+
const conflictingForTarget = rule.conflictingOptions[targetQuestion];
|
|
87
|
+
|
|
88
|
+
if (conflictingForTarget) {
|
|
89
|
+
conflictingForTarget.forEach((conflictOption) => {
|
|
90
|
+
if (filteredOptions[conflictOption]) {
|
|
91
|
+
delete filteredOptions[conflictOption];
|
|
92
|
+
appliedFilters.push({
|
|
93
|
+
removedOption: conflictOption,
|
|
94
|
+
reason: rule.reason,
|
|
95
|
+
severity: rule.severity,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
filteredOptions,
|
|
105
|
+
appliedFilters,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Validate user selection for internal conflicts and return validation message
|
|
111
|
+
* @param {string} questionType - Question type (documentPurpose, targetAudienceTypes)
|
|
112
|
+
* @param {Array} selectedValues - User selected values
|
|
113
|
+
* @returns {string|boolean} Error message if conflicts exist, true if valid
|
|
114
|
+
*/
|
|
115
|
+
export function validateSelection(questionType, selectedValues) {
|
|
116
|
+
const conflicts = detectInternalConflicts(questionType, selectedValues);
|
|
117
|
+
|
|
118
|
+
if (conflicts.length === 0) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Return error message for severe conflicts
|
|
123
|
+
const severeConflicts = conflicts.filter((c) => c.severity === "severe");
|
|
124
|
+
if (severeConflicts.length > 0) {
|
|
125
|
+
const conflict = severeConflicts[0];
|
|
126
|
+
return `Conflict detected: ${conflict.reason}. ${conflict.suggestion}`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// For moderate conflicts, allow but warn
|
|
130
|
+
return true;
|
|
131
|
+
}
|