@aigne/doc-smith 0.4.4 → 0.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aigne/doc-smith",
3
- "version": "0.4.4",
3
+ "version": "0.5.1",
4
4
  "description": "",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -12,13 +12,13 @@
12
12
  "author": "Arcblock <blocklet@arcblock.io> https://github.com/blocklet",
13
13
  "license": "MIT",
14
14
  "dependencies": {
15
- "@aigne/aigne-hub": "^0.6.8",
16
- "@aigne/anthropic": "^0.11.8",
17
- "@aigne/cli": "^1.39.1",
15
+ "@aigne/aigne-hub": "^0.6.9",
16
+ "@aigne/anthropic": "^0.11.9",
17
+ "@aigne/cli": "^1.41.0",
18
18
  "@aigne/core": "^1.55.0",
19
- "@aigne/gemini": "^0.9.8",
20
- "@aigne/openai": "^0.12.2",
21
- "@aigne/publish-docs": "^0.5.6",
19
+ "@aigne/gemini": "^0.9.9",
20
+ "@aigne/openai": "^0.12.3",
21
+ "@aigne/publish-docs": "^0.5.7",
22
22
  "chalk": "^5.5.0",
23
23
  "dompurify": "^3.2.6",
24
24
  "glob": "^11.0.3",
@@ -39,7 +39,8 @@
39
39
  "@biomejs/biome": "^2.1.4"
40
40
  },
41
41
  "scripts": {
42
- "test": "echo \"Error: no test specified\" && exit 1",
42
+ "test": "bun test",
43
+ "test:watch": "bun test --watch",
43
44
  "lint": "biome check && pnpm -r run lint",
44
45
  "update:deps": "npx -y taze major -r -w -f -n '/@abtnode|@aigne|@arcblock|@blocklet|@did-connect|@did-pay|@did-space|@nft-store|@nft-studio|@ocap/' && pnpm install && pnpm run deduplicate",
45
46
  "deduplicate": "pnpm dedupe",
@@ -56,6 +56,14 @@ parentId: {{parentId}}
56
56
  ** 使用 {{ locale }} 语言输出内容 **
57
57
  </user_rules>
58
58
 
59
+ <user_preferences>
60
+ {{userPreferences}}
61
+
62
+ 用户偏好使用规则:
63
+ - 用户偏好来自用户之前操作中提供的反馈,生成结构规划中需要考虑用户的偏好,避免出现用户反馈的问题又重复出现
64
+ - 用户偏好的权重低于本次用户提交的反馈
65
+ </user_preferences>
66
+
59
67
  <rules>
60
68
 
61
69
  目标受众:{{targetAudience}}
@@ -0,0 +1,84 @@
1
+ <role>
2
+ 你是"反馈→规则"转换器。将一次性的自然语言反馈提炼为**一条单句**、**可执行**、**可复用**的指令,
3
+ 并判断是否需要**持久化保存**,以及作用域(global/structure/document/translation)与是否仅限于"输入 paths 范围"。
4
+ </role>
5
+
6
+ <input>
7
+ - feedback: {{feedback}}
8
+ - stage: {{stage}} # 可取:structure_planning | document_refine | translation_refine
9
+ - paths: {{paths}} # 本次命令输入的路径数组(可为空)。仅用于判断是否"限定到这些路径"。不要把它们写进输出。
10
+ - existingPreferences: {{existingPreferences}} # 当前已保存的用户偏好规则
11
+ </input>
12
+
13
+ <scope_rules>
14
+ 作用域判定启发式规则:
15
+
16
+ **按 stage 分类**:
17
+ - 若 stage=structure_planning:默认 `scope="structure"`,除非反馈显然是全局写作/语气/排除类政策(则用 `global`)。
18
+ - 若 stage=document_refine:默认 `scope="document"`;若反馈是通用写作政策、排除策略且不依赖具体页面,则可提升为 `global`。
19
+ - 若 stage=translation_refine:默认 `scope="translation"`;若反馈是翻译阶段的一般政策可保持此 scope。
20
+
21
+ **路径限制判定**:
22
+ - 若用户反馈显著只影响本批 `paths` 指向的范围(例如"examples 目录中的页面精简说明"),将 `limitToInputPaths=true`;否则为 `false`。
23
+ - **永远不要**在输出中返回具体的 paths 列表。
24
+ </scope_rules>
25
+
26
+ <save_rules>
27
+ 是否保存判定规则:
28
+
29
+ **一次性操作(不保存)**:
30
+ - 只修正当下版本/错别字/个别句式/局部事实错误,且无稳定可复用价值 → `save=false`
31
+
32
+ **可复用政策(保存)**:
33
+ - 写作风格、结构约定、包含/排除项、翻译约定等可广泛适用且未来应持续执行 → `save=true`
34
+
35
+ **重复性检查(不保存)**:
36
+ - 若 `existingPreferences` 中已有**相似或覆盖**本次反馈意图的规则,则 `save=false`
37
+ - 检查逻辑:对比反馈意图、规则含义、适用范围。若新反馈已被现有规则充分覆盖,无需重复保存
38
+ - 若新反馈是对现有规则的**细化、补充或矛盾修正**,则仍可 `save=true`
39
+
40
+ **判定原则**:
41
+ - 优先避免重复保存;若难以判定是否重复,优先 `save=false`,以避免规则冗余
42
+ </save_rules>
43
+
44
+ <rule_format>
45
+ 规则写法要求:
46
+
47
+ - 面向模型的**单句**指令;允许使用"必须/不得/总是"等明确措辞。
48
+ - 不引入具体路径、不绑定具体文件名。
49
+ - 例:"写作面向初学者;术语首次出现必须给出简明解释。"
50
+ </rule_format>
51
+
52
+ <examples>
53
+ 示例1:
54
+ - 输入:stage=document_refine,paths=["overview.md"],feedback="示例页废话太多,代码要最小可运行。"
55
+ - 输出:
56
+ {"rule":"示例页面以最小可运行代码为主,移除与主题无关的说明段。","scope":"document","save":true,"limitToInputPaths":true}
57
+
58
+ 示例2:
59
+ - 输入:stage=structure_planning,paths=[],feedback="概览与教程结尾加"下一步"并给出2–3个链接。"
60
+ - 输出:
61
+ {"rule":"在概览与教程文档结尾添加"下一步"小节并提供 2–3 个本仓库内链接。","scope":"structure","save":true,"limitToInputPaths":false}
62
+
63
+ 示例3:
64
+ - 输入:stage=translation_refine,paths=[],feedback="变量名和代码不要翻译。"
65
+ - 输出:
66
+ {"rule":"翻译时保持代码与标识符原样,不得翻译。","scope":"translation","save":true,"limitToInputPaths":false}
67
+
68
+ 示例4:
69
+ - 输入:stage=document_refine,paths=["overview.md"],feedback="这段话事实有误,改成 2021 年发布。"
70
+ - 输出:
71
+ {"rule":"更正事实到正确年份。","scope":"document","save":false,"limitToInputPaths":true}
72
+
73
+ 示例5(去重案例):
74
+ - 输入:stage=document_refine,paths=[],feedback="代码示例太复杂了,简化一下。",existingPreferences="rules:\n - rule: 示例页面以最小可运行代码为主,移除与主题无关的说明段。\n scope: document\n active: true"
75
+ - 输出:
76
+ {"rule":"简化代码示例的复杂度。","scope":"document","save":false,"limitToInputPaths":false}
77
+ # 理由:现有规则已覆盖简化代码示例的意图
78
+
79
+ 示例6(非重复案例):
80
+ - 输入:stage=document_refine,paths=[],feedback="代码注释要用英文写。",existingPreferences="rules:\n - rule: 示例页面以最小可运行代码为主,移除与主题无关的说明段。\n scope: document\n active: true"
81
+ - 输出:
82
+ {"rule":"代码注释必须使用英文编写。","scope":"document","save":true,"limitToInputPaths":false}
83
+ # 理由:现有规则未涉及注释语言,属于新的规则维度
84
+ </examples>
@@ -56,6 +56,14 @@
56
56
  {{ rules }}
57
57
  </user_rules>
58
58
 
59
+ <user_preferences>
60
+ {{userPreferences}}
61
+
62
+ 用户偏好使用规则:
63
+ - 用户偏好来自用户之前操作中提供的反馈,生成结构规划中需要考虑用户的偏好,避免出现用户反馈的问题又重复出现
64
+ - 用户偏好的权重低于本次用户提交的反馈
65
+ </user_preferences>
66
+
59
67
  <rules>
60
68
  DataSources 使用规则:
61
69
  1. 结构规划时要要尽可能的把 DataSources 中的信息合理的进行规划展示,不能遗漏
@@ -69,5 +69,13 @@
69
69
  {{ detailFeedback }}
70
70
  </review_feedback>
71
71
 
72
+ <user_preferences>
73
+ {{userPreferences}}
74
+
75
+ 用户偏好使用规则:
76
+ - 用户偏好来自用户之前操作中提供的反馈,生成结构规划中需要考虑用户的偏好,避免出现用户反馈的问题又重复出现
77
+ - 用户偏好的权重低于本次用户提交的反馈
78
+ </user_preferences>
79
+
72
80
  指令:
73
81
  请将 <content> 中的内容(不包含最外层的 <content> 标签) **准确** 地翻译成 **{{ language }}**,并严格遵循翻译要求。
@@ -1,5 +1,4 @@
1
- #!/usr/bin/env node
2
-
1
+ import { afterAll, describe, expect, test } from "bun:test";
3
2
  import checkDetailResult from "../agents/check-detail-result.mjs";
4
3
  import { checkMarkdown } from "../utils/markdown-checker.mjs";
5
4
  import { shutdownValidation } from "../utils/mermaid-validator.mjs";
@@ -459,7 +458,7 @@ This content ends properly.
459
458
  category: "🧩 MERMAID VALIDATION",
460
459
  name: "Mermaid with subgraph reference issues (rendering failure)",
461
460
  expectPass: false,
462
- expectedErrors: ["subgraph reference"],
461
+ expectedErrors: ["Mermaid syntax error"],
463
462
  content: `# Test Document
464
463
 
465
464
  \`\`\`mermaid
@@ -618,146 +617,70 @@ This comprehensive document demonstrates all validation rules in their correct u
618
617
  },
619
618
  ];
620
619
 
621
- async function runValidationTests() {
622
- console.log("🧪 Comprehensive Markdown Validation Test Suite\n");
623
- console.log("=".repeat(80));
624
-
625
- let totalTests = 0;
626
- let passedTests = 0;
627
- let failedTests = 0;
628
-
629
- let currentCategory = "";
630
-
631
- for (const testCase of testCases) {
632
- // Print category header if it changed
633
- if (testCase.category !== currentCategory) {
634
- currentCategory = testCase.category;
635
- console.log(`\n${currentCategory}`);
636
- console.log("-".repeat(80));
637
- }
638
-
639
- console.log(`\n📝 Testing: ${testCase.name}`);
640
- totalTests++;
641
-
620
+ describe("Markdown Validation Test Suite", () => {
621
+ afterAll(async () => {
622
+ // Shutdown worker pool to ensure clean exit
642
623
  try {
643
- // Test with checkMarkdown directly
644
- const errors = await checkMarkdown(testCase.content, "test", {
645
- allowedLinks,
646
- });
647
-
648
- // Test with checkDetailResult wrapper
649
- const wrapperResult = await checkDetailResult({
650
- structurePlan: mockStructurePlan,
651
- reviewContent: testCase.content,
652
- });
653
-
654
- const hasErrors = errors.length > 0;
655
- const expectPass = testCase.expectPass;
656
-
657
- // Verify test expectation
658
- if (expectPass && !hasErrors) {
659
- console.log("✅ PASS - Content correctly passed validation");
660
- passedTests++;
661
- } else if (!expectPass && hasErrors) {
662
- console.log("✅ PASS - Content correctly failed validation");
663
-
664
- // Check if expected error types are present
665
- if (testCase.expectedErrors) {
666
- const foundExpectedErrors = testCase.expectedErrors.every((expectedError) =>
667
- errors.some((error) => error.toLowerCase().includes(expectedError.toLowerCase())),
668
- );
624
+ await shutdownValidation();
625
+ } catch {
626
+ // Ignore shutdown errors in tests
627
+ }
628
+ });
669
629
 
670
- if (foundExpectedErrors) {
671
- console.log("✅ Expected error types found");
630
+ // Group tests by category
631
+ const testsByCategory = testCases.reduce((acc, testCase) => {
632
+ if (!acc[testCase.category]) {
633
+ acc[testCase.category] = [];
634
+ }
635
+ acc[testCase.category].push(testCase);
636
+ return acc;
637
+ }, {});
638
+
639
+ Object.entries(testsByCategory).forEach(([category, categoryTests]) => {
640
+ describe(category, () => {
641
+ categoryTests.forEach((testCase) => {
642
+ test(testCase.name, async () => {
643
+ // Test with checkMarkdown directly
644
+ const errors = await checkMarkdown(testCase.content, "test", {
645
+ allowedLinks,
646
+ });
647
+
648
+ // Test with checkDetailResult wrapper
649
+ const wrapperResult = await checkDetailResult({
650
+ structurePlan: mockStructurePlan,
651
+ reviewContent: testCase.content,
652
+ });
653
+
654
+ const hasErrors = errors.length > 0;
655
+ const expectPass = testCase.expectPass;
656
+
657
+ // Verify test expectation
658
+ if (expectPass) {
659
+ expect(hasErrors).toBe(false);
672
660
  } else {
673
- console.log("⚠️ Expected error types not all found");
674
- console.log(` Expected: ${testCase.expectedErrors.join(", ")}`);
661
+ expect(hasErrors).toBe(true);
662
+
663
+ // Check if expected error types are present
664
+ if (testCase.expectedErrors) {
665
+ const foundExpectedErrors = testCase.expectedErrors.every((expectedError) =>
666
+ errors.some((error) => error.toLowerCase().includes(expectedError.toLowerCase())),
667
+ );
668
+ expect(foundExpectedErrors).toBe(true);
669
+ }
675
670
  }
676
- }
677
-
678
- passedTests++;
679
- } else {
680
- console.log(
681
- `❌ FAIL - Expected ${expectPass ? "PASS" : "FAIL"} but got ${
682
- hasErrors ? "FAIL" : "PASS"
683
- }`,
684
- );
685
- failedTests++;
686
- }
687
-
688
- // Show error details for failing cases
689
- if (hasErrors) {
690
- console.log(` Found ${errors.length} issue(s):`);
691
- errors.slice(0, 3).forEach((error) => {
692
- console.log(` • ${error}`);
693
- });
694
- if (errors.length > 3) {
695
- console.log(` ... and ${errors.length - 3} more issues`);
696
- }
697
- }
698
-
699
- // Verify consistency between direct call and wrapper
700
- const wrapperErrors = wrapperResult.detailFeedback
701
- ? wrapperResult.detailFeedback.split("\n").filter((line) => line.trim())
702
- : [];
703
-
704
- if (errors.length === wrapperErrors.length) {
705
- console.log("✅ Direct call and wrapper consistent");
706
- } else {
707
- console.log(
708
- `⚠️ Inconsistent results: direct=${errors.length}, wrapper=${wrapperErrors.length}`,
709
- );
710
- }
711
- } catch (error) {
712
- console.log(`❌ ERROR: ${error.message}`);
713
- failedTests++;
714
- }
715
- }
716
671
 
717
- // Final summary
718
- console.log(`\n${"=".repeat(80)}`);
719
- console.log("📊 TEST SUMMARY");
720
- console.log("=".repeat(80));
721
- console.log(`Total Tests: ${totalTests}`);
722
- console.log(`Passed: ${passedTests} ✅`);
723
- console.log(`Failed: ${failedTests} ❌`);
724
- console.log(`Success Rate: ${((passedTests / totalTests) * 100).toFixed(1)}%`);
725
-
726
- console.log("\n🔍 VALIDATION COVERAGE:");
727
- console.log("✅ Link validation (dead links, allowed links)");
728
- console.log("✅ Code block validation (indentation, completeness)");
729
- console.log("✅ Content structure (line breaks, punctuation)");
730
- console.log("✅ Table validation (column count consistency)");
731
- console.log("✅ Mermaid validation (syntax, rendering issues)");
732
- console.log("✅ Standard markdown linting (formatting rules)");
733
- console.log("✅ Complex mixed scenarios");
734
- console.log("✅ Edge cases and error conditions");
735
-
736
- if (failedTests === 0) {
737
- console.log("\n🎉 ALL TESTS PASSED! Validation system is working correctly.");
738
- } else {
739
- console.log(`\n⚠️ ${failedTests} test(s) failed. Please review the validation logic.`);
740
- }
672
+ // Verify consistency between direct call and wrapper
673
+ const wrapperErrors = wrapperResult.detailFeedback
674
+ ? wrapperResult.detailFeedback.split("\n").filter((line) => line.trim())
675
+ : [];
741
676
 
742
- // Shutdown worker pool to ensure clean exit
743
- try {
744
- await shutdownValidation();
745
- } catch (error) {
746
- console.error("Error shutting down validation:", error);
747
- }
748
- }
677
+ // Note: We don't enforce exact equality as wrapper may format differently
678
+ expect(wrapperErrors.length > 0).toBe(hasErrors);
679
+ });
680
+ });
681
+ });
682
+ });
683
+ });
749
684
 
750
685
  // Export test cases for external use
751
686
  export { testCases, mockStructurePlan, allowedLinks };
752
-
753
- // Run tests if this file is executed directly
754
- if (import.meta.url === `file://${process.argv[1]}`) {
755
- runValidationTests()
756
- .then(() => {
757
- process.exit(0);
758
- })
759
- .catch((error) => {
760
- console.error("Test suite error:", error);
761
- process.exit(1);
762
- });
763
- }
@@ -1,92 +1,56 @@
1
+ import { describe, expect, test } from "bun:test";
1
2
  import checkDetailResult from "../agents/check-detail-result.mjs";
2
3
 
3
- async function runTests() {
4
- function assert(condition, message) {
5
- if (!condition) {
6
- throw new Error(`Assertion failed: ${message}`);
7
- }
8
- }
9
-
10
- async function testApproveValidContent() {
11
- console.log("Testing: should approve valid content");
4
+ describe("checkDetailResult", () => {
5
+ test("should approve valid content", async () => {
12
6
  const structurePlan = [{ path: "/getting-started" }];
13
- const content = "This is a test with a [valid link](/getting-started).";
14
- const result = await checkDetailResult({ structurePlan, content });
15
- assert(result.isApproved === true, "Should be approved");
16
- assert(result.detailFeedback === "", "Feedback should be empty");
17
- console.log("✅ Test passed: should approve valid content");
18
- }
19
-
20
- async function testRejectDeadLink() {
21
- console.log("Testing: should reject content with a dead link");
7
+ const reviewContent =
8
+ "This is a test with a [valid link](/getting-started).\n\nThis has proper structure with multiple lines.";
9
+ const result = await checkDetailResult({ structurePlan, reviewContent });
10
+ expect(result.isApproved).toBe(true);
11
+ expect(result.detailFeedback).toBe("");
12
+ });
13
+
14
+ test("should reject content with a dead link", async () => {
22
15
  const structurePlan = [{ path: "/getting-started" }];
23
- const content = "This contains a [dead link](/dead-link).";
24
- const result = await checkDetailResult({ structurePlan, content });
25
- assert(result.isApproved === false, "Should not be approved");
26
- assert(result.detailFeedback.includes("Found a dead link"), "Should report dead link");
27
- console.log("✅ Test passed: should reject content with a dead link");
28
- }
16
+ const reviewContent = "This contains a [dead link](/dead-link).";
17
+ const result = await checkDetailResult({ structurePlan, reviewContent });
18
+ expect(result.isApproved).toBe(false);
19
+ expect(result.detailFeedback).toContain("Found a dead link");
20
+ });
29
21
 
30
- async function testRejectIncorrectTableSeparator() {
31
- console.log("Testing: should reject content with incorrect table separator");
22
+ test("should accept valid table format", async () => {
32
23
  const structurePlan = [];
33
- const content = "| Header | Header |\n| - | - |\n| Cell | Cell |";
34
- const result = await checkDetailResult({ structurePlan, content });
35
- assert(result.isApproved === false, "Should not be approved");
36
- assert(
37
- result.detailFeedback.includes("incorrect table separator"),
38
- "Should report incorrect table separator",
39
- );
40
- console.log("✅ Test passed: should reject content with incorrect table separator");
41
- }
42
-
43
- async function testApproveExternalLink() {
44
- console.log("Testing: should approve content with an external link");
24
+ const reviewContent =
25
+ "| Header | Header |\n|--------|--------|\n| Cell | Cell |\n\nThis table is properly formatted.";
26
+ const result = await checkDetailResult({ structurePlan, reviewContent });
27
+ expect(result.isApproved).toBe(true);
28
+ expect(result.detailFeedback).toBe("");
29
+ });
30
+
31
+ test("should approve content with an external link", async () => {
45
32
  const structurePlan = [];
46
- const content = "This is a [valid external link](https://example.com).";
47
- const result = await checkDetailResult({ structurePlan, content });
48
- assert(result.isApproved === true, "Should be approved");
49
- assert(result.detailFeedback === "", "Feedback should be empty");
50
- console.log("✅ Test passed: should approve content with an external link");
51
- }
52
-
53
- async function testRejectMultipleIssues() {
54
- console.log("Testing: should reject content with multiple issues");
33
+ const reviewContent =
34
+ "This is a [valid external link](https://example.com).\n\nThis has proper multi-line structure.";
35
+ const result = await checkDetailResult({ structurePlan, reviewContent });
36
+ expect(result.isApproved).toBe(true);
37
+ expect(result.detailFeedback).toBe("");
38
+ });
39
+
40
+ test("should reject content with multiple issues", async () => {
55
41
  const structurePlan = [{ path: "/getting-started" }];
56
- const content = "This has a [dead link](/dead-link) and an incorrect table: | - |.";
57
- const result = await checkDetailResult({ structurePlan, content });
58
- assert(result.isApproved === false, "Should not be approved");
59
- assert(result.detailFeedback.includes("Found a dead link"), "Should report dead link");
60
- assert(
61
- result.detailFeedback.includes("incorrect table separator"),
62
- "Should report incorrect table separator",
63
- );
64
- console.log("✅ Test passed: should reject content with multiple issues");
65
- }
42
+ const reviewContent = "This has a [dead link](/dead-link).";
43
+ const result = await checkDetailResult({ structurePlan, reviewContent });
44
+ expect(result.isApproved).toBe(false);
45
+ expect(result.detailFeedback).toContain("dead link");
46
+ });
66
47
 
67
- async function testApproveImageSyntax() {
68
- console.log("Testing: should approve content with image syntax");
48
+ test("should approve content with image syntax", async () => {
69
49
  const structurePlan = [];
70
- const content = "This is an image ![MCP Go Logo](/logo.png).";
71
- const result = await checkDetailResult({ structurePlan, content });
72
- assert(result.isApproved === true, "Should be approved");
73
- assert(result.detailFeedback === "", "Feedback should be empty");
74
- console.log("✅ Test passed: should approve content with image syntax");
75
- }
76
-
77
- try {
78
- console.log("🚀 Starting checkDetailResult tests...");
79
- await testApproveValidContent();
80
- await testRejectDeadLink();
81
- await testRejectIncorrectTableSeparator();
82
- await testApproveExternalLink();
83
- await testRejectMultipleIssues();
84
- await testApproveImageSyntax();
85
- console.log("🎉 All tests passed!");
86
- } catch (error) {
87
- console.error("❌ Test failed:", error.message);
88
- process.exit(1);
89
- }
90
- }
91
-
92
- runTests();
50
+ const reviewContent =
51
+ "This is an image ![MCP Go Logo](/logo.png).\n\nThis has proper structure.";
52
+ const result = await checkDetailResult({ structurePlan, reviewContent });
53
+ expect(result.isApproved).toBe(true);
54
+ expect(result.detailFeedback).toBe("");
55
+ });
56
+ });