psyclaw 0.28.0 → 0.28.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/README.md +5 -5
  2. package/agents/recommended/catalog.json +99 -0
  3. package/dist/apps/panel/index.html +5 -5
  4. package/dist/src/adapters/pi/extension.js +164 -18
  5. package/dist/src/adapters/pi/extension.js.map +1 -1
  6. package/dist/src/adapters/pi/rpc.d.ts +2 -0
  7. package/dist/src/adapters/pi/rpc.js +9 -2
  8. package/dist/src/adapters/pi/rpc.js.map +1 -1
  9. package/dist/src/agents/recommended-personas.d.ts +42 -0
  10. package/dist/src/agents/recommended-personas.js +135 -0
  11. package/dist/src/agents/recommended-personas.js.map +1 -0
  12. package/dist/src/ars/mode-editor.d.ts +20 -0
  13. package/dist/src/ars/mode-editor.js +57 -0
  14. package/dist/src/ars/mode-editor.js.map +1 -0
  15. package/dist/src/ars/profile.d.ts +4 -1
  16. package/dist/src/ars/profile.js +23 -6
  17. package/dist/src/ars/profile.js.map +1 -1
  18. package/dist/src/cli.js +6 -3
  19. package/dist/src/cli.js.map +1 -1
  20. package/dist/src/creation/contracts.d.ts +3 -0
  21. package/dist/src/creation/service.js +17 -5
  22. package/dist/src/creation/service.js.map +1 -1
  23. package/dist/src/index.d.ts +1 -0
  24. package/dist/src/index.js +1 -0
  25. package/dist/src/index.js.map +1 -1
  26. package/dist/src/orchestration/bundled-personas.d.ts +16 -0
  27. package/dist/src/orchestration/bundled-personas.js +101 -0
  28. package/dist/src/orchestration/bundled-personas.js.map +1 -0
  29. package/dist/src/orchestration/effects.d.ts +8 -0
  30. package/dist/src/orchestration/effects.js +39 -0
  31. package/dist/src/orchestration/effects.js.map +1 -0
  32. package/dist/src/orchestration/personas.d.ts +2 -1
  33. package/dist/src/orchestration/personas.js +20 -7
  34. package/dist/src/orchestration/personas.js.map +1 -1
  35. package/dist/src/orchestration/pi-executor.d.ts +9 -5
  36. package/dist/src/orchestration/pi-executor.js +55 -22
  37. package/dist/src/orchestration/pi-executor.js.map +1 -1
  38. package/dist/src/orchestration/research-agents.d.ts +3 -7
  39. package/dist/src/orchestration/research-agents.js +5 -69
  40. package/dist/src/orchestration/research-agents.js.map +1 -1
  41. package/dist/src/panel/server.js +6 -0
  42. package/dist/src/panel/server.js.map +1 -1
  43. package/dist/src/style/cli-ui.d.ts +14 -0
  44. package/dist/src/style/cli-ui.js +45 -0
  45. package/dist/src/style/cli-ui.js.map +1 -1
  46. package/dist/src/tui/skill-manager.js +1 -1
  47. package/dist/src/tui/skill-manager.js.map +1 -1
  48. package/dist/src/updates/update.js +9 -3
  49. package/dist/src/updates/update.js.map +1 -1
  50. package/package.json +11 -3
  51. package/vendor/academic-paper-skills/LICENSE +21 -0
  52. package/vendor/academic-paper-skills/NOTICE.md +7 -0
  53. package/vendor/academic-paper-skills/PSYCLAW_SOURCE.json +14 -0
  54. package/vendor/academic-paper-skills/academic-paper-composer/SKILL.md +840 -0
  55. package/vendor/academic-paper-skills/academic-paper-composer/references/section_guides.md +675 -0
  56. package/vendor/academic-paper-skills/academic-paper-composer/references/writing_standards.md +629 -0
  57. package/vendor/academic-paper-skills/academic-paper-composer/scripts/chapter_quality_check.py +470 -0
  58. package/vendor/academic-paper-skills/academic-paper-composer/scripts/final_evaluation.py +550 -0
  59. package/vendor/academic-paper-skills/academic-paper-strategist/SKILL.md +670 -0
  60. package/vendor/academic-paper-skills/academic-paper-strategist/references/quality_standards.md +336 -0
  61. package/vendor/academic-paper-skills/academic-paper-strategist/references/search_strategy.md +459 -0
  62. package/vendor/academic-paper-skills/academic-paper-strategist/scripts/evaluate_samples.py +300 -0
  63. package/vendor/academic-paper-skills/academic-paper-strategist/scripts/gap_analysis.py +399 -0
  64. package/vendor/ars/pi/wrapper.js +9 -0
  65. package/vendor/nature-skills/LICENSE +201 -0
  66. package/vendor/nature-skills/NOTICE.md +7 -0
  67. package/vendor/nature-skills/PSYCLAW_SOURCE.json +19 -0
  68. package/vendor/nature-skills/skills/nature-figure/README.md +107 -0
  69. package/vendor/nature-skills/skills/nature-figure/README_EN.md +107 -0
  70. package/vendor/nature-skills/skills/nature-figure/SKILL.md +154 -0
  71. package/vendor/nature-skills/skills/nature-figure/agents/openai.yaml +4 -0
  72. package/vendor/nature-skills/skills/nature-figure/evals/evals.json +325 -0
  73. package/vendor/nature-skills/skills/nature-figure/manifest.yaml +103 -0
  74. package/vendor/nature-skills/skills/nature-figure/references/ai-graphical-abstract-workflow.md +129 -0
  75. package/vendor/nature-skills/skills/nature-figure/references/api.md +576 -0
  76. package/vendor/nature-skills/skills/nature-figure/references/asset-adaptation.md +72 -0
  77. package/vendor/nature-skills/skills/nature-figure/references/backend-selection.md +118 -0
  78. package/vendor/nature-skills/skills/nature-figure/references/chart-types.md +348 -0
  79. package/vendor/nature-skills/skills/nature-figure/references/common-patterns.md +371 -0
  80. package/vendor/nature-skills/skills/nature-figure/references/demos.md +58 -0
  81. package/vendor/nature-skills/skills/nature-figure/references/design-theory.md +467 -0
  82. package/vendor/nature-skills/skills/nature-figure/references/figure-contract.md +122 -0
  83. package/vendor/nature-skills/skills/nature-figure/references/figure-legend-conventions.md +90 -0
  84. package/vendor/nature-skills/skills/nature-figure/references/multipanel-evidence-architecture.md +268 -0
  85. package/vendor/nature-skills/skills/nature-figure/references/nature-2026-observations.md +124 -0
  86. package/vendor/nature-skills/skills/nature-figure/references/nature-article-requirements.md +149 -0
  87. package/vendor/nature-skills/skills/nature-figure/references/openrouter-image-generation.md +141 -0
  88. package/vendor/nature-skills/skills/nature-figure/references/qa-contract.md +344 -0
  89. package/vendor/nature-skills/skills/nature-figure/references/r-template-index.md +66 -0
  90. package/vendor/nature-skills/skills/nature-figure/references/r-workflow.md +234 -0
  91. package/vendor/nature-skills/skills/nature-figure/references/template-catalog.md +33 -0
  92. package/vendor/nature-skills/skills/nature-figure/references/tutorials.md +260 -0
  93. package/vendor/nature-skills/skills/nature-figure/requirements.txt +1 -0
  94. package/vendor/nature-skills/skills/nature-figure/scripts/audit_figure_collisions.py +742 -0
  95. package/vendor/nature-skills/skills/nature-figure/scripts/audit_panel_alignment.py +933 -0
  96. package/vendor/nature-skills/skills/nature-figure/scripts/audit_pdf_text.py +152 -0
  97. package/vendor/nature-skills/skills/nature-figure/scripts/figure_safety.py +50 -0
  98. package/vendor/nature-skills/skills/nature-figure/scripts/generate_openrouter_schematic.py +260 -0
  99. package/vendor/nature-skills/skills/nature-figure/scripts/nature_figure_backend.py +93 -0
  100. package/vendor/nature-skills/skills/nature-figure/scripts/panel_alignment.R +188 -0
  101. package/vendor/nature-skills/skills/nature-figure/scripts/plot_templates.py +604 -0
  102. package/vendor/nature-skills/skills/nature-figure/scripts/validate_figure.py +835 -0
  103. package/vendor/nature-skills/skills/nature-figure/static/core/contract.md +55 -0
  104. package/vendor/nature-skills/skills/nature-figure/static/core/stance.md +41 -0
  105. package/vendor/nature-skills/skills/nature-figure/static/fragments/backend/python.md +56 -0
  106. package/vendor/nature-skills/skills/nature-figure/static/fragments/backend/r.md +63 -0
  107. package/vendor/nature-skills/skills/nature-figure/tests/test_figure_safety.py +209 -0
  108. package/vendor/nature-skills/skills/nature-polishing/README.md +58 -0
  109. package/vendor/nature-skills/skills/nature-polishing/README_EN.md +58 -0
  110. package/vendor/nature-skills/skills/nature-polishing/SKILL.md +111 -0
  111. package/vendor/nature-skills/skills/nature-polishing/agents/openai.yaml +4 -0
  112. package/vendor/nature-skills/skills/nature-polishing/manifest.yaml +101 -0
  113. package/vendor/nature-skills/skills/nature-polishing/references/latex-layout.md +211 -0
  114. package/vendor/nature-skills/skills/nature-polishing/references/nat-comms-2025-diction.md +73 -0
  115. package/vendor/nature-skills/skills/nature-polishing/references/phrasebank-playbook.md +175 -0
  116. package/vendor/nature-skills/skills/nature-polishing/references/published-article-patterns.md +126 -0
  117. package/vendor/nature-skills/skills/nature-polishing/references/section-moves.md +252 -0
  118. package/vendor/nature-skills/skills/nature-polishing/references/style-guardrails.md +94 -0
  119. package/vendor/nature-skills/skills/nature-polishing/references/writing-strategy.md +160 -0
  120. package/vendor/nature-skills/skills/nature-polishing/static/core/failure-modes.md +29 -0
  121. package/vendor/nature-skills/skills/nature-polishing/static/core/output-format.md +21 -0
  122. package/vendor/nature-skills/skills/nature-polishing/static/core/stance.md +26 -0
  123. package/vendor/nature-skills/skills/nature-polishing/static/fragments/journal/generic.md +17 -0
  124. package/vendor/nature-skills/skills/nature-polishing/static/fragments/journal/nat-comms.md +38 -0
  125. package/vendor/nature-skills/skills/nature-polishing/static/fragments/journal/nat-mach-intell.md +102 -0
  126. package/vendor/nature-skills/skills/nature-polishing/static/fragments/journal/nature.md +19 -0
  127. package/vendor/nature-skills/skills/nature-polishing/static/fragments/language/en.md +19 -0
  128. package/vendor/nature-skills/skills/nature-polishing/static/fragments/language/zh-to-en.md +19 -0
  129. package/vendor/nature-skills/skills/nature-polishing/static/fragments/paper_type/algorithmic.md +18 -0
  130. package/vendor/nature-skills/skills/nature-polishing/static/fragments/paper_type/hypothesis.md +17 -0
  131. package/vendor/nature-skills/skills/nature-polishing/static/fragments/paper_type/methods.md +35 -0
  132. package/vendor/nature-skills/skills/nature-polishing/static/fragments/paper_type/research.md +29 -0
  133. package/vendor/nature-skills/skills/nature-polishing/static/fragments/paper_type/review.md +17 -0
  134. package/vendor/nature-skills/skills/nature-polishing/static/fragments/section/abstract.md +26 -0
  135. package/vendor/nature-skills/skills/nature-polishing/static/fragments/section/conclusion.md +15 -0
  136. package/vendor/nature-skills/skills/nature-polishing/static/fragments/section/discussion.md +48 -0
  137. package/vendor/nature-skills/skills/nature-polishing/static/fragments/section/intro.md +33 -0
  138. package/vendor/nature-skills/skills/nature-polishing/static/fragments/section/methods.md +26 -0
  139. package/vendor/nature-skills/skills/nature-polishing/static/fragments/section/results.md +61 -0
  140. package/vendor/nature-skills/skills/nature-polishing/static/fragments/section/title.md +17 -0
  141. package/vendor/nature-skills/skills/nature-ref-verifier/README.md +44 -0
  142. package/vendor/nature-skills/skills/nature-ref-verifier/README_EN.md +44 -0
  143. package/vendor/nature-skills/skills/nature-ref-verifier/SKILL.md +196 -0
  144. package/vendor/nature-skills/skills/nature-ref-verifier/agents/openai.yaml +4 -0
  145. package/vendor/nature-skills/skills/nature-ref-verifier/manifest.yaml +19 -0
  146. package/vendor/nature-skills/skills/nature-ref-verifier/references/common-patterns.md +96 -0
  147. package/vendor/nature-skills/skills/nature-shared/README.md +43 -0
  148. package/vendor/nature-skills/skills/nature-shared/README_EN.md +43 -0
  149. package/vendor/nature-skills/skills/nature-shared/SKILL.md +33 -0
  150. package/vendor/nature-skills/skills/nature-shared/agents/openai.yaml +6 -0
  151. package/vendor/nature-skills/skills/nature-shared/core/consistency-sweep.md +122 -0
  152. package/vendor/nature-skills/skills/nature-shared/core/discussion-argument-language.md +192 -0
  153. package/vendor/nature-skills/skills/nature-shared/core/ethics.md +88 -0
  154. package/vendor/nature-skills/skills/nature-shared/core/main-text-discipline.md +179 -0
  155. package/vendor/nature-skills/skills/nature-shared/core/nature-abstract.md +171 -0
  156. package/vendor/nature-skills/skills/nature-shared/core/nature-introduction.md +164 -0
  157. package/vendor/nature-skills/skills/nature-shared/core/nature-results-discussion.md +215 -0
  158. package/vendor/nature-skills/skills/nature-shared/core/paper-type-taxonomy.md +41 -0
  159. package/vendor/nature-skills/skills/nature-shared/core/reader-workflow.md +21 -0
  160. package/vendor/nature-skills/skills/nature-shared/core/research-compliance.md +192 -0
  161. package/vendor/nature-skills/skills/nature-shared/core/terminology-ledger.md +58 -0
  162. package/vendor/nature-skills/skills/nature-shared/journal-formats/nat-comms.md +139 -0
  163. package/vendor/nature-skills/skills/nature-shared/journal-formats/nature-machine-intelligence.md +431 -0
  164. package/vendor/nature-skills/skills/nature-shared/journal-formats/nature.md +313 -0
  165. package/vendor/nature-skills/skills/nature-shared/manifest.yaml +52 -0
  166. package/vendor/nature-skills/skills/nature-shared/scripts/check_consistency.py +273 -0
  167. package/vendor/nature-skills/skills/nature-shared/tests/test_check_consistency.py +63 -0
@@ -0,0 +1,470 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Chapter Quality Check Script
4
+
5
+ This script evaluates individual chapters against quality standards.
6
+ Used after writing each chapter to ensure it meets threshold before proceeding.
7
+ """
8
+
9
+ import json
10
+ from datetime import datetime
11
+ from typing import Dict, List
12
+
13
+ def create_chapter_evaluation_template() -> Dict:
14
+ """Create template for evaluating a chapter."""
15
+ return {
16
+ "chapter_id": "", # e.g., "chapter_2"
17
+ "chapter_title": "",
18
+ "target_word_count": 0,
19
+ "actual_word_count": 0,
20
+ "evaluation": {
21
+ "argument_quality": {
22
+ "score": 0, # 1-4
23
+ "notes": ""
24
+ },
25
+ "citation_quality": {
26
+ "score": 0, # 1-4
27
+ "notes": ""
28
+ },
29
+ "clarity_readability": {
30
+ "score": 0, # 1-4
31
+ "notes": ""
32
+ },
33
+ "structure_flow": {
34
+ "score": 0, # 1-4
35
+ "notes": ""
36
+ },
37
+ "platform_conformity": {
38
+ "score": 0, # 1-4
39
+ "notes": ""
40
+ }
41
+ },
42
+ "issues": [], # List of specific issues identified
43
+ "revision_needed": "" # "none", "minor", "moderate", "major"
44
+ }
45
+
46
+ def calculate_chapter_score(evaluation: Dict) -> Dict:
47
+ """Calculate total score and assess quality."""
48
+ scores = evaluation.get("evaluation", {})
49
+
50
+ total = sum([
51
+ scores.get("argument_quality", {}).get("score", 0),
52
+ scores.get("citation_quality", {}).get("score", 0),
53
+ scores.get("clarity_readability", {}).get("score", 0),
54
+ scores.get("structure_flow", {}).get("score", 0),
55
+ scores.get("platform_conformity", {}).get("score", 0)
56
+ ])
57
+
58
+ percentage = (total / 20) * 100
59
+ passes = total >= 16 # 80% threshold
60
+
61
+ # Categorize scores
62
+ if total >= 18:
63
+ quality_level = "Excellent"
64
+ elif total >= 16:
65
+ quality_level = "Good"
66
+ elif total >= 14:
67
+ quality_level = "Acceptable (needs revision)"
68
+ else:
69
+ quality_level = "Poor (major revision required)"
70
+
71
+ return {
72
+ "total_score": total,
73
+ "max_score": 20,
74
+ "percentage": round(percentage, 1),
75
+ "passes_threshold": passes,
76
+ "quality_level": quality_level
77
+ }
78
+
79
+ def analyze_word_count(target: int, actual: int) -> Dict:
80
+ """Analyze word count variance."""
81
+ if target == 0:
82
+ return {
83
+ "within_range": True,
84
+ "variance_percent": 0,
85
+ "assessment": "No target specified"
86
+ }
87
+
88
+ variance = abs(actual - target)
89
+ variance_percent = (variance / target) * 100
90
+
91
+ if variance_percent <= 5:
92
+ within_range = True
93
+ assessment = "Perfect"
94
+ elif variance_percent <= 10:
95
+ within_range = True
96
+ assessment = "Good"
97
+ elif variance_percent <= 20:
98
+ within_range = False
99
+ assessment = "Acceptable"
100
+ else:
101
+ within_range = False
102
+ assessment = "Poor"
103
+
104
+ return {
105
+ "within_range": within_range,
106
+ "variance_percent": round(variance_percent, 1),
107
+ "assessment": assessment,
108
+ "difference": actual - target
109
+ }
110
+
111
+ def identify_weak_dimensions(evaluation: Dict) -> List[Dict]:
112
+ """Identify dimensions scoring below 3."""
113
+ scores = evaluation.get("evaluation", {})
114
+ weak_dimensions = []
115
+
116
+ dimensions = [
117
+ ("argument_quality", "Argument Quality"),
118
+ ("citation_quality", "Citation Quality"),
119
+ ("clarity_readability", "Clarity & Readability"),
120
+ ("structure_flow", "Structure & Flow"),
121
+ ("platform_conformity", "Platform Style Conformity")
122
+ ]
123
+
124
+ for key, name in dimensions:
125
+ dim_data = scores.get(key, {})
126
+ score = dim_data.get("score", 0)
127
+
128
+ if score < 3:
129
+ weak_dimensions.append({
130
+ "dimension": name,
131
+ "score": score,
132
+ "max_score": 4,
133
+ "notes": dim_data.get("notes", ""),
134
+ "severity": "high" if score <= 1 else "medium"
135
+ })
136
+
137
+ return weak_dimensions
138
+
139
+ def generate_recommendations(evaluation: Dict, weak_dims: List[Dict],
140
+ word_analysis: Dict) -> List[Dict]:
141
+ """Generate specific revision recommendations."""
142
+ recommendations = []
143
+
144
+ # Word count issues
145
+ if word_analysis["variance_percent"] > 20:
146
+ recommendations.append({
147
+ "category": "word_count",
148
+ "priority": "medium",
149
+ "issue": f"Word count off by {word_analysis['variance_percent']:.1f}% ({word_analysis['difference']:+d} words)",
150
+ "action": "Cut or expand content to match target" if word_analysis['difference'] > 0 else "Expand content to match target"
151
+ })
152
+
153
+ # Weak dimensions
154
+ for dim in weak_dims:
155
+ priority = dim["severity"]
156
+ recommendations.append({
157
+ "category": dim["dimension"],
158
+ "priority": priority,
159
+ "issue": f"{dim['dimension']} scored {dim['score']}/4",
160
+ "action": f"Address: {dim['notes']}" if dim['notes'] else f"Improve {dim['dimension'].lower()}"
161
+ })
162
+
163
+ # Custom issues from evaluation
164
+ for issue in evaluation.get("issues", []):
165
+ recommendations.append({
166
+ "category": "custom",
167
+ "priority": "high",
168
+ "issue": issue,
169
+ "action": "Address this specific issue"
170
+ })
171
+
172
+ # Sort by priority
173
+ priority_order = {"high": 0, "medium": 1, "low": 2}
174
+ recommendations.sort(key=lambda x: priority_order.get(x["priority"], 3))
175
+
176
+ return recommendations
177
+
178
+ def generate_chapter_report(evaluation: Dict) -> str:
179
+ """Generate comprehensive chapter evaluation report."""
180
+ score_result = calculate_chapter_score(evaluation)
181
+ word_analysis = analyze_word_count(
182
+ evaluation.get("target_word_count", 0),
183
+ evaluation.get("actual_word_count", 0)
184
+ )
185
+ weak_dims = identify_weak_dimensions(evaluation)
186
+ recommendations = generate_recommendations(evaluation, weak_dims, word_analysis)
187
+
188
+ report = f"""
189
+ # Chapter Quality Report
190
+
191
+ Generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}
192
+
193
+ ## Chapter Information
194
+
195
+ - **Chapter ID**: {evaluation.get('chapter_id', 'N/A')}
196
+ - **Title**: {evaluation.get('chapter_title', 'N/A')}
197
+ - **Target word count**: {evaluation.get('target_word_count', 'N/A')}
198
+ - **Actual word count**: {evaluation.get('actual_word_count', 'N/A')}
199
+
200
+ ## Overall Assessment
201
+
202
+ - **Total Score**: {score_result['total_score']}/{score_result['max_score']} ({score_result['percentage']}%)
203
+ - **Quality Level**: {score_result['quality_level']}
204
+ - **Passes Threshold**: {'✓ YES' if score_result['passes_threshold'] else '✗ NO (16/20 required)'}
205
+
206
+ ## Dimension Scores
207
+
208
+ """
209
+
210
+ # Add dimension breakdown
211
+ scores = evaluation.get("evaluation", {})
212
+ dimensions = [
213
+ ("argument_quality", "Argument Quality"),
214
+ ("citation_quality", "Citation Quality"),
215
+ ("clarity_readability", "Clarity & Readability"),
216
+ ("structure_flow", "Structure & Flow"),
217
+ ("platform_conformity", "Platform Style Conformity")
218
+ ]
219
+
220
+ for key, name in dimensions:
221
+ dim_data = scores.get(key, {})
222
+ score = dim_data.get("score", 0)
223
+ notes = dim_data.get("notes", "No notes")
224
+
225
+ icon = "✓" if score >= 3 else "⚠"
226
+ report += f"### {icon} {name}: {score}/4\n\n"
227
+ report += f"{notes}\n\n"
228
+
229
+ # Word count analysis
230
+ report += "## Word Count Analysis\n\n"
231
+ report += f"- **Variance**: {word_analysis['variance_percent']:.1f}% ({word_analysis['difference']:+d} words)\n"
232
+ report += f"- **Assessment**: {word_analysis['assessment']}\n"
233
+
234
+ if word_analysis['variance_percent'] > 10:
235
+ report += f"- **Note**: Word count outside recommended ±10% range\n"
236
+
237
+ report += "\n"
238
+
239
+ # Weak dimensions
240
+ if weak_dims:
241
+ report += "## Weak Dimensions (Score <3)\n\n"
242
+ for dim in weak_dims:
243
+ report += f"### {dim['dimension']}: {dim['score']}/4 ({dim['severity'].upper()} priority)\n\n"
244
+ if dim['notes']:
245
+ report += f"{dim['notes']}\n\n"
246
+
247
+ # Recommendations
248
+ if recommendations:
249
+ report += "## Revision Recommendations\n\n"
250
+ report += "Prioritized list of recommended revisions:\n\n"
251
+
252
+ for i, rec in enumerate(recommendations, 1):
253
+ priority_icon = {"high": "🔴", "medium": "🟡", "low": "🟢"}.get(rec['priority'], "⚪")
254
+ report += f"{i}. {priority_icon} **{rec['category'].title()}** ({rec['priority'].upper()} priority)\n"
255
+ report += f" - Issue: {rec['issue']}\n"
256
+ report += f" - Action: {rec['action']}\n\n"
257
+
258
+ # Decision
259
+ report += "## Decision\n\n"
260
+
261
+ if score_result['passes_threshold']:
262
+ report += "✓ **PROCEED**: Chapter meets quality threshold. Proceed to next chapter.\n\n"
263
+ if weak_dims or word_analysis['variance_percent'] > 10:
264
+ report += "**Optional improvements**:\n"
265
+ report += "- Consider minor revisions to address noted issues\n"
266
+ report += "- Not required before proceeding\n"
267
+ else:
268
+ report += "✗ **REVISE**: Chapter below quality threshold. Revise before proceeding.\n\n"
269
+ report += "**Required actions**:\n"
270
+ report += "1. Address all HIGH priority recommendations\n"
271
+ report += "2. Re-evaluate chapter after revisions\n"
272
+ report += "3. Verify score ≥16/20 before proceeding to next chapter\n\n"
273
+ report += "**Why this matters**: Each chapter builds on previous ones. Low-quality chapters create cascading problems.\n"
274
+
275
+ return report
276
+
277
+ def validate_evaluation(evaluation: Dict) -> Dict:
278
+ """Validate that evaluation is complete and properly formatted."""
279
+ issues = []
280
+ warnings = []
281
+
282
+ # Check required fields
283
+ required_fields = ["chapter_id", "chapter_title", "target_word_count", "actual_word_count"]
284
+ for field in required_fields:
285
+ if not evaluation.get(field):
286
+ issues.append(f"Missing required field: {field}")
287
+
288
+ # Check scores present
289
+ eval_data = evaluation.get("evaluation", {})
290
+ dimensions = ["argument_quality", "citation_quality", "clarity_readability",
291
+ "structure_flow", "platform_conformity"]
292
+
293
+ for dim in dimensions:
294
+ if dim not in eval_data:
295
+ issues.append(f"Missing dimension: {dim}")
296
+ else:
297
+ score = eval_data[dim].get("score", 0)
298
+ if not isinstance(score, int) or score < 1 or score > 4:
299
+ issues.append(f"Invalid score for {dim}: {score} (must be 1-4)")
300
+
301
+ if not eval_data[dim].get("notes"):
302
+ warnings.append(f"No notes provided for {dim} (recommended)")
303
+
304
+ return {
305
+ "valid": len(issues) == 0,
306
+ "issues": issues,
307
+ "warnings": warnings
308
+ }
309
+
310
+ def compare_chapters(chapters: List[Dict]) -> str:
311
+ """Generate comparison report across multiple chapters."""
312
+ if not chapters:
313
+ return "No chapters to compare."
314
+
315
+ report = f"""
316
+ # Multi-Chapter Comparison Report
317
+
318
+ Generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}
319
+
320
+ ## Chapter Score Summary
321
+
322
+ | Chapter | Title | Score | % | Quality | Pass |
323
+ |---------|-------|-------|---|---------|------|
324
+ """
325
+
326
+ total_scores = []
327
+
328
+ for ch in chapters:
329
+ score_result = calculate_chapter_score(ch)
330
+ total_scores.append(score_result['total_score'])
331
+
332
+ pass_icon = "✓" if score_result['passes_threshold'] else "✗"
333
+ report += f"| {ch.get('chapter_id', 'N/A')} | {ch.get('chapter_title', 'N/A')[:30]}... | {score_result['total_score']}/20 | {score_result['percentage']:.1f}% | {score_result['quality_level']} | {pass_icon} |\n"
334
+
335
+ # Statistics
336
+ avg_score = sum(total_scores) / len(total_scores) if total_scores else 0
337
+ min_score = min(total_scores) if total_scores else 0
338
+ max_score = max(total_scores) if total_scores else 0
339
+ passing = sum(1 for s in total_scores if s >= 16)
340
+
341
+ report += f"""
342
+
343
+ ## Statistics
344
+
345
+ - **Average score**: {avg_score:.1f}/20 ({(avg_score/20)*100:.1f}%)
346
+ - **Range**: {min_score}-{max_score}
347
+ - **Passing chapters**: {passing}/{len(chapters)}
348
+
349
+ """
350
+
351
+ # Dimension comparison
352
+ report += "## Dimension Averages\n\n"
353
+
354
+ dimensions = [
355
+ ("argument_quality", "Argument Quality"),
356
+ ("citation_quality", "Citation Quality"),
357
+ ("clarity_readability", "Clarity & Readability"),
358
+ ("structure_flow", "Structure & Flow"),
359
+ ("platform_conformity", "Platform Style Conformity")
360
+ ]
361
+
362
+ for key, name in dimensions:
363
+ scores = [ch.get("evaluation", {}).get(key, {}).get("score", 0) for ch in chapters]
364
+ avg = sum(scores) / len(scores) if scores else 0
365
+ report += f"- **{name}**: {avg:.2f}/4\n"
366
+
367
+ report += "\n## Overall Assessment\n\n"
368
+
369
+ if passing == len(chapters):
370
+ report += "✓ All chapters meet quality threshold. Ready for final quality assessment.\n"
371
+ else:
372
+ report += f"⚠ {len(chapters) - passing} chapter(s) below threshold. Revise before proceeding to final assessment.\n"
373
+
374
+ return report
375
+
376
+ def main():
377
+ """Interactive chapter quality check interface."""
378
+ print("=" * 60)
379
+ print("Chapter Quality Check Tool")
380
+ print("=" * 60)
381
+ print()
382
+
383
+ print("Options:")
384
+ print("1. Create evaluation template")
385
+ print("2. Evaluate chapter from JSON")
386
+ print("3. Compare multiple chapters")
387
+ print()
388
+
389
+ choice = input("Select option (1-3): ").strip()
390
+
391
+ if choice == "1":
392
+ # Create template
393
+ chapter_id = input("Chapter ID (e.g., 'chapter_2'): ").strip()
394
+ chapter_title = input("Chapter title: ").strip()
395
+ target_wc = int(input("Target word count: ").strip())
396
+
397
+ template = create_chapter_evaluation_template()
398
+ template["chapter_id"] = chapter_id
399
+ template["chapter_title"] = chapter_title
400
+ template["target_word_count"] = target_wc
401
+
402
+ filename = f"{chapter_id}_evaluation.json"
403
+ with open(filename, 'w') as f:
404
+ json.dump(template, f, indent=2)
405
+
406
+ print(f"\nTemplate saved to {filename}")
407
+ print("Fill in the evaluation scores and notes, then use option 2 to generate report.")
408
+
409
+ elif choice == "2":
410
+ # Evaluate chapter
411
+ filename = input("Enter evaluation JSON filename: ").strip()
412
+ try:
413
+ with open(filename, 'r') as f:
414
+ evaluation = json.load(f)
415
+
416
+ # Validate
417
+ validation = validate_evaluation(evaluation)
418
+ if not validation['valid']:
419
+ print("\n⚠ Validation Issues:")
420
+ for issue in validation['issues']:
421
+ print(f" - {issue}")
422
+ print("\nPlease fix issues before generating report.")
423
+ return
424
+
425
+ if validation['warnings']:
426
+ print("\nℹ️ Warnings:")
427
+ for warning in validation['warnings']:
428
+ print(f" - {warning}")
429
+ print()
430
+
431
+ # Generate report
432
+ report = generate_chapter_report(evaluation)
433
+ print("\n" + report)
434
+
435
+ # Save report
436
+ report_filename = filename.replace('.json', '_report.md')
437
+ with open(report_filename, 'w') as f:
438
+ f.write(report)
439
+ print(f"\nReport saved to {report_filename}")
440
+
441
+ except FileNotFoundError:
442
+ print(f"Error: File '{filename}' not found")
443
+ except json.JSONDecodeError:
444
+ print(f"Error: '{filename}' is not valid JSON")
445
+
446
+ elif choice == "3":
447
+ # Compare chapters
448
+ filename = input("Enter JSON filename with list of chapter evaluations: ").strip()
449
+ try:
450
+ with open(filename, 'r') as f:
451
+ chapters = json.load(f)
452
+
453
+ report = compare_chapters(chapters)
454
+ print("\n" + report)
455
+
456
+ report_filename = filename.replace('.json', '_comparison_report.md')
457
+ with open(report_filename, 'w') as f:
458
+ f.write(report)
459
+ print(f"\nComparison report saved to {report_filename}")
460
+
461
+ except FileNotFoundError:
462
+ print(f"Error: File '{filename}' not found")
463
+ except json.JSONDecodeError:
464
+ print(f"Error: '{filename}' is not valid JSON")
465
+
466
+ else:
467
+ print("Invalid option")
468
+
469
+ if __name__ == "__main__":
470
+ main()