fabricatio 0.3.14.dev8__tar.gz → 0.3.15.dev4__tar.gz

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 (163) hide show
  1. fabricatio-0.3.15.dev4/.github/workflows/build-package.yaml +130 -0
  2. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/PKG-INFO +2 -1
  3. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/README.md +1 -0
  4. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/write_article/write_article.py +44 -8
  5. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/pyproject.toml +1 -1
  6. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/actions/article.py +115 -19
  7. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/extra/article_base.py +96 -32
  8. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/extra/article_main.py +8 -3
  9. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/generic.py +1 -1
  10. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/rust.pyi +13 -0
  11. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/config.rs +1 -0
  12. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/typst_tools.rs +25 -1
  13. fabricatio-0.3.15.dev4/templates/built-in/research_content_summary.hbs +17 -0
  14. fabricatio-0.3.15.dev4/uv.lock +1917 -0
  15. fabricatio-0.3.14.dev8/.github/workflows/build-package.yaml +0 -97
  16. fabricatio-0.3.14.dev8/templates.tar.gz +0 -0
  17. fabricatio-0.3.14.dev8/uv.lock +0 -1917
  18. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/.github/workflows/ruff.yaml +0 -0
  19. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/.github/workflows/tests.yaml +0 -0
  20. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/.gitignore +0 -0
  21. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/Cargo.lock +0 -0
  22. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/Cargo.toml +0 -0
  23. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/LICENSE +0 -0
  24. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/Makefile +0 -0
  25. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/build.rs +0 -0
  26. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/correct/correct.py +0 -0
  27. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/correct/correct_loop.py +0 -0
  28. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/extract_and_inject/.gitignore +0 -0
  29. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/extract_and_inject/article_rag.py +0 -0
  30. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/extract_and_inject/ask.py +0 -0
  31. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/extract_and_inject/chunk_article.py +0 -0
  32. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/extract_and_inject/extract_and_inject.py +0 -0
  33. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/extract_article/extract.py +0 -0
  34. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/llm_usages/llm_usage.py +0 -0
  35. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/make_a_rating/rating.py +0 -0
  36. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/make_diary/commits.json +0 -0
  37. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/make_diary/diary.py +0 -0
  38. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/minor/hello_fabricatio.py +0 -0
  39. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/minor/write_a_poem.py +0 -0
  40. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/propose_task/.gitignore +0 -0
  41. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/propose_task/propose.py +0 -0
  42. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/reviewer/censor.py +0 -0
  43. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/reviewer/review.py +0 -0
  44. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/rules/.gitignore +0 -0
  45. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/rules/draft_ruleset.py +0 -0
  46. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/search_bibtex/.gitignore +0 -0
  47. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/search_bibtex/search.py +0 -0
  48. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/simple_chat/chat.py +0 -0
  49. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/simple_rag/simple_rag.py +0 -0
  50. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/task_handle/handle_task.py +0 -0
  51. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/write_article/.gitignore +0 -0
  52. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/write_article/article_rag.py +0 -0
  53. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/write_article/post_process.py +0 -0
  54. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/write_outline/.gitignore +0 -0
  55. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/write_outline/write_outline.py +0 -0
  56. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/examples/write_outline/write_outline_corrected.py +0 -0
  57. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/proto/tei.proto +0 -0
  58. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/__init__.py +0 -0
  59. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/actions/__init__.py +0 -0
  60. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/actions/article_rag.py +0 -0
  61. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/actions/fs.py +0 -0
  62. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/actions/output.py +0 -0
  63. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/actions/rag.py +0 -0
  64. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/actions/rules.py +0 -0
  65. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/capabilities/__init__.py +0 -0
  66. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/capabilities/advanced_judge.py +0 -0
  67. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/capabilities/advanced_rag.py +0 -0
  68. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/capabilities/censor.py +0 -0
  69. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/capabilities/check.py +0 -0
  70. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/capabilities/correct.py +0 -0
  71. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/capabilities/extract.py +0 -0
  72. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/capabilities/persist.py +0 -0
  73. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/capabilities/propose.py +0 -0
  74. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/capabilities/rag.py +0 -0
  75. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/capabilities/rating.py +0 -0
  76. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/capabilities/review.py +0 -0
  77. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/capabilities/task.py +0 -0
  78. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/decorators.py +0 -0
  79. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/emitter.py +0 -0
  80. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/fs/__init__.py +0 -0
  81. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/fs/curd.py +0 -0
  82. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/fs/readers.py +0 -0
  83. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/journal.py +0 -0
  84. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/action.py +0 -0
  85. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/adv_kwargs_types.py +0 -0
  86. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/extra/__init__.py +0 -0
  87. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/extra/advanced_judge.py +0 -0
  88. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/extra/aricle_rag.py +0 -0
  89. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/extra/article_essence.py +0 -0
  90. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/extra/article_outline.py +0 -0
  91. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/extra/article_proposal.py +0 -0
  92. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/extra/patches.py +0 -0
  93. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/extra/problem.py +0 -0
  94. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/extra/rag.py +0 -0
  95. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/extra/rule.py +0 -0
  96. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/kwargs_types.py +0 -0
  97. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/role.py +0 -0
  98. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/task.py +0 -0
  99. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/tool.py +0 -0
  100. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/models/usages.py +0 -0
  101. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/parser.py +0 -0
  102. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/py.typed +0 -0
  103. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/toolboxes/__init__.py +0 -0
  104. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/toolboxes/arithmetic.py +0 -0
  105. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/toolboxes/fs.py +0 -0
  106. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/utils.py +0 -0
  107. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/workflows/__init__.py +0 -0
  108. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/workflows/articles.py +0 -0
  109. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/python/fabricatio/workflows/rag.py +0 -0
  110. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/bib_tools.rs +0 -0
  111. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/event.rs +0 -0
  112. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/hash.rs +0 -0
  113. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/hbs_helpers.rs +0 -0
  114. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/language.rs +0 -0
  115. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/lib.rs +0 -0
  116. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/macro_utils/Cargo.toml +0 -0
  117. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/macro_utils/src/lib.rs +0 -0
  118. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/tei_client.rs +0 -0
  119. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/templates.rs +0 -0
  120. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/typst_conversion/Cargo.toml +0 -0
  121. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/typst_conversion/src/lib.rs +0 -0
  122. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/src/word_split.rs +0 -0
  123. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/as_prompt.hbs +0 -0
  124. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/binary-exploitation-ctf-solver.hbs +0 -0
  125. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/chap_summary.hbs +0 -0
  126. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/check_string.hbs +0 -0
  127. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/claude-xml.hbs +0 -0
  128. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/clean-up-code.hbs +0 -0
  129. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/co_validation.hbs +0 -0
  130. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/create_json_obj.hbs +0 -0
  131. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/cryptography-ctf-solver.hbs +0 -0
  132. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/dependencies.hbs +0 -0
  133. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/document-the-code.hbs +0 -0
  134. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/draft_rating_criteria.hbs +0 -0
  135. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/draft_rating_manual.hbs +0 -0
  136. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/draft_rating_weights_klee.hbs +0 -0
  137. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/draft_tool_usage_code.hbs +0 -0
  138. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/extract.hbs +0 -0
  139. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/extract_criteria_from_reasons.hbs +0 -0
  140. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/extract_reasons_from_examples.hbs +0 -0
  141. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/find-security-vulnerabilities.hbs +0 -0
  142. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/fix-bugs.hbs +0 -0
  143. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/fix_troubled_obj.hbs +0 -0
  144. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/fix_troubled_string.hbs +0 -0
  145. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/generic_string.hbs +0 -0
  146. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/improve-performance.hbs +0 -0
  147. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/liststr.hbs +0 -0
  148. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/make_choice.hbs +0 -0
  149. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/make_judgment.hbs +0 -0
  150. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/pathstr.hbs +0 -0
  151. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/rate_fine_grind.hbs +0 -0
  152. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/refactor.hbs +0 -0
  153. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/refined_query.hbs +0 -0
  154. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/retrieved_display.hbs +0 -0
  155. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/reverse-engineering-ctf-solver.hbs +0 -0
  156. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/review_string.hbs +0 -0
  157. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/rule_requirement.hbs +0 -0
  158. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/ruleset_requirement_breakdown.hbs +0 -0
  159. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/task_briefing.hbs +0 -0
  160. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/web-ctf-solver.hbs +0 -0
  161. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/write-git-commit.hbs +0 -0
  162. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/write-github-pull-request.hbs +0 -0
  163. {fabricatio-0.3.14.dev8 → fabricatio-0.3.15.dev4}/templates/built-in/write-github-readme.hbs +0 -0
@@ -0,0 +1,130 @@
1
+ name: Build and Release
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ pull_request:
8
+ branches:
9
+ - master
10
+
11
+ jobs:
12
+ determine_changes:
13
+ name: Determine Release Necessity & Version Details
14
+ runs-on: ubuntu-latest
15
+ outputs:
16
+ version_changed: ${{ steps.check_version_change.outputs.VERSION_CHANGED }}
17
+ current_version: ${{ steps.get_version.outputs.CURRENT_VERSION }}
18
+ is_prerelease: ${{ steps.check_prerelease.outputs.IS_PRERELEASE }}
19
+ steps:
20
+ - name: Checkout repository
21
+ uses: actions/checkout@v4
22
+ - name: Get current version from pyproject.toml
23
+ id: get_version
24
+ run: |
25
+ CURRENT_VERSION_RAW=$(grep '^version' pyproject.toml | cut -d '"' -f 2)
26
+ echo "CURRENT_VERSION=v$CURRENT_VERSION_RAW" >> $GITHUB_OUTPUT
27
+ shell: bash
28
+ - name: Get latest git tag
29
+ uses: JinoArch/get-latest-tag@latest
30
+ id: tag
31
+ - name: Check if version has changed against latest tag
32
+ id: check_version_change
33
+ run: |
34
+ LATEST_TAG=${{ steps.tag.outputs.latestTag }}
35
+ VERSION_FROM_PYPROJECT=${{ steps.get_version.outputs.CURRENT_VERSION }}
36
+ echo "Latest git tag is $LATEST_TAG"
37
+ echo "Current version (from pyproject.toml, prefixed with 'v') is $VERSION_FROM_PYPROJECT"
38
+ if [ "$LATEST_TAG" != "$VERSION_FROM_PYPROJECT" ]; then
39
+ echo "VERSION_CHANGED=true" >> $GITHUB_OUTPUT
40
+ else
41
+ echo "VERSION_CHANGED=false" >> $GITHUB_OUTPUT
42
+ fi
43
+ shell: bash
44
+ - name: Check if current version is a prerelease
45
+ id: check_prerelease
46
+ run: |
47
+ VERSION_FROM_PYPROJECT=${{ steps.get_version.outputs.CURRENT_VERSION }}
48
+ if [[ "$VERSION_FROM_PYPROJECT" == *"-"* ]]; then # e.g., v1.0.0-alpha
49
+ echo "IS_PRERELEASE=true" >> $GITHUB_OUTPUT
50
+ else
51
+ echo "IS_PRERELEASE=false" >> $GITHUB_OUTPUT
52
+ fi
53
+ shell: bash
54
+
55
+
56
+ create-release:
57
+ if: needs.determine_changes.outputs.version_changed == 'true'
58
+ name: Create Release
59
+ needs: determine_changes
60
+ runs-on: ubuntu-latest
61
+ steps:
62
+ - name: Checkout repository
63
+ uses: actions/checkout@v4
64
+ - name: Packing templates
65
+ run: |
66
+ tar -czf templates.tar.gz templates
67
+ - name: Create Release and upload assets
68
+ id: create_release
69
+ uses: softprops/action-gh-release@v2
70
+ with:
71
+ tag_name: ${{ needs.determine_changes.outputs.current_version }}
72
+ name: ${{ needs.determine_changes.outputs.current_version }}
73
+ files: |
74
+ templates.tar.gz
75
+ prerelease: ${{ needs.determine_changes.outputs.is_prerelease }}
76
+ generate_release_notes: true
77
+ env:
78
+ GITHUB_TOKEN: ${{ secrets.PAT }}
79
+
80
+
81
+
82
+ check-and-release:
83
+ name: Build, Package, and Release
84
+ needs:
85
+ - create-release
86
+ - determine_changes
87
+ runs-on: ${{ matrix.os }}
88
+ strategy:
89
+ matrix:
90
+ os: [ windows-latest, ubuntu-latest ]
91
+ python-version: [ "3.12", "3.13" ]
92
+ steps:
93
+ - name: Checkout repository
94
+ uses: actions/checkout@v4
95
+
96
+ - name: Install the latest version of uv
97
+ uses: astral-sh/setup-uv@v6.0.1
98
+ with:
99
+ version: "latest"
100
+ - name: Install nightly rust
101
+ run: |
102
+ rustup default nightly
103
+ - name: Install Protoc
104
+ uses: arduino/setup-protoc@v3
105
+
106
+ - name: Install deps
107
+ run: |
108
+ uv sync --no-install-project --all-extras --index https://pypi.org/simple -p ${{ matrix.python-version }}
109
+
110
+ - name: Build
111
+ run: |
112
+ make PY=${{ matrix.python-version }}
113
+
114
+ - name: Upload assets
115
+ uses: softprops/action-gh-release@v2
116
+ with:
117
+ tag_name: ${{ needs.determine_changes.outputs.current_version }}
118
+ files: |
119
+ dist/*.whl
120
+ env:
121
+ GITHUB_TOKEN: ${{ secrets.PAT }}
122
+
123
+ - name: Upload to PyPI
124
+ if: needs.determine_changes.outputs.version_changed == 'true'
125
+ run: |
126
+ make publish PY=${{ matrix.python-version }}
127
+ env:
128
+ MATURIN_USERNAME: __token__
129
+ MATURIN_PASSWORD: ${{ secrets.PYPI_TOKEN }}
130
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fabricatio
3
- Version: 0.3.14.dev8
3
+ Version: 0.3.15.dev4
4
4
  Classifier: License :: OSI Approved :: MIT License
5
5
  Classifier: Programming Language :: Rust
6
6
  Classifier: Programming Language :: Python :: 3.12
@@ -185,4 +185,5 @@ Special thanks to the contributors and maintainers of:
185
185
  - [PyO3](https://github.com/PyO3/pyo3)
186
186
  - [Maturin](https://github.com/PyO3/maturin)
187
187
  - [Handlebars.rs](https://github.com/sunng87/handlebars-rust)
188
+ - [LiteLLM](https://github.com/BerriAI/litellm)
188
189
 
@@ -145,3 +145,4 @@ Special thanks to the contributors and maintainers of:
145
145
  - [PyO3](https://github.com/PyO3/pyo3)
146
146
  - [Maturin](https://github.com/PyO3/maturin)
147
147
  - [Handlebars.rs](https://github.com/sunng87/handlebars-rust)
148
+ - [LiteLLM](https://github.com/BerriAI/litellm)
@@ -2,7 +2,7 @@
2
2
 
3
3
  import asyncio
4
4
  from pathlib import Path
5
- from typing import Optional
5
+ from typing import List, Optional
6
6
 
7
7
  import typer
8
8
  from fabricatio import Event, WorkFlow, logger
@@ -12,11 +12,10 @@ from fabricatio.actions.article import (
12
12
  GenerateArticleProposal,
13
13
  GenerateInitialOutline,
14
14
  WriteChapterSummary,
15
+ WriteResearchContentSummary,
15
16
  )
16
17
  from fabricatio.actions.article_rag import ArticleConsultRAG, WriteArticleContentRAG
17
18
  from fabricatio.actions.output import DumpFinalizedOutput, PersistentAll, RenderedDump
18
- from fabricatio.fs import safe_text_read
19
- from fabricatio.models.extra.article_main import Article
20
19
  from fabricatio.models.extra.article_outline import ArticleOutline
21
20
  from fabricatio.models.task import Task
22
21
  from fabricatio.models.usages import LLMUsage
@@ -95,6 +94,11 @@ Role(
95
94
  description="Generate chapter summary based on given article outline. dump the outline to the given path. in typst format.",
96
95
  steps=(WriteChapterSummary().to_task_output(),),
97
96
  ),
97
+ Event.quick_instantiate(ns6 := "resc-suma"): WorkFlow(
98
+ name="Research Content Summary",
99
+ description="Generate research content summary based on given article outline. dump the outline to the given path. in typst format.",
100
+ steps=(WriteResearchContentSummary().to_task_output(),),
101
+ ),
98
102
  },
99
103
  )
100
104
 
@@ -216,21 +220,53 @@ def suma(
216
220
  article_path: Path = typer.Option( # noqa: B008
217
221
  Path("article.typ"), "-a", "--article-path", help="Path to the article file."
218
222
  ),
219
- dump_path: Path = typer.Option(Path("out.typ"), "-d", "--dump-path", help="Path to dump the final output."), # noqa: B008
223
+ skip_chapters: List[str] = typer.Option( # noqa: B008
224
+ [], "-s", "--skip-chapters", help="Chapters to skip."
225
+ ),
226
+ suma_title: str = typer.Option("Chapter Summary", "-t", "--suma-title", help="Title of the chapter summary."),
227
+ summary_word_count: int = typer.Option(220, "-w", "--word-count", help="Word count for the summary."),
220
228
  ) -> None:
221
229
  """Write chap summary based on given article."""
222
- path = ok(
230
+ _ = ok(
223
231
  asyncio.run(
224
232
  Task(name="write an article")
225
233
  .update_init_context(
226
- article=Article.from_typst_code("article", body=safe_text_read(article_path)),
227
- write_to=dump_path,
234
+ article_path=article_path,
235
+ summary_title=suma_title,
236
+ skip_chapters=skip_chapters,
237
+ summary_word_count=summary_word_count,
228
238
  )
229
239
  .delegate(ns5)
230
240
  ),
231
241
  "Failed to generate an article ",
232
242
  )
233
- logger.success(f"The outline is saved in:\n{path}")
243
+ logger.success(f"The outline is saved in:\n{article_path.as_posix()}")
244
+
245
+
246
+ @app.command()
247
+ def rcsuma(
248
+ article_path: Path = typer.Option( # noqa: B008
249
+ Path("article.typ"), "-a", "--article-path", help="Path to the article file."
250
+ ),
251
+ suma_title: str = typer.Option("Research Content", "-t", "--suma-title", help="Title of the summary."),
252
+ summary_word_count: int = typer.Option(220, "-w", "--word-count", help="Word count for the summary."),
253
+ paragraph_count: int = typer.Option(1, "-p", "--paragraph-count", help="Number of paragraphs for the summary."),
254
+ ) -> None:
255
+ """Write research summary based on given article."""
256
+ _ = ok(
257
+ asyncio.run(
258
+ Task(name="write an article")
259
+ .update_init_context(
260
+ article_path=article_path,
261
+ summary_title=suma_title,
262
+ summary_word_count=summary_word_count,
263
+ paragraph_count=paragraph_count,
264
+ )
265
+ .delegate(ns6)
266
+ ),
267
+ "Failed to generate an article ",
268
+ )
269
+ logger.success(f"The outline is saved in:\n{article_path.as_posix()}")
234
270
 
235
271
 
236
272
  if __name__ == "__main__":
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fabricatio"
3
- version = "0.3.14-dev8"
3
+ version = "0.3.15-dev4"
4
4
  description = "A LLM multi-agent framework."
5
5
  readme = "README.md"
6
6
  license = { file = "LICENSE" }
@@ -2,7 +2,7 @@
2
2
 
3
3
  from asyncio import gather
4
4
  from pathlib import Path
5
- from typing import Callable, List, Optional
5
+ from typing import Callable, ClassVar, List, Optional
6
6
 
7
7
  from more_itertools import filter_map
8
8
  from pydantic import Field
@@ -15,14 +15,14 @@ from fabricatio.fs import dump_text, safe_text_read
15
15
  from fabricatio.journal import logger
16
16
  from fabricatio.models.action import Action
17
17
  from fabricatio.models.extra.article_essence import ArticleEssence
18
- from fabricatio.models.extra.article_main import Article
18
+ from fabricatio.models.extra.article_main import Article, ArticleChapter, ArticleSubsection
19
19
  from fabricatio.models.extra.article_outline import ArticleOutline
20
20
  from fabricatio.models.extra.article_proposal import ArticleProposal
21
21
  from fabricatio.models.extra.rule import RuleSet
22
22
  from fabricatio.models.kwargs_types import ValidateKwargs
23
23
  from fabricatio.models.task import Task
24
24
  from fabricatio.models.usages import LLMUsage
25
- from fabricatio.rust import CONFIG, TEMPLATE_MANAGER, BibManager, detect_language
25
+ from fabricatio.rust import CONFIG, TEMPLATE_MANAGER, BibManager, detect_language, word_count
26
26
  from fabricatio.utils import ok, wrapp_in_block
27
27
 
28
28
 
@@ -277,43 +277,139 @@ class LoadArticle(Action):
277
277
  class WriteChapterSummary(Action, LLMUsage):
278
278
  """Write the chapter summary."""
279
279
 
280
- output_key: str = "chapter_summaries"
280
+ ctx_override: ClassVar[bool] = True
281
281
 
282
282
  paragraph_count: int = 1
283
+ """The number of paragraphs to generate in the chapter summary."""
283
284
 
284
- summary_word_count: int = 200
285
-
285
+ summary_word_count: int = 120
286
+ """The number of words to use in each chapter summary."""
287
+ output_key: str = "summarized_article"
288
+ """The key under which the summarized article will be stored in the output."""
286
289
  summary_title: str = "Chapter Summary"
287
- write_to: Optional[Path] = None
290
+ """The title to be used for the generated chapter summary section."""
291
+
292
+ skip_chapters: List[str] = Field(default_factory=list)
293
+ """A list of chapter titles to skip during summary generation."""
294
+
295
+ async def _execute(self, article_path: Path, **cxt) -> Article:
296
+ article = Article.from_article_file(article_path, article_path.stem)
297
+
298
+ chaps = [c for c in article.chapters if c.title not in self.skip_chapters]
299
+
300
+ retained_chapters = []
301
+ # Count chapters before filtering based on section presence,
302
+ # chaps at this point has already been filtered by self.skip_chapters
303
+ initial_chaps_for_summary_step_count = len(chaps)
304
+
305
+ for chapter_candidate in chaps:
306
+ if chapter_candidate.sections: # Check if the sections list is non-empty
307
+ retained_chapters.append(chapter_candidate)
308
+ else:
309
+ # Log c warning for each chapter skipped due to lack of sections
310
+ logger.warning(
311
+ f"Chapter '{chapter_candidate.title}' has no sections and will be skipped for summary generation."
312
+ )
313
+
314
+ chaps = retained_chapters # Update chaps to only include chapters with sections
288
315
 
289
- async def _execute(self, article: Article, write_to: Optional[Path] = None, **cxt) -> List[str]:
290
- logger.info(";".join(a.title for a in article.chapters))
316
+ # If chaps is now empty, but there were chapters to consider at the start of this step,
317
+ # log c specific warning.
318
+ if not chaps and initial_chaps_for_summary_step_count > 0:
319
+ raise ValueError("No chapters with sections were found. Please check your input data.")
291
320
 
321
+ # This line was part of the original selection.
322
+ # It will now log the titles of the chapters that are actually being processed (those with sections).
323
+ # If 'chaps' is empty, this will result in logger.info(""), which is acceptable.
324
+ logger.info(";".join(a.title for a in chaps))
292
325
  ret = [
293
- f"== {self.summary_title}\n{raw}"
326
+ ArticleSubsection.from_typst_code(self.summary_title, raw)
294
327
  for raw in (
295
328
  await self.aask(
296
329
  TEMPLATE_MANAGER.render_template(
297
330
  CONFIG.templates.chap_summary_template,
298
331
  [
299
332
  {
300
- "chapter": a.to_typst_code(),
301
- "title": a.title,
302
- "language": a.language,
333
+ "chapter": c.to_typst_code(),
334
+ "title": c.title,
335
+ "language": c.language,
303
336
  "summary_word_count": self.summary_word_count,
304
337
  "paragraph_count": self.paragraph_count,
305
338
  }
306
- for a in article.chapters
339
+ for c in chaps
307
340
  ],
308
341
  )
309
342
  )
310
343
  )
311
344
  ]
312
345
 
313
- if (to := (self.write_to or write_to)) is not None:
314
- dump_text(
315
- to,
316
- "\n\n\n".join(f"//{a.title}\n\n{s}" for a, s in zip(article.chapters, ret, strict=True)),
346
+ for c, n in zip(chaps, ret, strict=True):
347
+ c: ArticleChapter
348
+ n: ArticleSubsection
349
+ if c.sections[-1].title == self.summary_title:
350
+ logger.debug(f"Removing old summary `{self.summary_title}` at {c.title}")
351
+ c.sections.pop()
352
+
353
+ c.sections[-1].subsections.append(n)
354
+
355
+ article.update_article_file(article_path)
356
+
357
+ dump_text(
358
+ article_path, safe_text_read(article_path).replace(f"=== {self.summary_title}", f"== {self.summary_title}")
359
+ )
360
+ return article
361
+
362
+
363
+ class WriteResearchContentSummary(Action, LLMUsage):
364
+ """Write the research content summary."""
365
+
366
+ ctx_override: ClassVar[bool] = True
367
+ summary_word_count: int = 160
368
+ """The number of words to use in the research content summary."""
369
+
370
+ output_key: str = "summarized_article"
371
+ """The key under which the summarized article will be stored in the output."""
372
+
373
+ summary_title: str = "Research Content"
374
+ """The title to be used for the generated research content summary section."""
375
+
376
+ paragraph_count: int = 1
377
+ """The number of paragraphs to generate in the research content summary."""
378
+
379
+ async def _execute(self, article_path: Path, **cxt) -> Article:
380
+ article = Article.from_article_file(article_path, article_path.stem)
381
+ if not article.chapters:
382
+ raise ValueError("No chapters found in the article.")
383
+ chap_1 = article.chapters[0]
384
+ if not chap_1.sections:
385
+ raise ValueError("No sections found in the first chapter of the article.")
386
+
387
+ outline = article.extrac_outline()
388
+ suma: str = await self.aask(
389
+ TEMPLATE_MANAGER.render_template(
390
+ CONFIG.templates.research_content_summary_template,
391
+ {
392
+ "title": outline.title,
393
+ "outline": outline.to_typst_code(),
394
+ "language": detect_language(self.summary_title),
395
+ "summary_word_count": self.summary_word_count,
396
+ "paragraph_count": self.paragraph_count,
397
+ },
317
398
  )
399
+ )
400
+ logger.success(
401
+ f"{self.summary_title}|Wordcount: {word_count(suma)}|Expected: {self.summary_word_count}\n{suma}"
402
+ )
403
+
404
+ if chap_1.sections[-1].title == self.summary_title:
405
+ # remove old
406
+ logger.debug(f"Removing old summary `{self.summary_title}`")
407
+ chap_1.sections.pop()
318
408
 
319
- return ret
409
+ chap_1.sections[-1].subsections.append(ArticleSubsection.from_typst_code(self.summary_title, suma))
410
+
411
+ article.update_article_file(article_path)
412
+ dump_text(
413
+ article_path, safe_text_read(article_path).replace(f"=== {self.summary_title}", f"== {self.summary_title}")
414
+ )
415
+ return article
@@ -21,7 +21,15 @@ from fabricatio.models.generic import (
21
21
  Titled,
22
22
  WordCount,
23
23
  )
24
- from fabricatio.rust import extract_body, replace_thesis_body, split_out_metadata, to_metadata, word_count
24
+ from fabricatio.rust import (
25
+ detect_language,
26
+ extract_body,
27
+ replace_thesis_body,
28
+ split_out_metadata,
29
+ strip_comment,
30
+ to_metadata,
31
+ word_count,
32
+ )
25
33
  from fabricatio.utils import fallback_kwargs, ok
26
34
  from pydantic import Field
27
35
 
@@ -52,10 +60,31 @@ class ArticleMetaData(SketchedAble, Described, WordCount, Titled, Language):
52
60
  aims: List[str]
53
61
  """List of writing aims of the research component in academic style."""
54
62
 
63
+ _unstructured_body: str = ""
64
+ """Store the source of the unknown information."""
65
+
55
66
  @property
56
67
  def typst_metadata_comment(self) -> str:
57
68
  """Generates a comment for the metadata of the article component."""
58
- return to_metadata(self.model_dump(include={"description", "aims", "expected_word_count"}, by_alias=True))
69
+ data = self.model_dump(
70
+ include={"description", "aims", "expected_word_count"},
71
+ by_alias=True,
72
+ )
73
+ return to_metadata({k: v for k, v in data.items() if v})
74
+
75
+ @property
76
+ def unstructured_body(self) -> str:
77
+ """Returns the unstructured body of the article component."""
78
+ return self._unstructured_body
79
+
80
+ def update_unstructured_body[S: "ArticleMetaData"](self: S, body: str) -> S:
81
+ """Update the unstructured body of the article component."""
82
+ self._unstructured_body = body
83
+ return self
84
+
85
+ @property
86
+ def language(self) -> str:
87
+ return detect_language(self.title)
59
88
 
60
89
 
61
90
  class FromTypstCode(ArticleMetaData):
@@ -67,13 +96,8 @@ class FromTypstCode(ArticleMetaData):
67
96
  data, body = split_out_metadata(body)
68
97
 
69
98
  return cls(
70
- heading=title,
71
- **fallback_kwargs(
72
- data or {},
73
- elaboration="",
74
- expected_word_count=word_count(body),
75
- aims=[],
76
- ),
99
+ heading=title.strip(),
100
+ **fallback_kwargs(data or {}, elaboration="", expected_word_count=word_count(body), aims=[]),
77
101
  **kwargs,
78
102
  )
79
103
 
@@ -83,7 +107,7 @@ class ToTypstCode(ArticleMetaData):
83
107
 
84
108
  def to_typst_code(self) -> str:
85
109
  """Converts the component into a Typst code snippet for rendering."""
86
- return f"{self.title}\n{self.typst_metadata_comment}\n"
110
+ return f"{self.title}\n{self.typst_metadata_comment}\n\n{self._unstructured_body}"
87
111
 
88
112
 
89
113
  class ArticleOutlineBase(
@@ -151,12 +175,16 @@ class SectionBase[T: SubSectionBase](ArticleOutlineBase):
151
175
  @classmethod
152
176
  def from_typst_code(cls, title: str, body: str, **kwargs) -> Self:
153
177
  """Creates an Article object from the given Typst code."""
154
- return super().from_typst_code(
155
- title,
156
- body,
157
- subsections=[
158
- cls.child_type.from_typst_code(*pack) for pack in extract_sections(body, level=3, section_char="=")
159
- ],
178
+ raw = extract_sections(body, level=3, section_char="=")
179
+
180
+ return (
181
+ super()
182
+ .from_typst_code(
183
+ title,
184
+ body,
185
+ subsections=[cls.child_type.from_typst_code(*pack) for pack in raw],
186
+ )
187
+ .update_unstructured_body("" if raw else strip_comment(body))
160
188
  )
161
189
 
162
190
  def resolve_update_conflict(self, other: Self) -> str:
@@ -191,6 +219,11 @@ class SectionBase[T: SubSectionBase](ArticleOutlineBase):
191
219
  return f"Section `{self.title}` contains no subsections, expected at least one, but got 0, you can add one or more as needed."
192
220
  return ""
193
221
 
222
+ @property
223
+ def exact_word_count(self) -> int:
224
+ """Returns the exact word count of the article section outline."""
225
+ return sum(a.exact_word_count for a in self.subsections)
226
+
194
227
 
195
228
  class ChapterBase[T: SectionBase](ArticleOutlineBase):
196
229
  """Base class for article chapters."""
@@ -206,12 +239,16 @@ class ChapterBase[T: SectionBase](ArticleOutlineBase):
206
239
  @classmethod
207
240
  def from_typst_code(cls, title: str, body: str, **kwargs) -> Self:
208
241
  """Creates an Article object from the given Typst code."""
209
- return super().from_typst_code(
210
- title,
211
- body,
212
- sections=[
213
- cls.child_type.from_typst_code(*pack) for pack in extract_sections(body, level=2, section_char="=")
214
- ],
242
+ raw_sec = extract_sections(body, level=2, section_char="=")
243
+
244
+ return (
245
+ super()
246
+ .from_typst_code(
247
+ title,
248
+ body,
249
+ sections=[cls.child_type.from_typst_code(*pack) for pack in raw_sec],
250
+ )
251
+ .update_unstructured_body("" if raw_sec else strip_comment(body))
215
252
  )
216
253
 
217
254
  def resolve_update_conflict(self, other: Self) -> str:
@@ -243,6 +280,15 @@ class ChapterBase[T: SectionBase](ArticleOutlineBase):
243
280
  return f"Chapter `{self.title}` contains no sections, expected at least one, but got 0, you can add one or more as needed."
244
281
  return ""
245
282
 
283
+ @property
284
+ def exact_word_count(self) -> int:
285
+ """Calculates the total word count across all sections in the chapter.
286
+
287
+ Returns:
288
+ int: The cumulative word count of all sections.
289
+ """
290
+ return sum(a.exact_word_count for a in self.sections)
291
+
246
292
 
247
293
  class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, FromTypstCode, ToTypstCode, ABC):
248
294
  """Base class for article outlines."""
@@ -263,15 +309,33 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, FromTypstCode, To
263
309
 
264
310
  child_type: ClassVar[Type[ChapterBase]]
265
311
 
312
+ @property
313
+ def language(self) -> str:
314
+ if self.title:
315
+ return super().language
316
+ return self.chapters[0].language
317
+
318
+ @property
319
+ def exact_word_count(self) -> int:
320
+ """Calculates the total word count across all chapters in the article.
321
+
322
+ Returns:
323
+ int: The cumulative word count of all chapters.
324
+ """
325
+ return sum(ch.exact_word_count for ch in self.chapters)
326
+
266
327
  @classmethod
267
328
  def from_typst_code(cls, title: str, body: str, **kwargs) -> Self:
268
329
  """Generates an article from the given Typst code."""
269
- return super().from_typst_code(
270
- title,
271
- body,
272
- chapters=[
273
- cls.child_type.from_typst_code(*pack) for pack in extract_sections(body, level=1, section_char="=")
274
- ],
330
+ raw = extract_sections(body, level=1, section_char="=")
331
+ return (
332
+ super()
333
+ .from_typst_code(
334
+ title,
335
+ body,
336
+ chapters=[cls.child_type.from_typst_code(*pack) for pack in raw],
337
+ )
338
+ .update_unstructured_body("" if raw else strip_comment(body))
275
339
  )
276
340
 
277
341
  def iter_dfs_rev(
@@ -350,7 +414,7 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, FromTypstCode, To
350
414
 
351
415
  def to_typst_code(self) -> str:
352
416
  """Generates the Typst code representation of the article."""
353
- return f"// #{super().to_typst_code()}\n\n" + "\n\n".join(a.to_typst_code() for a in self.chapters)
417
+ return f"// #Title: {super().to_typst_code()}\n" + "\n\n".join(a.to_typst_code() for a in self.chapters)
354
418
 
355
419
  def finalized_dump(self) -> str:
356
420
  """Generates standardized hierarchical markup for academic publishing systems.
@@ -401,11 +465,11 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, FromTypstCode, To
401
465
  """Set all chap, sec, subsec have same word count sum up to be `self.expected_word_count`."""
402
466
  return self.avg_chap_wordcount().avg_sec_wordcount().avg_subsec_wordcount()
403
467
 
404
- def update_article_file(self, file: str | Path) -> Self:
468
+ def update_article_file[S: "ArticleBase"](self: S, file: str | Path) -> S:
405
469
  """Update the article file."""
406
470
  file = Path(file)
407
471
  string = safe_text_read(file)
408
- if updated := replace_thesis_body(string, ARTICLE_WRAPPER, self.to_typst_code()):
472
+ if updated := replace_thesis_body(string, ARTICLE_WRAPPER, f"\n\n{self.to_typst_code()}\n\n"):
409
473
  dump_text(file, updated)
410
474
  logger.success(f"Successfully updated {file.as_posix()}.")
411
475
  else:
@@ -413,7 +477,7 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, FromTypstCode, To
413
477
  return self
414
478
 
415
479
  @classmethod
416
- def from_article_file[S: "ArticleBase"](cls: Type[S], file: str | Path, title: str) -> S:
480
+ def from_article_file[S: "ArticleBase"](cls: Type[S], file: str | Path, title: str = "") -> S:
417
481
  """Load article from file."""
418
482
  file = Path(file)
419
483
  string = safe_text_read(file)