qwen-sci 0.2.2__py3-none-any.whl

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 (549) hide show
  1. qwen_sci-0.2.2.dist-info/METADATA +492 -0
  2. qwen_sci-0.2.2.dist-info/RECORD +549 -0
  3. qwen_sci-0.2.2.dist-info/WHEEL +4 -0
  4. qwen_sci-0.2.2.dist-info/entry_points.txt +6 -0
  5. qwen_sci-0.2.2.dist-info/licenses/LICENSE +21 -0
  6. src/1.txt +1 -0
  7. src/__init__.py +1 -0
  8. src/__main__.py +5 -0
  9. src/agents/__init__.py +1 -0
  10. src/agents/experiment_design_agent/__init__.py +268 -0
  11. src/agents/experiment_design_agent/artifacts.py +747 -0
  12. src/agents/experiment_design_agent/cache.py +319 -0
  13. src/agents/experiment_design_agent/completeness.py +130 -0
  14. src/agents/experiment_design_agent/contracts.py +771 -0
  15. src/agents/experiment_design_agent/counterexample_analyzer.py +187 -0
  16. src/agents/experiment_design_agent/design_evidence_paper_screener.py +470 -0
  17. src/agents/experiment_design_agent/discipline_catalog.py +178 -0
  18. src/agents/experiment_design_agent/evidence_cards.py +338 -0
  19. src/agents/experiment_design_agent/evidence_planner.py +452 -0
  20. src/agents/experiment_design_agent/formal_reasoning_planner.py +884 -0
  21. src/agents/experiment_design_agent/fulltext_acquisition.py +863 -0
  22. src/agents/experiment_design_agent/idea_adapter.py +100 -0
  23. src/agents/experiment_design_agent/idea_intake.py +87 -0
  24. src/agents/experiment_design_agent/llm_json.py +328 -0
  25. src/agents/experiment_design_agent/orchestrator.py +1220 -0
  26. src/agents/experiment_design_agent/reasoning_context.py +183 -0
  27. src/agents/experiment_design_agent/reasoning_validation.py +546 -0
  28. src/agents/experiment_design_agent/run.py +481 -0
  29. src/agents/experiment_design_agent/run_logging.py +292 -0
  30. src/agents/experiment_design_agent/scope_gate.py +198 -0
  31. src/agents/experiment_design_agent/study_type_composer.py +1360 -0
  32. src/agents/experiment_design_agent/survey_evidence.py +1381 -0
  33. src/agents/experiment_design_agent/template_router.py +195 -0
  34. src/agents/experiment_design_agent/variable_claim_extractor.py +93 -0
  35. src/agents/idea_agent/__init__.py +1 -0
  36. src/agents/idea_agent/agent/__init__.py +6 -0
  37. src/agents/idea_agent/agent/artifacts.py +308 -0
  38. src/agents/idea_agent/agent/base.py +122 -0
  39. src/agents/idea_agent/agent/ligagent.py +488 -0
  40. src/agents/idea_agent/agent/mcts.py +2060 -0
  41. src/agents/idea_agent/agent/prompts/__init__.py +48 -0
  42. src/agents/idea_agent/agent/prompts/advanced_analysis.py +235 -0
  43. src/agents/idea_agent/agent/prompts/algorithm_alignment.py +55 -0
  44. src/agents/idea_agent/agent/prompts/algorithm_structuring.py +48 -0
  45. src/agents/idea_agent/agent/prompts/component_extraction.py +62 -0
  46. src/agents/idea_agent/agent/prompts/component_novelty_evaluation.py +54 -0
  47. src/agents/idea_agent/agent/prompts/experiment_findings_extraction.py +39 -0
  48. src/agents/idea_agent/agent/prompts/idea_fusion.py +295 -0
  49. src/agents/idea_agent/agent/prompts/idea_introduction.py +30 -0
  50. src/agents/idea_agent/agent/prompts/idea_result_alignment.py +39 -0
  51. src/agents/idea_agent/agent/prompts/input_interpreter.py +52 -0
  52. src/agents/idea_agent/agent/prompts/keynote_ops.py +78 -0
  53. src/agents/idea_agent/agent/prompts/mcts_evaluation.py +235 -0
  54. src/agents/idea_agent/agent/prompts/mcts_generation.py +140 -0
  55. src/agents/idea_agent/agent/prompts/mechanism_commit_query.py +99 -0
  56. src/agents/idea_agent/agent/prompts/memory_selection.py +0 -0
  57. src/agents/idea_agent/agent/prompts/prompt_modes.py +16 -0
  58. src/agents/idea_agent/agent/prompts/rag_query.py +34 -0
  59. src/agents/idea_agent/agent/prompts/re_analysis_replan.py +48 -0
  60. src/agents/idea_agent/agent/prompts/reference_grounding.py +34 -0
  61. src/agents/idea_agent/agent/prompts/root_domain_classification.py +32 -0
  62. src/agents/idea_agent/agent/prompts/scientific_debate.py +88 -0
  63. src/agents/idea_agent/agent/prompts/scientific_materialization.py +45 -0
  64. src/agents/idea_agent/agent/prompts/skill_instantiation.py +193 -0
  65. src/agents/idea_agent/agent/prompts/theory_transfer_query.py +93 -0
  66. src/agents/idea_agent/agent/prompts/topic_background.py +15 -0
  67. src/agents/idea_agent/agent/skills/DEFAULT_SKILL_TEMPLATES.json +263 -0
  68. src/agents/idea_agent/agent/skills/edit_operator_skills/alternative-path-contrast/SKILL.md +40 -0
  69. src/agents/idea_agent/agent/skills/edit_operator_skills/alternative-path-contrast/references/contrast_patterns.md +22 -0
  70. src/agents/idea_agent/agent/skills/edit_operator_skills/feedback-closed-loop/SKILL.md +40 -0
  71. src/agents/idea_agent/agent/skills/edit_operator_skills/feedback-closed-loop/references/mechanism_cards.md +24 -0
  72. src/agents/idea_agent/agent/skills/edit_operator_skills/hierarchical-decomposition/SKILL.md +39 -0
  73. src/agents/idea_agent/agent/skills/edit_operator_skills/hierarchical-decomposition/references/hierarchy_patterns.md +22 -0
  74. src/agents/idea_agent/agent/skills/edit_operator_skills/mechanism-commit-innovation/SKILL.md +39 -0
  75. src/agents/idea_agent/agent/skills/edit_operator_skills/mechanism-commit-innovation/references/mechanism_commit_patterns.md +24 -0
  76. src/agents/idea_agent/agent/skills/edit_operator_skills/multi-scale-coordinator/SKILL.md +39 -0
  77. src/agents/idea_agent/agent/skills/edit_operator_skills/multi-scale-coordinator/references/coordination_patterns.md +22 -0
  78. src/agents/idea_agent/agent/skills/edit_operator_skills/speculative-execution-with-repair/SKILL.md +41 -0
  79. src/agents/idea_agent/agent/skills/edit_operator_skills/speculative-execution-with-repair/references/speculation_patterns.md +22 -0
  80. src/agents/idea_agent/agent/skills/edit_operator_skills/surgical-modularity/SKILL.md +39 -0
  81. src/agents/idea_agent/agent/skills/edit_operator_skills/surgical-modularity/references/modularity_patterns.md +22 -0
  82. src/agents/idea_agent/agent/skills/edit_operator_skills/theory-transfer-injection/SKILL.md +40 -0
  83. src/agents/idea_agent/agent/skills/edit_operator_skills/theory-transfer-injection/references/transfer_patterns.md +24 -0
  84. src/agents/idea_agent/example/idea_result.json +900 -0
  85. src/agents/idea_agent/run.py +276 -0
  86. src/agents/idea_agent/tests/test_advanced_analysis_contract.py +90 -0
  87. src/agents/idea_agent/tests/test_gap_hypothesis_bridge.py +166 -0
  88. src/agents/idea_agent/tests/test_idea_debate.py +480 -0
  89. src/agents/idea_agent/tests/test_idea_result_v5.py +99 -0
  90. src/agents/idea_agent/tests/test_idea_synthesis.py +92 -0
  91. src/agents/idea_agent/tests/test_mature_idea_sources.py +27 -0
  92. src/agents/idea_agent/tests/test_multimodal_idea_integration.py +386 -0
  93. src/agents/idea_agent/tests/test_profile_materialization_and_scoring.py +219 -0
  94. src/agents/idea_agent/tests/test_profile_native_skill_rendering.py +122 -0
  95. src/agents/idea_agent/tests/test_result_persistence.py +183 -0
  96. src/agents/idea_agent/tests/test_scientific_intervention_profiles.py +241 -0
  97. src/agents/idea_agent/tests/test_scientific_rubric_and_prompts.py +559 -0
  98. src/agents/idea_agent/tests/test_strict_json_object_responses.py +48 -0
  99. src/agents/idea_agent/utils/core/__init__.py +1 -0
  100. src/agents/idea_agent/utils/core/ablation_inputs.py +60 -0
  101. src/agents/idea_agent/utils/core/chat_errors.py +95 -0
  102. src/agents/idea_agent/utils/core/chat_router.py +130 -0
  103. src/agents/idea_agent/utils/core/chat_transport.py +188 -0
  104. src/agents/idea_agent/utils/core/config_loader.py +166 -0
  105. src/agents/idea_agent/utils/core/json_utils.py +28 -0
  106. src/agents/idea_agent/utils/core/logger.py +287 -0
  107. src/agents/idea_agent/utils/core/progress.py +58 -0
  108. src/agents/idea_agent/utils/core/response_parsing.py +57 -0
  109. src/agents/idea_agent/utils/core/run_inputs.py +198 -0
  110. src/agents/idea_agent/utils/mcts/__init__.py +1 -0
  111. src/agents/idea_agent/utils/mcts/component_novelty.py +342 -0
  112. src/agents/idea_agent/utils/mcts/defect_registry.py +302 -0
  113. src/agents/idea_agent/utils/mcts/idea_routes.py +61 -0
  114. src/agents/idea_agent/utils/mcts/idea_taste_presets.py +312 -0
  115. src/agents/idea_agent/utils/mcts/mcts_helpers.py +738 -0
  116. src/agents/idea_agent/utils/mcts/mcts_runtime.py +3000 -0
  117. src/agents/idea_agent/utils/mcts/scientific_intervention_ontology.py +1561 -0
  118. src/agents/idea_agent/utils/mcts/scientific_rubric.py +211 -0
  119. src/agents/idea_agent/utils/mcts/skill_parsing.py +92 -0
  120. src/agents/idea_agent/utils/papers/__init__.py +1 -0
  121. src/agents/idea_agent/utils/papers/paper_graph_vector_store.py +499 -0
  122. src/agents/idea_agent/utils/papers/paper_repository.py +372 -0
  123. src/agents/idea_agent/utils/prompting/__init__.py +1 -0
  124. src/agents/idea_agent/utils/prompting/prompt_views.py +637 -0
  125. src/agents/idea_agent/utils/workflow/__init__.py +1 -0
  126. src/agents/idea_agent/utils/workflow/advanced_analysis_contract.py +244 -0
  127. src/agents/idea_agent/utils/workflow/gap_hypothesis_bridge.py +461 -0
  128. src/agents/idea_agent/utils/workflow/idea_contract.py +426 -0
  129. src/agents/idea_agent/utils/workflow/idea_debate.py +1238 -0
  130. src/agents/idea_agent/utils/workflow/idea_diversity.py +163 -0
  131. src/agents/idea_agent/utils/workflow/idea_fusion.py +889 -0
  132. src/agents/idea_agent/utils/workflow/idea_helpers.py +478 -0
  133. src/agents/idea_agent/utils/workflow/idea_portfolio.py +422 -0
  134. src/agents/idea_agent/utils/workflow/idea_synthesis.py +482 -0
  135. src/agents/idea_agent/utils/workflow/ligagent_flow.py +581 -0
  136. src/agents/idea_agent/utils/workflow/ligagent_handlers.py +1836 -0
  137. src/agents/idea_agent/utils/workflow/ligagent_helpers.py +1331 -0
  138. src/agents/idea_agent/utils/workflow/ligagent_utils.py +322 -0
  139. src/agents/idea_agent/utils/workflow/mature_idea_sources.py +648 -0
  140. src/agents/idea_agent/utils/workflow/multimodal_data_anchoring.py +578 -0
  141. src/agents/idea_agent/utils/workflow/result_persistence.py +774 -0
  142. src/agents/idea_agent/utils/workflow/stage_contract.py +76 -0
  143. src/agents/idea_agent/utils/workflow/workflow_runtime.py +288 -0
  144. src/agents/research_plan_author/__init__.py +50 -0
  145. src/agents/research_plan_author/artifacts.py +270 -0
  146. src/agents/research_plan_author/authoring_blueprint.py +1246 -0
  147. src/agents/research_plan_author/bibtex_renderer.py +252 -0
  148. src/agents/research_plan_author/contract_repair.py +131 -0
  149. src/agents/research_plan_author/contracts.py +745 -0
  150. src/agents/research_plan_author/cross_section_editor.py +232 -0
  151. src/agents/research_plan_author/document_quality.py +987 -0
  152. src/agents/research_plan_author/idea_evolution.py +223 -0
  153. src/agents/research_plan_author/input_loader.py +76 -0
  154. src/agents/research_plan_author/latex_compiler.py +260 -0
  155. src/agents/research_plan_author/latex_safety.py +159 -0
  156. src/agents/research_plan_author/llm_json.py +74 -0
  157. src/agents/research_plan_author/markdown_renderer.py +275 -0
  158. src/agents/research_plan_author/pdf_validator.py +149 -0
  159. src/agents/research_plan_author/render.py +166 -0
  160. src/agents/research_plan_author/render_artifacts.py +187 -0
  161. src/agents/research_plan_author/run.py +765 -0
  162. src/agents/research_plan_author/run_logging.py +209 -0
  163. src/agents/research_plan_author/section_cache.py +184 -0
  164. src/agents/research_plan_author/section_composer.py +1764 -0
  165. src/agents/research_plan_author/section_router.py +209 -0
  166. src/agents/research_plan_author/semantic_validator.py +254 -0
  167. src/agents/research_plan_author/source_bundle.py +46 -0
  168. src/agents/research_plan_author/source_registry.py +208 -0
  169. src/agents/research_plan_author/survey_source_loader.py +137 -0
  170. src/agents/research_plan_author/template_adapter.py +173 -0
  171. src/agents/research_plan_author/template_profile.py +204 -0
  172. src/agents/research_plan_author/tex_renderer.py +496 -0
  173. src/agents/research_plan_author/theory_presentation.py +134 -0
  174. src/agents/research_plan_author/theory_spine.py +703 -0
  175. src/agents/survey_agent/Dockerfile +54 -0
  176. src/agents/survey_agent/README.md +350 -0
  177. src/agents/survey_agent/config/deep_survey.yaml +217 -0
  178. src/agents/survey_agent/config/deep_survey_batch.yaml +176 -0
  179. src/agents/survey_agent/config/deep_survey_fast.yaml +183 -0
  180. src/agents/survey_agent/config/evaluate_baseline.yaml +197 -0
  181. src/agents/survey_agent/config/outcomeRAG.yaml +159 -0
  182. src/agents/survey_agent/env_report.md +348 -0
  183. src/agents/survey_agent/modules/__init__.py +0 -0
  184. src/agents/survey_agent/modules/code_collector.py +2194 -0
  185. src/agents/survey_agent/modules/code_report_generator.py +1282 -0
  186. src/agents/survey_agent/modules/data_manager.py +1713 -0
  187. src/agents/survey_agent/modules/database.py +167 -0
  188. src/agents/survey_agent/modules/fulltext_download_cache.py +462 -0
  189. src/agents/survey_agent/modules/fulltext_http.py +134 -0
  190. src/agents/survey_agent/modules/fulltext_resolution.py +749 -0
  191. src/agents/survey_agent/modules/judge.py +434 -0
  192. src/agents/survey_agent/modules/outcome_RAG.py +236 -0
  193. src/agents/survey_agent/modules/paper_graph_retriever.py +1075 -0
  194. src/agents/survey_agent/modules/pe.py +1729 -0
  195. src/agents/survey_agent/modules/pseudo_creator.py +782 -0
  196. src/agents/survey_agent/modules/pseudo_reviser.py +537 -0
  197. src/agents/survey_agent/modules/refine_agent.py +1952 -0
  198. src/agents/survey_agent/modules/survey_generator.py +5691 -0
  199. src/agents/survey_agent/modules/survey_visualizer.py +1817 -0
  200. src/agents/survey_agent/modules/visual_prompts.py +327 -0
  201. src/agents/survey_agent/modules/work_analyzer.py +2750 -0
  202. src/agents/survey_agent/modules/work_collector.py +5649 -0
  203. src/agents/survey_agent/scripts/baseline_evaluation.py +411 -0
  204. src/agents/survey_agent/scripts/run_deep_survey.py +224 -0
  205. src/agents/survey_agent/scripts/run_deep_survey_adapter.py +407 -0
  206. src/agents/survey_agent/scripts/run_deep_survey_adapter_with_input.py +409 -0
  207. src/agents/survey_agent/scripts/run_deep_survey_batch.py +228 -0
  208. src/agents/survey_agent/scripts/run_deep_survey_batch_arg.py +243 -0
  209. src/agents/survey_agent/scripts/run_deep_survey_batch_settings.py +218 -0
  210. src/agents/survey_agent/scripts/run_keynotes_gen.py +128 -0
  211. src/agents/survey_agent/scripts/run_work_clusters.py +140 -0
  212. src/agents/survey_agent/utils/__init__.py +0 -0
  213. src/agents/survey_agent/utils/ablation_utils.py +317 -0
  214. src/agents/survey_agent/utils/api_call.py +2518 -0
  215. src/agents/survey_agent/utils/chat_utils.py +82 -0
  216. src/agents/survey_agent/utils/config_utils.py +70 -0
  217. src/agents/survey_agent/utils/convert_to_md.py +29 -0
  218. src/agents/survey_agent/utils/err_info.py +24 -0
  219. src/agents/survey_agent/utils/file_utils.py +176 -0
  220. src/agents/survey_agent/utils/gpu_utils.py +145 -0
  221. src/agents/survey_agent/utils/graph_visualizer.py +279 -0
  222. src/agents/survey_agent/utils/html_utils.py +181 -0
  223. src/agents/survey_agent/utils/latex_generator_intra_cluster.py +529 -0
  224. src/agents/survey_agent/utils/latex_generator_relation_table.py +193 -0
  225. src/agents/survey_agent/utils/mineru_section_packer.py +334 -0
  226. src/agents/survey_agent/utils/mineru_utils.py +396 -0
  227. src/agents/survey_agent/utils/paper.py +144 -0
  228. src/agents/survey_agent/utils/read_yaml.py +15 -0
  229. src/agents/survey_agent/utils/repo_utils.py +167 -0
  230. src/agents/survey_agent/utils/rich_logger.py +77 -0
  231. src/agents/survey_agent/utils/step2v2.py +874 -0
  232. src/agents/survey_agent/utils/step2v2_extractor.py +856 -0
  233. src/agents/survey_agent/utils/topic_survey_storage.py +309 -0
  234. src/agents/survey_agent/utils/utils.py +118 -0
  235. src/agents/survey_agent/utils/work.py +2 -0
  236. src/cli.py +1611 -0
  237. src/config/__init__.py +213 -0
  238. src/config/default.yaml +1122 -0
  239. src/harness/LICENSE +21 -0
  240. src/harness/VENDORED.md +14 -0
  241. src/harness/src/__init__.py +0 -0
  242. src/harness/src/openharness/__init__.py +0 -0
  243. src/harness/src/openharness/__main__.py +6 -0
  244. src/harness/src/openharness/api/__init__.py +21 -0
  245. src/harness/src/openharness/api/client.py +281 -0
  246. src/harness/src/openharness/api/codex_client.py +407 -0
  247. src/harness/src/openharness/api/copilot_auth.py +240 -0
  248. src/harness/src/openharness/api/copilot_client.py +134 -0
  249. src/harness/src/openharness/api/errors.py +19 -0
  250. src/harness/src/openharness/api/openai_client.py +577 -0
  251. src/harness/src/openharness/api/provider.py +186 -0
  252. src/harness/src/openharness/api/registry.py +437 -0
  253. src/harness/src/openharness/api/usage.py +17 -0
  254. src/harness/src/openharness/auth/__init__.py +29 -0
  255. src/harness/src/openharness/auth/external.py +610 -0
  256. src/harness/src/openharness/auth/flows.py +189 -0
  257. src/harness/src/openharness/auth/manager.py +482 -0
  258. src/harness/src/openharness/auth/storage.py +269 -0
  259. src/harness/src/openharness/autopilot/__init__.py +23 -0
  260. src/harness/src/openharness/autopilot/service.py +2239 -0
  261. src/harness/src/openharness/autopilot/types.py +91 -0
  262. src/harness/src/openharness/bridge/__init__.py +20 -0
  263. src/harness/src/openharness/bridge/manager.py +106 -0
  264. src/harness/src/openharness/bridge/session_runner.py +46 -0
  265. src/harness/src/openharness/bridge/types.py +37 -0
  266. src/harness/src/openharness/bridge/work_secret.py +41 -0
  267. src/harness/src/openharness/channels/UPSTREAM +6 -0
  268. src/harness/src/openharness/channels/__init__.py +22 -0
  269. src/harness/src/openharness/channels/adapter.py +131 -0
  270. src/harness/src/openharness/channels/bus/__init__.py +6 -0
  271. src/harness/src/openharness/channels/bus/events.py +38 -0
  272. src/harness/src/openharness/channels/bus/queue.py +44 -0
  273. src/harness/src/openharness/channels/impl/__init__.py +6 -0
  274. src/harness/src/openharness/channels/impl/base.py +141 -0
  275. src/harness/src/openharness/channels/impl/dingtalk.py +445 -0
  276. src/harness/src/openharness/channels/impl/discord.py +311 -0
  277. src/harness/src/openharness/channels/impl/email.py +410 -0
  278. src/harness/src/openharness/channels/impl/feishu.py +1342 -0
  279. src/harness/src/openharness/channels/impl/manager.py +257 -0
  280. src/harness/src/openharness/channels/impl/matrix.py +700 -0
  281. src/harness/src/openharness/channels/impl/mochat.py +897 -0
  282. src/harness/src/openharness/channels/impl/qq.py +141 -0
  283. src/harness/src/openharness/channels/impl/slack.py +284 -0
  284. src/harness/src/openharness/channels/impl/telegram.py +525 -0
  285. src/harness/src/openharness/channels/impl/whatsapp.py +159 -0
  286. src/harness/src/openharness/cli.py +2551 -0
  287. src/harness/src/openharness/commands/__init__.py +21 -0
  288. src/harness/src/openharness/commands/registry.py +2783 -0
  289. src/harness/src/openharness/config/__init__.py +34 -0
  290. src/harness/src/openharness/config/paths.py +160 -0
  291. src/harness/src/openharness/config/schema.py +119 -0
  292. src/harness/src/openharness/config/settings.py +1104 -0
  293. src/harness/src/openharness/coordinator/__init__.py +12 -0
  294. src/harness/src/openharness/coordinator/agent_definitions.py +975 -0
  295. src/harness/src/openharness/coordinator/coordinator_mode.py +520 -0
  296. src/harness/src/openharness/engine/__init__.py +80 -0
  297. src/harness/src/openharness/engine/cost_tracker.py +24 -0
  298. src/harness/src/openharness/engine/messages.py +222 -0
  299. src/harness/src/openharness/engine/query.py +1188 -0
  300. src/harness/src/openharness/engine/query_engine.py +313 -0
  301. src/harness/src/openharness/engine/stream_events.py +90 -0
  302. src/harness/src/openharness/hooks/__init__.py +50 -0
  303. src/harness/src/openharness/hooks/events.py +20 -0
  304. src/harness/src/openharness/hooks/executor.py +242 -0
  305. src/harness/src/openharness/hooks/hot_reload.py +31 -0
  306. src/harness/src/openharness/hooks/loader.py +68 -0
  307. src/harness/src/openharness/hooks/schemas.py +66 -0
  308. src/harness/src/openharness/hooks/types.py +38 -0
  309. src/harness/src/openharness/keybindings/__init__.py +14 -0
  310. src/harness/src/openharness/keybindings/default_bindings.py +11 -0
  311. src/harness/src/openharness/keybindings/loader.py +22 -0
  312. src/harness/src/openharness/keybindings/parser.py +18 -0
  313. src/harness/src/openharness/keybindings/resolver.py +13 -0
  314. src/harness/src/openharness/mcp/__init__.py +79 -0
  315. src/harness/src/openharness/mcp/client.py +298 -0
  316. src/harness/src/openharness/mcp/config.py +16 -0
  317. src/harness/src/openharness/mcp/types.py +76 -0
  318. src/harness/src/openharness/memory/__init__.py +25 -0
  319. src/harness/src/openharness/memory/agent.py +91 -0
  320. src/harness/src/openharness/memory/manager.py +183 -0
  321. src/harness/src/openharness/memory/memdir.py +52 -0
  322. src/harness/src/openharness/memory/migrate.py +137 -0
  323. src/harness/src/openharness/memory/paths.py +22 -0
  324. src/harness/src/openharness/memory/relevance.py +145 -0
  325. src/harness/src/openharness/memory/scan.py +116 -0
  326. src/harness/src/openharness/memory/schema.py +443 -0
  327. src/harness/src/openharness/memory/search.py +71 -0
  328. src/harness/src/openharness/memory/team.py +90 -0
  329. src/harness/src/openharness/memory/types.py +33 -0
  330. src/harness/src/openharness/memory/usage.py +153 -0
  331. src/harness/src/openharness/output_styles/__init__.py +5 -0
  332. src/harness/src/openharness/output_styles/loader.py +42 -0
  333. src/harness/src/openharness/permissions/__init__.py +26 -0
  334. src/harness/src/openharness/permissions/checker.py +200 -0
  335. src/harness/src/openharness/permissions/modes.py +13 -0
  336. src/harness/src/openharness/personalization/__init__.py +6 -0
  337. src/harness/src/openharness/personalization/extractor.py +138 -0
  338. src/harness/src/openharness/personalization/rules.py +64 -0
  339. src/harness/src/openharness/personalization/session_hook.py +65 -0
  340. src/harness/src/openharness/platforms.py +86 -0
  341. src/harness/src/openharness/plugins/__init__.py +53 -0
  342. src/harness/src/openharness/plugins/bundled/__init__.py +0 -0
  343. src/harness/src/openharness/plugins/installer.py +39 -0
  344. src/harness/src/openharness/plugins/loader.py +730 -0
  345. src/harness/src/openharness/plugins/schemas.py +24 -0
  346. src/harness/src/openharness/plugins/types.py +59 -0
  347. src/harness/src/openharness/prompts/__init__.py +14 -0
  348. src/harness/src/openharness/prompts/claudemd.py +48 -0
  349. src/harness/src/openharness/prompts/context.py +187 -0
  350. src/harness/src/openharness/prompts/environment.py +135 -0
  351. src/harness/src/openharness/prompts/system_prompt.py +109 -0
  352. src/harness/src/openharness/sandbox/Dockerfile +6 -0
  353. src/harness/src/openharness/sandbox/__init__.py +33 -0
  354. src/harness/src/openharness/sandbox/adapter.py +148 -0
  355. src/harness/src/openharness/sandbox/docker_backend.py +232 -0
  356. src/harness/src/openharness/sandbox/docker_image.py +103 -0
  357. src/harness/src/openharness/sandbox/path_validator.py +37 -0
  358. src/harness/src/openharness/sandbox/session.py +63 -0
  359. src/harness/src/openharness/services/__init__.py +30 -0
  360. src/harness/src/openharness/services/autodream/__init__.py +34 -0
  361. src/harness/src/openharness/services/autodream/backup.py +104 -0
  362. src/harness/src/openharness/services/autodream/lock.py +138 -0
  363. src/harness/src/openharness/services/autodream/prompt.py +128 -0
  364. src/harness/src/openharness/services/autodream/service.py +313 -0
  365. src/harness/src/openharness/services/compact/__init__.py +1725 -0
  366. src/harness/src/openharness/services/cron.py +137 -0
  367. src/harness/src/openharness/services/cron_scheduler.py +595 -0
  368. src/harness/src/openharness/services/lsp/__init__.py +216 -0
  369. src/harness/src/openharness/services/memory_extract/__init__.py +261 -0
  370. src/harness/src/openharness/services/oauth/__init__.py +0 -0
  371. src/harness/src/openharness/services/session_backend.py +97 -0
  372. src/harness/src/openharness/services/session_memory/__init__.py +139 -0
  373. src/harness/src/openharness/services/session_storage.py +230 -0
  374. src/harness/src/openharness/services/token_estimation.py +15 -0
  375. src/harness/src/openharness/services/tool_outputs.py +55 -0
  376. src/harness/src/openharness/skills/__init__.py +44 -0
  377. src/harness/src/openharness/skills/_frontmatter.py +97 -0
  378. src/harness/src/openharness/skills/bundled/__init__.py +75 -0
  379. src/harness/src/openharness/skills/bundled/content/commit.md +25 -0
  380. src/harness/src/openharness/skills/bundled/content/debug.md +25 -0
  381. src/harness/src/openharness/skills/bundled/content/diagnose.md +35 -0
  382. src/harness/src/openharness/skills/bundled/content/plan.md +39 -0
  383. src/harness/src/openharness/skills/bundled/content/review.md +33 -0
  384. src/harness/src/openharness/skills/bundled/content/simplify.md +26 -0
  385. src/harness/src/openharness/skills/bundled/content/skill-creator.md +175 -0
  386. src/harness/src/openharness/skills/bundled/content/test.md +38 -0
  387. src/harness/src/openharness/skills/loader.py +229 -0
  388. src/harness/src/openharness/skills/registry.py +29 -0
  389. src/harness/src/openharness/skills/types.py +24 -0
  390. src/harness/src/openharness/state/__init__.py +6 -0
  391. src/harness/src/openharness/state/app_state.py +30 -0
  392. src/harness/src/openharness/state/store.py +40 -0
  393. src/harness/src/openharness/swarm/__init__.py +70 -0
  394. src/harness/src/openharness/swarm/in_process.py +693 -0
  395. src/harness/src/openharness/swarm/lockfile.py +24 -0
  396. src/harness/src/openharness/swarm/mailbox.py +522 -0
  397. src/harness/src/openharness/swarm/permission_sync.py +1168 -0
  398. src/harness/src/openharness/swarm/registry.py +410 -0
  399. src/harness/src/openharness/swarm/spawn_utils.py +229 -0
  400. src/harness/src/openharness/swarm/subprocess_backend.py +171 -0
  401. src/harness/src/openharness/swarm/team_lifecycle.py +910 -0
  402. src/harness/src/openharness/swarm/types.py +398 -0
  403. src/harness/src/openharness/swarm/worktree.py +315 -0
  404. src/harness/src/openharness/tasks/__init__.py +18 -0
  405. src/harness/src/openharness/tasks/local_agent_task.py +28 -0
  406. src/harness/src/openharness/tasks/local_shell_task.py +17 -0
  407. src/harness/src/openharness/tasks/manager.py +475 -0
  408. src/harness/src/openharness/tasks/stop_task.py +11 -0
  409. src/harness/src/openharness/tasks/types.py +32 -0
  410. src/harness/src/openharness/themes/__init__.py +21 -0
  411. src/harness/src/openharness/themes/builtin.py +89 -0
  412. src/harness/src/openharness/themes/loader.py +55 -0
  413. src/harness/src/openharness/themes/schema.py +54 -0
  414. src/harness/src/openharness/tools/__init__.py +107 -0
  415. src/harness/src/openharness/tools/agent_tool.py +141 -0
  416. src/harness/src/openharness/tools/ask_user_question_tool.py +46 -0
  417. src/harness/src/openharness/tools/base.py +80 -0
  418. src/harness/src/openharness/tools/bash_tool.py +259 -0
  419. src/harness/src/openharness/tools/brief_tool.py +33 -0
  420. src/harness/src/openharness/tools/config_tool.py +54 -0
  421. src/harness/src/openharness/tools/cron_create_tool.py +105 -0
  422. src/harness/src/openharness/tools/cron_delete_tool.py +32 -0
  423. src/harness/src/openharness/tools/cron_list_tool.py +69 -0
  424. src/harness/src/openharness/tools/cron_toggle_tool.py +37 -0
  425. src/harness/src/openharness/tools/enter_plan_mode_tool.py +28 -0
  426. src/harness/src/openharness/tools/enter_worktree_tool.py +80 -0
  427. src/harness/src/openharness/tools/exit_plan_mode_tool.py +28 -0
  428. src/harness/src/openharness/tools/exit_worktree_tool.py +42 -0
  429. src/harness/src/openharness/tools/file_edit_tool.py +95 -0
  430. src/harness/src/openharness/tools/file_read_tool.py +72 -0
  431. src/harness/src/openharness/tools/file_write_tool.py +87 -0
  432. src/harness/src/openharness/tools/glob_tool.py +175 -0
  433. src/harness/src/openharness/tools/grep_tool.py +374 -0
  434. src/harness/src/openharness/tools/image_generation_tool.py +447 -0
  435. src/harness/src/openharness/tools/image_to_text_tool.py +237 -0
  436. src/harness/src/openharness/tools/list_mcp_resources_tool.py +36 -0
  437. src/harness/src/openharness/tools/lsp_tool.py +154 -0
  438. src/harness/src/openharness/tools/mcp_auth_tool.py +71 -0
  439. src/harness/src/openharness/tools/mcp_tool.py +72 -0
  440. src/harness/src/openharness/tools/notebook_edit_tool.py +97 -0
  441. src/harness/src/openharness/tools/read_mcp_resource_tool.py +38 -0
  442. src/harness/src/openharness/tools/remote_trigger_tool.py +74 -0
  443. src/harness/src/openharness/tools/send_message_tool.py +58 -0
  444. src/harness/src/openharness/tools/skill_tool.py +43 -0
  445. src/harness/src/openharness/tools/sleep_tool.py +32 -0
  446. src/harness/src/openharness/tools/task_create_tool.py +56 -0
  447. src/harness/src/openharness/tools/task_get_tool.py +33 -0
  448. src/harness/src/openharness/tools/task_list_tool.py +35 -0
  449. src/harness/src/openharness/tools/task_output_tool.py +35 -0
  450. src/harness/src/openharness/tools/task_stop_tool.py +30 -0
  451. src/harness/src/openharness/tools/task_update_tool.py +50 -0
  452. src/harness/src/openharness/tools/team_create_tool.py +31 -0
  453. src/harness/src/openharness/tools/team_delete_tool.py +30 -0
  454. src/harness/src/openharness/tools/todo_write_tool.py +46 -0
  455. src/harness/src/openharness/tools/tool_search_tool.py +38 -0
  456. src/harness/src/openharness/tools/web_fetch_tool.py +117 -0
  457. src/harness/src/openharness/tools/web_search_tool.py +119 -0
  458. src/harness/src/openharness/ui/__init__.py +5 -0
  459. src/harness/src/openharness/ui/app.py +320 -0
  460. src/harness/src/openharness/ui/backend_host.py +941 -0
  461. src/harness/src/openharness/ui/coordinator_drain.py +198 -0
  462. src/harness/src/openharness/ui/input.py +32 -0
  463. src/harness/src/openharness/ui/output.py +265 -0
  464. src/harness/src/openharness/ui/permission_dialog.py +14 -0
  465. src/harness/src/openharness/ui/protocol.py +247 -0
  466. src/harness/src/openharness/ui/react_launcher.py +179 -0
  467. src/harness/src/openharness/ui/runtime.py +799 -0
  468. src/harness/src/openharness/ui/textual_app.py +495 -0
  469. src/harness/src/openharness/utils/__init__.py +0 -0
  470. src/harness/src/openharness/utils/file_lock.py +86 -0
  471. src/harness/src/openharness/utils/fs.py +98 -0
  472. src/harness/src/openharness/utils/helpers.py +77 -0
  473. src/harness/src/openharness/utils/network_guard.py +340 -0
  474. src/harness/src/openharness/utils/shell.py +147 -0
  475. src/harness/src/openharness/vim/__init__.py +5 -0
  476. src/harness/src/openharness/vim/transitions.py +8 -0
  477. src/harness/src/openharness/voice/__init__.py +7 -0
  478. src/harness/src/openharness/voice/keyterms.py +10 -0
  479. src/harness/src/openharness/voice/stream_stt.py +8 -0
  480. src/harness/src/openharness/voice/voice_mode.py +44 -0
  481. src/llm/__init__.py +29 -0
  482. src/llm/image_generation.py +402 -0
  483. src/llm/provider_registry.py +419 -0
  484. src/llm/runtime_env.py +64 -0
  485. src/llm/structured_output.py +131 -0
  486. src/llm/vision.py +198 -0
  487. src/memory/__init__.py +2 -0
  488. src/memory/api/__init__.py +1 -0
  489. src/memory/api/base_memory_system_api.py +98 -0
  490. src/memory/api/base_symbolic_memory_system_api.py +190 -0
  491. src/memory/api/base_vector_memory_system_api.py +116 -0
  492. src/memory/api/faiss_memory_system_api.py +228 -0
  493. src/memory/api/slot_process_api.py +827 -0
  494. src/memory/api/symbolic_memory_system_api.py +572 -0
  495. src/memory/memory_system/__init__.py +48 -0
  496. src/memory/memory_system/component_taxonomy.py +430 -0
  497. src/memory/memory_system/llm.py +437 -0
  498. src/memory/memory_system/models.py +168 -0
  499. src/memory/memory_system/user_prompt.py +418 -0
  500. src/memory/memory_system/utils.py +282 -0
  501. src/memory/memory_system/vectorstore.py +311 -0
  502. src/memory/memory_system/working_slot.py +99 -0
  503. src/pipeline/__init__.py +129 -0
  504. src/pipeline/contracts.py +531 -0
  505. src/pipeline/discipline_taxonomy.py +916 -0
  506. src/pipeline/evidence_coverage_ledger.py +1150 -0
  507. src/pipeline/evidence_refinement.py +620 -0
  508. src/pipeline/experiment_to_symbolic.py +137 -0
  509. src/pipeline/multimodal_evidence/__init__.py +28 -0
  510. src/pipeline/multimodal_evidence/capabilities.py +107 -0
  511. src/pipeline/multimodal_evidence/claims.py +135 -0
  512. src/pipeline/multimodal_evidence/contract.py +291 -0
  513. src/pipeline/multimodal_evidence/data_sh_compiler.py +505 -0
  514. src/pipeline/multimodal_evidence/handoff_binding.py +135 -0
  515. src/pipeline/multimodal_evidence/manifest.py +254 -0
  516. src/pipeline/multimodal_evidence/native_analysis.py +479 -0
  517. src/pipeline/multimodal_evidence/perception.py +277 -0
  518. src/pipeline/multimodal_evidence/query_binding.py +65 -0
  519. src/pipeline/multimodal_evidence/rendering.py +120 -0
  520. src/pipeline/multimodal_evidence/safety.py +42 -0
  521. src/pipeline/multimodal_evidence/sampling.py +69 -0
  522. src/pipeline/multimodal_evidence/service.py +148 -0
  523. src/pipeline/multimodal_evidence/survey_integration.py +460 -0
  524. src/pipeline/paper_identity.py +52 -0
  525. src/pipeline/research_design_inventory.py +338 -0
  526. src/pipeline/research_domain_resolution.py +669 -0
  527. src/pipeline/research_identity.py +548 -0
  528. src/pipeline/research_question_contract.py +646 -0
  529. src/pipeline/retrieval_lanes.py +889 -0
  530. src/pipeline/run_loop.py +862 -0
  531. src/pipeline/science_events.py +30 -0
  532. src/pipeline/science_manifests.py +602 -0
  533. src/pipeline/science_run.py +918 -0
  534. src/pipeline/science_stages.py +709 -0
  535. src/pipeline/science_workflow.py +807 -0
  536. src/pipeline/sh_cluster_projection.py +306 -0
  537. src/pipeline/sh_graph_provenance.py +648 -0
  538. src/pipeline/subhypothesis_decomposition.py +392 -0
  539. src/pipeline/subhypothesis_evidence.py +856 -0
  540. src/pipeline/survey_evidence_plan.py +858 -0
  541. src/pipeline/survey_gap_adjudication.py +647 -0
  542. src/pipeline/survey_gap_candidates.py +840 -0
  543. src/pipeline/survey_gap_ledger.py +683 -0
  544. src/pipeline/survey_gap_triage.py +401 -0
  545. src/pipeline/survey_handoff_persistence.py +481 -0
  546. src/pipeline/survey_handoff_projection.py +457 -0
  547. src/pipeline/survey_idea_handoff.py +1112 -0
  548. src/pipeline/survey_idea_loader.py +289 -0
  549. src/research_run_ids.py +11 -0
@@ -0,0 +1,208 @@
1
+ """Build a global, source-bounded knowledge base for Research Plan Authoring."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from copy import deepcopy
6
+ import hashlib
7
+ import json
8
+ from typing import Any, Mapping
9
+
10
+
11
+ def _mapping(value: object) -> dict[str, Any]:
12
+ return dict(value) if isinstance(value, Mapping) else {}
13
+
14
+
15
+ def _text(value: object) -> str:
16
+ return str(value or "").strip()
17
+
18
+
19
+ def _append_unique(records: list[dict[str, Any]], record: dict[str, Any], *, key: str) -> None:
20
+ value = _text(record.get(key))
21
+ if value and not any(_text(existing.get(key)) == value for existing in records):
22
+ records.append(record)
23
+
24
+
25
+ def _citation_key(source_id: str) -> str:
26
+ """Keep BibTeX keys stable and syntax-safe without exposing a raw URL/DOI."""
27
+
28
+ digest = hashlib.sha256(source_id.encode("utf-8")).hexdigest()[:16]
29
+ return f"cite_{digest}"
30
+
31
+
32
+ def _source_items(items: object, *, prefix: str) -> list[dict[str, Any]]:
33
+ values = items if isinstance(items, list) else []
34
+ records: list[dict[str, Any]] = []
35
+ for index, item in enumerate(values, start=1):
36
+ canonical = json.dumps(item, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False)
37
+ records.append(
38
+ {
39
+ "source_item_id": f"{prefix}-{index}",
40
+ "source_item_sha256": hashlib.sha256(canonical.encode("utf-8")).hexdigest(),
41
+ "original_item": deepcopy(item),
42
+ }
43
+ )
44
+ return records
45
+
46
+
47
+ def build_frozen_source_registry(preparation: Mapping[str, Any]) -> dict[str, Any]:
48
+ """Expose every already-known source; an LLM cannot mint bibliography data."""
49
+
50
+ bundle = _mapping(preparation.get("source_bundle"))
51
+ handoff = _mapping(bundle.get("author_context"))
52
+ compact = _mapping(handoff.get("source_registry"))
53
+ evidence_cards_by_id = {
54
+ str(card_id): deepcopy(dict(card))
55
+ for card_id, card in (_mapping(compact.get("evidence_cards_by_id"))).items()
56
+ if isinstance(card, Mapping)
57
+ }
58
+ allowed_source_ids = [
59
+ _text(source_id)
60
+ for source_id in compact.get("allowed_source_ids") or []
61
+ if _text(source_id)
62
+ ]
63
+ citations = [
64
+ deepcopy(dict(citation))
65
+ for citation in compact.get("citation_registry") or []
66
+ if isinstance(citation, Mapping)
67
+ ]
68
+ survey_sources = _mapping(bundle.get("survey_sources"))
69
+ survey_artifacts = _mapping(survey_sources.get("artifacts"))
70
+ survey_anchor_ids = [f"survey:{name}" for name in sorted(survey_artifacts)]
71
+ survey_markdown = _mapping(survey_artifacts.get("survey_markdown"))
72
+ for excerpt in survey_markdown.get("excerpts") or []:
73
+ if not isinstance(excerpt, Mapping):
74
+ continue
75
+ anchor_id = _text(excerpt.get("anchor_id"))
76
+ if anchor_id.startswith("survey:survey_markdown#") and anchor_id not in survey_anchor_ids:
77
+ survey_anchor_ids.append(anchor_id)
78
+ unknown_items = _source_items(handoff.get("unknown_items"), prefix="unknown")
79
+ review_items = _source_items(handoff.get("review_items"), prefix="review")
80
+ return {
81
+ "schema_version": "research_plan_author_source_registry_v3",
82
+ "allowed_source_ids": allowed_source_ids,
83
+ "allowed_survey_anchor_ids": survey_anchor_ids,
84
+ "evidence_cards_by_id": evidence_cards_by_id,
85
+ "citation_registry": deepcopy(citations),
86
+ "unknown_items": unknown_items,
87
+ "review_items": review_items,
88
+ }
89
+
90
+
91
+ _RECOMMENDED_SLOTS_BY_SECTION = {
92
+ "introduction": {"research_object_measurability", "mechanism", "boundary_conditions"},
93
+ "survey_and_research_gap": {"research_object_measurability", "mechanism", "boundary_conditions"},
94
+ "study_design_and_methods": {
95
+ "study_design",
96
+ "measurement_calibration",
97
+ "comparison_controls",
98
+ "statistics_bias",
99
+ },
100
+ "computational_evaluation_protocol": {
101
+ "study_design",
102
+ "measurement_calibration",
103
+ "comparison_controls",
104
+ "statistics_bias",
105
+ },
106
+ "expected_outcomes": {"mechanism", "comparison_controls", "statistics_bias"},
107
+ "risk_limitations_and_review": {"risk_ethics_reproducibility", "boundary_conditions"},
108
+ "appendix_evidence_and_review": None,
109
+ }
110
+
111
+
112
+ def build_authoring_knowledge_base(
113
+ preparation: Mapping[str, Any],
114
+ source_registry: Mapping[str, Any],
115
+ ) -> dict[str, Any]:
116
+ """Assemble every frozen upstream artifact into one writer-visible knowledge base.
117
+
118
+ The knowledge base is deliberately broader than a route recommendation. It
119
+ preserves provenance by exposing canonical IDs and metadata, but it never
120
+ turns an evidence slot into a permission boundary for scholarly synthesis.
121
+ """
122
+
123
+ bundle = _mapping(preparation.get("source_bundle"))
124
+ author_context = _mapping(bundle.get("author_context"))
125
+ return {
126
+ "schema_version": "research_plan_authoring_knowledge_base_v1",
127
+ "theory_spine": deepcopy(_mapping(preparation.get("theory_spine"))),
128
+ "source_catalog": {
129
+ "allowed_source_ids": list(source_registry.get("allowed_source_ids") or []),
130
+ "citation_registry": deepcopy(list(source_registry.get("citation_registry") or [])),
131
+ "evidence_cards_by_id": deepcopy(_mapping(source_registry.get("evidence_cards_by_id"))),
132
+ "survey_anchor_ids": list(source_registry.get("allowed_survey_anchor_ids") or []),
133
+ },
134
+ "upstream_artifacts": {
135
+ "selected_direction": deepcopy(author_context.get("selected_direction")),
136
+ "research_design": deepcopy(author_context.get("research_design")),
137
+ "hypothesis_mapping": deepcopy(author_context.get("hypothesis_mapping")),
138
+ "formal_reasoning": deepcopy(author_context.get("formal_reasoning")),
139
+ "counterexample_analysis": deepcopy(author_context.get("counterexample_analysis")),
140
+ "outcome_branches": deepcopy(author_context.get("outcome_branches")),
141
+ "reasoning_context": deepcopy(author_context.get("reasoning_context")),
142
+ "variables_and_operationalization": deepcopy(author_context.get("variables_and_operationalization")),
143
+ "idea_evolution": deepcopy(_mapping(bundle.get("idea_evolution"))),
144
+ "survey_binding": deepcopy(_mapping(bundle.get("survey_binding"))),
145
+ },
146
+ "unknown_items": deepcopy(list(source_registry.get("unknown_items") or [])),
147
+ "review_items": deepcopy(list(source_registry.get("review_items") or [])),
148
+ }
149
+
150
+
151
+ def source_registry_for_route(source_registry: Mapping[str, Any], route: Mapping[str, Any]) -> dict[str, Any]:
152
+ """Return the global catalog plus non-binding relevance recommendations.
153
+
154
+ Earlier versions used ``claim_slot`` to hide sources from most sections.
155
+ That made source traceability a writing prohibition and caused shallow,
156
+ repetitive prose. The writer may now use any canonical source; slots only
157
+ help it start from a sensible evidence cluster.
158
+ """
159
+
160
+ section_id = _text(route.get("section_id")).casefold()
161
+ recommended_slots = _RECOMMENDED_SLOTS_BY_SECTION.get(section_id, set())
162
+ cards = _mapping(source_registry.get("evidence_cards_by_id"))
163
+ if recommended_slots is None:
164
+ recommended_source_ids = list(source_registry.get("allowed_source_ids") or [])
165
+ else:
166
+ recommended_source_ids = sorted(
167
+ {
168
+ _text(_mapping(card).get("source_id"))
169
+ for card in cards.values()
170
+ if _text(_mapping(card).get("source_id"))
171
+ and _text(_mapping(card).get("claim_slot")).casefold() in recommended_slots
172
+ }
173
+ )
174
+ return {
175
+ "schema_version": _text(source_registry.get("schema_version")),
176
+ "allowed_source_ids": list(source_registry.get("allowed_source_ids") or []),
177
+ "allowed_survey_anchor_ids": list(source_registry.get("allowed_survey_anchor_ids") or []),
178
+ "evidence_cards_by_id": deepcopy(cards),
179
+ "citation_registry": deepcopy(list(source_registry.get("citation_registry") or [])),
180
+ "unknown_items": deepcopy(list(source_registry.get("unknown_items") or [])),
181
+ "review_items": deepcopy(list(source_registry.get("review_items") or [])),
182
+ "recommended_source_ids": recommended_source_ids,
183
+ "recommended_claim_slots": sorted(recommended_slots or set()),
184
+ "authoring_knowledge_base": deepcopy(_mapping(source_registry.get("authoring_knowledge_base"))),
185
+ }
186
+
187
+
188
+ def source_registry_for_blueprint_section(
189
+ source_registry: Mapping[str, Any],
190
+ route: Mapping[str, Any],
191
+ blueprint_section: Mapping[str, Any],
192
+ ) -> dict[str, Any]:
193
+ """Preserve global source access after Blueprint planning.
194
+
195
+ ``allowed_source_ids`` in historic Blueprint artifacts is retained only for
196
+ cache compatibility and observability. It must never narrow the sources
197
+ available to the section writer.
198
+ """
199
+
200
+ return source_registry_for_route(source_registry, route)
201
+
202
+
203
+ __all__ = [
204
+ "build_frozen_source_registry",
205
+ "build_authoring_knowledge_base",
206
+ "source_registry_for_blueprint_section",
207
+ "source_registry_for_route",
208
+ ]
@@ -0,0 +1,137 @@
1
+ """Load the Survey artifacts required by a complete Research Plan Author run."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import re
7
+ from pathlib import Path
8
+ from typing import Any, Mapping
9
+
10
+ from src.pipeline.survey_idea_loader import SurveyIdeaLoadError, load_survey_idea_context
11
+
12
+
13
+ class SurveyAuthorSourceError(ValueError):
14
+ """Raised when verified Survey sources cannot be used for Author writing."""
15
+
16
+
17
+ _REQUIRED_ARTIFACTS = (
18
+ "idea_handoff",
19
+ "gap_ledger",
20
+ "project_context",
21
+ "survey_json",
22
+ "survey_markdown",
23
+ "claim_traceability",
24
+ "evidence_plan",
25
+ )
26
+ _MARKDOWN_HEADING = re.compile(r"^#{1,6}\s+.+?\s*$", re.MULTILINE)
27
+
28
+
29
+ def _mapping(value: object) -> dict[str, Any]:
30
+ return dict(value) if isinstance(value, Mapping) else {}
31
+
32
+
33
+ def _text(value: object) -> str:
34
+ return str(value or "").strip()
35
+
36
+
37
+ def _safe_artifact_path(base_dir: Path, entry: Mapping[str, Any], name: str) -> Path:
38
+ relative_path = _text(entry.get("path"))
39
+ if not relative_path:
40
+ raise SurveyAuthorSourceError(f"Survey manifest is missing artifact entry: {name}")
41
+ candidate = (base_dir / relative_path).resolve()
42
+ try:
43
+ candidate.relative_to(base_dir)
44
+ except ValueError as error:
45
+ raise SurveyAuthorSourceError(f"Survey artifact path escapes manifest directory: {name}") from error
46
+ if not candidate.is_file():
47
+ raise SurveyAuthorSourceError(f"Survey artifact is missing: {candidate}")
48
+ return candidate
49
+
50
+
51
+ def _read_json(path: Path, name: str) -> dict[str, Any]:
52
+ try:
53
+ payload = json.loads(path.read_text(encoding="utf-8"))
54
+ except OSError as error:
55
+ raise SurveyAuthorSourceError(f"Cannot read Survey artifact '{name}': {error}") from error
56
+ except json.JSONDecodeError as error:
57
+ raise SurveyAuthorSourceError(f"Survey artifact '{name}' is not valid JSON: {error}") from error
58
+ if not isinstance(payload, Mapping):
59
+ raise SurveyAuthorSourceError(f"Survey artifact '{name}' must contain one JSON object")
60
+ return dict(payload)
61
+
62
+
63
+ def _survey_markdown_excerpts(markdown: str) -> list[dict[str, Any]]:
64
+ """Split verified Survey Markdown into complete, source-addressable excerpts."""
65
+
66
+ starts = [match.start() for match in _MARKDOWN_HEADING.finditer(markdown)]
67
+ boundaries = [0, *starts] if starts and starts[0] else starts
68
+ if not boundaries:
69
+ boundaries = [0] if markdown.strip() else []
70
+ excerpts: list[dict[str, Any]] = []
71
+ for ordinal, start in enumerate(boundaries, start=1):
72
+ end = boundaries[ordinal] if ordinal < len(boundaries) else len(markdown)
73
+ text = markdown[start:end].strip()
74
+ if not text:
75
+ continue
76
+ first_line = text.splitlines()[0].strip()
77
+ heading = first_line.lstrip("#").strip() if first_line.startswith("#") else "Survey preamble"
78
+ excerpts.append(
79
+ {
80
+ "anchor_id": f"survey:survey_markdown#section-{ordinal:03d}",
81
+ "heading": heading,
82
+ "ordinal": ordinal,
83
+ "text": text,
84
+ }
85
+ )
86
+ return excerpts
87
+
88
+
89
+ def load_verified_survey_sources(source: str | Path) -> dict[str, Any]:
90
+ """Return a verified, source-addressable Survey bundle for Author composition."""
91
+
92
+ try:
93
+ context = load_survey_idea_context(source)
94
+ except SurveyIdeaLoadError as error:
95
+ raise SurveyAuthorSourceError(str(error)) from error
96
+ if context.legacy:
97
+ raise SurveyAuthorSourceError(
98
+ "Research Plan Author requires a completed, verified survey_manifest.json; legacy Survey directories are unsupported"
99
+ )
100
+ artifact_entries = _mapping(context.manifest.get("artifacts"))
101
+ artifact_paths: dict[str, str] = {}
102
+ artifacts: dict[str, Any] = {}
103
+ for name in _REQUIRED_ARTIFACTS:
104
+ path = _safe_artifact_path(context.base_dir, _mapping(artifact_entries.get(name)), name)
105
+ artifact_paths[name] = str(path)
106
+ if name == "survey_markdown":
107
+ artifacts[name] = {
108
+ "path": str(path),
109
+ "content_available": True,
110
+ "excerpts": _survey_markdown_excerpts(context.survey_markdown),
111
+ }
112
+ else:
113
+ artifacts[name] = _read_json(path, name)
114
+ for name, raw_entry in artifact_entries.items():
115
+ if name in artifacts:
116
+ continue
117
+ entry = _mapping(raw_entry)
118
+ relative_path = _text(entry.get("path"))
119
+ if not relative_path:
120
+ continue
121
+ optional_path = _safe_artifact_path(context.base_dir, entry, str(name))
122
+ artifact_paths[str(name)] = str(optional_path)
123
+ return {
124
+ "schema_version": "research_plan_author_survey_sources_v1",
125
+ "manifest_path": str(context.manifest_path),
126
+ "base_dir": str(context.base_dir),
127
+ "survey_run_id": context.survey_run_id,
128
+ "project_id": context.project_id,
129
+ "project_context_fingerprint": context.project_context_fingerprint,
130
+ "topic": context.topic,
131
+ "manifest": dict(context.manifest),
132
+ "artifacts": artifacts,
133
+ "artifact_paths": artifact_paths,
134
+ }
135
+
136
+
137
+ __all__ = ["SurveyAuthorSourceError", "load_verified_survey_sources"]
@@ -0,0 +1,173 @@
1
+ """Copy and adapt declared LaTeX templates without changing their source tree."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ import os
7
+ from pathlib import Path
8
+ import shutil
9
+ import tempfile
10
+ from typing import Mapping
11
+
12
+ from .template_profile import TemplateInsertion, TemplateProfile
13
+
14
+
15
+ class TemplateAdapterError(RuntimeError):
16
+ """Raised when an explicit template cannot be copied or adapted safely."""
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class MaterializedTemplate:
21
+ profile_id: str
22
+ source_dir: Path
23
+ project_dir: Path
24
+ main_tex: Path
25
+ generated_bib: Path
26
+
27
+
28
+ def _resolve_relative(root: Path, value: str, *, label: str) -> Path:
29
+ relative = Path(value)
30
+ if relative.is_absolute() or ".." in relative.parts:
31
+ raise TemplateAdapterError(f"{label} must be a relative path inside the template")
32
+ candidate = (root / relative).resolve()
33
+ try:
34
+ candidate.relative_to(root)
35
+ except ValueError as error:
36
+ raise TemplateAdapterError(f"{label} escapes the template root") from error
37
+ return candidate
38
+
39
+
40
+ def _reject_symlinks(root: Path) -> None:
41
+ for candidate in root.rglob("*"):
42
+ if candidate.is_symlink():
43
+ raise TemplateAdapterError(
44
+ f"template contains unsupported symlink '{candidate.relative_to(root)}'; copy real supporting files instead"
45
+ )
46
+
47
+
48
+ def _find_exact(content: str, needle: str, *, label: str) -> int:
49
+ count = content.count(needle)
50
+ if count != 1:
51
+ raise TemplateAdapterError(
52
+ f"template insertion '{label}' must occur exactly once; found {count} occurrences"
53
+ )
54
+ return content.index(needle)
55
+
56
+
57
+ def _replacement_span(content: str, insertion: TemplateInsertion, *, label: str) -> tuple[int, int]:
58
+ if insertion.kind == "marker":
59
+ start = _find_exact(content, insertion.marker, label=label)
60
+ return start, start + len(insertion.marker)
61
+ if insertion.kind != "region":
62
+ raise TemplateAdapterError(f"template insertion '{label}' has unsupported kind '{insertion.kind}'")
63
+ start_anchor = _find_exact(content, insertion.start, label=f"{label}.start")
64
+ end_anchor = _find_exact(content, insertion.end, label=f"{label}.end")
65
+ if end_anchor <= start_anchor:
66
+ raise TemplateAdapterError(f"template insertion '{label}' end anchor must follow its start anchor")
67
+ start = start_anchor if insertion.include_start else start_anchor + len(insertion.start)
68
+ end = end_anchor + len(insertion.end) if insertion.include_end else end_anchor
69
+ if end <= start:
70
+ raise TemplateAdapterError(f"template insertion '{label}' resolves to an empty or reversed span")
71
+ return start, end
72
+
73
+
74
+ class TemplateAdapter:
75
+ """Materialize a user-owned template and apply one prevalidated replacement set."""
76
+
77
+ def materialize(
78
+ self,
79
+ template_dir: str | Path,
80
+ project_dir: str | Path,
81
+ profile: TemplateProfile,
82
+ ) -> MaterializedTemplate:
83
+ source_dir = Path(template_dir).expanduser().resolve()
84
+ destination = Path(project_dir).expanduser().resolve()
85
+ if not source_dir.is_dir():
86
+ raise TemplateAdapterError(f"template directory does not exist: {source_dir}")
87
+ _reject_symlinks(source_dir)
88
+ source_main = _resolve_relative(source_dir, profile.main_tex, label="template main TeX path")
89
+ if not source_main.is_file():
90
+ raise TemplateAdapterError(
91
+ f"template profile '{profile.profile_id}' main TeX file does not exist: {profile.main_tex}"
92
+ )
93
+ reserved_destination = False
94
+ try:
95
+ destination.parent.mkdir(parents=True, exist_ok=True)
96
+ destination.mkdir(exist_ok=False)
97
+ reserved_destination = True
98
+ shutil.copytree(source_dir, destination, dirs_exist_ok=True)
99
+ except OSError as error:
100
+ try:
101
+ if reserved_destination and destination.exists():
102
+ shutil.rmtree(destination, ignore_errors=True)
103
+ except OSError:
104
+ pass
105
+ raise TemplateAdapterError(f"cannot copy template to output-owned project: {error}") from error
106
+ return MaterializedTemplate(
107
+ profile_id=profile.profile_id,
108
+ source_dir=source_dir,
109
+ project_dir=destination,
110
+ main_tex=_resolve_relative(destination, profile.main_tex, label="template main TeX path"),
111
+ generated_bib=_resolve_relative(destination, profile.generated_bib, label="generated BibTeX path"),
112
+ )
113
+
114
+ def apply(
115
+ self,
116
+ materialized: MaterializedTemplate,
117
+ profile: TemplateProfile,
118
+ replacements: Mapping[str, str],
119
+ ) -> Path:
120
+ required = set(profile.insertions)
121
+ missing = sorted(required - set(replacements))
122
+ unexpected = sorted(set(replacements) - required)
123
+ if missing or unexpected:
124
+ details: list[str] = []
125
+ if missing:
126
+ details.append("missing replacements: " + ", ".join(missing))
127
+ if unexpected:
128
+ details.append("unexpected replacements: " + ", ".join(unexpected))
129
+ raise TemplateAdapterError("; ".join(details))
130
+ try:
131
+ content = materialized.main_tex.read_text(encoding="utf-8")
132
+ except OSError as error:
133
+ raise TemplateAdapterError(f"cannot read copied template main TeX file: {error}") from error
134
+ spans: list[tuple[int, int, str, str]] = []
135
+ for label, insertion in profile.insertions.items():
136
+ start, end = _replacement_span(content, insertion, label=label)
137
+ spans.append((start, end, label, str(replacements[label])))
138
+ for index, insertion in enumerate(profile.discard_regions):
139
+ start, end = _replacement_span(content, insertion, label=f"discard_regions[{index}]")
140
+ spans.append((start, end, f"discard_regions[{index}]", ""))
141
+ ordered = sorted(spans)
142
+ for previous, current in zip(ordered, ordered[1:]):
143
+ if previous[1] > current[0]:
144
+ raise TemplateAdapterError(
145
+ f"template profile '{profile.profile_id}' contains overlapping insertion regions "
146
+ f"('{previous[2]}' and '{current[2]}')"
147
+ )
148
+ rendered = content
149
+ for start, end, _label, replacement in sorted(spans, reverse=True):
150
+ rendered = rendered[:start] + replacement + rendered[end:]
151
+ temporary: Path | None = None
152
+ try:
153
+ descriptor, raw_path = tempfile.mkstemp(
154
+ prefix=f".{materialized.main_tex.name}.",
155
+ suffix=".tmp",
156
+ dir=materialized.main_tex.parent,
157
+ text=True,
158
+ )
159
+ temporary = Path(raw_path)
160
+ with os.fdopen(descriptor, "w", encoding="utf-8", newline="\n") as handle:
161
+ handle.write(rendered)
162
+ handle.flush()
163
+ os.fsync(handle.fileno())
164
+ os.replace(temporary, materialized.main_tex)
165
+ except OSError as error:
166
+ raise TemplateAdapterError(f"cannot write adapted TeX file: {error}") from error
167
+ finally:
168
+ if temporary is not None:
169
+ temporary.unlink(missing_ok=True)
170
+ return materialized.main_tex
171
+
172
+
173
+ __all__ = ["MaterializedTemplate", "TemplateAdapter", "TemplateAdapterError"]